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 

18import util 

19import os 

20import sys 

21import re 

22import xs_errors 

23import mpath_cli 

24import json 

25 

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

27 

28LOCK_TYPE_HOST = "host" 

29LOCK_NS1 = "mpathcount1" 

30LOCK_NS2 = "mpathcount2" 

31 

32MAPPER_DIR = "/dev/mapper" 

33MPATHS_DIR = "/dev/shm" 

34MPATH_FILE_NAME = "/dev/shm/mpath_status" 

35match_bySCSIid = False 

36mpath_enabled = True 

37SCSIid = 'NOTSUPPLIED' 

38 

39cached_DM_maj = None 

40 

41def get_dm_major(): 

42 global cached_DM_maj 

43 if not cached_DM_maj: 

44 try: 

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

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

47 except: 

48 pass 

49 return cached_DM_maj 

50 

51 

52def mpc_exit(session, code): 

53 if session is not None: 

54 try: 

55 session.xenapi.session.logout() 

56 except: 

57 pass 

58 sys.exit(code) 

59 

60 

61def match_host_id(s): 

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

63 return regex.search(s, 0) 

64 

65 

66def get_localhost_uuid(): 

67 filename = '/etc/xensource-inventory' 

68 try: 

69 f = open(filename, 'r') 

70 except: 

71 raise xs_errors.XenError('EIO', \ 

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

73 domid = '' 

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

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

76 return domid 

77 

78 

79def match_dmpLUN(s): 

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

81 return regex.search(s, 0) 

82 

83 

84def match_pathup(s): 

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

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

87 path_status = match.group(1) 

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

89 return False 

90 return True 

91 

92 

93def _tostring(l): 

94 return str(l) 

95 

96 

97def get_path_count(SCSIid): 

98 count = 0 

99 total = 0 

100 lines = mpath_cli.get_topology(SCSIid) 

101 for line in filter(match_dmpLUN, lines): 

102 total += 1 

103 if match_pathup(line): 

104 count += 1 

105 return (count, total) 

106 

107 

108def get_root_dev_major(): 

109 buf = os.stat('/') 

110 devno = buf.st_dev 

111 return os.major(devno) 

112 

113 

114# @key: key to update 

115# @SCSIid: SCSI id of multipath map 

116# @entry: string representing previous value 

117# @remove: callback to remove key 

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

119# @mpath_status: map to record multipath status 

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

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

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

123 if os.path.exists(path): 

124 count, total = get_path_count(SCSIid) 

125 max = 0 

126 if len(entry) != 0: 

127 try: 

128 p = entry.strip('[') 

129 p = p.strip(']') 

130 q = p.split(',') 

131 max = int(q[1]) 

132 except: 

133 pass 

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

135 max = total 

136 newentry = [count, max] 

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

138 remove('multipathed') 

139 remove(key) 

140 add('multipathed', 'true') 

141 add(key, str(newentry)) 

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

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

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

145 else: 

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

147 remove('multipathed') 

148 remove(key) 

149 

150 

151def get_SCSIidlist(devconfig, sm_config): 

152 SCSIidlist = [] 

153 if 'SCSIid' in sm_config: 

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

155 elif 'SCSIid' in devconfig: 

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

157 elif 'provider' in devconfig: 

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

159 else: 

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

161 if util._isSCSIid(key): 

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

163 return SCSIidlist 

164 

165 

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

167 if get_root_dev_major() == get_dm_major(): 

168 # Ensure output headers are not in the list 

169 if 'name' in maps: 

170 maps.remove('name') 

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

172 assert(len(maps) > 0) 

173 i = maps[0] 

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

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

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

177 key = "mpath-boot" 

178 if key not in config: 

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

180 else: 

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

182 

183 

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

185 SCSIidlist = get_SCSIidlist(devconfig, sm_config) 

186 if not len(SCSIidlist): 

187 return 

188 for i in SCSIidlist: 

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

190 continue 

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

192 key = "mpath-" + i 

193 if not mpath_enabled: 

194 remove(key) 

195 remove('multipathed') 

196 else: 

197 if key not in config: 

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

199 else: 

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

201 

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

203 try: 

204 session = util.get_localAPI_session() 

205 except: 

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

207 sys.exit(-1) 

208 

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

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

211 try: 

212 if get_root_dev_major() != get_dm_major(): 

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

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

215 mpath_enabled = True 

216 except: 

217 mpath_enabled = False 

218 

219 # Check root disk if multipathed 

220 try: 

221 def _remove(key): 

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

223 

224 

225 def _add(key, val): 

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

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

228 maps = mpath_cli.list_maps() 

229 check_root_disk(config, maps, _remove, _add) 

230 

231 except: 

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

233 mpc_exit(session, -1) 

234 

235 try: 

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

237 except: 

238 mpc_exit(session, -1) 

239 

240 try: 

241 mpath_status = {} 

242 for pbd in pbds: 

243 def remove(key): 

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

245 

246 

247 def add(key, val): 

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

249 record = pbds[pbd] 

250 config = record['other_config'] 

251 SR = record['SR'] 

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

253 if srtype in supported: 

254 devconfig = record["device_config"] 

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

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

257 mpath_status = mpath_status if mpath_enabled else {} 

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

259 os.chmod(MPATH_FILE_NAME, 0o0644) 

260 except: 

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

262 mpc_exit(session, -1) 

263 

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

265 

266 mpc_exit(session, 0)