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# 

18# LVM-based journaling 

19 

20import util 

21import xs_errors 

22from srmetadata import open_file, file_read_wrapper, file_write_wrapper 

23 

24LVM_MAX_NAME_LEN = 127 

25 

26 

27class Journaler: 

28 """Simple journaler that uses LVM namespace for persistent "storage". 

29 A journal is a id-value pair, and there can be only one journal for a 

30 given id.""" 

31 

32 LV_SIZE = 4 * 1024 * 1024 # minimum size 

33 LV_TAG = "journaler" 

34 SEPARATOR = "_" 

35 JRN_CLONE = "clone" 

36 JRN_LEAF = "leaf" 

37 

38 def __init__(self, lvmCache): 

39 self.vgName = lvmCache.vgName 

40 self.lvmCache = lvmCache 

41 

42 def create(self, type, id, val): 

43 """Create an entry of type "type" for "id" with the value "val". 

44 Error if such an entry already exists.""" 

45 valExisting = self.get(type, id) 

46 writeData = False 

47 if valExisting or util.fistpoint.is_active("LVM_journaler_exists"): 

48 raise xs_errors.XenError('LVMCreate', opterr="Journal already exists for '%s:%s': %s" % (type, id, valExisting)) 

49 lvName = self._getNameLV(type, id, val) 

50 

51 mapperDevice = self._getLVMapperName(lvName) 

52 if len(mapperDevice) > LVM_MAX_NAME_LEN: 

53 lvName = self._getNameLV(type, id) 

54 writeData = True 

55 mapperDevice = self._getLVMapperName(lvName) 

56 assert len(mapperDevice) <= LVM_MAX_NAME_LEN 

57 

58 self.lvmCache.create(lvName, self.LV_SIZE, self.LV_TAG) 

59 

60 if writeData: 

61 fullPath = self.lvmCache._getPath(lvName) 

62 journal_file = open_file(fullPath, True) 

63 try: 

64 e = None 

65 try: 

66 data = ("%d %s" % (len(val), val)).encode() 

67 file_write_wrapper(journal_file, 0, data) 

68 if util.fistpoint.is_active("LVM_journaler_writefail"): 

69 raise ValueError("LVM_journaler_writefail FistPoint active") 

70 except Exception as e: 

71 raise 

72 finally: 

73 try: 

74 journal_file.close() 

75 self.lvmCache.deactivateNoRefcount(lvName) 

76 except Exception as e2: 

77 msg = 'failed to close/deactivate %s: %s' \ 

78 % (lvName, e2) 

79 if not e: 

80 util.SMlog(msg) 

81 raise e2 

82 else: 

83 util.SMlog('WARNING: %s (error ignored)' % msg) 

84 

85 except: 

86 util.logException("journaler.create") 

87 try: 

88 self.lvmCache.remove(lvName) 

89 except Exception as e: 

90 util.SMlog('WARNING: failed to clean up failed journal ' \ 

91 ' creation: %s (error ignored)' % e) 

92 raise xs_errors.XenError('LVMWrite', opterr="Failed to write to journal %s" % lvName) 

93 

94 def remove(self, type, id): 

95 """Remove the entry of type "type" for "id". Error if the entry doesn't 

96 exist.""" 

97 val = self.get(type, id) 

98 if not val or util.fistpoint.is_active("LVM_journaler_none"): 

99 raise xs_errors.XenError('LVMNoVolume', opterr="No journal for '%s:%s'" % (type, id)) 

100 lvName = self._getNameLV(type, id, val) 

101 

102 mapperDevice = self._getLVMapperName(lvName) 

103 if len(mapperDevice) > LVM_MAX_NAME_LEN: 

104 lvName = self._getNameLV(type, id) 

105 self.lvmCache.remove(lvName) 

106 

107 def get(self, type, id): 

108 """Get the value for the journal entry of type "type" for "id". 

109 Return None if no such entry exists""" 

110 entries = self._getAllEntries() 

111 if not entries.get(type): 

112 return None 

113 return entries[type].get(id) 

114 

115 def getAll(self, type): 

116 """Get a mapping id->value for all entries of type "type".""" 

117 entries = self._getAllEntries() 

118 if not entries.get(type): 118 ↛ 120line 118 didn't jump to line 120, because the condition on line 118 was never false

119 return dict() 

120 return entries[type] 

121 

122 def hasJournals(self, id): 

123 """Return True if there any journals for "id", False otherwise""" 

