Coverage for libs/sm/core/wwid_conf.py: 14%
93 statements
« prev ^ index » next coverage.py v7.3.2, created at 2026-07-10 07:59 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2026-07-10 07:59 +0000
1#
2# Copyright (C) Citrix Systems Inc.
3#
4# This program is free software; you can redistribute it and/or modify
5# it under the terms of the GNU Lesser General Public License as published
6# by the Free Software Foundation; version 2.1 only.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU Lesser General Public License for more details.
12#
13# You should have received a copy of the GNU Lesser General Public License
14# along with this program; if not, write to the Free Software Foundation, Inc.,
15# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16#
17# Manipulation utilities for multipath.conf
18#
20import fileinput
21import shutil
22import sys
23import re
24from sm.core import util
25from sm.core import lock
27LOCK_TYPE_HOST = "host"
28LOCK_NS = "multipath.conf"
29# We always expect this file.
30# It must be a regular file pointed by /etc/multipath.conf
31CONF_FILE = "/etc/multipath.xenserver/multipath.conf"
32BELIST_TAG = "blacklist_exceptions"
35class WWIDException(Exception):
36 def __init__(self, errstr):
37 self.errstr = errstr
40def edit_wwid(wwid, remove=False):
41 """Add a wwid to the list of exceptions or remove if
42 remove is set to 1.
44 """
46 tmp_file = CONF_FILE + "~"
47 filt_regex = re.compile(r'^\s*%s\s*{' % BELIST_TAG)
48 wwid_regex = re.compile(r'^\s*wwid\s+\"%s\"' % wwid)
50 conflock = lock.Lock(LOCK_TYPE_HOST, LOCK_NS)
51 conflock.acquire()
53 try:
54 shutil.copy2(CONF_FILE, tmp_file)
55 except:
56 util.SMlog("Failed to create temp file %s" % (tmp_file))
57 raise
59 add_mode = True
60 for line in fileinput.input(tmp_file, inplace=1):
61 if add_mode:
62 print(line, end=' ')
63 else:
64 if wwid_regex.match(line):
65 add_mode = True
66 else:
67 print(line, end=' ')
68 continue
70 if filt_regex.match(line):
71 if remove:
72 # looking for the line to remove
73 add_mode = False
74 continue
75 else:
76 print("\twwid \"%s\"" % wwid)
78 shutil.move(tmp_file, CONF_FILE)
81def is_blacklisted(dev):
82 """This function returns True if the device is blacklisted according
83 to multipath.conf rules.
85 There is also the possibility that the path to the device we are
86 checking is unavailable for some reason. In that case,
87 is_blacklisted() cannot tell if the device is blacklisted or not
88 and a WWIDException is raised.
90 It cannot be used to check the current daemon rules because it
91 could be running with an old configuration file in memory
93 Input:
94 dev -- it is any string accepted by "multipath -c". A full path
95 is expected.
97 Return:
98 bool -- True or False, depending on whether the device is
99 blacklisted or not.
101 Raise:
102 WWIDException
103 """
105 (rc, stdout, stderr) = util.doexec(['/sbin/multipath', '-v3', '-c', dev])
107 if "scope is nul" in stdout:
108 raise WWIDException("WARNING: Could not retrieve device's "
109 "WWID. Path {} is down".format(dev))
111 # If the device is blacklisted according to multipath.conf, the
112 # string "wwid blacklisted" appears in the verbose output.
113 # This is a very fragile mechanism and keeps changing.
114 # What we want is a method to tell immediately if a device is
115 # blacklisted according only to configuration file rules regardless
116 # of daemon in-memory configuration.
117 return "wwid blacklisted" in stdout
120def check_conf_file():
121 (rc, stdout, stderr) = util.doexec(['/sbin/multipath', '-h'])
122 # Ugly way to check for malformed conf file
123 if len(stdout):
124 util.SMlog("Malformed multipath conf file")
125 return 1
126 return 0
129def usage():
130 print("Usage: %s [-r] -d <device path> -w <device wwid>" % sys.argv[0])
131 print("Usage: %s -f [-r] -w <device wwid>" % sys.argv[0])
132 print("\tAdd a device wwid to multipath.conf whitelist")
133 print("\t-r: remove")
134 print("\t-f: if provided the operation will be performed anyway")
135 print("\t otherwise it will be after checking <device> is")
136 print("\t blacklisted (or not)")
139if __name__ == "__main__": 139 ↛ 140line 139 didn't jump to line 140, because the condition on line 139 was never true
140 import getopt
141 from operator import xor
143 try:
144 opts, args = getopt.getopt(sys.argv[1:], "fd:w:r")
145 except getopt.GetoptError:
146 usage()
147 sys.exit(1)
149 remove = False
150 device = ""
151 force = False
152 wwid = ""
154 for o, a in opts:
155 if o == "-r":
156 remove = True
157 elif o == "-d":
158 device = a
159 elif o == "-f":
160 force = True
161 elif o == "-w":
162 wwid = a
164 if not wwid:
165 usage()
166 sys.exit(1)
168 if not (device or force):
169 usage()
170 sys.exit(1)
172 try:
173 if force or xor(remove, is_blacklisted(device)):
174 try:
175 edit_wwid(wwid, remove)
176 except:
177 sys.exit(1)
178 except WWIDException as e:
179 util.SMlog(e.errstr)
181 sys.exit(0)