~ubuntu-branches/ubuntu/quantal/nova/quantal-proposed

« back to all changes in this revision

Viewing changes to plugins/xenserver/xenapi/etc/xapi.d/plugins/utils.py

  • Committer: Package Import Robot
  • Author(s): Chuck Short
  • Date: 2012-08-16 14:04:11 UTC
  • mto: This revision was merged to the branch mainline in revision 84.
  • Revision ID: package-import@ubuntu.com-20120816140411-0mr4n241wmk30t9l
Tags: upstream-2012.2~f3
ImportĀ upstreamĀ versionĀ 2012.2~f3

Show diffs side-by-side

added added

removed removed

Lines of Context:
25
25
CHUNK_SIZE = 8192
26
26
 
27
27
 
 
28
def _link(src, dst):
 
29
    logging.info("Hard-linking file '%s' -> '%s'" % (src, dst))
 
30
    os.link(src, dst)
 
31
 
 
32
 
 
33
def _rename(src, dst):
 
34
    logging.info("Renaming file '%s' -> '%s'" % (src, dst))
 
35
    os.rename(src, dst)
 
36
 
 
37
 
28
38
def make_subprocess(cmdline, stdout=False, stderr=False, stdin=False):
29
39
    """Make a subprocess according to the given command-line string
30
40
    """
 
41
    # NOTE(dprince): shlex python 2.4 doesn't like unicode so we
 
42
    # explicitly convert to ascii
 
43
    cmdline = cmdline.encode('ascii')
31
44
    logging.info("Running cmd '%s'" % cmdline)
32
45
    kwargs = {}
33
46
    kwargs['stdout'] = stdout and subprocess.PIPE or None
102
115
        shutil.rmtree(staging_path)
103
116
 
104
117
 
 
118
def _handle_old_style_images(staging_path):
 
119
    """Rename files to conform to new image format, if needed.
 
120
 
 
121
    Old-Style:
 
122
 
 
123
        snap.vhd -> image.vhd -> base.vhd
 
124
 
 
125
    New-Style:
 
126
 
 
127
        0.vhd -> 1.vhd -> ... (n-1).vhd
 
128
 
 
129
    The New-Style format has the benefit of being able to support a VDI chain
 
130
    of arbitrary length.
 
131
    """
 
132
    file_num = 0
 
133
    for filename in ('snap.vhd', 'image.vhd', 'base.vhd'):
 
134
        path = os.path.join(staging_path, filename)
 
135
        if os.path.exists(path):
 
136
            _rename(path, os.path.join(staging_path, "%d.vhd" % file_num))
 
137
            file_num += 1
 
138
 
 
139
 
 
140
def _assert_vhd_not_hidden(path):
 
141
    """Sanity check to ensure that only appropriate VHDs are marked as hidden.
 
142
 
 
143
    If this flag is incorrectly set, then when we move the VHD into the SR, it
 
144
    will be deleted out from under us.
 
145
    """
 
146
    query_cmd = "vhd-util query -n %(path)s -f" % locals()
 
147
    query_proc = make_subprocess(query_cmd, stdout=True, stderr=True)
 
148
    out, err = finish_subprocess(query_proc, query_cmd)
 
149
 
 
150
    for line in out.splitlines():
 
151
        if line.startswith('hidden'):
 
152
            value = line.split(':')[1].strip()
 
153
            if value == "1":
 
154
                raise Exception(
 
155
                    "VHD %(path)s is marked as hidden without child" %
 
156
                    locals())
 
157
 
 
158
 
 
159
def _validate_footer_timestamp(vdi_path):
 
160
    """
 
161
    This check ensures that the timestamps listed in the VHD footer aren't in
 
162
    the future.  This can occur during a migration if the clocks on the the two
 
163
    Dom0's are out-of-sync. This would corrupt the SR if it were imported, so
 
164
    generate an exception to bail.
 
165
    """
 
166
    check_cmd = "vhd-util check -n %(vdi_path)s -p" % locals()
 
