~ubuntu-branches/ubuntu/quantal/maas/quantal-updates

« back to all changes in this revision

Viewing changes to src/maasserver/management/commands/install_pxe_image.py

  • Committer: Package Import Robot
  • Author(s): Andres Rodriguez
  • Date: 2012-07-03 17:42:37 UTC
  • mfrom: (1.1.13)
  • Revision ID: package-import@ubuntu.com-20120703174237-p8l0keuuznfg721k
Tags: 0.1+bzr709+dfsg-0ubuntu1
* New Upstream release
* debian/control:
  - Depends on python-celery, python-tempita, libjs-yui3-{full,min},
    libjs-raphael
* debian/maas.install:
  - Install apiclient, celeryconfig.py, maas-import-pxe-files, preseeds_v2.
  - Update to install various files from chroot, rather tha manually copy
    them from the source.
* debian/maas.links: symlink celeryconfig.py
* debian/maas.maas-celery.upstart: Add job.
* debian/rules:
  - Install celery upstart job.
  - Do not install jslibs as packages are now used.
  - Drop copying of maas_local_settings_sample.py as source now ships
    a maas_local_settings.py
* debian/patches:
  - 04-maas-http-fix.patch: Drop. Merged upstream.
  - 01-fix-database-settings.patch: Refreshed.
  - 99_enums_js.patch: Added until creation of enum.js / build process
    is fixed.
* debian/maas.postinst: Update bzr version to correctly handle upgrades.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright 2012 Canonical Ltd.  This software is licensed under the
 
2
# GNU Affero General Public License version 3 (see the file LICENSE).
 
3
 
 
4
"""Install a netboot image directory for TFTP download."""
 
5
 
 
6
from __future__ import (
 
7
    absolute_import,
 
8
    print_function,
 
9
    unicode_literals,
 
10
    )
 
11
 
 
12
__metaclass__ = type
 
13
__all__ = [
 
14
    'Command',
 
15
    ]
 
16
 
 
17
from filecmp import cmpfiles
 
18
from optparse import make_option
 
19
import os.path
 
20
from shutil import (
 
21
    copytree,
 
22
    rmtree,
 
23
    )
 
24
 
 
25
from celeryconfig import TFTPROOT
 
26
from django.core.management.base import BaseCommand
 
27
from provisioningserver.pxe.tftppath import (
 
28
    compose_image_path,
 
29
    locate_tftp_path,
 
30
    )
 
31
 
 
32
 
 
33
def make_destination(tftproot, arch, subarch, release, purpose):
 
34
    """Locate an image's destination.  Create containing directory if needed.
 
35
 
 
36
    :param tftproot: The root directory served up by the TFTP server,
 
37
        e.g. /var/lib/tftpboot/.
 
38
    :param arch: Main architecture to locate the destination for.
 
39
    :param subarch: Sub-architecture of the main architecture.
 
40
    :param release: OS release name, e.g. "precise".
 
41
    :param purpose: Purpose of this image, e.g. "install".
 
42
    :return: Full path describing the image directory's new location and
 
43
        name.  In other words, a file "linux" in the image directory should
 
44
        become os.path.join(make_destination(...), 'linux').
 
45
    """
 
46
    dest = locate_tftp_path(
 
47
        compose_image_path(arch, subarch, release, purpose),
 
48
        tftproot=tftproot)
 
49
    parent = os.path.dirname(dest)
 
50
    if not os.path.isdir(parent):
 
51
        os.makedirs(parent)
 
52
    return dest
 
53
 
 
54
 
 
55
def are_identical_dirs(old, new):
 
56
    """Do directories `old` and `new` contain identical files?
 
57
 
 
58
    It's OK for `old` not to exist; that is considered a difference rather
 
59
    than an error.  But `new` is assumed to exist - if it doesn't, you
 
60
    shouldn't have come far enough to call this function.
 
61
    """
 
62
    assert os.path.isdir(new)
 
63
    if os.path.isdir(old):
 
64
        files = set(os.listdir(old) + os.listdir(new))
 
65
        # The shallow=False is needed to make cmpfiles() compare file
 
66
        # contents.  Otherwise it only compares os.stat() results,
 
