Coverage for drivers/LVMSR.py : 48%
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 self._setType(vdi_type)
1379 try:
1380 self.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 try:
1538 return self._attach()
1539 finally:
1540 if not self.sr.lvActivator.deactivateAll():
1541 util.SMlog("Failed to deactivate LVs back (%s)" % self.uuid)
1543 @override
1544 def detach(self, sr_uuid, vdi_uuid) -> None:
1545 util.SMlog("LVMVDI.detach for %s" % self.uuid)
1546 self._loadThis()
1547 already_deflated = (self.utilisation < \
1548 self.lvmcowutil.calcVolumeSize(self.size))
1549 needDeflate = True
1550 if not VdiType.isCowImage(self.vdi_type) or already_deflated:
1551 needDeflate = False
1552 elif self.sr.provision == "thick":
1553 needDeflate = False
1554 # except for snapshots, which are always deflated
1555 if self.sr.srcmd.cmd != 'vdi_detach_from_config':
1556 vdi_ref = self.sr.srcmd.params['vdi_ref']
1557 snap = self.session.xenapi.VDI.get_is_a_snapshot(vdi_ref)
1558 if snap:
1559 needDeflate = True
1561 if needDeflate:
1562 try:
1563 self._prepareThin(False, self.vdi_type)
1564 except:
1565 util.logException("_prepareThin")
1566 raise xs_errors.XenError('VDIUnavailable', opterr='deflate')
1568 try:
1569 self._detach()
1570 finally:
1571 if not self.sr.lvActivator.deactivateAll():
1572 raise xs_errors.XenError("SMGeneral", opterr="deactivation")
1574 # We only support offline resize
1575 @override
1576 def resize(self, sr_uuid, vdi_uuid, size) -> str:
1577 util.SMlog("LVMVDI.resize for %s" % self.uuid)
1578 if not self.sr.isMaster:
1579 raise xs_errors.XenError('LVMMaster')
1581 self._loadThis()
1582 if self.hidden:
1583 raise xs_errors.XenError('VDIUnavailable', opterr='hidden VDI')
1585 if size < self.size:
1586 util.SMlog('vdi_resize: shrinking not supported: ' + \
1587 '(current size: %d, new size: %d)' % (self.size, size))
1588 raise xs_errors.XenError('VDISize', opterr='shrinking not allowed')
1590 size = self.cowutil.validateAndRoundImageSize(int(size))
1592 if size == self.size:
1593 return VDI.VDI.get_params(self)
1595 if not VdiType.isCowImage(self.vdi_type):
1596 lvSizeOld = self.size
1597 lvSizeNew = util.roundup(lvutil.LVM_SIZE_INCREMENT, size)
1598 else:
1599 lvSizeOld = self.utilisation
1600 lvSizeNew = self.lvmcowutil.calcVolumeSize(size)
1601 if self.sr.provision == "thin":
1602 # VDI is currently deflated, so keep it deflated
1603 lvSizeNew = lvSizeOld
1604 assert(lvSizeNew >= lvSizeOld)
1605 spaceNeeded = lvSizeNew - lvSizeOld
1606 self.sr._ensureSpaceAvailable(spaceNeeded)
1608 oldSize = self.size
1609 if not VdiType.isCowImage(self.vdi_type):
1610 self.sr.lvmCache.setSize(self.lvname, lvSizeNew)
1611 self.size = self.sr.lvmCache.getSize(self.lvname)
1612 self.utilisation = self.size
1613 else:
1614 if lvSizeNew != lvSizeOld:
1615 self.lvmcowutil.inflate(self.sr.journaler, self.sr.uuid, self.uuid, self.vdi_type, lvSizeNew)
1616 if self.vdi_type == VdiType.QCOW2:
1617 # We only do this for QCOW2 since qemu-img need to read the chain to resize
1618 self._chainSetActive(True, True)
1619 self.cowutil.setSizeVirtFast(self.path, size)
1620 self.size = self.cowutil.getSizeVirt(self.path)
1621 self.utilisation = self.sr.lvmCache.getSize(self.lvname)
1623 vdi_ref = self.sr.srcmd.params['vdi_ref']
1624 self.session.xenapi.VDI.set_virtual_size(vdi_ref, str(self.size))
1625 self.session.xenapi.VDI.set_physical_utilisation(vdi_ref,
1626 str(self.utilisation))
1627 self.sr._updateStats(self.sr.uuid, self.size - oldSize)
1628 super(LVMVDI, self).resize_cbt(self.sr.uuid, self.uuid, self.size)
1629 if self.vdi_type == VdiType.QCOW2:
1630 self._chainSetActive(False, True)
1631 return VDI.VDI.get_params(self)
1633 @override
1634 def clone(self, sr_uuid, vdi_uuid) -> str:
1635 return self._do_snapshot(
1636 sr_uuid, vdi_uuid, VDI.SNAPSHOT_DOUBLE, cloneOp=True)
1638 @override
1639 def compose(self, sr_uuid, vdi1, vdi2) -> None:
1640 util.SMlog("LVMSR.compose for %s -> %s" % (vdi2, vdi1))
1641 if not VdiType.isCowImage(self.vdi_type):
1642 raise xs_errors.XenError('Unimplemented')
1644 parent_uuid = vdi1
1645 parent_lvname = LV_PREFIX[self.vdi_type] + parent_uuid
1646 assert(self.sr.lvmCache.checkLV(parent_lvname))
1647 parent_path = os.path.join(self.sr.path, parent_lvname)
1649 self.sr.lvActivator.activate(self.uuid, self.lvname, False)
1650 self.sr.lvActivator.activate(parent_uuid, parent_lvname, False)
1652 self.cowutil.setParent(self.path, parent_path, False)
1653 self.cowutil.setHidden(parent_path)
1654 self.sr.session.xenapi.VDI.set_managed(self.sr.srcmd.params['args'][0], False)
1656 if not blktap2.VDI.tap_refresh(self.session, self.sr.uuid, self.uuid,
1657 True):
1658 raise util.SMException("failed to refresh VDI %s" % self.uuid)
1660 util.SMlog("Compose done")
1662 def reset_leaf(self, sr_uuid, vdi_uuid):
1663 util.SMlog("LVMSR.reset_leaf for %s" % vdi_uuid)
1664 if not VdiType.isCowImage(self.vdi_type):
1665 raise xs_errors.XenError('Unimplemented')
1667 self.sr.lvActivator.activate(self.uuid, self.lvname, False)
1669 # safety check
1670 if not self.cowutil.hasParent(self.path):
1671 raise util.SMException("ERROR: VDI %s has no parent, " + \
1672 "will not reset contents" % self.uuid)
1674 self.cowutil.killData(self.path)
1676 def _attach(self):
1677 self._chainSetActive(True, True, True)
1678 if not util.pathexists(self.path):
1679 raise xs_errors.XenError('VDIUnavailable', \
1680 opterr='Could not find: %s' % self.path)
1682 if not hasattr(self, 'xenstore_data'):
1683 self.xenstore_data = {}
1685 self.xenstore_data.update(scsiutil.update_XS_SCSIdata(self.uuid, \
1686 scsiutil.gen_synthetic_page_data(self.uuid)))
1688 self.xenstore_data['storage-type'] = 'lvm'
1689 self.xenstore_data['vdi-type'] = self.vdi_type
1691 self.attached = True
1692 self.sr.lvActivator.persist()
1693 return VDI.VDI.attach(self, self.sr.uuid, self.uuid)
1695 def _detach(self):
1696 self._chainSetActive(False, True)
1697 self.attached = False
1699 @override
1700 def _do_snapshot(self, sr_uuid, vdi_uuid, snapType,
1701 cloneOp=False, secondary=None, cbtlog=None, is_mirror_destination=False) -> str:
1702 # If cbt enabled, save file consistency state
1703 if cbtlog is not None:
1704 if blktap2.VDI.tap_status(self.session, vdi_uuid): 1704 ↛ 1705line 1704 didn't jump to line 1705, because the condition on line 1704 was never true
1705 consistency_state = False
1706 else:
1707 consistency_state = True
1708 util.SMlog("Saving log consistency state of %s for vdi: %s" %
1709 (consistency_state, vdi_uuid))
1710 else:
1711 consistency_state = None
1713 pause_time = time.time()
1714 if not blktap2.VDI.tap_pause(self.session, sr_uuid, vdi_uuid): 1714 ↛ 1715line 1714 didn't jump to line 1715, because the condition on line 1714 was never true
1715 raise util.SMException("failed to pause VDI %s" % vdi_uuid)
1717 snapResult = None
1718 try:
1719 snapResult = self._snapshot(snapType, cloneOp, cbtlog, consistency_state, is_mirror_destination)
1720 except Exception as e1:
1721 try:
1722 blktap2.VDI.tap_unpause(self.session, sr_uuid, vdi_uuid,
1723 secondary=None)
1724 except Exception as e2:
1725 util.SMlog('WARNING: failed to clean up failed snapshot: '
1726 '%s (error ignored)' % e2)
1727 raise
1728 self.disable_leaf_on_secondary(vdi_uuid, secondary=secondary)
1729 blktap2.VDI.tap_unpause(self.session, sr_uuid, vdi_uuid, secondary)
1730 unpause_time = time.time()
1731 if (unpause_time - pause_time) > LONG_SNAPTIME: 1731 ↛ 1732line 1731 didn't jump to line 1732, because the condition on line 1731 was never true
1732 util.SMlog('WARNING: snapshot paused VM for %s seconds' %
1733 (unpause_time - pause_time))
1734 return snapResult
1736 def _snapshot(self, snapType, cloneOp=False, cbtlog=None, cbt_consistency=None, is_mirror_destination=False):
1737 util.SMlog("LVMVDI._snapshot for %s (type %s)" % (self.uuid, snapType))
1739 if not self.sr.isMaster: 1739 ↛ 1740line 1739 didn't jump to line 1740, because the condition on line 1739 was never true
1740 raise xs_errors.XenError('LVMMaster')
1741 if self.sr.legacyMode: 1741 ↛ 1742line 1741 didn't jump to line 1742, because the condition on line 1741 was never true
1742 raise xs_errors.XenError('Unimplemented', opterr='In legacy mode')
1744 self._loadThis()
1745 if self.hidden: 1745 ↛ 1746line 1745 didn't jump to line 1746, because the condition on line 1745 was never true
1746 raise xs_errors.XenError('VDISnapshot', opterr='hidden VDI')
1748 snapVdiType = self.sr._get_snap_vdi_type(self.vdi_type, self.size)
1750 self.sm_config = self.session.xenapi.VDI.get_sm_config( \
1751 self.sr.srcmd.params['vdi_ref'])
1752 if "type" in self.sm_config and self.sm_config['type'] == 'raw': 1752 ↛ 1753line 1752 didn't jump to line 1753, because the condition on line 1752 was never true
1753 if not util.fistpoint.is_active("testsm_clone_allow_raw"):
1754 raise xs_errors.XenError('Unimplemented', \
1755 opterr='Raw VDI, snapshot or clone not permitted')
1757 # we must activate the entire image chain because the real parent could
1758 # theoretically be anywhere in the chain if all images under it are empty
1759 self._chainSetActive(True, False)
1760 if not util.pathexists(self.path): 1760 ↛ 1761line 1760 didn't jump to line 1761, because the condition on line 1760 was never true
1761 raise xs_errors.XenError('VDIUnavailable', \
1762 opterr='VDI unavailable: %s' % (self.path))
1764 if VdiType.isCowImage(self.vdi_type): 1764 ↛ 1772line 1764 didn't jump to line 1772, because the condition on line 1764 was never false
1765 depth = self.cowutil.getDepth(self.path)
1766 if depth == -1: 1766 ↛ 1767line 1766 didn't jump to line 1767, because the condition on line 1766 was never true
1767 raise xs_errors.XenError('VDIUnavailable', \
1768 opterr='failed to get COW depth')
1769 elif depth >= self.cowutil.getMaxChainLength(): 1769 ↛ 1770line 1769 didn't jump to line 1770, because the condition on line 1769 was never true
1770 raise xs_errors.XenError('SnapshotChainTooLong')
1772 self.issnap = self.session.xenapi.VDI.get_is_a_snapshot( \
1773 self.sr.srcmd.params['vdi_ref'])
1775 fullpr = self.lvmcowutil.calcVolumeSize(self.size)
1776 thinpr = util.roundup(
1777 lvutil.LVM_SIZE_INCREMENT,
1778 self.cowutil.calcOverheadEmpty(max(self.size, self.cowutil.getDefaultPreallocationSizeVirt()))
1779 )
1780 lvSizeOrig = thinpr
1781 lvSizeClon = thinpr
1783 hostRefs = []
1784 if self.sr.cmd == "vdi_snapshot":
1785 hostRefs = util.get_hosts_attached_on(self.session, [self.uuid])
1786 if hostRefs: 1786 ↛ 1788line 1786 didn't jump to line 1788, because the condition on line 1786 was never false
1787 lvSizeOrig = fullpr
1788 if self.sr.provision == "thick": 1788 ↛ 1794line 1788 didn't jump to line 1794, because the condition on line 1788 was never false
1789 if not self.issnap: 1789 ↛ 1790line 1789 didn't jump to line 1790, because the condition on line 1789 was never true
1790 lvSizeOrig = fullpr
1791 if self.sr.cmd != "vdi_snapshot":
1792 lvSizeClon = fullpr
1794 if (snapType == VDI.SNAPSHOT_SINGLE or 1794 ↛ 1796line 1794 didn't jump to line 1796, because the condition on line 1794 was never true
1795 snapType == VDI.SNAPSHOT_INTERNAL):
1796 lvSizeClon = 0
1798 # the space required must include 2 journal LVs: a clone journal and an
1799 # inflate journal (for the failure handling
1800 size_req = lvSizeOrig + lvSizeClon + 2 * self.sr.journaler.LV_SIZE
1801 lvSizeBase = self.size
1802 if VdiType.isCowImage(self.vdi_type): 1802 ↛ 1805line 1802 didn't jump to line 1805, because the condition on line 1802 was never false
1803 lvSizeBase = util.roundup(lvutil.LVM_SIZE_INCREMENT, self.cowutil.getSizePhys(self.path))
1804 size_req -= (self.utilisation - lvSizeBase)
1805 self.sr._ensureSpaceAvailable(size_req)
1807 if hostRefs:
1808 self.sr._deactivateOnSlave(hostRefs, self.lvname)
1810 baseUuid = util.gen_uuid()
1811 origUuid = self.uuid
1812 clonUuid = ""
1813 if snapType == VDI.SNAPSHOT_DOUBLE: 1813 ↛ 1815line 1813 didn't jump to line 1815, because the condition on line 1813 was never false
1814 clonUuid = util.gen_uuid()
1815 jval = "%s_%s" % (baseUuid, clonUuid)
1816 self.sr.journaler.create(self.JRN_CLONE, origUuid, jval)
1817 util.fistpoint.activate("LVHDRT_clone_vdi_after_create_journal", self.sr.uuid)
1819 try:
1820 # self becomes the "base vdi"
1821 origOldLV = self.lvname
1822 baseLV = LV_PREFIX[self.vdi_type] + baseUuid
1823 self.sr.lvmCache.rename(self.lvname, baseLV)
1824 self.sr.lvActivator.replace(self.uuid, baseUuid, baseLV, False)
1825 RefCounter.set(baseUuid, 1, 0, NS_PREFIX_LVM + self.sr.uuid)
1826 self.uuid = baseUuid
1827 self.lvname = baseLV
1828 self.path = os.path.join(self.sr.path, baseLV)
1829 self.label = "base copy"
1830 self.read_only = True
1831 self.location = self.uuid
1832 self.managed = False
1834 # shrink the base copy to the minimum - we do it before creating
1835 # the snapshot volumes to avoid requiring double the space
1836 if VdiType.isCowImage(self.vdi_type): 1836 ↛ 1839line 1836 didn't jump to line 1839, because the condition on line 1836 was never false
1837 self.lvmcowutil.deflate(self.sr.lvmCache, self.lvname, lvSizeBase)
1838 self.utilisation = lvSizeBase
1839 util.fistpoint.activate("LVHDRT_clone_vdi_after_shrink_parent", self.sr.uuid)
1841 snapVDI = self._createSnap(origUuid, snapVdiType, lvSizeOrig, False, is_mirror_destination)
1842 util.fistpoint.activate("LVHDRT_clone_vdi_after_first_snap", self.sr.uuid)
1843 snapVDI2 = None
1844 if snapType == VDI.SNAPSHOT_DOUBLE: 1844 ↛ 1850line 1844 didn't jump to line 1850, because the condition on line 1844 was never false
1845 snapVDI2 = self._createSnap(clonUuid, snapVdiType, lvSizeClon, True)
1846 # If we have CBT enabled on the VDI,
1847 # set CBT status for the new snapshot disk
1848 if cbtlog:
1849 snapVDI2.cbt_enabled = True
1850 util.fistpoint.activate("LVHDRT_clone_vdi_after_second_snap", self.sr.uuid)
1852 # note: it is important to mark the parent hidden only AFTER the
1853 # new image children have been created, which are referencing it;
1854 # otherwise we would introduce a race with GC that could reclaim
1855 # the parent before we snapshot it
1856 if not VdiType.isCowImage(self.vdi_type): 1856 ↛ 1857line 1856 didn't jump to line 1857, because the condition on line 1856 was never true
1857 self.sr.lvmCache.setHidden(self.lvname)
1858 else:
1859 self.cowutil.setHidden(self.path)
1860 util.fistpoint.activate("LVHDRT_clone_vdi_after_parent_hidden", self.sr.uuid)
1862 # set the base copy to ReadOnly
1863 self.sr.lvmCache.setReadonly(self.lvname, True)
1864 util.fistpoint.activate("LVHDRT_clone_vdi_after_parent_ro", self.sr.uuid)
1866 if hostRefs:
1867 self.sr._updateSlavesOnClone(hostRefs, origOldLV,
1868 snapVDI.lvname, self.uuid, self.lvname)
1870 # Update cbt files if user created snapshot (SNAPSHOT_DOUBLE)
1871 if snapType == VDI.SNAPSHOT_DOUBLE and cbtlog:
1872 snapVDI._cbt_snapshot(clonUuid, cbt_consistency)
1873 if hostRefs: 1873 ↛ 1887line 1873 didn't jump to line 1887, because the condition on line 1873 was never false
1874 cbtlog_file = self._get_cbt_logname(snapVDI.uuid)
1875 try:
1876 self.sr._updateSlavesOnCBTClone(hostRefs, cbtlog_file)
1877 except:
1878 alert_name = "VDI_CBT_SNAPSHOT_FAILED"
1879 alert_str = ("Creating CBT snapshot for {} failed"
1880 .format(snapVDI.uuid))
1881 snapVDI._disable_cbt_on_error(alert_name, alert_str)
1882 pass
1884 except (util.SMException, XenAPI.Failure) as e:
1885 util.logException("LVMVDI._snapshot")
1886 self._failClone(origUuid, jval, str(e))
1887 util.fistpoint.activate("LVHDRT_clone_vdi_before_remove_journal", self.sr.uuid)
1889 self.sr.journaler.remove(self.JRN_CLONE, origUuid)
1891 return self._finishSnapshot(snapVDI, snapVDI2, hostRefs, cloneOp, snapType)
1893 def _createSnap(self, snapUuid, snapVdiType, snapSizeLV, isNew, is_mirror_destination=False):
1894 """Snapshot self and return the snapshot VDI object"""
1896 snapLV = LV_PREFIX[snapVdiType] + snapUuid
1897 snapPath = os.path.join(self.sr.path, snapLV)
1898 self.sr.lvmCache.create(snapLV, int(snapSizeLV))
1899 util.fistpoint.activate("LVHDRT_clone_vdi_after_lvcreate", self.sr.uuid)
1900 if isNew:
1901 RefCounter.set(snapUuid, 1, 0, NS_PREFIX_LVM + self.sr.uuid)
1902 self.sr.lvActivator.add(snapUuid, snapLV, False)
1903 parentRaw = (self.vdi_type == VdiType.RAW)
1904 self.cowutil.snapshot(
1905 snapPath, self.path, parentRaw, max(self.size, self.cowutil.getDefaultPreallocationSizeVirt()), is_mirror_image=is_mirror_destination
1906 )
1907 snapParent = self.cowutil.getParent(snapPath, LvmCowUtil.extractUuid)
1909 snapVDI = LVMVDI(self.sr, snapUuid)
1910 snapVDI.read_only = False
1911 snapVDI.location = snapUuid
1912 snapVDI.size = self.size
1913 snapVDI.utilisation = snapSizeLV
1914 snapVDI.sm_config = dict()
1915 for key, val in self.sm_config.items(): 1915 ↛ 1916line 1915 didn't jump to line 1916, because the loop on line 1915 never started
1916 if key not in [
1917 "type", "vdi_type", "vhd-parent", "paused", "relinking", "activating"] and \
1918 not key.startswith("host_"):
1919 snapVDI.sm_config[key] = val
1920 snapVDI.sm_config["vdi_type"] = snapVdiType
1921 snapVDI.sm_config["vhd-parent"] = snapParent
1922 # TODO: fix the raw snapshot case
1923 snapVDI.sm_config["image-format"] = getImageStringFromVdiType(self.vdi_type)
1924 snapVDI.lvname = snapLV
1925 return snapVDI
1927 def _finishSnapshot(self, snapVDI, snapVDI2, hostRefs, cloneOp=False, snapType=None):
1928 if snapType is not VDI.SNAPSHOT_INTERNAL: 1928 ↛ 1930line 1928 didn't jump to line 1930, because the condition on line 1928 was never false
1929 self.sr._updateStats(self.sr.uuid, self.size)
1930 basePresent = True
1932 # Verify parent locator field of both children and delete basePath if
1933 # unused
1934 snapParent = snapVDI.sm_config["vhd-parent"]
1935 snap2Parent = ""
1936 if snapVDI2: 1936 ↛ 1938line 1936 didn't jump to line 1938, because the condition on line 1936 was never false
1937 snap2Parent = snapVDI2.sm_config["vhd-parent"]
1938 if snapParent != self.uuid and \ 1938 ↛ 1972line 1938 didn't jump to line 1972, because the condition on line 1938 was never false
1939 (not snapVDI2 or snap2Parent != self.uuid):
1940 util.SMlog("%s != %s != %s => deleting unused base %s" % \
1941 (snapParent, self.uuid, snap2Parent, self.lvname))
1942 RefCounter.put(self.uuid, False, NS_PREFIX_LVM + self.sr.uuid)
1944 # The removed LV could still be activated on a slave host if it's
1945 # part of a VM currently running there, we need to deactivate it
1946 # before it gets removed to avoid a LV leak.
1947 if hostRefs:
1948 self.sr._deactivateOnSlave(hostRefs, self.lvname)
1950 self.sr.lvmCache.remove(self.lvname)
1951 self.sr.lvActivator.remove(self.uuid, False)
1952 if hostRefs:
1953 self.sr._updateSlavesOnRemove(hostRefs, self.uuid, self.lvname)
1954 basePresent = False
1955 else:
1956 # assign the _binary_ refcount of the original VDI to the new base
1957 # VDI (but as the normal refcount, since binary refcounts are only
1958 # for leaf nodes). The normal refcount of the child is not
1959 # transferred to to the base VDI because normal refcounts are
1960 # incremented and decremented individually, and not based on the
1961 # image chain (i.e., the child's normal refcount will be decremented
1962 # independently of its parent situation). Add 1 for this clone op.
1963 # Note that we do not need to do protect the refcount operations
1964 # below with per-VDI locking like we do in lvutil because at this
1965 # point we have exclusive access to the VDIs involved. Other SM
1966 # operations are serialized by the Agent or with the SR lock, and
1967 # any coalesce activations are serialized with the SR lock. (The
1968 # coalesce activates the coalesced VDI pair in the beginning, which
1969 # cannot affect the VDIs here because they cannot possibly be
1970 # involved in coalescing at this point, and at the relinkSkip step
1971 # that activates the children, which takes the SR lock.)
1972 ns = NS_PREFIX_LVM + self.sr.uuid
1973 (cnt, bcnt) = RefCounter.check(snapVDI.uuid, ns)
1974 RefCounter.set(self.uuid, bcnt + 1, 0, ns)
1976 # the "paused" and "host_*" sm-config keys are special and must stay on
1977 # the leaf without being inherited by anyone else
1978 for key in [x for x in self.sm_config.keys() if x == "paused" or x.startswith("host_")]: 1978 ↛ 1979line 1978 didn't jump to line 1979, because the loop on line 1978 never started
1979 snapVDI.sm_config[key] = self.sm_config[key]
1980 del self.sm_config[key]
1982 # Introduce any new VDI records & update the existing one
1983 type = self.session.xenapi.VDI.get_type( \
1984 self.sr.srcmd.params['vdi_ref'])
1985 if snapVDI2: 1985 ↛ 2027line 1985 didn't jump to line 2027, because the condition on line 1985 was never false
1986 LVMMetadataHandler(self.sr.mdpath).ensureSpaceIsAvailableForVdis(1)
1987 vdiRef = snapVDI2._db_introduce()
1988 if cloneOp:
1989 vdi_info = {UUID_TAG: snapVDI2.uuid,
1990 NAME_LABEL_TAG: util.to_plain_string( \
1991 self.session.xenapi.VDI.get_name_label( \
1992 self.sr.srcmd.params['vdi_ref'])),
1993 NAME_DESCRIPTION_TAG: util.to_plain_string( \
1994 self.session.xenapi.VDI.get_name_description(self.sr.srcmd.params['vdi_ref'])),
1995 IS_A_SNAPSHOT_TAG: 0,
1996 SNAPSHOT_OF_TAG: '',
1997 SNAPSHOT_TIME_TAG: '',
1998 TYPE_TAG: type,
1999 VDI_TYPE_TAG: snapVDI2.sm_config['vdi_type'],
2000 READ_ONLY_TAG: 0,
2001 MANAGED_TAG: int(snapVDI2.managed),
2002 METADATA_OF_POOL_TAG: ''
2003 }
2004 else:
2005 util.SMlog("snapshot VDI params: %s" % \
2006 self.session.xenapi.VDI.get_snapshot_time(vdiRef))
2007 vdi_info = {UUID_TAG: snapVDI2.uuid,
2008 NAME_LABEL_TAG: util.to_plain_string( \
2009 self.session.xenapi.VDI.get_name_label( \
2010 self.sr.srcmd.params['vdi_ref'])),
2011 NAME_DESCRIPTION_TAG: util.to_plain_string( \
2012 self.session.xenapi.VDI.get_name_description(self.sr.srcmd.params['vdi_ref'])),
2013 IS_A_SNAPSHOT_TAG: 1,
2014 SNAPSHOT_OF_TAG: snapVDI.uuid,
2015 SNAPSHOT_TIME_TAG: '',
2016 TYPE_TAG: type,
2017 VDI_TYPE_TAG: snapVDI2.sm_config['vdi_type'],
2018 READ_ONLY_TAG: 0,
2019 MANAGED_TAG: int(snapVDI2.managed),
2020 METADATA_OF_POOL_TAG: ''
2021 }
2023 LVMMetadataHandler(self.sr.mdpath).addVdi(vdi_info)
2024 util.SMlog("vdi_clone: introduced 2nd snap VDI: %s (%s)" % \
2025 (vdiRef, snapVDI2.uuid))
2027 if basePresent: 2027 ↛ 2028line 2027 didn't jump to line 2028, because the condition on line 2027 was never true
2028 LVMMetadataHandler(self.sr.mdpath).ensureSpaceIsAvailableForVdis(1)
2029 vdiRef = self._db_introduce()
2030 vdi_info = {UUID_TAG: self.uuid,
2031 NAME_LABEL_TAG: self.label,
2032 NAME_DESCRIPTION_TAG: self.description,
2033 IS_A_SNAPSHOT_TAG: 0,
2034 SNAPSHOT_OF_TAG: '',
2035 SNAPSHOT_TIME_TAG: '',
2036 TYPE_TAG: type,
2037 VDI_TYPE_TAG: self.sm_config['vdi_type'],
2038 READ_ONLY_TAG: 1,
2039 MANAGED_TAG: 0,
2040 METADATA_OF_POOL_TAG: ''
2041 }
2043 LVMMetadataHandler(self.sr.mdpath).addVdi(vdi_info)
2044 util.SMlog("vdi_clone: introduced base VDI: %s (%s)" % \
2045 (vdiRef, self.uuid))
2047 # Update the original record
2048 vdi_ref = self.sr.srcmd.params['vdi_ref']
2049 self.session.xenapi.VDI.set_sm_config(vdi_ref, snapVDI.sm_config)
2050 self.session.xenapi.VDI.set_physical_utilisation(vdi_ref, \
2051 str(snapVDI.utilisation))
2053 # Return the info on the new snap VDI
2054 snap = snapVDI2
2055 if not snap: 2055 ↛ 2056line 2055 didn't jump to line 2056, because the condition on line 2055 was never true
2056 snap = self
2057 if not basePresent:
2058 # a single-snapshot of an empty VDI will be a noop, resulting
2059 # in no new VDIs, so return the existing one. The GC wouldn't
2060 # normally try to single-snapshot an empty image of course, but
2061 # if an external snapshot operation manages to sneak in right
2062 # before a snapshot-coalesce phase, we would get here
2063 snap = snapVDI
2064 return snap.get_params()
2066 def _setType(self, vdiType: str) -> None:
2067 self.vdi_type = vdiType
2068 self.cowutil = getCowUtil(self.vdi_type)
2069 self.lvmcowutil = LvmCowUtil(self.cowutil)
2071 def _initFromVDIInfo(self, vdiInfo):
2072 self._setType(vdiInfo.vdiType)
2073 self.lvname = vdiInfo.lvName
2074 self.size = vdiInfo.sizeVirt
2075 self.utilisation = vdiInfo.sizeLV
2076 self.hidden = vdiInfo.hidden
2077 if self.hidden: 2077 ↛ 2078line 2077 didn't jump to line 2078, because the condition on line 2077 was never true
2078 self.managed = False
2079 self.active = vdiInfo.lvActive
2080 self.readonly = vdiInfo.lvReadonly
2081 self.parent = vdiInfo.parentUuid
2082 self.path = os.path.join(self.sr.path, self.lvname)
2083 if hasattr(self, "sm_config_override"): 2083 ↛ 2086line 2083 didn't jump to line 2086, because the condition on line 2083 was never false
2084 self.sm_config_override["vdi_type"] = self.vdi_type
2085 else:
2086 self.sm_config_override = {'vdi_type': self.vdi_type}
2087 self.loaded = True
2089 def _initFromLVInfo(self, lvInfo):
2090 self._setType(lvInfo.vdiType)
2091 self.lvname = lvInfo.name
2092 self.size = lvInfo.size
2093 self.utilisation = lvInfo.size
2094 self.hidden = lvInfo.hidden
2095 self.active = lvInfo.active
2096 self.readonly = lvInfo.readonly
2097 self.parent = ''
2098 self.path = os.path.join(self.sr.path, self.lvname)
2099 if hasattr(self, "sm_config_override"): 2099 ↛ 2102line 2099 didn't jump to line 2102, because the condition on line 2099 was never false
2100 self.sm_config_override["vdi_type"] = self.vdi_type
2101 else:
2102 self.sm_config_override = {'vdi_type': self.vdi_type}
2103 if 'vhd-parent' in self.sm_config_override: 2103 ↛ 2104line 2103 didn't jump to line 2104, because the condition on line 2103 was never true
2104 self.parent = self.sm_config_override['vhd-parent']
2105 if not VdiType.isCowImage(self.vdi_type): 2105 ↛ 2106line 2105 didn't jump to line 2106, because the condition on line 2105 was never true
2106 self.loaded = True
2108 def _initFromImageInfo(self, imageInfo):
2109 self.size = imageInfo.sizeVirt
2110 if self.parent == '' or (imageInfo.parentUuid != '' and imageInfo.parentUuid != self.parent): 2110 ↛ 2112line 2110 didn't jump to line 2112, because the condition on line 2110 was never false
2111 self.parent = imageInfo.parentUuid
2112 self.hidden = imageInfo.hidden
2113 self.loaded = True
2115 def _determineType(self):
2116 """
2117 Determine whether this is a RAW or a COW VDI.
2118 """
2119 if "vdi_ref" in self.sr.srcmd.params:
2120 vdi_ref = self.sr.srcmd.params["vdi_ref"]
2121 sm_config = self.session.xenapi.VDI.get_sm_config(vdi_ref)
2122 if sm_config.get("vdi_type"): 2122 ↛ 2123line 2122 didn't jump to line 2123, because the condition on line 2122 was never true
2123 self._setType(sm_config["vdi_type"])
2124 prefix = LV_PREFIX[self.vdi_type]
2125 self.lvname = "%s%s" % (prefix, self.uuid)
2126 self.path = os.path.join(self.sr.path, self.lvname)
2127 self.sm_config_override = sm_config
2128 return True
2130 # LVM commands can be costly, so check the file directly first in case
2131 # the LV is active
2132 found = False
2133 for vdi_type, prefix in LV_PREFIX.items():
2134 lvname = "%s%s" % (prefix, self.uuid)
2135 path = os.path.join(self.sr.path, lvname)
2136 if util.pathexists(path):
2137 if found: 2137 ↛ 2138line 2137 didn't jump to line 2138, because the condition on line 2137 was never true
2138 raise xs_errors.XenError('VDILoad',
2139 opterr="multiple VDI's: uuid %s" % self.uuid)
2140 found = True
2141 self._setType(vdi_type)
2142 self.lvname = lvname
2143 self.path = path
2144 if found:
2145 return True
2147 # now list all LV's
2148 if not lvutil._checkVG(self.sr.vgname): 2148 ↛ 2150line 2148 didn't jump to line 2150, because the condition on line 2148 was never true
2149 # when doing attach_from_config, the VG won't be there yet
2150 return False
2152 lvs = LvmCowUtil.getVolumeInfo(self.sr.lvmCache)
2153 if lvs.get(self.uuid): 2153 ↛ 2156line 2153 didn't jump to line 2156, because the condition on line 2153 was never false
2154 self._initFromLVInfo(lvs[self.uuid])
2155 return True
2156 return False
2158 def _loadThis(self):
2159 """
2160 Load VDI info for this VDI and activate the LV if it's COW. We
2161 don't do it in VDI.load() because not all VDI operations need it.
2162 """
2163 if self.loaded: 2163 ↛ 2164line 2163 didn't jump to line 2164, because the condition on line 2163 was never true
2164 if VdiType.isCowImage(self.vdi_type):
2165 self.sr.lvActivator.activate(self.uuid, self.lvname, False)
2166 return
2167 try:
2168 lvs = LvmCowUtil.getVolumeInfo(self.sr.lvmCache, self.lvname)
2169 except util.CommandException as e:
2170 raise xs_errors.XenError('VDIUnavailable',
2171 opterr='%s (LV scan error)' % os.strerror(abs(e.code)))
2172 if not lvs.get(self.uuid): 2172 ↛ 2173line 2172 didn't jump to line 2173, because the condition on line 2172 was never true
2173 raise xs_errors.XenError('VDIUnavailable', opterr='LV not found')
2174 self._initFromLVInfo(lvs[self.uuid])
2175 if VdiType.isCowImage(self.vdi_type): 2175 ↛ 2181line 2175 didn't jump to line 2181, because the condition on line 2175 was never false
2176 self.sr.lvActivator.activate(self.uuid, self.lvname, False)
2177 imageInfo = self.cowutil.getInfo(self.path, LvmCowUtil.extractUuid, False)
2178 if not imageInfo: 2178 ↛ 2179line 2178 didn't jump to line 2179, because the condition on line 2178 was never true
2179 raise xs_errors.XenError('VDIUnavailable', opterr='getInfo failed')
2180 self._initFromImageInfo(imageInfo)
2181 self.loaded = True
2183 def _chainSetActive(self, active, binary, persistent=False):
2184 if binary: 2184 ↛ 2185line 2184 didn't jump to line 2185, because the condition on line 2184 was never true
2185 (count, bcount) = RefCounter.checkLocked(self.uuid,
2186 NS_PREFIX_LVM + self.sr.uuid)
2187 if (active and bcount > 0) or (not active and bcount == 0):
2188 return # this is a redundant activation/deactivation call
2190 vdiList = {self.uuid: self.lvname}
2191 if VdiType.isCowImage(self.vdi_type): 2191 ↛ 2193line 2191 didn't jump to line 2193, because the condition on line 2191 was never false
2192 vdiList = self.cowutil.getParentChain(self.lvname, LvmCowUtil.extractUuid, self.sr.vgname)
2193 for uuid, lvName in vdiList.items(): 2193 ↛ 2194line 2193 didn't jump to line 2194, because the loop on line 2193 never started
2194 binaryParam = binary
2195 if uuid != self.uuid:
2196 binaryParam = False # binary param only applies to leaf nodes
2197 if active:
2198 self.sr.lvActivator.activate(uuid, lvName, binaryParam,
2199 persistent)
2200 else:
2201 # just add the LVs for deactivation in the final (cleanup)
2202 # step. The LVs must not have been activated during the current
2203 # operation
2204 self.sr.lvActivator.add(uuid, lvName, binaryParam)
2206 def _failClone(self, uuid, jval, msg):
2207 try:
2208 self.sr._handleInterruptedCloneOp(uuid, jval, True)
2209 self.sr.journaler.remove(self.JRN_CLONE, uuid)
2210 except Exception as e:
2211 util.SMlog('WARNING: failed to clean up failed snapshot: ' \
2212 ' %s (error ignored)' % e)
2213 raise xs_errors.XenError('VDIClone', opterr=msg)
2215 def _markHidden(self):
2216 if not VdiType.isCowImage(self.vdi_type):
2217 self.sr.lvmCache.setHidden(self.lvname)
2218 else:
2219 self.cowutil.setHidden(self.path)
2220 self.hidden = 1
2222 def _prepareThin(self, attach, vdiType):
2223 origUtilisation = self.sr.lvmCache.getSize(self.lvname)
2224 if self.sr.isMaster:
2225 # the master can prepare the VDI locally
2226 if attach:
2227 self.lvmcowutil.attachThin(self.sr.journaler, self.sr.uuid, self.uuid, self.vdi_type)
2228 else:
2229 self.lvmcowutil.detachThin(self.session, self.sr.lvmCache, self.sr.uuid, self.uuid, self.vdi_type)
2230 else:
2231 fn = "attach"
2232 if not attach:
2233 fn = "detach"
2234 pools = self.session.xenapi.pool.get_all()
2235 master = self.session.xenapi.pool.get_master(pools[0])
2236 rv = self.session.xenapi.host.call_plugin(
2237 master,
2238 self.sr.THIN_PLUGIN,
2239 fn,
2240 {
2241 "srUuid": self.sr.uuid,
2242 "vdiUuid": self.uuid,
2243 "vdiType": vdiType
2244 }
2245 )
2246 util.SMlog("call-plugin returned: %s" % rv)
2247 if not rv:
2248 raise Exception('plugin %s failed' % self.sr.THIN_PLUGIN)
2249 # refresh to pick up the size change on this slave
2250 self.sr.lvmCache.activateNoRefcount(self.lvname, True)
2252 self.utilisation = self.sr.lvmCache.getSize(self.lvname)
2253 if origUtilisation != self.utilisation:
2254 vdi_ref = self.sr.srcmd.params['vdi_ref']
2255 self.session.xenapi.VDI.set_physical_utilisation(vdi_ref,
2256 str(self.utilisation))
2257 stats = lvutil._getVGstats(self.sr.vgname)
2258 sr_utilisation = stats['physical_utilisation']
2259 self.session.xenapi.SR.set_physical_utilisation(self.sr.sr_ref,
2260 str(sr_utilisation))
2262 @override
2263 def update(self, sr_uuid, vdi_uuid) -> None:
2264 if self.sr.legacyMode:
2265 return
2267 #Synch the name_label of this VDI on storage with the name_label in XAPI
2268 vdi_ref = self.session.xenapi.VDI.get_by_uuid(self.uuid)
2269 update_map = {}
2270 update_map[METADATA_UPDATE_OBJECT_TYPE_TAG] = \
2271 METADATA_OBJECT_TYPE_VDI
2272 update_map[UUID_TAG] = self.uuid
2273 update_map[NAME_LABEL_TAG] = util.to_plain_string( \
2274 self.session.xenapi.VDI.get_name_label(vdi_ref))
2275 update_map[NAME_DESCRIPTION_TAG] = util.to_plain_string( \
2276 self.session.xenapi.VDI.get_name_description(vdi_ref))
2277 update_map[SNAPSHOT_TIME_TAG] = \
2278 self.session.xenapi.VDI.get_snapshot_time(vdi_ref)
2279 update_map[METADATA_OF_POOL_TAG] = \
2280 self.session.xenapi.VDI.get_metadata_of_pool(vdi_ref)
2281 LVMMetadataHandler(self.sr.mdpath).updateMetadata(update_map)
2283 @override
2284 def _ensure_cbt_space(self) -> None:
2285 # We need virtual_size to compute the size in case of a bigger VDI
2286 self.sr.ensureCBTSpace(self.size)
2288 @override
2289 def _create_cbt_log(self) -> str:
2290 logname = self._get_cbt_logname(self.uuid)
2291 logsize = max(util.roundup(CBT_BLOCK_SIZE, self.size//CBT_BLOCK_SIZE), self.sr.journaler.LV_SIZE)
2292 # We choose 4MiB as the minimum for the log size to maintain the old behavior and compute the correct amount
2293 # if we need a bigger LV for the CBT (can happen with big QCOW2)
2294 self.sr.lvmCache.create(logname, logsize, CBTLOG_TAG)
2295 logpath = super(LVMVDI, self)._create_cbt_log()
2296 self.sr.lvmCache.deactivateNoRefcount(logname)
2297 return logpath
2299 @override
2300 def _delete_cbt_log(self) -> None:
2301 logpath = self._get_cbt_logpath(self.uuid)
2302 if self._cbt_log_exists(logpath):
2303 logname = self._get_cbt_logname(self.uuid)
2304 self.sr.lvmCache.remove(logname)
2306 @override
2307 def _rename(self, oldpath, newpath) -> None:
2308 oldname = os.path.basename(oldpath)
2309 newname = os.path.basename(newpath)
2310 self.sr.lvmCache.rename(oldname, newname)
2312 @override
2313 def update_slaves_on_cbt_disable(self, cbtlog) -> None:
2314 args = {
2315 "vgName": self.sr.vgname,
2316 "action1": "deactivateNoRefcount",
2317 "lvName1": cbtlog
2318 }
2320 host_refs = util.get_hosts_attached_on(self.session, [self.uuid])
2322 message = f"Deactivating {cbtlog}"
2323 self.sr.call_on_slave(args, host_refs, message)
2325 @override
2326 def _activate_cbt_log(self, lv_name) -> bool:
2327 self.sr.lvmCache.refresh()
2328 if not self.sr.lvmCache.is_active(lv_name): 2328 ↛ 2329line 2328 didn't jump to line 2329, because the condition on line 2328 was never true
2329 try:
2330 self.sr.lvmCache.activateNoRefcount(lv_name)
2331 return True
2332 except Exception as e:
2333 util.SMlog("Exception in _activate_cbt_log, "
2334 "Error: %s." % str(e))
2335 raise
2336 else:
2337 return False
2339 @override
2340 def _deactivate_cbt_log(self, lv_name) -> None:
2341 try:
2342 self.sr.lvmCache.deactivateNoRefcount(lv_name)
2343 except Exception as e:
2344 util.SMlog("Exception in _deactivate_cbt_log, Error: %s." % str(e))
2345 raise
2347 @override
2348 def _cbt_log_exists(self, logpath) -> bool:
2349 return lvutil.exists(logpath)
2351if __name__ == '__main__': 2351 ↛ 2352line 2351 didn't jump to line 2352, because the condition on line 2351 was never true
2352 SRCommand.run(LVMSR, DRIVER_INFO)
2353else:
2354 SR.registerSR(LVMSR)