Coverage for drivers/LVMSR.py : 47%
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#!/usr/bin/python3
2#
3# Copyright (C) Citrix Systems Inc.
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License as published
7# by the Free Software Foundation; version 2.1 only.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with this program; if not, write to the Free Software Foundation, Inc.,
16# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17#
18# LVMSR: VHD and QCOW2 on LVM storage repository
19#
21from sm_typing import Dict, List, override
23import SR
24from SR import deviceCheck
25import VDI
26import SRCommand
27import util
28import lvutil
29import lvmcache
30import scsiutil
31import lock
32import os
33import sys
34import time
35import errno
36import xs_errors
37import cleanup
38import blktap2
39from journaler import Journaler
40from refcounter import RefCounter
41from ipc import IPCFlag
42from constants import NS_PREFIX_LVM, VG_LOCATION, VG_PREFIX, CBT_BLOCK_SIZE
43from cowutil import CowUtil, getCowUtil, getImageStringFromVdiType, getVdiTypeFromImageFormat
44from lvmcowutil import LV_PREFIX, LvmCowUtil
45from lvmanager import LVActivator
46from vditype import VdiType
47import XenAPI # pylint: disable=import-error
48import re
49from srmetadata import ALLOCATION_TAG, NAME_LABEL_TAG, NAME_DESCRIPTION_TAG, \
50 UUID_TAG, IS_A_SNAPSHOT_TAG, SNAPSHOT_OF_TAG, TYPE_TAG, VDI_TYPE_TAG, \
51 READ_ONLY_TAG, MANAGED_TAG, SNAPSHOT_TIME_TAG, METADATA_OF_POOL_TAG, \
52 LVMMetadataHandler, METADATA_OBJECT_TYPE_VDI, \
53 METADATA_OBJECT_TYPE_SR, METADATA_UPDATE_OBJECT_TYPE_TAG
54from metadata import retrieveXMLfromFile, _parseXML
55from xmlrpc.client import DateTime
56import glob
57from constants import CBTLOG_TAG
58from fairlock import Fairlock
59DEV_MAPPER_ROOT = os.path.join('/dev/mapper', VG_PREFIX)
61geneology: Dict[str, List[str]] = {}
62CAPABILITIES = ["SR_PROBE", "SR_UPDATE", "SR_TRIM",
63 "VDI_CREATE", "VDI_DELETE", "VDI_ATTACH", "VDI_DETACH", "VDI_MIRROR",
64 "VDI_CLONE", "VDI_SNAPSHOT", "VDI_RESIZE", "ATOMIC_PAUSE",
65 "VDI_RESET_ON_BOOT/2", "VDI_UPDATE", "VDI_CONFIG_CBT",
66 "VDI_ACTIVATE", "VDI_DEACTIVATE"]
68CONFIGURATION = [['device', 'local device path (required) (e.g. /dev/sda3)']]
70DRIVER_INFO = {
71 'name': 'Local VHD and QCOW2 on LVM',
72 'description': 'SR plugin which represents disks as VHD and QCOW2 disks on ' + \
73 'Logical Volumes within a locally-attached Volume Group',
74 'vendor': 'XenSource Inc',
75 'copyright': '(C) 2008 XenSource Inc',
76 'driver_version': '1.0',
77 'required_api_version': '1.0',
78 'capabilities': CAPABILITIES,
79 'configuration': CONFIGURATION
80 }
82OPS_EXCLUSIVE = [
83 "sr_create", "sr_delete", "sr_attach", "sr_detach", "sr_scan",
84 "sr_update", "vdi_create", "vdi_delete", "vdi_resize", "vdi_snapshot",
85 "vdi_clone"]
87# Log if snapshot pauses VM for more than this many seconds
88LONG_SNAPTIME = 60
90class LVMSR(SR.SR):
91 DRIVER_TYPE = 'lvhd'
93 PROVISIONING_TYPES = ["thin", "thick"]
94 PROVISIONING_DEFAULT = "thick"
95 THIN_PLUGIN = "lvhd-thin"
97 PLUGIN_ON_SLAVE = "on-slave"
99 FLAG_USE_VHD = "use_vhd"
100 MDVOLUME_NAME = "MGT"
102 ALLOCATION_QUANTUM = "allocation_quantum"
103 INITIAL_ALLOCATION = "initial_allocation"
105 LOCK_RETRY_INTERVAL = 3
106 LOCK_RETRY_ATTEMPTS = 10
108 TEST_MODE_KEY = "testmode"
109 TEST_MODE_VHD_FAIL_REPARENT_BEGIN = "vhd_fail_reparent_begin"
110 TEST_MODE_VHD_FAIL_REPARENT_LOCATOR = "vhd_fail_reparent_locator"
111 TEST_MODE_VHD_FAIL_REPARENT_END = "vhd_fail_reparent_end"
112 TEST_MODE_VHD_FAIL_RESIZE_BEGIN = "vhd_fail_resize_begin"
113 TEST_MODE_VHD_FAIL_RESIZE_DATA = "vhd_fail_resize_data"
114 TEST_MODE_VHD_FAIL_RESIZE_METADATA = "vhd_fail_resize_metadata"
115 TEST_MODE_VHD_FAIL_RESIZE_END = "vhd_fail_resize_end"
117 ENV_VAR_VHD_TEST = {
118 TEST_MODE_VHD_FAIL_REPARENT_BEGIN:
119 "VHD_UTIL_TEST_FAIL_REPARENT_BEGIN",
120 TEST_MODE_VHD_FAIL_REPARENT_LOCATOR:
121 "VHD_UTIL_TEST_FAIL_REPARENT_LOCATOR",
122 TEST_MODE_VHD_FAIL_REPARENT_END:
123 "VHD_UTIL_TEST_FAIL_REPARENT_END",
124 TEST_MODE_VHD_FAIL_RESIZE_BEGIN:
125 "VHD_UTIL_TEST_FAIL_RESIZE_BEGIN",
126 TEST_MODE_VHD_FAIL_RESIZE_DATA:
127 "VHD_UTIL_TEST_FAIL_RESIZE_DATA_MOVED",
128 TEST_MODE_VHD_FAIL_RESIZE_METADATA:
129 "VHD_UTIL_TEST_FAIL_RESIZE_METADATA_MOVED",
130 TEST_MODE_VHD_FAIL_RESIZE_END:
131 "VHD_UTIL_TEST_FAIL_RESIZE_END"
132 }
133 testMode = ""
135 legacyMode = True
137 @override
138 @staticmethod
139 def handles(type) -> bool:
140 """Returns True if this SR class understands the given dconf string"""
141 # we can pose as LVMSR or EXTSR for compatibility purposes
142 if __name__ == '__main__':
143 name = sys.argv[0]
144 else:
145 name = __name__
146 if name.endswith("LVMSR"):
147 return type == "lvm"
148 elif name.endswith("EXTSR"):
149 return type == "ext"
150 return type == LVMSR.DRIVER_TYPE
152 def __init__(self, srcmd, sr_uuid):
153 SR.SR.__init__(self, srcmd, sr_uuid)
154 self._init_image_formats()
156 @override
157 def load(self, sr_uuid) -> None:
158 self.ops_exclusive = OPS_EXCLUSIVE
160 self.isMaster = False
161 if 'SRmaster' in self.dconf and self.dconf['SRmaster'] == 'true':
162 self.isMaster = True
164 self.lock = lock.Lock(lock.LOCK_TYPE_SR, self.uuid)
165 self.sr_vditype = SR.DEFAULT_TAP
166 self.uuid = sr_uuid
167 self.vgname = VG_PREFIX + self.uuid
168 self.path = os.path.join(VG_LOCATION, self.vgname)
169 self.mdpath = os.path.join(self.path, self.MDVOLUME_NAME)
170 self.provision = self.PROVISIONING_DEFAULT
172 has_sr_ref = self.srcmd.params.get("sr_ref")
173 if has_sr_ref:
174 self.other_conf = self.session.xenapi.SR.get_other_config(self.sr_ref)
175 else:
176 self.other_conf = None
178 self.lvm_conf = None
179 if self.other_conf:
180 self.lvm_conf = self.other_conf.get('lvm-conf')
182 try:
183 self.lvmCache = lvmcache.LVMCache(self.vgname, self.lvm_conf)
184 except:
185 raise xs_errors.XenError('SRUnavailable', \
186 opterr='Failed to initialise the LVMCache')
187 self.lvActivator = LVActivator(self.uuid, self.lvmCache)
188 self.journaler = Journaler(self.lvmCache)
189 if not has_sr_ref:
190 return # must be a probe call
191 # Test for thick vs thin provisioning conf parameter
192 if 'allocation' in self.dconf: 192 ↛ 193line 192 didn't jump to line 193, because the condition on line 192 was never true
193 if self.dconf['allocation'] in self.PROVISIONING_TYPES:
194 self.provision = self.dconf['allocation']
195 else:
196 raise xs_errors.XenError('InvalidArg', \
197 opterr='Allocation parameter must be one of %s' % self.PROVISIONING_TYPES)
199 if self.other_conf.get(self.TEST_MODE_KEY): 199 ↛ 203line 199 didn't jump to line 203, because the condition on line 199 was never false
200 self.testMode = self.other_conf[self.TEST_MODE_KEY]
201 self._prepareTestMode()
203 self.sm_config = self.session.xenapi.SR.get_sm_config(self.sr_ref)
204 # sm_config flag overrides PBD, if any
205 if self.sm_config.get('allocation') in self.PROVISIONING_TYPES:
206 self.provision = self.sm_config.get('allocation')
208 if self.sm_config.get(self.FLAG_USE_VHD) == "true":
209 self.legacyMode = False
211 if lvutil._checkVG(self.vgname):
212 if self.isMaster and not self.cmd in ["vdi_attach", "vdi_detach", 212 ↛ 215line 212 didn't jump to line 215, because the condition on line 212 was never false
213 "vdi_activate", "vdi_deactivate"]:
214 self._undoAllJournals()
215 if not self.cmd in ["sr_attach", "sr_probe"]:
216 self._checkMetadataVolume()
218 self.mdexists = False
220 # get a VDI -> TYPE map from the storage
221 contains_uuid_regex = \
222 re.compile("^.*[0-9a-f]{8}-(([0-9a-f]{4})-){3}[0-9a-f]{12}.*")
223 self.storageVDIs = {}
225 for key in self.lvmCache.lvs.keys(): 225 ↛ 227line 225 didn't jump to line 227, because the loop on line 225 never started
226 # if the lvname has a uuid in it
227 type = None
228 vdi = None
229 if contains_uuid_regex.search(key) is not None:
230 for vdi_type, prefix in LV_PREFIX.items():
231 if key.startswith(prefix):
232 vdi = key[len(prefix):]
233 self.storageVDIs[vdi] = vdi_type
234 break
236 # check if metadata volume exists
237 try:
238 self.mdexists = self.lvmCache.checkLV(self.MDVOLUME_NAME)
239 except:
240 pass
242 @override
243 def cleanup(self) -> None:
244 # we don't need to hold the lock to dec refcounts of activated LVs
245 if not self.lvActivator.deactivateAll(): 245 ↛ 246line 245 didn't jump to line 246, because the condition on line 245 was never true
246 raise util.SMException("failed to deactivate LVs")
248 def updateSRMetadata(self, allocation):
249 try:
250 # Add SR specific SR metadata
251 sr_info = \
252 {ALLOCATION_TAG: allocation,
253 UUID_TAG: self.uuid,
254 NAME_LABEL_TAG: util.to_plain_string(self.session.xenapi.SR.get_name_label(self.sr_ref)),
255 NAME_DESCRIPTION_TAG: util.to_plain_string(self.session.xenapi.SR.get_name_description(self.sr_ref))
256 }
258 vdi_info = {}
259 for vdi in self.session.xenapi.SR.get_VDIs(self.sr_ref):
260 vdi_uuid = self.session.xenapi.VDI.get_uuid(vdi)
262 vdi_type = self.session.xenapi.VDI.get_sm_config(vdi).get('vdi_type')
263 if not vdi_type:
264 raise xs_errors.XenError('MetadataError', opterr=f"Missing `vdi_type` for VDI {vdi_uuid}")
266 # Create the VDI entry in the SR metadata
267 vdi_info[vdi_uuid] = \
268 {
269 UUID_TAG: vdi_uuid,
270 NAME_LABEL_TAG: util.to_plain_string(self.session.xenapi.VDI.get_name_label(vdi)),
271 NAME_DESCRIPTION_TAG: util.to_plain_string(self.session.xenapi.VDI.get_name_description(vdi)),
272 IS_A_SNAPSHOT_TAG: \
273 int(self.session.xenapi.VDI.get_is_a_snapshot(vdi)),
274 SNAPSHOT_OF_TAG: \
275 self.session.xenapi.VDI.get_snapshot_of(vdi),
276 SNAPSHOT_TIME_TAG: \
277 self.session.xenapi.VDI.get_snapshot_time(vdi),
278 TYPE_TAG: \
279 self.session.xenapi.VDI.get_type(vdi),
280 VDI_TYPE_TAG: \
281 vdi_type,
282 READ_ONLY_TAG: \
283 int(self.session.xenapi.VDI.get_read_only(vdi)),
284 METADATA_OF_POOL_TAG: \
285 self.session.xenapi.VDI.get_metadata_of_pool(vdi),
286 MANAGED_TAG: \
287 int(self.session.xenapi.VDI.get_managed(vdi))
288 }
289 LVMMetadataHandler(self.mdpath).writeMetadata(sr_info, vdi_info)
291 except Exception as e:
292 raise xs_errors.XenError('MetadataError', \
293 opterr='Error upgrading SR Metadata: %s' % str(e))
295 def syncMetadataAndStorage(self):
296 try:
297 # if a VDI is present in the metadata but not in the storage
298 # then delete it from the metadata
299 vdi_info = LVMMetadataHandler(self.mdpath, False).getMetadata()[1]
300 for vdi in list(vdi_info.keys()):
301 update_map = {}
302 if not vdi_info[vdi][UUID_TAG] in set(self.storageVDIs.keys()): 302 ↛ 309line 302 didn't jump to line 309, because the condition on line 302 was never false
303 # delete this from metadata
304 LVMMetadataHandler(self.mdpath). \
305 deleteVdiFromMetadata(vdi_info[vdi][UUID_TAG])
306 else:
307 # search for this in the metadata, compare types
308 # self.storageVDIs is a map of vdi_uuid to vdi_type
309 if vdi_info[vdi][VDI_TYPE_TAG] != \
310 self.storageVDIs[vdi_info[vdi][UUID_TAG]]:
311 # storage type takes authority
312 update_map[METADATA_UPDATE_OBJECT_TYPE_TAG] \
313 = METADATA_OBJECT_TYPE_VDI
314 update_map[UUID_TAG] = vdi_info[vdi][UUID_TAG]
315 update_map[VDI_TYPE_TAG] = \
316 self.storageVDIs[vdi_info[vdi][UUID_TAG]]
317 LVMMetadataHandler(self.mdpath) \
318 .updateMetadata(update_map)
319 else:
320 # This should never happen
321 pass
323 except Exception as e:
324 raise xs_errors.XenError('MetadataError', \
325 opterr='Error synching SR Metadata and storage: %s' % str(e))
327 def syncMetadataAndXapi(self):
328 try:
329 # get metadata
330 (sr_info, vdi_info) = \
331 LVMMetadataHandler(self.mdpath, False).getMetadata()
333 # First synch SR parameters
334 self.update(self.uuid)
336 # Now update the VDI information in the metadata if required
337 for vdi_offset in vdi_info.keys():
338 try:
339 vdi_ref = \
340 self.session.xenapi.VDI.get_by_uuid( \
341 vdi_info[vdi_offset][UUID_TAG])
342 except:
343 # may be the VDI is not in XAPI yet dont bother
344 continue
346 new_name_label = util.to_plain_string(self.session.xenapi.VDI.get_name_label(vdi_ref))
347 new_name_description = util.to_plain_string(self.session.xenapi.VDI.get_name_description(vdi_ref))
349 if vdi_info[vdi_offset][NAME_LABEL_TAG] != new_name_label or \
350 vdi_info[vdi_offset][NAME_DESCRIPTION_TAG] != \
351 new_name_description:
352 update_map = {}
353 update_map[METADATA_UPDATE_OBJECT_TYPE_TAG] = \
354 METADATA_OBJECT_TYPE_VDI
355 update_map[UUID_TAG] = vdi_info[vdi_offset][UUID_TAG]
356 update_map[NAME_LABEL_TAG] = new_name_label
357 update_map[NAME_DESCRIPTION_TAG] = new_name_description
358 LVMMetadataHandler(self.mdpath) \
359 .updateMetadata(update_map)
360 except Exception as e:
361 raise xs_errors.XenError('MetadataError', \
362 opterr='Error synching SR Metadata and XAPI: %s' % str(e))
364 def _checkMetadataVolume(self):
365 util.SMlog("Entering _checkMetadataVolume")
366 self.mdexists = self.lvmCache.checkLV(self.MDVOLUME_NAME)
367 if self.isMaster: 367 ↛ 383line 367 didn't jump to line 383, because the condition on line 367 was never false
368 if self.mdexists and self.cmd == "sr_attach":
369 try:
370 # activate the management volume
371 # will be deactivated at detach time
372 self.lvmCache.activateNoRefcount(self.MDVOLUME_NAME)
373 self._synchSmConfigWithMetaData()
374 util.SMlog("Sync SR metadata and the state on the storage.")
375 self.syncMetadataAndStorage()
376 self.syncMetadataAndXapi()
377 except Exception as e:
378 util.SMlog("Exception in _checkMetadataVolume, " \
379 "Error: %s." % str(e))
380 elif not self.mdexists and not self.legacyMode:
381 self._introduceMetaDataVolume()
383 if self.mdexists:
384 self.legacyMode = False
386 def _synchSmConfigWithMetaData(self):
387 util.SMlog("Synching sm-config with metadata volume")
389 try:
390 # get SR info from metadata
391 sr_info = {}
392 map = {}
393 sr_info = LVMMetadataHandler(self.mdpath, False).getMetadata()[0]
395 if sr_info == {}: 395 ↛ 396line 395 didn't jump to line 396, because the condition on line 395 was never true
396 raise Exception("Failed to get SR information from metadata.")
398 if "allocation" in sr_info: 398 ↛ 402line 398 didn't jump to line 402, because the condition on line 398 was never false
399 self.provision = sr_info.get("allocation")
400 map['allocation'] = sr_info.get("allocation")
401 else:
402 raise Exception("Allocation key not found in SR metadata. "
403 "SR info found: %s" % sr_info)
405 except Exception as e:
406 raise xs_errors.XenError(
407 'MetadataError',
408 opterr='Error reading SR params from '
409 'metadata Volume: %s' % str(e))
410 try:
411 map[self.FLAG_USE_VHD] = 'true'
412 self.session.xenapi.SR.set_sm_config(self.sr_ref, map)
413 except:
414 raise xs_errors.XenError(
415 'MetadataError',
416 opterr='Error updating sm_config key')
418 def _introduceMetaDataVolume(self):
419 util.SMlog("Creating Metadata volume")
420 try:
421 config = {}
422 self.lvmCache.create(self.MDVOLUME_NAME, 4 * 1024 * 1024)
424 # activate the management volume, will be deactivated at detach time
425 self.lvmCache.activateNoRefcount(self.MDVOLUME_NAME)
427 name_label = util.to_plain_string( \
428 self.session.xenapi.SR.get_name_label(self.sr_ref))
429 name_description = util.to_plain_string( \
430 self.session.xenapi.SR.get_name_description(self.sr_ref))
431 config[self.FLAG_USE_VHD] = "true"
432 config['allocation'] = self.provision
433 self.session.xenapi.SR.set_sm_config(self.sr_ref, config)
435 # Add the SR metadata
436 self.updateSRMetadata(self.provision)
437 except Exception as e:
438 raise xs_errors.XenError('MetadataError', \
439 opterr='Error introducing Metadata Volume: %s' % str(e))
441 def _removeMetadataVolume(self):
442 if self.mdexists:
443 try:
444 self.lvmCache.remove(self.MDVOLUME_NAME)
445 except:
446 raise xs_errors.XenError('MetadataError', \
447 opterr='Failed to delete MGT Volume')
449 def _refresh_size(self):
450 """
451 Refreshs the size of the backing device.
452 Return true if all paths/devices agree on the same size.
453 """
454 if hasattr(self, 'SCSIid'): 454 ↛ 456line 454 didn't jump to line 456, because the condition on line 454 was never true
455 # LVMoHBASR, LVMoISCSISR
456 return scsiutil.refresh_lun_size_by_SCSIid(getattr(self, 'SCSIid'))
457 else:
458 # LVMSR
459 devices = self.dconf['device'].split(',')
460 scsiutil.refreshdev(devices)
461 return True
463 def _expand_size(self):
464 """
465 Expands the size of the SR by growing into additional availiable
466 space, if extra space is availiable on the backing device.
467 Needs to be called after a successful call of _refresh_size.
468 """
469 currentvgsize = lvutil._getVGstats(self.vgname)['physical_size']
470 # We are comparing PV- with VG-sizes that are aligned. Need a threshold
471 resizethreshold = 100 * 1024 * 1024 # 100MB
472 devices = self.dconf['device'].split(',')
473 totaldevicesize = 0
474 for device in devices:
475 totaldevicesize = totaldevicesize + scsiutil.getsize(device)
476 if totaldevicesize >= (currentvgsize + resizethreshold):
477 try:
478 if hasattr(self, 'SCSIid'): 478 ↛ 480line 478 didn't jump to line 480, because the condition on line 478 was never true
479 # LVMoHBASR, LVMoISCSISR might have slaves
480 scsiutil.refresh_lun_size_by_SCSIid_on_slaves(self.session,
481 getattr(self, 'SCSIid'))
482 util.SMlog("LVMSR._expand_size for %s will resize the pv." %
483 self.uuid)
484 for pv in lvutil.get_pv_for_vg(self.vgname):
485 lvutil.resizePV(pv)
486 except:
487 util.logException("LVMSR._expand_size for %s failed to resize"
488 " the PV" % self.uuid)
490 @override
491 @deviceCheck
492 def create(self, uuid, size) -> None:
493 util.SMlog("LVMSR.create for %s" % self.uuid)
494 if not self.isMaster:
495 util.SMlog('sr_create blocked for non-master')
496 raise xs_errors.XenError('LVMMaster')
498 if lvutil._checkVG(self.vgname):
499 raise xs_errors.XenError('SRExists')
501 # Check none of the devices already in use by other PBDs
502 if util.test_hostPBD_devs(self.session, uuid, self.dconf['device']):
503 raise xs_errors.XenError('SRInUse')
505 # Check serial number entry in SR records
506 for dev in self.dconf['device'].split(','):
507 if util.test_scsiserial(self.session, dev):
508 raise xs_errors.XenError('SRInUse')
510 lvutil.createVG(self.dconf['device'], self.vgname)
512 #Update serial number string
513 scsiutil.add_serial_record(self.session, self.sr_ref, \
514 scsiutil.devlist_to_serialstring(self.dconf['device'].split(',')))
516 # since this is an SR.create turn off legacy mode
517 self.session.xenapi.SR.add_to_sm_config(self.sr_ref, \
518 self.FLAG_USE_VHD, 'true')
520 @override
521 def delete(self, uuid) -> None:
522 util.SMlog("LVMSR.delete for %s" % self.uuid)
523 if not self.isMaster:
524 raise xs_errors.XenError('LVMMaster')
525 cleanup.gc_force(self.session, self.uuid)
527 success = True
528 for fileName in glob.glob(DEV_MAPPER_ROOT + '*'):
529 if util.extractSRFromDevMapper(fileName) != self.uuid:
530 continue
532 if util.doesFileHaveOpenHandles(fileName):
533 util.SMlog("LVMSR.delete: The dev mapper entry %s has open " \
534 "handles" % fileName)
535 success = False
536 continue
538 # Now attempt to remove the dev mapper entry
539 if not lvutil.removeDevMapperEntry(fileName, False):
540 success = False
541 continue
543 try:
544 lvname = os.path.basename(fileName.replace('-', '/'). \
545 replace('//', '-'))
546 lpath = os.path.join(self.path, lvname)
547 os.unlink(lpath)
548 except OSError as e:
549 if e.errno != errno.ENOENT:
550 util.SMlog("LVMSR.delete: failed to remove the symlink for " \
551 "file %s. Error: %s" % (fileName, str(e)))
552 success = False
554 if success:
555 try:
556 if util.pathexists(self.path):
557 os.rmdir(self.path)
558 except Exception as e:
559 util.SMlog("LVMSR.delete: failed to remove the symlink " \
560 "directory %s. Error: %s" % (self.path, str(e)))
561 success = False
563 self._removeMetadataVolume()
564 self.lvmCache.refresh()
565 if LvmCowUtil.getVolumeInfo(self.lvmCache):
566 raise xs_errors.XenError('SRNotEmpty')
568 if not success:
569 raise Exception("LVMSR delete failed, please refer to the log " \
570 "for details.")
572 lvutil.removeVG(self.dconf['device'], self.vgname)
573 self._cleanup()
575 @override
576 def attach(self, uuid) -> None:
577 util.SMlog("LVMSR.attach for %s" % self.uuid)
579 self._cleanup(True) # in case of host crashes, if detach wasn't called
581 if not util.match_uuid(self.uuid) or not lvutil._checkVG(self.vgname): 581 ↛ 582line 581 didn't jump to line 582, because the condition on line 581 was never true
582 raise xs_errors.XenError('SRUnavailable', \
583 opterr='no such volume group: %s' % self.vgname)
585 # Refresh the metadata status
586 self._checkMetadataVolume()
588 refreshsizeok = self._refresh_size()
590 if self.isMaster: 590 ↛ 601line 590 didn't jump to line 601, because the condition on line 590 was never false
591 if refreshsizeok: 591 ↛ 595line 591 didn't jump to line 595, because the condition on line 591 was never false
592 self._expand_size()
594 # Update SCSIid string
595 util.SMlog("Calling devlist_to_serial")
596 scsiutil.add_serial_record(
597 self.session, self.sr_ref,
598 scsiutil.devlist_to_serialstring(self.dconf['device'].split(',')))
600 # Test Legacy Mode Flag and update if COW volumes exist
601 if self.isMaster and self.legacyMode: 601 ↛ 602line 601 didn't jump to line 602, because the condition on line 601 was never true
602 vdiInfo = LvmCowUtil.getVDIInfo(self.lvmCache)
603 for uuid, info in vdiInfo.items():
604 if VdiType.isCowImage(info.vdiType):
605 self.legacyMode = False
606 map = self.session.xenapi.SR.get_sm_config(self.sr_ref)
607 self._introduceMetaDataVolume()
608 break
610 # Set the block scheduler
611 for dev in self.dconf['device'].split(','):
612 self.block_setscheduler(dev)
614 @override
615 def detach(self, uuid) -> None:
616 util.SMlog("LVMSR.detach for %s" % self.uuid)
617 cleanup.abort(self.uuid)
619 # Do a best effort cleanup of the dev mapper entries
620 # go through all devmapper entries for this VG
621 success = True
622 for fileName in glob.glob(DEV_MAPPER_ROOT + '*'):
623 if util.extractSRFromDevMapper(fileName) != self.uuid: 623 ↛ 624line 623 didn't jump to line 624, because the condition on line 623 was never true
624 continue
626 with Fairlock('devicemapper'):
627 # check if any file has open handles
628 if util.doesFileHaveOpenHandles(fileName):
629 # if yes, log this and signal failure
630 util.SMlog(
631 f"LVMSR.detach: The dev mapper entry {fileName} has "
632 "open handles")
633 success = False
634 continue
636 # Now attempt to remove the dev mapper entry
637 if not lvutil.removeDevMapperEntry(fileName, False): 637 ↛ 638line 637 didn't jump to line 638, because the condition on line 637 was never true
638 success = False
639 continue
641 # also remove the symlinks from /dev/VG-XenStorage-SRUUID/*
642 try:
643 lvname = os.path.basename(fileName.replace('-', '/'). \
644 replace('//', '-'))
645 lvname = os.path.join(self.path, lvname)
646 util.force_unlink(lvname)
647 except Exception as e:
648 util.SMlog("LVMSR.detach: failed to remove the symlink for " \
649 "file %s. Error: %s" % (fileName, str(e)))
650 success = False
652 # now remove the directory where the symlinks are
653 # this should pass as the directory should be empty by now
654 if success:
655 try:
656 if util.pathexists(self.path): 656 ↛ 657line 656 didn't jump to line 657, because the condition on line 656 was never true
657 os.rmdir(self.path)
658 except Exception as e:
659 util.SMlog("LVMSR.detach: failed to remove the symlink " \
660 "directory %s. Error: %s" % (self.path, str(e)))
661 success = False
663 if not success:
664 raise Exception("SR detach failed, please refer to the log " \
665 "for details.")
667 # Don't delete lock files on the master as it will break the locking
668 # between SM and any GC thread that survives through SR.detach.
669 # However, we should still delete lock files on slaves as it is the
670 # only place to do so.
671 self._cleanup(self.isMaster)
673 @override
674 def forget_vdi(self, uuid) -> None:
675 if not self.legacyMode:
676 LVMMetadataHandler(self.mdpath).deleteVdiFromMetadata(uuid)
677 super(LVMSR, self).forget_vdi(uuid)
679 @override
680 def scan(self, uuid) -> None:
681 activated_lvs = set()
682 try:
683 util.SMlog("LVMSR.scan for %s" % self.uuid)
684 if not self.isMaster: 684 ↛ 685line 684 didn't jump to line 685, because the condition on line 684 was never true
685 util.SMlog('sr_scan blocked for non-master')
686 raise xs_errors.XenError('LVMMaster')
688 if self._refresh_size(): 688 ↛ 690line 688 didn't jump to line 690, because the condition on line 688 was never false
689 self._expand_size()
690 self.lvmCache.refresh()
691 cbt_vdis = self.lvmCache.getTagged(CBTLOG_TAG)
692 self._loadvdis()
693 stats = lvutil._getVGstats(self.vgname)
694 self.physical_size = stats['physical_size']
695 self.physical_utilisation = stats['physical_utilisation']
697 # Now check if there are any VDIs in the metadata, which are not in
698 # XAPI
699 if self.mdexists: 699 ↛ 818line 699 didn't jump to line 818, because the condition on line 699 was never false
700 vdiToSnaps: Dict[str, List[str]] = {}
701 # get VDIs from XAPI
702 vdis = self.session.xenapi.SR.get_VDIs(self.sr_ref)
703 vdi_uuids = set([])
704 for vdi in vdis:
705 vdi_uuids.add(self.session.xenapi.VDI.get_uuid(vdi))
707 info = LVMMetadataHandler(self.mdpath, False).getMetadata()[1]
709 for vdi in list(info.keys()):
710 vdi_uuid = info[vdi][UUID_TAG]
711 if bool(int(info[vdi][IS_A_SNAPSHOT_TAG])): 711 ↛ 712line 711 didn't jump to line 712, because the condition on line 711 was never true
712 if info[vdi][SNAPSHOT_OF_TAG] in vdiToSnaps:
713 vdiToSnaps[info[vdi][SNAPSHOT_OF_TAG]].append(vdi_uuid)
714 else:
715 vdiToSnaps[info[vdi][SNAPSHOT_OF_TAG]] = [vdi_uuid]
717 if vdi_uuid not in vdi_uuids:
718 vdi_type = info[vdi][VDI_TYPE_TAG]
719 sm_config = {}
720 sm_config['vdi_type'] = vdi_type
721 lvname = "%s%s" % (LV_PREFIX[sm_config['vdi_type']], vdi_uuid)
722 if not self.lvmCache.checkLV(lvname):
723 util.SMlog("VDI %s is present in metadata but "
724 "its LV %s does not exist in the VG; "
725 "removing from metadata." %
726 (vdi_uuid, lvname))
727 LVMMetadataHandler(self.mdpath). \
728 deleteVdiFromMetadata(vdi_uuid)
729 continue
730 util.SMlog("Introduce VDI %s as it is present in " \
731 "metadata and not in XAPI." % vdi_uuid)
732 self.lvActivator.activate(
733 vdi_uuid, lvname, LVActivator.NORMAL)
734 activated_lvs.add(vdi_uuid)
735 lvPath = os.path.join(self.path, lvname)
737 if not VdiType.isCowImage(vdi_type):
738 size = self.lvmCache.getSize(LV_PREFIX[vdi_type] + vdi_uuid)
739 utilisation = \
740 util.roundup(lvutil.LVM_SIZE_INCREMENT,
741 int(size))
742 else:
743 cowutil = getCowUtil(vdi_type)
744 lvmcowutil = LvmCowUtil(cowutil)
746 parent = cowutil.getParentNoCheck(lvPath)
748 if parent is not None: 748 ↛ 749line 748 didn't jump to line 749, because the condition on line 748 was never true
749 sm_config['vhd-parent'] = parent[parent.find('-') + 1:]
750 size = cowutil.getSizeVirt(lvPath)
751 if self.provision == "thin": 751 ↛ 752line 751 didn't jump to line 752, because the condition on line 751 was never true
752 utilisation = util.roundup(
753 lvutil.LVM_SIZE_INCREMENT,
754 cowutil.calcOverheadEmpty(max(size, cowutil.getDefaultPreallocationSizeVirt()))
755 )
756 else:
757 utilisation = lvmcowutil.calcVolumeSize(int(size))
759 vdi_ref = self.session.xenapi.VDI.db_introduce(
760 vdi_uuid,
761 info[vdi][NAME_LABEL_TAG],
762 info[vdi][NAME_DESCRIPTION_TAG],
763 self.sr_ref,
764 info[vdi][TYPE_TAG],
765 False,
766 bool(int(info[vdi][READ_ONLY_TAG])),
767 {},
768 vdi_uuid,
769 {},
770 sm_config)
772 self.session.xenapi.VDI.set_managed(vdi_ref,
773 bool(int(info[vdi][MANAGED_TAG])))
774 self.session.xenapi.VDI.set_virtual_size(vdi_ref,
775 str(size))
776 self.session.xenapi.VDI.set_physical_utilisation( \
777 vdi_ref, str(utilisation))
778 self.session.xenapi.VDI.set_is_a_snapshot( \
779 vdi_ref, bool(int(info[vdi][IS_A_SNAPSHOT_TAG])))
780 if bool(int(info[vdi][IS_A_SNAPSHOT_TAG])): 780 ↛ 781line 780 didn't jump to line 781, because the condition on line 780 was never true
781 self.session.xenapi.VDI.set_snapshot_time( \
782 vdi_ref, DateTime(info[vdi][SNAPSHOT_TIME_TAG]))
783 if info[vdi][TYPE_TAG] == 'metadata': 783 ↛ 784line 783 didn't jump to line 784, because the condition on line 783 was never true
784 self.session.xenapi.VDI.set_metadata_of_pool( \
785 vdi_ref, info[vdi][METADATA_OF_POOL_TAG])
787 # Update CBT status of disks either just added
788 # or already in XAPI
789 cbt_logname = "%s.%s" % (vdi_uuid, CBTLOG_TAG)
790 if cbt_logname in cbt_vdis: 790 ↛ 791line 790 didn't jump to line 791, because the condition on line 790 was never true
791 vdi_ref = self.session.xenapi.VDI.get_by_uuid(vdi_uuid)
792 self.session.xenapi.VDI.set_cbt_enabled(vdi_ref, True)
793 # For existing VDIs, update local state too
794 # Scan in base class SR updates existing VDIs
795 # again based on local states
796 if vdi_uuid in self.vdis:
797 self.vdis[vdi_uuid].cbt_enabled = True
798 cbt_vdis.remove(cbt_logname)
800 # Now set the snapshot statuses correctly in XAPI
801 for srcvdi in vdiToSnaps.keys(): 801 ↛ 802line 801 didn't jump to line 802, because the loop on line 801 never started
802 try:
803 srcref = self.session.xenapi.VDI.get_by_uuid(srcvdi)
804 except:
805 # the source VDI no longer exists, continue
806 continue
808 for snapvdi in vdiToSnaps[srcvdi]:
809 try:
810 # this might fail in cases where its already set
811 snapref = \
812 self.session.xenapi.VDI.get_by_uuid(snapvdi)
813 self.session.xenapi.VDI.set_snapshot_of(snapref, srcref)
814 except Exception as e:
815 util.SMlog("Setting snapshot failed. " \
816 "Error: %s" % str(e))
818 if cbt_vdis: 818 ↛ 829line 818 didn't jump to line 829, because the condition on line 818 was never false
819 # If we have items remaining in this list,
820 # they are cbt_metadata VDI that XAPI doesn't know about
821 # Add them to self.vdis and they'll get added to the DB
822 for cbt_vdi in cbt_vdis: 822 ↛ 823line 822 didn't jump to line 823, because the loop on line 822 never started
823 cbt_uuid = cbt_vdi.split(".")[0]
824 new_vdi = self.vdi(cbt_uuid)
825 new_vdi.ty = "cbt_metadata"
826 new_vdi.cbt_enabled = True
827 self.vdis[cbt_uuid] = new_vdi
829 super(LVMSR, self).scan(uuid)
830 self._kickGC()
832 finally:
833 for vdi in activated_lvs:
834 self.lvActivator.deactivate(
835 vdi, LVActivator.NORMAL, False)
837 @override
838 def update(self, uuid) -> None:
839 if not lvutil._checkVG(self.vgname): 839 ↛ 840line 839 didn't jump to line 840, because the condition on line 839 was never true
840 return
841 self._updateStats(uuid, 0)
843 if self.legacyMode: 843 ↛ 844line 843 didn't jump to line 844, because the condition on line 843 was never true
844 return
846 # synch name_label in metadata with XAPI
847 update_map = {}
848 update_map = {METADATA_UPDATE_OBJECT_TYPE_TAG: \
849 METADATA_OBJECT_TYPE_SR,
850 NAME_LABEL_TAG: util.to_plain_string( \
851 self.session.xenapi.SR.get_name_label(self.sr_ref)),
852 NAME_DESCRIPTION_TAG: util.to_plain_string( \
853 self.session.xenapi.SR.get_name_description(self.sr_ref))
854 }
855 LVMMetadataHandler(self.mdpath).updateMetadata(update_map)
857 def _updateStats(self, uuid, virtAllocDelta):
858 valloc = int(self.session.xenapi.SR.get_virtual_allocation(self.sr_ref))
859 self.virtual_allocation = valloc + virtAllocDelta
860 util.SMlog("Setting virtual_allocation of SR %s to %d" %
861 (uuid, self.virtual_allocation))
862 stats = lvutil._getVGstats(self.vgname)
863 self.physical_size = stats['physical_size']
864 self.physical_utilisation = stats['physical_utilisation']
865 self._db_update()
867 @override
868 @deviceCheck
869 def probe(self) -> str:
870 return lvutil.srlist_toxml(
871 lvutil.scan_srlist(VG_PREFIX, self.dconf['device']),
872 VG_PREFIX,
873 ('metadata' in self.srcmd.params['sr_sm_config'] and \
874 self.srcmd.params['sr_sm_config']['metadata'] == 'true'))
876 @override
877 def vdi(self, uuid) -> VDI.VDI:
878 return LVMVDI(self, uuid)
880 def _loadvdis(self):
881 self.virtual_allocation = 0
882 self.vdiInfo = LvmCowUtil.getVDIInfo(self.lvmCache)
883 self.allVDIs = {}
885 for uuid, info in self.vdiInfo.items():
886 if uuid.startswith(cleanup.SR.TMP_RENAME_PREFIX): 886 ↛ 887line 886 didn't jump to line 887, because the condition on line 886 was never true
887 continue
888 if info.scanError: 888 ↛ 889line 888 didn't jump to line 889, because the condition on line 888 was never true
889 raise xs_errors.XenError('VDIUnavailable', \
890 opterr='Error scanning VDI %s' % uuid)
891 self.vdis[uuid] = self.allVDIs[uuid] = self.vdi(uuid)
892 if not self.vdis[uuid].hidden: 892 ↛ 885line 892 didn't jump to line 885, because the condition on line 892 was never false
893 self.virtual_allocation += self.vdis[uuid].utilisation
895 for uuid, vdi in self.vdis.items():
896 if vdi.parent: 896 ↛ 897line 896 didn't jump to line 897, because the condition on line 896 was never true
897 if vdi.parent in self.vdis:
898 self.vdis[vdi.parent].read_only = True
899 if vdi.parent in geneology:
900 geneology[vdi.parent].append(uuid)
901 else:
902 geneology[vdi.parent] = [uuid]
904 # Now remove all hidden leaf nodes to avoid introducing records that
905 # will be GC'ed
906 for uuid in list(self.vdis.keys()):
907 if uuid not in geneology and self.vdis[uuid].hidden: 907 ↛ 908line 907 didn't jump to line 908, because the condition on line 907 was never true
908 util.SMlog("Scan found hidden leaf (%s), ignoring" % uuid)
909 del self.vdis[uuid]
911 def _ensureSpaceAvailable(self, amount_needed):
912 space_available = lvutil._getVGstats(self.vgname)['freespace']
913 if (space_available < amount_needed):
914 util.SMlog("Not enough space! free space: %d, need: %d" % \
915 (space_available, amount_needed))
916 raise xs_errors.XenError('SRNoSpace')
918 def _handleInterruptedCloneOps(self):
919 entries = self.journaler.getAll(LVMVDI.JRN_CLONE)
920 for uuid, val in entries.items(): 920 ↛ 921line 920 didn't jump to line 921, because the loop on line 920 never started
921 util.fistpoint.activate("LVHDRT_clone_vdi_before_undo_clone", self.uuid)
922 self._handleInterruptedCloneOp(uuid, val)
923 util.fistpoint.activate("LVHDRT_clone_vdi_after_undo_clone", self.uuid)
924 self.journaler.remove(LVMVDI.JRN_CLONE, uuid)
926 def _handleInterruptedCoalesceLeaf(self):
927 entries = self.journaler.getAll(cleanup.VDI.JRN_LEAF)
928 if len(entries) > 0: 928 ↛ 929line 928 didn't jump to line 929, because the condition on line 928 was never true
929 util.SMlog("*** INTERRUPTED COALESCE-LEAF OP DETECTED ***")
930 cleanup.gc_force(self.session, self.uuid)
931 self.lvmCache.refresh()
933 def _handleInterruptedCloneOp(self, origUuid, jval, forceUndo=False):
934 """Either roll back or finalize the interrupted snapshot/clone
935 operation. Rolling back is unsafe if the leaf images have already been
936 in use and written to. However, it is always safe to roll back while
937 we're still in the context of the failed snapshot operation since the
938 VBD is paused for the duration of the operation"""
939 util.SMlog("*** INTERRUPTED CLONE OP: for %s (%s)" % (origUuid, jval))
940 lvs = LvmCowUtil.getVolumeInfo(self.lvmCache)
941 baseUuid, clonUuid = jval.split("_")
943 # is there a "base copy" VDI?
944 if not lvs.get(baseUuid):
945 # no base copy: make sure the original is there
946 if lvs.get(origUuid):
947 util.SMlog("*** INTERRUPTED CLONE OP: nothing to do")
948 return
949 raise util.SMException("base copy %s not present, " \
950 "but no original %s found" % (baseUuid, origUuid))
952 vdis = LvmCowUtil.getVDIInfo(self.lvmCache)
953 base = vdis[baseUuid]
954 cowutil = getCowUtil(base.vdiType)
956 if forceUndo:
957 util.SMlog("Explicit revert")
958 self._undoCloneOp(cowutil, lvs, origUuid, baseUuid, clonUuid)
959 return
961 if not lvs.get(origUuid) or (clonUuid and not lvs.get(clonUuid)):
962 util.SMlog("One or both leaves missing => revert")
963 self._undoCloneOp(cowutil, lvs, origUuid, baseUuid, clonUuid)
964 return
966 if vdis[origUuid].scanError or (clonUuid and vdis[clonUuid].scanError):
967 util.SMlog("One or both leaves invalid => revert")
968 self._undoCloneOp(cowutil, lvs, origUuid, baseUuid, clonUuid)
969 return
971 orig = vdis[origUuid]
972 self.lvActivator.activate(baseUuid, base.lvName, False)
973 self.lvActivator.activate(origUuid, orig.lvName, False)
974 if orig.parentUuid != baseUuid:
975 parent = vdis[orig.parentUuid]
976 self.lvActivator.activate(parent.uuid, parent.lvName, False)
977 origPath = os.path.join(self.path, orig.lvName)
979 if cowutil.check(origPath) != CowUtil.CheckResult.Success:
980 util.SMlog("Orig image invalid => revert")
981 self._undoCloneOp(cowutil, lvs, origUuid, baseUuid, clonUuid)
982 return
984 if clonUuid:
985 clon = vdis[clonUuid]
986 clonPath = os.path.join(self.path, clon.lvName)
987 self.lvActivator.activate(clonUuid, clon.lvName, False)
988 if cowutil.check(clonPath) != CowUtil.CheckResult.Success:
989 util.SMlog("Clon image invalid => revert")
990 self._undoCloneOp(cowutil, lvs, origUuid, baseUuid, clonUuid)
991 return
993 util.SMlog("Snapshot appears valid, will not roll back")
994 self._completeCloneOp(cowutil, vdis, origUuid, baseUuid, clonUuid)
996 def _undoCloneOp(self, cowutil, lvs, origUuid, baseUuid, clonUuid):
997 base = lvs[baseUuid]
998 basePath = os.path.join(self.path, base.name)
1000 # make the parent RW
1001 if base.readonly:
1002 self.lvmCache.setReadonly(base.name, False)
1004 ns = NS_PREFIX_LVM + self.uuid
1005 origRefcountBinary = RefCounter.check(origUuid, ns)[1]
1006 origRefcountNormal = 0
1008 # un-hide the parent
1009 if VdiType.isCowImage(base.vdiType):
1010 self.lvActivator.activate(baseUuid, base.name, False)
1011 origRefcountNormal = 1
1012 imageInfo = cowutil.getInfo(basePath, LvmCowUtil.extractUuid, False)
1013 if imageInfo.hidden:
1014 cowutil.setHidden(basePath, False)
1015 elif base.hidden:
1016 self.lvmCache.setHidden(base.name, False)
1018 # remove the child nodes
1019 if clonUuid and lvs.get(clonUuid):
1020 if not VdiType.isCowImage(lvs[clonUuid].vdiType):
1021 raise util.SMException("clone %s not a COW image" % clonUuid)
1022 self.lvmCache.remove(lvs[clonUuid].name)
1023 if self.lvActivator.get(clonUuid, False):
1024 self.lvActivator.remove(clonUuid, False)
1025 if lvs.get(origUuid):
1026 self.lvmCache.remove(lvs[origUuid].name)
1028 # inflate the parent to fully-allocated size
1029 if VdiType.isCowImage(base.vdiType):
1030 lvmcowutil = LvmCowUtil(cowutil)
1031 fullSize = lvmcowutil.calcVolumeSize(imageInfo.sizeVirt)
1032 lvmcowutil.inflate(self.journaler, self.uuid, baseUuid, base.vdiType, fullSize)
1034 # rename back
1035 origLV = LV_PREFIX[base.vdiType] + origUuid
1036 self.lvmCache.rename(base.name, origLV)
1037 RefCounter.reset(baseUuid, ns)
1038 if self.lvActivator.get(baseUuid, False):
1039 self.lvActivator.replace(baseUuid, origUuid, origLV, False)
1040 RefCounter.set(origUuid, origRefcountNormal, origRefcountBinary, ns)
1042 # At this stage, tapdisk and SM vdi will be in paused state. Remove
1043 # flag to facilitate vm deactivate
1044 origVdiRef = self.session.xenapi.VDI.get_by_uuid(origUuid)
1045 self.session.xenapi.VDI.remove_from_sm_config(origVdiRef, 'paused')
1047 # update LVM metadata on slaves
1048 slaves = util.get_slaves_attached_on(self.session, [origUuid])
1049 LvmCowUtil.refreshVolumeOnSlaves(self.session, self.uuid, self.vgname,
1050 origLV, origUuid, slaves)
1052 util.SMlog("*** INTERRUPTED CLONE OP: rollback success")
1054 def _completeCloneOp(self, cowutil, vdis, origUuid, baseUuid, clonUuid):
1055 """Finalize the interrupted snapshot/clone operation. This must not be
1056 called from the live snapshot op context because we attempt to pause/
1057 unpause the VBD here (the VBD is already paused during snapshot, so it
1058 would cause a deadlock)"""
1059 base = vdis[baseUuid]
1060 clon = None
1061 if clonUuid:
1062 clon = vdis[clonUuid]
1064 cleanup.abort(self.uuid)
1066 # make sure the parent is hidden and read-only
1067 if not base.hidden:
1068 if not VdiType.isCowImage(base.vdiType):
1069 self.lvmCache.setHidden(base.lvName)
1070 else:
1071 basePath = os.path.join(self.path, base.lvName)
1072 cowutil.setHidden(basePath)
1073 if not base.lvReadonly:
1074 self.lvmCache.setReadonly(base.lvName, True)
1076 # NB: since this snapshot-preserving call is only invoked outside the
1077 # snapshot op context, we assume the LVM metadata on the involved slave
1078 # has by now been refreshed and do not attempt to do it here
1080 # Update the original record
1081 try:
1082 vdi_ref = self.session.xenapi.VDI.get_by_uuid(origUuid)
1083 sm_config = self.session.xenapi.VDI.get_sm_config(vdi_ref)
1084 type = self.session.xenapi.VDI.get_type(vdi_ref)
1085 sm_config["vdi_type"] = vdis[origUuid].vdiType
1086 sm_config['vhd-parent'] = baseUuid
1087 self.session.xenapi.VDI.set_sm_config(vdi_ref, sm_config)
1088 except XenAPI.Failure:
1089 util.SMlog("ERROR updating the orig record")
1091 # introduce the new VDI records
1092 if clonUuid:
1093 try:
1094 clon_vdi = VDI.VDI(self, clonUuid)
1095 clon_vdi.read_only = False
1096 clon_vdi.location = clonUuid
1097 clon_vdi.utilisation = clon.sizeLV
1098 clon_vdi.sm_config = {
1099 "vdi_type": clon.vdiType,
1100 "vhd-parent": baseUuid}
1102 if not self.legacyMode:
1103 LVMMetadataHandler(self.mdpath). \
1104 ensureSpaceIsAvailableForVdis(1)
1106 clon_vdi_ref = clon_vdi._db_introduce()
1107 util.SMlog("introduced clon VDI: %s (%s)" % \
1108 (clon_vdi_ref, clonUuid))
1110 vdi_info = {UUID_TAG: clonUuid,
1111 NAME_LABEL_TAG: clon_vdi.label,
1112 NAME_DESCRIPTION_TAG: clon_vdi.description,
1113 IS_A_SNAPSHOT_TAG: 0,
1114 SNAPSHOT_OF_TAG: '',
1115 SNAPSHOT_TIME_TAG: '',
1116 TYPE_TAG: type,
1117 VDI_TYPE_TAG: clon_vdi.sm_config['vdi_type'],
1118 READ_ONLY_TAG: int(clon_vdi.read_only),
1119 MANAGED_TAG: int(clon_vdi.managed),
1120 METADATA_OF_POOL_TAG: ''
1121 }
1123 if not self.legacyMode:
1124 LVMMetadataHandler(self.mdpath).addVdi(vdi_info)
1126 except XenAPI.Failure:
1127 util.SMlog("ERROR introducing the clon record")
1129 try:
1130 base_vdi = VDI.VDI(self, baseUuid) # readonly parent
1131 base_vdi.label = "base copy"
1132 base_vdi.read_only = True
1133 base_vdi.location = baseUuid
1134 base_vdi.size = base.sizeVirt
1135 base_vdi.utilisation = base.sizeLV
1136 base_vdi.managed = False
1137 base_vdi.sm_config = {
1138 "vdi_type": base.vdiType,
1139 "vhd-parent": baseUuid}
1141 if not self.legacyMode:
1142 LVMMetadataHandler(self.mdpath).ensureSpaceIsAvailableForVdis(1)
1144 base_vdi_ref = base_vdi._db_introduce()
1145 util.SMlog("introduced base VDI: %s (%s)" % \
1146 (base_vdi_ref, baseUuid))
1148 vdi_info = {UUID_TAG: baseUuid,
1149 NAME_LABEL_TAG: base_vdi.label,
1150 NAME_DESCRIPTION_TAG: base_vdi.description,
1151 IS_A_SNAPSHOT_TAG: 0,
1152 SNAPSHOT_OF_TAG: '',
1153 SNAPSHOT_TIME_TAG: '',
1154 TYPE_TAG: type,
1155 VDI_TYPE_TAG: base_vdi.sm_config['vdi_type'],
1156 READ_ONLY_TAG: int(base_vdi.read_only),
1157 MANAGED_TAG: int(base_vdi.managed),
1158 METADATA_OF_POOL_TAG: ''
1159 }
1161 if not self.legacyMode:
1162 LVMMetadataHandler(self.mdpath).addVdi(vdi_info)
1163 except XenAPI.Failure:
1164 util.SMlog("ERROR introducing the base record")
1166 util.SMlog("*** INTERRUPTED CLONE OP: complete")
1168 def _undoAllJournals(self):
1169 """Undo all COW image & SM interrupted journaled operations. This call must
1170 be serialized with respect to all operations that create journals"""
1171 # undoing interrupted inflates must be done first, since undoing COW images
1172 # ops might require inflations
1173 self.lock.acquire()
1174 try:
1175 self._undoAllInflateJournals()
1176 self._undoAllCowJournals()
1177 self._handleInterruptedCloneOps()
1178 self._handleInterruptedCoalesceLeaf()
1179 finally:
1180 self.lock.release()
1181 self.cleanup()
1183 def _undoAllInflateJournals(self):
1184 entries = self.journaler.getAll(LvmCowUtil.JOURNAL_INFLATE)
1185 if len(entries) == 0:
1186 return
1187 self._loadvdis()
1188 for uuid, val in entries.items():
1189 vdi = self.vdis.get(uuid)
1190 if vdi: 1190 ↛ 1210line 1190 didn't jump to line 1210, because the condition on line 1190 was never false
1191 util.SMlog("Found inflate journal %s, deflating %s to %s" % \
1192 (uuid, vdi.path, val))
1193 if vdi.readonly: 1193 ↛ 1194line 1193 didn't jump to line 1194, because the condition on line 1193 was never true
1194 self.lvmCache.setReadonly(vdi.lvname, False)
1195 self.lvActivator.activate(uuid, vdi.lvname, False)
1196 currSizeLV = self.lvmCache.getSize(vdi.lvname)
1198 cowutil = getCowUtil(vdi.vdi_type)
1199 lvmcowutil = LvmCowUtil(cowutil)
1201 footer_size = cowutil.getFooterSize()
1202 util.zeroOut(vdi.path, currSizeLV - footer_size, footer_size)
1203 lvmcowutil.deflate(self.lvmCache, vdi.lvname, int(val))
1204 if vdi.readonly: 1204 ↛ 1205line 1204 didn't jump to line 1205, because the condition on line 1204 was never true
1205 self.lvmCache.setReadonly(vdi.lvname, True)
1206 if "true" == self.session.xenapi.SR.get_shared(self.sr_ref): 1206 ↛ 1207line 1206 didn't jump to line 1207, because the condition on line 1206 was never true
1207 LvmCowUtil.refreshVolumeOnAllSlaves(
1208 self.session, self.uuid, self.vgname, vdi.lvname, uuid
1209 )
1210 self.journaler.remove(LvmCowUtil.JOURNAL_INFLATE, uuid)
1211 delattr(self, "vdiInfo")
1212 delattr(self, "allVDIs")
1214 def _undoAllCowJournals(self):
1215 """
1216 Check if there are COW journals in existence and revert them.
1217 """
1218 journals = LvmCowUtil.getAllResizeJournals(self.lvmCache)
1219 if len(journals) == 0: 1219 ↛ 1221line 1219 didn't jump to line 1221, because the condition on line 1219 was never false
1220 return
1221 self._loadvdis()
1223 for uuid, jlvName in journals:
1224 vdi = self.vdis[uuid]
1225 util.SMlog("Found COW journal %s, reverting %s" % (uuid, vdi.path))
1226 cowutil = getCowUtil(vdi.vdi_type)
1227 lvmcowutil = LvmCowUtil(cowutil)
1229 self.lvActivator.activate(uuid, vdi.lvname, False)
1230 self.lvmCache.activateNoRefcount(jlvName)
1231 fullSize = lvmcowutil.calcVolumeSize(vdi.size)
1232 lvmcowutil.inflate(self.journaler, self.uuid, vdi.uuid, vdi.vdi_type, fullSize)
1233 try:
1234 jFile = os.path.join(self.path, jlvName)
1235 cowutil.revert(vdi.path, jFile)
1236 except util.CommandException:
1237 util.logException("COW journal revert")
1238 cowutil.check(vdi.path)
1239 util.SMlog("COW image revert failed but COW image ok: removing journal")
1240 # Attempt to reclaim unused space
1243 imageInfo = cowutil.getInfo(vdi.path, LvmCowUtil.extractUuid, False)
1244 NewSize = lvmcowutil.calcVolumeSize(imageInfo.sizeVirt)
1245 if NewSize < fullSize:
1246 lvmcowutil.deflate(self.lvmCache, vdi.lvname, int(NewSize))
1247 LvmCowUtil.refreshVolumeOnAllSlaves(self.session, self.uuid, self.vgname, vdi.lvname, uuid)
1248 self.lvmCache.remove(jlvName)
1249 delattr(self, "vdiInfo")
1250 delattr(self, "allVDIs")
1252 def call_on_slave(self, args, host_refs, message: str):
1253 master_ref = util.get_this_host_ref(self.session)
1254 for hostRef in host_refs:
1255 if hostRef == master_ref: 1255 ↛ 1256line 1255 didn't jump to line 1256, because the condition on line 1255 was never true
1256 continue
1257 util.SMlog(f"{message} on slave {hostRef}")
1258 rv = self.session.xenapi.host.call_plugin(
1259 hostRef, self.PLUGIN_ON_SLAVE, "multi", args)
1260 util.SMlog("call-plugin returned: %s" % rv)
1261 if not rv: 1261 ↛ 1262line 1261 didn't jump to line 1262, because the condition on line 1261 was never true
1262 raise Exception('plugin %s failed' % self.PLUGIN_ON_SLAVE)
1264 def _updateSlavesOnClone(self, hostRefs, origOldLV, origLV,
1265 baseUuid, baseLV):
1266 """We need to reactivate the original LV on each slave (note that the
1267 name for the original LV might change), as well as init the refcount
1268 for the base LV"""
1269 args = {"vgName": self.vgname,
1270 "action1": "refresh",
1271 "lvName1": origLV,
1272 "action2": "activate",
1273 "ns2": NS_PREFIX_LVM + self.uuid,
1274 "lvName2": baseLV,
1275 "uuid2": baseUuid}
1277 message = f"Updating {origOldLV}, {origLV}, {baseLV}"
1278 self.call_on_slave(args, hostRefs, message)
1280 def _updateSlavesOnCBTClone(self, hostRefs, cbtlog):
1281 """Reactivate and refresh CBT log file on slaves"""
1282 args = {"vgName": self.vgname,
1283 "action1": "deactivateNoRefcount",
1284 "lvName1": cbtlog,
1285 "action2": "refresh",
1286 "lvName2": cbtlog}
1288 message = f"Updating {cbtlog}"
1289 self.call_on_slave(args, hostRefs, message)
1291 def _updateSlavesOnRemove(self, hostRefs, baseUuid, baseLV):
1292 """Tell the slave we deleted the base image"""
1293 args = {"vgName": self.vgname,
1294 "action1": "cleanupLockAndRefcount",
1295 "uuid1": baseUuid,
1296 "ns1": NS_PREFIX_LVM + self.uuid}
1298 message = f"Cleaning locks for {baseLV}"
1299 self.call_on_slave(args, hostRefs, message)
1301 def _deactivateOnSlave(self, hostRefs, lvname):
1302 """Tell the slave we need to deactivate the base image"""
1303 args = {
1304 "vgName": self.vgname,
1305 "action1": "deactivateNoRefcount",
1306 "lvName1": lvname}
1308 message = f"Deactivating {lvname}"
1309 self.call_on_slave(args, hostRefs, message)
1311 def _cleanup(self, skipLockCleanup=False):
1312 """delete stale refcounter, flag, and lock files"""
1313 RefCounter.resetAll(NS_PREFIX_LVM + self.uuid)
1314 IPCFlag(self.uuid).clearAll()
1315 if not skipLockCleanup: 1315 ↛ 1316line 1315 didn't jump to line 1316, because the condition on line 1315 was never true
1316 lock.Lock.cleanupAll(self.uuid)
1317 lock.Lock.cleanupAll(NS_PREFIX_LVM + self.uuid)
1319 def _prepareTestMode(self):
1320 util.SMlog("Test mode: %s" % self.testMode)
1321 if self.ENV_VAR_VHD_TEST.get(self.testMode): 1321 ↛ 1322line 1321 didn't jump to line 1322, because the condition on line 1321 was never true
1322 os.environ[self.ENV_VAR_VHD_TEST[self.testMode]] = "yes"
1323 util.SMlog("Setting env %s" % self.ENV_VAR_VHD_TEST[self.testMode])
1325 def _kickGC(self):
1326 util.SMlog("Kicking GC")
1327 cleanup.start_gc_service(self.uuid)
1329 def ensureCBTSpace(self, virtual_size=0):
1330 # Ensure we have space for at least one LV
1331 size = max(util.roundup(CBT_BLOCK_SIZE, virtual_size//CBT_BLOCK_SIZE), self.journaler.LV_SIZE)
1332 self._ensureSpaceAvailable(size)
1335class LVMVDI(VDI.VDI):
1337 JRN_CLONE = "clone" # journal entry type for the clone operation
1339 @override
1340 def load(self, vdi_uuid) -> None:
1341 self.lock = self.sr.lock
1342 self.lvActivator = self.sr.lvActivator
1343 self.loaded = False
1344 if self.sr.legacyMode or util.fistpoint.is_active("xenrt_default_vdi_type_legacy"): 1344 ↛ 1346line 1344 didn't jump to line 1346, because the condition on line 1344 was never false
1345 self._setType(VdiType.RAW)
1346 self.uuid = vdi_uuid
1347 self.location = self.uuid
1348 self.exists = True
1350 if hasattr(self.sr, "vdiInfo") and self.sr.vdiInfo.get(self.uuid):
1351 self._initFromVDIInfo(self.sr.vdiInfo[self.uuid])
1352 if self.parent: 1352 ↛ 1353line 1352 didn't jump to line 1353, because the condition on line 1352 was never true
1353 self.sm_config_override['vhd-parent'] = self.parent
1354 else:
1355 self.sm_config_override['vhd-parent'] = None
1356 return
1358 # scan() didn't run: determine the type of the VDI manually
1359 if self._determineType(): 1359 ↛ 1362line 1359 didn't jump to line 1362, because the condition on line 1359 was never false
1360 return
1362 image_format = None
1364 # the VDI must be in the process of being created
1365 self.exists = False
1367 vdi_sm_config = self.sr.srcmd.params.get("vdi_sm_config")
1368 if vdi_sm_config:
1369 image_format = self.sr.read_config_image_format(vdi_sm_config)
1371 if not image_format:
1372 if self.sr.srcmd.cmd == "vdi_create":
1373 size = int(self.sr.srcmd.params['args'][0])
1374 # In the case of vdi_create, the first parameter is size.
1375 # We need it to validate the vdi_type choice
1376 for image_format in self.sr.preferred_image_formats:
1377 vdi_type = self.sr._resolve_vdi_type_from_image_format(image_format)
1378 cowutil = getCowUtil(vdi_type)
1379 try:
1380 cowutil.validateAndRoundImageSize(size)
1381 break
1382 except xs_errors.SROSError:
1383 util.SMlog(f"We won't be able to create the VDI with format {vdi_type}.")
1384 else:
1385 # For cbt_metadata, we found ourselves here without being a vdi_create, we don't have size
1386 util.SMlog("Not a vdi_create, must be cbtlog")
1387 image_format = self.sr.preferred_image_formats[0]
1388 self._setType(self.sr._resolve_vdi_type_from_image_format(image_format))
1390 if self.sr.legacyMode and self.sr.cmd == 'vdi_create' and VdiType.isCowImage(self.vdi_type):
1391 raise xs_errors.XenError('VDICreate', opterr='Cannot create COW type disk in legacy mode')
1393 self.lvname = "%s%s" % (LV_PREFIX[self.vdi_type], vdi_uuid)
1394 self.path = os.path.join(self.sr.path, self.lvname)
1396 @override
1397 def create(self, sr_uuid, vdi_uuid, size) -> str:
1398 util.SMlog("LVMVDI.create for %s" % self.uuid)
1399 if not self.sr.isMaster:
1400 raise xs_errors.XenError('LVMMaster')
1401 if self.exists:
1402 raise xs_errors.XenError('VDIExists')
1404 size = self.cowutil.validateAndRoundImageSize(int(size))
1406 util.SMlog("LVMVDI.create: type = %s, %s (size=%s)" % \
1407 (self.vdi_type, self.path, size))
1408 lvSize = 0
1409 self.sm_config = self.sr.srcmd.params["vdi_sm_config"]
1410 if not VdiType.isCowImage(self.vdi_type):
1411 lvSize = util.roundup(lvutil.LVM_SIZE_INCREMENT, int(size))
1412 else:
1413 if self.sr.provision == "thin":
1414 lvSize = util.roundup(
1415 lvutil.LVM_SIZE_INCREMENT,
1416 self.cowutil.calcOverheadEmpty(max(size, self.cowutil.getDefaultPreallocationSizeVirt()))
1417 )
1418 elif self.sr.provision == "thick":
1419 lvSize = self.lvmcowutil.calcVolumeSize(int(size))
1421 self.sr._ensureSpaceAvailable(lvSize)
1423 try:
1424 self.sr.lvmCache.create(self.lvname, lvSize)
1425 if not VdiType.isCowImage(self.vdi_type):
1426 self.size = self.sr.lvmCache.getSize(self.lvname)
1427 else:
1428 self.cowutil.create(
1429 self.path, int(size), False, self.cowutil.getDefaultPreallocationSizeVirt()
1430 )
1431 self.size = self.cowutil.getSizeVirt(self.path)
1432 self.sr.lvmCache.deactivateNoRefcount(self.lvname)
1433 except util.CommandException as e:
1434 util.SMlog("Unable to create VDI")
1435 self.sr.lvmCache.remove(self.lvname)
1436 raise xs_errors.XenError('VDICreate', opterr='error %d' % e.code)
1438 self.utilisation = lvSize
1439 self.sm_config["vdi_type"] = self.vdi_type
1440 self.sm_config["image-format"] = getImageStringFromVdiType(self.vdi_type)
1442 if not self.sr.legacyMode:
1443 LVMMetadataHandler(self.sr.mdpath).ensureSpaceIsAvailableForVdis(1)
1445 self.ref = self._db_introduce()
1446 self.sr._updateStats(self.sr.uuid, self.size)
1448 vdi_info = {UUID_TAG: self.uuid,
1449 NAME_LABEL_TAG: util.to_plain_string(self.label),
1450 NAME_DESCRIPTION_TAG: util.to_plain_string(self.description),
1451 IS_A_SNAPSHOT_TAG: 0,
1452 SNAPSHOT_OF_TAG: '',
1453 SNAPSHOT_TIME_TAG: '',
1454 TYPE_TAG: self.ty,
1455 VDI_TYPE_TAG: self.vdi_type,
1456 READ_ONLY_TAG: int(self.read_only),
1457 MANAGED_TAG: int(self.managed),
1458 METADATA_OF_POOL_TAG: ''
1459 }
1461 if not self.sr.legacyMode:
1462 LVMMetadataHandler(self.sr.mdpath).addVdi(vdi_info)
1464 return VDI.VDI.get_params(self)
1466 @override
1467 def delete(self, sr_uuid, vdi_uuid, data_only=False) -> None:
1468 util.SMlog("LVMVDI.delete for %s" % self.uuid)
1469 try:
1470 self._loadThis()
1471 except xs_errors.SRException as e:
1472 # Catch 'VDI doesn't exist' exception
1473 if e.errno == 46:
1474 return super(LVMVDI, self).delete(sr_uuid, vdi_uuid, data_only)
1475 raise
1477 vdi_ref = self.sr.srcmd.params['vdi_ref']
1478 if not self.session.xenapi.VDI.get_managed(vdi_ref):
1479 raise xs_errors.XenError("VDIDelete", \
1480 opterr="Deleting non-leaf node not permitted")
1482 if not self.hidden:
1483 self._markHidden()
1485 if not data_only:
1486 # Remove from XAPI and delete from MGT
1487 self._db_forget()
1488 else:
1489 # If this is a data_destroy call, don't remove from XAPI db
1490 # Only delete from MGT
1491 if not self.sr.legacyMode:
1492 LVMMetadataHandler(self.sr.mdpath).deleteVdiFromMetadata(self.uuid)
1494 # deactivate here because it might be too late to do it in the "final"
1495 # step: GC might have removed the LV by then
1496 if self.sr.lvActivator.get(self.uuid, False):
1497 self.sr.lvActivator.deactivate(self.uuid, False)
1499 try:
1500 self.sr.lvmCache.remove(self.lvname)
1501 self.sr.lock.cleanup(vdi_uuid, NS_PREFIX_LVM + sr_uuid)
1502 self.sr.lock.cleanupAll(vdi_uuid)
1503 except xs_errors.SRException as e:
1504 util.SMlog(
1505 "Failed to remove the volume (maybe is leaf coalescing) "
1506 "for %s err:%d" % (self.uuid, e.errno))
1508 self.sr._updateStats(self.sr.uuid, -self.size)
1509 self.sr._kickGC()
1510 return super(LVMVDI, self).delete(sr_uuid, vdi_uuid, data_only)
1512 @override
1513 def attach(self, sr_uuid, vdi_uuid) -> str:
1514 util.SMlog("LVMVDI.attach for %s" % self.uuid)
1515 if self.sr.journaler.hasJournals(self.uuid):
1516 raise xs_errors.XenError('VDIUnavailable',
1517 opterr='Interrupted operation detected on this VDI, '
1518 'scan SR first to trigger auto-repair')
1520 writable = ('args' not in self.sr.srcmd.params) or \
1521 (self.sr.srcmd.params['args'][0] == "true")
1522 needInflate = True
1523 if not VdiType.isCowImage(self.vdi_type) or not writable:
1524 needInflate = False
1525 else:
1526 self._loadThis()
1527 if self.utilisation >= self.lvmcowutil.calcVolumeSize(self.size):
1528 needInflate = False
1530 if needInflate:
1531 try:
1532 self._prepareThin(True, self.vdi_type)
1533 except:
1534 util.logException("attach")
1535 raise xs_errors.XenError('LVMProvisionAttach')
1537 if self.cowutil.isCoalesceableOnRemote():
1538 # We make the chain RW before it's used so it can be coalesced online
1539 self._make_chain_rw()
1541 try:
1542 return self._attach()
1543 finally:
1544 if not self.sr.lvActivator.deactivateAll():
1545 util.SMlog("Failed to deactivate LVs back (%s)" % self.uuid)
1547 @override
1548 def detach(self, sr_uuid, vdi_uuid) -> None:
1549 util.SMlog("LVMVDI.detach for %s" % self.uuid)
1550 self._loadThis()
1551 already_deflated = (self.utilisation < \
1552 self.lvmcowutil.calcVolumeSize(self.size))
1553 needDeflate = True
1554 if not VdiType.isCowImage(self.vdi_type) or already_deflated:
1555 needDeflate = False
1556 elif self.sr.provision == "thick":
1557 needDeflate = False
1558 # except for snapshots, which are always deflated
1559 if self.sr.srcmd.cmd != 'vdi_detach_from_config':
1560 vdi_ref = self.sr.srcmd.params['vdi_ref']
1561 snap = self.session.xenapi.VDI.get_is_a_snapshot(vdi_ref)
1562 if snap:
1563 needDeflate = True
1565 if needDeflate:
1566 try:
1567 self._prepareThin(False, self.vdi_type)
1568 except:
1569 util.logException("_prepareThin")
1570 raise xs_errors.XenError('VDIUnavailable', opterr='deflate')
1572 try:
1573 self._detach()
1574 finally:
1575 if not self.sr.lvActivator.deactivateAll():
1576 raise xs_errors.XenError("SMGeneral", opterr="deactivation")
1578 # We only support offline resize
1579 @override
1580 def resize(self, sr_uuid, vdi_uuid, size) -> str:
1581 util.SMlog("LVMVDI.resize for %s" % self.uuid)
1582 if not self.sr.isMaster:
1583 raise xs_errors.XenError('LVMMaster')
1585 self._loadThis()
1586 if self.hidden:
1587 raise xs_errors.XenError('VDIUnavailable', opterr='hidden VDI')
1589 if size < self.size:
1590 util.SMlog('vdi_resize: shrinking not supported: ' + \
1591 '(current size: %d, new size: %d)' % (self.size, size))
1592 raise xs_errors.XenError('VDISize', opterr='shrinking not allowed')
1594 size = self.cowutil.validateAndRoundImageSize(int(size))
1596 if size == self.size:
1597 return VDI.VDI.get_params(self)
1599 if not VdiType.isCowImage(self.vdi_type):
1600 lvSizeOld = self.size
1601 lvSizeNew = util.roundup(lvutil.LVM_SIZE_INCREMENT, size)
1602 else:
1603 lvSizeOld = self.utilisation
1604 lvSizeNew = self.lvmcowutil.calcVolumeSize(size)
1605 if self.sr.provision == "thin":
1606 # VDI is currently deflated, so keep it deflated
1607 lvSizeNew = lvSizeOld
1608 assert(lvSizeNew >= lvSizeOld)
1609 spaceNeeded = lvSizeNew - lvSizeOld
1610 self.sr._ensureSpaceAvailable(spaceNeeded)
1612 oldSize = self.size
1613 if not VdiType.isCowImage(self.vdi_type):
1614 self.sr.lvmCache.setSize(self.lvname, lvSizeNew)
1615 self.size = self.sr.lvmCache.getSize(self.lvname)
1616 self.utilisation = self.size
1617 else:
1618 if lvSizeNew != lvSizeOld:
1619 self.lvmcowutil.inflate(self.sr.journaler, self.sr.uuid, self.uuid, self.vdi_type, lvSizeNew)
1620 if self.vdi_type == VdiType.QCOW2:
1621 # We only do this for QCOW2 since qemu-img need to read the chain to resize
1622 self._chainSetActive(True, True)
1623 self.cowutil.setSizeVirtFast(self.path, size)
1624 self.size = self.cowutil.getSizeVirt(self.path)
1625 self.utilisation = self.sr.lvmCache.getSize(self.lvname)
1627 vdi_ref = self.sr.srcmd.params['vdi_ref']
1628 self.session.xenapi.VDI.set_virtual_size(vdi_ref, str(self.size))
1629 self.session.xenapi.VDI.set_physical_utilisation(vdi_ref,
1630 str(self.utilisation))
1631 self.sr._updateStats(self.sr.uuid, self.size - oldSize)
1632 super(LVMVDI, self).resize_cbt(self.sr.uuid, self.uuid, self.size)
1633 if self.vdi_type == VdiType.QCOW2:
1634 self._chainSetActive(False, True)
1635 return VDI.VDI.get_params(self)
1637 @override
1638 def clone(self, sr_uuid, vdi_uuid) -> str:
1639 return self._do_snapshot(
1640 sr_uuid, vdi_uuid, VDI.SNAPSHOT_DOUBLE, cloneOp=True)
1642 @override
1643 def compose(self, sr_uuid, vdi1, vdi2) -> None:
1644 util.SMlog("LVMSR.compose for %s -> %s" % (vdi2, vdi1))
1645 if not VdiType.isCowImage(self.vdi_type):
1646 raise xs_errors.XenError('Unimplemented')
1648 parent_uuid = vdi1
1649 parent_lvname = LV_PREFIX[self.vdi_type] + parent_uuid
1650 assert(self.sr.lvmCache.checkLV(parent_lvname))
1651 parent_path = os.path.join(self.sr.path, parent_lvname)
1653 self.sr.lvActivator.activate(self.uuid, self.lvname, False)
1654 self.sr.lvActivator.activate(parent_uuid, parent_lvname, False)
1656 self.cowutil.setParent(self.path, parent_path, False)
1657 self.cowutil.setHidden(parent_path)
1658 self.sr.session.xenapi.VDI.set_managed(self.sr.srcmd.params['args'][0], False)
1660 if not blktap2.VDI.tap_refresh(self.session, self.sr.uuid, self.uuid,
1661 True):
1662 raise util.SMException("failed to refresh VDI %s" % self.uuid)
1664 util.SMlog("Compose done")
1666 def reset_leaf(self, sr_uuid, vdi_uuid):
1667 util.SMlog("LVMSR.reset_leaf for %s" % vdi_uuid)
1668 if not VdiType.isCowImage(self.vdi_type):
1669 raise xs_errors.XenError('Unimplemented')
1671 self.sr.lvActivator.activate(self.uuid, self.lvname, False)
1673 # safety check
1674 if not self.cowutil.hasParent(self.path):
1675 raise util.SMException("ERROR: VDI %s has no parent, " + \
1676 "will not reset contents" % self.uuid)
1678 self.cowutil.killData(self.path)
1680 def _attach(self):
1681 self._chainSetActive(True, True, True)
1682 if not util.pathexists(self.path):
1683 raise xs_errors.XenError('VDIUnavailable', \
1684 opterr='Could not find: %s' % self.path)
1686 if not hasattr(self, 'xenstore_data'):
1687 self.xenstore_data = {}
1689 self.xenstore_data.update(scsiutil.update_XS_SCSIdata(self.uuid, \
1690 scsiutil.gen_synthetic_page_data(self.uuid)))
1692 self.xenstore_data['storage-type'] = 'lvm'
1693 self.xenstore_data['vdi-type'] = self.vdi_type
1695 self.attached = True
1696 self.sr.lvActivator.persist()
1697 return VDI.VDI.attach(self, self.sr.uuid, self.uuid)
1699 def _detach(self):
1700 self._chainSetActive(False, True)
1701 self.attached = False
1703 @override
1704 def _do_snapshot(self, sr_uuid, vdi_uuid, snapType,
1705 cloneOp=False, secondary=None, cbtlog=None, is_mirror_destination=False) -> str:
1706 # If cbt enabled, save file consistency state
1707 if cbtlog is not None:
1708 if blktap2.VDI.tap_status(self.session, vdi_uuid): 1708 ↛ 1709line 1708 didn't jump to line 1709, because the condition on line 1708 was never true
1709 consistency_state = False
1710 else:
1711 consistency_state = True
1712 util.SMlog("Saving log consistency state of %s for vdi: %s" %
1713 (consistency_state, vdi_uuid))
1714 else:
1715 consistency_state = None
1717 pause_time = time.time()
1718 if not blktap2.VDI.tap_pause(self.session, sr_uuid, vdi_uuid): 1718 ↛ 1719line 1718 didn't jump to line 1719, because the condition on line 1718 was never true
1719 raise util.SMException("failed to pause VDI %s" % vdi_uuid)
1721 snapResult = None
1722 try:
1723 snapResult = self._snapshot(snapType, cloneOp, cbtlog, consistency_state, is_mirror_destination)
1724 except Exception as e1:
1725 try:
1726 blktap2.VDI.tap_unpause(self.session, sr_uuid, vdi_uuid,
1727 secondary=None)
1728 except Exception as e2:
1729 util.SMlog('WARNING: failed to clean up failed snapshot: '
1730 '%s (error ignored)' % e2)
1731 raise
1732 self.disable_leaf_on_secondary(vdi_uuid, secondary=secondary)
1733 blktap2.VDI.tap_unpause(self.session, sr_uuid, vdi_uuid, secondary)
1734 unpause_time = time.time()
1735 if (unpause_time - pause_time) > LONG_SNAPTIME: 1735 ↛ 1736line 1735 didn't jump to line 1736, because the condition on line 1735 was never true
1736 util.SMlog('WARNING: snapshot paused VM for %s seconds' %
1737 (unpause_time - pause_time))
1738 return snapResult
1740 def _snapshot(self, snapType, cloneOp=False, cbtlog=None, cbt_consistency=None, is_mirror_destination=False):
1741 util.SMlog("LVMVDI._snapshot for %s (type %s)" % (self.uuid, snapType))
1743 if not self.sr.isMaster: 1743 ↛ 1744line 1743 didn't jump to line 1744, because the condition on line 1743 was never true
1744 raise xs_errors.XenError('LVMMaster')
1745 if self.sr.legacyMode: 1745 ↛ 1746line 1745 didn't jump to line 1746, because the condition on line 1745 was never true
1746 raise xs_errors.XenError('Unimplemented', opterr='In legacy mode')
1748 self._loadThis()
1749 if self.hidden: 1749 ↛ 1750line 1749 didn't jump to line 1750, because the condition on line 1749 was never true
1750 raise xs_errors.XenError('VDISnapshot', opterr='hidden VDI')
1752 snapVdiType = self.sr._get_snap_vdi_type(self.vdi_type, self.size)
1754 self.sm_config = self.session.xenapi.VDI.get_sm_config( \
1755 self.sr.srcmd.params['vdi_ref'])
1756 if "type" in self.sm_config and self.sm_config['type'] == 'raw': 1756 ↛ 1757line 1756 didn't jump to line 1757, because the condition on line 1756 was never true
1757 if not util.fistpoint.is_active("testsm_clone_allow_raw"):
1758 raise xs_errors.XenError('Unimplemented', \
1759 opterr='Raw VDI, snapshot or clone not permitted')
1761 # we must activate the entire image chain because the real parent could
1762 # theoretically be anywhere in the chain if all images under it are empty
1763 self._chainSetActive(True, False)
1764 if not util.pathexists(self.path): 1764 ↛ 1765line 1764 didn't jump to line 1765, because the condition on line 1764 was never true
1765 raise xs_errors.XenError('VDIUnavailable', \
1766 opterr='VDI unavailable: %s' % (self.path))
1768 if VdiType.isCowImage(self.vdi_type): 1768 ↛ 1776line 1768 didn't jump to line 1776, because the condition on line 1768 was never false
1769 depth = self.cowutil.getDepth(self.path)
1770 if depth == -1: 1770 ↛ 1771line 1770 didn't jump to line 1771, because the condition on line 1770 was never true
1771 raise xs_errors.XenError('VDIUnavailable', \
1772 opterr='failed to get COW depth')
1773 elif depth >= self.cowutil.getMaxChainLength(): 1773 ↛ 1774line 1773 didn't jump to line 1774, because the condition on line 1773 was never true
1774 raise xs_errors.XenError('SnapshotChainTooLong')
1776 self.issnap = self.session.xenapi.VDI.get_is_a_snapshot( \
1777 self.sr.srcmd.params['vdi_ref'])
1779 fullpr = self.lvmcowutil.calcVolumeSize(self.size)
1780 thinpr = util.roundup(
1781 lvutil.LVM_SIZE_INCREMENT,
1782 self.cowutil.calcOverheadEmpty(max(self.size, self.cowutil.getDefaultPreallocationSizeVirt()))
1783 )
1784 lvSizeOrig = thinpr
1785 lvSizeClon = thinpr
1787 hostRefs = []
1788 if self.sr.cmd == "vdi_snapshot":
1789 hostRefs = util.get_hosts_attached_on(self.session, [self.uuid])
1790 if hostRefs: 1790 ↛ 1792line 1790 didn't jump to line 1792, because the condition on line 1790 was never false
1791 lvSizeOrig = fullpr
1792 if self.sr.provision == "thick": 1792 ↛ 1798line 1792 didn't jump to line 1798, because the condition on line 1792 was never false
1793 if not self.issnap: 1793 ↛ 1794line 1793 didn't jump to line 1794, because the condition on line 1793 was never true
1794 lvSizeOrig = fullpr
1795 if self.sr.cmd != "vdi_snapshot":
1796 lvSizeClon = fullpr
1798 if (snapType == VDI.SNAPSHOT_SINGLE or 1798 ↛ 1800line 1798 didn't jump to line 1800, because the condition on line 1798 was never true
1799 snapType == VDI.SNAPSHOT_INTERNAL):
1800 lvSizeClon = 0
1802 # the space required must include 2 journal LVs: a clone journal and an
1803 # inflate journal (for the failure handling
1804 size_req = lvSizeOrig + lvSizeClon + 2 * self.sr.journaler.LV_SIZE
1805 lvSizeBase = self.size
1806 if VdiType.isCowImage(self.vdi_type): 1806 ↛ 1809line 1806 didn't jump to line 1809, because the condition on line 1806 was never false
1807 lvSizeBase = util.roundup(lvutil.LVM_SIZE_INCREMENT, self.cowutil.getSizePhys(self.path))
1808 size_req -= (self.utilisation - lvSizeBase)
1809 self.sr._ensureSpaceAvailable(size_req)
1811 if hostRefs:
1812 self.sr._deactivateOnSlave(hostRefs, self.lvname)
1814 baseUuid = util.gen_uuid()
1815 origUuid = self.uuid
1816 clonUuid = ""
1817 if snapType == VDI.SNAPSHOT_DOUBLE: 1817 ↛ 1819line 1817 didn't jump to line 1819, because the condition on line 1817 was never false
1818 clonUuid = util.gen_uuid()
1819 jval = "%s_%s" % (baseUuid, clonUuid)
1820 self.sr.journaler.create(self.JRN_CLONE, origUuid, jval)
1821 util.fistpoint.activate("LVHDRT_clone_vdi_after_create_journal", self.sr.uuid)
1823 try:
1824 # self becomes the "base vdi"
1825 origOldLV = self.lvname
1826 baseLV = LV_PREFIX[self.vdi_type] + baseUuid
1827 self.sr.lvmCache.rename(self.lvname, baseLV)
1828 self.sr.lvActivator.replace(self.uuid, baseUuid, baseLV, False)
1829 RefCounter.set(baseUuid, 1, 0, NS_PREFIX_LVM + self.sr.uuid)
1830 self.uuid = baseUuid
1831 self.lvname = baseLV
1832 self.path = os.path.join(self.sr.path, baseLV)
1833 self.label = "base copy"
1834 self.read_only = True
1835 self.location = self.uuid
1836 self.managed = False
1838 # shrink the base copy to the minimum - we do it before creating
1839 # the snapshot volumes to avoid requiring double the space
1840 if VdiType.isCowImage(self.vdi_type): 1840 ↛ 1843line 1840 didn't jump to line 1843, because the condition on line 1840 was never false
1841 self.lvmcowutil.deflate(self.sr.lvmCache, self.lvname, lvSizeBase)
1842 self.utilisation = lvSizeBase
1843 util.fistpoint.activate("LVHDRT_clone_vdi_after_shrink_parent", self.sr.uuid)
1845 snapVDI = self._createSnap(origUuid, snapVdiType, lvSizeOrig, False, is_mirror_destination)
1846 util.fistpoint.activate("LVHDRT_clone_vdi_after_first_snap", self.sr.uuid)
1847 snapVDI2 = None
1848 if snapType == VDI.SNAPSHOT_DOUBLE: 1848 ↛ 1854line 1848 didn't jump to line 1854, because the condition on line 1848 was never false
1849 snapVDI2 = self._createSnap(clonUuid, snapVdiType, lvSizeClon, True)
1850 # If we have CBT enabled on the VDI,
1851 # set CBT status for the new snapshot disk
1852 if cbtlog:
1853 snapVDI2.cbt_enabled = True
1854 util.fistpoint.activate("LVHDRT_clone_vdi_after_second_snap", self.sr.uuid)
1856 # note: it is important to mark the parent hidden only AFTER the
1857 # new image children have been created, which are referencing it;
1858 # otherwise we would introduce a race with GC that could reclaim
1859 # the parent before we snapshot it
1860 if not VdiType.isCowImage(self.vdi_type): 1860 ↛ 1861line 1860 didn't jump to line 1861, because the condition on line 1860 was never true
1861 self.sr.lvmCache.setHidden(self.lvname)
1862 else:
1863 self.cowutil.setHidden(self.path)
1864 util.fistpoint.activate("LVHDRT_clone_vdi_after_parent_hidden", self.sr.uuid)
1866 # set the base copy to ReadOnly
1867 self.sr.lvmCache.setReadonly(self.lvname, True)
1868 util.fistpoint.activate("LVHDRT_clone_vdi_after_parent_ro", self.sr.uuid)
1870 if hostRefs:
1871 self.sr._updateSlavesOnClone(hostRefs, origOldLV,
1872 snapVDI.lvname, self.uuid, self.lvname)
1874 # Update cbt files if user created snapshot (SNAPSHOT_DOUBLE)
1875 if snapType == VDI.SNAPSHOT_DOUBLE and cbtlog:
1876 snapVDI._cbt_snapshot(clonUuid, cbt_consistency)
1877 if hostRefs: 1877 ↛ 1891line 1877 didn't jump to line 1891, because the condition on line 1877 was never false
1878 cbtlog_file = self._get_cbt_logname(snapVDI.uuid)
1879 try:
1880 self.sr._updateSlavesOnCBTClone(hostRefs, cbtlog_file)
1881 except:
1882 alert_name = "VDI_CBT_SNAPSHOT_FAILED"
1883 alert_str = ("Creating CBT snapshot for {} failed"
1884 .format(snapVDI.uuid))
1885 snapVDI._disable_cbt_on_error(alert_name, alert_str)
1886 pass
1888 except (util.SMException, XenAPI.Failure) as e:
1889 util.logException("LVMVDI._snapshot")
1890 self._failClone(origUuid, jval, str(e))
1891 util.fistpoint.activate("LVHDRT_clone_vdi_before_remove_journal", self.sr.uuid)
1893 self.sr.journaler.remove(self.JRN_CLONE, origUuid)
1895 return self._finishSnapshot(snapVDI, snapVDI2, hostRefs, cloneOp, snapType)
1897 def _createSnap(self, snapUuid, snapVdiType, snapSizeLV, isNew, is_mirror_destination=False):
1898 """Snapshot self and return the snapshot VDI object"""
1900 snapLV = LV_PREFIX[snapVdiType] + snapUuid
1901 snapPath = os.path.join(self.sr.path, snapLV)
1902 self.sr.lvmCache.create(snapLV, int(snapSizeLV))
1903 util.fistpoint.activate("LVHDRT_clone_vdi_after_lvcreate", self.sr.uuid)
1904 if isNew:
1905 RefCounter.set(snapUuid, 1, 0, NS_PREFIX_LVM + self.sr.uuid)
1906 self.sr.lvActivator.add(snapUuid, snapLV, False)
1907 parentRaw = (self.vdi_type == VdiType.RAW)
1908 self.cowutil.snapshot(
1909 snapPath, self.path, parentRaw, max(self.size, self.cowutil.getDefaultPreallocationSizeVirt()), is_mirror_image=is_mirror_destination
1910 )
1911 snapParent = self.cowutil.getParent(snapPath, LvmCowUtil.extractUuid)
1913 snapVDI = LVMVDI(self.sr, snapUuid)
1914 snapVDI.read_only = False
1915 snapVDI.location = snapUuid
1916 snapVDI.size = self.size
1917 snapVDI.utilisation = snapSizeLV
1918 snapVDI.sm_config = dict()
1919 for key, val in self.sm_config.items(): 1919 ↛ 1920line 1919 didn't jump to line 1920, because the loop on line 1919 never started
1920 if key not in [
1921 "type", "vdi_type", "vhd-parent", "paused", "relinking", "activating"] and \
1922 not key.startswith("host_"):
1923 snapVDI.sm_config[key] = val
1924 snapVDI.sm_config["vdi_type"] = snapVdiType
1925 snapVDI.sm_config["vhd-parent"] = snapParent
1926 # TODO: fix the raw snapshot case
1927 snapVDI.sm_config["image-format"] = getImageStringFromVdiType(self.vdi_type)
1928 snapVDI.lvname = snapLV
1929 return snapVDI
1931 def _finishSnapshot(self, snapVDI, snapVDI2, hostRefs, cloneOp=False, snapType=None):
1932 if snapType is not VDI.SNAPSHOT_INTERNAL: 1932 ↛ 1934line 1932 didn't jump to line 1934, because the condition on line 1932 was never false
1933 self.sr._updateStats(self.sr.uuid, self.size)
1934 basePresent = True
1936 # Verify parent locator field of both children and delete basePath if
1937 # unused
1938 snapParent = snapVDI.sm_config["vhd-parent"]
1939 snap2Parent = ""
1940 if snapVDI2: 1940 ↛ 1942line 1940 didn't jump to line 1942, because the condition on line 1940 was never false
1941 snap2Parent = snapVDI2.sm_config["vhd-parent"]
1942 if snapParent != self.uuid and \ 1942 ↛ 1976line 1942 didn't jump to line 1976, because the condition on line 1942 was never false
1943 (not snapVDI2 or snap2Parent != self.uuid):
1944 util.SMlog("%s != %s != %s => deleting unused base %s" % \
1945 (snapParent, self.uuid, snap2Parent, self.lvname))
1946 RefCounter.put(self.uuid, False, NS_PREFIX_LVM + self.sr.uuid)
1948 # The removed LV could still be activated on a slave host if it's
1949 # part of a VM currently running there, we need to deactivate it
1950 # before it gets removed to avoid a LV leak.
1951 if hostRefs:
1952 self.sr._deactivateOnSlave(hostRefs, self.lvname)
1954 self.sr.lvmCache.remove(self.lvname)
1955 self.sr.lvActivator.remove(self.uuid, False)
1956 if hostRefs:
1957 self.sr._updateSlavesOnRemove(hostRefs, self.uuid, self.lvname)
1958 basePresent = False
1959 else:
1960 # assign the _binary_ refcount of the original VDI to the new base
1961 # VDI (but as the normal refcount, since binary refcounts are only
1962 # for leaf nodes). The normal refcount of the child is not
1963 # transferred to to the base VDI because normal refcounts are
1964 # incremented and decremented individually, and not based on the
1965 # image chain (i.e., the child's normal refcount will be decremented
1966 # independently of its parent situation). Add 1 for this clone op.
1967 # Note that we do not need to do protect the refcount operations
1968 # below with per-VDI locking like we do in lvutil because at this
1969 # point we have exclusive access to the VDIs involved. Other SM
1970 # operations are serialized by the Agent or with the SR lock, and
1971 # any coalesce activations are serialized with the SR lock. (The
1972 # coalesce activates the coalesced VDI pair in the beginning, which
1973 # cannot affect the VDIs here because they cannot possibly be
1974 # involved in coalescing at this point, and at the relinkSkip step
1975 # that activates the children, which takes the SR lock.)
1976 ns = NS_PREFIX_LVM + self.sr.uuid
1977 (cnt, bcnt) = RefCounter.check(snapVDI.uuid, ns)
1978 RefCounter.set(self.uuid, bcnt + 1, 0, ns)
1980 # the "paused" and "host_*" sm-config keys are special and must stay on
1981 # the leaf without being inherited by anyone else
1982 for key in [x for x in self.sm_config.keys() if x == "paused" or x.startswith("host_")]: 1982 ↛ 1983line 1982 didn't jump to line 1983, because the loop on line 1982 never started
1983 snapVDI.sm_config[key] = self.sm_config[key]
1984 del self.sm_config[key]
1986 # Introduce any new VDI records & update the existing one
1987 type = self.session.xenapi.VDI.get_type( \
1988 self.sr.srcmd.params['vdi_ref'])
1989 if snapVDI2: 1989 ↛ 2031line 1989 didn't jump to line 2031, because the condition on line 1989 was never false
1990 LVMMetadataHandler(self.sr.mdpath).ensureSpaceIsAvailableForVdis(1)
1991 vdiRef = snapVDI2._db_introduce()
1992 if cloneOp:
1993 vdi_info = {UUID_TAG: snapVDI2.uuid,
1994 NAME_LABEL_TAG: util.to_plain_string( \
1995 self.session.xenapi.VDI.get_name_label( \
1996 self.sr.srcmd.params['vdi_ref'])),
1997 NAME_DESCRIPTION_TAG: util.to_plain_string( \
1998 self.session.xenapi.VDI.get_name_description(self.sr.srcmd.params['vdi_ref'])),
1999 IS_A_SNAPSHOT_TAG: 0,
2000 SNAPSHOT_OF_TAG: '',
2001 SNAPSHOT_TIME_TAG: '',
2002 TYPE_TAG: type,
2003 VDI_TYPE_TAG: snapVDI2.sm_config['vdi_type'],
2004 READ_ONLY_TAG: 0,
2005 MANAGED_TAG: int(snapVDI2.managed),
2006 METADATA_OF_POOL_TAG: ''
2007 }
2008 else:
2009 util.SMlog("snapshot VDI params: %s" % \
2010 self.session.xenapi.VDI.get_snapshot_time(vdiRef))
2011 vdi_info = {UUID_TAG: snapVDI2.uuid,
2012 NAME_LABEL_TAG: util.to_plain_string( \
2013 self.session.xenapi.VDI.get_name_label( \
2014 self.sr.srcmd.params['vdi_ref'])),
2015 NAME_DESCRIPTION_TAG: util.to_plain_string( \
2016 self.session.xenapi.VDI.get_name_description(self.sr.srcmd.params['vdi_ref'])),
2017 IS_A_SNAPSHOT_TAG: 1,
2018 SNAPSHOT_OF_TAG: snapVDI.uuid,
2019 SNAPSHOT_TIME_TAG: '',
2020 TYPE_TAG: type,
2021 VDI_TYPE_TAG: snapVDI2.sm_config['vdi_type'],
2022 READ_ONLY_TAG: 0,
2023 MANAGED_TAG: int(snapVDI2.managed),
2024 METADATA_OF_POOL_TAG: ''
2025 }
2027 LVMMetadataHandler(self.sr.mdpath).addVdi(vdi_info)
2028 util.SMlog("vdi_clone: introduced 2nd snap VDI: %s (%s)" % \
2029 (vdiRef, snapVDI2.uuid))
2031 if basePresent: 2031 ↛ 2032line 2031 didn't jump to line 2032, because the condition on line 2031 was never true
2032 LVMMetadataHandler(self.sr.mdpath).ensureSpaceIsAvailableForVdis(1)
2033 vdiRef = self._db_introduce()
2034 vdi_info = {UUID_TAG: self.uuid,
2035 NAME_LABEL_TAG: self.label,
2036 NAME_DESCRIPTION_TAG: self.description,
2037 IS_A_SNAPSHOT_TAG: 0,
2038 SNAPSHOT_OF_TAG: '',
2039 SNAPSHOT_TIME_TAG: '',
2040 TYPE_TAG: type,
2041 VDI_TYPE_TAG: self.sm_config['vdi_type'],
2042 READ_ONLY_TAG: 1,
2043 MANAGED_TAG: 0,
2044 METADATA_OF_POOL_TAG: ''
2045 }
2047 LVMMetadataHandler(self.sr.mdpath).addVdi(vdi_info)
2048 util.SMlog("vdi_clone: introduced base VDI: %s (%s)" % \
2049 (vdiRef, self.uuid))
2051 # Update the original record
2052 vdi_ref = self.sr.srcmd.params['vdi_ref']
2053 self.session.xenapi.VDI.set_sm_config(vdi_ref, snapVDI.sm_config)
2054 self.session.xenapi.VDI.set_physical_utilisation(vdi_ref, \
2055 str(snapVDI.utilisation))
2057 # Return the info on the new snap VDI
2058 snap = snapVDI2
2059 if not snap: 2059 ↛ 2060line 2059 didn't jump to line 2060, because the condition on line 2059 was never true
2060 snap = self
2061 if not basePresent:
2062 # a single-snapshot of an empty VDI will be a noop, resulting
2063 # in no new VDIs, so return the existing one. The GC wouldn't
2064 # normally try to single-snapshot an empty image of course, but
2065 # if an external snapshot operation manages to sneak in right
2066 # before a snapshot-coalesce phase, we would get here
2067 snap = snapVDI
2068 return snap.get_params()
2070 def _setType(self, vdiType: str) -> None:
2071 self.vdi_type = vdiType
2072 self.cowutil = getCowUtil(self.vdi_type)
2073 self.lvmcowutil = LvmCowUtil(self.cowutil)
2075 def _initFromVDIInfo(self, vdiInfo):
2076 self._setType(vdiInfo.vdiType)
2077 self.lvname = vdiInfo.lvName
2078 self.size = vdiInfo.sizeVirt
2079 self.utilisation = vdiInfo.sizeLV
2080 self.hidden = vdiInfo.hidden
2081 if self.hidden: 2081 ↛ 2082line 2081 didn't jump to line 2082, because the condition on line 2081 was never true
2082 self.managed = False
2083 self.active = vdiInfo.lvActive
2084 self.readonly = vdiInfo.lvReadonly
2085 self.parent = vdiInfo.parentUuid
2086 self.path = os.path.join(self.sr.path, self.lvname)
2087 if hasattr(self, "sm_config_override"): 2087 ↛ 2090line 2087 didn't jump to line 2090, because the condition on line 2087 was never false
2088 self.sm_config_override["vdi_type"] = self.vdi_type
2089 else:
2090 self.sm_config_override = {'vdi_type': self.vdi_type}
2091 self.loaded = True
2093 def _initFromLVInfo(self, lvInfo):
2094 self._setType(lvInfo.vdiType)
2095 self.lvname = lvInfo.name
2096 self.size = lvInfo.size
2097 self.utilisation = lvInfo.size
2098 self.hidden = lvInfo.hidden
2099 self.active = lvInfo.active
2100 self.readonly = lvInfo.readonly
2101 self.parent = ''
2102 self.path = os.path.join(self.sr.path, self.lvname)
2103 if hasattr(self, "sm_config_override"): 2103 ↛ 2106line 2103 didn't jump to line 2106, because the condition on line 2103 was never false
2104 self.sm_config_override["vdi_type"] = self.vdi_type
2105 else:
2106 self.sm_config_override = {'vdi_type': self.vdi_type}
2107 if 'vhd-parent' in self.sm_config_override: 2107 ↛ 2108line 2107 didn't jump to line 2108, because the condition on line 2107 was never true
2108 self.parent = self.sm_config_override['vhd-parent']
2109 if not VdiType.isCowImage(self.vdi_type): 2109 ↛ 2110line 2109 didn't jump to line 2110, because the condition on line 2109 was never true
2110 self.loaded = True
2112 def _initFromImageInfo(self, imageInfo):
2113 self.size = imageInfo.sizeVirt
2114 if self.parent == '' or (imageInfo.parentUuid != '' and imageInfo.parentUuid != self.parent): 2114 ↛ 2116line 2114 didn't jump to line 2116, because the condition on line 2114 was never false
2115 self.parent = imageInfo.parentUuid
2116 self.hidden = imageInfo.hidden
2117 self.loaded = True
2119 def _determineType(self):
2120 """
2121 Determine whether this is a RAW or a COW VDI.
2122 """
2123 if "vdi_ref" in self.sr.srcmd.params:
2124 vdi_ref = self.sr.srcmd.params["vdi_ref"]
2125 sm_config = self.session.xenapi.VDI.get_sm_config(vdi_ref)
2126 if sm_config.get("vdi_type"): 2126 ↛ 2127line 2126 didn't jump to line 2127, because the condition on line 2126 was never true
2127 self._setType(sm_config["vdi_type"])
2128 prefix = LV_PREFIX[self.vdi_type]
2129 self.lvname = "%s%s" % (prefix, self.uuid)
2130 self.path = os.path.join(self.sr.path, self.lvname)
2131 self.sm_config_override = sm_config
2132 return True
2134 # LVM commands can be costly, so check the file directly first in case
2135 # the LV is active
2136 found = False
2137 for vdi_type, prefix in LV_PREFIX.items():
2138 lvname = "%s%s" % (prefix, self.uuid)
2139 path = os.path.join(self.sr.path, lvname)
2140 if util.pathexists(path):
2141 if found: 2141 ↛ 2142line 2141 didn't jump to line 2142, because the condition on line 2141 was never true
2142 raise xs_errors.XenError('VDILoad',
2143 opterr="multiple VDI's: uuid %s" % self.uuid)
2144 found = True
2145 self._setType(vdi_type)
2146 self.lvname = lvname
2147 self.path = path
2148 if found:
2149 return True
2151 # now list all LV's
2152 if not lvutil._checkVG(self.sr.vgname): 2152 ↛ 2154line 2152 didn't jump to line 2154, because the condition on line 2152 was never true
2153 # when doing attach_from_config, the VG won't be there yet
2154 return False
2156 lvs = LvmCowUtil.getVolumeInfo(self.sr.lvmCache)
2157 if lvs.get(self.uuid): 2157 ↛ 2160line 2157 didn't jump to line 2160, because the condition on line 2157 was never false
2158 self._initFromLVInfo(lvs[self.uuid])
2159 return True
2160 return False
2162 def _loadThis(self):
2163 """
2164 Load VDI info for this VDI and activate the LV if it's COW. We
2165 don't do it in VDI.load() because not all VDI operations need it.
2166 """
2167 if self.loaded: 2167 ↛ 2168line 2167 didn't jump to line 2168, because the condition on line 2167 was never true
2168 if VdiType.isCowImage(self.vdi_type):
2169 self.sr.lvActivator.activate(self.uuid, self.lvname, False)
2170 return
2171 try:
2172 lvs = LvmCowUtil.getVolumeInfo(self.sr.lvmCache, self.lvname)
2173 except util.CommandException as e:
2174 raise xs_errors.XenError('VDIUnavailable',
2175 opterr='%s (LV scan error)' % os.strerror(abs(e.code)))
2176 if not lvs.get(self.uuid): 2176 ↛ 2177line 2176 didn't jump to line 2177, because the condition on line 2176 was never true
2177 raise xs_errors.XenError('VDIUnavailable', opterr='LV not found')
2178 self._initFromLVInfo(lvs[self.uuid])
2179 if VdiType.isCowImage(self.vdi_type): 2179 ↛ 2185line 2179 didn't jump to line 2185, because the condition on line 2179 was never false
2180 self.sr.lvActivator.activate(self.uuid, self.lvname, False)
2181 imageInfo = self.cowutil.getInfo(self.path, LvmCowUtil.extractUuid, False)
2182 if not imageInfo: 2182 ↛ 2183line 2182 didn't jump to line 2183, because the condition on line 2182 was never true
2183 raise xs_errors.XenError('VDIUnavailable', opterr='getInfo failed')
2184 self._initFromImageInfo(imageInfo)
2185 self.loaded = True
2187 def _chainSetActive(self, active, binary, persistent=False):
2188 if binary: 2188 ↛ 2189line 2188 didn't jump to line 2189, because the condition on line 2188 was never true
2189 (count, bcount) = RefCounter.checkLocked(self.uuid,
2190 NS_PREFIX_LVM + self.sr.uuid)
2191 if (active and bcount > 0) or (not active and bcount == 0):
2192 return # this is a redundant activation/deactivation call
2194 vdiList = {self.uuid: self.lvname}
2195 if VdiType.isCowImage(self.vdi_type): 2195 ↛ 2197line 2195 didn't jump to line 2197, because the condition on line 2195 was never false
2196 vdiList = self.cowutil.getParentChain(self.lvname, LvmCowUtil.extractUuid, self.sr.vgname)
2197 for uuid, lvName in vdiList.items(): 2197 ↛ 2198line 2197 didn't jump to line 2198, because the loop on line 2197 never started
2198 binaryParam = binary
2199 if uuid != self.uuid:
2200 binaryParam = False # binary param only applies to leaf nodes
2201 if active:
2202 self.sr.lvActivator.activate(uuid, lvName, binaryParam,
2203 persistent)
2204 else:
2205 # just add the LVs for deactivation in the final (cleanup)
2206 # step. The LVs must not have been activated during the current
2207 # operation
2208 self.sr.lvActivator.add(uuid, lvName, binaryParam)
2210 def _failClone(self, uuid, jval, msg):
2211 try:
2212 self.sr._handleInterruptedCloneOp(uuid, jval, True)
2213 self.sr.journaler.remove(self.JRN_CLONE, uuid)
2214 except Exception as e:
2215 util.SMlog('WARNING: failed to clean up failed snapshot: ' \
2216 ' %s (error ignored)' % e)
2217 raise xs_errors.XenError('VDIClone', opterr=msg)
2219 def _markHidden(self):
2220 if not VdiType.isCowImage(self.vdi_type):
2221 self.sr.lvmCache.setHidden(self.lvname)
2222 else:
2223 self.cowutil.setHidden(self.path)
2224 self.hidden = 1
2226 def _prepareThin(self, attach, vdiType):
2227 origUtilisation = self.sr.lvmCache.getSize(self.lvname)
2228 if self.sr.isMaster:
2229 # the master can prepare the VDI locally
2230 if attach:
2231 self.lvmcowutil.attachThin(self.sr.journaler, self.sr.uuid, self.uuid, self.vdi_type)
2232 else:
2233 self.lvmcowutil.detachThin(self.session, self.sr.lvmCache, self.sr.uuid, self.uuid, self.vdi_type)
2234 else:
2235 fn = "attach"
2236 if not attach:
2237 fn = "detach"
2238 pools = self.session.xenapi.pool.get_all()
2239 master = self.session.xenapi.pool.get_master(pools[0])
2240 rv = self.session.xenapi.host.call_plugin(
2241 master,
2242 self.sr.THIN_PLUGIN,
2243 fn,
2244 {
2245 "srUuid": self.sr.uuid,
2246 "vdiUuid": self.uuid,
2247 "vdiType": vdiType
2248 }
2249 )
2250 util.SMlog("call-plugin returned: %s" % rv)
2251 if not rv:
2252 raise Exception('plugin %s failed' % self.sr.THIN_PLUGIN)
2253 # refresh to pick up the size change on this slave
2254 self.sr.lvmCache.activateNoRefcount(self.lvname, True)
2256 self.utilisation = self.sr.lvmCache.getSize(self.lvname)
2257 if origUtilisation != self.utilisation:
2258 vdi_ref = self.sr.srcmd.params['vdi_ref']
2259 self.session.xenapi.VDI.set_physical_utilisation(vdi_ref,
2260 str(self.utilisation))
2261 stats = lvutil._getVGstats(self.sr.vgname)
2262 sr_utilisation = stats['physical_utilisation']
2263 self.session.xenapi.SR.set_physical_utilisation(self.sr.sr_ref,
2264 str(sr_utilisation))
2266 def _make_chain_rw(self):
2267 """
2268 In the case of QCOW2, we need the chain to be RW for the coalesce to be done by tapdisk.
2269 Setting the chain to be RW early avoids having to stop the tapdisk so LVM can acknowledge the chain RW on the slave.
2270 And while making the change to RW from the slave works without the refresh, it can cause LVM metadata corruption.
2271 """
2272 # TODO(XCPNG-3689): We could check if any VDI on the chain is RO before doing the call to master to change this
2273 if self.sr.isMaster:
2274 lvmcache = self.sr.lvmCache
2275 for uuid, lvName in self.cowutil.getParentChain(self.lvname, LvmCowUtil.extractUuid, self.sr.vgname).items():
2276 lvmcache.setReadonly(lvName, False)
2277 else:
2278 master = util.get_master_ref(self.session)
2279 response = self.session.xenapi.host.call_plugin(
2280 master,
2281 self.sr.PLUGIN_ON_SLAVE,
2282 "make_chain_rw",
2283 {
2284 "vgName": self.sr.vgname,
2285 "lvName": self.lvname,
2286 "vdiType": self.vdi_type
2287 }
2288 )
2289 util.SMlog(f"call-plugin returned: {response}")
2290 if not response:
2291 raise Exception(f"plugin {self.sr.PLUGIN_ON_SLAVE} failed")
2293 @override
2294 def update(self, sr_uuid, vdi_uuid) -> None:
2295 if self.sr.legacyMode:
2296 return
2298 #Synch the name_label of this VDI on storage with the name_label in XAPI
2299 vdi_ref = self.session.xenapi.VDI.get_by_uuid(self.uuid)
2300 update_map = {}
2301 update_map[METADATA_UPDATE_OBJECT_TYPE_TAG] = \
2302 METADATA_OBJECT_TYPE_VDI
2303 update_map[UUID_TAG] = self.uuid
2304 update_map[NAME_LABEL_TAG] = util.to_plain_string( \
2305 self.session.xenapi.VDI.get_name_label(vdi_ref))
2306 update_map[NAME_DESCRIPTION_TAG] = util.to_plain_string( \
2307 self.session.xenapi.VDI.get_name_description(vdi_ref))
2308 update_map[SNAPSHOT_TIME_TAG] = \
2309 self.session.xenapi.VDI.get_snapshot_time(vdi_ref)
2310 update_map[METADATA_OF_POOL_TAG] = \
2311 self.session.xenapi.VDI.get_metadata_of_pool(vdi_ref)
2312 LVMMetadataHandler(self.sr.mdpath).updateMetadata(update_map)
2314 @override
2315 def _ensure_cbt_space(self) -> None:
2316 # We need virtual_size to compute the size in case of a bigger VDI
2317 self.sr.ensureCBTSpace(self.size)
2319 @override
2320 def _create_cbt_log(self) -> str:
2321 logname = self._get_cbt_logname(self.uuid)
2322 logsize = max(util.roundup(CBT_BLOCK_SIZE, self.size//CBT_BLOCK_SIZE), self.sr.journaler.LV_SIZE)
2323 # We choose 4MiB as the minimum for the log size to maintain the old behavior and compute the correct amount
2324 # if we need a bigger LV for the CBT (can happen with big QCOW2)
2325 self.sr.lvmCache.create(logname, logsize, CBTLOG_TAG)
2326 logpath = super(LVMVDI, self)._create_cbt_log()
2327 self.sr.lvmCache.deactivateNoRefcount(logname)
2328 return logpath
2330 @override
2331 def _delete_cbt_log(self) -> None:
2332 logpath = self._get_cbt_logpath(self.uuid)
2333 if self._cbt_log_exists(logpath):
2334 logname = self._get_cbt_logname(self.uuid)
2335 self.sr.lvmCache.remove(logname)
2337 @override
2338 def _rename(self, oldpath, newpath) -> None:
2339 oldname = os.path.basename(oldpath)
2340 newname = os.path.basename(newpath)
2341 self.sr.lvmCache.rename(oldname, newname)
2343 @override
2344 def update_slaves_on_cbt_disable(self, cbtlog) -> None:
2345 args = {
2346 "vgName": self.sr.vgname,
2347 "action1": "deactivateNoRefcount",
2348 "lvName1": cbtlog
2349 }
2351 host_refs = util.get_hosts_attached_on(self.session, [self.uuid])
2353 message = f"Deactivating {cbtlog}"
2354 self.sr.call_on_slave(args, host_refs, message)
2356 @override
2357 def _activate_cbt_log(self, lv_name) -> bool:
2358 self.sr.lvmCache.refresh()
2359 if not self.sr.lvmCache.is_active(lv_name): 2359 ↛ 2360line 2359 didn't jump to line 2360, because the condition on line 2359 was never true
2360 try:
2361 self.sr.lvmCache.activateNoRefcount(lv_name)
2362 return True
2363 except Exception as e:
2364 util.SMlog("Exception in _activate_cbt_log, "
2365 "Error: %s." % str(e))
2366 raise
2367 else:
2368 return False
2370 @override
2371 def _deactivate_cbt_log(self, lv_name) -> None:
2372 try:
2373 self.sr.lvmCache.deactivateNoRefcount(lv_name)
2374 except Exception as e:
2375 util.SMlog("Exception in _deactivate_cbt_log, Error: %s." % str(e))
2376 raise
2378 @override
2379 def _cbt_log_exists(self, logpath) -> bool:
2380 return lvutil.exists(logpath)
2382if __name__ == '__main__': 2382 ↛ 2383line 2382 didn't jump to line 2383, because the condition on line 2382 was never true
2383 SRCommand.run(LVMSR, DRIVER_INFO)
2384else:
2385 SR.registerSR(LVMSR)