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