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 lvhdutil import VG_LOCATION, VG_PREFIX
31from constants import EXT_PREFIX
32import lvmcache
33import srmetadata
35MDVOLUME_NAME = 'MGT'
36VDI_UUID_TAG_PREFIX = 'vdi_'
37LVM_BIN = os.path.isfile('/sbin/lvdisplay') and '/sbin' or '/usr/sbin'
38CMD_VGS = "vgs"
39CMD_VGCREATE = "vgcreate"
40CMD_VGREMOVE = "vgremove"
41CMD_VGCHANGE = "vgchange"
42CMD_VGEXTEND = "vgextend"
43CMD_PVS = "pvs"
44CMD_PVCREATE = "pvcreate"
45CMD_PVREMOVE = "pvremove"
46CMD_PVRESIZE = "pvresize"
47CMD_LVS = "lvs"
48CMD_LVDISPLAY = "lvdisplay"
49CMD_LVCREATE = "lvcreate"
50CMD_LVREMOVE = "lvremove"
51CMD_LVCHANGE = "lvchange"
52CMD_LVRENAME = "lvrename"
53CMD_LVRESIZE = "lvresize"
54CMD_LVEXTEND = "lvextend"
55CMD_DMSETUP = "/sbin/dmsetup"
57MAX_OPERATION_DURATION = 15
59LVM_SIZE_INCREMENT = 4 * 1024 * 1024
60LV_TAG_HIDDEN = "hidden"
61LVM_FAIL_RETRIES = 10
63MASTER_LVM_CONF = '/etc/lvm/master'
64DEF_LVM_CONF = '/etc/lvm'
66VG_COMMANDS = frozenset({CMD_VGS, CMD_VGCREATE, CMD_VGREMOVE, CMD_VGCHANGE,
67 CMD_VGEXTEND})
68PV_COMMANDS = frozenset({CMD_PVS, CMD_PVCREATE, CMD_PVREMOVE, CMD_PVRESIZE})
69LV_COMMANDS = frozenset({CMD_LVS, CMD_LVDISPLAY, CMD_LVCREATE, CMD_LVREMOVE,
70 CMD_LVCHANGE, CMD_LVRENAME, CMD_LVRESIZE,
71 CMD_LVEXTEND})
72DM_COMMANDS = frozenset({CMD_DMSETUP})
74LVM_COMMANDS = VG_COMMANDS.union(PV_COMMANDS, LV_COMMANDS, DM_COMMANDS)
76LVM_LOCK = 'lvm'
79def extract_vgname(str_in):
80 """Search for and return a VG name
82 Search 'str_in' for a substring in the form of 'VG_XenStorage-<UUID>'.
83 If there are more than one VG names, the first is returned.
85 Input:
86 str_in -- (str) string to search for a VG name
87 in the format specified above.
89 Return:
90 vgname -- if found -> (str)
91 if not found -> None
93 Raise:
94 TypeError
95 """
97 if not util.is_string(str_in):
98 raise TypeError("'str_in' not of type 'str'.")
100 i = str_in.find(VG_PREFIX)
101 prefix = VG_PREFIX
103 if i == -1:
104 i = str_in.find(EXT_PREFIX)
105 prefix = EXT_PREFIX
107 uuid_start = i + len(prefix)
108 re_obj = util.match_uuid(str_in[uuid_start:])
110 if i != -1 and re_obj:
111 return prefix + re_obj.group(0) # vgname
113 return None
115LVM_RETRY_ERRORS = [
116 "Incorrect checksum in metadata area header"
117]
120def lvmretry(func):
121 def check_exception(exception):
122 retry = False
123 for error in LVM_RETRY_ERRORS:
124 if error in exception.reason:
125 retry = True
126 return retry
128 def decorated(*args, **kwargs):
129 for i in range(LVM_FAIL_RETRIES): 129 ↛ exitline 129 didn't return from function 'decorated', because the loop on line 129 didn't complete
130 try:
131 return func(*args, **kwargs)
132 except util.CommandException as ce:
133 retry = check_exception(ce)
134 if not retry or (i == LVM_FAIL_RETRIES - 1):
135 raise
137 time.sleep(1)
139 decorated.__name__ = func.__name__
140 return decorated
143def cmd_lvm(cmd, pread_func=util.pread2, *args):
144 """ Construct and run the appropriate lvm command.
146 For PV commands, the full path to the device is required.
148 Input:
149 cmd -- (list) lvm command
150 cmd[0] -- (str) lvm command name
151 cmd[1:] -- (str) lvm command parameters
153 pread_func -- (function) the flavor of util.pread to use
154 to execute the lvm command
155 Default: util.pread2()
157 *args -- extra arguments passed to cmd_lvm will be passed
158 to 'pread_func'
160 Return:
161 stdout -- (str) stdout after running the lvm command.
163 Raise:
164 util.CommandException
165 """
167 if type(cmd) is not list:
168 util.SMlog("CMD_LVM: Argument 'cmd' not of type 'list'")
169 return None
170 if not len(cmd):
171 util.SMlog("CMD_LVM: 'cmd' list is empty")
172 return None
174 lvm_cmd, lvm_args = cmd[0], cmd[1:]
176 if lvm_cmd not in LVM_COMMANDS:
177 util.SMlog("CMD_LVM: '{}' is not a valid lvm command".format(lvm_cmd))
178 return None
180 for arg in lvm_args:
181 if not util.is_string(arg):
182 util.SMlog("CMD_LVM: Not all lvm arguments are of type 'str'")
183 return None
185 with Fairlock("devicemapper"):
186 start_time = time.time()
187 stdout = pread_func([os.path.join(LVM_BIN, lvm_cmd)] + lvm_args, * args)
188 end_time = time.time()
190 if (end_time - start_time > MAX_OPERATION_DURATION):
191 util.SMlog("***** Long LVM call of '%s' took %s" % (lvm_cmd, (end_time - start_time)))
193 return stdout
196class LVInfo:
197 name = ""
198 size = 0
199 active = False
200 open = False
201 hidden = False
202 readonly = False
204 def __init__(self, name):
205 self.name = name
207 def toString(self):
208 return "%s, size=%d, active=%s, open=%s, hidden=%s, ro=%s" % \
209 (self.name, self.size, self.active, self.open, self.hidden, \
210 self.readonly)
213def _checkVG(vgname):
214 try:
215 cmd_lvm([CMD_VGS, "--readonly", vgname])
216 return True
217 except:
218 return False
221def _checkPV(pvname):
222 try:
223 cmd_lvm([CMD_PVS, pvname])
224 return True
225 except:
226 return False
229def _checkLV(path):
230 try:
231 cmd_lvm([CMD_LVDISPLAY, path])
232 return True
233 except:
234 return False
237def _getLVsize(path):
238 try:
239 lines = cmd_lvm([CMD_LVDISPLAY, "-c", path]).split(':')
240 return int(lines[6]) * 512
241 except:
242 raise xs_errors.XenError('VDIUnavailable', \
243 opterr='no such VDI %s' % path)
246def _getVGstats(vgname):
247 try:
248 text = cmd_lvm([CMD_VGS, "--noheadings", "--nosuffix",
249 "--units", "b", vgname],
250 pread_func=util.pread).split()
251 size = int(text[5])
252 freespace = int(text[6])
253 utilisation = size - freespace
254 stats = {}
255 stats['physical_size'] = size
256 stats['physical_utilisation'] = utilisation
257 stats['freespace'] = freespace
258 return stats
259 except util.CommandException as inst:
260 raise xs_errors.XenError('VDILoad', \
261 opterr='rvgstats failed error is %d' % inst.code)
262 except ValueError:
263 raise xs_errors.XenError('VDILoad', opterr='rvgstats failed')
266def _getPVstats(dev):
267 try:
268 text = cmd_lvm([CMD_PVS, "--noheadings", "--nosuffix",
269 "--units", "b", dev],
270 pread_func=util.pread).split()
271 size = int(text[4])
272 freespace = int(text[5])
273 utilisation = size - freespace
274 stats = {}
275 stats['physical_size'] = size
276 stats['physical_utilisation'] = utilisation
277 stats['freespace'] = freespace
278 return stats
279 except util.CommandException as inst:
280 raise xs_errors.XenError('VDILoad', \
281 opterr='pvstats failed error is %d' % inst.code)
282 except ValueError:
283 raise xs_errors.XenError('VDILoad', opterr='rvgstats failed')
286# Retrieves the UUID of the SR that corresponds to the specified Physical
287# Volume (pvname). Each element in prefix_list is checked whether it is a
288# prefix of Volume Groups that correspond to the specified PV. If so, the
289# prefix is stripped from the matched VG name and the remainder is returned
290# (effectively the SR UUID). If no match if found, the empty string is
291# returned.
292# E.g.
293# PV VG Fmt Attr PSize PFree
294# /dev/sda4 VG_XenStorage-some-hex-value lvm2 a- 224.74G 223.73G
295# will return "some-hex-value".
296def _get_sr_uuid(pvname, prefix_list):
297 try:
298 return match_VG(cmd_lvm([CMD_PVS, "--noheadings",
299 "-o", "vg_name", pvname]), prefix_list)
300 except:
301 return ""
304# Retrieves the names of the Physical Volumes which are used by the specified
305# Volume Group
306# e.g.
307# PV VG Fmt Attr PSize PFree
308# /dev/sda4 VG_XenStorage-some-hex-value lvm2 a- 224.74G 223.73G
309# will return "/dev/sda4" when given the argument "VG_XenStorage-some-hex-value".
310def get_pv_for_vg(vgname):
311 try:
312 result = cmd_lvm([CMD_PVS, "--noheadings",
313 '-S', 'vg_name=%s' % vgname, '-o', 'name'])
314 return [x.strip() for x in result.splitlines()]
315 except util.CommandException:
316 return []
319# Tries to match any prefix contained in prefix_list in s. If matched, the
320# remainder string is returned, else the empty string is returned. E.g. if s is
321# "VG_XenStorage-some-hex-value" and prefix_list contains "VG_XenStorage-",
322# "some-hex-value" is returned.
323#
324# TODO Items in prefix_list are expected to be found at the beginning of the
325# target string, though if any of them is found inside it a match will be
326# produced. This could be remedied by making the regular expression more
327# specific.
328def match_VG(s, prefix_list):
329 for val in prefix_list:
330 regex = re.compile(val)
331 if regex.search(s, 0):
332 return s.split(val)[1]
333 return ""
336# Retrieves the devices an SR is composed of. A dictionary is returned, indexed
337# by the SR UUID, where each SR UUID is mapped to a comma-separated list of
338# devices. Exceptions are ignored.
339def scan_srlist(prefix, root):
340 VGs = {}
341 for dev in root.split(','):
342 try:
343 sr_uuid = _get_sr_uuid(dev, [prefix]).strip(' \n')
344 if len(sr_uuid):
345 if sr_uuid in VGs:
346 VGs[sr_uuid] += ",%s" % dev
347 else:
348 VGs[sr_uuid] = dev
349 except Exception as e:
350 util.logException("exception (ignored): %s" % e)
351 continue
352 return VGs
355# Converts an SR list to an XML document with the following structure:
356# <SRlist>
357# <SR>
358# <UUID>...</UUID>
359# <Devlist>...</Devlist>
360# <size>...</size>
361# <!-- If includeMetadata is set to True, the following additional nodes
362# are supplied. -->
363# <name_label>...</name_label>
364# <name_description>...</name_description>
365# <pool_metadata_detected>...</pool_metadata_detected>
366# </SR>
367#
368# <SR>...</SR>
369# </SRlist>
370#
371# Arguments:
372# VGs: a dictionary containing the SR UUID to device list mappings
373# prefix: the prefix that if prefixes the SR UUID the VG is produced
374# includeMetadata (optional): include additional information
375def srlist_toxml(VGs, prefix, includeMetadata=False):
376 dom = xml.dom.minidom.Document()
377 element = dom.createElement("SRlist")
378 dom.appendChild(element)
380 for val in VGs:
381 entry = dom.createElement('SR')
382 element.appendChild(entry)
384 subentry = dom.createElement("UUID")
385 entry.appendChild(subentry)
386 textnode = dom.createTextNode(val)
387 subentry.appendChild(textnode)
389 subentry = dom.createElement("Devlist")
390 entry.appendChild(subentry)
391 textnode = dom.createTextNode(VGs[val])
392 subentry.appendChild(textnode)
394 subentry = dom.createElement("size")
395 entry.appendChild(subentry)
396 size = str(_getVGstats(prefix + val)['physical_size'])
397 textnode = dom.createTextNode(size)
398 subentry.appendChild(textnode)
400 if includeMetadata:
401 metadataVDI = None
403 # add SR name_label
404 mdpath = os.path.join(VG_LOCATION, VG_PREFIX + val)
405 mdpath = os.path.join(mdpath, MDVOLUME_NAME)
406 mgtVolActivated = False
407 try:
408 if not os.path.exists(mdpath):
409 # probe happens out of band with attach so this volume
410 # may not have been activated at this point
411 lvmCache = lvmcache.LVMCache(VG_PREFIX + val)
412 lvmCache.activateNoRefcount(MDVOLUME_NAME)
413 mgtVolActivated = True
415 sr_metadata = \
416 srmetadata.LVMMetadataHandler(mdpath, \
417 False).getMetadata()[0]
418 subentry = dom.createElement("name_label")
419 entry.appendChild(subentry)
420 textnode = dom.createTextNode(sr_metadata[srmetadata.NAME_LABEL_TAG])
421 subentry.appendChild(textnode)
423 # add SR description
424 subentry = dom.createElement("name_description")
425 entry.appendChild(subentry)
426 textnode = dom.createTextNode(sr_metadata[srmetadata.NAME_DESCRIPTION_TAG])
427 subentry.appendChild(textnode)
429 # add metadata VDI UUID
430 metadataVDI = srmetadata.LVMMetadataHandler(mdpath, \
431 False).findMetadataVDI()
432 subentry = dom.createElement("pool_metadata_detected")
433 entry.appendChild(subentry)
434 if metadataVDI is not None:
435 subentry.appendChild(dom.createTextNode("true"))
436 else:
437 subentry.appendChild(dom.createTextNode("false"))
438 finally:
439 if mgtVolActivated:
440 # deactivate only if we activated it
441 lvmCache.deactivateNoRefcount(MDVOLUME_NAME)
443 return dom.toprettyxml()
446def _openExclusive(dev, retry):
447 try:
448 return os.open("%s" % dev, os.O_RDWR | os.O_EXCL)
449 except OSError as ose:
450 opened_by = ''
451 if ose.errno == 16:
452 if retry:
453 util.SMlog('Device %s is busy, settle and one shot retry' %
454 dev)
455 util.pread2(['/usr/sbin/udevadm', 'settle'])
456 return _openExclusive(dev, False)
457 else:
458 util.SMlog('Device %s is busy after retry' % dev)
460 util.SMlog('Opening device %s failed with %d' % (dev, ose.errno))
461 raise xs_errors.XenError(
462 'SRInUse', opterr=('Device %s in use, please check your existing '
463 + 'SRs for an instance of this device') % dev)
466def createVG(root, vgname):
467 systemroot = util.getrootdev()
468 rootdev = root.split(',')[0]
470 # Create PVs for each device
471 for dev in root.split(','):
472 if dev in [systemroot, '%s1' % systemroot, '%s2' % systemroot]:
473 raise xs_errors.XenError('Rootdev', \
474 opterr=('Device %s contains core system files, ' \
475 + 'please use another device') % dev)
476 if not os.path.exists(dev):
477 raise xs_errors.XenError('InvalidDev', \
478 opterr=('Device %s does not exist') % dev)
480 f = _openExclusive(dev, True)
481 os.close(f)
483 # Wipe any fs signature
484 try:
485 util.wipefs(dev)
486 except util.CommandException as inst:
487 raise xs_errors.XenError('WipefsFailure', opterr='device %s' % dev) # from inst
489 if not (dev == rootdev):
490 try:
491 cmd_lvm([CMD_PVCREATE, "-ff", "-y", "--metadatasize", "10M", dev])
492 except util.CommandException as inst:
493 raise xs_errors.XenError('LVMPartCreate',
494 opterr='error is %d' % inst.code)
496 # Create VG on first device
497 try:
498 cmd_lvm([CMD_VGCREATE, "--metadatasize", "10M", vgname, rootdev])
499 except:
500 raise xs_errors.XenError('LVMGroupCreate')
502 # Then add any additional devs into the VG
503 for dev in root.split(',')[1:]:
504 try:
505 cmd_lvm([CMD_VGEXTEND, vgname, dev])
506 except util.CommandException as inst:
507 # One of the PV args failed, delete SR
508 try:
509 cmd_lvm([CMD_VGREMOVE, vgname])
510 except:
511 pass
512 raise xs_errors.XenError('LVMGroupCreate')
514 try:
515 cmd_lvm([CMD_VGCHANGE, "-an", vgname])
516 except util.CommandException as inst:
517 raise xs_errors.XenError('LVMUnMount', opterr='errno is %d' % inst.code)
519 # End block
521def getPVsInVG(vgname):
522 # Get PVs in a specific VG
523 pvs_ret = cmd_lvm([CMD_PVS, '--separator', ' ', '--noheadings', '-o', 'pv_name,vg_name'])
525 # Parse each line to extract PV and VG information
526 # No need to handle exceptions here, return empty list if any error
527 pvs_in_vg = []
528 lines = pvs_ret.strip().split('\n')
529 for line in lines:
530 # To avoid invalid return format
531 parts = line.split()
532 if len(parts) != 2:
533 util.SMlog("Warning: Invalid or empty line in pvs output: %s" % line)
534 continue
535 pv, vg = parts
536 if vg == vgname:
537 pvs_in_vg.append(pv)
539 util.SMlog("PVs in VG %s: %s" % (vgname, pvs_in_vg))
540 return pvs_in_vg
542def removeVG(root, vgname):
543 # Check PVs match VG
544 try:
545 for dev in root.split(','):
546 txt = cmd_lvm([CMD_PVS, dev])
547 if txt.find(vgname) == -1:
548 raise xs_errors.XenError('LVMNoVolume', \
549 opterr='volume is %s' % vgname)
550 except util.CommandException as inst:
551 raise xs_errors.XenError('PVSfailed', \
552 opterr='error is %d' % inst.code)
554 try:
555 # Get PVs in VG before removing the VG
556 devs_in_vg = getPVsInVG(vgname)
557 cmd_lvm([CMD_VGREMOVE, vgname])
559 for dev in devs_in_vg:
560 cmd_lvm([CMD_PVREMOVE, dev])
561 except util.CommandException as inst:
562 raise xs_errors.XenError('LVMDelete', \
563 opterr='errno is %d' % inst.code)
566def resizePV(dev):
567 try:
568 cmd_lvm([CMD_PVRESIZE, dev])
569 except util.CommandException as inst:
570 util.SMlog("Failed to grow the PV, non-fatal")
573def setActiveVG(path, active, config=None):
574 "activate or deactivate VG 'path'"
575 val = "n"
576 if active:
577 val = "y"
578 cmd = [CMD_VGCHANGE, "-a" + val, path]
579 if config:
580 cmd.append("--config")
581 cmd.append(config)
582 cmd_lvm(cmd)
585def checkPVScsiIds(vgname, SCSIid):
586 # Get all the PVs for the specified vgName even if not active
587 cmd = [CMD_PVS, '-a', '--select', f'vgname={vgname}', '--no-headings']
588 text = cmd_lvm(cmd)
589 pv_paths = [x.split()[0] for x in text.splitlines()]
590 for pv_path in pv_paths:
591 pv_scsi_id = scsiutil.getSCSIid(pv_path)
592 if pv_scsi_id != SCSIid:
593 raise xs_errors.XenError(
594 'PVMultiIDs',
595 opterr=f'Found PVs {",".join(pv_paths)} and unexpected '
596 f'SCSI ID {pv_scsi_id}, expected {SCSIid}')
598@lvmretry
599def create(name, size, vgname, tag=None, size_in_percentage=None):
600 if size_in_percentage:
601 cmd = [CMD_LVCREATE, "-n", name, "-l", size_in_percentage, vgname]
602 else:
603 size_mb = size // (1024 * 1024)
604 cmd = [CMD_LVCREATE, "-n", name, "-L", str(size_mb), vgname]
605 if tag:
606 cmd.extend(["--addtag", tag])
608 cmd.extend(['-W', 'n'])
609 cmd_lvm(cmd)
612def remove(path, config_param=None):
613 # see deactivateNoRefcount()
614 for i in range(LVM_FAIL_RETRIES): 614 ↛ 622line 614 didn't jump to line 622, because the loop on line 614 didn't complete
615 try:
616 _remove(path, config_param)
617 break
618 except util.CommandException as e:
619 if i >= LVM_FAIL_RETRIES - 1:
620 raise
621 util.SMlog("*** lvremove failed on attempt #%d" % i)
622 _lvmBugCleanup(path)
625@lvmretry
626def _remove(path, config_param=None):
627 CONFIG_TAG = "--config"
628 cmd = [CMD_LVREMOVE, "-f", path]
629 if config_param:
630 cmd.extend([CONFIG_TAG, "devices{" + config_param + "}"])
631 ret = cmd_lvm(cmd)
634@lvmretry
635def rename(path, newName):
636 cmd_lvm([CMD_LVRENAME, path, newName], pread_func=util.pread)
639@lvmretry
640def setReadonly(path, readonly):
641 val = "r"
642 if not readonly:
643 val += "w"
644 ret = cmd_lvm([CMD_LVCHANGE, path, "-p", val], pread_func=util.pread)
647def exists(path):
648 (rc, stdout, stderr) = cmd_lvm([CMD_LVS, "--noheadings", path], pread_func=util.doexec)
649 return rc == 0
651def listLv(path=None):
652 rc, stdout, stderr = cmd_lvm([CMD_LVS, "--noheadings", path or ""], pread_func=util.doexec)
653 if rc == 0: 653 ↛ 656line 653 didn't jump to line 656, because the condition on line 653 was never false
654 return stdout
656 raise xs_errors.XenError('VolNotFound')
658@lvmretry
659def setSize(path, size, confirm):
660 sizeMB = size // (1024 * 1024)
661 if confirm:
662 cmd_lvm([CMD_LVRESIZE, "-L", str(sizeMB), path], util.pread3, "y\n")
663 else:
664 cmd_lvm([CMD_LVRESIZE, "-L", str(sizeMB), path], pread_func=util.pread)
667@lvmretry
668def setHidden(path, hidden=True):
669 opt = "--addtag"
670 if not hidden:
671 opt = "--deltag"
672 cmd_lvm([CMD_LVCHANGE, opt, LV_TAG_HIDDEN, path])
675@lvmretry
676def _activate(path):
677 cmd = [CMD_LVCHANGE, "-ay", path]
678 cmd_lvm(cmd)
679 if not _checkActive(path):
680 raise util.CommandException(-1, str(cmd), "LV not activated")
683def activateNoRefcount(path, refresh):
684 _activate(path)
685 if refresh: 685 ↛ 687line 685 didn't jump to line 687, because the condition on line 685 was never true
686 # Override slave mode lvm.conf for this command
687 os.environ['LVM_SYSTEM_DIR'] = MASTER_LVM_CONF
688 text = cmd_lvm([CMD_LVCHANGE, "--refresh", path])
689 mapperDevice = path[5:].replace("-", "--").replace("/", "-")
690 cmd = [CMD_DMSETUP, "table", mapperDevice]
691 with Fairlock("devicemapper"):
692 ret = util.pread(cmd)
693 util.SMlog("DM table for %s: %s" % (path, ret.strip()))
694 # Restore slave mode lvm.conf
695 os.environ['LVM_SYSTEM_DIR'] = DEF_LVM_CONF
698def deactivateNoRefcount(path):
699 # LVM has a bug where if an "lvs" command happens to run at the same time
700 # as "lvchange -an", it might hold the device in use and cause "lvchange
701 # -an" to fail. Thus, we need to retry if "lvchange -an" fails. Worse yet,
702 # the race could lead to "lvchange -an" starting to deactivate (removing
703 # the symlink), failing to "dmsetup remove" the device, and still returning
704 # success. Thus, we need to check for the device mapper file existence if
705 # "lvchange -an" returns success.
706 for i in range(LVM_FAIL_RETRIES): 706 ↛ 714line 706 didn't jump to line 714, because the loop on line 706 didn't complete
707 try:
708 _deactivate(path)
709 break
710 except util.CommandException:
711 if i >= LVM_FAIL_RETRIES - 1:
712 raise
713 util.SMlog("*** lvchange -an failed on attempt #%d" % i)
714 _lvmBugCleanup(path)
717@lvmretry
718def _deactivate(path):
719 text = cmd_lvm([CMD_LVCHANGE, "-an", path])
722def _checkActive(path):
723 if util.pathexists(path):
724 return True
726 util.SMlog("_checkActive: %s does not exist!" % path)
727 symlinkExists = os.path.lexists(path)
728 util.SMlog("_checkActive: symlink exists: %s" % symlinkExists)
730 util.SMlog(f"LVM says {listLv(path=path)}")
732 mapperDeviceExists = False
733 mapperDevice = path[5:].replace("-", "--").replace("/", "-")
734 cmd = [CMD_DMSETUP, "status", mapperDevice]
735 try:
736 with Fairlock("devicemapper"):
737 ret = util.pread2(cmd)
738 mapperDeviceExists = True
739 util.SMlog("_checkActive: %s: %s" % (mapperDevice, ret))
740 except util.CommandException:
741 util.SMlog("_checkActive: device %s does not exist" % mapperDevice)
743 mapperPath = "/dev/mapper/" + mapperDevice
744 mapperPathExists = util.pathexists(mapperPath)
745 util.SMlog("_checkActive: path %s exists: %s" % \
746 (mapperPath, mapperPathExists))
748 if mapperDeviceExists and mapperPathExists and not symlinkExists: 748 ↛ 750line 748 didn't jump to line 750, because the condition on line 748 was never true
749 # we can fix this situation manually here
750 try:
751 util.SMlog("_checkActive: attempt to create the symlink manually.")
752 os.symlink(mapperPath, path)
753 except OSError as e:
754 util.SMlog("ERROR: failed to symlink!")
755 if e.errno != errno.EEXIST:
756 raise
757 if util.pathexists(path):
758 util.SMlog("_checkActive: created the symlink manually")
759 return True
761 return False
764def _lvmBugCleanup(path):
765 # the device should not exist at this point. If it does, this was an LVM
766 # bug, and we manually clean up after LVM here
767 mapperDevice = path[5:].replace("-", "--").replace("/", "-")
768 mapperPath = "/dev/mapper/" + mapperDevice
770 nodeExists = False
771 cmd_st = [CMD_DMSETUP, "status", mapperDevice]
772 cmd_rm = [CMD_DMSETUP, "remove", mapperDevice]
773 cmd_rf = [CMD_DMSETUP, "remove", mapperDevice, "--force"]
775 try:
776 with Fairlock("devicemapper"):
777 util.pread(cmd_st, expect_rc=1)
778 except util.CommandException as e:
779 if e.code == 0: 779 ↛ 782line 779 didn't jump to line 782, because the condition on line 779 was never false
780 nodeExists = True
782 if not util.pathexists(mapperPath) and not nodeExists:
783 return
785 util.SMlog("_lvmBugCleanup: seeing dm file %s" % mapperPath)
787 # destroy the dm device
788 if nodeExists: 788 ↛ 815line 788 didn't jump to line 815, because the condition on line 788 was never false
789 util.SMlog("_lvmBugCleanup: removing dm device %s" % mapperDevice)
790 for i in range(LVM_FAIL_RETRIES): 790 ↛ 815line 790 didn't jump to line 815, because the loop on line 790 didn't complete
791 try:
792 with Fairlock("devicemapper"):
793 util.pread2(cmd_rm)
794 break
795 except util.CommandException as e:
796 if i < LVM_FAIL_RETRIES - 1:
797 util.SMlog("Failed on try %d, retrying" % i)
798 try:
799 with Fairlock("devicemapper"):
800 util.pread(cmd_st, expect_rc=1)
801 util.SMlog("_lvmBugCleanup: dm device {}"
802 " removed".format(mapperDevice)
803 )
804 break
805 except:
806 cmd_rm = cmd_rf
807 time.sleep(1)
808 else:
809 # make sure the symlink is still there for consistency
810 if not os.path.lexists(path): 810 ↛ 813line 810 didn't jump to line 813, because the condition on line 810 was never false
811 os.symlink(mapperPath, path)
812 util.SMlog("_lvmBugCleanup: restored symlink %s" % path)
813 raise e
815 if util.pathexists(mapperPath):
816 os.unlink(mapperPath)
817 util.SMlog("_lvmBugCleanup: deleted devmapper file %s" % mapperPath)
819 # delete the symlink
820 if os.path.lexists(path):
821 os.unlink(path)
822 util.SMlog("_lvmBugCleanup: deleted symlink %s" % path)
825# mdpath is of format /dev/VG-SR-UUID/MGT
826# or in other words /VG_LOCATION/VG_PREFIXSR-UUID/MDVOLUME_NAME
827def ensurePathExists(mdpath):
828 if not os.path.exists(mdpath):
829 vgname = mdpath.split('/')[2]
830 lvmCache = lvmcache.LVMCache(vgname)
831 lvmCache.activateNoRefcount(MDVOLUME_NAME)
834def removeDevMapperEntry(path, strict=True):
835 try:
836 # remove devmapper entry using dmsetup
837 cmd = [CMD_DMSETUP, "remove", path]
838 cmd_lvm(cmd)
839 return True
840 except Exception as e:
841 if not strict:
842 cmd = [CMD_DMSETUP, "status", path]
843 try:
844 with Fairlock("devicemapper"):
845 util.pread(cmd, expect_rc=1)
846 return True
847 except:
848 pass # Continuining will fail and log the right way
849 ret = util.pread2(["lsof", path])
850 util.SMlog("removeDevMapperEntry: dmsetup remove failed for file %s " \
851 "with error %s, and lsof ret is %s." % (path, str(e), ret))
852 return False