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/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# 

17 

18 

19from linstorvolumemanager import \ 

20 get_controller_uri, LinstorVolumeManager, LinstorVolumeManagerError 

21import linstor 

22import re 

23import util 

24 

25 

26class LinstorJournalerError(Exception): 

27 pass 

28 

29# ============================================================================== 

30 

31 

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 """ 

38 

39 REG_TYPE = re.compile('^([^/]+)$') 

40 REG_TRANSACTION = re.compile('^[^/]+/([^/]+)$') 

41 

42 """ 

43 Types of transaction in the journal. 

44 """ 

45 CLONE = 'clone' 

46 INFLATE = 'inflate' 

47 ZERO = 'zero' 

48 

49 @staticmethod 

50 def default_logger(*args): 

51 print(args) 

52 

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 ) 

61 

62 def create(self, type, identifier, value): 

63 # TODO: Maybe rename to 'add' in the future (in Citrix code too). 

64 

65 key = self._get_key(type, identifier) 

66 

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 ) 

74 

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 ) 

90 

91 raise LinstorJournalerError( 

92 'Failed to write to journal: {}'.format(e) 

93 ) 

94 

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 ) 

109 

110 def get(self, type, identifier): 

111 self._reset_namespace() 

112 return self._journal.get(self._get_key(type, identifier)) 

113 

114 def get_all(self, type): 

115 entries = {} 

116 

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 

124 

125 # Added to compatibility with Citrix API. 

126 def getAll(self, type): 

127 return self.get_all(type) 

128 

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 

138 

139 # Added to compatibility with Citrix API. 

140 def hasJournals(self, identifier): 

141 return self.has_entries(identifier) 

142 

143 def _reset_namespace(self): 

144 self._journal.namespace = self._namespace 

145 

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 ) 

160 

161 try: 

162 return connect(uri) 

163 except (linstor.errors.LinstorNetworkError, LinstorVolumeManagerError): 

164 pass 

165 

166 return util.retry( 

167 lambda: connect(None), 

168 maxretry=10, 

169 exceptions=[ 

170 linstor.errors.LinstorNetworkError, LinstorVolumeManagerError 

171 ] 

172 ) 

173 

174 @staticmethod 

175 def _get_key(type, identifier): 

176 return '{}/{}'.format(type, identifier)