Coverage for drivers/journaler.py : 15%

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
21import xs_errors
22from srmetadata import open_file, file_read_wrapper, file_write_wrapper
24LVM_MAX_NAME_LEN = 127
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."""
32 LV_SIZE = 4 * 1024 * 1024 # minimum size
33 LV_TAG = "journaler"
34 SEPARATOR = "_"
35 JRN_CLONE = "clone"
36 JRN_LEAF = "leaf"
38 def __init__(self, lvmCache):
39 self.vgName = lvmCache.vgName
40 self.lvmCache = lvmCache
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)
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
58 self.lvmCache.create(lvName, self.LV_SIZE, self.LV_TAG)
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)
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)
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)
102 mapperDevice = self._getLVMapperName(lvName)
103 if len(mapperDevice) > LVM_MAX_NAME_LEN:
104 lvName = self._getNameLV(type, id)
105 self.lvmCache.remove(lvName)
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)
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]
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
131 def _getNameLV(self, type, id, val=1):
132 return "%s%s%s%s%s" % (type, self.SEPARATOR, id, self.SEPARATOR, val)
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
167 def _getLVMapperName(self, lvName):
168 return '%s-%s' % (self.vgName.replace("-", "--"), lvName)
170###########################################################################
171#
172# Unit tests
173#
174import lvutil
175import lvmcache
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
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
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)