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 

27import subprocess 

28 

29supported = ['iscsi', 'lvmoiscsi', 'lvmohba', 'lvmofcoe', 'gfs2'] 

30 

31LOCK_TYPE_HOST = "host" 

32LOCK_NS1 = "mpathcount1" 

33LOCK_NS2 = "mpathcount2" 

34 

35MAPPER_DIR = "/dev/mapper" 

36MPATHS_DIR = "/dev/shm" 

37MPATH_FILE_NAME = "/dev/shm/mpath_status" 

38match_bySCSIid = False 

39mpath_enabled = True 

40SCSIid = 'NOTSUPPLIED' 

41XAPI_HEALTH_CHECK = '/opt/xensource/libexec/xapi-health-check' 

42 

43cached_DM_maj = None 

44 

45def get_dm_major(): 

46 global cached_DM_maj 

47 if not cached_DM_maj: 

48 try: 

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

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

51 except: 

52 pass 

53 return cached_DM_maj 

54 

55 

56def match_host_id(s): 

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

58 return regex.search(s, 0) 

59 

60 

61def get_localhost_uuid(): 

62 filename = '/etc/xensource-inventory' 

63 try: 

64 f = open(filename, 'r') 

65 except: 

66 raise xs_errors.XenError('EIO', \ 

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

68 domid = '' 

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

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

71 return domid 

72 

73 

74def match_dmpLUN(s): 

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

76 return regex.search(s, 0) 

77 

78 

79def match_pathup(s): 

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

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

82 path_status = match.group(1) 

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

84 return False 

85 return True 

86 

87 

88def _tostring(l): 

89 return str(l) 

90 

91 

92def get_path_count(SCSIid): 

93 count = 0 

94 total = 0 

95 lines = mpath_cli.get_topology(SCSIid) 

96 for line in filter(match_dmpLUN, lines): 

97 total += 1 

98 if match_pathup(line): 

99 count += 1 

100 return (count, total) 

101 

102 

103def get_root_dev_major(): 

104 buf = os.stat('/') 

105 devno = buf.st_dev 

106 return os.major(devno) 

107 

108 

109# @key: key to update 

110# @SCSIid: SCSI id of multipath map 

111# @entry: string representing previous value 

112# @remove: callback to remove key 

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

114# @mpath_status: map to record multipath status 

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

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

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

118 if os.path.exists(path): 

119 count, total = get_path_count(SCSIid) 

120 max = 0 

121 if len(entry) != 0: 

122 try: 

123 p = entry.strip('[') 

124 p = p.strip(']') 

125 q = p.split(',') 

126 max = int(q[1]) 

127 except: 

128 pass 

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

130 max = total 

131 newentry = [count, max] 

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

133 remove('multipathed') 

134 remove(key) 

135 add('multipathed', 'true') 

136 add(key, str(newentry)) 

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

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

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

140 else: 

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

142 remove('multipathed') 

143 remove(key) 

144 

145 

146def get_SCSIidlist(devconfig, sm_config): 

147 SCSIidlist = [] 

148 if 'SCSIid' in sm_config: 

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

150 elif 'SCSIid' in devconfig: 

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

152 elif 'provider' in devconfig: 

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

154 else: 

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

156 if util._isSCSIid(key): 

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

158 return SCSIidlist 

159 

160 

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

162 if get_root_dev_major() == get_dm_major(): 

163 # Ensure output headers are not in the list 

164 if 'name' in maps: 

165 maps.remove('name') 

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

167 assert(len(maps) > 0) 

168 i = maps[0] 

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

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

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

172 key = "mpath-boot" 

173 if key not in config: 

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

175 else: 

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

177 

178 

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

180 SCSIidlist = get_SCSIidlist(devconfig, sm_config) 

181 if not len(SCSIidlist): 

182 return 

183 for i in SCSIidlist: 

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

185 continue 

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

187 key = "mpath-" + i 

188 if not mpath_enabled: 

189 remove(key) 

190 remove('multipathed') 

191 else: 

192 if key not in config: 

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

194 else: 

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

196 

197def check_xapi_is_enabled(): 

198 """Check XAPI health status""" 

199 def _run_command(command, timeout): 

200 try: 

201 process = subprocess.Popen( 

202 command, 

203 stdout=subprocess.PIPE, 

204 stderr=subprocess.PIPE, 

205 universal_newlines=True 

206 ) 

207 try: 

208 stdout, stderr = process.communicate(timeout=timeout) 

209 return process.returncode, stdout, stderr 

210 except subprocess.TimeoutExpired: 

211 process.kill() 

212 util.SMlog(f"Command execution timeout after {timeout}s: {' '.join(command)}") 

213 return -1, "", "Timeout" 

214 except Exception as e: 

215 util.SMlog(f"Error executing command: {e}") 

216 return -1, "", str(e) 

217 

218 returncode, _, stderr = _run_command([XAPI_HEALTH_CHECK], timeout=120) 

219 if returncode != 0: 

220 util.SMlog(f"XAPI health check failed: {stderr}") 

221 return returncode == 0 

222 

223def main(session): 

224 global mpath_enabled 

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

226 check_xapi_is_enabled() 

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

228 try: 

229 if get_root_dev_major() != get_dm_major(): 

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

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

232 mpath_enabled = True 

233 except: 

234 mpath_enabled = False 

235 

236 # Check root disk if multipathed 

237 try: 

238 def _remove(key): 

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

240 

241 def _add(key, val): 

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

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

244 maps = mpath_cli.list_maps() 

245 check_root_disk(config, maps, _remove, _add) 

246 

247 except: 

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

249 sys.exit(-1) 

250 

251 try: 

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

253 except: 

254 sys.exit(-1) 

255 

256 try: 

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

258 for pbd in pbds: 

259 def remove(key): 

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

261 

262 def add(key, val): 

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

264 record = pbds[pbd] 

265 config = record['other_config'] 

266 SR = record['SR'] 

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

268 if srtype in supported: 

269 devconfig = record["device_config"] 

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

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

272 mpath_status = mpath_status if mpath_enabled else {} 

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

274 os.chmod(MPATH_FILE_NAME, 0o0644) 

275 except: 

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

277 sys.exit(-1) 

278 

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

280 

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

282 try: 

283 with util.ApiSession("SM-mpathcount") as api_session: 

284 main(api_session) 

285 except xs_errors.XenError as e: 

286 util.SMlog(f"MPATH: Failure `{e}`") 

287 sys.exit(-1) 

288 sys.exit(0)