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"
98 PLUGIN_ON_MASTER = "on-master"
100 FLAG_USE_VHD = "use_vhd"
101 MDVOLUME_NAME = "MGT"
103 ALLOCATION_QUANTUM = "allocation_quantum"
104 INITIAL_ALLOCATION = "initial_allocation"
106 LOCK_RETRY_INTERVAL = 3
107 LOCK_RETRY_ATTEMPTS = 10
109 TEST_MODE_KEY = "testmode"
110 TEST_MODE_VHD_FAIL_REPARENT_BEGIN = "vhd_fail_reparent_begin"
111 TEST_MODE_VHD_FAIL_REPARENT_LOCATOR = "vhd_fail_reparent_locator"
112 TEST_MODE_VHD_FAIL_REPARENT_END = "vhd_fail_reparent_end"
113 TEST_MODE_VHD_FAIL_RESIZE_BEGIN = "vhd_fail_resize_begin"
114 TEST_MODE_VHD_FAIL_RESIZE_DATA = "vhd_fail_resize_data"
115 TEST_MODE_VHD_FAIL_RESIZE_METADATA = "vhd_fail_resize_metadata"
116 TEST_MODE_VHD_FAIL_RESIZE_END = "vhd_fail_resize_end"
118 ENV_VAR_VHD_TEST = {
119 TEST_MODE_VHD_FAIL_REPARENT_BEGIN:
120 "VHD_UTIL_TEST_FAIL_REPARENT_BEGIN",
121 TEST_MODE_VHD_FAIL_REPARENT_LOCATOR:
122 "VHD_UTIL_TEST_FAIL_REPARENT_LOCATOR",
123 TEST_MODE_VHD_FAIL_REPARENT_END:
124 "VHD_UTIL_TEST_FAIL_REPARENT_END",
125 TEST_MODE_VHD_FAIL_RESIZE_BEGIN:
126 "VHD_UTIL_TEST_FAIL_RESIZE_BEGIN",
127 TEST_MODE_VHD_FAIL_RESIZE_DATA:
128 "VHD_UTIL_TEST_FAIL_RESIZE_DATA_MOVED",
129 TEST_MODE_VHD_FAIL_RESIZE_METADATA:
130 "VHD_UTIL_TEST_FAIL_RESIZE_METADATA_MOVED",
131 TEST_MODE_VHD_FAIL_RESIZE_END:
132 "VHD_UTIL_TEST_FAIL_RESIZE_END"
133 }
134 testMode = ""
136 legacyMode = True
138 @override
139 @staticmethod
140 def handles(type) -> bool:
141 """Returns True if this SR class understands the given dconf string"""
142 # we can pose as LVMSR or EXTSR for compatibility purposes
143 if __name__ == '__main__':
144 name = sys.argv[0]
145 else:
146 name = __name__
147 if name.endswith("LVMSR"):
148 return type == "lvm"
149 elif name.endswith("EXTSR"):
150 return type == "ext"
151 return type == LVMSR.DRIVER_TYPE
153 def __init__(self, srcmd, sr_uuid):
154 SR.SR.__init__(self, srcmd, sr_uuid)
155 self._init_image_formats()
157 @override
158 def load(self, sr_uuid) -> None:
159 self.ops_exclusive = OPS_EXCLUSIVE
161 self.isMaster = False
162 if 'SRmaster' in self.dconf and self.dconf['SRmaster'] == 'true':
163 self.isMaster = True
165 self.lock = lock.Lock(lock.LOCK_TYPE_SR, self.uuid)
166 self.sr_vditype = SR.DEFAULT_TAP
167 self.uuid = sr_uuid
168 self.vgname = VG_PREFIX + self.uuid
169 self.path = os.path.join(VG_LOCATION, self.vgname)
170 self.mdpath = os.path.join(self.path, self.MDVOLUME_NAME)
171 self.provision = self.PROVISIONING_DEFAULT
173 has_sr_ref = self.srcmd.params.get("sr_ref")
174 if has_sr_ref:
175 self.other_conf = self.session.xenapi.SR.get_other_config(self.sr_ref)
176 else:
177 self.other_conf = None
179 self.lvm_conf = None
180 if self.other_conf:
181 self.lvm_conf = self.other_conf.get('lvm-conf')
183 try:
184 self.lvmCache = lvmcache.LVMCache(self.vgname, self.lvm_conf)
185 except:
186 raise xs_errors.XenError('SRUnavailable', \
187 opterr='Failed to initialise the LVMCache')
188 self.lvActivator = LVActivator(self.uuid, self.lvmCache)
189 self.journaler = Journaler(self.lvmCache)
190 if not has_sr_ref:
191 return # must be a probe call
192 # Test for thick vs thin provisioning conf parameter
193 if 'allocation' in self.dconf: 193 ↛ 194line 193 didn't jump to line 194, because the condition on line 193 was never true
194 if self.dconf['allocation'] in self.PROVISIONING_TYPES:
195 self.provision = self.dconf['allocation']
196 else:
197 raise xs_errors.XenError('InvalidArg', \
198 opterr='Allocation parameter must be one of %s' % self.PROVISIONING_TYPES)
200 if self.other_conf.get(self.TEST_MODE_KEY): 200 ↛ 204line 200 didn't jump to line 204, because the condition on line 200 was never false
201 self.testMode = self.other_conf[self.TEST_MODE_KEY]
202 self._prepareTestMode()
204 self.sm_config = self.session.xenapi.SR.get_sm_config(self.sr_ref)
205 # sm_config flag overrides PBD, if any
206 if self.sm_config.get('allocation') in self.PROVISIONING_TYPES:
207 self.provision = self.sm_config.get('allocation')
209 if self.sm_config.get(self.FLAG_USE_VHD) == "true":
210 self.legacyMode = False
212 if lvutil._checkVG(self.vgname):
213 if self.isMaster and not self.cmd in ["vdi_attach", "vdi_detach", 213 ↛ 216line 213 didn't jump to line 216, because the condition on line 213 was never false
214 "vdi_activate", "vdi_deactivate"]:
215 self._undoAllJournals()
216 if not self.cmd in ["sr_attach", "sr_probe"]:
217 self._checkMetadataVolume()
219 self.mdexists = False
221 # get a VDI -> TYPE map from the storage
222 contains_uuid_regex = \
223 re.compile("^.*[0-9a-f]{8}-(([0-9a-f]{4})-){3}[0-9a-f]{12}.*")
224 self.storageVDIs = {}
226 for key in self.lvmCache.lvs.keys(): 226 ↛ 228line 226 didn't jump to line 228, because the loop on line 226 never started
227 # if the lvname has a uuid in it
228 type = None
229 vdi = None
230 if contains_uuid_regex.search(key) is not None:
231 for vdi_type, prefix in LV_PREFIX.items():
232 if key.startswith(prefix):
233 vdi = key[len(prefix):]
234 self.storageVDIs[vdi] = vdi_type
235 break
237 # check if metadata volume exists
238 try:
239 self.mdexists = self.lvmCache.checkLV(self.MDVOLUME_NAME)
240 except:
241 pass
243 @override
244 def cleanup(self) -> None:
245 # we don't need to hold the lock to dec refcounts of activated LVs
246 if not self.lvActivator.deactivateAll(): 246 ↛ 247line 246 didn't jump to line 247, because the condition on line 246 was never true
247 raise util.SMException("failed to deactivate LVs")
249 def updateSRMetadata(self, allocation):
250 try:
251 # Add SR specific SR metadata
252 sr_info = \
253 {ALLOCATION_TAG: allocation,
254 UUID_TAG: self.uuid,
255 NAME_LABEL_TAG: util.to_plain_string(self.session.xenapi.SR.get_name_label(self.sr_ref)),
256 NAME_DESCRIPTION_TAG: util.to_plain_string(self.session.xenapi.SR.get_name_description(self.sr_ref))
257 }
259 vdi_info = {}
260 for vdi in self.session.xenapi.SR.get_VDIs(self.sr_ref):
261 vdi_uuid = self.session.xenapi.VDI.get_uuid(vdi)
262 is_a_snapshot = int(self.session.xenapi.VDI.get_is_a_snapshot(vdi))
264 vdi_type = self.session.xenapi.VDI.get_sm_config(vdi).get('vdi_type')
265 if not vdi_type:
266 raise xs_errors.XenError('MetadataError', opterr=f"Missing `vdi_type` for VDI {vdi_uuid}")
268 # Create the VDI entry in the SR metadata
269 vdi_info[vdi_uuid] = \
270 {
271 UUID_TAG: vdi_uuid,
272 NAME_LABEL_TAG: util.to_plain_string(self.session.xenapi.VDI.get_name_label(vdi)),
273 NAME_DESCRIPTION_TAG: util.to_plain_string(self.session.xenapi.VDI.get_name_description(vdi)),
274 IS_A_SNAPSHOT_TAG: \
275 is_a_snapshot,
276 SNAPSHOT_OF_TAG: \
277 self.session.xenapi.VDI.get_uuid(self.session.xenapi.VDI.get_snapshot_of(vdi)) if bool(is_a_snapshot) else '',
278 SNAPSHOT_TIME_TAG: \
279 self.session.xenapi.VDI.get_snapshot_time(vdi),
280 TYPE_TAG: \
281 self.session.xenapi.VDI.get_type(vdi),
282 VDI_TYPE_TAG: \
283 vdi_type,
284 READ_ONLY_TAG: \
285 int(self.session.xenapi.VDI.get_read_only(vdi)),
286 METADATA_OF_POOL_TAG: \
287 self.session.xenapi.VDI.get_metadata_of_pool(vdi),
288 MANAGED_TAG: \
289 int(self.session.xenapi.VDI.get_managed(vdi))
290 }
291 LVMMetadataHandler(self.mdpath).writeMetadata(sr_info, vdi_info)
293 except Exception as e:
294 raise xs_errors.XenError('MetadataError', \
295 opterr='Error upgrading SR Metadata: %s' % str(e))
297 def syncMetadataAndStorage(self):
298 try:
299 # if a VDI is present in the metadata but not in the storage
300 # then delete it from the metadata
301 vdi_info = LVMMetadataHandler(self.mdpath, False).getMetadata()[1]
302 for vdi in list(vdi_info.keys()):
303 update_map = {}
304 if not vdi_info[vdi][UUID_TAG] in set(self.storageVDIs.keys()): 304 ↛ 311line 304 didn't jump to line 311, because the condition on line 304 was never false
305 # delete this from metadata
306 LVMMetadataHandler(self.mdpath). \
307 deleteVdiFromMetadata(vdi_info[vdi][UUID_TAG])
308 else:
309 # search for this in the metadata, compare types
310 # self.storageVDIs is a map of vdi_uuid to vdi_type
311 if vdi_info[vdi][VDI_TYPE_TAG] != \
312 self.storageVDIs[vdi_info[vdi][UUID_TAG]]:
313 # storage type takes authority
314 update_map[METADATA_UPDATE_OBJECT_TYPE_TAG] \
315 = METADATA_OBJECT_TYPE_VDI
316 update_map[UUID_TAG] = vdi_info[vdi][UUID_TAG]
317 update_map[VDI_TYPE_TAG] = \
318 self.storageVDIs[vdi_info[vdi][UUID_TAG]]
319 LVMMetadataHandler(self.mdpath) \
320 .updateMetadata(update_map)
321 else:
322 # This should never happen
323 pass
325 except Exception as e:
326 raise xs_errors.XenError('MetadataError', \
327 opterr='Error synching SR Metadata and storage: %s' % str(e))
329 def syncMetadataAndXapi(self):
330 try:
331 # get metadata
332 (sr_info, vdi_info) = \
333 LVMMetadataHandler(self.mdpath, False).getMetadata()
335 # First synch SR parameters
336 self.update(self.uuid)
338 # Now update the VDI information in the metadata if required
339 for vdi_offset in vdi_info.keys():
340 try:
341 vdi_ref = \
342 self.session.xenapi.VDI.get_by_uuid( \
343 vdi_info[vdi_offset][UUID_TAG])
344 except:
345 # may be the VDI is not in XAPI yet dont bother
346 continue
348 new_name_label = util.to_plain_string(self.session.xenapi.VDI.get_name_label(vdi_ref))
349 new_name_description = util.to_plain_string(self.session.xenapi.VDI.get_name_description(vdi_ref))
351 if vdi_info[vdi_offset][NAME_LABEL_TAG] != new_name_label or \
352 vdi_info[vdi_offset][NAME_DESCRIPTION_TAG] != \
353 new_name_description:
354 update_map = {}
355 update_map[METADATA_UPDATE_OBJECT_TYPE_TAG] = \
356 METADATA_OBJECT_TYPE_VDI
357 update_map[UUID_TAG] = vdi_info[vdi_offset][UUID_TAG]
358 update_map[NAME_LABEL_TAG] = new_name_label
359 update_map[NAME_DESCRIPTION_TAG] = new_name_description
360 LVMMetadataHandler(self.mdpath) \
361 .updateMetadata(update_map)
362 except Exception as e:
363 raise xs_errors.XenError('MetadataError', \
364 opterr='Error synching SR Metadata and XAPI: %s' % str(e))
366 def _checkMetadataVolume(self):
367 util.SMlog("Entering _checkMetadataVolume")
368 self.mdexists = self.lvmCache.checkLV(self.MDVOLUME_NAME)
369 if self.isMaster: 369 ↛ 385line 369 didn't jump to line 385, because the condition on line 369 was never false
370 if self.mdexists and self.cmd == "sr_attach":
371 try:
372 # activate the management volume
373 # will be deactivated at detach time
374 self.lvmCache.activateNoRefcount(self.MDVOLUME_NAME)
375 self._synchSmConfigWithMetaData()
376 util.SMlog("Sync SR metadata and the state on the storage.")
377 self.syncMetadataAndStorage()
378 self.syncMetadataAndXapi()
379 except Exception as e:
380 util.SMlog("Exception in _checkMetadataVolume, " \
381 "Error: %s." % str(e))
382 elif not self.mdexists and not self.legacyMode:
383 self._introduceMetaDataVolume()
385 if self.mdexists:
386 self.legacyMode = False
388 def _synchSmConfigWithMetaData(self):
389 util.SMlog("Synching sm-config with metadata volume")
391 try:
392 # get SR info from metadata
393 sr_info = {}
394 map = {}
395 sr_info = LVMMetadataHandler(self.mdpath, False).getMetadata()[0]
397 if sr_info == {}: 397 ↛ 398line 397 didn't jump to line 398, because the condition on line 397 was never true
398 raise Exception("Failed to get SR information from metadata.")
400 if "allocation" in sr_info: 400 ↛ 404line 400 didn't jump to line 404, because the condition on line 400 was never false
401 self.provision = sr_info.get("allocation")
402 map['allocation'] = sr_info.get("allocation")
403 else:
404 raise Exception("Allocation key not found in SR metadata. "
405 "SR info found: %s" % sr_info)
407 except Exception as e:
408 raise xs_errors.XenError(
409 'MetadataError',
410 opterr='Error reading SR params from '
411 'metadata Volume: %s' % str(e))
412 try:
413 map[self.FLAG_USE_VHD] = 'true'
414 self.session.xenapi.SR.set_sm_config(self.sr_ref, map)
415 except:
416 raise xs_errors.XenError(
417 'MetadataError',
418 opterr='Error updating sm_config key')
420 def _introduceMetaDataVolume(self):
421 util.SMlog("Creating Metadata volume")
422 try:
423 config = {}
424 self.lvmCache.create(self.MDVOLUME_NAME, 4 * 1024 * 1024)
426 # activate the management volume, will be deactivated at detach time
427 self.lvmCache.activateNoRefcount(self.MDVOLUME_NAME)
429 name_label = util.to_plain_string( \
430 self.session.xenapi.SR.get_name_label(self.sr_ref))
431 name_description = util.to_plain_string( \
432 self.session.xenapi.SR.get_name_description(self.sr_ref))
433 config[self.FLAG_USE_VHD] = "true"
434 config['allocation'] = self.provision
435 self.session.xenapi.SR.set_sm_config(self.sr_ref, config)
437 # Add the SR metadata
438 self.updateSRMetadata(self.provision)
439 except Exception as e:
440 raise xs_errors.XenError('MetadataError', \
441 opterr='Error introducing Metadata Volume: %s' % str(e))
443 def _removeMetadataVolume(self):
444 if self.mdexists:
445 try:
446 self.lvmCache.remove(self.MDVOLUME_NAME)
447 except:
448 raise xs_errors.XenError('MetadataError', \
449 opterr='Failed to delete MGT Volume')
451 def _refresh_size(self):
452 """
453 Refreshs the size of the backing device.
454 Return true if all paths/devices agree on the same size.
455 """
456 if hasattr(self, 'SCSIid'): 456 ↛ 458line 456 didn't jump to line 458, because the condition on line 456 was never true
457 # LVMoHBASR, LVMoISCSISR
458 return scsiutil.refresh_lun_size_by_SCSIid(getattr(self, 'SCSIid'))
459 else:
460 # LVMSR
461 devices = self.dconf['device'].split(',')
462 scsiutil.refreshdev(devices)
463 return True
465 def _expand_size(self):
466 """
467 Expands the size of the SR by growing into additional availiable
468 space, if extra space is availiable on the backing device.
469 Needs to be called after a successful call of _refresh_size.
470 """
471 currentvgsize = lvutil._getVGstats(self.vgname)['physical_size']
472 # We are comparing PV- with VG-sizes that are aligned. Need a threshold
473 resizethreshold = 100 * 1024 * 1024 # 100MB
474 devices = self.dconf['device'].split(',')
475 totaldevicesize = 0
476 for device in devices:
477 totaldevicesize = totaldevicesize + scsiutil.getsize(device)
478 if totaldevicesize >= (currentvgsize + resizethreshold):
479 try:
480 if hasattr(self, 'SCSIid'): 480 ↛ 482line 480 didn't jump to line 482, because the condition on line 480 was never true
481 # LVMoHBASR, LVMoISCSISR might have slaves
482 scsiutil.refresh_lun_size_by_SCSIid_on_slaves(self.session,
483 getattr(self, 'SCSIid'))
484 util.SMlog("LVMSR._expand_size for %s will resize the pv." %
485 self.uuid)
486 for pv in lvutil.get_pv_for_vg(self.vgname):
487 lvutil.resizePV(pv)
488 except:
489 util.logException("LVMSR._expand_size for %s failed to resize"
490 " the PV" % self.uuid)
492 @override
493 @deviceCheck
494 def create(self, uuid, size) -> None:
495 util.SMlog("LVMSR.create for %s" % self.uuid)
496 if not self.isMaster:
497 util.SMlog('sr_create blocked for non-master')
498 raise xs_errors.XenError('LVMMaster')
500 if lvutil._checkVG(self.vgname):
501 raise xs_errors.XenError('SRExists')
503 # Check none of the devices already in use by other PBDs
504 if util.test_hostPBD_devs(self.session, uuid, self.dconf['device']):
505 raise xs_errors.XenError('SRInUse')
507 # Check serial number entry in SR records
508 for dev in self.dconf['device'].split(','):
509 if util.test_scsiserial(self.session, dev):
510 raise xs_errors.XenError('SRInUse')
512 lvutil.createVG(self.dconf['device'], self.vgname)
514 #Update serial number string
515 scsiutil.add_serial_record(self.session, self.sr_ref, \
516 scsiutil.devlist_to_serialstring(self.dconf['device'].split(',')))
518 # since this is an SR.create turn off legacy mode
519 self.session.xenapi.SR.add_to_sm_config(self.sr_ref, \
520 self.FLAG_USE_VHD, 'true')
522 @override
523 def delete(self, uuid) -> None:
524 util.SMlog("LVMSR.delete for %s" % self.uuid)
525 if not self.isMaster:
526 raise xs_errors.XenError('LVMMaster')
527 cleanup.gc_force(self.session, self.uuid)
529 success = True
530 for fileName in glob.glob(DEV_MAPPER_ROOT + '*'):
531 if util.extractSRFromDevMapper(fileName) != self.uuid:
532 continue
534 if util.doesFileHaveOpenHandles(fileName):
535 util.SMlog("LVMSR.delete: The dev mapper entry %s has open " \
536 "handles" % fileName)
537 success = False
538 continue
540 # Now attempt to remove the dev mapper entry
541 if not lvutil.removeDevMapperEntry(fileName, False):
542 success = False
543 continue
545 try:
546 lvname = os.path.basename(fileName.replace('-', '/'). \
547 replace('//', '-'))
548 lpath = os.path.join(self.path, lvname)
549 os.unlink(lpath)
550 except OSError as e:
551 if e.errno != errno.ENOENT:
552 util.SMlog("LVMSR.delete: failed to remove the symlink for " \
553 "file %s. Error: %s" % (fileName, str(e)))
554 success = False
556 if success:
557 try:
558 if util.pathexists(self.path):
559 os.rmdir(self.path)
560 except Exception as e:
561 util.SMlog("LVMSR.delete: failed to remove the symlink " \
562 "directory %s. Error: %s" % (self.path, str(e)))
563 success = False
565 self._removeMetadataVolume()
566 self.lvmCache.refresh()
567 if LvmCowUtil.getVolumeInfo(self.lvmCache):
568 raise xs_errors.XenError('SRNotEmpty')
570 if not success:
571 raise Exception("LVMSR delete failed, please refer to the log " \
572 "for details.")
574 lvutil.removeVG(self.dconf['device'], self.vgname)
575 self._cleanup()
577 @override
578 def attach(self, uuid) -> None:
579 util.SMlog("LVMSR.attach for %s" % self.uuid)
581 self._cleanup(True) # in case of host crashes, if detach wasn't called
583 if not util.match_uuid(self.uuid) or not lvutil._checkVG(self.vgname): 583 ↛ 584line 583 didn't jump to line 584, because the condition on line 583 was never true
584 raise xs_errors.XenError('SRUnavailable', \
585 opterr='no such volume group: %s' % self.vgname)
587 # Refresh the metadata status
588 self._checkMetadataVolume()
590 refreshsizeok = self._refresh_size()
592 if self.isMaster: 592 ↛ 603line 592 didn't jump to line 603, because the condition on line 592 was never false
593 if refreshsizeok: 593 ↛ 597line 593 didn't jump to line 597, because the condition on line 593 was never false
594 self._expand_size()
596 # Update SCSIid string
597 util.SMlog("Calling devlist_to_serial")
598 scsiutil.add_serial_record(
599 self.session, self.sr_ref,
600 scsiutil.devlist_to_serialstring(self.dconf['device'].split(',')))
602 # Test Legacy Mode Flag and update if COW volumes exist
603 if self.isMaster and self.legacyMode: 603 ↛ 604line 603 didn't jump to line 604, because the condition on line 603 was never true
604 vdiInfo = LvmCowUtil.getVDIInfo(self.lvmCache)
605 for uuid, info in vdiInfo.items():
606 if VdiType.isCowImage(info.vdiType):
607 self.legacyMode = False
608 map = self.session.xenapi.SR.get_sm_config(self.sr_ref)
609 self._introduceMetaDataVolume()
610 break
612 # Set the block scheduler
613 for dev in self.dconf['device'].split(','):
614 self.block_setscheduler(dev)
616 @override
617 def detach(self, uuid) -> None:
618 util.SMlog("LVMSR.detach for %s" % self.uuid)
619 cleanup.abort(self.uuid)
621 # Do a best effort cleanup of the dev mapper entries
622 # go through all devmapper entries for this VG
623 success = True
624 for fileName in glob.glob(DEV_MAPPER_ROOT + '*'):
625 if util.extractSRFromDevMapper(fileName) != self.uuid: 625 ↛ 626line 625 didn't jump to line 626, because the condition on line 625 was never true
626 continue
628 with Fairlock('devicemapper'):
629 # check if any file has open handles
630 if util.doesFileHaveOpenHandles(fileName):
631 # if yes, log this and signal failure
632 util.SMlog(
633 f"LVMSR.detach: The dev mapper entry {fileName} has "
634 "open handles")
635 success = False
636 continue
638 # Now attempt to remove the dev mapper entry
639 if not lvutil.removeDevMapperEntry(fileName, False): 639 ↛ 640line 639 didn't jump to line 640, because the condition on line 639 was never true
640 success = False
641 continue
643 # also remove the symlinks from /dev/VG-XenStorage-SRUUID/*
644 try:
645 lvname = os.path.basename(fileName.replace('-', '/'). \
646 replace('//', '-'))
647 lvname = os.path.join(self.path, lvname)
648 util.force_unlink(lvname)
649 except Exception as e:
650 util.SMlog("LVMSR.detach: failed to remove the symlink for " \
651 "file %s. Error: %s" % (fileName, str(e)))
652 success = False
654 # now remove the directory where the symlinks are
655 # this should pass as the directory should be empty by now
656 if success:
657 try:
658 if util.pathexists(self.path): 658 ↛ 659line 658 didn't jump to line 659, because the condition on line 658 was never true
659 os.rmdir(self.path)
660 except Exception as e:
661 util.SMlog("LVMSR.detach: failed to remove the symlink " \
662 "directory %s. Error: %s" % (self.path, str(e)))
663 success = False
665 if not success:
666 raise Exception("SR detach failed, please refer to the log " \
667 "for details.")
669 # Don't delete lock files on the master as it will break the locking
670 # between SM and any GC thread that survives through SR.detach.
671 # However, we should still delete lock files on slaves as it is the
672 # only place to do so.
673 self._cleanup(self.isMaster)
675 @override
676 def forget_vdi(self, uuid) -> None:
677 if not self.legacyMode:
678 LVMMetadataHandler(self.mdpath).deleteVdiFromMetadata(uuid)
679 super(LVMSR, self).forget_vdi(uuid)
681 @override
682 def scan(self, uuid) -> None:
683 activated_lvs = set()
684 try:
685 util.SMlog("LVMSR.scan for %s" % self.uuid)
686 if not self.isMaster: 686 ↛ 687line 686 didn't jump to line 687, because the condition on line 686 was never true
687 util.SMlog('sr_scan blocked for non-master')
688 raise xs_errors.XenError('LVMMaster')
690 if self._refresh_size(): 690 ↛ 692line 690 didn't jump to line 692, because the condition on line 690 was never false
691 self._expand_size()
692 self.lvmCache.refresh()
693 cbt_vdis = self.lvmCache.getTagged(CBTLOG_TAG)
694 self._loadvdis()
695 stats = lvutil._getVGstats(self.vgname)
696 self.physical_size = stats['physical_size']
697 self.physical_utilisation = stats['physical_utilisation']
699 # Now check if there are any VDIs in the metadata, which are not in
700 # XAPI
701 if self.mdexists: 701 ↛ 820line 701 didn't jump to line 820, because the condition on line 701 was never false
702 vdiToSnaps: Dict[str, List[str]] = {}
703 # get VDIs from XAPI
704 vdis = self.session.xenapi.SR.get_VDIs(self.sr_ref)
705 vdi_uuids = set([])
706 for vdi in vdis:
707 vdi_uuids.add(self.session.xenapi.VDI.get_uuid(vdi))
709 info = LVMMetadataHandler(self.mdpath, False).getMetadata()[1]
711 for vdi in list(info.keys()):
712 vdi_uuid = info[vdi][UUID_TAG]
713 if bool(int(info[vdi][IS_A_SNAPSHOT_TAG])): 713 ↛ 714line 713 didn't jump to line 714, because the condition on line 713 was never true
714 if info[vdi][SNAPSHOT_OF_TAG] in vdiToSnaps:
715 vdiToSnaps[info[vdi][SNAPSHOT_OF_TAG]].append(vdi_uuid)
716 else:
717 vdiToSnaps[info[vdi][SNAPSHOT_OF_TAG]] = [vdi_uuid]
719 if vdi_uuid not in vdi_uuids:
720 vdi_type = info[vdi][VDI_TYPE_TAG]
721 sm_config = {}
722 sm_config['vdi_type'] = vdi_type
723 lvname = "%s%s" % (LV_PREFIX[sm_config['vdi_type']], vdi_uuid)
724 if not self.lvmCache.checkLV(lvname):
725 util.SMlog("VDI %s is present in metadata but "
726 "its LV %s does not exist in the VG; "
727 "removing from metadata." %
728 (vdi_uuid, lvname))
729 LVMMetadataHandler(self.mdpath). \
730 deleteVdiFromMetadata(vdi_uuid)
731 continue
732 util.SMlog("Introduce VDI %s as it is present in " \
733 "metadata and not in XAPI." % vdi_uuid)
734 self.lvActivator.activate(
735 vdi_uuid, lvname, LVActivator.NORMAL)
736 activated_lvs.add(vdi_uuid)
737 lvPath = os.path.join(self.path, lvname)
739 if not VdiType.isCowImage(vdi_type):
740 size = self.lvmCache.getSize(LV_PREFIX[vdi_type] + vdi_uuid)
741 utilisation = \
742 util.roundup(lvutil.LVM_SIZE_INCREMENT,
743 int(size))
744 else:
745 cowutil = getCowUtil(vdi_type)
746 lvmcowutil = LvmCowUtil(cowutil)
748 parent = cowutil.getParentNoCheck(lvPath)
750 if parent is not None: 750 ↛ 751line 750 didn't jump to line 751, because the condition on line 750 was never true
751 sm_config['vhd-parent'] = parent[parent.find('-') + 1:]
752 size = cowutil.getSizeVirt(lvPath)
753 if self.provision == "thin": 753 ↛ 754line 753 didn't jump to line 754, because the condition on line 753 was never true
754 utilisation = util.roundup(
755 lvutil.LVM_SIZE_INCREMENT,
756 cowutil.calcOverheadEmpty(max(size, cowutil.getDefaultPreallocationSizeVirt()))
757 )
758 else:
759 utilisation = lvmcowutil.calcVolumeSize(int(size))
761 vdi_ref = self.session.xenapi.VDI.db_introduce(
762 vdi_uuid,
763 info[vdi][NAME_LABEL_TAG],
764 info[vdi][NAME_DESCRIPTION_TAG],
765 self.sr_ref,
766 info[vdi][TYPE_TAG],
767 False,
768 bool(int(info[vdi][READ_ONLY_TAG])),
769 {},
770 vdi_uuid,
771 {},
772 sm_config)
774 self.session.xenapi.VDI.set_managed(vdi_ref,
775 bool(int(info[vdi][MANAGED_TAG])))
776 self.session.xenapi.VDI.set_virtual_size(vdi_ref,
777 str(size))
778 self.session.xenapi.VDI.set_physical_utilisation( \
779 vdi_ref, str(utilisation))
780 self.session.xenapi.VDI.set_is_a_snapshot( \
781 vdi_ref, bool(int(info[vdi][IS_A_SNAPSHOT_TAG])))
782 if bool(int(info[vdi][IS_A_SNAPSHOT_TAG])): 782 ↛ 783line 782 didn't jump to line 783, because the condition on line 782 was never true
783 self.session.xenapi.VDI.set_snapshot_time( \
784 vdi_ref, DateTime(info[vdi][SNAPSHOT_TIME_TAG]))
785 if info[vdi][TYPE_TAG] == 'metadata': 785 ↛ 786line 785 didn't jump to line 786, because the condition on line 785 was never true
786 self.session.xenapi.VDI.set_metadata_of_pool( \
787 vdi_ref, info[vdi][METADATA_OF_POOL_TAG])
789 # Update CBT status of disks either just added
790 # or already in XAPI
791 cbt_logname = "%s.%s" % (vdi_uuid, CBTLOG_TAG)
792 if cbt_logname in cbt_vdis: 792 ↛ 793line 792 didn't jump to line 793, because the condition on line 792 was never true
793 vdi_ref = self.session.xenapi.VDI.get_by_uuid(vdi_uuid)
794 self.session.xenapi.VDI.set_cbt_enabled(vdi_ref, True)
795 # For existing VDIs, update local state too
796 # Scan in base class SR updates existing VDIs
797 # again based on local states
798 if vdi_uuid in self.vdis:
799 self.vdis[vdi_uuid].cbt_enabled = True
800 cbt_vdis.remove(cbt_logname)
802 # Now set the snapshot statuses correctly in XAPI
803 for srcvdi in vdiToSnaps.keys(): 803 ↛ 804line 803 didn't jump to line 804, because the loop on line 803 never started
804 try:
805 srcref = self.session.xenapi.VDI.get_by_uuid(srcvdi)
806 except:
807 # the source VDI no longer exists, continue
808 continue
810 for snapvdi in vdiToSnaps[srcvdi]:
811 try:
812 # this might fail in cases where its already set
813 snapref = \
814 self.session.xenapi.VDI.get_by_uuid(snapvdi)
815 self.session.xenapi.VDI.set_snapshot_of(snapref, srcref)
816 except Exception as e:
817 util.SMlog("Setting snapshot failed. " \
818 "Error: %s" % str(e))
820 if cbt_vdis: 820 ↛ 831line 820 didn't jump to line 831, because the condition on line 820 was never false
821 # If we have items remaining in this list,
822 # they are cbt_metadata VDI that XAPI doesn't know about
823 # Add them to self.vdis and they'll get added to the DB
824 for cbt_vdi in cbt_vdis: 824 ↛ 825line 824 didn't jump to line 825, because the loop on line 824 never started
825 cbt_uuid = cbt_vdi.split(".")[0]
826 new_vdi = self.vdi(cbt_uuid)
827 new_vdi.ty = "cbt_metadata"
828 new_vdi.cbt_enabled = True
829 self.vdis[cbt_uuid] = new_vdi
831 super(LVMSR, self).scan(uuid)
832 self._kickGC()
834 finally:
835 for vdi in activated_lvs:
836 self.lvActivator.deactivate(
837 vdi, LVActivator.NORMAL, False)
839 @override
840 def update(self, uuid) -> None:
841 if not lvutil._checkVG(self.vgname): 841 ↛ 842line 841 didn't jump to line 842, because the condition on line 841 was never true
842 return
843 self._updateStats(uuid, 0)
845 if self.legacyMode: 845 ↛ 846line 845 didn't jump to line 846, because the condition on line 845 was never true
846 return
848 # synch name_label in metadata with XAPI
849 update_map = {}
850 update_map = {METADATA_UPDATE_OBJECT_TYPE_TAG: \
851 METADATA_OBJECT_TYPE_SR,
852 NAME_LABEL_TAG: util.to_plain_string( \
853 self.session.xenapi.SR.get_name_label(self.sr_ref)),
854 NAME_DESCRIPTION_TAG: util.to_plain_string( \
855 self.session.xenapi.SR.get_name_description(self.sr_ref))
856 }
857 LVMMetadataHandler(self.mdpath).updateMetadata(update_map)
859 def _updateStats(self, uuid, virtAllocDelta):
860 valloc = int(self.session.xenapi.SR.get_virtual_allocation(self.sr_ref))
861 self.virtual_allocation = valloc + virtAllocDelta
862 util.SMlog("Setting virtual_allocation of SR %s to %d" %
863 (uuid, self.virtual_allocation))
864 stats = lvutil._getVGstats(self.vgname)
865 self.physical_size = stats['physical_size']
866 self.physical_utilisation = stats['physical_utilisation']
867 self._db_update()
869 @override
870 @deviceCheck
871 def probe(self) -> str:
872 return lvutil.srlist_toxml(
873 lvutil.scan_srlist(VG_PREFIX, self.dconf['device']),
874 VG_PREFIX,
875 ('metadata' in self.srcmd.params['sr_sm_config'] and \
876 self.srcmd.params['sr_sm_config']['metadata'] == 'true'))
878 @override
879 def vdi(self, uuid) -> VDI.VDI:
880 return LVMVDI(self, uuid)
882 def _loadvdis(self):
883 self.virtual_allocation = 0
884 self.vdiInfo = LvmCowUtil.getVDIInfo(self.lvmCache)
885 self.allVDIs = {}
887 for uuid, info in self.vdiInfo.items():
888 if uuid.startswith(cleanup.SR.TMP_RENAME_PREFIX): 888 ↛ 889line 888 didn't jump to line 889, because the condition on line 888 was never true
889 continue
890 if info.scanError: 890 ↛ 891line 890 didn't jump to line 891, because the condition on line 890 was never true
891 raise xs_errors.XenError('VDIUnavailable', \
892 opterr='Error scanning VDI %s' % uuid)
893 self.vdis[uuid] = self.allVDIs[uuid] = self.vdi(uuid)
894 if not self.vdis[uuid].hidden: 894 ↛ 887line 894 didn't jump to line 887, because the condition on line 894 was never false
895 self.virtual_allocation += self.vdis[uuid].utilisation
897 for uuid, vdi in self.vdis.items():
898 if vdi.parent: 898 ↛ 899line 898 didn't jump to line 899, because the condition on line 898 was never true
899 if vdi.parent in self.vdis:
900 self.vdis[vdi.parent].read_only = True
901 if vdi.parent in geneology:
902 geneology[vdi.parent].append(uuid)
903 else:
904 geneology[vdi.parent] = [uuid]
906 # Now remove all hidden leaf nodes to avoid introducing records that
907 # will be GC'ed
908 for uuid in list(self.vdis.keys()):
909 if uuid not in geneology and self.vdis[uuid].hidden: 909 ↛ 910line 909 didn't jump to line 910, because the condition on line 909 was never true
910 util.SMlog("Scan found hidden leaf (%s), ignoring" % uuid)
911 del self.vdis[uuid]
913 def _ensureSpaceAvailable(self, amount_needed):
914 space_available = lvutil._getVGstats(self.vgname)['freespace']
915 if (space_available < amount_needed): 915 ↛ 916line 915 didn't jump to line 916, because the condition on line 915 was never true
916 util.SMlog("Not enough space! free space: %d, need: %d" % \
917 (space_available, amount_needed))
918 raise xs_errors.XenError('SRNoSpace')
920 def _handleInterruptedCloneOps(self):
921 entries = self.journaler.getAll(LVMVDI.JRN_CLONE)
922 for uuid, val in entries.items(): 922 ↛ 923line 922 didn't jump to line 923, because the loop on line 922 never started
923 util.fistpoint.activate("LVHDRT_clone_vdi_before_undo_clone", self.uuid)
924 self._handleInterruptedCloneOp(uuid, val)
925 util.fistpoint.activate("LVHDRT_clone_vdi_after_undo_clone", self.uuid)
926 self.journaler.remove(LVMVDI.JRN_CLONE, uuid)
928 def _handleInterruptedCoalesceLeaf(self):
929 entries = self.journaler.getAll(cleanup.VDI.JRN_LEAF)
930 if len(entries) > 0: 930 ↛ 931line 930 didn't jump to line 931, because the condition on line 930 was never true
931 util.SMlog("*** INTERRUPTED COALESCE-LEAF OP DETECTED ***")
932 cleanup.gc_force(self.session, self.uuid)
933 self.lvmCache.refresh()
935 def _handleInterruptedCloneOp(self, origUuid, jval, forceUndo=False):
936 """Either roll back or finalize the interrupted snapshot/clone
937 operation. Rolling back is unsafe if the leaf images have already been
938 in use and written to. However, it is always safe to roll back while
939 we're still in the context of the failed snapshot operation since the
940 VBD is paused for the duration of the operation"""
941 util.SMlog("*** INTERRUPTED CLONE OP: for %s (%s)" % (origUuid, jval))
942 lvs = LvmCowUtil.getVolumeInfo(self.lvmCache)
943 baseUuid, clonUuid = jval.split("_")
945 # is there a "base copy" VDI?
946 if not lvs.get(baseUuid):
947 # no base copy: make sure the original is there
948 if lvs.get(origUuid):
949 util.SMlog("*** INTERRUPTED CLONE OP: nothing to do")
950 return
951 raise util.SMException("base copy %s not present, " \
952 "but no original %s found" % (baseUuid, origUuid))
954 vdis = LvmCowUtil.getVDIInfo(self.lvmCache)
955 base = vdis[baseUuid]
956 cowutil = getCowUtil(base.vdiType)
958 if forceUndo:
959 util.SMlog("Explicit revert")
960 self._undoCloneOp(cowutil, lvs, origUuid, baseUuid, clonUuid)
961 return
963 if not lvs.get(origUuid) or (clonUuid and not lvs.get(clonUuid)):
964 util.SMlog("One or both leaves missing => revert")
965 self._undoCloneOp(cowutil, lvs, origUuid, baseUuid, clonUuid)
966 return
968 if vdis[origUuid].scanError or (clonUuid and vdis[clonUuid].scanError):
969 util.SMlog("One or both leaves invalid => revert")
970 self._undoCloneOp(cowutil, lvs, origUuid, baseUuid, clonUuid)
971 return
973 orig = vdis[origUuid]
974 self.lvActivator.activate(baseUuid, base.lvName, False)
975 self.lvActivator.activate(origUuid, orig.lvName, False)
976 if orig.parentUuid != baseUuid:
977 parent = vdis[orig.parentUuid]
978 self.lvActivator.activate(parent.uuid, parent.lvName, False)
979 origPath = os.path.join(self.path, orig.lvName)
981 if cowutil.check(origPath) != CowUtil.CheckResult.Success:
982 util.SMlog("Orig image invalid => revert")
983 self._undoCloneOp(cowutil, lvs, origUuid, baseUuid, clonUuid)
984 return
986 if clonUuid:
987 clon = vdis[clonUuid]
988 clonPath = os.path.join(self.path, clon.lvName)
989 self.lvActivator.activate(clonUuid, clon.lvName, False)
990 if cowutil.check(clonPath) != CowUtil.CheckResult.Success:
991 util.SMlog("Clon image invalid => revert")
992 self._undoCloneOp(cowutil, lvs, origUuid, baseUuid, clonUuid)
993 return
995 util.SMlog("Snapshot appears valid, will not roll back")
996 self._completeCloneOp(cowutil, vdis, origUuid, baseUuid, clonUuid)
998 def _undoCloneOp(self, cowutil, lvs, origUuid, baseUuid, clonUuid):
999 base = lvs[baseUuid]
1000 basePath = os.path.join(self.path, base.name)
1002 # make the parent RW
1003 if base.readonly:
1004 self.lvmCache.setReadonly(base.name, False)
1006 ns = NS_PREFIX_LVM + self.uuid
1007 origRefcountBinary = RefCounter.check(origUuid, ns)[1]
1008 origRefcountNormal = 0
1010 # un-hide the parent
1011 if VdiType.isCowImage(base.vdiType):
1012 self.lvActivator.activate(baseUuid, base.name, False)
1013 origRefcountNormal = 1
1014 imageInfo = cowutil.getInfo(basePath, LvmCowUtil.extractUuid, False)
1015 if imageInfo.hidden:
1016 cowutil.setHidden(basePath, False)
1017 elif base.hidden:
1018 self.lvmCache.setHidden(base.name, False)
1020 # remove the child nodes
1021 if clonUuid and lvs.get(clonUuid):
1022 if not VdiType.isCowImage(lvs[clonUuid].vdiType):
1023 raise util.SMException("clone %s not a COW image" % clonUuid)
1024 self.lvmCache.remove(lvs[clonUuid].name)
1025 if self.lvActivator.get(clonUuid, False):
1026 self.lvActivator.remove(clonUuid, False)
1027 if lvs.get(origUuid):
1028 self.lvmCache.remove(lvs[origUuid].name)
1030 # inflate the parent to fully-allocated size
1031 if VdiType.isCowImage(base.vdiType):
1032 lvmcowutil = LvmCowUtil(cowutil)
1033 fullSize = lvmcowutil.calcVolumeSize(imageInfo.sizeVirt)
1034 lvmcowutil.inflate(self.journaler, self.uuid, baseUuid, base.vdiType, fullSize)
1036 # rename back
1037 origLV = LV_PREFIX[base.vdiType] + origUuid
1038 self.lvmCache.rename(base.name, origLV)
1039 RefCounter.reset(baseUuid, ns)
1040 if self.lvActivator.get(baseUuid, False):
1041 self.lvActivator.replace(baseUuid, origUuid, origLV, False)
1042 RefCounter.set(origUuid, origRefcountNormal, origRefcountBinary, ns)
1044 # At this stage, tapdisk and SM vdi will be in paused state. Remove
1045 # flag to facilitate vm deactivate
1046 origVdiRef = self.session.xenapi.VDI.get_by_uuid(origUuid)
1047 self.session.xenapi.VDI.remove_from_sm_config(origVdiRef, 'paused')
1049 # update LVM metadata on slaves
1050 slaves = util.get_slaves_attached_on(self.session, [origUuid])
1051 LvmCowUtil.refreshVolumeOnSlaves(self.session, self.uuid, self.vgname,
1052 origLV, origUuid, slaves)
1054 util.SMlog("*** INTERRUPTED CLONE OP: rollback success")
1056 def _completeCloneOp(self, cowutil, vdis, origUuid, baseUuid, clonUuid):
1057 """Finalize the interrupted snapshot/clone operation. This must not be
1058 called from the live snapshot op context because we attempt to pause/
1059 unpause the VBD here (the VBD is already paused during snapshot, so it
1060 would cause a deadlock)"""
1061 base = vdis[baseUuid]
1062 clon = None
1063 if clonUuid:
1064 clon = vdis[clonUuid]
1066 cleanup.abort(self.uuid)
1068 # make sure the parent is hidden and read-only
1069 if not base.hidden:
1070 if not VdiType.isCowImage(base.vdiType):
1071 self.lvmCache.setHidden(base.lvName)
1072 else:
1073 basePath = os.path.join(self.path, base.lvName)
1074 cowutil.setHidden(basePath)
1075 if not base.lvReadonly:
1076 self.lvmCache.setReadonly(base.lvName, True)
1078 # NB: since this snapshot-preserving call is only invoked outside the
1079 # snapshot op context, we assume the LVM metadata on the involved slave
1080 # has by now been refreshed and do not attempt to do it here
1082 # Update the original record
1083 try:
1084 vdi_ref = self.session.xenapi.VDI.get_by_uuid(origUuid)
1085 sm_config = self.session.xenapi.VDI.get_sm_config(vdi_ref)
1086 type = self.session.xenapi.VDI.get_type(vdi_ref)
1087 sm_config["vdi_type"] = vdis[origUuid].vdiType
1088 sm_config['vhd-parent'] = baseUuid
1089 self.session.xenapi.VDI.set_sm_config(vdi_ref, sm_config)
1090 except XenAPI.Failure:
1091 util.SMlog("ERROR updating the orig record")
1093 # introduce the new VDI records
1094 if clonUuid:
1095 try:
1096 clon_vdi = VDI.VDI(self, clonUuid)
1097 clon_vdi.read_only = False
1098 clon_vdi.location = clonUuid
1099 clon_vdi.utilisation = clon.sizeLV
1100 clon_vdi.sm_config = {
1101 "vdi_type": clon.vdiType,
1102 "vhd-parent": baseUuid}
1104 if not self.legacyMode:
1105 LVMMetadataHandler(self.mdpath). \
1106 ensureSpaceIsAvailableForVdis(1)
1108 clon_vdi_ref = clon_vdi._db_introduce()
1109 util.SMlog("introduced clon VDI: %s (%s)" % \
1110 (clon_vdi_ref, clonUuid))
1112 vdi_info = {UUID_TAG: clonUuid,
1113 NAME_LABEL_TAG: clon_vdi.label,
1114 NAME_DESCRIPTION_TAG: clon_vdi.description,
1115 IS_A_SNAPSHOT_TAG: 0,
1116 SNAPSHOT_OF_TAG: '',
1117 SNAPSHOT_TIME_TAG: '',
1118 TYPE_TAG: type,
1119 VDI_TYPE_TAG: clon_vdi.sm_config['vdi_type'],
1120 READ_ONLY_TAG: int(clon_vdi.read_only),
1121 MANAGED_TAG: int(clon_vdi.managed),
1122 METADATA_OF_POOL_TAG: ''
1123 }
1125 if not self.legacyMode:
1126 LVMMetadataHandler(self.mdpath).addVdi(vdi_info)
1128 except XenAPI.Failure:
1129 util.SMlog("ERROR introducing the clon record")
1131 try:
1132 base_vdi = VDI.VDI(self, baseUuid) # readonly parent
1133 base_vdi.label = "base copy"
1134 base_vdi.read_only = True
1135 base_vdi.location = baseUuid
1136 base_vdi.size = base.sizeVirt
1137 base_vdi.utilisation = base.sizeLV
1138 base_vdi.managed = False
1139 base_vdi.sm_config = {
1140 "vdi_type": base.vdiType,
1141 "vhd-parent": baseUuid}
1143 if not self.legacyMode:
1144 LVMMetadataHandler(self.mdpath).ensureSpaceIsAvailableForVdis(1)
1146 base_vdi_ref = base_vdi._db_introduce()
1147 util.SMlog("introduced base VDI: %s (%s)" % \
1148 (base_vdi_ref, baseUuid))
1150 vdi_info = {UUID_TAG: baseUuid,
1151 NAME_LABEL_TAG: base_vdi.label,
1152 NAME_DESCRIPTION_TAG: base_vdi.description,
1153 IS_A_SNAPSHOT_TAG: 0,
1154 SNAPSHOT_OF_TAG: '',
1155 SNAPSHOT_TIME_TAG: '',
1156 TYPE_TAG: type,
1157 VDI_TYPE_TAG: base_vdi.sm_config['vdi_type'],
1158 READ_ONLY_TAG: int(base_vdi.read_only),
1159 MANAGED_TAG: int(base_vdi.managed),
1160 METADATA_OF_POOL_TAG: ''
1161 }
1163 if not self.legacyMode:
1164 LVMMetadataHandler(self.mdpath).addVdi(vdi_info)
1165 except XenAPI.Failure:
1166 util.SMlog("ERROR introducing the base record")
1168 util.SMlog("*** INTERRUPTED CLONE OP: complete")
1170 def _undoAllJournals(self):
1171 """Undo all COW image & SM interrupted journaled operations. This call must
1172 be serialized with respect to all operations that create journals"""
1173 # undoing interrupted inflates must be done first, since undoing COW images
1174 # ops might require inflations
1175 self.lock.acquire()
1176 try:
1177 self._undoAllInflateJournals()
1178 self._undoAllCowJournals()
1179 self._handleInterruptedCloneOps()
1180 self._handleInterruptedCoalesceLeaf()
1181 finally:
1182 self.lock.release()
1183 self.cleanup()
1185 def _undoAllInflateJournals(self):
1186 entries = self.journaler.getAll(LvmCowUtil.JOURNAL_INFLATE)
1187 if len(entries) == 0:
1188 return
1189 self._loadvdis()
1190 for uuid, val in entries.items():
1191 vdi = self.vdis.get(uuid)
1192 if vdi: 1192 ↛ 1212line 1192 didn't jump to line 1212, because the condition on line 1192 was never false
1193 util.SMlog("Found inflate journal %s, deflating %s to %s" % \
1194 (uuid, vdi.path, val))
1195 if vdi.readonly: 1195 ↛ 1196line 1195 didn't jump to line 1196, because the condition on line 1195 was never true
1196 self.lvmCache.setReadonly(vdi.lvname, False)
1197 self.lvActivator.activate(uuid, vdi.lvname, False)
1198 currSizeLV = self.lvmCache.getSize(vdi.lvname)
1200 cowutil = getCowUtil(vdi.vdi_type)
1201 lvmcowutil = LvmCowUtil(cowutil)
1203 footer_size = cowutil.getFooterSize()
1204 util.zeroOut(vdi.path, currSizeLV - footer_size, footer_size)
1205 lvmcowutil.deflate(self.lvmCache, vdi.lvname, int(val))
1206 if vdi.readonly: 1206 ↛ 1207line 1206 didn't jump to line 1207, because the condition on line 1206 was never true
1207 self.lvmCache.setReadonly(vdi.lvname, True)
1208 if "true" == self.session.xenapi.SR.get_shared(self.sr_ref): 1208 ↛ 1209line 1208 didn't jump to line 1209, because the condition on line 1208 was never true
1209 LvmCowUtil.refreshVolumeOnAllSlaves(
1210 self.session, self.uuid, self.vgname, vdi.lvname, uuid
1211 )
1212 self.journaler.remove(LvmCowUtil.JOURNAL_INFLATE, uuid)
1213 delattr(self, "vdiInfo")
1214 delattr(self, "allVDIs")
1216 def _undoAllCowJournals(self):
1217 """
1218 Check if there are COW journals in existence and revert them.
1219 """
1220 journals = LvmCowUtil.getAllResizeJournals(self.lvmCache)
1221 if len(journals) == 0: 1221 ↛ 1223line 1221 didn't jump to line 1223, because the condition on line 1221 was never false
1222 return
1223 self._loadvdis()
1225 for uuid, jlvName in journals:
1226 vdi = self.vdis[uuid]
1227 util.SMlog("Found COW journal %s, reverting %s" % (uuid, vdi.path))
1228 cowutil = getCowUtil(vdi.vdi_type)
1229 lvmcowutil = LvmCowUtil(cowutil)
1231 self.lvActivator.activate(uuid, vdi.lvname, False)
1232 self.lvmCache.activateNoRefcount(jlvName)
1233 fullSize = lvmcowutil.calcVolumeSize(vdi.size)
1234 lvmcowutil.inflate(self.journaler, self.uuid, vdi.uuid, vdi.vdi_type, fullSize)
1235 try:
1236 jFile = os.path.join(self.path, jlvName)
1237 cowutil.revert(vdi.path, jFile)
1238 except util.CommandException:
1239 util.logException("COW journal revert")
1240 cowutil.check(vdi.path)
1241 util.SMlog("COW image revert failed but COW image ok: removing journal")
1242 # Attempt to reclaim unused space
1245 imageInfo = cowutil.getInfo(vdi.path, LvmCowUtil.extractUuid, False)
1246 NewSize = lvmcowutil.calcVolumeSize(imageInfo.sizeVirt)
1247 if NewSize < fullSize:
1248 lvmcowutil.deflate(self.lvmCache, vdi.lvname, int(NewSize))
1249 LvmCowUtil.refreshVolumeOnAllSlaves(self.session, self.uuid, self.vgname, vdi.lvname, uuid)
1250 self.lvmCache.remove(jlvName)
1251 delattr(self, "vdiInfo")
1252 delattr(self, "allVDIs")
1254 def call_on_slave(self, args, host_refs, message: str):
1255 master_ref = util.get_this_host_ref(self.session)
1256 for hostRef in host_refs:
1257 if hostRef == master_ref: 1257 ↛ 1258line 1257 didn't jump to line 1258, because the condition on line 1257 was never true
1258 continue
1259 util.SMlog(f"{message} on slave {hostRef}")
1260 rv = self.session.xenapi.host.call_plugin(
1261 hostRef, self.PLUGIN_ON_SLAVE, "multi", args)
1262 util.SMlog("call-plugin returned: %s" % rv)
1263 if not rv: 1263 ↛ 1264line 1263 didn't jump to line 1264, because the condition on line 1263 was never true
1264 raise Exception('plugin %s failed' % self.PLUGIN_ON_SLAVE)
1266 def _updateSlavesOnClone(self, hostRefs, origOldLV, origLV,
1267 baseUuid, baseLV):
1268 """We need to reactivate the original LV on each slave (note that the
1269 name for the original LV might change), as well as init the refcount
1270 for the base LV"""
1271 args = {"vgName": self.vgname,
1272 "action1": "refresh",
1273 "lvName1": origLV,
1274 "action2": "activate",
1275 "ns2": NS_PREFIX_LVM + self.uuid,
1276 "lvName2": baseLV,
1277 "uuid2": baseUuid}
1279 message = f"Updating {origOldLV}, {origLV}, {baseLV}"
1280 self.call_on_slave(args, hostRefs, message)
1282 def _updateSlavesOnCBTClone(self, hostRefs, cbtlog):
1283 """Reactivate and refresh CBT log file on slaves"""
1284 args = {"vgName": self.vgname,
1285 "action1": "deactivateNoRefcount",
1286 "lvName1": cbtlog,
1287 "action2": "refresh",
1288 "lvName2": cbtlog}
1290 message = f"Updating {cbtlog}"
1291 self.call_on_slave(args, hostRefs, message)
1293 def _updateSlavesOnRemove(self, hostRefs, baseUuid, baseLV):
1294 """Tell the slave we deleted the base image"""
1295 args = {"vgName": self.vgname,
1296 "action1": "cleanupLockAndRefcount",
1297 "uuid1": baseUuid,
1298 "ns1": NS_PREFIX_LVM + self.uuid}
1300 message = f"Cleaning locks for {baseLV}"
1301 self.call_on_slave(args, hostRefs, message)
1303 def _deactivateOnSlave(self, hostRefs, lvname):
1304 """Tell the slave we need to deactivate the base image"""
1305 args = {
1306 "vgName": self.vgname,
1307 "action1": "deactivateNoRefcount",
1308 "lvName1": lvname}
1310 message = f"Deactivating {lvname}"
1311 self.call_on_slave(args, hostRefs, message)
1313 def _cleanup(self, skipLockCleanup=False):
1314 """delete stale refcounter, flag, and lock files"""
1315 RefCounter.resetAll(NS_PREFIX_LVM + self.uuid)
1316 IPCFlag(self.uuid).clearAll()
1317 if not skipLockCleanup: 1317 ↛ 1318line 1317 didn't jump to line 1318, because the condition on line 1317 was never true
1318 lock.Lock.cleanupAll(self.uuid)
1319 lock.Lock.cleanupAll(NS_PREFIX_LVM + self.uuid)
1321 def _prepareTestMode(self):
1322 util.SMlog("Test mode: %s" % self.testMode)
1323 if self.ENV_VAR_VHD_TEST.get(self.testMode): 1323 ↛ 1324line 1323 didn't jump to line 1324, because the condition on line 1323 was never true
1324 os.environ[self.ENV_VAR_VHD_TEST[self.testMode]] = "yes"
1325 util.SMlog("Setting env %s" % self.ENV_VAR_VHD_TEST[self.testMode])
1327 def _kickGC(self):
1328 util.SMlog("Kicking GC")
1329 cleanup.start_gc_service(self.uuid)
1331 def ensureCBTSpace(self, virtual_size=0):
1332 # Ensure we have space for at least one LV
1333 size = max(util.roundup(CBT_BLOCK_SIZE, virtual_size//CBT_BLOCK_SIZE), self.journaler.LV_SIZE)
1334 self._ensureSpaceAvailable(size)
1337class LVMVDI(VDI.VDI):
1339 JRN_CLONE = "clone" # journal entry type for the clone operation
1341 @override
1342 def load(self, vdi_uuid) -> None:
1343 self.lock = self.sr.lock
1344 self.lvActivator = self.sr.lvActivator
1345 self.loaded = False
1346 if self.sr.legacyMode or util.fistpoint.is_active("xenrt_default_vdi_type_legacy"):
1347 self._setType(VdiType.RAW)
1348 self.uuid = vdi_uuid
1349 self.location = self.uuid
1350 self.exists = True
1352 if hasattr(self.sr, "vdiInfo") and self.sr.vdiInfo.get(self.uuid):
1353 self._initFromVDIInfo(self.sr.vdiInfo[self.uuid])
1354 if self.parent: 1354 ↛ 1355line 1354 didn't jump to line 1355, because the condition on line 1354 was never true
1355 self.sm_config_override['vhd-parent'] = self.parent
1356 else:
1357 self.sm_config_override['vhd-parent'] = None
1358 return
1360 # scan() didn't run: determine the type of the VDI manually
1361 if self._determineType(): 1361 ↛ 1364line 1361 didn't jump to line 1364, because the condition on line 1361 was never false
1362 return
1364 image_format = None
1366 # the VDI must be in the process of being created
1367 self.exists = False
1369 vdi_sm_config = self.sr.srcmd.params.get("vdi_sm_config")
1370 if vdi_sm_config:
1371 image_format = self.sr.read_config_image_format(vdi_sm_config)
1373 if not image_format:
1374 if self.sr.srcmd.cmd == "vdi_create":
1375 size = int(self.sr.srcmd.params['args'][0])
1376 # In the case of vdi_create, the first parameter is size.
1377 # We need it to validate the vdi_type choice
1378 for image_format in self.sr.preferred_image_formats:
1379 vdi_type = self.sr._resolve_vdi_type_from_image_format(image_format)
1380 cowutil = getCowUtil(vdi_type)
1381 try:
1382 cowutil.validateAndRoundImageSize(size)
1383 break
1384 except xs_errors.SROSError:
1385 util.SMlog(f"We won't be able to create the VDI with format {vdi_type}.")
1386 else:
1387 # For cbt_metadata, we found ourselves here without being a vdi_create, we don't have size
1388 util.SMlog("Not a vdi_create, must be cbtlog")
1389 image_format = self.sr.preferred_image_formats[0]
1390 self._setType(self.sr._resolve_vdi_type_from_image_format(image_format))
1392 if self.sr.legacyMode and self.sr.cmd == 'vdi_create' and VdiType.isCowImage(self.vdi_type):
1393 raise xs_errors.XenError('VDICreate', opterr='Cannot create COW type disk in legacy mode')
1395 self.lvname = "%s%s" % (LV_PREFIX[self.vdi_type], vdi_uuid)
1396 self.path = os.path.join(self.sr.path, self.lvname)
1398 @override
1399 def create(self, sr_uuid, vdi_uuid, size) -> str:
1400 util.SMlog("LVMVDI.create for %s" % self.uuid)
1401 if not self.sr.isMaster:
1402 raise xs_errors.XenError('LVMMaster')
1403 if self.exists:
1404 raise xs_errors.XenError('VDIExists')
1406 size = self.cowutil.validateAndRoundImageSize(int(size))
1408 util.SMlog("LVMVDI.create: type = %s, %s (size=%s)" % \
1409 (self.vdi_type, self.path, size))
1410 lvSize = 0
1411 self.sm_config = self.sr.srcmd.params["vdi_sm_config"]
1412 if not VdiType.isCowImage(self.vdi_type):
1413 lvSize = util.roundup(lvutil.LVM_SIZE_INCREMENT, int(size))
1414 else:
1415 if self.sr.provision == "thin":
1416 lvSize = util.roundup(
1417 lvutil.LVM_SIZE_INCREMENT,
1418 self.cowutil.calcOverheadEmpty(max(size, self.cowutil.getDefaultPreallocationSizeVirt()))
1419 )
1420 elif self.sr.provision == "thick":
1421 lvSize = self.lvmcowutil.calcVolumeSize(int(size))
1423 self.sr._ensureSpaceAvailable(lvSize)
1425 try:
1426 self.sr.lvmCache.create(self.lvname, lvSize)
1427 if not VdiType.isCowImage(self.vdi_type):
1428 self.size = self.sr.lvmCache.getSize(self.lvname)
1429 else:
1430 self.cowutil.create(
1431 self.path, int(size), False, self.cowutil.getDefaultPreallocationSizeVirt()
1432 )
1433 self.size = self.cowutil.getSizeVirt(self.path)
1434 self.sr.lvmCache.deactivateNoRefcount(self.lvname)
1435 except util.CommandException as e:
1436 util.SMlog("Unable to create VDI")
1437 self.sr.lvmCache.remove(self.lvname)
1438 raise xs_errors.XenError('VDICreate', opterr='error %d' % e.code)
1440 self.utilisation = lvSize
1441 self.sm_config["vdi_type"] = self.vdi_type
1442 self.sm_config["image-format"] = getImageStringFromVdiType(self.vdi_type)
1444 if not self.sr.legacyMode:
1445 LVMMetadataHandler(self.sr.mdpath).ensureSpaceIsAvailableForVdis(1)
1447 self.ref = self._db_introduce()
1448 self.sr._updateStats(self.sr.uuid, self.size)
1450 vdi_info = {UUID_TAG: self.uuid,
1451 NAME_LABEL_TAG: util.to_plain_string(self.label),
1452 NAME_DESCRIPTION_TAG: util.to_plain_string(self.description),
1453 IS_A_SNAPSHOT_TAG: 0,
1454 SNAPSHOT_OF_TAG: '',
1455 SNAPSHOT_TIME_TAG: '',
1456 TYPE_TAG: self.ty,
1457 VDI_TYPE_TAG: self.vdi_type,
1458 READ_ONLY_TAG: int(self.read_only),
1459 MANAGED_TAG: int(self.managed),
1460 METADATA_OF_POOL_TAG: ''
1461 }
1463 if not self.sr.legacyMode:
1464 LVMMetadataHandler(self.sr.mdpath).addVdi(vdi_info)
1466 return VDI.VDI.get_params(self)
1468 @override
1469 def delete(self, sr_uuid, vdi_uuid, data_only=False) -> None:
1470 util.SMlog("LVMVDI.delete for %s" % self.uuid)
1471 try:
1472 self._loadThis()
1473 except xs_errors.SRException as e:
1474 # Catch 'VDI doesn't exist' exception
1475 if e.errno == 46:
1476 return super(LVMVDI, self).delete(sr_uuid, vdi_uuid, data_only)
1477 raise
1479 vdi_ref = self.sr.srcmd.params['vdi_ref']
1480 if not self.session.xenapi.VDI.get_managed(vdi_ref):
1481 raise xs_errors.XenError("VDIDelete", \
1482 opterr="Deleting non-leaf node not permitted")
1484 if not self.hidden:
1485 self._markHidden()
1487 if not data_only:
1488 # Remove from XAPI and delete from MGT
1489 self._db_forget()
1490 else:
1491 # If this is a data_destroy call, don't remove from XAPI db
1492 # Only delete from MGT
1493 if not self.sr.legacyMode:
1494 LVMMetadataHandler(self.sr.mdpath).deleteVdiFromMetadata(self.uuid)
1496 # deactivate here because it might be too late to do it in the "final"
1497 # step: GC might have removed the LV by then
1498 if self.sr.lvActivator.get(self.uuid, False):
1499 self.sr.lvActivator.deactivate(self.uuid, False)
1501 try:
1502 self.sr.lvmCache.remove(self.lvname)
1503 self.sr.lock.cleanup(vdi_uuid, NS_PREFIX_LVM + sr_uuid)
1504 self.sr.lock.cleanupAll(vdi_uuid)
1505 except xs_errors.SRException as e:
1506 util.SMlog(
1507 "Failed to remove the volume (maybe is leaf coalescing) "
1508 "for %s err:%d" % (self.uuid, e.errno))
1510 self.sr._updateStats(self.sr.uuid, -self.size)
1511 self.sr._kickGC()
1512 return super(LVMVDI, self).delete(sr_uuid, vdi_uuid, data_only)
1514 @override
1515 def attach(self, sr_uuid, vdi_uuid) -> str:
1516 util.SMlog("LVMVDI.attach for %s" % self.uuid)
1517 if self.sr.journaler.hasJournals(self.uuid):
1518 raise xs_errors.XenError('VDIUnavailable',
1519 opterr='Interrupted operation detected on this VDI, '
1520 'scan SR first to trigger auto-repair')
1522 writable = ('args' not in self.sr.srcmd.params) or \
1523 (self.sr.srcmd.params['args'][0] == "true")
1524 needInflate = True
1525 if not VdiType.isCowImage(self.vdi_type) or not writable:
1526 needInflate = False
1527 else:
1528 self._loadThis()
1529 if self.utilisation >= self.lvmcowutil.calcVolumeSize(self.size):
1530 needInflate = False
1532 if needInflate:
1533 try:
1534 self._prepareThin(True, self.vdi_type)
1535 except:
1536 util.logException("attach")
1537 raise xs_errors.XenError('LVMProvisionAttach')
1539 if self.cowutil.isCoalesceableOnRemote():
1540 # We make the chain RW before it's used so it can be coalesced online
1541 self._make_chain_rw()
1543 try:
1544 return self._attach()
1545 finally:
1546 if not self.sr.lvActivator.deactivateAll():
1547 util.SMlog("Failed to deactivate LVs back (%s)" % self.uuid)
1549 @override
1550 def detach(self, sr_uuid, vdi_uuid) -> None:
1551 util.SMlog("LVMVDI.detach for %s" % self.uuid)
1552 self._loadThis()
1553 already_deflated = (self.utilisation < \
1554 self.lvmcowutil.calcVolumeSize(self.size))
1555 needDeflate = True
1556 if not VdiType.isCowImage(self.vdi_type) or already_deflated:
1557 needDeflate = False
1558 elif self.sr.provision == "thick":
1559 needDeflate = False
1560 # except for snapshots, which are always deflated
1561 if self.sr.srcmd.cmd != 'vdi_detach_from_config':
1562 vdi_ref = self.sr.srcmd.params['vdi_ref']
1563 snap = self.session.xenapi.VDI.get_is_a_snapshot(vdi_ref)
1564 if snap:
1565 needDeflate = True
1567 if needDeflate:
1568 try:
1569 self._prepareThin(False, self.vdi_type)
1570 except:
1571 util.logException("_prepareThin")
1572 raise xs_errors.XenError('VDIUnavailable', opterr='deflate')
1574 try:
1575 self._detach()
1576 finally:
1577 if not self.sr.lvActivator.deactivateAll():
1578 raise xs_errors.XenError("SMGeneral", opterr="deactivation")
1580 # We only support offline resize
1581 @override
1582 def resize(self, sr_uuid, vdi_uuid, size) -> str:
1583 util.SMlog("LVMVDI.resize for %s" % self.uuid)
1584 if not self.sr.isMaster:
1585 raise xs_errors.XenError('LVMMaster')
1587 self._loadThis()
1588 if self.hidden:
1589 raise xs_errors.XenError('VDIUnavailable', opterr='hidden VDI')
1591 if size < self.size:
1592 util.SMlog('vdi_resize: shrinking not supported: ' + \
1593 '(current size: %d, new size: %d)' % (self.size, size))
1594 raise xs_errors.XenError('VDISize', opterr='shrinking not allowed')
1596 size = self.cowutil.validateAndRoundImageSize(int(size))
1598 if size == self.size:
1599 return VDI.VDI.get_params(self)
1601 if not VdiType.isCowImage(self.vdi_type):
1602 lvSizeOld = self.size
1603 lvSizeNew = util.roundup(lvutil.LVM_SIZE_INCREMENT, size)
1604 else:
1605 lvSizeOld = self.utilisation
1606 lvSizeNew = self.lvmcowutil.calcVolumeSize(size)
1607 if self.sr.provision == "thin":
1608 # VDI is currently deflated, so keep it deflated
1609 lvSizeNew = lvSizeOld
1610 assert(lvSizeNew >= lvSizeOld)
1611 spaceNeeded = lvSizeNew - lvSizeOld
1612 self.sr._ensureSpaceAvailable(spaceNeeded)
1614 oldSize = self.size
1615 if not VdiType.isCowImage(self.vdi_type):
1616 self.sr.lvmCache.setSize(self.lvname, lvSizeNew)
1617 self.size = self.sr.lvmCache.getSize(self.lvname)
1618 self.utilisation = self.size
1619 else:
1620 if lvSizeNew != lvSizeOld:
1621 self.lvmcowutil.inflate(self.sr.journaler, self.sr.uuid, self.uuid, self.vdi_type, lvSizeNew)
1622 if self.vdi_type == VdiType.QCOW2:
1623 # We only do this for QCOW2 since qemu-img need to read the chain to resize
1624 self._chainSetActive(True, True)
1625 self.cowutil.setSizeVirtFast(self.path, size)
1626 self.size = self.cowutil.getSizeVirt(self.path)
1627 self.utilisation = self.sr.lvmCache.getSize(self.lvname)
1629 vdi_ref = self.sr.srcmd.params['vdi_ref']
1630 self.session.xenapi.VDI.set_virtual_size(vdi_ref, str(self.size))
1631 self.session.xenapi.VDI.set_physical_utilisation(vdi_ref,
1632 str(self.utilisation))
1633 self.sr._updateStats(self.sr.uuid, self.size - oldSize)
1634 super(LVMVDI, self).resize_cbt(self.sr.uuid, self.uuid, self.size)
1635 if self.vdi_type == VdiType.QCOW2:
1636 self._chainSetActive(False, True)
1637 return VDI.VDI.get_params(self)
1639 @override
1640 def clone(self, sr_uuid, vdi_uuid) -> str:
1641 return self._do_snapshot(
1642 sr_uuid, vdi_uuid, VDI.SNAPSHOT_DOUBLE, cloneOp=True)
1644 @override
1645 def compose(self, sr_uuid, vdi1, vdi2) -> None:
1646 util.SMlog("LVMSR.compose for %s -> %s" % (vdi2, vdi1))
1647 if not VdiType.isCowImage(self.vdi_type):
1648 raise xs_errors.XenError('Unimplemented')
1650 parent_uuid = vdi1
1651 parent_lvname = LV_PREFIX[self.vdi_type] + parent_uuid
1652 assert(self.sr.lvmCache.checkLV(parent_lvname))
1653 parent_path = os.path.join(self.sr.path, parent_lvname)
1655 self.sr.lvActivator.activate(self.uuid, self.lvname, False)
1656 self.sr.lvActivator.activate(parent_uuid, parent_lvname, False)
1658 self.cowutil.setParent(self.path, parent_path, False)
1659 self.cowutil.setHidden(parent_path)
1660 self.sr.session.xenapi.VDI.set_managed(self.sr.srcmd.params['args'][0], False)
1662 if not blktap2.VDI.tap_refresh(self.session, self.sr.uuid, self.uuid,
1663 True):
1664 raise util.SMException("failed to refresh VDI %s" % self.uuid)
1666 util.SMlog("Compose done")
1668 def reset_leaf(self, sr_uuid, vdi_uuid):
1669 util.SMlog("LVMSR.reset_leaf for %s" % vdi_uuid)
1670 if not VdiType.isCowImage(self.vdi_type):
1671 raise xs_errors.XenError('Unimplemented')
1673 self.sr.lvActivator.activate(self.uuid, self.lvname, False)
1675 # safety check
1676 if not self.cowutil.hasParent(self.path):
1677 raise util.SMException("ERROR: VDI %s has no parent, " + \
1678 "will not reset contents" % self.uuid)
1680 self.cowutil.killData(self.path)
1682 def _attach(self):
1683 self._chainSetActive(True, True, True)
1684 if not util.pathexists(self.path):
1685 raise xs_errors.XenError('VDIUnavailable', \
1686 opterr='Could not find: %s' % self.path)
1688 if not hasattr(self, 'xenstore_data'):
1689 self.xenstore_data = {}
1691 self.xenstore_data.update(scsiutil.update_XS_SCSIdata(self.uuid, \
1692 scsiutil.gen_synthetic_page_data(self.uuid)))
1694 self.xenstore_data['storage-type'] = 'lvm'
1695 self.xenstore_data['vdi-type'] = self.vdi_type
1697 self.attached = True
1698 self.sr.lvActivator.persist()
1699 return VDI.VDI.attach(self, self.sr.uuid, self.uuid)
1701 def _detach(self):
1702 self._chainSetActive(False, True)
1703 self.attached = False
1705 @override
1706 def _do_snapshot(self, sr_uuid, vdi_uuid, snapType,
1707 cloneOp=False, secondary=None, cbtlog=None, is_mirror_destination=False) -> str:
1708 # If cbt enabled, save file consistency state
1709 if cbtlog is not None:
1710 if blktap2.VDI.tap_status(self.session, vdi_uuid): 1710 ↛ 1711line 1710 didn't jump to line 1711, because the condition on line 1710 was never true
1711 consistency_state = False
1712 else:
1713 consistency_state = True
1714 util.SMlog("Saving log consistency state of %s for vdi: %s" %
1715 (consistency_state, vdi_uuid))
1716 else:
1717 consistency_state = None
1719 pause_time = time.time()
1720 if not blktap2.VDI.tap_pause(self.session, sr_uuid, vdi_uuid): 1720 ↛ 1721line 1720 didn't jump to line 1721, because the condition on line 1720 was never true
1721 raise util.SMException("failed to pause VDI %s" % vdi_uuid)
1723 snapResult = None
1724 try:
1725 snapResult = self._snapshot(snapType, cloneOp, cbtlog, consistency_state, is_mirror_destination)
1726 except Exception as e1:
1727 try:
1728 blktap2.VDI.tap_unpause(self.session, sr_uuid, vdi_uuid,
1729 secondary=None)
1730 except Exception as e2:
1731 util.SMlog('WARNING: failed to clean up failed snapshot: '
1732 '%s (error ignored)' % e2)
1733 raise
1734 self.disable_leaf_on_secondary(vdi_uuid, secondary=secondary)
1735 blktap2.VDI.tap_unpause(self.session, sr_uuid, vdi_uuid, secondary)
1736 unpause_time = time.time()
1737 if (unpause_time - pause_time) > LONG_SNAPTIME: 1737 ↛ 1738line 1737 didn't jump to line 1738, because the condition on line 1737 was never true
1738 util.SMlog('WARNING: snapshot paused VM for %s seconds' %
1739 (unpause_time - pause_time))
1740 return snapResult
1742 def _snapshot(self, snapType, cloneOp=False, cbtlog=None, cbt_consistency=None, is_mirror_destination=False):
1743 util.SMlog("LVMVDI._snapshot for %s (type %s)" % (self.uuid, snapType))
1745 if not self.sr.isMaster: 1745 ↛ 1746line 1745 didn't jump to line 1746, because the condition on line 1745 was never true
1746 raise xs_errors.XenError('LVMMaster')
1747 if self.sr.legacyMode: 1747 ↛ 1748line 1747 didn't jump to line 1748, because the condition on line 1747 was never true
1748 raise xs_errors.XenError('Unimplemented', opterr='In legacy mode')
1750 self._loadThis()
1751 if self.hidden: 1751 ↛ 1752line 1751 didn't jump to line 1752, because the condition on line 1751 was never true
1752 raise xs_errors.XenError('VDISnapshot', opterr='hidden VDI')
1754 snapVdiType = self.sr._get_snap_vdi_type(self.vdi_type, self.size)
1756 self.sm_config = self.session.xenapi.VDI.get_sm_config( \
1757 self.sr.srcmd.params['vdi_ref'])
1758 if "type" in self.sm_config and self.sm_config['type'] == 'raw': 1758 ↛ 1759line 1758 didn't jump to line 1759, because the condition on line 1758 was never true
1759 if not util.fistpoint.is_active("testsm_clone_allow_raw"):
1760 raise xs_errors.XenError('Unimplemented', \
1761 opterr='Raw VDI, snapshot or clone not permitted')
1763 # we must activate the entire image chain because the real parent could
1764 # theoretically be anywhere in the chain if all images under it are empty
1765 self._chainSetActive(True, False)
1766 if not util.pathexists(self.path): 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='VDI unavailable: %s' % (self.path))
1770 if VdiType.isCowImage(self.vdi_type): 1770 ↛ 1778line 1770 didn't jump to line 1778, because the condition on line 1770 was never false
1771 depth = self.cowutil.getDepth(self.path)
1772 if depth == -1: 1772 ↛ 1773line 1772 didn't jump to line 1773, because the condition on line 1772 was never true
1773 raise xs_errors.XenError('VDIUnavailable', \
1774 opterr='failed to get COW depth')
1775 elif depth >= self.cowutil.getMaxChainLength(): 1775 ↛ 1776line 1775 didn't jump to line 1776, because the condition on line 1775 was never true
1776 raise xs_errors.XenError('SnapshotChainTooLong')
1778 self.issnap = self.session.xenapi.VDI.get_is_a_snapshot( \
1779 self.sr.srcmd.params['vdi_ref'])
1781 fullpr = self.lvmcowutil.calcVolumeSize(self.size)
1782 thinpr = util.roundup(
1783 lvutil.LVM_SIZE_INCREMENT,
1784 self.cowutil.calcOverheadEmpty(max(self.size, self.cowutil.getDefaultPreallocationSizeVirt()))
1785 )
1786 lvSizeOrig = thinpr
1787 lvSizeClon = thinpr
1789 hostRefs = []
1790 if self.sr.cmd == "vdi_snapshot":
1791 hostRefs = util.get_hosts_attached_on(self.session, [self.uuid])
1792 if hostRefs: 1792 ↛ 1794line 1792 didn't jump to line 1794, because the condition on line 1792 was never false
1793 lvSizeOrig = fullpr
1794 if self.sr.provision == "thick": 1794 ↛ 1800line 1794 didn't jump to line 1800, because the condition on line 1794 was never false
1795 if not self.issnap:
1796 lvSizeOrig = fullpr
1797 if self.sr.cmd != "vdi_snapshot":
1798 lvSizeClon = fullpr
1800 if (snapType == VDI.SNAPSHOT_SINGLE or 1800 ↛ 1802line 1800 didn't jump to line 1802, because the condition on line 1800 was never true
1801 snapType == VDI.SNAPSHOT_INTERNAL):
1802 lvSizeClon = 0
1804 # the space required must include 2 journal LVs: a clone journal and an
1805 # inflate journal (for the failure handling
1806 size_req = lvSizeOrig + lvSizeClon + 2 * self.sr.journaler.LV_SIZE
1807 lvSizeBase = self.size
1808 if VdiType.isCowImage(self.vdi_type): 1808 ↛ 1811line 1808 didn't jump to line 1811, because the condition on line 1808 was never false
1809 lvSizeBase = util.roundup(lvutil.LVM_SIZE_INCREMENT, self.cowutil.getSizePhys(self.path))
1810 size_req -= (self.utilisation - lvSizeBase)
1811 self.sr._ensureSpaceAvailable(size_req)
1813 if hostRefs:
1814 self.sr._deactivateOnSlave(hostRefs, self.lvname)
1816 baseUuid = util.gen_uuid()
1817 origUuid = self.uuid
1818 clonUuid = ""
1819 if snapType == VDI.SNAPSHOT_DOUBLE: 1819 ↛ 1821line 1819 didn't jump to line 1821, because the condition on line 1819 was never false
1820 clonUuid = util.gen_uuid()
1821 jval = "%s_%s" % (baseUuid, clonUuid)
1822 self.sr.journaler.create(self.JRN_CLONE, origUuid, jval)
1823 util.fistpoint.activate("LVHDRT_clone_vdi_after_create_journal", self.sr.uuid)
1825 try:
1826 # self becomes the "base vdi"
1827 origOldLV = self.lvname
1828 baseLV = LV_PREFIX[self.vdi_type] + baseUuid
1829 self.sr.lvmCache.rename(self.lvname, baseLV)
1830 self.sr.lvActivator.replace(self.uuid, baseUuid, baseLV, False)
1831 RefCounter.set(baseUuid, 1, 0, NS_PREFIX_LVM + self.sr.uuid)
1832 self.uuid = baseUuid
1833 self.lvname = baseLV
1834 self.path = os.path.join(self.sr.path, baseLV)
1835 self.label = "base copy"
1836 self.read_only = True
1837 self.location = self.uuid
1838 self.managed = False
1840 # shrink the base copy to the minimum - we do it before creating
1841 # the snapshot volumes to avoid requiring double the space
1842 if VdiType.isCowImage(self.vdi_type): 1842 ↛ 1845line 1842 didn't jump to line 1845, because the condition on line 1842 was never false
1843 self.lvmcowutil.deflate(self.sr.lvmCache, self.lvname, lvSizeBase)
1844 self.utilisation = lvSizeBase
1845 util.fistpoint.activate("LVHDRT_clone_vdi_after_shrink_parent", self.sr.uuid)
1847 snapVDI = self._createSnap(origUuid, snapVdiType, lvSizeOrig, False, is_mirror_destination)
1848 util.fistpoint.activate("LVHDRT_clone_vdi_after_first_snap", self.sr.uuid)
1849 snapVDI2 = None
1850 if snapType == VDI.SNAPSHOT_DOUBLE: 1850 ↛ 1856line 1850 didn't jump to line 1856, because the condition on line 1850 was never false
1851 snapVDI2 = self._createSnap(clonUuid, snapVdiType, lvSizeClon, True)
1852 # If we have CBT enabled on the VDI,
1853 # set CBT status for the new snapshot disk
1854 if cbtlog:
1855 snapVDI2.cbt_enabled = True
1856 util.fistpoint.activate("LVHDRT_clone_vdi_after_second_snap", self.sr.uuid)
1858 # note: it is important to mark the parent hidden only AFTER the
1859 # new image children have been created, which are referencing it;
1860 # otherwise we would introduce a race with GC that could reclaim
1861 # the parent before we snapshot it
1862 if not VdiType.isCowImage(self.vdi_type): 1862 ↛ 1863line 1862 didn't jump to line 1863, because the condition on line 1862 was never true
1863 self.sr.lvmCache.setHidden(self.lvname)
1864 else:
1865 self.cowutil.setHidden(self.path)
1866 util.fistpoint.activate("LVHDRT_clone_vdi_after_parent_hidden", self.sr.uuid)
1868 # set the base copy to ReadOnly
1869 if not self.cowutil.isCoalesceableOnRemote(): 1869 ↛ 1876line 1869 didn't jump to line 1876, because the condition on line 1869 was never false
1870 # For QCOW2, the whole chain need to be
1871 # writable for tapdisk to do the coalesce.
1872 # In this case, we don't mark the base copy RO.
1873 self.sr.lvmCache.setReadonly(self.lvname, True)
1874 util.fistpoint.activate("LVHDRT_clone_vdi_after_parent_ro", self.sr.uuid)
1876 if hostRefs:
1877 self.sr._updateSlavesOnClone(hostRefs, origOldLV,
1878 snapVDI.lvname, self.uuid, self.lvname)
1880 # Update cbt files if user created snapshot (SNAPSHOT_DOUBLE)
1881 if snapType == VDI.SNAPSHOT_DOUBLE and cbtlog:
1882 snapVDI._cbt_snapshot(clonUuid, cbt_consistency)
1883 if hostRefs: 1883 ↛ 1897line 1883 didn't jump to line 1897, because the condition on line 1883 was never false
1884 cbtlog_file = self._get_cbt_logname(snapVDI.uuid)
1885 try:
1886 self.sr._updateSlavesOnCBTClone(hostRefs, cbtlog_file)
1887 except:
1888 alert_name = "VDI_CBT_SNAPSHOT_FAILED"
1889 alert_str = ("Creating CBT snapshot for {} failed"
1890 .format(snapVDI.uuid))
1891 snapVDI._disable_cbt_on_error(alert_name, alert_str)
1892 pass
1894 except (util.SMException, XenAPI.Failure) as e:
1895 util.logException("LVMVDI._snapshot")
1896 self._failClone(origUuid, jval, str(e))
1897 util.fistpoint.activate("LVHDRT_clone_vdi_before_remove_journal", self.sr.uuid)
1899 self.sr.journaler.remove(self.JRN_CLONE, origUuid)
1901 return self._finishSnapshot(snapVDI, snapVDI2, hostRefs, cloneOp, snapType)
1903 def _createSnap(self, snapUuid, snapVdiType, snapSizeLV, isNew, is_mirror_destination=False):
1904 """Snapshot self and return the snapshot VDI object"""
1906 snapLV = LV_PREFIX[snapVdiType] + snapUuid
1907 snapPath = os.path.join(self.sr.path, snapLV)
1908 self.sr.lvmCache.create(snapLV, int(snapSizeLV))
1909 util.fistpoint.activate("LVHDRT_clone_vdi_after_lvcreate", self.sr.uuid)
1910 if isNew:
1911 RefCounter.set(snapUuid, 1, 0, NS_PREFIX_LVM + self.sr.uuid)
1912 self.sr.lvActivator.add(snapUuid, snapLV, False)
1913 parentRaw = (self.vdi_type == VdiType.RAW)
1914 self.cowutil.snapshot(
1915 snapPath, self.path, parentRaw, max(self.size, self.cowutil.getDefaultPreallocationSizeVirt()), is_mirror_image=is_mirror_destination
1916 )
1917 snapParent = self.cowutil.getParent(snapPath, LvmCowUtil.extractUuid)
1919 snapVDI = LVMVDI(self.sr, snapUuid)
1920 snapVDI.read_only = False
1921 snapVDI.location = snapUuid
1922 snapVDI.size = self.size
1923 snapVDI.utilisation = snapSizeLV
1924 snapVDI.sm_config = dict()
1925 for key, val in self.sm_config.items():
1926 if key not in [ 1926 ↛ 1929line 1926 didn't jump to line 1929, because the condition on line 1926 was never true
1927 "type", "vdi_type", "vhd-parent", "paused", "relinking", "activating"] and \
1928 not key.startswith("host_"):
1929 snapVDI.sm_config[key] = val
1930 snapVDI.sm_config["vdi_type"] = snapVdiType
1931 snapVDI.sm_config["vhd-parent"] = snapParent
1932 # TODO: fix the raw snapshot case
1933 snapVDI.sm_config["image-format"] = getImageStringFromVdiType(self.vdi_type)
1934 snapVDI.lvname = snapLV
1935 return snapVDI
1937 def _finishSnapshot(self, snapVDI, snapVDI2, hostRefs, cloneOp=False, snapType=None):
1938 if snapType is not VDI.SNAPSHOT_INTERNAL: 1938 ↛ 1940line 1938 didn't jump to line 1940, because the condition on line 1938 was never false
1939 self.sr._updateStats(self.sr.uuid, self.size)
1940 basePresent = True
1942 # Verify parent locator field of both children and delete basePath if
1943 # unused
1944 snapParent = snapVDI.sm_config["vhd-parent"]
1945 snap2Parent = ""
1946 if snapVDI2: 1946 ↛ 1948line 1946 didn't jump to line 1948, because the condition on line 1946 was never false
1947 snap2Parent = snapVDI2.sm_config["vhd-parent"]
1948 if snapParent != self.uuid and \ 1948 ↛ 1982line 1948 didn't jump to line 1982, because the condition on line 1948 was never false
1949 (not snapVDI2 or snap2Parent != self.uuid):
1950 util.SMlog("%s != %s != %s => deleting unused base %s" % \
1951 (snapParent, self.uuid, snap2Parent, self.lvname))
1952 RefCounter.put(self.uuid, False, NS_PREFIX_LVM + self.sr.uuid)
1954 # The removed LV could still be activated on a slave host if it's
1955 # part of a VM currently running there, we need to deactivate it
1956 # before it gets removed to avoid a LV leak.
1957 if hostRefs:
1958 self.sr._deactivateOnSlave(hostRefs, self.lvname)
1960 self.sr.lvmCache.remove(self.lvname)
1961 self.sr.lvActivator.remove(self.uuid, False)
1962 if hostRefs:
1963 self.sr._updateSlavesOnRemove(hostRefs, self.uuid, self.lvname)
1964 basePresent = False
1965 else:
1966 # assign the _binary_ refcount of the original VDI to the new base
1967 # VDI (but as the normal refcount, since binary refcounts are only
1968 # for leaf nodes). The normal refcount of the child is not
1969 # transferred to to the base VDI because normal refcounts are
1970 # incremented and decremented individually, and not based on the
1971 # image chain (i.e., the child's normal refcount will be decremented
1972 # independently of its parent situation). Add 1 for this clone op.
1973 # Note that we do not need to do protect the refcount operations
1974 # below with per-VDI locking like we do in lvutil because at this
1975 # point we have exclusive access to the VDIs involved. Other SM
1976 # operations are serialized by the Agent or with the SR lock, and
1977 # any coalesce activations are serialized with the SR lock. (The
1978 # coalesce activates the coalesced VDI pair in the beginning, which
1979 # cannot affect the VDIs here because they cannot possibly be
1980 # involved in coalescing at this point, and at the relinkSkip step
1981 # that activates the children, which takes the SR lock.)
1982 ns = NS_PREFIX_LVM + self.sr.uuid
1983 (cnt, bcnt) = RefCounter.check(snapVDI.uuid, ns)
1984 RefCounter.set(self.uuid, bcnt + 1, 0, ns)
1986 # the "paused" and "host_*" sm-config keys are special and must stay on
1987 # the leaf without being inherited by anyone else
1988 for key in [x for x in self.sm_config.keys() if x == "paused" or x.startswith("host_")]: 1988 ↛ 1989line 1988 didn't jump to line 1989, because the loop on line 1988 never started
1989 snapVDI.sm_config[key] = self.sm_config[key]
1990 del self.sm_config[key]
1992 # Introduce any new VDI records & update the existing one
1993 type = self.session.xenapi.VDI.get_type( \
1994 self.sr.srcmd.params['vdi_ref'])
1995 if snapVDI2: 1995 ↛ 2037line 1995 didn't jump to line 2037, because the condition on line 1995 was never false
1996 LVMMetadataHandler(self.sr.mdpath).ensureSpaceIsAvailableForVdis(1)
1997 vdiRef = snapVDI2._db_introduce()
1998 if cloneOp:
1999 vdi_info = {UUID_TAG: snapVDI2.uuid,
2000 NAME_LABEL_TAG: util.to_plain_string( \
2001 self.session.xenapi.VDI.get_name_label( \
2002 self.sr.srcmd.params['vdi_ref'])),
2003 NAME_DESCRIPTION_TAG: util.to_plain_string( \
2004 self.session.xenapi.VDI.get_name_description(self.sr.srcmd.params['vdi_ref'])),
2005 IS_A_SNAPSHOT_TAG: 0,
2006 SNAPSHOT_OF_TAG: '',
2007 SNAPSHOT_TIME_TAG: '',
2008 TYPE_TAG: type,
2009 VDI_TYPE_TAG: snapVDI2.sm_config['vdi_type'],
2010 READ_ONLY_TAG: 0,
2011 MANAGED_TAG: int(snapVDI2.managed),
2012 METADATA_OF_POOL_TAG: ''
2013 }
2014 else:
2015 util.SMlog("snapshot VDI params: %s" % \
2016 self.session.xenapi.VDI.get_snapshot_time(vdiRef))
2017 vdi_info = {UUID_TAG: snapVDI2.uuid,
2018 NAME_LABEL_TAG: util.to_plain_string( \
2019 self.session.xenapi.VDI.get_name_label( \
2020 self.sr.srcmd.params['vdi_ref'])),
2021 NAME_DESCRIPTION_TAG: util.to_plain_string( \
2022 self.session.xenapi.VDI.get_name_description(self.sr.srcmd.params['vdi_ref'])),
2023 IS_A_SNAPSHOT_TAG: 1,
2024 SNAPSHOT_OF_TAG: snapVDI.uuid,
2025 SNAPSHOT_TIME_TAG: '',
2026 TYPE_TAG: type,
2027 VDI_TYPE_TAG: snapVDI2.sm_config['vdi_type'],
2028 READ_ONLY_TAG: 0,
2029 MANAGED_TAG: int(snapVDI2.managed),
2030 METADATA_OF_POOL_TAG: ''
2031 }
2033 LVMMetadataHandler(self.sr.mdpath).addVdi(vdi_info)
2034 util.SMlog("vdi_clone: introduced 2nd snap VDI: %s (%s)" % \
2035 (vdiRef, snapVDI2.uuid))
2037 if basePresent: 2037 ↛ 2038line 2037 didn't jump to line 2038, because the condition on line 2037 was never true
2038 LVMMetadataHandler(self.sr.mdpath).ensureSpaceIsAvailableForVdis(1)
2039 vdiRef = self._db_introduce()
2040 vdi_info = {UUID_TAG: self.uuid,
2041 NAME_LABEL_TAG: self.label,
2042 NAME_DESCRIPTION_TAG: self.description,
2043 IS_A_SNAPSHOT_TAG: 0,
2044 SNAPSHOT_OF_TAG: '',
2045 SNAPSHOT_TIME_TAG: '',
2046 TYPE_TAG: type,
2047 VDI_TYPE_TAG: self.sm_config['vdi_type'],
2048 READ_ONLY_TAG: 1,
2049 MANAGED_TAG: 0,
2050 METADATA_OF_POOL_TAG: ''
2051 }
2053 LVMMetadataHandler(self.sr.mdpath).addVdi(vdi_info)
2054 util.SMlog("vdi_clone: introduced base VDI: %s (%s)" % \
2055 (vdiRef, self.uuid))
2057 # Update the original record
2058 vdi_ref = self.sr.srcmd.params['vdi_ref']
2059 self.session.xenapi.VDI.set_sm_config(vdi_ref, snapVDI.sm_config)
2060 self.session.xenapi.VDI.set_physical_utilisation(vdi_ref, \
2061 str(snapVDI.utilisation))
2063 # Return the info on the new snap VDI
2064 snap = snapVDI2
2065 if not snap: 2065 ↛ 2066line 2065 didn't jump to line 2066, because the condition on line 2065 was never true
2066 snap = self
2067 if not basePresent:
2068 # a single-snapshot of an empty VDI will be a noop, resulting
2069 # in no new VDIs, so return the existing one. The GC wouldn't
2070 # normally try to single-snapshot an empty image of course, but
2071 # if an external snapshot operation manages to sneak in right
2072 # before a snapshot-coalesce phase, we would get here
2073 snap = snapVDI
2074 return snap.get_params()
2076 def _setType(self, vdiType: str) -> None:
2077 self.vdi_type = vdiType
2078 self.cowutil = getCowUtil(self.vdi_type)
2079 self.lvmcowutil = LvmCowUtil(self.cowutil)
2081 def _initFromVDIInfo(self, vdiInfo):
2082 self._setType(vdiInfo.vdiType)
2083 self.lvname = vdiInfo.lvName
2084 self.size = vdiInfo.sizeVirt
2085 self.utilisation = vdiInfo.sizeLV
2086 self.hidden = vdiInfo.hidden
2087 if self.hidden: 2087 ↛ 2088line 2087 didn't jump to line 2088, because the condition on line 2087 was never true
2088 self.managed = False
2089 self.active = vdiInfo.lvActive
2090 self.readonly = vdiInfo.lvReadonly
2091 self.parent = vdiInfo.parentUuid
2092 self.path = os.path.join(self.sr.path, self.lvname)
2093 if hasattr(self, "sm_config_override"): 2093 ↛ 2096line 2093 didn't jump to line 2096, because the condition on line 2093 was never false
2094 self.sm_config_override["vdi_type"] = self.vdi_type
2095 else:
2096 self.sm_config_override = {'vdi_type': self.vdi_type}
2097 self.loaded = True
2099 def _initFromLVInfo(self, lvInfo):
2100 self._setType(lvInfo.vdiType)
2101 self.lvname = lvInfo.name
2102 self.size = lvInfo.size
2103 self.utilisation = lvInfo.size
2104 self.hidden = lvInfo.hidden
2105 self.active = lvInfo.active
2106 self.readonly = lvInfo.readonly
2107 self.parent = ''
2108 self.path = os.path.join(self.sr.path, self.lvname)
2109 if hasattr(self, "sm_config_override"): 2109 ↛ 2112line 2109 didn't jump to line 2112, because the condition on line 2109 was never false
2110 self.sm_config_override["vdi_type"] = self.vdi_type
2111 else:
2112 self.sm_config_override = {'vdi_type': self.vdi_type}
2113 if 'vhd-parent' in self.sm_config_override: 2113 ↛ 2114line 2113 didn't jump to line 2114, because the condition on line 2113 was never true
2114 self.parent = self.sm_config_override['vhd-parent']
2115 if not VdiType.isCowImage(self.vdi_type): 2115 ↛ 2116line 2115 didn't jump to line 2116, because the condition on line 2115 was never true
2116 self.loaded = True
2118 def _initFromImageInfo(self, imageInfo):
2119 self.size = imageInfo.sizeVirt
2120 if self.parent == '' or (imageInfo.parentUuid != '' and imageInfo.parentUuid != self.parent): 2120 ↛ 2122line 2120 didn't jump to line 2122, because the condition on line 2120 was never false
2121 self.parent = imageInfo.parentUuid
2122 self.hidden = imageInfo.hidden
2123 self.loaded = True
2125 def _determineType(self):
2126 """
2127 Determine whether this is a RAW or a COW VDI.
2128 """
2129 if "vdi_ref" in self.sr.srcmd.params:
2130 vdi_ref = self.sr.srcmd.params["vdi_ref"]
2131 sm_config = self.session.xenapi.VDI.get_sm_config(vdi_ref)
2132 if sm_config.get("vdi_type"):
2133 self._setType(sm_config["vdi_type"])
2134 prefix = LV_PREFIX[self.vdi_type]
2135 self.lvname = "%s%s" % (prefix, self.uuid)
2136 self.path = os.path.join(self.sr.path, self.lvname)
2137 self.sm_config_override = sm_config
2138 return True
2140 # LVM commands can be costly, so check the file directly first in case
2141 # the LV is active
2142 found = False
2143 for vdi_type, prefix in LV_PREFIX.items():
2144 lvname = "%s%s" % (prefix, self.uuid)
2145 path = os.path.join(self.sr.path, lvname)
2146 if util.pathexists(path):
2147 if found: 2147 ↛ 2148line 2147 didn't jump to line 2148, because the condition on line 2147 was never true
2148 raise xs_errors.XenError('VDILoad',
2149 opterr="multiple VDI's: uuid %s" % self.uuid)
2150 found = True
2151 self._setType(vdi_type)
2152 self.lvname = lvname
2153 self.path = path
2154 if found:
2155 return True
2157 # now list all LV's
2158 if not lvutil._checkVG(self.sr.vgname): 2158 ↛ 2160line 2158 didn't jump to line 2160, because the condition on line 2158 was never true
2159 # when doing attach_from_config, the VG won't be there yet
2160 return False
2162 lvs = LvmCowUtil.getVolumeInfo(self.sr.lvmCache)
2163 if lvs.get(self.uuid): 2163 ↛ 2166line 2163 didn't jump to line 2166, because the condition on line 2163 was never false
2164 self._initFromLVInfo(lvs[self.uuid])
2165 return True
2166 return False
2168 def _loadThis(self):
2169 """
2170 Load VDI info for this VDI and activate the LV if it's COW. We
2171 don't do it in VDI.load() because not all VDI operations need it.
2172 """
2173 if self.loaded: 2173 ↛ 2174line 2173 didn't jump to line 2174, because the condition on line 2173 was never true
2174 if VdiType.isCowImage(self.vdi_type):
2175 self.sr.lvActivator.activate(self.uuid, self.lvname, False)
2176 return
2177 try:
2178 lvs = LvmCowUtil.getVolumeInfo(self.sr.lvmCache, self.lvname)
2179 except util.CommandException as e:
2180 raise xs_errors.XenError('VDIUnavailable',
2181 opterr='%s (LV scan error)' % os.strerror(abs(e.code)))
2182 if not lvs.get(self.uuid): 2182 ↛ 2183line 2182 didn't jump to line 2183, because the condition on line 2182 was never true
2183 raise xs_errors.XenError('VDIUnavailable', opterr='LV not found')
2184 self._initFromLVInfo(lvs[self.uuid])
2185 if VdiType.isCowImage(self.vdi_type): 2185 ↛ 2191line 2185 didn't jump to line 2191, because the condition on line 2185 was never false
2186 self.sr.lvActivator.activate(self.uuid, self.lvname, False)
2187 imageInfo = self.cowutil.getInfo(self.path, LvmCowUtil.extractUuid, False)
2188 if not imageInfo: 2188 ↛ 2189line 2188 didn't jump to line 2189, because the condition on line 2188 was never true
2189 raise xs_errors.XenError('VDIUnavailable', opterr='getInfo failed')
2190 self._initFromImageInfo(imageInfo)
2191 self.loaded = True
2193 def _chainSetActive(self, active, binary, persistent=False):
2194 if binary: 2194 ↛ 2195line 2194 didn't jump to line 2195, because the condition on line 2194 was never true
2195 (count, bcount) = RefCounter.checkLocked(self.uuid,
2196 NS_PREFIX_LVM + self.sr.uuid)
2197 if (active and bcount > 0) or (not active and bcount == 0):
2198 return # this is a redundant activation/deactivation call
2200 vdiList = {self.uuid: self.lvname}
2201 if VdiType.isCowImage(self.vdi_type): 2201 ↛ 2203line 2201 didn't jump to line 2203, because the condition on line 2201 was never false
2202 vdiList = self.cowutil.getParentChain(self.lvname, LvmCowUtil.extractUuid, self.sr.vgname)
2203 for uuid, lvName in vdiList.items():
2204 binaryParam = binary
2205 if uuid != self.uuid: 2205 ↛ 2206line 2205 didn't jump to line 2206, because the condition on line 2205 was never true
2206 binaryParam = False # binary param only applies to leaf nodes
2207 if active: 2207 ↛ 2214line 2207 didn't jump to line 2214, because the condition on line 2207 was never false
2208 self.sr.lvActivator.activate(uuid, lvName, binaryParam,
2209 persistent)
2210 else:
2211 # just add the LVs for deactivation in the final (cleanup)
2212 # step. The LVs must not have been activated during the current
2213 # operation
2214 self.sr.lvActivator.add(uuid, lvName, binaryParam)
2216 def _failClone(self, uuid, jval, msg):
2217 try:
2218 self.sr._handleInterruptedCloneOp(uuid, jval, True)
2219 self.sr.journaler.remove(self.JRN_CLONE, uuid)
2220 except Exception as e:
2221 util.SMlog('WARNING: failed to clean up failed snapshot: ' \
2222 ' %s (error ignored)' % e)
2223 raise xs_errors.XenError('VDIClone', opterr=msg)
2225 def _markHidden(self):
2226 if not VdiType.isCowImage(self.vdi_type):
2227 self.sr.lvmCache.setHidden(self.lvname)
2228 else:
2229 self.cowutil.setHidden(self.path)
2230 self.hidden = 1
2232 def _prepareThin(self, attach, vdiType):
2233 origUtilisation = self.sr.lvmCache.getSize(self.lvname)
2234 if self.sr.isMaster:
2235 # the master can prepare the VDI locally
2236 if attach:
2237 self.lvmcowutil.attachThin(self.sr.journaler, self.sr.uuid, self.uuid, self.vdi_type)
2238 else:
2239 self.lvmcowutil.detachThin(self.session, self.sr.lvmCache, self.sr.uuid, self.uuid, self.vdi_type)
2240 else:
2241 fn = "attach"
2242 if not attach:
2243 fn = "detach"
2244 pools = self.session.xenapi.pool.get_all()
2245 master = self.session.xenapi.pool.get_master(pools[0])
2246 rv = self.session.xenapi.host.call_plugin(
2247 master,
2248 self.sr.THIN_PLUGIN,
2249 fn,
2250 {
2251 "srUuid": self.sr.uuid,
2252 "vdiUuid": self.uuid,
2253 "vdiType": vdiType
2254 }
2255 )
2256 util.SMlog("call-plugin returned: %s" % rv)
2257 if not rv:
2258 raise Exception('plugin %s failed' % self.sr.THIN_PLUGIN)
2259 # refresh to pick up the size change on this slave
2260 self.sr.lvmCache.activateNoRefcount(self.lvname, True)
2262 self.utilisation = self.sr.lvmCache.getSize(self.lvname)
2263 if origUtilisation != self.utilisation:
2264 vdi_ref = self.sr.srcmd.params['vdi_ref']
2265 self.session.xenapi.VDI.set_physical_utilisation(vdi_ref,
2266 str(self.utilisation))
2267 stats = lvutil._getVGstats(self.sr.vgname)
2268 sr_utilisation = stats['physical_utilisation']
2269 self.session.xenapi.SR.set_physical_utilisation(self.sr.sr_ref,
2270 str(sr_utilisation))
2272 def _make_chain_rw(self):
2273 """
2274 In the case of QCOW2, we need the chain to be RW for the coalesce to be done by tapdisk.
2275 Setting the chain to be RW early avoids having to stop the tapdisk so LVM can acknowledge the chain RW on the slave.
2276 And while making the change to RW from the slave works without the refresh, it can cause LVM metadata corruption.
2277 """
2278 # TODO(XCPNG-3689): We could check if any VDI on the chain is RO before doing the call to master to change this
2279 if self.sr.isMaster:
2280 lvmcache = self.sr.lvmCache
2281 for uuid, lvName in self.cowutil.getParentChain(self.lvname, LvmCowUtil.extractUuid, self.sr.vgname).items():
2282 lvmcache.setReadonly(lvName, False)
2283 else:
2284 master = util.get_master_ref(self.session)
2285 response = self.session.xenapi.host.call_plugin(
2286 master,
2287 self.sr.PLUGIN_ON_SLAVE,
2288 "make_chain_rw",
2289 {
2290 "vgName": self.sr.vgname,
2291 "lvName": self.lvname,
2292 "vdiType": self.vdi_type
2293 }
2294 )
2295 util.SMlog(f"call-plugin returned: {response}")
2296 if not response:
2297 raise Exception(f"plugin {self.sr.PLUGIN_ON_SLAVE} failed")
2299 @override
2300 def update(self, sr_uuid, vdi_uuid) -> None:
2301 if self.sr.legacyMode:
2302 return
2304 #Synch the name_label of this VDI on storage with the name_label in XAPI
2305 vdi_ref = self.session.xenapi.VDI.get_by_uuid(self.uuid)
2306 update_map = {}
2307 update_map[METADATA_UPDATE_OBJECT_TYPE_TAG] = \
2308 METADATA_OBJECT_TYPE_VDI
2309 update_map[UUID_TAG] = self.uuid
2310 update_map[NAME_LABEL_TAG] = util.to_plain_string( \
2311 self.session.xenapi.VDI.get_name_label(vdi_ref))
2312 update_map[NAME_DESCRIPTION_TAG] = util.to_plain_string( \
2313 self.session.xenapi.VDI.get_name_description(vdi_ref))
2314 update_map[SNAPSHOT_TIME_TAG] = \
2315 self.session.xenapi.VDI.get_snapshot_time(vdi_ref)
2316 update_map[METADATA_OF_POOL_TAG] = \
2317 self.session.xenapi.VDI.get_metadata_of_pool(vdi_ref)
2318 LVMMetadataHandler(self.sr.mdpath).updateMetadata(update_map)
2320 @override
2321 def _ensure_cbt_space(self) -> None:
2322 # We need virtual_size to compute the size in case of a bigger VDI
2323 self.sr.ensureCBTSpace(self.size)
2325 @override
2326 def _create_cbt_log(self) -> str:
2327 logname = self._get_cbt_logname(self.uuid)
2328 logsize = max(util.roundup(CBT_BLOCK_SIZE, self.size//CBT_BLOCK_SIZE), self.sr.journaler.LV_SIZE)
2329 # We choose 4MiB as the minimum for the log size to maintain the old behavior and compute the correct amount
2330 # if we need a bigger LV for the CBT (can happen with big QCOW2)
2331 self.sr.lvmCache.create(logname, logsize, CBTLOG_TAG)
2332 logpath = super(LVMVDI, self)._create_cbt_log()
2333 self.sr.lvmCache.deactivateNoRefcount(logname)
2334 return logpath
2336 @override
2337 def _delete_cbt_log(self) -> None:
2338 logpath = self._get_cbt_logpath(self.uuid)
2339 if not self._cbt_log_exists(logpath):
2340 return
2341 logname = self._get_cbt_logname(self.uuid)
2342 if not self.sr.isMaster and self.sr.is_shared():
2343 master = util.get_master_ref(self.session)
2344 response = self.session.xenapi.host.call_plugin(
2345 master,
2346 self.sr.PLUGIN_ON_MASTER,
2347 "delete_cbt_log",
2348 {
2349 "vgName": self.sr.vgname,
2350 "lvName": logname,
2351 }
2352 )
2353 util.SMlog(f"on-master.delete_cbt_log call-plugin returned: {response}")
2354 if not response:
2355 raise Exception(f"plugin {self.sr.PLUGIN_ON_MASTER} failed")
2356 lvutil._lvmBugCleanup(logpath)
2357 return
2358 self.sr.lvmCache.remove(logname)
2360 @override
2361 def _rename(self, oldpath, newpath) -> None:
2362 oldname = os.path.basename(oldpath)
2363 newname = os.path.basename(newpath)
2364 self.sr.lvmCache.rename(oldname, newname)
2366 @override
2367 def update_slaves_on_cbt_disable(self, cbtlog) -> None:
2368 args = {
2369 "vgName": self.sr.vgname,
2370 "action1": "deactivateNoRefcount",
2371 "lvName1": cbtlog
2372 }
2374 host_refs = util.get_hosts_attached_on(self.session, [self.uuid])
2376 message = f"Deactivating {cbtlog}"
2377 self.sr.call_on_slave(args, host_refs, message)
2379 @override
2380 def _activate_cbt_log(self, lv_name) -> bool:
2381 self.sr.lvmCache.refresh()
2382 if not self.sr.lvmCache.is_active(lv_name): 2382 ↛ 2383line 2382 didn't jump to line 2383, because the condition on line 2382 was never true
2383 try:
2384 self.sr.lvmCache.activateNoRefcount(lv_name)
2385 return True
2386 except Exception as e:
2387 util.SMlog("Exception in _activate_cbt_log, "
2388 "Error: %s." % str(e))
2389 raise
2390 else:
2391 return False
2393 @override
2394 def _deactivate_cbt_log(self, lv_name) -> None:
2395 try:
2396 self.sr.lvmCache.deactivateNoRefcount(lv_name)
2397 except Exception as e:
2398 util.SMlog("Exception in _deactivate_cbt_log, Error: %s." % str(e))
2399 raise
2401 @override
2402 def _cbt_log_exists(self, logpath) -> bool:
2403 return lvutil.exists(logpath)
2405if __name__ == '__main__': 2405 ↛ 2406line 2405 didn't jump to line 2406, because the condition on line 2405 was never true
2406 SRCommand.run(LVMSR, DRIVER_INFO)
2407else:
2408 SR.registerSR(LVMSR)