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 

21from srmetadata import open_file, get_min_blk_size_wrapper, \ 

22 file_read_wrapper, file_write_wrapper 

23 

24LVM_MAX_NAME_LEN = 127 

25 

26 

27class JournalerException(util.SMException): 

28 pass 

29 

30 

31class Journaler: 

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

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

34 given id.""" 

35 

36 LV_SIZE = 4 * 1024 * 1024 # minimum size 

37 LV_TAG = "journaler" 

38 SEPARATOR = "_" 

39 JRN_CLONE = "clone" 

40 JRN_LEAF = "leaf" 

41 

42 def __init__(self, lvmCache): 

43 self.vgName = lvmCache.vgName 

44 self.lvmCache = lvmCache 

45 

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

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

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

49 valExisting = self.get(type, id) 

50 writeData = False 

51 if valExisting: 

52 raise JournalerException("Journal already exists for '%s:%s': %s" \ 

53 % (type, id, valExisting)) 

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

55 

56 mapperDevice = self._getLVMapperName(lvName) 

57 if len(mapperDevice) > LVM_MAX_NAME_LEN: 

58 lvName = self._getNameLV(type, id) 

59 writeData = True 

60 mapperDevice = self._getLVMapperName(lvName) 

61 assert len(mapperDevice) <= LVM_MAX_NAME_LEN 

62 

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

64 

65 if writeData: 

66 fullPath = self.lvmCache._getPath(lvName) 

67 journal_file = open_file(fullPath, True) 

68 try: 

69 e = None 

70 try: 

71 min_block_size = get_min_blk_size_wrapper(journal_file) 

72 data = "%d %s" % (len(val), val) 

73 file_write_wrapper(journal_file, 0, min_block_size, data, len(data)) 

74 except Exception as e: 

75 raise 

76 finally: 

77 try: 

78 journal_file.close() 

79 self.lvmCache.deactivateNoRefcount(lvName) 

80 except Exception as e2: 

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

82 % (lvName, e2) 

83 if not e: 

84 util.SMlog(msg) 

85 raise e2 

86 else: 

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

88 

89 except: 

90 util.logException("journaler.create") 

91 try: 

92 self.lvmCache.remove(lvName) 

93 except Exception as e: 

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

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

96 raise JournalerException("Failed to write to journal %s" \ 

97 % lvName) 

98 

99 def remove(self, type, id): 

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

101 exist.""" 

102 val = self.get(type, id) 

103 if not val: 

104 raise JournalerException("No journal for '%s:%s'" % (type, id)) 

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

106 

107 mapperDevice = self._getLVMapperName(lvName) 

108 if len(mapperDevice) > LVM_MAX_NAME_LEN: 

109 lvName = self._getNameLV(type, id) 

110 self.lvmCache.remove(lvName) 

111 

112 def get(self, type, id): 

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

114 Return None if no such entry exists""" 

115 entries = self._getAllEntries() 

116 if not entries.get(type): 

117 return None 

118 return entries[type].get(id) 

119 

120 def getAll(self, type): 

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

122 entries = self._getAllEntries() 

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

124 return dict() 

125 return entries[type] 

126 

127 def hasJournals(self, id): 

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

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

130 entries = self._getAllEntries(False) 

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

132 if ids.get(id): 

133 return True 

134 return False 

135 

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

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

138 

139 def _getAllEntries(self, readFile=True): 

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

141 entries = dict() 

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

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

144 if len(parts) != 3: 

145 raise JournalerException("Bad LV name: %s" % lvName) 

146 type, id, val = parts 

147 if readFile: 

148 # For clone and leaf journals, additional 

149 # data is written inside file 

150 # TODO: Remove dependency on journal type 

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

152 fullPath = self.lvmCache._getPath(lvName) 

153 self.lvmCache.activateNoRefcount(lvName, False) 

154 journal_file = open_file(fullPath) 

155 try: 

156 try: 

157 min_block_size = get_min_blk_size_wrapper(journal_file) 

158 data = file_read_wrapper(journal_file, 0, min_block_size, min_block_size) 

159 length, val = data.split(" ", 1) 

160 val = val[:int(length)] 

161 except: 

162 raise JournalerException("Failed to read from journal %s" \ 

163 % lvName) 

164 finally: 

165 journal_file.close() 

166 self.lvmCache.deactivateNoRefcount(lvName) 

167 if not entries.get(type): 

168 entries[type] = dict() 

169 entries[type][id] = val 

170 return entries 

171 

172 def _getLVMapperName(self, lvName): 

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

174 

175########################################################################### 

176# 

177# Unit tests 

178# 

179import lvutil 

180import lvmcache 

181 

182 

183def _runTests(vgName): 

184 """Unit testing""" 

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

186 if not vgName: 

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

188 return 1 

189 if not lvutil._checkVG(vgName): 

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

191 return 1 

192 

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

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

195 print("get non-existing failed") 

196 return 1 

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

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

199 if val != "a": 

200 print("create-get failed") 

201 return 1 

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

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

204 print("remove failed") 

205 return 1 

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

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

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

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

210 if val != "831_3": 

211 print("create underscore_val failed") 

212 return 1 

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

214 if val != "53_0": 

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

216 return 1 

217 entries = j.getAll("modify") 

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

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

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

221 return 1 

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

223 val = j.getAll("modify") 

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

225 print("remove(X) failed") 

226 return 1 

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

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

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

230 print("remove(Y) failed") 

231 return 1 

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

233 print("remove(Z) failed") 

234 return 1 

235 print("All tests passed") 

236 return 0 

237 

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

239 import sys 

240 vgName = None 

241 if len(sys.argv) > 1: 

242 vgName = sys.argv[1] 

243 ret = _runTests(vgName) 

244 sys.exit(ret)