]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/orchestrator/tests/test_orchestrator.py
import 15.2.9
[ceph.git] / ceph / src / pybind / mgr / orchestrator / tests / test_orchestrator.py
1 from __future__ import absolute_import
2
3 import json
4
5 import pytest
6 import yaml
7
8 from ceph.deployment.service_spec import ServiceSpec
9 from ceph.deployment import inventory
10 from ceph.utils import datetime_now
11
12 from test_orchestrator import TestOrchestrator as _TestOrchestrator
13 from tests import mock
14
15 from orchestrator import raise_if_exception, Completion, ProgressReference
16 from orchestrator import InventoryHost, DaemonDescription, ServiceDescription
17 from orchestrator import OrchestratorValidationError
18 from orchestrator.module import to_format, preview_table_osd
19
20
21 def _test_resource(data, resource_class, extra=None):
22 # ensure we can deserialize and serialize
23 rsc = resource_class.from_json(data)
24 rsc.to_json()
25
26 if extra:
27 # if there is an unexpected data provided
28 data.update(extra)
29 with pytest.raises(OrchestratorValidationError):
30 resource_class.from_json(data)
31
32
33 def test_inventory():
34 json_data = {
35 'name': 'host0',
36 'addr': '1.2.3.4',
37 'devices': [
38 {
39 'sys_api': {
40 'rotational': '1',
41 'size': 1024,
42 },
43 'path': '/dev/sda',
44 'available': False,
45 'rejected_reasons': [],
46 'lvs': []
47 }
48 ]
49 }
50 _test_resource(json_data, InventoryHost, {'abc': False})
51 for devices in json_data['devices']:
52 _test_resource(devices, inventory.Device)
53
54 json_data = [{}, {'name': 'host0', 'addr': '1.2.3.4'}, {'devices': []}]
55 for data in json_data:
56 with pytest.raises(OrchestratorValidationError):
57 InventoryHost.from_json(data)
58
59
60 def test_daemon_description():
61 json_data = {
62 'hostname': 'test',
63 'daemon_type': 'mon',
64 'daemon_id': 'a'
65 }
66 _test_resource(json_data, DaemonDescription, {'abc': False})
67
68
69 def test_raise():
70 c = Completion()
71 c._exception = ZeroDivisionError()
72 with pytest.raises(ZeroDivisionError):
73 raise_if_exception(c)
74
75
76 def test_promise():
77 p = Completion(value=3)
78 p.finalize()
79 assert p.result == 3
80
81
82 def test_promise_then():
83 p = Completion(value=3).then(lambda three: three + 1)
84 p.finalize()
85 assert p.result == 4
86
87
88 def test_promise_mondatic_then():
89 p = Completion(value=3)
90 p.then(lambda three: Completion(value=three + 1))
91 p.finalize()
92 assert p.result == 4
93
94
95 def some_complex_completion():
96 c = Completion(value=3).then(
97 lambda three: Completion(value=three + 1).then(
98 lambda four: four + 1))
99 return c
100
101
102 def test_promise_mondatic_then_combined():
103 p = some_complex_completion()
104 p.finalize()
105 assert p.result == 5
106
107
108 def test_promise_flat():
109 p = Completion()
110 p.then(lambda r1: Completion(value=r1 + ' there').then(
111 lambda r11: r11 + '!'))
112 p.finalize('hello')
113 assert p.result == 'hello there!'
114
115
116 def test_side_effect():
117 foo = {'x': 1}
118
119 def run(x):
120 foo['x'] = x
121
122 foo['x'] = 1
123 Completion(value=3).then(run).finalize()
124 assert foo['x'] == 3
125
126
127 def test_progress():
128 c = some_complex_completion()
129 mgr = mock.MagicMock()
130 mgr.process = lambda cs: [c.finalize(None) for c in cs]
131
132 progress_val = 0.75
133 c._last_promise().then(
134 on_complete=ProgressReference(message='hello world',
135 mgr=mgr,
136 completion=lambda: Completion(
137 on_complete=lambda _: progress_val))
138 )
139 mgr.remote.assert_called_with('progress', 'update', c.progress_reference.progress_id, 'hello world', 0.0, [
140 ('origin', 'orchestrator')])
141
142 c.finalize()
143 mgr.remote.assert_called_with('progress', 'complete', c.progress_reference.progress_id)
144
145 c.progress_reference.update()
146 mgr.remote.assert_called_with('progress', 'update', c.progress_reference.progress_id,
147 'hello world', progress_val, [('origin', 'orchestrator')])
148 assert not c.progress_reference.effective
149
150 progress_val = 1
151 c.progress_reference.update()
152 assert c.progress_reference.effective
153 mgr.remote.assert_called_with('progress', 'complete', c.progress_reference.progress_id)
154
155
156 def test_with_progress():
157 mgr = mock.MagicMock()
158 mgr.process = lambda cs: [c.finalize(None) for c in cs]
159
160 def execute(y):
161 return str(y)
162
163 def run(x):
164 def two(_):
165 return execute(x * 2)
166
167 return Completion.with_progress(
168 message='message',
169 on_complete=two,
170 mgr=mgr
171
172 )
173 c = Completion(on_complete=lambda x: x * 10).then(run)._first_promise
174 c.finalize(2)
175 assert c.result == '40'
176 c.progress_reference.update()
177 assert c.progress_reference.effective
178
179
180 def test_exception():
181
182 def run(x):
183 raise KeyError(x)
184
185 c = Completion(value=3).then(run)
186 c.finalize()
187 with pytest.raises(KeyError):
188 raise_if_exception(c)
189
190
191 def test_fail():
192 c = Completion().then(lambda _: 3)
193 c._first_promise.fail(KeyError())
194 assert isinstance(c.exception, KeyError)
195
196 with pytest.raises(ValueError,
197 match='Invalid State: called fail, but Completion is already finished: {}'.format(
198 str(ZeroDivisionError()))):
199 c._first_promise.fail(ZeroDivisionError())
200
201
202 def test_pretty_print():
203 mgr = mock.MagicMock()
204 mgr.process = lambda cs: [c.finalize(None) for c in cs]
205
206 def add_one(x):
207 return x+1
208
209 c = Completion(value=1, on_complete=add_one).then(
210 str
211 ).add_progress('message', mgr)
212
213 assert c.pretty_print() == """<Completion>[
214 add_one(1),
215 str(...),
216 ProgressReference(...),
217 ]"""
218 c.finalize()
219 assert c.pretty_print() == """<Completion>[
220 (done) add_one(1),
221 (done) str(2),
222 (done) ProgressReference('2'),
223 ]"""
224
225 p = some_complex_completion()
226 assert p.pretty_print() == """<Completion>[
227 <lambda>(3),
228 lambda x: x(...),
229 ]"""
230 p.finalize()
231 assert p.pretty_print() == """<Completion>[
232 (done) <lambda>(3),
233 (done) <lambda>(4),
234 (done) lambda x: x(5),
235 (done) lambda x: x(5),
236 ]"""
237
238 assert p.result == 5
239
240
241 def test_apply():
242 to = _TestOrchestrator('', 0, 0)
243 completion = to.apply([
244 ServiceSpec(service_type='nfs'),
245 ServiceSpec(service_type='nfs'),
246 ServiceSpec(service_type='nfs'),
247 ])
248 completion.finalize(42)
249 assert completion.result == [None, None, None]
250
251
252 def test_yaml():
253 y = """daemon_type: crash
254 daemon_id: ubuntu
255 hostname: ubuntu
256 status: 1
257 status_desc: starting
258 is_active: false
259 events:
260 - 2020-06-10T10:08:22.933241Z daemon:crash.ubuntu [INFO] "Deployed crash.ubuntu on
261 host 'ubuntu'"
262 ---
263 service_type: crash
264 service_name: crash
265 placement:
266 host_pattern: '*'
267 status:
268 container_image_id: 74803e884bea289d2d2d3ebdf6d37cd560499e955595695b1390a89800f4e37a
269 container_image_name: docker.io/ceph/daemon-base:latest-master-devel
270 created: '2020-06-10T10:37:31.051288Z'
271 last_refresh: '2020-06-10T10:57:40.715637Z'
272 running: 1
273 size: 1
274 events:
275 - 2020-06-10T10:37:31.139159Z service:crash [INFO] "service was created"
276 """
277 types = (DaemonDescription, ServiceDescription)
278
279 for y, cls in zip(y.split('---\n'), types):
280 data = yaml.safe_load(y)
281 object = cls.from_json(data)
282
283 assert to_format(object, 'yaml', False, cls) == y
284 assert to_format([object], 'yaml', True, cls) == y
285
286 j = json.loads(to_format(object, 'json', False, cls))
287 assert to_format(cls.from_json(j), 'yaml', False, cls) == y
288
289
290 def test_event_multiline():
291 from .._interface import OrchestratorEvent
292 e = OrchestratorEvent(datetime_now(), 'service', 'subject', 'ERROR', 'message')
293 assert OrchestratorEvent.from_json(e.to_json()) == e
294
295 e = OrchestratorEvent(datetime_now(), 'service',
296 'subject', 'ERROR', 'multiline\nmessage')
297 assert OrchestratorEvent.from_json(e.to_json()) == e
298
299
300 def test_preview_table_osd_smoke():
301 data = [
302 {
303 'service_type': 'osd',
304 'data':
305 {
306 'foo host':
307 [
308 {
309 'osdspec': 'foo',
310 'error': '',
311 'data':
312 [
313 {
314 "block_db": "/dev/nvme0n1",
315 "block_db_size": "66.67 GB",
316 "data": "/dev/sdb",
317 "data_size": "300.00 GB",
318 "encryption": "None"
319 },
320 {
321 "block_db": "/dev/nvme0n1",
322 "block_db_size": "66.67 GB",
323 "data": "/dev/sdc",
324 "data_size": "300.00 GB",
325 "encryption": "None"
326 },
327 {
328 "block_db": "/dev/nvme0n1",
329 "block_db_size": "66.67 GB",
330 "data": "/dev/sdd",
331 "data_size": "300.00 GB",
332 "encryption": "None"
333 }
334 ]
335 }
336 ]
337 }
338 }
339 ]
340 preview_table_osd(data)