167
    check_proc = make_subprocess(check_cmd, stdout=True, stderr=True)
 
168
    out, err = finish_subprocess(
 
169
            check_proc, check_cmd, ok_exit_codes=[0, 22])
 
170
    first_line = out.splitlines()[0].strip()
 
171
 
 
172
    if 'primary footer invalid' in first_line:
 
173
        raise Exception("VDI '%(vdi_path)s' has timestamp in the future,"
 
174
                        " ensure source and destination host machines have"
 
175
                        " time set correctly" % locals())
 
176
    elif check_proc.returncode != 0:
 
177
        raise Exception("Unexpected output '%(out)s' from vhd-util" %
 
178
                        locals())
 
179
 
 
180
 
 
181
def _validate_vdi_chain(vdi_path):
 
182
    """
 
183
    This check ensures that the parent pointers on the VHDs are valid
 
184
    before we move the VDI chain to the SR. This is *very* important
 
185
    because a bad parent pointer will corrupt the SR causing a cascade of
 
186
    failures.
 
187
    """
 
188
    def get_parent_path(path):
 
189
        query_cmd = "vhd-util query -n %(path)s -p" % locals()
 
190
        query_proc = make_subprocess(query_cmd, stdout=True, stderr=True)
 
191
        out, err = finish_subprocess(
 
192
                query_proc, query_cmd, ok_exit_codes=[0, 22])
 
193
        first_line = out.splitlines()[0].strip()
 
194
 
 
195
        if first_line.endswith(".vhd"):
 
196
            return first_line
 
197
        elif 'has no parent' in first_line:
 
198
            return None
 
199
        elif 'query failed' in first_line:
 
200
            raise Exception("VDI '%(path)s' not present which breaks"
 
201
                            " the VDI chain, bailing out" % locals())
 
202
        else:
 
203
            raise Exception("Unexpected output '%(out)s' from vhd-util" %
 
204
                            locals())
 
205
 
 
206
    cur_path = vdi_path
 
207
    while cur_path:
 
208
        _validate_footer_timestamp(cur_path)
 
209
        cur_path = get_parent_path(cur_path)
 
210
 
 
211
 
 
212
def _validate_sequenced_vhds(staging_path):
 
213
    """This check ensures that the VHDs in the staging area are sequenced
 
214
    properly from 0 to n-1 with no gaps.
 
215
    """
 
216
    seq_num = 0
 
217
    filenames = os.listdir(staging_path)
 
218
    for filename in filenames:
 
219
        if not filename.endswith('.vhd'):
 
220
            continue
 
221
 
 
222
        if filename == "swap.vhd":
 
223
            continue
 
224
 
 
225
        vhd_path = os.path.join(staging_path, "%d.vhd" % seq_num)
 
226
        if not os.path.exists(vhd_path):
 
227
            raise Exception("Corrupt image. Expected seq number: %d. Files: %s"
 
228
                            % (seq_num, filenames))
 
229
 
 
230
        seq_num += 1
 
231
 
 
232
 
105
233
def import_vhds(sr_path, staging_path, uuid_stack):
106
 
    """Import the VHDs found in the staging path.
107
 
 
108
 
    We cannot extract VHDs directly into the SR since they don't yet have
109
 
    UUIDs, aren't properly associated with each other, and would be subject to
110
 
    a race-condition of one-file being present and the other not being
111
 
    downloaded yet.
112
 
 
113
 
    To avoid these we problems, we use a staging area to fixup the VHDs before
114
 
    moving them into the SR. The steps involved are:
115
 
 
116
 
        1. Extracting tarball into staging area (done prior to this call)
117
 
 
118
 
        2. Renaming VHDs to use UUIDs ('snap.vhd' -> 'ffff-aaaa-...vhd')
119
 
 
120
 
        3. Linking VHDs together if there's a snap.vhd
121
 
 
122
 
        4. Pseudo-atomically moving the images into the SR. (It's not really
123
 
           atomic because it takes place as multiple os.rename operations;
124
 
           however, the chances of an SR.scan occuring between the rename()s
125
 
           invocations is so small that we can safely ignore it)
126
 
 
127
 
    Returns: A dict of the VDIs imported. For example:
 
234
    """Move VHDs from staging area into the SR.
 
