Coverage for libs/sm/core/mpath_dmp.py: 90%
150 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
16from sm.core import util
17from sm.core.util import CommandException
18from sm.core import xs_errors
19from sm.core import iscsi
20from sm.core import mpath_cli
21import os
22import glob
23import time
24from sm.core import scsiutil
25from sm.core import wwid_conf
26import errno
27from fairlock import Fairlock
29DMPBIN = "/sbin/multipath"
30DEVMAPPERPATH = "/dev/mapper"
31DEVBYIDPATH = "/dev/disk/by-id"
32DEVBYSCSIPATH = "/dev/disk/by-scsibus"
33SYSFS_PATH = '/sys/class/scsi_host'
34MP_INUSEDIR = "/dev/disk/mpInuse"
37def _is_mpath_daemon_running():
38 cmd = ["/usr/bin/systemctl", "is-active", "multipathd"]
39 rc, stdout, stderr = util.doexec(cmd)
40 return rc == 0
43def activate_MPdev(sid, dst):
44 try:
45 os.mkdir(MP_INUSEDIR)
46 except OSError as exc:
47 if exc.errno == errno.EEXIST:
48 pass
49 else:
50 raise
51 path = os.path.join(MP_INUSEDIR, sid)
52 cmd = ['ln', '-sf', dst, path]
53 util.pread2(cmd)
54 util.set_scheduler(os.path.realpath(path))
57def deactivate_MPdev(sid):
58 path = os.path.join(MP_INUSEDIR, sid)
59 if os.path.exists(path): 59 ↛ 60line 59 didn't jump to line 60, because the condition on line 59 was never true
60 os.unlink(path)
62# The explicit_unmap argument used to default to False, but since every caller
63# either needs it to be True or explicitly specifies, changed it to True by
64# default.
65def reset(sid, explicit_unmap=True):
66 util.SMlog("Resetting LUN %s" % sid)
67 _resetDMP(sid, explicit_unmap)
70def _resetDMP(sid, explicit_unmap=False):
71# If mpath has been turned on since the sr/vdi was attached, we
72# might be trying to unmap it before the daemon has been started
73# This is unnecessary (and will fail) so just return.
74 deactivate_MPdev(sid)
75 if not _is_mpath_daemon_running(): 75 ↛ 76line 75 didn't jump to line 76, because the condition on line 75 was never true
76 util.SMlog("Warning: Trying to unmap mpath device when multipathd not running")
77 return
79 if explicit_unmap: 79 ↛ 99line 79 didn't jump to line 99, because the condition on line 79 was never false
80 util.SMlog("_resetDMP, explicit unmap")
81 with Fairlock("devicemapper"):
82 util.SMlog(f"removing {sid} from multipath and wwids")
83 util.retry(
84 lambda: util.pread2(['/usr/sbin/multipath', '-w', sid]),
85 maxretry=3, period=4)
87 try:
88 util.retry(
89 lambda: util.pread2(['/usr/sbin/multipath', '-f', sid]),
90 maxretry=3, period=4)
91 except CommandException as ce:
92 # If the device is already gone then that is what we want
93 if "device not found" not in ce.reason:
94 raise
96 util.retry(lambda: util.pread2(['/usr/sbin/multipath', '-W']),
97 maxretry=3, period=4)
98 else:
99 mpath_cli.ensure_map_gone(sid)
101 path = "/dev/mapper/%s" % sid
103 if not util.wait_for_nopath(path, 10): 103 ↛ 104line 103 didn't jump to line 104, because the condition on line 103 was never true
104 util.SMlog("MPATH: WARNING - path did not disappear [%s]" % path)
105 else:
106 util.SMlog("MPATH: path disappeared [%s]" % path)
109def refresh(sid, npaths=0):
110 """
111 Refresh the multipath status
113 The npaths argument is defaulted to 0 because it is never actually used
114 (_refresh_DMP() doesn't use it either) and this makes the call compatible
115 with sm-core-libs, which doesn't supply the argument.
116 """
118 util.SMlog("Refreshing LUN %s" % sid)
119 if len(sid):
120 path = DEVBYIDPATH + "/scsi-" + sid
121 if not os.path.exists(path):
122 scsiutil.rescan(scsiutil._genHostList(""))
123 if not util.wait_for_path(path, 60):
124 raise xs_errors.XenError('MultipathDeviceNotAppeared', path)
125 _refresh_DMP(sid, npaths)
126 else:
127 raise xs_errors.XenError('MultipathDeviceNoScsiid')
130def _is_valid_multipath_device(sid):
132 # Check if device is already multipathed
133 with Fairlock("devicemapper"):
134 (ret, stdout, stderr) = util.doexec(['/usr/sbin/multipath', '-l', sid])
135 if not stdout + stderr:
136 with Fairlock("devicemapper"):
137 (ret, stdout, stderr) = util.doexec(['/usr/sbin/multipath', '-ll', sid])
138 if not stdout + stderr:
139 with Fairlock("devicemapper"):
140 (ret, stdout, stderr) = util.doexec(['/usr/sbin/multipath', '-a', sid])
141 if ret < 0: 141 ↛ 142line 141 didn't jump to line 142, because the condition on line 141 was never true
142 util.SMlog("Failed to add {}: wwid could be explicitly "
143 "blacklisted\n Continue with multipath disabled for "
144 "this SR".format(sid))
145 return False
147 by_scsid_path = "/dev/disk/by-scsid/" + sid
148 if os.path.exists(by_scsid_path):
149 devs = os.listdir(by_scsid_path)
150 else:
151 util.SMlog("Device {} is not ready yet, skipping multipath check".format(by_scsid_path))
152 return False
153 ret = 1
154 # Some paths might be down, check all associated devices
155 for dev in devs:
156 devpath = os.path.join(by_scsid_path, dev)
157 real_path = os.path.realpath(devpath)
158 with Fairlock("devicemapper"):
159 (ret, stdout, stderr) = util.doexec(['/usr/sbin/multipath', '-c', real_path])
160 if ret == 0:
161 break
163 if ret == 1:
164 # This is very fragile but it is not a good sign to fail without
165 # any output. At least until multipath 0.4.9, for example,
166 # multipath -c fails without any log if it is able to retrieve the
167 # wwid of the device.
168 # In this case it is better to fail immediately.
169 if not stdout + stderr:
170 # Attempt to cleanup wwids file before raising
171 try:
172 with Fairlock("devicemapper"): 172 ↛ 176line 172 didn't jump to line 176
173 (ret, stdout, stderr) = util.doexec(['/usr/sbin/multipath', '-w', sid])
174 except OSError:
175 util.SMlog("Error removing {} from wwids file".format(sid))
176 raise xs_errors.XenError('MultipathGenericFailure',
177 '"multipath -c" failed without any'
178 ' output on {}'.format(real_path))
179 util.SMlog("When dealing with {} multipath status returned:\n "
180 "{}{} Continue with multipath disabled for this SR"
181 .format(sid, stdout, stderr))
182 return False
183 return True
186def _refresh_DMP(sid, npaths):
187 if not _is_valid_multipath_device(sid): 187 ↛ 188line 187 didn't jump to line 188, because the condition on line 187 was never true
188 return
189 path = os.path.join(DEVMAPPERPATH, sid)
190 # If the mapper path doesn't exist force a reload in multipath
191 if not os.path.exists(path):
192 with Fairlock("devicemapper"):
193 util.retry(lambda: util.pread2(['/usr/sbin/multipath', '-r', sid]), maxretry=3, period=4)
194 util.wait_for_path(path, 30)
195 if not os.path.exists(path):
196 raise xs_errors.XenError('MultipathMapperPathMissing',
197 'Device mapper path {} not found'.format(
198 path))
199 lvm_path = "/dev/disk/by-scsid/" + sid + "/mapper"
200 util.wait_for_path(lvm_path, 30)
201 activate_MPdev(sid, path)
204def activate():
205 util.SMlog("MPATH: multipath activate called")
207 # If we've got no active sessions, and the deamon is already running,
208 # we're ok to restart the daemon
209 if iscsi.is_iscsi_daemon_running():
210 if not iscsi._checkAnyTGT():
211 iscsi.restart_daemon()
213 if not _is_mpath_daemon_running():
214 util.SMlog("Warning: multipath daemon not running. Starting daemon!")
215 cmd = ["/usr/bin/systemctl", "start", "multipathd.service"]
216 util.pread2(cmd)
218 for i in range(0, 120):
219 if mpath_cli.is_working():
220 util.SMlog("MPATH: dm-multipath activated.")
221 return
222 time.sleep(1)
224 util.SMlog("Failed to communicate with the multipath daemon!")
225 raise xs_errors.XenError('MultipathdCommsFailure')
228def deactivate():
229 util.SMlog("MPATH: multipath deactivate called")
231 if _is_mpath_daemon_running(): 231 ↛ 238line 231 didn't jump to line 238, because the condition on line 231 was never false
232 # Flush the multipath nodes
233 for sid in mpath_cli.list_maps():
234 reset(sid, True)
236 # Check the ISCSI daemon doesn't have any active sessions, if not,
237 # restart in the new mode
238 if iscsi.is_iscsi_daemon_running() and not iscsi._checkAnyTGT():
239 iscsi.restart_daemon()
241 util.kickpipe_mpathcount()
242 util.SMlog("MPATH: multipath deactivated.")
245def path(SCSIid):
246 if _is_valid_multipath_device(SCSIid) and _is_mpath_daemon_running():
247 path = os.path.join(MP_INUSEDIR, SCSIid)
248 return path
249 else:
250 return DEVBYIDPATH + "/scsi-" + SCSIid