67
        match, mismatch, errors = cmpfiles(old, new, files, shallow=False)
 
68
        return len(match) == len(files)
 
69
    else:
 
70
        return False
 
71
 
 
72
 
 
73
def install_dir(new, old):
 
74
    """Install directory `new`, replacing directory `old` if it exists.
 
75
 
 
76
    This works as atomically as possible, but isn't entirely.  Moreover,
 
77
    any TFTP downloads that are reading from the old directory during
 
78
    the move may receive inconsistent data, with some of the files (or
 
79
    parts of files!) coming from the old directory and some from the
 
80
    new.
 
81
 
 
82
    Some temporary paths will be used that are identical to `old`, but with
 
83
    suffixes ".old" or ".new".  If either of these directories already
 
84
    exists, it will be mercilessly deleted.
 
85
 
 
86
    This function makes no promises about whether it moves or copies
 
87
    `new` into place.  The caller should make an attempt to clean it up,
 
88
    but be prepared for it not being there.
 
89
    """
 
90
    # Get rid of any leftover temporary directories from potential
 
91
    # interrupted previous runs.
 
92
    rmtree('%s.old' % old, ignore_errors=True)
 
93
    rmtree('%s.new' % old, ignore_errors=True)
 
94
 
 
95
    # We have to move the existing directory out of the way and the new
 
96
    # one into place.  Between those steps, there is a window where
 
97
    # neither is in place.  To minimize that window, move the new one
 
98
    # into the same location (ensuring that it no longer needs copying
 
99
    # from one partition to another) and then swizzle the two as quickly
 
100
    # as possible.
 
101
    # This could be a simple "remove" if the downloaded image is on the
 
102
    # same filesystem as the destination, but because that isn't
 
103
    # certain, copy instead.  It's not particularly fast, but the extra
 
104
    # work happens outside the critical window so it shouldn't matter
 
105
    # much.
 
106
    copytree(new, '%s.new' % old)
 
107
 
 
108
    # Start of critical window.
 
109
    if os.path.isdir(old):
 
110
        os.rename(old, '%s.old' % old)
 
111
    os.rename('%s.new' % old, old)
 
112
    # End of critical window.
 
113
 
 
114
    # Now delete the old image directory at leisure.
 
115
    rmtree('%s.old' % old, ignore_errors=True)
 
116
 
 
117
 
 
118
class Command(BaseCommand):
 
119
    """Move a netboot image into the TFTP directory structure.
 
120
 
 
121
    The image is a directory containing a kernel and an initrd.  If the
 
122
    destination location already has an image of the same name and
 
123
    containing identical files, the new image is deleted and the old one
 
124
    is left untouched.
 
125
    """
 
126
 
 
127
    option_list = BaseCommand.option_list + (
 
128
        make_option(
 
129
            '--arch', dest='arch', default=None,
 
130
            help="Main system architecture that the image is for."),
 
131
        make_option(
 
132
            '--subarch', dest='subarch', default='generic',
 
133
            help="Sub-architecture of the main architecture."),
 
134
        make_option(
 
135
            '--release', dest='release', default=None,
 
136
            help="Ubuntu release that the image is for."),
 
137
        make_option(
 
138
            '--purpose', dest='purpose', default=None,
 
139
            help="Purpose of the image (e.g. 'install' or 'commissioning')."),
 
140
        make_option(
 
141
            '--image', dest='image', default=None,
 
142
            help="Netboot image directory, containing kernel & initrd."),
 
143
        make_option(
 
144
            '--tftproot', dest='tftproot', default=TFTPROOT,
 
145
            help="Store to this TFTP directory tree instead of the default."),
 
146
        )
 
147
 
 
148
    def handle(self, arch=None, subarch=None, release=None, purpose=None,
 
149
               image=None, tftproot=None, **kwargs):
 
150
        if tftproot is None:
 
151
            tftproot = TFTPROOT
 
152
 
 
153
        dest = make_destination(tftproot, arch, subarch, release, purpose)
 
154
        if not are_identical_dirs(dest, image):
 
155
            # Image has changed.  Move the new version into place.
 
156
            install_dir(image, dest)
 
157
        rmtree(image, ignore_errors=True)