Coverage for drivers/journaler.py : 16%

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
20import util
21from srmetadata import open_file, get_min_blk_size_wrapper, \
22 file_read_wrapper, file_write_wrapper
24LVM_MAX_NAME_LEN = 127
27class JournalerException(util.SMException):
28 pass
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."""
36 LV_SIZE = 4 * 1024 * 1024 # minimum size
37 LV_TAG = "journaler"
38 SEPARATOR = "_"
39 JRN_CLONE = "clone"
40 JRN_LEAF = "leaf"
42 def __init__(self, lvmCache):
43 self.vgName = lvmCache.vgName
44 self.lvmCache = lvmCache
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)
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
63 self.lvmCache.create(lvName, self.LV_SIZE, self.LV_TAG)
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)
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)
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)
107 mapperDevice = self._getLVMapperName(lvName)
108 if len(mapperDevice) > LVM_MAX_NAME_LEN:
109 lvName = self._getNameLV(type, id)
110 self.lvmCache.remove(lvName)
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)
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]
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
136 def _getNameLV(self, type, id, val=1):
137 return "%s%s%s%s%s" % (type, self.SEPARATOR, id, self.SEPARATOR, val)
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
172 def _getLVMapperName(self, lvName):
173 return '%s-%s' % (self.vgName.replace("-", "--"), lvName)
175###########################################################################
176#
177# Unit tests
178#
179import lvutil
180import lvmcache
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
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
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)