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

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 

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 

28 

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" 

35 

36 

37def _is_mpath_daemon_running(): 

38 cmd = ["/usr/bin/systemctl", "is-active", "multipathd"] 

39 rc, stdout, stderr = util.doexec(cmd) 

40 return rc == 0 

41 

42 

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)) 

55 

56 

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) 

61 

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) 

68 

69 

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 

78 

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) 

86 

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 

95 

96 util.retry(lambda: util.pread2(['/usr/sbin/multipath', '-W']), 

97 maxretry=3, period=4) 

98 else: 

99 mpath_cli.ensure_map_gone(sid) 

100 

101 path = "/dev/mapper/%s" % sid 

102 

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) 

107 

108 

109def refresh(sid, npaths=0): 

110 """ 

111 Refresh the multipath status 

112 

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 """ 

117 

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') 

128 

129 

130def _is_valid_multipath_device(sid): 

131 

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 

146 

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 

162 

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 

184 

185 

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) 

202 

203 

204def activate(): 

205 util.SMlog("MPATH: multipath activate called") 

206 

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() 

212 

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) 

217 

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) 

223 

224 util.SMlog("Failed to communicate with the multipath daemon!") 

225 raise xs_errors.XenError('MultipathdCommsFailure') 

226 

227 

228def deactivate(): 

229 util.SMlog("MPATH: multipath deactivate called") 

230 

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) 

235 

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() 

240 

241 util.kickpipe_mpathcount() 

242 util.SMlog("MPATH: multipath deactivated.") 

243 

244 

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