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

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# 

19 

20import fileinput 

21import shutil 

22import sys 

23import re 

24from sm.core import util 

25from sm.core import lock 

26 

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" 

33 

34 

35class WWIDException(Exception): 

36 def __init__(self, errstr): 

37 self.errstr = errstr 

38 

39 

40def edit_wwid(wwid, remove=False): 

41 """Add a wwid to the list of exceptions or remove if 

42 remove is set to 1. 

43 

44 """ 

45 

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) 

49 

50 conflock = lock.Lock(LOCK_TYPE_HOST, LOCK_NS) 

51 conflock.acquire() 

52 

53 try: 

54 shutil.copy2(CONF_FILE, tmp_file) 

55 except: 

56 util.SMlog("Failed to create temp file %s" % (tmp_file)) 

57 raise 

58 

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 

69 

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) 

77 

78 shutil.move(tmp_file, CONF_FILE) 

79 

80 

81def is_blacklisted(dev): 

82 """This function returns True if the device is blacklisted according 

83 to multipath.conf rules. 

84 

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. 

89 

90 It cannot be used to check the current daemon rules because it 

91 could be running with an old configuration file in memory 

92 

93 Input: 

94 dev -- it is any string accepted by "multipath -c". A full path 

95 is expected. 

96 

97 Return: 

98 bool -- True or False, depending on whether the device is 

99 blacklisted or not. 

100 

101 Raise: 

102 WWIDException 

103 """ 

104 

105 (rc, stdout, stderr) = util.doexec(['/sbin/multipath', '-v3', '-c', dev]) 

106 

107 if "scope is nul" in stdout: 

108 raise WWIDException("WARNING: Could not retrieve device's " 

109 "WWID. Path {} is down".format(dev)) 

110 

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 

118 

119 

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 

127 

128 

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)") 

137 

138 

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 

142 

143 try: 

144 opts, args = getopt.getopt(sys.argv[1:], "fd:w:r") 

145 except getopt.GetoptError: 

146 usage() 

147 sys.exit(1) 

148 

149 remove = False 

150 device = "" 

151 force = False 

152 wwid = "" 

153 

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 

163 

164 if not wwid: 

165 usage() 

166 sys.exit(1) 

167 

168 if not (device or force): 

169 usage() 

170 sys.exit(1) 

171 

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) 

180 

181 sys.exit(0)