Coverage for drivers/VDI.py : 71%

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