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