235
 
 
236
    The staging area is necessary because we need to perform some fixups
 
237
    (assigning UUIDs, relinking the VHD chain) before moving into the SR,
 
238
    otherwise the SR manager process could potentially delete the VHDs out from
 
239
    under us.
 
240
 
 
241
    Returns: A dict of imported VHDs:
128
242
 
129
243
        {'root': {'uuid': 'ffff-aaaa'},
130
244
         'swap': {'uuid': 'ffff-bbbb'}}
131
245
    """
132
 
    def rename_with_uuid(orig_path):
133
 
        """Rename VHD using UUID so that it will be recognized by SR on a
134
 
        subsequent scan.
135
 
 
136
 
        Since Python2.4 doesn't have the `uuid` module, we pass a stack of
137
 
        pre-computed UUIDs from the compute worker.
138
 
        """
139
 
        orig_dirname = os.path.dirname(orig_path)
140
 
        uuid = uuid_stack.pop()
141
 
        new_path = os.path.join(orig_dirname, "%s.vhd" % uuid)
142
 
        os.rename(orig_path, new_path)
143
 
        return new_path, uuid
144
 
 
145
 
    def link_vhds(child_path, parent_path):
146
 
        """Use vhd-util to associate the snapshot VHD with its base_copy.
147
 
 
148
 
        This needs to be done before we move both VHDs into the SR to prevent
149
 
        the base_copy from being DOA (deleted-on-arrival).
150
 
        """
151
 
        modify_cmd = ("vhd-util modify -n %(child_path)s -p %(parent_path)s"
152
 
                      % locals())
153
 
        modify_proc = make_subprocess(modify_cmd, stderr=True)
154
 
        finish_subprocess(modify_proc, modify_cmd)
155
 
 
156
 
    def move_into_sr(orig_path):
157
 
        """Move a file into the SR"""
158
 
        filename = os.path.basename(orig_path)
159
 
        new_path = os.path.join(sr_path, filename)
160
 
        os.rename(orig_path, new_path)
161
 
        return new_path
162
 
 
163
 
    def assert_vhd_not_hidden(path):
164
 
        """
165
 
        This is a sanity check on the image; if a snap.vhd isn't
166
 
        present, then the image.vhd better not be marked 'hidden' or it will
167
 
        be deleted when moved into the SR.
168
 
        """
169
 
        query_cmd = "vhd-util query -n %(path)s -f" % locals()
170
 
        query_proc = make_subprocess(query_cmd, stdout=True, stderr=True)
171
 
        out, err = finish_subprocess(query_proc, query_cmd)
172
 
 
173
 
        for line in out.splitlines():
174
 
            if line.startswith('hidden'):
175
 
                value = line.split(':')[1].strip()
176
 
                if value == "1":
177
 
                    raise Exception(
178
 
                        "VHD %(path)s is marked as hidden without child" %
179
 
                        locals())
180
 
 
181
 
    def prepare_if_exists(staging_path, vhd_name, parent_path=None):
182
 
        """
183
 
        Check for existance of a particular VHD in the staging path and
184
 
        preparing it for moving into the SR.
185
 
 
186
 
        Returns: Tuple of (Path to move into the SR, VDI_UUID)
187
 
                 None, if the vhd_name doesn't exist in the staging path
188
 
 
189
 
        If the VHD exists, we will do the following:
190
 
            1. Rename it with a UUID.
191
 
            2. If parent_path exists, we'll link up the VHDs.
