Hide keyboard shortcuts

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#!/usr/bin/python3 

2 

3# Copyright (C) Citrix Systems Inc. 

4# 

5# This program is free software; you can redistribute it and/or modify 

6# it under the terms of the GNU Lesser General Public License as published 

7# by the Free Software Foundation; version 2.1 only. 

8# 

9# This program is distributed in the hope that it will be useful, 

10# but WITHOUT ANY WARRANTY; without even the implied warranty of 

11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

12# GNU Lesser General Public License for more details. 

13# 

14# You should have received a copy of the GNU Lesser General Public License 

15# along with this program; if not, write to the Free Software Foundation, Inc., 

16# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 

17 

18from sm_typing import Dict 

19 

20import util 

21import os 

22import sys 

23import re 

24import xs_errors 

25import mpath_cli 

26import json 

27 

28supported = ['iscsi', 'lvmoiscsi', 'rawhba', 'lvmohba', 'ocfsohba', 'ocfsoiscsi', 'netapp', 'lvmofcoe', 'gfs2'] 

29 

30LOCK_TYPE_HOST = "host" 

31LOCK_NS1 = "mpathcount1" 

32LOCK_NS2 = "mpathcount2" 

33 

34MAPPER_DIR = "/dev/mapper" 

35MPATHS_DIR = "/dev/shm" 

36MPATH_FILE_NAME = "/dev/shm/mpath_status" 

37match_bySCSIid = False 

38mpath_enabled = True 

39SCSIid = 'NOTSUPPLIED' 

40 

41cached_DM_maj = None 

42 

43def get_dm_major(): 

44 global cached_DM_maj 

45 if not cached_DM_maj: 

46 try: 

47 line = [x for x in open('/proc/devices').readlines() if x.endswith('device-mapper\n')] 

48 cached_DM_maj = int(line[0].split()[0]) 

49 except: 

50 pass 

51 return cached_DM_maj 

52 

53 

54def mpc_exit(session, code): 

55 if session is not None: 

56 try: 

57 session.xenapi.session.logout() 

58 except: 

59 pass 

60 sys.exit(code) 

61 

62 

63def match_host_id(s): 

64 regex = re.compile("^INSTALLATION_UUID") 

65 return regex.search(s, 0) 

66 

67 

68def get_localhost_uuid(): 

69 filename = '/etc/xensource-inventory' 

70 try: 

71 f = open(filename, 'r') 

72 except: 

73 raise xs_errors.XenError('EIO', \ 

74 opterr="Unable to open inventory file [%s]" % filename) 

75 domid = '' 

76 for line in filter(match_host_id, f.readlines()): 

77 domid = line.split("'")[1] 

78 return domid 

79 

80 

81def match_dmpLUN(s): 

82 regex = re.compile("[0-9]*:[0-9]*:[0-9]*:[0-9]*") 

83 return regex.search(s, 0) 

84 

85 

86def match_pathup(s): 

87 match = re.match(r'.*\d+:\d+:\d+:\d+\s+\S+\s+\S+\s+\S+\s+(\S+)', s) 

88 if match: 88 ↛ 90line 88 didn't jump to line 90, because the condition on line 88 was never false

89 path_status = match.group(1) 

90 if path_status in ['faulty', 'shaky', 'failed']: 

91 return False 

92 return True 

93 

94 

95def _tostring(l): 

96 return str(l) 

97 

98 

99def get_path_count(SCSIid): 

100 count = 0 

101 total = 0 

102 lines = mpath_cli.get_topology(SCSIid) 

103 for line in filter(match_dmpLUN, lines): 

104 total += 1 

105 if match_pathup(line): 

106 count += 1 

107 return (count, total) 

108 

109 

110def get_root_dev_major(): 

111 buf = os.stat('/') 

112 devno = buf.st_dev 

113 return os.major(devno) 

114 

115 

116# @key: key to update 

117# @SCSIid: SCSI id of multipath map 

118# @entry: string representing previous value 

119# @remove: callback to remove key 

120# @add: callback to add key/value pair 

121# @mpath_status: map to record multipath status 

122def update_config(key, SCSIid, entry, remove, add, mpath_status=None): 

123 path = os.path.join(MAPPER_DIR, SCSIid) 

124 util.SMlog("MPATH: Updating entry for [%s], current: %s" % (SCSIid, entry)) 

125 if os.path.exists(path): 

126 count, total = get_path_count(SCSIid) 

127 max = 0 

128 if len(entry) != 0: 

129 try: 

130 p = entry.strip('[') 

131 p = p.strip(']') 

132 q = p.split(',') 

133 max = int(q[1]) 

134 except: 

135 pass 

136 if total > max: 136 ↛ 138line 136 didn't jump to line 138, because the condition on line 136 was never false

137 max = total 

138 newentry = [count, max] 

139 if str(newentry) != entry: 139 ↛ 145line 139 didn't jump to line 145, because the condition on line 139 was never false

140 remove('multipathed') 

141 remove(key) 

142 add('multipathed', 'true') 

143 add(key, str(newentry)) 

144 util.SMlog("MPATH: Set val: %s" % str(newentry)) 

145 if mpath_status != None: 145 ↛ 146line 145 didn't jump to line 146, because the condition on line 145 was never true

146 mpath_status.update({str(key): f"{count}/{max}"}) 

