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