192
 
        """
193
 
        orig_path = os.path.join(staging_path, vhd_name)
194
 
        if not os.path.exists(orig_path):
195
 
            return None
196
 
        new_path, vdi_uuid = rename_with_uuid(orig_path)
 
246
    _handle_old_style_images(staging_path)
 
247
    _validate_sequenced_vhds(staging_path)
 
248
 
 
249
    imported_vhds = {}
 
250
    files_to_move = []
 
251
 
 
252
    # Collect sequenced VHDs and assign UUIDs to them
 
253
    seq_num = 0
 
254
    while True:
 
255
        orig_vhd_path = os.path.join(staging_path, "%d.vhd" % seq_num)
 
256
        if not os.path.exists(orig_vhd_path):
 
257
            break
 
258
 
 
259
        # Rename (0, 1 .. N).vhd -> aaaa-bbbb-cccc-dddd.vhd
 
260
        vhd_uuid = uuid_stack.pop()
 
261
        vhd_path = os.path.join(staging_path, "%s.vhd" % vhd_uuid)
 
262
        _rename(orig_vhd_path, vhd_path)
 
263
 
 
264
        if seq_num == 0:
 
265
            leaf_vhd_path = vhd_path
 
266
            leaf_vhd_uuid = vhd_uuid
 
267
 
 
268
        files_to_move.append(vhd_path)
 
269
        seq_num += 1
 
270
 
 
271
    # Re-link VHDs, in reverse order, from base-copy -> leaf
 
272
    parent_path = None
 
273
    for vhd_path in reversed(files_to_move):
197
274
        if parent_path:
198
 
            # NOTE(sirp): this step is necessary so that an SR scan won't
199
 
            # delete the base_copy out from under us (since it would be
200
 
            # orphaned)
201
 
            link_vhds(new_path, parent_path)
202
 
        return (new_path, vdi_uuid)
203
 
 
204
 
    def validate_vdi_chain(vdi_path):
205
 
        """
206
 
        This check ensures that the parent pointers on the VHDs are valid
207
 
        before we move the VDI chain to the SR. This is *very* important
208
 
        because a bad parent pointer will corrupt the SR causing a cascade of
209
 
        failures.
