]>
Commit | Line | Data |
---|---|---|
9f95a23c | 1 | # type: ignore |
9f95a23c | 2 | |
f6b5b4d7 TL |
3 | import json |
4 | import logging | |
9f95a23c TL |
5 | import os |
6 | ||
7 | if 'UNITTEST' in os.environ: | |
8 | ||
9 | # Mock ceph_module. Otherwise every module that is involved in a testcase and imports it will | |
10 | # raise an ImportError | |
11 | ||
12 | import sys | |
13 | ||
14 | try: | |
15 | from unittest import mock | |
16 | except ImportError: | |
17 | import mock | |
18 | ||
e306af50 TL |
19 | M_classes = set() |
20 | ||
9f95a23c | 21 | class M(object): |
f6b5b4d7 TL |
22 | """ |
23 | Note that: | |
e306af50 | 24 | |
f6b5b4d7 TL |
25 | * self.set_store() populates self._store |
26 | * self.set_module_option() populates self._store[module_name] | |
27 | * self.get(thing) comes from self._store['_ceph_get' + thing] | |
28 | ||
29 | """ | |
30 | ||
31 | def mock_store_get(self, kind, key, default): | |
32 | if not hasattr(self, '_store'): | |
33 | self._store = {} | |
34 | return self._store.get(f'mock_store/{kind}/{key}', default) | |
35 | ||
36 | def mock_store_set(self, kind, key, value): | |
37 | if not hasattr(self, '_store'): | |
38 | self._store = {} | |
39 | k = f'mock_store/{kind}/{key}' | |
40 | if value is None: | |
e306af50 TL |
41 | if k in self._store: |
42 | del self._store[k] | |
43 | else: | |
f6b5b4d7 | 44 | self._store[k] = value |
e306af50 | 45 | |
522d829b | 46 | def mock_store_prefix(self, kind, prefix): |
f6b5b4d7 TL |
47 | if not hasattr(self, '_store'): |
48 | self._store = {} | |
49 | full_prefix = f'mock_store/{kind}/{prefix}' | |
50 | kind_len = len(f'mock_store/{kind}/') | |
e306af50 | 51 | return { |
f6b5b4d7 TL |
52 | k[kind_len:]: v for k, v in self._store.items() |
53 | if k.startswith(full_prefix) | |
e306af50 TL |
54 | } |
55 | ||
f6b5b4d7 TL |
56 | def _ceph_get_store(self, k): |
57 | return self.mock_store_get('store', k, None) | |
58 | ||
59 | def _ceph_set_store(self, k, v): | |
60 | self.mock_store_set('store', k, v) | |
61 | ||
62 | def _ceph_get_store_prefix(self, prefix): | |
522d829b | 63 | return self.mock_store_prefix('store', prefix) |
f6b5b4d7 TL |
64 | |
65 | def _ceph_get_module_option(self, module, key, localized_prefix= None): | |
66 | try: | |
67 | _, val, _ = self.check_mon_command({ | |
68 | 'prefix': 'config get', | |
69 | 'who': 'mgr', | |
70 | 'key': f'mgr/{module}/{key}' | |
71 | }) | |
72 | except FileNotFoundError: | |
73 | val = None | |
74 | mo = [o for o in self.MODULE_OPTIONS if o['name'] == key] | |
f67539c2 TL |
75 | if len(mo) >= 1: # >= 1, cause self.MODULE_OPTIONS. otherwise it |
76 | # fails when importing multiple modules. | |
77 | if 'default' in mo and val is None: | |
78 | val = mo[0]['default'] | |
f91f0fd5 TL |
79 | if val is not None: |
80 | cls = { | |
81 | 'str': str, | |
82 | 'secs': int, | |
83 | 'bool': lambda s: bool(s) and s != 'false' and s != 'False', | |
84 | 'int': int, | |
85 | }[mo[0].get('type', 'str')] | |
86 | return cls(val) | |
87 | return val | |
88 | else: | |
89 | return val if val is not None else '' | |
e306af50 TL |
90 | |
91 | def _ceph_set_module_option(self, module, key, val): | |
f6b5b4d7 TL |
92 | _, _, _ = self.check_mon_command({ |
93 | 'prefix': 'config set', | |
94 | 'who': 'mgr', | |
95 | 'name': f'mgr/{module}/{key}', | |
96 | 'value': val | |
97 | }) | |
98 | return val | |
99 | ||
100 | def _ceph_get(self, data_name): | |
101 | return self.mock_store_get('_ceph_get', data_name, mock.MagicMock()) | |
102 | ||
cd265ab1 | 103 | def _ceph_send_command(self, res, svc_type, svc_id, command, tag, inbuf): |
f67539c2 | 104 | |
f6b5b4d7 | 105 | cmd = json.loads(command) |
f67539c2 | 106 | getattr(self, '_mon_commands_sent', []).append(cmd) |
f6b5b4d7 TL |
107 | |
108 | # Mocking the config store is handy sometimes: | |
109 | def config_get(): | |
110 | who = cmd['who'].split('.') | |
111 | whos = ['global'] + ['.'.join(who[:i+1]) for i in range(len(who))] | |
112 | for attepmt in reversed(whos): | |
113 | val = self.mock_store_get('config', f'{attepmt}/{cmd["key"]}', None) | |
114 | if val is not None: | |
115 | return val | |
116 | return None | |
117 | ||
118 | def config_set(): | |
119 | self.mock_store_set('config', f'{cmd["who"]}/{cmd["name"]}', cmd['value']) | |
120 | return '' | |
121 | ||
f67539c2 TL |
122 | def config_rm(): |
123 | self.mock_store_set('config', f'{cmd["who"]}/{cmd["name"]}', None) | |
124 | return '' | |
125 | ||
f6b5b4d7 TL |
126 | def config_dump(): |
127 | r = [] | |
522d829b | 128 | for prefix, value in self.mock_store_prefix('config', '').items(): |
f6b5b4d7 TL |
129 | section, name = prefix.split('/', 1) |
130 | r.append({ | |
131 | 'name': name, | |
132 | 'section': section, | |
133 | 'value': value | |
134 | }) | |
135 | return json.dumps(r) | |
136 | ||
137 | outb = '' | |
138 | if cmd['prefix'] == 'config get': | |
139 | outb = config_get() | |
140 | elif cmd['prefix'] == 'config set': | |
141 | outb = config_set() | |
142 | elif cmd['prefix'] == 'config dump': | |
143 | outb = config_dump() | |
f67539c2 TL |
144 | elif cmd['prefix'] == 'config rm': |
145 | outb = config_rm() | |
f6b5b4d7 TL |
146 | elif hasattr(self, '_mon_command_mock_' + cmd['prefix'].replace(' ', '_')): |
147 | a = getattr(self, '_mon_command_mock_' + cmd['prefix'].replace(' ', '_')) | |
148 | outb = a(cmd) | |
149 | ||
150 | res.complete(0, outb, '') | |
151 | ||
f67539c2 TL |
152 | def _ceph_get_foreign_option(self, entity, name): |
153 | who = entity.split('.') | |
154 | whos = ['global'] + ['.'.join(who[:i+1]) for i in range(len(who))] | |
155 | for attepmt in reversed(whos): | |
156 | val = self.mock_store_get('config', f'{attepmt}/{name}', None) | |
157 | if val is not None: | |
158 | return val | |
159 | return None | |
160 | ||
161 | def assert_issued_mon_command(self, command): | |
162 | assert command in self._mon_commands_sent, self._mon_commands_sent | |
163 | ||
f6b5b4d7 TL |
164 | @property |
165 | def _logger(self): | |
166 | return logging.getLogger(__name__) | |
167 | ||
168 | @_logger.setter | |
169 | def _logger(self, _): | |
170 | pass | |
e306af50 | 171 | |
9f95a23c | 172 | def __init__(self, *args): |
f67539c2 | 173 | self._mon_commands_sent = [] |
f6b5b4d7 TL |
174 | if not hasattr(self, '_store'): |
175 | self._store = {} | |
176 | ||
e306af50 | 177 | |
a4b75251 | 178 | if self.__class__ not in M_classes: |
cd265ab1 | 179 | # call those only once. |
e306af50 TL |
180 | self._register_commands('') |
181 | self._register_options('') | |
a4b75251 | 182 | M_classes.add(self.__class__) |
e306af50 | 183 | |
9f95a23c TL |
184 | super(M, self).__init__() |
185 | self._ceph_get_version = mock.Mock() | |
b3b6e05e | 186 | self._ceph_get_ceph_conf_path = mock.MagicMock() |
9f95a23c | 187 | self._ceph_get_option = mock.MagicMock() |
e306af50 TL |
188 | self._ceph_get_context = mock.MagicMock() |
189 | self._ceph_register_client = mock.MagicMock() | |
f6b5b4d7 | 190 | self._ceph_set_health_checks = mock.MagicMock() |
9f95a23c TL |
191 | self._configure_logging = lambda *_: None |
192 | self._unconfigure_logging = mock.MagicMock() | |
193 | self._ceph_log = mock.MagicMock() | |
9f95a23c | 194 | self._ceph_dispatch_remote = lambda *_: None |
f91f0fd5 | 195 | self._ceph_get_mgr_id = mock.MagicMock() |
9f95a23c TL |
196 | |
197 | ||
198 | cm = mock.Mock() | |
199 | cm.BaseMgrModule = M | |
200 | cm.BaseMgrStandbyModule = M | |
201 | sys.modules['ceph_module'] = cm | |
e306af50 TL |
202 | |
203 | def mock_ceph_modules(): | |
204 | class MockRadosError(Exception): | |
205 | def __init__(self, message, errno=None): | |
206 | super(MockRadosError, self).__init__(message) | |
207 | self.errno = errno | |
208 | ||
209 | def __str__(self): | |
210 | msg = super(MockRadosError, self).__str__() | |
211 | if self.errno is None: | |
212 | return msg | |
213 | return '[errno {0}] {1}'.format(self.errno, msg) | |
214 | ||
a4b75251 TL |
215 | class MockObjectNotFound(Exception): |
216 | pass | |
e306af50 TL |
217 | |
218 | sys.modules.update({ | |
a4b75251 TL |
219 | 'rados': mock.MagicMock( |
220 | Error=MockRadosError, | |
221 | OSError=MockRadosError, | |
222 | ObjectNotFound=MockObjectNotFound), | |
e306af50 TL |
223 | 'rbd': mock.Mock(), |
224 | 'cephfs': mock.Mock(), | |
f67539c2 TL |
225 | }) |
226 | ||
227 | # Unconditionally mock the rados objects when we're imported | |
228 | mock_ceph_modules() # type: ignore |