Coverage for drivers/linstorjournaler.py : 23%

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'
48 @staticmethod
49 def default_logger(*args):
50 print(args)
52 def __init__(self, uri, group_name, logger=default_logger.__func__):
53 self._namespace = '{}journal/'.format(
54 LinstorVolumeManager._build_sr_namespace()
55 )
56 self._logger = logger
57 self._journal = self._create_journal_instance(
58 uri, group_name, self._namespace
59 )
61 def create(self, type, identifier, value):
62 # TODO: Maybe rename to 'add' in the future (in Citrix code too).
64 key = self._get_key(type, identifier)
66 # 1. Ensure transaction doesn't exist.
67 current_value = self.get(type, identifier)
68 if current_value is not None:
69 raise LinstorJournalerError(
70 'Journal transaction already exists for \'{}:{}\': {}'
71 .format(type, identifier, current_value)
72 )
74 # 2. Write!
75 try:
76 self._reset_namespace()
77 self._logger(
78 'Create journal transaction \'{}:{}\''.format(type, identifier)
79 )
80 self._journal[key] = str(value)
81 except Exception as e:
82 try:
83 self._journal.pop(key, 'empty')
84 except Exception as e2:
85 self._logger(
86 'Failed to clean up failed journal write: {} (Ignored)'
87 .format(e2)
88 )
90 raise LinstorJournalerError(
91 'Failed to write to journal: {}'.format(e)
92 )
94 def remove(self, type, identifier):
95 key = self._get_key(type, identifier)
96 try:
97 self._reset_namespace()
98 self._logger(
99 'Destroy journal transaction \'{}:{}\''
100 .format(type, identifier)
101 )
102 self._journal.pop(key)
103 except Exception as e:
104 raise LinstorJournalerError(
105 'Failed to remove transaction \'{}:{}\': {}'
106 .format(type, identifier, e)
107 )
109 def get(self, type, identifier):
110 self._reset_namespace()
111 return self._journal.get(self._get_key(type, identifier))
113 def get_all(self, type):
114 entries = {}
116 self._journal.namespace = self._namespace + '{}/'.format(type)
117 for (key, value) in self._journal.items():
118 res = self.REG_TYPE.match(key)
119 if res:
120 identifier = res.groups()[0]
121 entries[identifier] = value
122 return entries
124 # Added to compatibility with Citrix API.
125 def getAll(self, type):
126 return self.get_all(type)
128 def has_entries(self, identifier):
129 self._reset_namespace()
130 for (key, value) in self._journal.items():
131 res = self.REG_TRANSACTION.match(key)
132 if res:
133 current_identifier = res.groups()[0]
134 if current_identifier == identifier:
135 return True
136 return False
138 # Added to compatibility with Citrix API.
139 def hasJournals(self, identifier):
140 return self.has_entries(identifier)
142 def _reset_namespace(self):
143 self._journal.namespace = self._namespace
145 @classmethod
146 def _create_journal_instance(cls, uri, group_name, namespace):
147 def connect(uri):
148 if not uri:
149 uri = get_controller_uri()
150 if not uri:
151 raise LinstorVolumeManagerError(
152 'Unable to find controller uri...'
153 )
154 return linstor.KV(
155 LinstorVolumeManager._build_group_name(group_name),
156 uri=uri,
157 namespace=namespace
158 )
160 try:
161 return connect(uri)
162 except (linstor.errors.LinstorNetworkError, LinstorVolumeManagerError):
163 pass
165 return util.retry(
166 lambda: connect(None),
167 maxretry=10,
168 exceptions=[
169 linstor.errors.LinstorNetworkError, LinstorVolumeManagerError
170 ]
171 )
173 @staticmethod
174 def _get_key(type, identifier):
175 return '{}/{}'.format(type, identifier)