210
 
        """
211
 
        def get_parent_path(path):
212
 
            query_cmd = "vhd-util query -n %(path)s -p" % locals()
213
 
            query_proc = make_subprocess(query_cmd, stdout=True, stderr=True)
214
 
            out, err = finish_subprocess(
215
 
                    query_proc, query_cmd, ok_exit_codes=[0, 22])
216
 
            first_line = out.splitlines()[0].strip()
217
 
            if first_line.endswith(".vhd"):
218
 
                return first_line
219
 
            elif 'has no parent' in first_line:
220
 
                return None
221
 
            elif 'query failed' in first_line:
222
 
                raise Exception("VDI '%(path)s' not present which breaks"
223
 
                                " the VDI chain, bailing out" % locals())
224
 
            else:
225
 
                raise Exception("Unexpected output '%(out)s' from vhd-util" %
226
 
                                locals())
227
 
 
228
 
        cur_path = vdi_path
229
 
        while cur_path:
230
 
            cur_path = get_parent_path(cur_path)
231
 
 
232
 
    imported_vhds = {}
233
 
    paths_to_move = []
234
 
 
235
 
    image_parent = None
236
 
    base_info = prepare_if_exists(staging_path, 'base.vhd')
237
 
    if base_info:
238
 
        paths_to_move.append(base_info[0])
239
 
        image_parent = base_info[0]
240
 
 
241
 
    image_info = prepare_if_exists(staging_path, 'image.vhd', image_parent)
242
 
    if not image_info:
243
 
        raise Exception("Invalid image: image.vhd not present")
244
 
 
245
 
    paths_to_move.insert(0, image_info[0])
246
 
 
247
 
    snap_info = prepare_if_exists(staging_path, 'snap.vhd',
248
 
            image_info[0])
249
 
    if snap_info:
250
 
        validate_vdi_chain(snap_info[0])
251
 
        # NOTE(sirp): this is an insert rather than an append since the
252
 
        # 'snapshot' vhd needs to be copied into the SR before the base copy.
253
 
        # If it doesn't, then there is a possibliity that snapwatchd will
254
 
        # delete the base_copy since it is an unreferenced parent.
255
 
        paths_to_move.insert(0, snap_info[0])
256
 
        # We return this snap as the VDI instead of image.vhd
257
 
        imported_vhds['root'] = dict(uuid=snap_info[1])
258
 
    else:
259
 
        validate_vdi_chain(image_info[0])
260
 
        assert_vhd_not_hidden(image_info[0])
261
 
        # If there's no snap, we return the image.vhd UUID
262
 
        imported_vhds['root'] = dict(uuid=image_info[1])
263
 
 
264
 
    swap_info = prepare_if_exists(staging_path, 'swap.vhd')
265
 
    if swap_info:
266
 
        assert_vhd_not_hidden(swap_info[0])
267
 
        paths_to_move.append(swap_info[0])
268
 
        imported_vhds['swap'] = dict(uuid=swap_info[1])
269
 
 
270
 
    for path in paths_to_move:
271
 
        move_into_sr(path)
 
275
            # Link to parent
 
276
            modify_cmd = ("vhd-util modify -n %(vhd_path)s"
 
277
                          " -p %(parent_path)s" % locals())
 
278
            modify_proc = make_subprocess(modify_cmd, stderr=True)
 
279
            finish_subprocess(modify_proc, modify_cmd)
 
280
 
 
281
        parent_path = vhd_path
 
282
 
 
283
    # Sanity check the leaf VHD
 
284
    _assert_vhd_not_hidden(leaf_vhd_path)
 
285
    _validate_vdi_chain(leaf_vhd_path)
 
286
 
 
287
    imported_vhds["root"] = {"uuid": leaf_vhd_uuid}
 
288
 
 
289
    # Handle swap file if present
 
290
    orig_swap_path = os.path.join(staging_path, "swap.vhd")
 
291
    if os.path.exists(orig_swap_path):
 
292
        # Rename swap.vhd -> aaaa-bbbb-cccc-dddd.vhd
 
293
        vhd_uuid = uuid_stack.pop()
 
294
        swap_path = os.path.join(staging_path, "%s.vhd" % vhd_uuid)
 
295
        _rename(orig_swap_path, swap_path)
 
296
 
 
297
        _assert_vhd_not_hidden(swap_path)
 
298
 
 
299
        imported_vhds["swap"] = {"uuid": vhd_uuid}
 
300
        files_to_move.append(swap_path)
 
301
 
 
302
    # Move files into SR
 
303
    for orig_path in files_to_move:
 
304
        new_path = os.path.join(sr_path, os.path.basename(orig_path))
 
305
        _rename(orig_path, new_path)
272
306
 
273
307
    return imported_vhds
274
308
 
275
309
 
276
 
def prepare_staging_area_for_upload(sr_path, staging_path, vdi_uuids):
277
 
    """Hard-link VHDs into staging area with appropriate filename
278
 
    ('snap' or 'image.vhd')
279
 
    """
280
 
    for name, uuid in vdi_uuids.items():
281
 
        if uuid:
282
 
            source = os.path.join(sr_path, "%s.vhd" % uuid)
283
 
            link_name = os.path.join(staging_path, "%s.vhd" % name)
284
 
            os.link(source, link_name)
 
310
def prepare_staging_area(sr_path, staging_path, vdi_uuids, seq_num=0):
 
311
    """Hard-link VHDs into staging area."""
 
312
    for vdi_uuid in vdi_uuids:
 
313
        source = os.path.join(sr_path, "%s.vhd" % vdi_uuid)
 
314
        link_name = os.path.join(staging_path, "%d.vhd" % seq_num)
 
315
        _link(source, link_name)
 
316
        seq_num += 1
285
317
 
286
318
 
287
319
def create_tarball(fileobj, path, callback=None):