Coverage for drivers/mpathcount.py : 62%

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
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
18from sm_typing import Dict
20import util
21import os
22import sys
23import re
24import xs_errors
25import mpath_cli
26import json
27import subprocess
29supported = ['iscsi', 'lvmoiscsi', 'rawhba', 'lvmohba', 'ocfsohba', 'ocfsoiscsi', 'netapp', 'lvmofcoe', 'gfs2']
31LOCK_TYPE_HOST = "host"
32LOCK_NS1 = "mpathcount1"
33LOCK_NS2 = "mpathcount2"
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'
43cached_DM_maj = None
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
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)
65def match_host_id(s):
66 regex = re.compile("^INSTALLATION_UUID")
67 return regex.search(s, 0)
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
83def match_dmpLUN(s):
84 regex = re.compile("[0-9]*:[0-9]*:[0-9]*:[0-9]*")
85 return regex.search(s, 0)
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 ↛ 92line 90 didn't jump to line 92, 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
97def _tostring(l):
98 return str(l)
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)
112def get_root_dev_major():
113 buf = os.stat('/')
114 devno = buf.st_dev
115 return os.major(devno)
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)
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
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)
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)
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)
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
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)
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
250 # Check root disk if multipathed
251 try:
252 def _remove(key):
253 session.xenapi.host.remove_from_other_config(localhost, key)
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)
262 except:
263 util.SMlog("MPATH: Failure updating Host.other-config:mpath-boot db")
264 mpc_exit(session, -1)
266 try:
267 pbds = session.xenapi.PBD.get_all_records_where("field \"host\" = \"%s\"" % localhost)
268 except:
269 mpc_exit(session, -1)
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)
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)
295 util.SMlog("MPATH: Update done")
297 mpc_exit(session, 0)