Coverage for drivers/lvutil.py : 49%
Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# Copyright (C) Citrix Systems Inc.
2#
3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU Lesser General Public License as published
5# by the Free Software Foundation; version 2.1 only.
6#
7# This program is distributed in the hope that it will be useful,
8# but WITHOUT ANY WARRANTY; without even the implied warranty of
9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10# GNU Lesser General Public License for more details.
11#
12# You should have received a copy of the GNU Lesser General Public License
13# along with this program; if not, write to the Free Software Foundation, Inc.,
14# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15#
16# Miscellaneous LVM utility functions
17#
19import traceback
20import re
21import os
22import errno
23import time
25import scsiutil
26from fairlock import Fairlock
27import util
28import xs_errors
29import xml.dom.minidom
30from constants import EXT_PREFIX, VG_LOCATION, VG_PREFIX
31import lvmcache
32import srmetadata
34MDVOLUME_NAME = 'MGT'
35VDI_UUID_TAG_PREFIX = 'vdi_'
36LVM_BIN = os.path.isfile('/sbin/lvdisplay') and '/sbin' or '/usr/sbin'
37CMD_VGS = "vgs"
38CMD_VGCREATE = "vgcreate"
39CMD_VGREMOVE = "vgremove"
40CMD_VGCHANGE = "vgchange"
41CMD_VGEXTEND = "vgextend"
42CMD_PVS = "pvs"
43CMD_PVCREATE = "pvcreate"
44CMD_PVREMOVE = "pvremove"
45CMD_PVRESIZE = "pvresize"
46CMD_LVS = "lvs"
47CMD_LVDISPLAY = "lvdisplay"
48CMD_LVCREATE = "lvcreate"
49CMD_LVREMOVE = "lvremove"
50CMD_LVCHANGE = "lvchange"
51CMD_LVRENAME = "lvrename"
52CMD_LVRESIZE = "lvresize"
53CMD_LVEXTEND = "lvextend"
54CMD_DMSETUP = "/sbin/dmsetup"
56MAX_OPERATION_DURATION = 15
58LVM_SIZE_INCREMENT = 4 * 1024 * 1024
59LV_TAG_HIDDEN = "hidden"
60LVM_FAIL_RETRIES = 10
62MASTER_LVM_CONF = '/etc/lvm/master'
63DEF_LVM_CONF = '/etc/lvm'
65VG_COMMANDS = frozenset({CMD_VGS, CMD_VGCREATE, CMD_VGREMOVE, CMD_VGCHANGE,
66 CMD_VGEXTEND})
67PV_COMMANDS = frozenset({CMD_PVS, CMD_PVCREATE, CMD_PVREMOVE, CMD_PVRESIZE})
68LV_COMMANDS = frozenset({CMD_LVS, CMD_LVDISPLAY, CMD_LVCREATE, CMD_LVREMOVE,
69 CMD_LVCHANGE, CMD_LVRENAME, CMD_LVRESIZE,
70 CMD_LVEXTEND})
71DM_COMMANDS = frozenset({CMD_DMSETUP})
73LVM_COMMANDS = VG_COMMANDS.union(PV_COMMANDS, LV_COMMANDS, DM_COMMANDS)
75LVM_LOCK = 'lvm'
78def extract_vgname(str_in):
79 """Search for and return a VG name
81 Search 'str_in' for a substring in the form of 'VG_XenStorage-<UUID>'.
82 If there are more than one VG names, the first is returned.
84 Input:
85 str_in -- (str) string to search for a VG name
86 in the format specified above.
88 Return:
89 vgname -- if found -> (str)
90 if not found -> None
92 Raise:
93 TypeError
94 """
96 if not util.is_string(str_in):
97 raise TypeError("'str_in' not of type 'str'.")
99 i = str_in.find(VG_PREFIX)
100 prefix = VG_PREFIX
102 if i == -1:
103 i = str_in.find(EXT_PREFIX)
104 prefix = EXT_PREFIX
106 uuid_start = i + len(prefix)
107 re_obj = util.match_uuid(str_in[uuid_start:])
109 if i != -1 and re_obj:
110 return prefix + re_obj.group(0) # vgname
112 return None
114LVM_RETRY_ERRORS = [
115 "Incorrect checksum in metadata area header"
116]
118def calcSizeLV(size: int) -> int:
119 return util.roundup(LVM_SIZE_INCREMENT, size)
122def lvmretry(func):
123 def check_exception(exception):
124 retry = False
125 for error in LVM_RETRY_ERRORS:
126 if error in exception.reason:
127 retry = True
128 return retry
130 def decorated(*args, **kwargs):
131 for i in range(LVM_FAIL_RETRIES): 131 ↛ exitline 131 didn't return from function 'decorated', because the loop on line 131 didn't complete
132 try:
133 return func(*args, **kwargs)
134 except util.CommandException as ce:
135 retry = check_exception(ce)
136 if not retry or (i == LVM_FAIL_RETRIES - 1):
137 raise
139 time.sleep(1)
141 decorated.__name__ = func.__name__
142 return decorated
145def cmd_lvm(cmd, pread_func=util.pread2, *args):
146 """ Construct and run the appropriate lvm command.
148 For PV commands, the full path to the device is required.
150 Input:
151 cmd -- (list) lvm command
152 cmd[0] -- (str) lvm command name
153 cmd[1:] -- (str) lvm command parameters
155 pread_func -- (function) the flavor of util.pread to use
156 to execute the lvm command
157 Default: util.pread2()
159 *args -- extra arguments passed to cmd_lvm will be passed
160 to 'pread_func'
162 Return:
163 stdout -- (str) stdout after running the lvm command.
165 Raise:
166 util.CommandException
167 """
169 if type(cmd) is not list:
170 util.SMlog("CMD_LVM: Argument 'cmd' not of type 'list'")
171 return None
172 if not len(cmd):
173 util.SMlog("CMD_LVM: 'cmd' list is empty")
174 return None
176 lvm_cmd, lvm_args = cmd[0], cmd[1:]
178 if lvm_cmd not in LVM_COMMANDS:
179 util.SMlog("CMD_LVM: '{}' is not a valid lvm command".format(lvm_cmd))
180 return None
182 for arg in lvm_args:
183 if not util.is_string(arg):
184 util.SMlog("CMD_LVM: Not all lvm arguments are of type 'str'")
185 return None
187 with Fairlock("devicemapper"):
188 start_time = time.time()
189 stdout = pread_func([os.path.join(LVM_BIN, lvm_cmd)] + lvm_args, * args)
190 end_time = time.time()
192 if (end_time - start_time > MAX_OPERATION_DURATION):
193 util.SMlog("***** Long LVM call of '%s' took %s" % (lvm_cmd, (end_time - start_time)))
195 return stdout
198class LVInfo:
199 name = ""
200 size = 0
201 active = False
202 open = False
203 hidden = False
204 readonly = False
206 def __init__(self, name):
207 self.name = name
209 def toString(self):
210 return "%s, size=%d, active=%s, open=%s, hidden=%s, ro=%s" % \
211 (self.name, self.size, self.active, self.open, self.hidden, \
212 self.readonly)
215def _checkVG(vgname):
216 try:
217 cmd_lvm([CMD_VGS, "--readonly", vgname])
218 return True
219 except:
220 return False
223def _checkPV(pvname):
224 try:
225 cmd_lvm([CMD_PVS, pvname])
226 return True
227 except:
228 return False
231def _checkLV(path):
232 try:
233 cmd_lvm([CMD_LVDISPLAY, path])
234 return True
235 except:
236 return False
239def _getLVsize(path):
240 try:
241 lines = cmd_lvm([CMD_LVDISPLAY, "-c", path]).split(':')
242 return int(lines[6]) * 512
243 except:
244 raise xs_errors.XenError('VDIUnavailable', \
245 opterr='no such VDI %s' % path)
248def _getVGstats(vgname):
249 try:
250 text = cmd_lvm([CMD_VGS, "--noheadings", "--nosuffix",
251 "--units", "b", vgname],
252 pread_func=util.pread).split()
253 size = int(text[5])
254 freespace = int(text[6])
255 utilisation = size - freespace
256 stats = {}
257 stats['physical_size'] = size
258 stats['physical_utilisation'] = utilisation
259 stats['freespace'] = freespace
260 return stats
261 except util.CommandException as inst:
262 raise xs_errors.XenError('VDILoad', \
263 opterr='rvgstats failed error is %d' % inst.code)
264 except ValueError:
265 raise xs_errors.XenError('VDILoad', opterr='rvgstats failed')
268def _getPVstats(dev):
269 try:
270 text = cmd_lvm([CMD_PVS, "--noheadings", "--nosuffix",
271 "--units", "b", dev],
272 pread_func=util.pread).split()
273 size = int(text[4])
274 freespace = int(text[5])
275 utilisation = size - freespace
276 stats = {}
277 stats['physical_size'] = size
278 stats['physical_utilisation'] = utilisation
279 stats['freespace'] = freespace
280 return stats
281 except util.CommandException as inst:
282 raise xs_errors.XenError('VDILoad', \
283 opterr='pvstats failed error is %d' % inst.code)
284 except ValueError:
285 raise xs_errors.XenError('VDILoad', opterr='rvgstats failed')
288# Retrieves the UUID of the SR that corresponds to the specified Physical
289# Volume (pvname). Each element in prefix_list is checked whether it is a
290# prefix of Volume Groups that correspond to the specified PV. If so, the
291# prefix is stripped from the matched VG name and the remainder is returned
292# (effectively the SR UUID). If no match if found, the empty string is
293# returned.
294# E.g.
295# PV VG Fmt Attr PSize PFree
296# /dev/sda4 VG_XenStorage-some-hex-value lvm2 a- 224.74G 223.73G
297# will return "some-hex-value".
298def _get_sr_uuid(pvname, prefix_list):
299 try:
300 return match_VG(cmd_lvm([CMD_PVS, "--noheadings",
301 "-o", "vg_name", pvname]), prefix_list)
302 except:
303 return ""
306# Retrieves the names of the Physical Volumes which are used by the specified
307# Volume Group
308# e.g.
309# PV VG Fmt Attr PSize PFree
310# /dev/sda4 VG_XenStorage-some-hex-value lvm2 a- 224.74G 223.73G
311# will return "/dev/sda4" when given the argument "VG_XenStorage-some-hex-value".
312def get_pv_for_vg(vgname):
313 try:
314 result = cmd_lvm([CMD_PVS, "--noheadings",
315 '-S', 'vg_name=%s' % vgname, '-o', 'name'])
316 return [x.strip() for x in result.splitlines()]
317 except util.CommandException:
318 return []
321# Tries to match any prefix contained in prefix_list in s. If matched, the
322# remainder string is returned, else the empty string is returned. E.g. if s is
323# "VG_XenStorage-some-hex-value" and prefix_list contains "VG_XenStorage-",
324# "some-hex-value" is returned.
325#
326# TODO Items in prefix_list are expected to be found at the beginning of the
327# target string, though if any of them is found inside it a match will be
328# produced. This could be remedied by making the regular expression more
329# specific.
330def match_VG(s, prefix_list):
331 for val in prefix_list:
332 regex = re.compile(val)
333 if regex.search(s, 0):
334 return s.split(val)[1]
335 return ""
338# Retrieves the devices an SR is composed of. A dictionary is returned, indexed
339# by the SR UUID, where each SR UUID is mapped to a comma-separated list of
340# devices. Exceptions are ignored.
341def scan_srlist(prefix, root):
342 VGs = {}
343 for dev in root.split(','):
344 try:
345 sr_uuid = _get_sr_uuid(dev, [prefix]).strip(' \n')
346 if len(sr_uuid):
347 if sr_uuid in VGs:
348 VGs[sr_uuid] += ",%s" % dev
349 else:
350 VGs[sr_uuid] = dev
351 except Exception as e:
352 util.logException("exception (ignored): %s" % e)
353 continue
354 return VGs
357# Converts an SR list to an XML document with the following structure:
358# <SRlist>
359# <SR>
360# <UUID>...</UUID>
361# <Devlist>...</Devlist>
362# <size>...</size>
363# <!-- If includeMetadata is set to True, the following additional nodes
364# are supplied. -->
365# <name_label>...</name_label>
366# <name_description>...</name_description>
367# <pool_metadata_detected>...</pool_metadata_detected>
368# </SR>
369#
370# <SR>...</SR>
371# </SRlist>
372#
373# Arguments:
374# VGs: a dictionary containing the SR UUID to device list mappings
375# prefix: the prefix that if prefixes the SR UUID the VG is produced
376# includeMetadata (optional): include additional information
377def srlist_toxml(VGs, prefix, includeMetadata=False):
378 dom = xml.dom.minidom.Document()
379 element = dom.createElement("SRlist")
380 dom.appendChild(element)
382 for val in VGs:
383 entry = dom.createElement('SR')
384 element.appendChild(entry)
386 subentry = dom.createElement("UUID")
387 entry.appendChild(subentry)
388 textnode = dom.createTextNode(val)
389 subentry.appendChild(textnode)
391 subentry = dom.createElement("Devlist")
392 entry.appendChild(subentry)
393 textnode = dom.createTextNode(VGs[val])
394 subentry.appendChild(textnode)
396 subentry = dom.createElement("size")
397 entry.appendChild(subentry)
398 size = str(_getVGstats(prefix + val)['physical_size'])
399 textnode = dom.createTextNode(size)
400 subentry.appendChild(textnode)
402 if includeMetadata:
403 metadataVDI = None
405 # add SR name_label
406 mdpath = os.path.join(VG_LOCATION, VG_PREFIX + val)
407 mdpath = os.path.join(mdpath, MDVOLUME_NAME)
408 mgtVolActivated = False
409 try:
410 if not os.path.exists(mdpath):
411 # probe happens out of band with attach so this volume
412 # may not have been activated at this point
413 lvmCache = lvmcache.LVMCache(VG_PREFIX + val)
414 lvmCache.activateNoRefcount(MDVOLUME_NAME)
415 mgtVolActivated = True
417 sr_metadata = \
418 srmetadata.LVMMetadataHandler(mdpath, \
419 False).getMetadata()[0]
420 subentry = dom.createElement("name_label")
421 entry.appendChild(subentry)
422 textnode = dom.createTextNode(sr_metadata[srmetadata.NAME_LABEL_TAG])
423 subentry.appendChild(textnode)
425 # add SR description
426 subentry = dom.createElement("name_description")
427 entry.appendChild(subentry)
428 textnode = dom.createTextNode(sr_metadata[srmetadata.NAME_DESCRIPTION_TAG])
429 subentry.appendChild(textnode)
431 # add metadata VDI UUID
432 metadataVDI = srmetadata.LVMMetadataHandler(mdpath, \
433 False).findMetadataVDI()
434 subentry = dom.createElement("pool_metadata_detected")
435 entry.appendChild(subentry)
436 if metadataVDI is not None:
437 subentry.appendChild(dom.createTextNode("true"))
438 else:
439 subentry.appendChild(dom.createTextNode("false"))
440 finally:
441 if mgtVolActivated:
442 # deactivate only if we activated it
443 lvmCache.deactivateNoRefcount(MDVOLUME_NAME)
445 return dom.toprettyxml()
448def _openExclusive(dev, retry):
449 try:
450 return os.open("%s" % dev, os.O_RDWR | os.O_EXCL)
451 except OSError as ose:
452 opened_by = ''
453 if ose.errno == 16:
454 if retry:
455 util.SMlog('Device %s is busy, settle and one shot retry' %
456 dev)
457 util.pread2(['/usr/sbin/udevadm', 'settle'])
458 return _openExclusive(dev, False)
459 else:
460 util.SMlog('Device %s is busy after retry' % dev)
462 util.SMlog('Opening device %s failed with %d' % (dev, ose.errno))
463 raise xs_errors.XenError(
464 'SRInUse', opterr=('Device %s in use, please check your existing '
465 + 'SRs for an instance of this device') % dev)
468def createVG(root, vgname):
469 systemroot = util.getrootdev()
470 rootdev = root.split(',')[0]
472 # Create PVs for each device
473 for dev in root.split(','):
474 if dev in [systemroot, '%s1' % systemroot, '%s2' % systemroot]:
475 raise xs_errors.XenError('Rootdev', \
476 opterr=('Device %s contains core system files, ' \
477 + 'please use another device') % dev)
478 if not os.path.exists(dev):
479 raise xs_errors.XenError('InvalidDev', \
480 opterr=('Device %s does not exist') % dev)
482 f = _openExclusive(dev, True)
483 os.close(f)
485 # Wipe any fs signature
486 try:
487 util.wipefs(dev)
488 except util.CommandException as inst:
489 raise xs_errors.XenError('WipefsFailure', opterr='device %s' % dev) # from inst
491 if not (dev == rootdev):
492 try:
493 cmd_lvm([CMD_PVCREATE, "-ff", "-y", "--metadatasize", "10M", dev])
494 except util.CommandException as inst:
495 raise xs_errors.XenError('LVMPartCreate',
496 opterr='error is %d' % inst.code)
498 # Create VG on first device
499 try:
500 cmd_lvm([CMD_VGCREATE, "--metadatasize", "10M", vgname, rootdev])
501 except:
502 raise xs_errors.XenError('LVMGroupCreate')
504 # Then add any additional devs into the VG
505 for dev in root.split(',')[1:]:
506 try:
507 cmd_lvm([CMD_VGEXTEND, vgname, dev])
508 except util.CommandException as inst:
509 # One of the PV args failed, delete SR
510 try:
511 cmd_lvm([CMD_VGREMOVE, vgname])
512 except:
513 pass
514 raise xs_errors.XenError('LVMGroupCreate')
516 try:
517 cmd_lvm([CMD_VGCHANGE, "-an", vgname])
518 except util.CommandException as inst:
519 raise xs_errors.XenError('LVMUnMount', opterr='errno is %d' % inst.code)
521 # End block
523def getPVsInVG(vgname):
524 # Get PVs in a specific VG
525 pvs_ret = cmd_lvm([CMD_PVS, '--separator', ' ', '--noheadings', '-o', 'pv_name,vg_name'])
527 # Parse each line to extract PV and VG information
528 # No need to handle exceptions here, return empty list if any error
529 pvs_in_vg = []
530 lines = pvs_ret.strip().split('\n')
531 for line in lines:
532 # To avoid invalid return format
533 parts = line.split()
534 if len(parts) != 2:
535 util.SMlog("Warning: Invalid or empty line in pvs output: %s" % line)
536 continue
537 pv, vg = parts
538 if vg == vgname:
539 pvs_in_vg.append(pv)
541 util.SMlog("PVs in VG %s: %s" % (vgname, pvs_in_vg))
542 return pvs_in_vg
544def removeVG(root, vgname):
545 # Check PVs match VG
546 try:
547 for dev in root.split(','):
548 txt = cmd_lvm([CMD_PVS, dev])
549 if txt.find(vgname) == -1:
550 raise xs_errors.XenError('LVMNoVolume', \
551 opterr='volume is %s' % vgname)
552 except util.CommandException as inst:
553 raise xs_errors.XenError('PVSfailed', \
554 opterr='error is %d' % inst.code)
556 try:
557 # Get PVs in VG before removing the VG
558 devs_in_vg = getPVsInVG(vgname)
559 cmd_lvm([CMD_VGREMOVE, vgname])
561 for dev in devs_in_vg:
562 cmd_lvm([CMD_PVREMOVE, dev])
563 except util.CommandException as inst:
564 raise xs_errors.XenError('LVMDelete', \
565 opterr='errno is %d' % inst.code)
568def resizePV(dev):
569 try:
570 cmd_lvm([CMD_PVRESIZE, dev])
571 except util.CommandException as inst:
572 util.SMlog("Failed to grow the PV, non-fatal")
575def setActiveVG(path, active, config=None):
576 "activate or deactivate VG 'path'"
577 val = "n"
578 if active:
579 val = "y"
580 cmd = [CMD_VGCHANGE, "-a" + val, path]
581 if config:
582 cmd.append("--config")
583 cmd.append(config)
584 cmd_lvm(cmd)
587def checkPVScsiIds(vgname, SCSIid):
588 # Get all the PVs for the specified vgName even if not active
589 cmd = [CMD_PVS, '-a', '--select', f'vgname={vgname}', '--no-headings']
590 text = cmd_lvm(cmd)
591 pv_paths = [x.split()[0] for x in text.splitlines()]
592 for pv_path in pv_paths:
593 pv_scsi_id = scsiutil.getSCSIid(pv_path)
594 if pv_scsi_id != SCSIid:
595 raise xs_errors.XenError(
596 'PVMultiIDs',
597 opterr=f'Found PVs {",".join(pv_paths)} and unexpected '
598 f'SCSI ID {pv_scsi_id}, expected {SCSIid}')
600@lvmretry
601def create(name, size, vgname, tag=None, size_in_percentage=None):
602 if size_in_percentage:
603 cmd = [CMD_LVCREATE, "-n", name, "-l", size_in_percentage, vgname]
604 else:
605 size_mb = size // (1024 * 1024)
606 cmd = [CMD_LVCREATE, "-n", name, "-L", str(size_mb), vgname]
607 if tag:
608 cmd.extend(["--addtag", tag])
610 cmd.extend(['-W', 'n'])
611 cmd_lvm(cmd)
614def remove(path, config_param=None):
615 # see deactivateNoRefcount()
616 for i in range(LVM_FAIL_RETRIES): 616 ↛ 624line 616 didn't jump to line 624, because the loop on line 616 didn't complete
617 try:
618 _remove(path, config_param)
619 break
620 except util.CommandException as e:
621 if i >= LVM_FAIL_RETRIES - 1:
622 raise
623 util.SMlog("*** lvremove failed on attempt #%d" % i)
624 _lvmBugCleanup(path)
627@lvmretry
628def _remove(path, config_param=None):
629 CONFIG_TAG = "--config"
630 cmd = [CMD_LVREMOVE, "-f", path]
631 if config_param:
632 cmd.extend([CONFIG_TAG, "devices{" + config_param + "}"])
633 ret = cmd_lvm(cmd)
636@lvmretry
637def rename(path, newName):
638 cmd_lvm([CMD_LVRENAME, path, newName], pread_func=util.pread)
641@lvmretry
642def setReadonly(path, readonly):
643 val = "r"
644 if not readonly:
645 val += "w"
646 ret = cmd_lvm([CMD_LVCHANGE, path, "-p", val], pread_func=util.pread)
649def exists(path):
650 (rc, stdout, stderr) = cmd_lvm([CMD_LVS, "--noheadings", path], pread_func=util.doexec)
651 return rc == 0
653def listLv(path=None):
654 rc, stdout, stderr = cmd_lvm([CMD_LVS, "--noheadings", path or ""], pread_func=util.doexec)
655 if rc == 0: 655 ↛ 658line 655 didn't jump to line 658, because the condition on line 655 was never false
656 return stdout
658 raise xs_errors.XenError('VolNotFound')
660@lvmretry
661def setSize(path, size, confirm):
662 sizeMB = size // (1024 * 1024)
663 if confirm:
664 cmd_lvm([CMD_LVRESIZE, "-L", str(sizeMB), path], util.pread3, "y\n")
665 else:
666 cmd_lvm([CMD_LVRESIZE, "-L", str(sizeMB), path], pread_func=util.pread)
669@lvmretry
670def setHidden(path, hidden=True):
671 opt = "--addtag"
672 if not hidden:
673 opt = "--deltag"
674 cmd_lvm([CMD_LVCHANGE, opt, LV_TAG_HIDDEN, path])
677@lvmretry
678def _activate(path):
679 cmd = [CMD_LVCHANGE, "-ay", path]
680 cmd_lvm(cmd)
681 if not _checkActive(path):
682 raise util.CommandException(-1, str(cmd), "LV not activated")
685def activateNoRefcount(path, refresh):
686 _activate(path)
687 if refresh: 687 ↛ 689line 687 didn't jump to line 689, because the condition on line 687 was never true
688 # Override slave mode lvm.conf for this command
689 os.environ['LVM_SYSTEM_DIR'] = MASTER_LVM_CONF
690 text = cmd_lvm([CMD_LVCHANGE, "--refresh", path])
691 mapperDevice = path[5:].replace("-", "--").replace("/", "-")
692 cmd = [CMD_DMSETUP, "table", mapperDevice]
693 with Fairlock("devicemapper"):
694 ret = util.pread(cmd)
695 util.SMlog("DM table for %s: %s" % (path, ret.strip()))
696 # Restore slave mode lvm.conf
697 os.environ['LVM_SYSTEM_DIR'] = DEF_LVM_CONF
700def deactivateNoRefcount(path):
701 # LVM has a bug where if an "lvs" command happens to run at the same time
702 # as "lvchange -an", it might hold the device in use and cause "lvchange
703 # -an" to fail. Thus, we need to retry if "lvchange -an" fails. Worse yet,
704 # the race could lead to "lvchange -an" starting to deactivate (removing
705 # the symlink), failing to "dmsetup remove" the device, and still returning
706 # success. Thus, we need to check for the device mapper file existence if
707 # "lvchange -an" returns success.
708 for i in range(LVM_FAIL_RETRIES): 708 ↛ 716line 708 didn't jump to line 716, because the loop on line 708 didn't complete
709 try:
710 _deactivate(path)
711 break
712 except util.CommandException:
713 if i >= LVM_FAIL_RETRIES - 1:
714 raise
715 util.SMlog("*** lvchange -an failed on attempt #%d" % i)
716 _lvmBugCleanup(path)
719@lvmretry
720def _deactivate(path):
721 text = cmd_lvm([CMD_LVCHANGE, "-an", path])
724def _checkActive(path):
725 if util.pathexists(path):
726 return True
728 util.SMlog("_checkActive: %s does not exist!" % path)
729 symlinkExists = os.path.lexists(path)
730 util.SMlog("_checkActive: symlink exists: %s" % symlinkExists)
732 util.SMlog(f"LVM says {listLv(path=path)}")
734 mapperDeviceExists = False
735 mapperDevice = path[5:].replace("-", "--").replace("/", "-")
736 cmd = [CMD_DMSETUP, "status", mapperDevice]
737 try:
738 with Fairlock("devicemapper"):
739 ret = util.pread2(cmd)
740 mapperDeviceExists = True
741 util.SMlog("_checkActive: %s: %s" % (mapperDevice, ret))
742 except util.CommandException:
743 util.SMlog("_checkActive: device %s does not exist" % mapperDevice)
745 mapperPath = "/dev/mapper/" + mapperDevice
746 mapperPathExists = util.pathexists(mapperPath)
747 util.SMlog("_checkActive: path %s exists: %s" % \
748 (mapperPath, mapperPathExists))
750 if mapperDeviceExists and mapperPathExists and not symlinkExists: 750 ↛ 752line 750 didn't jump to line 752, because the condition on line 750 was never true
751 # we can fix this situation manually here
752 try:
753 util.SMlog("_checkActive: attempt to create the symlink manually.")
754 os.symlink(mapperPath, path)
755 except OSError as e:
756 util.SMlog("ERROR: failed to symlink!")
757 if e.errno != errno.EEXIST:
758 raise
759 if util.pathexists(path):
760 util.SMlog("_checkActive: created the symlink manually")
761 return True
763 return False
766def _lvmBugCleanup(path):
767 # the device should not exist at this point. If it does, this was an LVM
768 # bug, and we manually clean up after LVM here
769 mapperDevice = path[5:].replace("-", "--").replace("/", "-")
770 mapperPath = "/dev/mapper/" + mapperDevice
772 nodeExists = False
773 cmd_st = [CMD_DMSETUP, "status", mapperDevice]
774 cmd_rm = [CMD_DMSETUP, "remove", mapperDevice]
775 cmd_rf = [CMD_DMSETUP, "remove", mapperDevice, "--force"]
777 try:
778 with Fairlock("devicemapper"):
779 util.pread(cmd_st, expect_rc=1)
780 except util.CommandException as e:
781 if e.code == 0: 781 ↛ 784line 781 didn't jump to line 784, because the condition on line 781 was never false
782 nodeExists = True
784 if not util.pathexists(mapperPath) and not nodeExists:
785 return
787 util.SMlog("_lvmBugCleanup: seeing dm file %s" % mapperPath)
789 # destroy the dm device
790 if nodeExists: 790 ↛ 817line 790 didn't jump to line 817, because the condition on line 790 was never false
791 util.SMlog("_lvmBugCleanup: removing dm device %s" % mapperDevice)
792 for i in range(LVM_FAIL_RETRIES): 792 ↛ 817line 792 didn't jump to line 817, because the loop on line 792 didn't complete
793 try:
794 with Fairlock("devicemapper"):
795 util.pread2(cmd_rm)
796 break
797 except util.CommandException as e:
798 if i < LVM_FAIL_RETRIES - 1:
799 util.SMlog("Failed on try %d, retrying" % i)
800 try:
801 with Fairlock("devicemapper"):
802 util.pread(cmd_st, expect_rc=1)
803 util.SMlog("_lvmBugCleanup: dm device {}"
804 " removed".format(mapperDevice)
805 )
806 break
807 except:
808 cmd_rm = cmd_rf
809 time.sleep(1)
810 else:
811 # make sure the symlink is still there for consistency
812 if not os.path.lexists(path): 812 ↛ 815line 812 didn't jump to line 815, because the condition on line 812 was never false
813 os.symlink(mapperPath, path)
814 util.SMlog("_lvmBugCleanup: restored symlink %s" % path)
815 raise e
817 if util.pathexists(mapperPath):
818 os.unlink(mapperPath)
819 util.SMlog("_lvmBugCleanup: deleted devmapper file %s" % mapperPath)
821 # delete the symlink
822 if os.path.lexists(path):
823 os.unlink(path)
824 util.SMlog("_lvmBugCleanup: deleted symlink %s" % path)
827# mdpath is of format /dev/VG-SR-UUID/MGT
828# or in other words /VG_LOCATION/VG_PREFIXSR-UUID/MDVOLUME_NAME
829def ensurePathExists(mdpath):
830 if not os.path.exists(mdpath):
831 vgname = mdpath.split('/')[2]
832 lvmCache = lvmcache.LVMCache(vgname)
833 lvmCache.activateNoRefcount(MDVOLUME_NAME)
836def removeDevMapperEntry(path, strict=True):
837 try:
838 # remove devmapper entry using dmsetup
839 cmd = [CMD_DMSETUP, "remove", path]
840 cmd_lvm(cmd)
841 return True
842 except Exception as e:
843 if not strict:
844 cmd = [CMD_DMSETUP, "status", path]
845 try:
846 with Fairlock("devicemapper"):
847 util.pread(cmd, expect_rc=1)
848 return True
849 except:
850 pass # Continuining will fail and log the right way
851 ret = util.pread2(["lsof", path])
852 util.SMlog("removeDevMapperEntry: dmsetup remove failed for file %s " \
853 "with error %s, and lsof ret is %s." % (path, str(e), ret))
854 return False