Coverage for drivers/VDI.py : 70%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# Copyright (C) Citrix Systems Inc.
2#
3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU Lesser General Public License as published
5# by the Free Software Foundation; version 2.1 only.
6#
7# This program is distributed in the hope that it will be useful,
8# but WITHOUT ANY WARRANTY; without even the implied warranty of
9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10# GNU Lesser General Public License for more details.
11#
12# You should have received a copy of the GNU Lesser General Public License
13# along with this program; if not, write to the Free Software Foundation, Inc.,
14# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15#
16# VDI: Base class for virtual disk instances
17#
19import SR
20import xmlrpc.client
21import xs_errors
22import util
23import vhdutil
24import cbtutil
25import os
26import base64
27from constants import CBTLOG_TAG
28from bitarray import bitarray
29import uuid
31SM_CONFIG_PASS_THROUGH_FIELDS = ["base_mirror", "key_hash"]
33SNAPSHOT_SINGLE = 1 # true snapshot: 1 leaf, 1 read-only parent
34SNAPSHOT_DOUBLE = 2 # regular snapshot/clone that creates 2 leaves
35SNAPSHOT_INTERNAL = 3 # SNAPSHOT_SINGLE but don't update SR's virtual allocation
36CBT_BLOCK_SIZE = (64 * 1024)
39def VDIMetadataSize(type, virtualsize):
40 size = 0
41 if type == 'vhd':
42 size_mb = virtualsize // (1024 * 1024)
43 #Footer + footer copy + header + possible CoW parent locator fields
44 size = 3 * 1024
46 # BAT 4 Bytes per block segment
47 size += (size_mb // 2) * 4
48 size = util.roundup(512, size)
50 # BATMAP 1 bit per block segment
51 size += (size_mb // 2) // 8
52 size = util.roundup(4096, size)
54 # Segment bitmaps + Page align offsets
55 size += (size_mb // 2) * 4096
57 return size
60class VDI(object):
61 """Virtual Disk Instance descriptor.
63 Attributes:
64 uuid: string, globally unique VDI identifier conforming to OSF DEC 1.1
65 label: string, user-generated tag string for identifyng the VDI
66 description: string, longer user generated description string
67 size: int, virtual size in bytes of this VDI
68 utilisation: int, actual size in Bytes of data on disk that is
69 utilised. For non-sparse disks, utilisation == size
70 vdi_type: string, disk type, e.g. raw file, partition
71 parent: VDI object, parent backing VDI if this disk is a
72 CoW instance
73 shareable: boolean, does this disk support multiple writer instances?
74 e.g. shared OCFS disk
75 attached: boolean, whether VDI is attached
76 read_only: boolean, whether disk is read-only.
77 """
79 def __init__(self, sr, uuid):
80 self.sr = sr
81 # Don't set either the UUID or location to None- no good can
82 # ever come of this.
83 if uuid is not None:
84 self.uuid = uuid
85 self.location = uuid
86 self.path = None
87 else:
88 # We assume that children class initializors calling without
89 # uuid will set these attributes themselves somewhere. They
90 # are VDIs whose physical paths/locations have no direct
91 # connections with their UUID strings (e.g. ISOSR, udevSR,
92 # SHMSR). So we avoid overwriting these attributes here.
93 pass
94 # deliberately not initialised self.sm_config so that it is
95 # ommitted from the XML output
97 self.label = ''
98 self.description = ''
99 self.vbds = []
100 self.size = 0
101 self.utilisation = 0
102 self.vdi_type = ''
103 self.has_child = 0
104 self.parent = None
105 self.shareable = False
106 self.attached = False
107 self.status = 0
108 self.read_only = False
109 self.xenstore_data = {}
110 self.deleted = False
111 self.session = sr.session
112 self.managed = True
113 self.sm_config_override = {}
114 self.sm_config_keep = ["key_hash"]
115 self.ty = "user"
116 self.cbt_enabled = False
118 self.load(uuid)
120 @staticmethod
121 def from_uuid(session, vdi_uuid):
123 _VDI = session.xenapi.VDI
124 vdi_ref = _VDI.get_by_uuid(vdi_uuid)
125 sr_ref = _VDI.get_SR(vdi_ref)
127 _SR = session.xenapi.SR
128 sr_uuid = _SR.get_uuid(sr_ref)
130 sr = SR.SR.from_uuid(session, sr_uuid)
132 sr.srcmd.params['vdi_ref'] = vdi_ref
133 return sr.vdi(vdi_uuid)
135 def create(self, sr_uuid, vdi_uuid, size):
136 """Create a VDI of size <Size> MB on the given SR.
138 This operation IS NOT idempotent and will fail if the UUID
139 already exists or if there is insufficient space. The vdi must
140 be explicitly attached via the attach() command following
141 creation. The actual disk size created may be larger than the
142 requested size if the substrate requires a size in multiples
143 of a certain extent size. The SR must be queried for the exact
144 size.
145 """
146 raise xs_errors.XenError('Unimplemented')
148 def update(self, sr_uuid, vdi_uuid):
149 """Query and update the configuration of a particular VDI.
151 Given an SR and VDI UUID, this operation returns summary statistics
152 on the named VDI. Note the XenAPI VDI object will exist when
153 this call is made.
154 """
155 # no-op unless individual backends implement it
156 return
158 def introduce(self, sr_uuid, vdi_uuid):
159 """Explicitly introduce a particular VDI.
161 Given an SR and VDI UUID and a disk location (passed in via the <conf>
162 XML), this operation verifies the existence of the underylying disk
163 object and then creates the XenAPI VDI object.
164 """
165 raise xs_errors.XenError('Unimplemented')
167 def attach(self, sr_uuid, vdi_uuid):
168 """Initiate local access to the VDI. Initialises any device
169 state required to access the VDI.
171 This operation IS idempotent and should succeed if the VDI can be
172 attached or if the VDI is already attached.
174 Returns:
175 string, local device path.
176 """
177 struct = {'params': self.path,
178 'xenstore_data': (self.xenstore_data or {})}
179 return xmlrpc.client.dumps((struct, ), "", True)
181 def detach(self, sr_uuid, vdi_uuid):
182 """Remove local access to the VDI. Destroys any device
183 state initialised via the vdi.attach() command.
185 This operation is idempotent.
186 """
187 raise xs_errors.XenError('Unimplemented')
189 def clone(self, sr_uuid, vdi_uuid):
190 """Create a mutable instance of the referenced VDI.
192 This operation is not idempotent and will fail if the UUID
193 already exists or if there is insufficient space. The SRC VDI
194 must be in a detached state and deactivated. Upon successful
195 creation of the clone, the clone VDI must be explicitly
196 attached via vdi.attach(). If the driver does not support
197 cloning this operation should raise SRUnsupportedOperation.
199 Arguments:
200 Raises:
201 SRUnsupportedOperation
202 """
203 raise xs_errors.XenError('Unimplemented')
205 def resize_online(self, sr_uuid, vdi_uuid, size):
206 """Resize the given VDI which may have active VBDs, which have
207 been paused for the duration of this call."""
208 raise xs_errors.XenError('Unimplemented')
210 def generate_config(self, sr_uuid, vdi_uuid):
211 """Generate the XML config required to activate a VDI for use
212 when XAPI is not running. Activation is handled by the
213 vdi_attach_from_config() SMAPI call.
214 """
215 raise xs_errors.XenError('Unimplemented')
217 def compose(self, sr_uuid, vdi1, vdi2):
218 """Layer the updates from [vdi2] onto [vdi1], calling the result
219 [vdi2].
221 Raises:
222 SRUnsupportedOperation
223 """
224 raise xs_errors.XenError('Unimplemented')
226 def attach_from_config(self, sr_uuid, vdi_uuid):
227 """Activate a VDI based on the config passed in on the CLI. For
228 use when XAPI is not running. The config is generated by the
229 Activation is handled by the vdi_generate_config() SMAPI call.
230 """
231 raise xs_errors.XenError('Unimplemented')
233 def _do_snapshot(self, sr_uuid, vdi_uuid, snapType,
234 cloneOp=False, secondary=None, cbtlog=None):
235 raise xs_errors.XenError('Unimplemented')
237 def _delete_cbt_log(self):
238 raise xs_errors.XenError('Unimplemented')
240 def _rename(self, old, new):
241 raise xs_errors.XenError('Unimplemented')
243 def _cbt_log_exists(self, logpath):
244 """Check if CBT log file exists
246 Must be implemented by all classes inheriting from base VDI class
247 """
248 raise xs_errors.XenError('Unimplemented')
250 def resize(self, sr_uuid, vdi_uuid, size):
251 """Resize the given VDI to size <size> MB. Size can
252 be any valid disk size greater than [or smaller than]
253 the current value.
255 This operation IS idempotent and should succeed if the VDI can
256 be resized to the specified value or if the VDI is already the
257 specified size. The actual disk size created may be larger
258 than the requested size if the substrate requires a size in
259 multiples of a certain extent size. The SR must be queried for
260 the exact size. This operation does not modify the contents on
261 the disk such as the filesystem. Responsibility for resizing
262 the FS is left to the VM administrator. [Reducing the size of
263 the disk is a very dangerous operation and should be conducted
264 very carefully.] Disk contents should always be backed up in
265 advance.
266 """
267 raise xs_errors.XenError('Unimplemented')
269 def resize_cbt(self, sr_uuid, vdi_uuid, size):
270 """Resize the given VDI to size <size> MB. Size can
271 be any valid disk size greater than [or smaller than]
272 the current value.
274 This operation IS idempotent and should succeed if the VDI can
275 be resized to the specified value or if the VDI is already the
276 specified size. The actual disk size created may be larger
277 than the requested size if the substrate requires a size in
278 multiples of a certain extent size. The SR must be queried for
279 the exact size. This operation does not modify the contents on
280 the disk such as the filesystem. Responsibility for resizing
281 the FS is left to the VM administrator. [Reducing the size of
282 the disk is a very dangerous operation and should be conducted
283 very carefully.] Disk contents should always be backed up in
284 advance.
285 """
286 try:
287 if self._get_blocktracking_status():
288 logpath = self._get_cbt_logpath(vdi_uuid)
289 self._cbt_op(vdi_uuid, cbtutil.set_cbt_size, logpath, size)
290 except util.CommandException as ex:
291 alert_name = "VDI_CBT_RESIZE_FAILED"
292 alert_str = ("Resizing of CBT metadata for disk %s failed."
293 % vdi_uuid)
294 self._disable_cbt_on_error(alert_name, alert_str)
296 def delete(self, sr_uuid, vdi_uuid, data_only=False):
297 """Delete this VDI.
299 This operation IS idempotent and should succeed if the VDI
300 exists and can be deleted or if the VDI does not exist. It is
301 the responsibility of the higher-level management tool to
302 ensure that the detach() operation has been explicitly called
303 prior to deletion, otherwise the delete() will fail if the
304 disk is still attached.
305 """
306 import blktap2
307 from lock import Lock
309 if data_only == False and self._get_blocktracking_status():
310 logpath = self._get_cbt_logpath(vdi_uuid)
311 parent_uuid = self._cbt_op(vdi_uuid, cbtutil.get_cbt_parent,
312 logpath)
313 parent_path = self._get_cbt_logpath(parent_uuid)
314 child_uuid = self._cbt_op(vdi_uuid, cbtutil.get_cbt_child, logpath)
315 child_path = self._get_cbt_logpath(child_uuid)
317 lock = Lock("cbtlog", str(vdi_uuid))
319 if self._cbt_log_exists(parent_path): 319 ↛ 323line 319 didn't jump to line 323, because the condition on line 319 was never false
320 self._cbt_op(parent_uuid, cbtutil.set_cbt_child,
321 parent_path, child_uuid)
323 if self._cbt_log_exists(child_path):
324 self._cbt_op(child_uuid, cbtutil.set_cbt_parent,
325 child_path, parent_uuid)
326 lock.acquire()
327 try:
328 # Coalesce contents of bitmap with child's bitmap
329 # Check if child bitmap is currently attached
330 paused_for_coalesce = False
331 consistent = self._cbt_op(child_uuid,
332 cbtutil.get_cbt_consistency,
333 child_path)
334 if not consistent:
335 if not blktap2.VDI.tap_pause(self.session, 335 ↛ 337line 335 didn't jump to line 337, because the condition on line 335 was never true
336 sr_uuid, child_uuid):
337 raise util.SMException("failed to pause VDI %s")
338 paused_for_coalesce = True
339 self._activate_cbt_log(self._get_cbt_logname(vdi_uuid))
340 self._cbt_op(child_uuid, cbtutil.coalesce_bitmap,
341 logpath, child_path)
342 lock.release()
343 except util.CommandException:
344 # If there is an exception in coalescing,
345 # CBT log file is not deleted and pointers are reset
346 # to what they were
347 util.SMlog("Exception in coalescing bitmaps on VDI delete,"
348 " restoring to previous state")
349 try:
350 if self._cbt_log_exists(parent_path): 350 ↛ 353line 350 didn't jump to line 353, because the condition on line 350 was never false
351 self._cbt_op(parent_uuid, cbtutil.set_cbt_child,
352 parent_path, vdi_uuid)
353 if self._cbt_log_exists(child_path): 353 ↛ 357line 353 didn't jump to line 357, because the condition on line 353 was never false
354 self._cbt_op(child_uuid, cbtutil.set_cbt_parent,
355 child_path, vdi_uuid)
356 finally:
357 lock.release()
358 lock.cleanup("cbtlog", str(vdi_uuid))
359 return
360 finally:
361 # Unpause tapdisk if it wasn't originally paused
362 if paused_for_coalesce: 362 ↛ 365line 362 didn't jump to line 365, because the condition on line 362 was never false
363 blktap2.VDI.tap_unpause(self.session, sr_uuid, 363 ↛ exitline 363 didn't return from function 'delete', because the return on line 359 wasn't executed
364 child_uuid)
365 lock.acquire()
366 try:
367 self._delete_cbt_log()
368 finally:
369 lock.release()
370 lock.cleanup("cbtlog", str(vdi_uuid))
372 def snapshot(self, sr_uuid, vdi_uuid):
373 """Save an immutable copy of the referenced VDI.
375 This operation IS NOT idempotent and will fail if the UUID
376 already exists or if there is insufficient space. The vdi must
377 be explicitly attached via the vdi_attach() command following
378 creation. If the driver does not support snapshotting this
379 operation should raise SRUnsupportedOperation
381 Arguments:
382 Raises:
383 SRUnsupportedOperation
384 """
385 # logically, "snapshot" should mean SNAPSHOT_SINGLE and "clone" should
386 # mean "SNAPSHOT_DOUBLE", but in practice we have to do SNAPSHOT_DOUBLE
387 # in both cases, unless driver_params overrides it
388 snapType = SNAPSHOT_DOUBLE
389 if self.sr.srcmd.params['driver_params'].get("type"): 389 ↛ 395line 389 didn't jump to line 395, because the condition on line 389 was never false
390 if self.sr.srcmd.params['driver_params']["type"] == "single": 390 ↛ 391line 390 didn't jump to line 391, because the condition on line 390 was never true
391 snapType = SNAPSHOT_SINGLE
392 elif self.sr.srcmd.params['driver_params']["type"] == "internal": 392 ↛ 393line 392 didn't jump to line 393, because the condition on line 392 was never true
393 snapType = SNAPSHOT_INTERNAL
395 secondary = None
396 if self.sr.srcmd.params['driver_params'].get("mirror"):
397 secondary = self.sr.srcmd.params['driver_params']["mirror"]
399 if self._get_blocktracking_status():
400 cbtlog = self._get_cbt_logpath(self.uuid)
401 else:
402 cbtlog = None
403 return self._do_snapshot(sr_uuid, vdi_uuid, snapType,
404 secondary=secondary, cbtlog=cbtlog)
406 def activate(self, sr_uuid, vdi_uuid):
407 """Activate VDI - called pre tapdisk open"""
408 if self._get_blocktracking_status():
409 if 'args' in self.sr.srcmd.params: 409 ↛ 410line 409 didn't jump to line 410, because the condition on line 409 was never true
410 read_write = self.sr.srcmd.params['args'][0]
411 if read_write == "false":
412 # Disk is being attached in RO mode,
413 # don't attach metadata log file
414 return None
416 from lock import Lock
417 lock = Lock("cbtlog", str(vdi_uuid))
418 lock.acquire()
420 try:
421 logpath = self._get_cbt_logpath(vdi_uuid)
422 logname = self._get_cbt_logname(vdi_uuid)
424 # Activate CBT log file, if required
425 self._activate_cbt_log(logname)
426 finally:
427 lock.release()
429 # Check and update consistency
430 consistent = self._cbt_op(vdi_uuid, cbtutil.get_cbt_consistency,
431 logpath)
432 if not consistent:
433 alert_name = "VDI_CBT_METADATA_INCONSISTENT"
434 alert_str = ("Changed Block Tracking metadata is inconsistent"
435 " for disk %s." % vdi_uuid)
436 self._disable_cbt_on_error(alert_name, alert_str)
437 return None
439 self._cbt_op(self.uuid, cbtutil.set_cbt_consistency,
440 logpath, False)
441 return {'cbtlog': logpath}
442 return None
444 def deactivate(self, sr_uuid, vdi_uuid):
445 """Deactivate VDI - called post tapdisk close"""
446 if self._get_blocktracking_status():
447 from lock import Lock
448 lock = Lock("cbtlog", str(vdi_uuid))
449 lock.acquire()
451 try:
452 logpath = self._get_cbt_logpath(vdi_uuid)
453 logname = self._get_cbt_logname(vdi_uuid)
454 self._cbt_op(vdi_uuid, cbtutil.set_cbt_consistency, logpath, True)
455 # Finally deactivate log file
456 self._deactivate_cbt_log(logname)
457 finally:
458 lock.release()
460 def get_params(self):
461 """
462 Returns:
463 XMLRPC response containing a single struct with fields
464 'location' and 'uuid'
465 """
466 struct = {'location': self.location,
467 'uuid': self.uuid}
468 return xmlrpc.client.dumps((struct, ), "", True)
470 def load(self, vdi_uuid):
471 """Post-init hook"""
472 pass
474 def _db_introduce(self):
475 uuid = util.default(self, "uuid", lambda: util.gen_uuid()) 475 ↛ exitline 475 didn't run the lambda on line 475
476 sm_config = util.default(self, "sm_config", lambda: {})
477 if "vdi_sm_config" in self.sr.srcmd.params: 477 ↛ 478line 477 didn't jump to line 478, because the condition on line 477 was never true
478 for key in SM_CONFIG_PASS_THROUGH_FIELDS:
479 val = self.sr.srcmd.params["vdi_sm_config"].get(key)
480 if val:
481 sm_config[key] = val
482 ty = util.default(self, "ty", lambda: "user") 482 ↛ exitline 482 didn't run the lambda on line 482
483 is_a_snapshot = util.default(self, "is_a_snapshot", lambda: False)
484 metadata_of_pool = util.default(self, "metadata_of_pool", lambda: "OpaqueRef:NULL")
485 snapshot_time = util.default(self, "snapshot_time", lambda: "19700101T00:00:00Z")
486 snapshot_of = util.default(self, "snapshot_of", lambda: "OpaqueRef:NULL")
487 cbt_enabled = util.default(self, "cbt_enabled", lambda: False) 487 ↛ exitline 487 didn't run the lambda on line 487
488 vdi = self.sr.session.xenapi.VDI.db_introduce(uuid, self.label, self.description, self.sr.sr_ref, ty, self.shareable, self.read_only, {}, self.location, {}, sm_config, self.managed, str(self.size), str(self.utilisation), metadata_of_pool, is_a_snapshot, xmlrpc.client.DateTime(snapshot_time), snapshot_of, cbt_enabled)
489 return vdi
491 def _db_forget(self):
492 self.sr.forget_vdi(self.uuid)
494 def _override_sm_config(self, sm_config):
495 for key, val in self.sm_config_override.items():
496 if val == sm_config.get(key):
497 continue
498 if val: 498 ↛ 502line 498 didn't jump to line 502, because the condition on line 498 was never false
499 util.SMlog("_override_sm_config: %s: %s -> %s" % \
500 (key, sm_config.get(key), val))
501 sm_config[key] = val
502 elif key in sm_config:
503 util.SMlog("_override_sm_config: del %s" % key)
504 del sm_config[key]
506 def _db_update_sm_config(self, ref, sm_config):
507 import cleanup
508 # List of sm-config keys that should not be modifed by db_update
509 smconfig_protected_keys = [
510 cleanup.VDI.DB_VDI_PAUSED,
511 cleanup.VDI.DB_VHD_BLOCKS,
512 cleanup.VDI.DB_VDI_RELINKING,
513 cleanup.VDI.DB_VDI_ACTIVATING]
515 current_sm_config = self.sr.session.xenapi.VDI.get_sm_config(ref)
516 for key, val in sm_config.items():
517 if (key.startswith("host_") or
518 key in smconfig_protected_keys):
519 continue
520 if sm_config.get(key) != current_sm_config.get(key):
521 util.SMlog("_db_update_sm_config: %s sm-config:%s %s->%s" % \
522 (self.uuid, key, current_sm_config.get(key), val))
523 self.sr.session.xenapi.VDI.remove_from_sm_config(ref, key)
524 self.sr.session.xenapi.VDI.add_to_sm_config(ref, key, val)
526 for key in current_sm_config.keys():
527 if (key.startswith("host_") or
528 key in smconfig_protected_keys or
529 key in self.sm_config_keep):
530 continue
531 if not sm_config.get(key):
532 util.SMlog("_db_update_sm_config: %s del sm-config:%s" % \
533 (self.uuid, key))
534 self.sr.session.xenapi.VDI.remove_from_sm_config(ref, key)
536 def _db_update(self):
537 vdi = self.sr.session.xenapi.VDI.get_by_uuid(self.uuid)
538 self.sr.session.xenapi.VDI.set_virtual_size(vdi, str(self.size))
539 self.sr.session.xenapi.VDI.set_physical_utilisation(vdi, str(self.utilisation))
540 self.sr.session.xenapi.VDI.set_read_only(vdi, self.read_only)
541 sm_config = util.default(self, "sm_config", lambda: {})
542 self._override_sm_config(sm_config)
543 self._db_update_sm_config(vdi, sm_config)
544 self.sr.session.xenapi.VDI.set_cbt_enabled(vdi,
545 self._get_blocktracking_status())
547 def in_sync_with_xenapi_record(self, x):
548 """Returns true if this VDI is in sync with the supplied XenAPI record"""
549 if self.location != util.to_plain_string(x['location']):
550 util.SMlog("location %s <> %s" % (self.location, x['location']))
551 return False
552 if self.read_only != x['read_only']:
553 util.SMlog("read_only %s <> %s" % (self.read_only, x['read_only']))
554 return False
555 if str(self.size) != x['virtual_size']:
556 util.SMlog("virtual_size %s <> %s" % (self.size, x['virtual_size']))
557 return False
558 if str(self.utilisation) != x['physical_utilisation']:
559 util.SMlog("utilisation %s <> %s" % (self.utilisation, x['physical_utilisation']))
560 return False
561 sm_config = util.default(self, "sm_config", lambda: {})
562 if set(sm_config.keys()) != set(x['sm_config'].keys()):
563 util.SMlog("sm_config %s <> %s" % (repr(sm_config), repr(x['sm_config'])))
564 return False
565 for k in sm_config.keys():
566 if sm_config[k] != x['sm_config'][k]:
567 util.SMlog("sm_config %s <> %s" % (repr(sm_config), repr(x['sm_config'])))
568 return False
569 if self.cbt_enabled != x['cbt_enabled']:
570 util.SMlog("cbt_enabled %s <> %s" % (
571 self.cbt_enabled, x['cbt_enabled']))
572 return False
573 return True
575 def configure_blocktracking(self, sr_uuid, vdi_uuid, enable):
576 """Function for configuring blocktracking"""
577 import blktap2
578 vdi_ref = self.sr.srcmd.params['vdi_ref']
580 # Check if raw VDI or snapshot
581 if self.vdi_type == vhdutil.VDI_TYPE_RAW or \
582 self.session.xenapi.VDI.get_is_a_snapshot(vdi_ref):
583 raise xs_errors.XenError('VDIType',
584 opterr='Raw VDI or snapshot not permitted')
586 # Check if already enabled
587 if self._get_blocktracking_status() == enable:
588 return
590 # Save disk state before pause
591 disk_state = blktap2.VDI.tap_status(self.session, vdi_uuid)
593 if not blktap2.VDI.tap_pause(self.session, sr_uuid, vdi_uuid):
594 error = "Failed to pause VDI %s" % vdi_uuid
595 raise xs_errors.XenError('CBTActivateFailed', opterr=error)
596 logfile = None
598 try:
599 if enable:
600 try:
601 # Check available space
602 self._ensure_cbt_space()
603 logfile = self._create_cbt_log()
604 # Set consistency
605 if disk_state: 605 ↛ 636line 605 didn't jump to line 636, because the condition on line 605 was never false
606 util.SMlog("Setting consistency of cbtlog file to False for VDI: %s"
607 % self.uuid)
608 logpath = self._get_cbt_logpath(self.uuid)
609 self._cbt_op(self.uuid, cbtutil.set_cbt_consistency,
610 logpath, False)
611 except Exception as error:
612 self._delete_cbt_log()
613 raise xs_errors.XenError('CBTActivateFailed',
614 opterr=str(error))
615 else:
616 from lock import Lock
617 lock = Lock("cbtlog", str(vdi_uuid))
618 lock.acquire()
619 try:
620 # Find parent of leaf metadata file, if any,
621 # and nullify its successor
622 logpath = self._get_cbt_logpath(self.uuid)
623 parent = self._cbt_op(self.uuid,
624 cbtutil.get_cbt_parent, logpath)
625 self._delete_cbt_log()
626 parent_path = self._get_cbt_logpath(parent)
627 if self._cbt_log_exists(parent_path): 627 ↛ 633line 627 didn't jump to line 633, because the condition on line 627 was never false
628 self._cbt_op(parent, cbtutil.set_cbt_child,
629 parent_path, uuid.UUID(int=0))
630 except Exception as error:
631 raise xs_errors.XenError('CBTDeactivateFailed', str(error))
632 finally:
633 lock.release()
634 lock.cleanup("cbtlog", str(vdi_uuid))
635 finally:
636 blktap2.VDI.tap_unpause(self.session, sr_uuid, vdi_uuid)
638 def data_destroy(self, sr_uuid, vdi_uuid):
639 """Delete the data associated with a CBT enabled snapshot
641 Can only be called for a snapshot VDI on a VHD chain that has
642 had CBT enabled on it at some point. The latter is enforced
643 by upper layers
644 """
646 vdi_ref = self.sr.srcmd.params['vdi_ref']
647 if not self.session.xenapi.VDI.get_is_a_snapshot(vdi_ref):
648 raise xs_errors.XenError('VDIType',
649 opterr='Only allowed for snapshot VDIs')
651 self.delete(sr_uuid, vdi_uuid, data_only=True)
653 def list_changed_blocks(self):
654 """ List all changed blocks """
655 vdi_from = self.uuid
656 params = self.sr.srcmd.params
657 _VDI = self.session.xenapi.VDI
658 vdi_to = _VDI.get_uuid(params['args'][0])
659 sr_uuid = params['sr_uuid']
661 if vdi_from == vdi_to:
662 raise xs_errors.XenError('CBTChangedBlocksError',
663 "Source and target VDI are same")
665 # Check 1: Check if CBT is enabled on VDIs and they are related
666 if (self._get_blocktracking_status(vdi_from) and
667 self._get_blocktracking_status(vdi_to)):
668 merged_bitmap = None
669 curr_vdi = vdi_from
670 vdi_size = 0
671 logpath = self._get_cbt_logpath(curr_vdi)
673 # Starting at log file after "vdi_from", traverse the CBT chain
674 # through child pointers until one of the following is true
675 # * We've reached destination VDI
676 # * We've reached end of CBT chain originating at "vdi_from"
677 while True:
678 # Check if we have reached end of CBT chain
679 next_vdi = self._cbt_op(curr_vdi, cbtutil.get_cbt_child,
680 logpath)
681 if not self._cbt_log_exists(self._get_cbt_logpath(next_vdi)): 681 ↛ 683line 681 didn't jump to line 683, because the condition on line 681 was never true
682 # VDIs are not part of the same metadata chain
683 break
684 else:
685 curr_vdi = next_vdi
687 logpath = self._get_cbt_logpath(curr_vdi)
688 curr_vdi_size = self._cbt_op(curr_vdi,
689 cbtutil.get_cbt_size, logpath)
690 util.SMlog("DEBUG: Processing VDI %s of size %d"
691 % (curr_vdi, curr_vdi_size))
692 curr_bitmap = bitarray()
693 curr_bitmap.frombytes(self._cbt_op(curr_vdi,
694 cbtutil.get_cbt_bitmap,
695 logpath))
696 curr_bitmap.bytereverse()
697 util.SMlog("Size of bitmap: %d" % len(curr_bitmap))
699 expected_bitmap_len = curr_vdi_size // CBT_BLOCK_SIZE
700 # This should ideally never happen but fail call to calculate
701 # changed blocks instead of returning corrupt data
702 if len(curr_bitmap) < expected_bitmap_len:
703 util.SMlog("Size of bitmap %d is less than expected size %d"
704 % (len(curr_bitmap), expected_bitmap_len))
705 raise xs_errors.XenError('CBTMetadataInconsistent',
706 "Inconsistent bitmaps")
708 if merged_bitmap:
709 # Rule out error conditions
710 # 1) New VDI size < original VDI size
711 # 2) New bitmap size < original bitmap size
712 # 3) new VDI size > original VDI size but new bitmap
713 # is not bigger
714 if (curr_vdi_size < vdi_size or
715 len(curr_bitmap) < len(merged_bitmap) or
716 (curr_vdi_size > vdi_size and
717 len(curr_bitmap) <= len(merged_bitmap))):
718 # Return error: Failure to calculate changed blocks
719 util.SMlog("Cannot calculate changed blocks with"
720 "inconsistent bitmap sizes")
721 raise xs_errors.XenError('CBTMetadataInconsistent',
722 "Inconsistent bitmaps")
724 # Check if disk has been resized
725 if curr_vdi_size > vdi_size:
726 vdi_size = curr_vdi_size
727 extended_size = len(curr_bitmap) - len(merged_bitmap)
728 # Extend merged_bitmap to match size of curr_bitmap
729 extended_bitmap = extended_size * bitarray('0')
730 merged_bitmap += extended_bitmap
732 # At this point bitmap sizes should be same
733 if (len(curr_bitmap) > len(merged_bitmap) and
734 curr_vdi_size == vdi_size):
735 # This is unusual. Log it but calculate merged
736 # bitmap by truncating new bitmap
737 util.SMlog("Bitmap for %s bigger than other bitmaps"
738 "in chain without change in size" % curr_vdi)
739 curr_bitmap = curr_bitmap[:len(merged_bitmap)]
741 merged_bitmap = merged_bitmap | curr_bitmap
742 else:
743 merged_bitmap = curr_bitmap
744 vdi_size = curr_vdi_size
746 # Check if we have reached "vdi_to"
747 if curr_vdi == vdi_to:
748 encoded_string = base64.b64encode(merged_bitmap.tobytes()).decode()
749 return xmlrpc.client.dumps((encoded_string, ), "", True)
750 # TODO: Check 2: If both VDIs still exist,
751 # find common ancestor and find difference
753 # TODO: VDIs are unrelated
754 # return fully populated bitmap size of to VDI
756 raise xs_errors.XenError('CBTChangedBlocksError',
757 "Source and target VDI are unrelated")
759 def _cbt_snapshot(self, snapshot_uuid, consistency_state):
760 """ CBT snapshot"""
761 snap_logpath = self._get_cbt_logpath(snapshot_uuid)
762 vdi_logpath = self._get_cbt_logpath(self.uuid)
764 # Rename vdi vdi.cbtlog to snapshot.cbtlog
765 # and mark it consistent
766 self._rename(vdi_logpath, snap_logpath)
767 self._cbt_op(snapshot_uuid, cbtutil.set_cbt_consistency,
768 snap_logpath, True)
770 #TODO: Make parent detection logic better. Ideally, get_cbt_parent
771 # should return None if the parent is set to a UUID made of all 0s.
772 # In this case, we don't know the difference between whether it is a
773 # NULL UUID or the parent file is missing. See cbtutil for why we can't
774 # do this
775 parent = self._cbt_op(snapshot_uuid,
776 cbtutil.get_cbt_parent, snap_logpath)
777 parent_path = self._get_cbt_logpath(parent)
778 if self._cbt_log_exists(parent_path):
779 self._cbt_op(parent, cbtutil.set_cbt_child,
780 parent_path, snapshot_uuid)
781 try:
782 # Ensure enough space for metadata file
783 self._ensure_cbt_space()
784 # Create new vdi.cbtlog
785 self._create_cbt_log()
786 # Set previous vdi node consistency status
787 if not consistency_state: 787 ↛ 788line 787 didn't jump to line 788, because the condition on line 787 was never true
788 self._cbt_op(self.uuid, cbtutil.set_cbt_consistency,
789 vdi_logpath, consistency_state)
790 # Set relationship pointers
791 # Save the child of the VDI just snapshotted
792 curr_child_uuid = self._cbt_op(snapshot_uuid, cbtutil.get_cbt_child,
793 snap_logpath)
794 self._cbt_op(self.uuid, cbtutil.set_cbt_parent,
795 vdi_logpath, snapshot_uuid)
796 # Set child of new vdi to existing child of snapshotted VDI
797 self._cbt_op(self.uuid, cbtutil.set_cbt_child,
798 vdi_logpath, curr_child_uuid)
799 self._cbt_op(snapshot_uuid, cbtutil.set_cbt_child,
800 snap_logpath, self.uuid)
801 except Exception as ex:
802 alert_name = "VDI_CBT_SNAPSHOT_FAILED"
803 alert_str = ("Creating CBT metadata log for disk %s failed."
804 % self.uuid)
805 self._disable_cbt_on_error(alert_name, alert_str)
807 def _get_blocktracking_status(self, uuid=None):
808 """ Get blocktracking status """
809 if not uuid: 809 ↛ 811line 809 didn't jump to line 811, because the condition on line 809 was never false
810 uuid = self.uuid
811 if self.vdi_type == vhdutil.VDI_TYPE_RAW: 811 ↛ 812line 811 didn't jump to line 812, because the condition on line 811 was never true
812 return False
813 elif 'VDI_CONFIG_CBT' not in util.sr_get_capability(self.sr.uuid):
814 return False
815 logpath = self._get_cbt_logpath(uuid)
816 return self._cbt_log_exists(logpath)
818 def _set_blocktracking_status(self, vdi_ref, enable):
819 """ Set blocktracking status"""
820 vdi_config = self.session.xenapi.VDI.get_other_config(vdi_ref)
821 if "cbt_enabled" in vdi_config:
822 self.session.xenapi.VDI.remove_from_other_config(
823 vdi_ref, "cbt_enabled")
825 self.session.xenapi.VDI.add_to_other_config(
826 vdi_ref, "cbt_enabled", enable)
828 def _ensure_cbt_space(self):
829 """ Ensure enough CBT space """
830 pass
832 def _get_cbt_logname(self, uuid):
833 """ Get CBT logname """
834 logName = "%s.%s" % (uuid, CBTLOG_TAG)
835 return logName
837 def _get_cbt_logpath(self, uuid):
838 """ Get CBT logpath """
839 logName = self._get_cbt_logname(uuid)
840 return os.path.join(self.sr.path, logName)
842 def _create_cbt_log(self):
843 """ Create CBT log """
844 try:
845 logpath = self._get_cbt_logpath(self.uuid)
846 vdi_ref = self.sr.srcmd.params['vdi_ref']
847 size = self.session.xenapi.VDI.get_virtual_size(vdi_ref)
848 #cbtutil.create_cbt_log(logpath, size)
849 self._cbt_op(self.uuid, cbtutil.create_cbt_log, logpath, size)
850 self._cbt_op(self.uuid, cbtutil.set_cbt_consistency, logpath, True)
851 except Exception as e:
852 try:
853 self._delete_cbt_log()
854 except:
855 pass
856 finally:
857 raise e
859 return logpath
861 def _activate_cbt_log(self, logname):
862 """Activate CBT log file
864 SR specific Implementation required for VDIs on block-based SRs.
865 No-op otherwise
866 """
867 return False
869 def _deactivate_cbt_log(self, logname):
870 """Deactivate CBT log file
872 SR specific Implementation required for VDIs on block-based SRs.
873 No-op otherwise
874 """
875 pass
877 def _cbt_op(self, uuid, func, *args):
878 # Lock cbtlog operations
879 from lock import Lock
880 lock = Lock("cbtlog", str(uuid))
881 lock.acquire()
883 try:
884 logname = self._get_cbt_logname(uuid)
885 activated = self._activate_cbt_log(logname)
886 ret = func( * args)
887 if activated: 887 ↛ 888line 887 didn't jump to line 888, because the condition on line 887 was never true
888 self._deactivate_cbt_log(logname)
889 return ret
890 finally:
891 lock.release()
893 def _disable_cbt_on_error(self, alert_name, alert_str):
894 util.SMlog(alert_str)
895 self._delete_cbt_log()
896 vdi_ref = self.sr.srcmd.params['vdi_ref']
897 self.sr.session.xenapi.VDI.set_cbt_enabled(vdi_ref, False)
898 alert_prio_warning = "3"
899 alert_obj = "VDI"
900 alert_uuid = str(self.uuid)
901 self.sr.session.xenapi.message.create(alert_name,
902 alert_prio_warning,
903 alert_obj, alert_uuid,
904 alert_str)