124 # Pass False as an argument to skip opening journal files 

125 entries = self._getAllEntries(False) 

126 for type, ids in entries.items(): 

127 if ids.get(id): 

128 return True 

129 return False 

130 

131 def _getNameLV(self, type, id, val=1): 

132 return "%s%s%s%s%s" % (type, self.SEPARATOR, id, self.SEPARATOR, val) 

133 

134 def _getAllEntries(self, readFile=True): 

135 lvList = self.lvmCache.getTagged(self.LV_TAG) 

136 entries = dict() 

137 for lvName in lvList: 137 ↛ 138line 137 didn't jump to line 138, because the loop on line 137 never started

138 parts = lvName.split(self.SEPARATOR, 2) 

139 if len(parts) != 3 or util.fistpoint.is_active("LVM_journaler_badname"): 

140 raise xs_errors.XenError('LVMNoVolume', opterr="Bad LV name: %s" % lvName) 

141 type, id, val = parts 

142 if readFile: 

143 # For clone and leaf journals, additional 

144 # data is written inside file 

145 # TODO: Remove dependency on journal type 

146 if type == self.JRN_CLONE or type == self.JRN_LEAF: 

147 fullPath = self.lvmCache._getPath(lvName) 

148 self.lvmCache.activateNoRefcount(lvName, False) 

149 journal_file = open_file(fullPath) 

150 try: 

151 try: 

152 data = file_read_wrapper(journal_file, 0) 

153 length, val = data.decode().split(" ", 1) 

154 val = val[:int(length)] 

155 if util.fistpoint.is_active("LVM_journaler_readfail"): 

156 raise ValueError("LVM_journaler_readfail FistPoint active") 

157 except: 

158 raise xs_errors.XenError('LVMRead', opterr="Failed to read from journal %s" % lvName) 

159 finally: 

160 journal_file.close() 

161 self.lvmCache.deactivateNoRefcount(lvName) 

162 if not entries.get(type): 

163 entries[type] = dict() 

164 entries[type][id] = val 

165 return entries 

166 

167 def _getLVMapperName(self, lvName): 

168 return '%s-%s' % (self.vgName.replace("-", "--"), lvName) 

169 

170########################################################################### 

171# 

172# Unit tests 

173# 

174import lvutil 

175import lvmcache 

176 

177 

178def _runTests(vgName): 

179 """Unit testing""" 

180 print("Running unit tests...") 

181 if not vgName: 

182 print("Error: missing VG name param") 

183 return 1 

184 if not lvutil._checkVG(vgName): 

185 print("Error: VG %s not found" % vgName) 

186 return 1 

187 

188 j = Journaler(lvmcache.LVMCache(vgName)) 

189 if j.get("clone", "1"): 

190 print("get non-existing failed") 

191 return 1 

192 j.create("clone", "1", "a") 

193 val = j.get("clone", "1") 

194 if val != "a": 

195 print("create-get failed") 

196 return 1 

197 j.remove("clone", "1") 

198 if j.get("clone", "1"): 

199 print("remove failed") 

200 return 1 

201 j.create("modify", "X", "831_3") 

202 j.create("modify", "Z", "831_4") 

203 j.create("modify", "Y", "53_0") 

204 val = j.get("modify", "X") 

205 if val != "831_3": 

206 print("create underscore_val failed") 

207 return 1 

208 val = j.get("modify", "Y") 

209 if val != "53_0": 

210 print("create multiple id's failed") 

211 return 1 

212 entries = j.getAll("modify") 

213 if not entries.get("X") or not entries.get("Y") or \ 

214 entries["X"] != "831_3" or entries["Y"] != "53_0": 

215 print("getAll failed: %s" % entries) 

216 return 1 

217 j.remove("modify", "X") 

218 val = j.getAll("modify") 

219 if val.get("X") or not val.get("Y") or val["Y"] != "53_0": 

220 print("remove(X) failed") 

221 return 1 

222 j.remove("modify", "Y") 

223 j.remove("modify", "Z") 

224 if j.get("modify", "Y"): 

225 print("remove(Y) failed") 

226 return 1 

227 if j.get("modify", "Z"): 

228 print("remove(Z) failed") 

229 return 1 

230 print("All tests passed") 

231 return 0 

232 

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

234 import sys 

235 vgName = None 

236 if len(sys.argv) > 1: 

237 vgName = sys.argv[1] 

238 ret = _runTests(vgName) 

239 sys.exit(ret)