Coverage for drivers/linstorjournaler.py : 24%

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/env python3
2#
3# Copyright (C) 2020 Vates SAS - ronan.abhamon@vates.fr
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
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 General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program. If not, see <https://www.gnu.org/licenses/>.
16#
19from linstorvolumemanager import \
20 get_controller_uri, LinstorVolumeManager, LinstorVolumeManagerError
21import linstor
22import re
23import util
26class LinstorJournalerError(Exception):
27 pass
29# ==============================================================================
32class LinstorJournaler:
33 """
34 Simple journaler that uses LINSTOR properties for persistent "storage".
35 A journal is a id-value pair, and there can be only one journal for a
36 given id. An identifier is juste a transaction name.
37 """
39 REG_TYPE = re.compile('^([^/]+)$')
40 REG_TRANSACTION = re.compile('^[^/]+/([^/]+)$')
42 """
43 Types of transaction in the journal.
44 """
45 CLONE = 'clone'
46 INFLATE = 'inflate'
47 ZERO = 'zero'
49 @staticmethod
50 def default_logger(*args):
51 print(args)
53 def __init__(self, uri, group_name, logger=default_logger.__func__):
54 self._namespace = '{}journal/'.format(
55 LinstorVolumeManager._build_sr_namespace()
56 )
57 self._logger = logger
58 self._journal = self._create_journal_instance(
59 uri, group_name, self._namespace
60 )
62 def create(self, type, identifier, value):
63 # TODO: Maybe rename to 'add' in the future (in Citrix code too).
65 key = self._get_key(type, identifier)
67 # 1. Ensure transaction doesn't exist.
68 current_value = self.get(type, identifier)
69 if current_value is not None:
70 raise LinstorJournalerError(
71 'Journal transaction already exists for \'{}:{}\': {}'
72 .format(type, identifier, current_value)
73 )
75 # 2. Write!
76 try:
77 self._reset_namespace()
78 self._logger(
79 'Create journal transaction \'{}:{}\''.format(type, identifier)
80 )
81 self._journal[key] = str(value)
82 except Exception as e:
83 try:
84 self._journal.pop(key, 'empty')
85 except Exception as e2:
86 self._logger(
87 'Failed to clean up failed journal write: {} (Ignored)'
88 .format(e2)
89 )
91 raise LinstorJournalerError(
92 'Failed to write to journal: {}'.format(e)
93 )
95 def remove(self, type, identifier):
96 key = self._get_key(type, identifier)
97 try:
98 self._reset_namespace()
99 self._logger(
100 'Destroy journal transaction \'{}:{}\''
101 .format(type, identifier)
102 )
103 self._journal.pop(key)
104 except Exception as e:
105 raise LinstorJournalerError(
106 'Failed to remove transaction \'{}:{}\': {}'
107 .format(type, identifier, e)
108 )
110 def get(self, type, identifier):
111 self._reset_namespace()
112 return self._journal.get(self._get_key(type, identifier))
114 def get_all(self, type):
115 entries = {}
117 self._journal.namespace = self._namespace + '{}/'.format(type)
118 for (key, value) in self._journal.items():
119 res = self.REG_TYPE.match(key)
120 if res:
121 identifier = res.groups()[0]
122 entries[identifier] = value
123 return entries
125 # Added to compatibility with Citrix API.
126 def getAll(self, type):
127 return self.get_all(type)
129 def has_entries(self, identifier):
130 self._reset_namespace()
131 for (key, value) in self._journal.items():
132 res = self.REG_TRANSACTION.match(key)
133 if res:
134 current_identifier = res.groups()[0]
135 if current_identifier == identifier:
136 return True
137 return False
139 # Added to compatibility with Citrix API.
140 def hasJournals(self, identifier):
141 return self.has_entries(identifier)
143 def _reset_namespace(self):
144 self._journal.namespace = self._namespace
146 @classmethod
147 def _create_journal_instance(cls, uri, group_name, namespace):
148 def connect(uri):
149 if not uri:
150 uri = get_controller_uri()
151 if not uri:
152 raise LinstorVolumeManagerError(
153 'Unable to find controller uri...'
154 )
155 return linstor.KV(
156 LinstorVolumeManager._build_group_name(group_name),
157 uri=uri,
158 namespace=namespace
159 )
161 try:
162 return connect(uri)
163 except (linstor.errors.LinstorNetworkError, LinstorVolumeManagerError):
164 pass
166 return util.retry(
167 lambda: connect(None),
168 maxretry=10,
169 exceptions=[
170 linstor.errors.LinstorNetworkError, LinstorVolumeManagerError
171 ]
172 )
174 @staticmethod
175 def _get_key(type, identifier):
176 return '{}/{}'.format(type, identifier)