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', 'rawhba', 'lvmohba', 'ocfsohba', 'ocfsoiscsi', 'netapp', '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 mpc_exit(session, code): 

57 if session is not None: 

58 try: 

59 session.xenapi.session.logout() 

60 except: 

61 pass 

62 sys.exit(code) 

63 

64 

65def match_host_id(s): 

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

67 return regex.search(s, 0) 

68 

69 

70def get_localhost_uuid(): 

71 filename = '/etc/xensource-inventory' 

72 try: 

73 f = open(filename, 'r') 

74 except: 

75 raise xs_errors.XenError('EIO', \ 

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

77 domid = '' 

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

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

80 return domid 

81 

82 

83def match_dmpLUN(s): 

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

85 return regex.search(s, 0) 

86 

87 

88def match_pathup(s): 

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

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

91 path_status = match.group(1) 

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

93 return False 

94 return True 

95 

96 

97def _tostring(l): 

98 return str(l) 

99 

100 

101def get_path_count(SCSIid): 

102 count = 0 

103 total = 0 

104 lines = mpath_cli.get_topology(SCSIid) 

105 for line in filter(match_dmpLUN, lines): 

106 total += 1 

107 if match_pathup(line): 

108 count += 1 

109 return (count, total) 

110 

111 

112def get_root_dev_major(): 

113 buf = os.stat('/') 

114 devno = buf.st_dev 

115 return os.major(devno) 

116 

117 

118# @key: key to update 

119# @SCSIid: SCSI id of multipath map 

120# @entry: string representing previous value 

121# @remove: callback to remove key 

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

123# @mpath_status: map to record multipath status 

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

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

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

127 if os.path.exists(path): 

128 count, total = get_path_count(SCSIid) 

129 max = 0 

130 if len(entry) != 0: 

131 try: 

132 p = entry.strip('[') 

133 p = p.strip(']') 

134 q = p.split(',') 

135 max = int(q[1]) 

136 except: 

137 pass 

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

139 max = total 

140 newentry = [count, max] 

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

142 remove('multipathed') 

143 remove(key) 

144 add('multipathed', 'true') 

145 add(key, str(newentry)) 

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

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

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

149 else: 

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

151 remove('multipathed') 

152 remove(key) 

153 

154 

155def get_SCSIidlist(devconfig, sm_config): 

156 SCSIidlist = [] 

157 if 'SCSIid' in sm_config: 

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

159 elif 'SCSIid' in devconfig: 

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

161 elif 'provider' in devconfig: 

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

163 else: 

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

165 if util._isSCSIid(key): 

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

167 return SCSIidlist 

168 

169 

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

171 if get_root_dev_major() == get_dm_major(): 

172 # Ensure output headers are not in the list 

173 if 'name' in maps: 

174 maps.remove('name') 

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

176 assert(len(maps) > 0) 

177 i = maps[0] 

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

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

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

181 key = "mpath-boot" 

182 if key not in config: 

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

184 else: 

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

186 

187 

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

189 SCSIidlist = get_SCSIidlist(devconfig, sm_config) 

190 if not len(SCSIidlist): 

191 return 

192 for i in SCSIidlist: 

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

194 continue 

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

196 key = "mpath-" + i 

197 if not mpath_enabled: 

198 remove(key) 

199 remove('multipathed') 

200 else: 

201 if key not in config: 

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

203 else: 

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

205 

206def check_xapi_is_enabled(): 

207 """Check XAPI health status""" 

208 def _run_command(command, timeout): 

209 try: 

210 process = subprocess.Popen( 

211 command, 

212 stdout=subprocess.PIPE, 

213 stderr=subprocess.PIPE, 

214 universal_newlines=True 

215 ) 

216 try: 

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

218 return process.returncode, stdout, stderr 

219 except subprocess.TimeoutExpired: 

220 process.kill() 

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

222 return -1, "", "Timeout" 

223 except Exception as e: 

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

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

226 

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

228 if returncode != 0: 

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

230 return returncode == 0 

231 

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

233 try: 

234 session = util.get_localAPI_session() 

235 except: 

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

237 sys.exit(-1) 

238 

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

240 check_xapi_is_enabled() 

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

242 try: 

243 if get_root_dev_major() != get_dm_major(): 

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

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

246 mpath_enabled = True 

247 except: 

248 mpath_enabled = False 

249 

250 # Check root disk if multipathed 

251 try: 

252 def _remove(key): 

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

254 

255 

256 def _add(key, val): 

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

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

259 maps = mpath_cli.list_maps() 

260 check_root_disk(config, maps, _remove, _add) 

261 

262 except: 

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

264 mpc_exit(session, -1) 

265 

266 try: 

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

268 except: 

269 mpc_exit(session, -1) 

270 

271 try: 

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

273 for pbd in pbds: 

274 def remove(key): 

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

276 

277 

278 def add(key, val): 

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

280 record = pbds[pbd] 

281 config = record['other_config'] 

282 SR = record['SR'] 

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

284 if srtype in supported: 

285 devconfig = record["device_config"] 

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

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

288 mpath_status = mpath_status if mpath_enabled else {} 

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

290 os.chmod(MPATH_FILE_NAME, 0o0644) 

291 except: 

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

293 mpc_exit(session, -1) 

294 

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

296 

297 mpc_exit(session, 0)