~smoser/ubuntu/wily/maas/lp1474417

« back to all changes in this revision

Viewing changes to .pc/99-fix-new-image-install-lp1182646.patch/src/provisioningserver/pxe/install_image.py

  • Committer: Package Import Robot
  • Author(s): Andres Rodriguez
  • Date: 2013-04-23 14:02:33 UTC
  • Revision ID: package-import@ubuntu.com-20130423140233-358whmd81xc0xms0
Tags: 1.3+bzr1461+dfsg-0ubuntu3
* debian/patches:
  - 99-fix-ipmi-stat-lp1086160.patch: Drop. The following patch removes
    the need for this fix. (LP: #1171988)
  - 99-fix-ipmi-lp1171418.patch: Do not check current node state when
    executing an ipmi command, which ensures that nodes are always
    turned on/off regardless of their power state. This fixes corner
    cases found when running automated tests. (LP: #1171418)
  - 99-fix-comissioning-lp1131418.patch: Fixes the commissioning process,
    allowing nodes to successfully commission, when tag's with no
    definition have been created. This issue will only appear when these
    special tags are created. (LP: #1131418)
  - 99-import-raring-images-lp1182642.patch: Enables the import of raring
    images by default (LP: #1182642)
  - 99-fix-new-image-install-lp1182646.patch: Fixes the installation of
    new ephemeral images, that fail due to not being able to overwrite
    a symlink. (LP: #1182646)

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
    "add_arguments",
 
15
    "run",
 
16
    ]
 
17
 
 
18
from filecmp import cmpfiles
 
19
import os.path
 
20
from shutil import (
 
21
    copytree,
 
22
    rmtree,
 
23
    )
 
24
 
 
25
from provisioningserver.config import Config
 
26
from provisioningserver.pxe.tftppath import (
 
27
    compose_image_path,
 
28
    locate_tftp_path,
 
29
    )
 
30
from twisted.python.filepath import FilePath
 
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/maas/tftp/.
 
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, symlink=None):
 
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
    # Normalise permissions.
 
109
    for filepath in FilePath('%s.new' % old).walk():
 
110
        if filepath.isdir():
 
111
            filepath.chmod(0755)
 
112
        else:
 
113
            filepath.chmod(0644)
 
114
 
 
115
    # Start of critical window.
 
116
    if os.path.isdir(old):
 
117
        os.rename(old, '%s.old' % old)
 
118
    os.rename('%s.new' % old, old)
 
119
    # End of critical window.
 
120
 
 
121
    # Now delete the old image directory at leisure.
 
122
    rmtree('%s.old' % old, ignore_errors=True)
 
123
 
 
124
    # Symlink the new image directory to 'symlink'.
 
125
    if symlink is not None:
 
126
        sdest = "%s/%s" % (os.path.dirname(old), symlink)
 
127
        os.symlink(old, sdest)
 
128
 
 
129
 
 
130
def add_arguments(parser):
 
131
    parser.add_argument(
 
132
        '--arch', dest='arch', default=None,
 
133
        help="Main system architecture that the image is for.")
 
134
    parser.add_argument(
 
135
        '--subarch', dest='subarch', default='generic',
 
136
        help="Sub-architecture of the main architecture [%(default)s].")
 
137
    parser.add_argument(
 
138
        '--release', dest='release', default=None,
 
139
        help="Ubuntu release that the image is for.")
 
140
    parser.add_argument(
 
141
        '--purpose', dest='purpose', default=None,
 
142
        help="Purpose of the image (e.g. 'install' or 'commissioning').")
 
143
    parser.add_argument(
 
144
        '--image', dest='image', default=None,
 
145
        help="Netboot image directory, containing kernel & initrd.")
 
146
    parser.add_argument(
 
147
        '--symlink', dest='symlink', default=None,
 
148
        help="Destination directory to symlink the installed images to.")
 
149
 
 
150
 
 
151
def run(args):
 
152
    """Move a netboot image into the TFTP directory structure.
 
153
 
 
154
    The image is a directory containing a kernel and an initrd.  If the
 
155
    destination location already has an image of the same name and
 
156
    containing identical files, the new image is deleted and the old one
 
157
    is left untouched.
 
158
    """
 
159
    config = Config.load(args.config_file)
 
160
    tftproot = config["tftp"]["root"]
 
161
    destination = make_destination(
 
162
        tftproot, args.arch, args.subarch, args.release, args.purpose)
 
163
    if not are_identical_dirs(destination, args.image):
 
164
        # Image has changed.  Move the new version into place.
 
165
        install_dir(args.image, destination, args.symlink)
 
166
    rmtree(args.image, ignore_errors=True)