147 else: 

148 util.SMlog('MPATH: device %s gone' % (SCSIid)) 

149 remove('multipathed') 

150 remove(key) 

151 

152 

153def get_SCSIidlist(devconfig, sm_config): 

154 SCSIidlist = [] 

155 if 'SCSIid' in sm_config: 

156 SCSIidlist = sm_config['SCSIid'].split(',') 

157 elif 'SCSIid' in devconfig: 

158 SCSIidlist.append(devconfig['SCSIid']) 

159 elif 'provider' in devconfig: 

160 SCSIidlist.append(devconfig['ScsiId']) 

161 else: 

162 for key in sm_config: 162 ↛ 163line 162 didn't jump to line 163, because the loop on line 162 never started

163 if util._isSCSIid(key): 

164 SCSIidlist.append(re.sub("^scsi-", "", key)) 

165 return SCSIidlist 

166 

167 

168def check_root_disk(config, maps, remove, add): 

169 if get_root_dev_major() == get_dm_major(): 

170 # Ensure output headers are not in the list 

171 if 'name' in maps: 

172 maps.remove('name') 

173 # first map will always correspond to the root dev, dm-0 

174 assert(len(maps) > 0) 

175 i = maps[0] 

176 if (not match_bySCSIid) or i == SCSIid: 176 ↛ exitline 176 didn't return from function 'check_root_disk', because the condition on line 176 was never false

177 util.SMlog("Matched SCSIid %s, updating " \ 

178 " Host.other-config:mpath-boot " % i) 

179 key = "mpath-boot" 

180 if key not in config: 

181 update_config(key, i, "", remove, add) 

182 else: 

183 update_config(key, i, config[key], remove, add) 

184 

185 

186def check_devconfig(devconfig, sm_config, config, remove, add, mpath_status=None): 

187 SCSIidlist = get_SCSIidlist(devconfig, sm_config) 

188 if not len(SCSIidlist): 

189 return 

190 for i in SCSIidlist: 

191 if match_bySCSIid and i != SCSIid: 191 ↛ 192line 191 didn't jump to line 192, because the condition on line 191 was never true

192 continue 

193 util.SMlog("Matched SCSIid, updating %s" % i) 

194 key = "mpath-" + i 

195 if not mpath_enabled: 

196 remove(key) 

197 remove('multipathed') 

198 else: 

199 if key not in config: 

200 update_config(key, i, "", remove, add, mpath_status) 

201 else: 

202 update_config(key, i, config[key], remove, add, mpath_status) 

203 

204if __name__ == '__main__': 204 ↛ 205line 204 didn't jump to line 205, because the condition on line 204 was never true

205 try: 

206 session = util.get_localAPI_session() 

207 except: 

208 print("Unable to open local XAPI session") 

209 sys.exit(-1) 

210 

211 localhost = session.xenapi.host.get_by_uuid(get_localhost_uuid()) 

212 # Check whether multipathing is enabled (either for root dev or SRs) 

213 try: 

214 if get_root_dev_major() != get_dm_major(): 

215 hconf = session.xenapi.host.get_other_config(localhost) 

216 assert(hconf['multipathing'] == 'true') 

217 mpath_enabled = True 

218 except: 

219 mpath_enabled = False 

220 

221 # Check root disk if multipathed 

222 try: 

223 def _remove(key): 

224 session.xenapi.host.remove_from_other_config(localhost, key) 

225 

226 

227 def _add(key, val): 

228 session.xenapi.host.add_to_other_config(localhost, key, val) 

229 config = session.xenapi.host.get_other_config(localhost) 

230 maps = mpath_cli.list_maps() 

231 check_root_disk(config, maps, _remove, _add) 

232 

233 except: 

234 util.SMlog("MPATH: Failure updating Host.other-config:mpath-boot db") 

235 mpc_exit(session, -1) 

236 

237 try: 

238 pbds = session.xenapi.PBD.get_all_records_where("field \"host\" = \"%s\"" % localhost) 

239 except: 

240 mpc_exit(session, -1) 

241 

242 try: 

243 mpath_status: Dict[str, str] = {} 

244 for pbd in pbds: 

245 def remove(key): 

246 session.xenapi.PBD.remove_from_other_config(pbd, key) 

247 

248 

249 def add(key, val): 

250 session.xenapi.PBD.add_to_other_config(pbd, key, val) 

251 record = pbds[pbd] 

252 config = record['other_config'] 

253 SR = record['SR'] 

254 srtype = session.xenapi.SR.get_type(SR) 

255 if srtype in supported: 

256 devconfig = record["device_config"] 

257 sm_config = session.xenapi.SR.get_sm_config(SR) 

258 check_devconfig(devconfig, sm_config, config, remove, add, mpath_status) 

259 mpath_status = mpath_status if mpath_enabled else {} 

260 util.atomicFileWrite(MPATH_FILE_NAME, MPATHS_DIR, json.dumps(mpath_status)) 

261 os.chmod(MPATH_FILE_NAME, 0o0644) 

262 except: 

263 util.SMlog("MPATH: Failure updating db. %s" % str(sys.exc_info())) 

264 mpc_exit(session, -1) 

265 

266 util.SMlog("MPATH: Update done") 

267 

268 mpc_exit(session, 0)