Coverage for libs/sm/lvutil.py: 51%
541 statements
« prev ^ index » next coverage.py v7.3.2, created at 2026-07-10 07:59 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2026-07-10 07:59 +0000
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
25from fairlock import Fairlock
26from sm.core import scsiutil
27from sm.core import util
28from sm.core import xs_errors
29import xml.dom.minidom
30from sm.lvhdutil import VG_LOCATION, VG_PREFIX
31from sm.constants import EXT_PREFIX
32from sm import lvmcache
33from sm import 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)
482 try:
483 # Overwrite the disk header, try direct IO first
484 cmd = [util.CMD_DD, "if=/dev/zero", "of=%s" % dev, "bs=1M",
485 "count=10", "oflag=direct"]
486 util.pread2(cmd)
487 except util.CommandException as inst:
488 if inst.code == errno.EPERM:
489 try:
490 # Overwrite the disk header, try normal IO
491 cmd = [util.CMD_DD, "if=/dev/zero", "of=%s" % dev,
492 "bs=1M", "count=10"]
493 util.pread2(cmd)
494 except util.CommandException as inst:
495 raise xs_errors.XenError('LVMWrite', \
496 opterr='device %s' % dev)
497 else:
498 raise xs_errors.XenError('LVMWrite', \
499 opterr='device %s' % dev)
501 if not (dev == rootdev):
502 try:
503 cmd_lvm([CMD_PVCREATE, "-ff", "-y", "--metadatasize", "10M", dev])
504 except util.CommandException as inst:
505 raise xs_errors.XenError('LVMPartCreate',
506 opterr='error is %d' % inst.code)
508 # Create VG on first device
509 try:
510 cmd_lvm([CMD_VGCREATE, "--metadatasize", "10M", vgname, rootdev])
511 except:
512 raise xs_errors.XenError('LVMGroupCreate')
514 # Then add any additional devs into the VG
515 for dev in root.split(',')[1:]:
516 try:
517 cmd_lvm([CMD_VGEXTEND, vgname, dev])
518 except util.CommandException as inst:
519 # One of the PV args failed, delete SR
520 try:
521 cmd_lvm([CMD_VGREMOVE, vgname])
522 except:
523 pass
524 raise xs_errors.XenError('LVMGroupCreate')
526 try:
527 cmd_lvm([CMD_VGCHANGE, "-an", vgname])
528 except util.CommandException as inst:
529 raise xs_errors.XenError('LVMUnMount', opterr='errno is %d' % inst.code)
531 # End block
533def getPVsInVG(vgname):
534 # Get PVs in a specific VG, returns PV uuids
535 pvs_ret = cmd_lvm([CMD_PVS, '--noheadings', '-o', 'pv_uuid,vg_name'])
537 # Parse each line to extract PV and VG information
538 # No need to handle exceptions here, return empty list if any error
539 pvs_in_vg = []
540 lines = pvs_ret.strip().split('\n')
541 for line in lines:
542 # To avoid invalid return format
543 parts = line.split()
544 if len(parts) != 2:
545 util.SMlog("Warning: Invalid or empty line in pvs output: %s" % line)
546 continue
547 pv_uuid, vg = parts
548 if vg == vgname:
549 pvs_in_vg.append(pv_uuid)
551 util.SMlog("PVs in VG %s: %s" % (vgname, pvs_in_vg))
552 return pvs_in_vg
554def getPVsWithUUID(pv_uuid):
555 # Get PVs with a specific UUID, returns PV device names
556 pvs_ret = cmd_lvm([CMD_PVS, '--noheadings', '-o', 'pv_name,pv_uuid'])
558 # No need to handle exceptions here, return empty list if any error
559 pvs_in_vg = []
560 lines = pvs_ret.strip().split('\n')
561 for line in lines:
562 # To avoid invalid return format
563 parts = line.split()
564 if len(parts) != 2:
565 util.SMlog("Warning: Invalid or empty line in pvs output: %s" % line)
566 continue
567 pv, uuid = parts
568 if uuid == pv_uuid:
569 pvs_in_vg.append(pv)
571 util.SMlog("PVs with uuid %s: %s" % (pv_uuid, pvs_in_vg))
572 return pvs_in_vg
574def removeVG(root, vgname):
575 # Check PVs match VG
576 try:
577 for dev in root.split(','):
578 txt = cmd_lvm([CMD_PVS, dev])
579 if txt.find(vgname) == -1:
580 raise xs_errors.XenError('LVMNoVolume', \
581 opterr='volume is %s' % vgname)
582 except util.CommandException as inst:
583 raise xs_errors.XenError('PVSfailed', \
584 opterr='error is %d' % inst.code)
586 try:
587 # Get PVs(uuid) in VG before removing the VG
588 pv_uuids_in_vg = getPVsInVG(vgname)
589 cmd_lvm([CMD_VGREMOVE, vgname])
591 # For multipath, after vgremove, LVM reassign the underlying device node represents the PV.
592 # To remove the correct PV, use uuid to get the real device that represents the PV.
593 for pv_uuid in pv_uuids_in_vg:
594 pvs = getPVsWithUUID(pv_uuid)
595 for pv in pvs:
596 cmd_lvm([CMD_PVREMOVE, pv])
597 except util.CommandException as inst:
598 raise xs_errors.XenError('LVMDelete', \
599 opterr='errno is %d' % inst.code)
602def resizePV(dev):
603 try:
604 cmd_lvm([CMD_PVRESIZE, dev])
605 except util.CommandException as inst:
606 util.SMlog("Failed to grow the PV, non-fatal")
609def setActiveVG(path, active):
610 "activate or deactivate VG 'path'"
611 val = "n"
612 if active:
613 val = "y"
614 text = cmd_lvm([CMD_VGCHANGE, "-a" + val, path])
617def checkPVScsiIds(vgname, SCSIid):
618 # Get all the PVs for the specified vgName even if not active
619 cmd = [CMD_PVS, '-a', '--select', f'vgname={vgname}', '--no-headings']
620 text = cmd_lvm(cmd)
621 pv_paths = [x.split()[0] for x in text.splitlines()]
622 for pv_path in pv_paths:
623 pv_scsi_id = scsiutil.getSCSIid(pv_path)
624 if pv_scsi_id != SCSIid:
625 raise xs_errors.XenError(
626 'PVMultiIDs',
627 opterr=f'Found PVs {",".join(pv_paths)} and unexpected '
628 f'SCSI ID {pv_scsi_id}, expected {SCSIid}')
630@lvmretry
631def create(name, size, vgname, tag=None, size_in_percentage=None):
632 if size_in_percentage:
633 cmd = [CMD_LVCREATE, "-n", name, "-l", size_in_percentage, vgname]
634 else:
635 size_mb = size // (1024 * 1024)
636 cmd = [CMD_LVCREATE, "-n", name, "-L", str(size_mb), vgname]
637 if tag:
638 cmd.extend(["--addtag", tag])
640 cmd.extend(['-W', 'y', '--yes'])
641 cmd_lvm(cmd)
644def remove(path, config_param=None):
645 # see deactivateNoRefcount()
646 for i in range(LVM_FAIL_RETRIES): 646 ↛ 654line 646 didn't jump to line 654, because the loop on line 646 didn't complete
647 try:
648 _remove(path, config_param)
649 break
650 except util.CommandException as e:
651 if i >= LVM_FAIL_RETRIES - 1:
652 raise
653 util.SMlog("*** lvremove failed on attempt #%d" % i)
654 _lvmBugCleanup(path)
657@lvmretry
658def _remove(path, config_param=None):
659 CONFIG_TAG = "--config"
660 cmd = [CMD_LVREMOVE, "-f", path]
661 if config_param:
662 cmd.extend([CONFIG_TAG, "devices{" + config_param + "}"])
663 ret = cmd_lvm(cmd)
666@lvmretry
667def rename(path, newName):
668 cmd_lvm([CMD_LVRENAME, path, newName], pread_func=util.pread)
671@lvmretry
672def setReadonly(path, readonly):
673 val = "r"
674 if not readonly:
675 val += "w"
676 ret = cmd_lvm([CMD_LVCHANGE, path, "-p", val], pread_func=util.pread)
679def exists(path):
680 (rc, stdout, stderr) = cmd_lvm([CMD_LVS, "--noheadings", path], pread_func=util.doexec)
681 return rc == 0
684@lvmretry
685def setSize(path, size, confirm):
686 sizeMB = size // (1024 * 1024)
687 if confirm:
688 cmd_lvm([CMD_LVRESIZE, "-L", str(sizeMB), path], util.pread3, "y\n")
689 else:
690 cmd_lvm([CMD_LVRESIZE, "-L", str(sizeMB), path], pread_func=util.pread)
693@lvmretry
694def setHidden(path, hidden=True):
695 opt = "--addtag"
696 if not hidden:
697 opt = "--deltag"
698 cmd_lvm([CMD_LVCHANGE, opt, LV_TAG_HIDDEN, path])
701@lvmretry
702def _activate(path):
703 cmd = [CMD_LVCHANGE, "-ay", path]
704 cmd_lvm(cmd)
705 if not _checkActive(path):
706 raise util.CommandException(-1, str(cmd), "LV not activated")
709def activateNoRefcount(path, refresh):
710 _activate(path)
711 if refresh: 711 ↛ 713line 711 didn't jump to line 713, because the condition on line 711 was never true
712 # Override slave mode lvm.conf for this command
713 os.environ['LVM_SYSTEM_DIR'] = MASTER_LVM_CONF
714 text = cmd_lvm([CMD_LVCHANGE, "--refresh", path])
715 mapperDevice = path[5:].replace("-", "--").replace("/", "-")
716 cmd = [CMD_DMSETUP, "table", mapperDevice]
717 with Fairlock("devicemapper"):
718 ret = util.pread(cmd)
719 util.SMlog("DM table for %s: %s" % (path, ret.strip()))
720 # Restore slave mode lvm.conf
721 os.environ['LVM_SYSTEM_DIR'] = DEF_LVM_CONF
724def deactivateNoRefcount(path):
725 # LVM has a bug where if an "lvs" command happens to run at the same time
726 # as "lvchange -an", it might hold the device in use and cause "lvchange
727 # -an" to fail. Thus, we need to retry if "lvchange -an" fails. Worse yet,
728 # the race could lead to "lvchange -an" starting to deactivate (removing
729 # the symlink), failing to "dmsetup remove" the device, and still returning
730 # success. Thus, we need to check for the device mapper file existence if
731 # "lvchange -an" returns success.
732 for i in range(LVM_FAIL_RETRIES): 732 ↛ 740line 732 didn't jump to line 740, because the loop on line 732 didn't complete
733 try:
734 _deactivate(path)
735 break
736 except util.CommandException:
737 if i >= LVM_FAIL_RETRIES - 1:
738 raise
739 util.SMlog("*** lvchange -an failed on attempt #%d" % i)
740 _lvmBugCleanup(path)
743@lvmretry
744def _deactivate(path):
745 text = cmd_lvm([CMD_LVCHANGE, "-an", path])
748def _checkActive(path):
749 if util.pathexists(path):
750 return True
752 util.SMlog("_checkActive: %s does not exist!" % path)
753 symlinkExists = os.path.lexists(path)
754 util.SMlog("_checkActive: symlink exists: %s" % symlinkExists)
756 mapperDeviceExists = False
757 mapperDevice = path[5:].replace("-", "--").replace("/", "-")
758 cmd = [CMD_DMSETUP, "status", mapperDevice]
759 try:
760 with Fairlock("devicemapper"):
761 ret = util.pread2(cmd)
762 mapperDeviceExists = True
763 util.SMlog("_checkActive: %s: %s" % (mapperDevice, ret))
764 except util.CommandException:
765 util.SMlog("_checkActive: device %s does not exist" % mapperDevice)
767 mapperPath = "/dev/mapper/" + mapperDevice
768 mapperPathExists = util.pathexists(mapperPath)
769 util.SMlog("_checkActive: path %s exists: %s" % \
770 (mapperPath, mapperPathExists))
772 if mapperDeviceExists and mapperPathExists and not symlinkExists: 772 ↛ 774line 772 didn't jump to line 774, because the condition on line 772 was never true
773 # we can fix this situation manually here
774 try:
775 util.SMlog("_checkActive: attempt to create the symlink manually.")
776 os.symlink(mapperPath, path)
777 except OSError as e:
778 util.SMlog("ERROR: failed to symlink!")
779 if e.errno != errno.EEXIST:
780 raise
781 if util.pathexists(path):
782 util.SMlog("_checkActive: created the symlink manually")
783 return True
785 return False
788def _lvmBugCleanup(path):
789 # the device should not exist at this point. If it does, this was an LVM
790 # bug, and we manually clean up after LVM here
791 mapperDevice = path[5:].replace("-", "--").replace("/", "-")
792 mapperPath = "/dev/mapper/" + mapperDevice
794 nodeExists = False
795 cmd_st = [CMD_DMSETUP, "status", mapperDevice]
796 cmd_rm = [CMD_DMSETUP, "remove", mapperDevice]
797 cmd_rf = [CMD_DMSETUP, "remove", mapperDevice, "--force"]
799 try:
800 with Fairlock("devicemapper"):
801 util.pread(cmd_st, expect_rc=1)
802 except util.CommandException as e:
803 if e.code == 0: 803 ↛ 806line 803 didn't jump to line 806, because the condition on line 803 was never false
804 nodeExists = True
806 if not util.pathexists(mapperPath) and not nodeExists:
807 return
809 util.SMlog("_lvmBugCleanup: seeing dm file %s" % mapperPath)
811 # destroy the dm device
812 if nodeExists: 812 ↛ 839line 812 didn't jump to line 839, because the condition on line 812 was never false
813 util.SMlog("_lvmBugCleanup: removing dm device %s" % mapperDevice)
814 for i in range(LVM_FAIL_RETRIES): 814 ↛ 839line 814 didn't jump to line 839, because the loop on line 814 didn't complete
815 try:
816 with Fairlock("devicemapper"):
817 util.pread2(cmd_rm)
818 break
819 except util.CommandException as e:
820 if i < LVM_FAIL_RETRIES - 1:
821 util.SMlog("Failed on try %d, retrying" % i)
822 try:
823 with Fairlock("devicemapper"):
824 util.pread(cmd_st, expect_rc=1)
825 util.SMlog("_lvmBugCleanup: dm device {}"
826 " removed".format(mapperDevice)
827 )
828 break
829 except:
830 cmd_rm = cmd_rf
831 time.sleep(1)
832 else:
833 # make sure the symlink is still there for consistency
834 if not os.path.lexists(path): 834 ↛ 837line 834 didn't jump to line 837, because the condition on line 834 was never false
835 os.symlink(mapperPath, path)
836 util.SMlog("_lvmBugCleanup: restored symlink %s" % path)
837 raise e
839 if util.pathexists(mapperPath):
840 os.unlink(mapperPath)
841 util.SMlog("_lvmBugCleanup: deleted devmapper file %s" % mapperPath)
843 # delete the symlink
844 if os.path.lexists(path):
845 os.unlink(path)
846 util.SMlog("_lvmBugCleanup: deleted symlink %s" % path)
849# mdpath is of format /dev/VG-SR-UUID/MGT
850# or in other words /VG_LOCATION/VG_PREFIXSR-UUID/MDVOLUME_NAME
851def ensurePathExists(mdpath):
852 if not os.path.exists(mdpath):
853 vgname = mdpath.split('/')[2]
854 lvmCache = lvmcache.LVMCache(vgname)
855 lvmCache.activateNoRefcount(MDVOLUME_NAME)
858def removeDevMapperEntry(path, strict=True):
859 try:
860 # remove devmapper entry using dmsetup
861 cmd = [CMD_DMSETUP, "remove", path]
862 cmd_lvm(cmd)
863 return True
864 except Exception as e:
865 if not strict:
866 cmd = [CMD_DMSETUP, "status", path]
867 try:
868 with Fairlock("devicemapper"):
869 util.pread(cmd, expect_rc=1)
870 return True
871 except:
872 pass # Continuining will fail and log the right way
873 ret = util.pread2(["lsof", path])
874 util.SMlog("removeDevMapperEntry: dmsetup remove failed for file %s " \
875 "with error %s, and lsof ret is %s." % (path, str(e), ret))
876 return False