]> git.proxmox.com Git - ceph.git/blob - ceph/src/cephadm/tests/test_cephadm.py
import ceph quincy 17.2.1
[ceph.git] / ceph / src / cephadm / tests / test_cephadm.py
1 # type: ignore
2
3 import errno
4 import json
5 import mock
6 import os
7 import pytest
8 import socket
9 import unittest
10 from textwrap import dedent
11
12 from .fixtures import (
13 cephadm_fs,
14 mock_docker,
15 mock_podman,
16 with_cephadm_ctx,
17 mock_bad_firewalld,
18 )
19
20 with mock.patch('builtins.open', create=True):
21 from importlib.machinery import SourceFileLoader
22 cd = SourceFileLoader('cephadm', 'cephadm').load_module()
23
24
25 def get_ceph_conf(
26 fsid='00000000-0000-0000-0000-0000deadbeef',
27 mon_host='[v2:192.168.1.1:3300/0,v1:192.168.1.1:6789/0]'):
28 return f'''
29 # minimal ceph.conf for {fsid}
30 [global]
31 fsid = {fsid}
32 mon_host = {mon_host}
33 '''
34
35 class TestCephAdm(object):
36
37 def test_docker_unit_file(self):
38 ctx = cd.CephadmContext()
39 ctx.container_engine = mock_docker()
40 r = cd.get_unit_file(ctx, '9b9d7609-f4d5-4aba-94c8-effa764d96c9')
41 assert 'Requires=docker.service' in r
42 ctx.container_engine = mock_podman()
43 r = cd.get_unit_file(ctx, '9b9d7609-f4d5-4aba-94c8-effa764d96c9')
44 assert 'Requires=docker.service' not in r
45
46 @mock.patch('cephadm.logger')
47 def test_attempt_bind(self, logger):
48 ctx = None
49 address = None
50 port = 0
51
52 def os_error(errno):
53 _os_error = OSError()
54 _os_error.errno = errno
55 return _os_error
56
57 for side_effect, expected_exception in (
58 (os_error(errno.EADDRINUSE), cd.PortOccupiedError),
59 (os_error(errno.EAFNOSUPPORT), cd.Error),
60 (os_error(errno.EADDRNOTAVAIL), cd.Error),
61 (None, None),
62 ):
63 _socket = mock.Mock()
64 _socket.bind.side_effect = side_effect
65 try:
66 cd.attempt_bind(ctx, _socket, address, port)
67 except Exception as e:
68 assert isinstance(e, expected_exception)
69 else:
70 if expected_exception is not None:
71 assert False
72
73 @mock.patch('cephadm.attempt_bind')
74 @mock.patch('cephadm.logger')
75 def test_port_in_use(self, logger, attempt_bind):
76 empty_ctx = None
77
78 assert cd.port_in_use(empty_ctx, 9100) == False
79
80 attempt_bind.side_effect = cd.PortOccupiedError('msg')
81 assert cd.port_in_use(empty_ctx, 9100) == True
82
83 os_error = OSError()
84 os_error.errno = errno.EADDRNOTAVAIL
85 attempt_bind.side_effect = os_error
86 assert cd.port_in_use(empty_ctx, 9100) == False
87
88 os_error = OSError()
89 os_error.errno = errno.EAFNOSUPPORT
90 attempt_bind.side_effect = os_error
91 assert cd.port_in_use(empty_ctx, 9100) == False
92
93 @mock.patch('socket.socket')
94 @mock.patch('cephadm.logger')
95 def test_check_ip_port_success(self, logger, _socket):
96 ctx = cd.CephadmContext()
97 ctx.skip_ping_check = False # enables executing port check with `check_ip_port`
98
99 for address, address_family in (
100 ('0.0.0.0', socket.AF_INET),
101 ('::', socket.AF_INET6),
102 ):
103 try:
104 cd.check_ip_port(ctx, cd.EndPoint(address, 9100))
105 except:
106 assert False
107 else:
108 assert _socket.call_args == mock.call(address_family, socket.SOCK_STREAM)
109
110 @mock.patch('socket.socket')
111 @mock.patch('cephadm.logger')
112 def test_check_ip_port_failure(self, logger, _socket):
113 ctx = cd.CephadmContext()
114 ctx.skip_ping_check = False # enables executing port check with `check_ip_port`
115
116 def os_error(errno):
117 _os_error = OSError()
118 _os_error.errno = errno
119 return _os_error
120
121 for address, address_family in (
122 ('0.0.0.0', socket.AF_INET),
123 ('::', socket.AF_INET6),
124 ):
125 for side_effect, expected_exception in (
126 (os_error(errno.EADDRINUSE), cd.PortOccupiedError),
127 (os_error(errno.EADDRNOTAVAIL), cd.Error),
128 (os_error(errno.EAFNOSUPPORT), cd.Error),
129 (None, None),
130 ):
131 mock_socket_obj = mock.Mock()
132 mock_socket_obj.bind.side_effect = side_effect
133 _socket.return_value = mock_socket_obj
134 try:
135 cd.check_ip_port(ctx, cd.EndPoint(address, 9100))
136 except Exception as e:
137 assert isinstance(e, expected_exception)
138 else:
139 if side_effect is not None:
140 assert False
141
142
143 def test_is_not_fsid(self):
144 assert not cd.is_fsid('no-uuid')
145
146 def test_is_fsid(self):
147 assert cd.is_fsid('e863154d-33c7-4350-bca5-921e0467e55b')
148
149 def test__get_parser_image(self):
150 args = cd._parse_args(['--image', 'foo', 'version'])
151 assert args.image == 'foo'
152
153 def test_parse_mem_usage(self):
154 cd.logger = mock.Mock()
155 len, summary = cd._parse_mem_usage(0, 'c6290e3f1489,-- / --')
156 assert summary == {}
157
158 def test_CustomValidation(self):
159 assert cd._parse_args(['deploy', '--name', 'mon.a', '--fsid', 'fsid'])
160
161 with pytest.raises(SystemExit):
162 cd._parse_args(['deploy', '--name', 'wrong', '--fsid', 'fsid'])
163
164 @pytest.mark.parametrize("test_input, expected", [
165 ("1.6.2", (1,6,2)),
166 ("1.6.2-stable2", (1,6,2)),
167 ])
168 def test_parse_podman_version(self, test_input, expected):
169 assert cd._parse_podman_version(test_input) == expected
170
171 def test_parse_podman_version_invalid(self):
172 with pytest.raises(ValueError) as res:
173 cd._parse_podman_version('inval.id')
174 assert 'inval' in str(res.value)
175
176 def test_is_ipv6(self):
177 cd.logger = mock.Mock()
178 for good in ("[::1]", "::1",
179 "fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"):
180 assert cd.is_ipv6(good)
181 for bad in ("127.0.0.1",
182 "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffg",
183 "1:2:3:4:5:6:7:8:9", "fd00::1::1", "[fg::1]"):
184 assert not cd.is_ipv6(bad)
185
186 def test_unwrap_ipv6(self):
187 def unwrap_test(address, expected):
188 assert cd.unwrap_ipv6(address) == expected
189
190 tests = [
191 ('::1', '::1'), ('[::1]', '::1'),
192 ('[fde4:8dba:82e1:0:5054:ff:fe6a:357]', 'fde4:8dba:82e1:0:5054:ff:fe6a:357'),
193 ('can actually be any string', 'can actually be any string'),
194 ('[but needs to be stripped] ', '[but needs to be stripped] ')]
195 for address, expected in tests:
196 unwrap_test(address, expected)
197
198 def test_wrap_ipv6(self):
199 def wrap_test(address, expected):
200 assert cd.wrap_ipv6(address) == expected
201
202 tests = [
203 ('::1', '[::1]'), ('[::1]', '[::1]'),
204 ('fde4:8dba:82e1:0:5054:ff:fe6a:357',
205 '[fde4:8dba:82e1:0:5054:ff:fe6a:357]'),
206 ('myhost.example.com', 'myhost.example.com'),
207 ('192.168.0.1', '192.168.0.1'),
208 ('', ''), ('fd00::1::1', 'fd00::1::1')]
209 for address, expected in tests:
210 wrap_test(address, expected)
211
212 @mock.patch('cephadm.Firewalld', mock_bad_firewalld)
213 @mock.patch('cephadm.logger')
214 def test_skip_firewalld(self, logger, cephadm_fs):
215 """
216 test --skip-firewalld actually skips changing firewall
217 """
218
219 ctx = cd.CephadmContext()
220 with pytest.raises(Exception):
221 cd.update_firewalld(ctx, 'mon')
222
223 ctx.skip_firewalld = True
224 cd.update_firewalld(ctx, 'mon')
225
226 ctx.skip_firewalld = False
227 with pytest.raises(Exception):
228 cd.update_firewalld(ctx, 'mon')
229
230 ctx = cd.CephadmContext()
231 ctx.ssl_dashboard_port = 8888
232 ctx.dashboard_key = None
233 ctx.dashboard_password_noupdate = True
234 ctx.initial_dashboard_password = 'password'
235 ctx.initial_dashboard_user = 'User'
236 with pytest.raises(Exception):
237 cd.prepare_dashboard(ctx, 0, 0, lambda _, extra_mounts=None, ___=None : '5', lambda : None)
238
239 ctx.skip_firewalld = True
240 cd.prepare_dashboard(ctx, 0, 0, lambda _, extra_mounts=None, ___=None : '5', lambda : None)
241
242 ctx.skip_firewalld = False
243 with pytest.raises(Exception):
244 cd.prepare_dashboard(ctx, 0, 0, lambda _, extra_mounts=None, ___=None : '5', lambda : None)
245
246 @mock.patch('cephadm.call_throws')
247 @mock.patch('cephadm.get_parm')
248 def test_registry_login(self, get_parm, call_throws):
249 # test normal valid login with url, username and password specified
250 call_throws.return_value = '', '', 0
251 ctx: cd.CephadmContext = cd.cephadm_init_ctx(
252 ['registry-login', '--registry-url', 'sample-url',
253 '--registry-username', 'sample-user', '--registry-password',
254 'sample-pass'])
255 ctx.container_engine = mock_docker()
256 retval = cd.command_registry_login(ctx)
257 assert retval == 0
258
259 # test bad login attempt with invalid arguments given
260 ctx: cd.CephadmContext = cd.cephadm_init_ctx(
261 ['registry-login', '--registry-url', 'bad-args-url'])
262 with pytest.raises(Exception) as e:
263 assert cd.command_registry_login(ctx)
264 assert str(e.value) == ('Invalid custom registry arguments received. To login to a custom registry include '
265 '--registry-url, --registry-username and --registry-password options or --registry-json option')
266
267 # test normal valid login with json file
268 get_parm.return_value = {"url": "sample-url", "username": "sample-username", "password": "sample-password"}
269 ctx: cd.CephadmContext = cd.cephadm_init_ctx(
270 ['registry-login', '--registry-json', 'sample-json'])
271 ctx.container_engine = mock_docker()
272 retval = cd.command_registry_login(ctx)
273 assert retval == 0
274
275 # test bad login attempt with bad json file
276 get_parm.return_value = {"bad-json": "bad-json"}
277 ctx: cd.CephadmContext = cd.cephadm_init_ctx(
278 ['registry-login', '--registry-json', 'sample-json'])
279 with pytest.raises(Exception) as e:
280 assert cd.command_registry_login(ctx)
281 assert str(e.value) == ("json provided for custom registry login did not include all necessary fields. "
282 "Please setup json file as\n"
283 "{\n"
284 " \"url\": \"REGISTRY_URL\",\n"
285 " \"username\": \"REGISTRY_USERNAME\",\n"
286 " \"password\": \"REGISTRY_PASSWORD\"\n"
287 "}\n")
288
289 # test login attempt with valid arguments where login command fails
290 call_throws.side_effect = Exception
291 ctx: cd.CephadmContext = cd.cephadm_init_ctx(
292 ['registry-login', '--registry-url', 'sample-url',
293 '--registry-username', 'sample-user', '--registry-password',
294 'sample-pass'])
295 with pytest.raises(Exception) as e:
296 cd.command_registry_login(ctx)
297 assert str(e.value) == "Failed to login to custom registry @ sample-url as sample-user with given password"
298
299 def test_get_image_info_from_inspect(self):
300 # podman
301 out = """204a01f9b0b6710dd0c0af7f37ce7139c47ff0f0105d778d7104c69282dfbbf1,[docker.io/ceph/ceph@sha256:1cc9b824e1b076cdff52a9aa3f0cc8557d879fb2fbbba0cafed970aca59a3992]"""
302 r = cd.get_image_info_from_inspect(out, 'registry/ceph/ceph:latest')
303 print(r)
304 assert r == {
305 'image_id': '204a01f9b0b6710dd0c0af7f37ce7139c47ff0f0105d778d7104c69282dfbbf1',
306 'repo_digests': ['docker.io/ceph/ceph@sha256:1cc9b824e1b076cdff52a9aa3f0cc8557d879fb2fbbba0cafed970aca59a3992']
307 }
308
309 # docker
310 out = """sha256:16f4549cf7a8f112bbebf7946749e961fbbd1b0838627fe619aab16bc17ce552,[quay.ceph.io/ceph-ci/ceph@sha256:4e13da36c1bd6780b312a985410ae678984c37e6a9493a74c87e4a50b9bda41f]"""
311 r = cd.get_image_info_from_inspect(out, 'registry/ceph/ceph:latest')
312 assert r == {
313 'image_id': '16f4549cf7a8f112bbebf7946749e961fbbd1b0838627fe619aab16bc17ce552',
314 'repo_digests': ['quay.ceph.io/ceph-ci/ceph@sha256:4e13da36c1bd6780b312a985410ae678984c37e6a9493a74c87e4a50b9bda41f']
315 }
316
317 # multiple digests (podman)
318 out = """e935122ab143a64d92ed1fbb27d030cf6e2f0258207be1baf1b509c466aeeb42,[docker.io/prom/prometheus@sha256:e4ca62c0d62f3e886e684806dfe9d4e0cda60d54986898173c1083856cfda0f4 docker.io/prom/prometheus@sha256:efd99a6be65885c07c559679a0df4ec709604bcdd8cd83f0d00a1a683b28fb6a]"""
319 r = cd.get_image_info_from_inspect(out, 'registry/prom/prometheus:latest')
320 assert r == {
321 'image_id': 'e935122ab143a64d92ed1fbb27d030cf6e2f0258207be1baf1b509c466aeeb42',
322 'repo_digests': [
323 'docker.io/prom/prometheus@sha256:e4ca62c0d62f3e886e684806dfe9d4e0cda60d54986898173c1083856cfda0f4',
324 'docker.io/prom/prometheus@sha256:efd99a6be65885c07c559679a0df4ec709604bcdd8cd83f0d00a1a683b28fb6a',
325 ]
326 }
327
328
329 def test_dict_get(self):
330 result = cd.dict_get({'a': 1}, 'a', require=True)
331 assert result == 1
332 result = cd.dict_get({'a': 1}, 'b')
333 assert result is None
334 result = cd.dict_get({'a': 1}, 'b', default=2)
335 assert result == 2
336
337 def test_dict_get_error(self):
338 with pytest.raises(cd.Error):
339 cd.dict_get({'a': 1}, 'b', require=True)
340
341 def test_dict_get_join(self):
342 result = cd.dict_get_join({'foo': ['a', 'b']}, 'foo')
343 assert result == 'a\nb'
344 result = cd.dict_get_join({'foo': [1, 2]}, 'foo')
345 assert result == '1\n2'
346 result = cd.dict_get_join({'bar': 'a'}, 'bar')
347 assert result == 'a'
348 result = cd.dict_get_join({'a': 1}, 'a')
349 assert result == 1
350
351 @mock.patch('os.listdir', return_value=[])
352 @mock.patch('cephadm.logger')
353 def test_infer_local_ceph_image(self, _logger, _listdir):
354 ctx = cd.CephadmContext()
355 ctx.fsid = '00000000-0000-0000-0000-0000deadbeez'
356 ctx.container_engine = mock_podman()
357
358 # make sure the right image is selected when container is found
359 cinfo = cd.ContainerInfo('935b549714b8f007c6a4e29c758689cf9e8e69f2e0f51180506492974b90a972',
360 'registry.hub.docker.com/rkachach/ceph:custom-v0.5',
361 '514e6a882f6e74806a5856468489eeff8d7106095557578da96935e4d0ba4d9d',
362 '2022-04-19 13:45:20.97146228 +0000 UTC',
363 '')
364 out = '''quay.ceph.io/ceph-ci/ceph@sha256:87f200536bb887b36b959e887d5984dd7a3f008a23aa1f283ab55d48b22c6185|dad864ee21e9|master|2022-03-23 16:29:19 +0000 UTC
365 quay.ceph.io/ceph-ci/ceph@sha256:b50b130fcda2a19f8507ddde3435bb4722266956e1858ac395c838bc1dcf1c0e|514e6a882f6e|pacific|2022-03-23 15:58:34 +0000 UTC
366 docker.io/ceph/ceph@sha256:939a46c06b334e094901560c8346de33c00309e3e3968a2db240eb4897c6a508|666bbfa87e8d|v15.2.5|2020-09-16 14:15:15 +0000 UTC'''
367 with mock.patch('cephadm.call_throws', return_value=(out, '', '')):
368 with mock.patch('cephadm.get_container_info', return_value=cinfo):
369 image = cd.infer_local_ceph_image(ctx, ctx.container_engine)
370 assert image == 'quay.ceph.io/ceph-ci/ceph@sha256:b50b130fcda2a19f8507ddde3435bb4722266956e1858ac395c838bc1dcf1c0e'
371
372 # make sure first valid image is used when no container_info is found
373 out = '''quay.ceph.io/ceph-ci/ceph@sha256:87f200536bb887b36b959e887d5984dd7a3f008a23aa1f283ab55d48b22c6185|dad864ee21e9|master|2022-03-23 16:29:19 +0000 UTC
374 quay.ceph.io/ceph-ci/ceph@sha256:b50b130fcda2a19f8507ddde3435bb4722266956e1858ac395c838bc1dcf1c0e|514e6a882f6e|pacific|2022-03-23 15:58:34 +0000 UTC
375 docker.io/ceph/ceph@sha256:939a46c06b334e094901560c8346de33c00309e3e3968a2db240eb4897c6a508|666bbfa87e8d|v15.2.5|2020-09-16 14:15:15 +0000 UTC'''
376 with mock.patch('cephadm.call_throws', return_value=(out, '', '')):
377 with mock.patch('cephadm.get_container_info', return_value=None):
378 image = cd.infer_local_ceph_image(ctx, ctx.container_engine)
379 assert image == 'quay.ceph.io/ceph-ci/ceph@sha256:87f200536bb887b36b959e887d5984dd7a3f008a23aa1f283ab55d48b22c6185'
380
381 # make sure images without digest are discarded (no container_info is found)
382 out = '''quay.ceph.io/ceph-ci/ceph@|||
383 docker.io/ceph/ceph@|||
384 docker.io/ceph/ceph@sha256:939a46c06b334e094901560c8346de33c00309e3e3968a2db240eb4897c6a508|666bbfa87e8d|v15.2.5|2020-09-16 14:15:15 +0000 UTC'''
385 with mock.patch('cephadm.call_throws', return_value=(out, '', '')):
386 with mock.patch('cephadm.get_container_info', return_value=None):
387 image = cd.infer_local_ceph_image(ctx, ctx.container_engine)
388 assert image == 'docker.io/ceph/ceph@sha256:939a46c06b334e094901560c8346de33c00309e3e3968a2db240eb4897c6a508'
389
390
391
392 @pytest.mark.parametrize('daemon_filter, by_name, daemon_list, container_stats, output',
393 [
394 # get container info by type ('mon')
395 (
396 'mon',
397 False,
398 [
399 {'name': 'mon.ceph-node-0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'},
400 {'name': 'mgr.ceph-node-0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'},
401 ],
402 ("935b549714b8f007c6a4e29c758689cf9e8e69f2e0f51180506492974b90a972,registry.hub.docker.com/rkachach/ceph:custom-v0.5,666bbfa87e8df05702d6172cae11dd7bc48efb1d94f1b9e492952f19647199a4,2022-04-19 13:45:20.97146228 +0000 UTC,",
403 "",
404 0),
405 cd.ContainerInfo('935b549714b8f007c6a4e29c758689cf9e8e69f2e0f51180506492974b90a972',
406 'registry.hub.docker.com/rkachach/ceph:custom-v0.5',
407 '666bbfa87e8df05702d6172cae11dd7bc48efb1d94f1b9e492952f19647199a4',
408 '2022-04-19 13:45:20.97146228 +0000 UTC',
409 '')
410 ),
411 # get container info by name ('mon.ceph-node-0')
412 (
413 'mon.ceph-node-0',
414 True,
415 [
416 {'name': 'mgr.ceph-node-0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'},
417 {'name': 'mon.ceph-node-0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'},
418 ],
419 ("935b549714b8f007c6a4e29c758689cf9e8e69f2e0f51180506492974b90a972,registry.hub.docker.com/rkachach/ceph:custom-v0.5,666bbfa87e8df05702d6172cae11dd7bc48efb1d94f1b9e492952f19647199a4,2022-04-19 13:45:20.97146228 +0000 UTC,",
420 "",
421 0),
422 cd.ContainerInfo('935b549714b8f007c6a4e29c758689cf9e8e69f2e0f51180506492974b90a972',
423 'registry.hub.docker.com/rkachach/ceph:custom-v0.5',
424 '666bbfa87e8df05702d6172cae11dd7bc48efb1d94f1b9e492952f19647199a4',
425 '2022-04-19 13:45:20.97146228 +0000 UTC',
426 '')
427 ),
428 # get container info by name (same daemon but two different fsids)
429 (
430 'mon.ceph-node-0',
431 True,
432 [
433 {'name': 'mon.ceph-node-0', 'fsid': '10000000-0000-0000-0000-0000deadbeef'},
434 {'name': 'mon.ceph-node-0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'},
435 ],
436 ("935b549714b8f007c6a4e29c758689cf9e8e69f2e0f51180506492974b90a972,registry.hub.docker.com/rkachach/ceph:custom-v0.5,666bbfa87e8df05702d6172cae11dd7bc48efb1d94f1b9e492952f19647199a4,2022-04-19 13:45:20.97146228 +0000 UTC,",
437 "",
438 0),
439 cd.ContainerInfo('935b549714b8f007c6a4e29c758689cf9e8e69f2e0f51180506492974b90a972',
440 'registry.hub.docker.com/rkachach/ceph:custom-v0.5',
441 '666bbfa87e8df05702d6172cae11dd7bc48efb1d94f1b9e492952f19647199a4',
442 '2022-04-19 13:45:20.97146228 +0000 UTC',
443 '')
444 ),
445 # get container info by type (bad container stats: 127 code)
446 (
447 'mon',
448 False,
449 [
450 {'name': 'mon.ceph-node-0', 'fsid': '00000000-FFFF-0000-0000-0000deadbeef'},
451 {'name': 'mon.ceph-node-0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'},
452 ],
453 ("",
454 "",
455 127),
456 None
457 ),
458 # get container info by name (bad container stats: 127 code)
459 (
460 'mon.ceph-node-0',
461 True,
462 [
463 {'name': 'mgr.ceph-node-0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'},
464 {'name': 'mon.ceph-node-0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'},
465 ],
466 ("",
467 "",
468 127),
469 None
470 ),
471 # get container info by invalid name (doens't contain '.')
472 (
473 'mon-ceph-node-0',
474 True,
475 [
476 {'name': 'mon.ceph-node-0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'},
477 {'name': 'mon.ceph-node-0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'},
478 ],
479 ("935b549714b8f007c6a4e29c758689cf9e8e69f2e0f51180506492974b90a972,registry.hub.docker.com/rkachach/ceph:custom-v0.5,666bbfa87e8df05702d6172cae11dd7bc48efb1d94f1b9e492952f19647199a4,2022-04-19 13:45:20.97146228 +0000 UTC,",
480 "",
481 0),
482 None
483 ),
484 # get container info by invalid name (empty)
485 (
486 '',
487 True,
488 [
489 {'name': 'mon.ceph-node-0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'},
490 {'name': 'mon.ceph-node-0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'},
491 ],
492 ("935b549714b8f007c6a4e29c758689cf9e8e69f2e0f51180506492974b90a972,registry.hub.docker.com/rkachach/ceph:custom-v0.5,666bbfa87e8df05702d6172cae11dd7bc48efb1d94f1b9e492952f19647199a4,2022-04-19 13:45:20.97146228 +0000 UTC,",
493 "",
494 0),
495 None
496 ),
497 # get container info by invalid type (empty)
498 (
499 '',
500 False,
501 [
502 {'name': 'mon.ceph-node-0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'},
503 {'name': 'mon.ceph-node-0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'},
504 ],
505 ("935b549714b8f007c6a4e29c758689cf9e8e69f2e0f51180506492974b90a972,registry.hub.docker.com/rkachach/ceph:custom-v0.5,666bbfa87e8df05702d6172cae11dd7bc48efb1d94f1b9e492952f19647199a4,2022-04-19 13:45:20.97146228 +0000 UTC,",
506 "",
507 0),
508 None
509 ),
510 # get container info by name: no match (invalid fsid)
511 (
512 'mon',
513 False,
514 [
515 {'name': 'mon.ceph-node-0', 'fsid': '00000000-1111-0000-0000-0000deadbeef'},
516 {'name': 'mon.ceph-node-0', 'fsid': '00000000-2222-0000-0000-0000deadbeef'},
517 ],
518 ("935b549714b8f007c6a4e29c758689cf9e8e69f2e0f51180506492974b90a972,registry.hub.docker.com/rkachach/ceph:custom-v0.5,666bbfa87e8df05702d6172cae11dd7bc48efb1d94f1b9e492952f19647199a4,2022-04-19 13:45:20.97146228 +0000 UTC,",
519 "",
520 0),
521 None
522 ),
523 # get container info by name: no match
524 (
525 'mon.ceph-node-0',
526 True,
527 [],
528 None,
529 None
530 ),
531 # get container info by type: no match
532 (
533 'mgr',
534 False,
535 [],
536 None,
537 None
538 ),
539 ])
540 def test_get_container_info(self, daemon_filter, by_name, daemon_list, container_stats, output):
541 cd.logger = mock.Mock()
542 ctx = cd.CephadmContext()
543 ctx.fsid = '00000000-0000-0000-0000-0000deadbeef'
544 ctx.container_engine = mock_podman()
545 with mock.patch('cephadm.list_daemons', return_value=daemon_list):
546 with mock.patch('cephadm.get_container_stats', return_value=container_stats):
547 assert cd.get_container_info(ctx, daemon_filter, by_name) == output
548
549 def test_should_log_to_journald(self):
550 ctx = cd.CephadmContext()
551 # explicit
552 ctx.log_to_journald = True
553 assert cd.should_log_to_journald(ctx)
554
555 ctx.log_to_journald = None
556 # enable if podman support --cgroup=split
557 ctx.container_engine = mock_podman()
558 ctx.container_engine.version = (2, 1, 0)
559 assert cd.should_log_to_journald(ctx)
560
561 # disable on old podman
562 ctx.container_engine.version = (2, 0, 0)
563 assert not cd.should_log_to_journald(ctx)
564
565 # disable on docker
566 ctx.container_engine = mock_docker()
567 assert not cd.should_log_to_journald(ctx)
568
569 def test_normalize_image_digest(self):
570 s = 'myhostname:5000/ceph/ceph@sha256:753886ad9049004395ae990fbb9b096923b5a518b819283141ee8716ddf55ad1'
571 assert cd.normalize_image_digest(s) == s
572
573 s = 'ceph/ceph:latest'
574 assert cd.normalize_image_digest(s) == f'{cd.DEFAULT_REGISTRY}/{s}'
575
576 @pytest.mark.parametrize('fsid, ceph_conf, list_daemons, result, err, ',
577 [
578 (
579 None,
580 None,
581 [],
582 None,
583 None,
584 ),
585 (
586 '00000000-0000-0000-0000-0000deadbeef',
587 None,
588 [],
589 '00000000-0000-0000-0000-0000deadbeef',
590 None,
591 ),
592 (
593 '00000000-0000-0000-0000-0000deadbeef',
594 None,
595 [
596 {'fsid': '10000000-0000-0000-0000-0000deadbeef'},
597 {'fsid': '20000000-0000-0000-0000-0000deadbeef'},
598 ],
599 '00000000-0000-0000-0000-0000deadbeef',
600 None,
601 ),
602 (
603 None,
604 None,
605 [
606 {'fsid': '00000000-0000-0000-0000-0000deadbeef'},
607 ],
608 '00000000-0000-0000-0000-0000deadbeef',
609 None,
610 ),
611 (
612 None,
613 None,
614 [
615 {'fsid': '10000000-0000-0000-0000-0000deadbeef'},
616 {'fsid': '20000000-0000-0000-0000-0000deadbeef'},
617 ],
618 None,
619 r'Cannot infer an fsid',
620 ),
621 (
622 None,
623 get_ceph_conf(fsid='00000000-0000-0000-0000-0000deadbeef'),
624 [],
625 '00000000-0000-0000-0000-0000deadbeef',
626 None,
627 ),
628 (
629 None,
630 get_ceph_conf(fsid='00000000-0000-0000-0000-0000deadbeef'),
631 [
632 {'fsid': '00000000-0000-0000-0000-0000deadbeef'},
633 ],
634 '00000000-0000-0000-0000-0000deadbeef',
635 None,
636 ),
637 (
638 None,
639 get_ceph_conf(fsid='00000000-0000-0000-0000-0000deadbeef'),
640 [
641 {'fsid': '10000000-0000-0000-0000-0000deadbeef'},
642 {'fsid': '20000000-0000-0000-0000-0000deadbeef'},
643 ],
644 None,
645 r'Cannot infer an fsid',
646 ),
647 ])
648 @mock.patch('cephadm.call')
649 def test_infer_fsid(self, _call, fsid, ceph_conf, list_daemons, result, err, cephadm_fs):
650 # build the context
651 ctx = cd.CephadmContext()
652 ctx.fsid = fsid
653
654 # mock the decorator
655 mock_fn = mock.Mock()
656 mock_fn.return_value = 0
657 infer_fsid = cd.infer_fsid(mock_fn)
658
659 # mock the ceph.conf file content
660 if ceph_conf:
661 f = cephadm_fs.create_file('ceph.conf', contents=ceph_conf)
662 ctx.config = f.path
663
664 # test
665 with mock.patch('cephadm.list_daemons', return_value=list_daemons):
666 if err:
667 with pytest.raises(cd.Error, match=err):
668 infer_fsid(ctx)
669 else:
670 infer_fsid(ctx)
671 assert ctx.fsid == result
672
673 @pytest.mark.parametrize('fsid, other_conf_files, config, name, list_daemons, result, ',
674 [
675 # per cluster conf has more precedence than default conf
676 (
677 '00000000-0000-0000-0000-0000deadbeef',
678 [cd.CEPH_DEFAULT_CONF],
679 None,
680 None,
681 [],
682 '/var/lib/ceph/00000000-0000-0000-0000-0000deadbeef/config/ceph.conf',
683 ),
684 # mon daemon conf has more precedence than cluster conf and default conf
685 (
686 '00000000-0000-0000-0000-0000deadbeef',
687 ['/var/lib/ceph/00000000-0000-0000-0000-0000deadbeef/config/ceph.conf',
688 cd.CEPH_DEFAULT_CONF],
689 None,
690 None,
691 [{'name': 'mon.a', 'fsid': '00000000-0000-0000-0000-0000deadbeef', 'style': 'cephadm:v1'}],
692 '/var/lib/ceph/00000000-0000-0000-0000-0000deadbeef/mon.a/config',
693 ),
694 # daemon conf (--name option) has more precedence than cluster, default and mon conf
695 (
696 '00000000-0000-0000-0000-0000deadbeef',
697 ['/var/lib/ceph/00000000-0000-0000-0000-0000deadbeef/config/ceph.conf',
698 '/var/lib/ceph/00000000-0000-0000-0000-0000deadbeef/mon.a/config',
699 cd.CEPH_DEFAULT_CONF],
700 None,
701 'osd.0',
702 [{'name': 'mon.a', 'fsid': '00000000-0000-0000-0000-0000deadbeef', 'style': 'cephadm:v1'},
703 {'name': 'osd.0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'}],
704 '/var/lib/ceph/00000000-0000-0000-0000-0000deadbeef/osd.0/config',
705 ),
706 # user provided conf ('/foo/ceph.conf') more precedence than any other conf
707 (
708 '00000000-0000-0000-0000-0000deadbeef',
709 ['/var/lib/ceph/00000000-0000-0000-0000-0000deadbeef/config/ceph.conf',
710 cd.CEPH_DEFAULT_CONF,
711 '/var/lib/ceph/00000000-0000-0000-0000-0000deadbeef/mon.a/config'],
712 '/foo/ceph.conf',
713 None,
714 [{'name': 'mon.a', 'fsid': '00000000-0000-0000-0000-0000deadbeef', 'style': 'cephadm:v1'}],
715 '/foo/ceph.conf',
716 ),
717 ])
718 @mock.patch('cephadm.call')
719 @mock.patch('cephadm.logger')
720 def test_infer_config_precedence(self, logger, _call, other_conf_files, fsid, config, name, list_daemons, result, cephadm_fs):
721 # build the context
722 ctx = cd.CephadmContext()
723 ctx.fsid = fsid
724 ctx.config = config
725 ctx.name = name
726
727 # mock the decorator
728 mock_fn = mock.Mock()
729 mock_fn.return_value = 0
730 infer_config = cd.infer_config(mock_fn)
731
732 # mock the config file
733 cephadm_fs.create_file(result)
734
735 # mock other potential config files
736 for f in other_conf_files:
737 cephadm_fs.create_file(f)
738
739 # test
740 with mock.patch('cephadm.list_daemons', return_value=list_daemons):
741 infer_config(ctx)
742 assert ctx.config == result
743
744 @pytest.mark.parametrize('fsid, config, name, list_daemons, result, ',
745 [
746 (
747 None,
748 '/foo/bar.conf',
749 None,
750 [],
751 '/foo/bar.conf',
752 ),
753 (
754 '00000000-0000-0000-0000-0000deadbeef',
755 None,
756 None,
757 [],
758 cd.CEPH_DEFAULT_CONF,
759 ),
760 (
761 '00000000-0000-0000-0000-0000deadbeef',
762 None,
763 None,
764 [],
765 '/var/lib/ceph/00000000-0000-0000-0000-0000deadbeef/config/ceph.conf',
766 ),
767 (
768 '00000000-0000-0000-0000-0000deadbeef',
769 None,
770 None,
771 [{'name': 'mon.a', 'fsid': '00000000-0000-0000-0000-0000deadbeef', 'style': 'cephadm:v1'}],
772 '/var/lib/ceph/00000000-0000-0000-0000-0000deadbeef/mon.a/config',
773 ),
774 (
775 '00000000-0000-0000-0000-0000deadbeef',
776 None,
777 None,
778 [{'name': 'mon.a', 'fsid': 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'style': 'cephadm:v1'}],
779 cd.CEPH_DEFAULT_CONF,
780 ),
781 (
782 '00000000-0000-0000-0000-0000deadbeef',
783 None,
784 None,
785 [{'name': 'mon.a', 'fsid': '00000000-0000-0000-0000-0000deadbeef', 'style': 'legacy'}],
786 cd.CEPH_DEFAULT_CONF,
787 ),
788 (
789 '00000000-0000-0000-0000-0000deadbeef',
790 None,
791 None,
792 [{'name': 'osd.0'}],
793 cd.CEPH_DEFAULT_CONF,
794 ),
795 (
796 '00000000-0000-0000-0000-0000deadbeef',
797 '/foo/bar.conf',
798 'mon.a',
799 [{'name': 'mon.a', 'style': 'cephadm:v1'}],
800 '/foo/bar.conf',
801 ),
802 (
803 '00000000-0000-0000-0000-0000deadbeef',
804 None,
805 'mon.a',
806 [],
807 '/var/lib/ceph/00000000-0000-0000-0000-0000deadbeef/mon.a/config',
808 ),
809 (
810 '00000000-0000-0000-0000-0000deadbeef',
811 None,
812 'osd.0',
813 [],
814 '/var/lib/ceph/00000000-0000-0000-0000-0000deadbeef/osd.0/config',
815 ),
816 (
817 None,
818 None,
819 None,
820 [],
821 cd.CEPH_DEFAULT_CONF,
822 ),
823 ])
824 @mock.patch('cephadm.call')
825 @mock.patch('cephadm.logger')
826 def test_infer_config(self, logger, _call, fsid, config, name, list_daemons, result, cephadm_fs):
827 # build the context
828 ctx = cd.CephadmContext()
829 ctx.fsid = fsid
830 ctx.config = config
831 ctx.name = name
832
833 # mock the decorator
834 mock_fn = mock.Mock()
835 mock_fn.return_value = 0
836 infer_config = cd.infer_config(mock_fn)
837
838 # mock the config file
839 cephadm_fs.create_file(result)
840
841 # test
842 with mock.patch('cephadm.list_daemons', return_value=list_daemons):
843 infer_config(ctx)
844 assert ctx.config == result
845
846 @mock.patch('cephadm.call')
847 def test_extract_uid_gid_fail(self, _call):
848 err = """Error: container_linux.go:370: starting container process caused: process_linux.go:459: container init caused: process_linux.go:422: setting cgroup config for procHooks process caused: Unit libpod-056038e1126191fba41d8a037275136f2d7aeec9710b9ee
849 ff792c06d8544b983.scope not found.: OCI runtime error"""
850 _call.return_value = ('', err, 127)
851 ctx = cd.CephadmContext()
852 ctx.container_engine = mock_podman()
853 with pytest.raises(cd.Error, match='OCI'):
854 cd.extract_uid_gid(ctx)
855
856 @pytest.mark.parametrize('test_input, expected', [
857 ([cd.make_fsid(), cd.make_fsid(), cd.make_fsid()], 3),
858 ([cd.make_fsid(), 'invalid-fsid', cd.make_fsid(), '0b87e50c-8e77-11ec-b890-'], 2),
859 (['f6860ec2-8e76-11ec-', '0b87e50c-8e77-11ec-b890-', ''], 0),
860 ([], 0),
861 ])
862 def test_get_ceph_cluster_count(self, test_input, expected):
863 ctx = cd.CephadmContext()
864 with mock.patch('os.listdir', return_value=test_input):
865 assert cd.get_ceph_cluster_count(ctx) == expected
866
867 def test_set_image_minimize_config(self):
868 def throw_cmd(cmd):
869 raise cd.Error(' '.join(cmd))
870 ctx = cd.CephadmContext()
871 ctx.image = 'test_image'
872 ctx.no_minimize_config = True
873 fake_cli = lambda cmd, __=None, ___=None: throw_cmd(cmd)
874 with pytest.raises(cd.Error, match='config set global container_image test_image'):
875 cd.finish_bootstrap_config(
876 ctx=ctx,
877 fsid=cd.make_fsid(),
878 config='',
879 mon_id='a', mon_dir='mon_dir',
880 mon_network=None, ipv6=False,
881 cli=fake_cli,
882 cluster_network=None,
883 ipv6_cluster_network=False
884 )
885
886
887 class TestCustomContainer(unittest.TestCase):
888 cc: cd.CustomContainer
889
890 def setUp(self):
891 self.cc = cd.CustomContainer(
892 'e863154d-33c7-4350-bca5-921e0467e55b',
893 'container',
894 config_json={
895 'entrypoint': 'bash',
896 'gid': 1000,
897 'args': [
898 '--no-healthcheck',
899 '-p 6800:6800'
900 ],
901 'envs': ['SECRET=password'],
902 'ports': [8080, 8443],
903 'volume_mounts': {
904 '/CONFIG_DIR': '/foo/conf',
905 'bar/config': '/bar:ro'
906 },
907 'bind_mounts': [
908 [
909 'type=bind',
910 'source=/CONFIG_DIR',
911 'destination=/foo/conf',
912 ''
913 ],
914 [
915 'type=bind',
916 'source=bar/config',
917 'destination=/bar:ro',
918 'ro=true'
919 ]
920 ]
921 },
922 image='docker.io/library/hello-world:latest'
923 )
924
925 def test_entrypoint(self):
926 self.assertEqual(self.cc.entrypoint, 'bash')
927
928 def test_uid_gid(self):
929 self.assertEqual(self.cc.uid, 65534)
930 self.assertEqual(self.cc.gid, 1000)
931
932 def test_ports(self):
933 self.assertEqual(self.cc.ports, [8080, 8443])
934
935 def test_get_container_args(self):
936 result = self.cc.get_container_args()
937 self.assertEqual(result, [
938 '--no-healthcheck',
939 '-p 6800:6800'
940 ])
941
942 def test_get_container_envs(self):
943 result = self.cc.get_container_envs()
944 self.assertEqual(result, ['SECRET=password'])
945
946 def test_get_container_mounts(self):
947 result = self.cc.get_container_mounts('/xyz')
948 self.assertDictEqual(result, {
949 '/CONFIG_DIR': '/foo/conf',
950 '/xyz/bar/config': '/bar:ro'
951 })
952
953 def test_get_container_binds(self):
954 result = self.cc.get_container_binds('/xyz')
955 self.assertEqual(result, [
956 [
957 'type=bind',
958 'source=/CONFIG_DIR',
959 'destination=/foo/conf',
960 ''
961 ],
962 [
963 'type=bind',
964 'source=/xyz/bar/config',
965 'destination=/bar:ro',
966 'ro=true'
967 ]
968 ])
969
970
971 class TestMaintenance:
972 systemd_target = "ceph.00000000-0000-0000-0000-000000c0ffee.target"
973 fsid = '0ea8cdd0-1bbf-11ec-a9c7-5254002763fa'
974
975 def test_systemd_target_OK(self, tmp_path):
976 base = tmp_path
977 wants = base / "ceph.target.wants"
978 wants.mkdir()
979 target = wants / TestMaintenance.systemd_target
980 target.touch()
981 ctx = cd.CephadmContext()
982 ctx.unit_dir = str(base)
983
984 assert cd.systemd_target_state(ctx, target.name)
985
986 def test_systemd_target_NOTOK(self, tmp_path):
987 base = tmp_path
988 ctx = cd.CephadmContext()
989 ctx.unit_dir = str(base)
990 assert not cd.systemd_target_state(ctx, TestMaintenance.systemd_target)
991
992 def test_parser_OK(self):
993 args = cd._parse_args(['host-maintenance', 'enter'])
994 assert args.maintenance_action == 'enter'
995
996 def test_parser_BAD(self):
997 with pytest.raises(SystemExit):
998 cd._parse_args(['host-maintenance', 'wah'])
999
1000 @mock.patch('os.listdir', return_value=[])
1001 @mock.patch('cephadm.call')
1002 @mock.patch('cephadm.systemd_target_state')
1003 def test_enter_failure_1(self, _target_state, _call, _listdir):
1004 _call.return_value = '', '', 999
1005 _target_state.return_value = True
1006 ctx: cd.CephadmContext = cd.cephadm_init_ctx(
1007 ['host-maintenance', 'enter', '--fsid', TestMaintenance.fsid])
1008 ctx.container_engine = mock_podman()
1009 retval = cd.command_maintenance(ctx)
1010 assert retval.startswith('failed')
1011
1012 @mock.patch('os.listdir', return_value=[])
1013 @mock.patch('cephadm.call')
1014 @mock.patch('cephadm.systemd_target_state')
1015 def test_enter_failure_2(self, _target_state, _call, _listdir):
1016 _call.side_effect = [('', '', 0), ('', '', 999), ('', '', 0), ('', '', 999)]
1017 _target_state.return_value = True
1018 ctx: cd.CephadmContext = cd.cephadm_init_ctx(
1019 ['host-maintenance', 'enter', '--fsid', TestMaintenance.fsid])
1020 ctx.container_engine = mock_podman()
1021 retval = cd.command_maintenance(ctx)
1022 assert retval.startswith('failed')
1023
1024 @mock.patch('os.listdir', return_value=[])
1025 @mock.patch('cephadm.call')
1026 @mock.patch('cephadm.systemd_target_state')
1027 @mock.patch('cephadm.target_exists')
1028 def test_exit_failure_1(self, _target_exists, _target_state, _call, _listdir):
1029 _call.return_value = '', '', 999
1030 _target_state.return_value = False
1031 _target_exists.return_value = True
1032 ctx: cd.CephadmContext = cd.cephadm_init_ctx(
1033 ['host-maintenance', 'exit', '--fsid', TestMaintenance.fsid])
1034 ctx.container_engine = mock_podman()
1035 retval = cd.command_maintenance(ctx)
1036 assert retval.startswith('failed')
1037
1038 @mock.patch('os.listdir', return_value=[])
1039 @mock.patch('cephadm.call')
1040 @mock.patch('cephadm.systemd_target_state')
1041 @mock.patch('cephadm.target_exists')
1042 def test_exit_failure_2(self, _target_exists, _target_state, _call, _listdir):
1043 _call.side_effect = [('', '', 0), ('', '', 999), ('', '', 0), ('', '', 999)]
1044 _target_state.return_value = False
1045 _target_exists.return_value = True
1046 ctx: cd.CephadmContext = cd.cephadm_init_ctx(
1047 ['host-maintenance', 'exit', '--fsid', TestMaintenance.fsid])
1048 ctx.container_engine = mock_podman()
1049 retval = cd.command_maintenance(ctx)
1050 assert retval.startswith('failed')
1051
1052
1053 class TestMonitoring(object):
1054 @mock.patch('cephadm.call')
1055 def test_get_version_alertmanager(self, _call):
1056 ctx = cd.CephadmContext()
1057 ctx.container_engine = mock_podman()
1058 daemon_type = 'alertmanager'
1059
1060 # binary `prometheus`
1061 _call.return_value = '', '{}, version 0.16.1'.format(daemon_type), 0
1062 version = cd.Monitoring.get_version(ctx, 'container_id', daemon_type)
1063 assert version == '0.16.1'
1064
1065 # binary `prometheus-alertmanager`
1066 _call.side_effect = (
1067 ('', '', 1),
1068 ('', '{}, version 0.16.1'.format(daemon_type), 0),
1069 )
1070 version = cd.Monitoring.get_version(ctx, 'container_id', daemon_type)
1071 assert version == '0.16.1'
1072
1073 @mock.patch('cephadm.call')
1074 def test_get_version_prometheus(self, _call):
1075 ctx = cd.CephadmContext()
1076 ctx.container_engine = mock_podman()
1077 daemon_type = 'prometheus'
1078 _call.return_value = '', '{}, version 0.16.1'.format(daemon_type), 0
1079 version = cd.Monitoring.get_version(ctx, 'container_id', daemon_type)
1080 assert version == '0.16.1'
1081
1082 def test_prometheus_external_url(self):
1083 ctx = cd.CephadmContext()
1084 daemon_type = 'prometheus'
1085 daemon_id = 'home'
1086 fsid = 'aaf5a720-13fe-4a3b-82b9-2d99b7fd9704'
1087 args = cd.get_daemon_args(ctx, fsid, daemon_type, daemon_id)
1088 assert any([x.startswith('--web.external-url=http://') for x in args])
1089
1090 @mock.patch('cephadm.call')
1091 def test_get_version_node_exporter(self, _call):
1092 ctx = cd.CephadmContext()
1093 ctx.container_engine = mock_podman()
1094 daemon_type = 'node-exporter'
1095 _call.return_value = '', '{}, version 0.16.1'.format(daemon_type.replace('-', '_')), 0
1096 version = cd.Monitoring.get_version(ctx, 'container_id', daemon_type)
1097 assert version == '0.16.1'
1098
1099 def test_create_daemon_dirs_prometheus(self, cephadm_fs):
1100 """
1101 Ensures the required and optional files given in the configuration are
1102 created and mapped correctly inside the container. Tests absolute and
1103 relative file paths given in the configuration.
1104 """
1105
1106 fsid = 'aaf5a720-13fe-4a3b-82b9-2d99b7fd9704'
1107 daemon_type = 'prometheus'
1108 uid, gid = 50, 50
1109 daemon_id = 'home'
1110 ctx = cd.CephadmContext()
1111 ctx.data_dir = '/somedir'
1112 ctx.config_json = json.dumps({
1113 'files': {
1114 'prometheus.yml': 'foo',
1115 '/etc/prometheus/alerting/ceph_alerts.yml': 'bar'
1116 }
1117 })
1118
1119 cd.create_daemon_dirs(ctx,
1120 fsid,
1121 daemon_type,
1122 daemon_id,
1123 uid,
1124 gid,
1125 config=None,
1126 keyring=None)
1127
1128 prefix = '{data_dir}/{fsid}/{daemon_type}.{daemon_id}'.format(
1129 data_dir=ctx.data_dir,
1130 fsid=fsid,
1131 daemon_type=daemon_type,
1132 daemon_id=daemon_id
1133 )
1134
1135 expected = {
1136 'etc/prometheus/prometheus.yml': 'foo',
1137 'etc/prometheus/alerting/ceph_alerts.yml': 'bar',
1138 }
1139
1140 for file,content in expected.items():
1141 file = os.path.join(prefix, file)
1142 assert os.path.exists(file)
1143 with open(file) as f:
1144 assert f.read() == content
1145
1146 # assert uid/gid after redeploy
1147 new_uid = uid+1
1148 new_gid = gid+1
1149 cd.create_daemon_dirs(ctx,
1150 fsid,
1151 daemon_type,
1152 daemon_id,
1153 new_uid,
1154 new_gid,
1155 config=None,
1156 keyring=None)
1157 for file,content in expected.items():
1158 file = os.path.join(prefix, file)
1159 assert os.stat(file).st_uid == new_uid
1160 assert os.stat(file).st_gid == new_gid
1161
1162
1163 class TestBootstrap(object):
1164
1165 @staticmethod
1166 def _get_cmd(*args):
1167 return [
1168 'bootstrap',
1169 '--allow-mismatched-release',
1170 '--skip-prepare-host',
1171 '--skip-dashboard',
1172 *args,
1173 ]
1174
1175
1176 ###############################################3
1177
1178 def test_config(self, cephadm_fs):
1179 conf_file = 'foo'
1180 cmd = self._get_cmd(
1181 '--mon-ip', '192.168.1.1',
1182 '--skip-mon-network',
1183 '--config', conf_file,
1184 )
1185
1186 with with_cephadm_ctx(cmd) as ctx:
1187 msg = r'No such file or directory'
1188 with pytest.raises(cd.Error, match=msg):
1189 cd.command_bootstrap(ctx)
1190
1191 cephadm_fs.create_file(conf_file)
1192 with with_cephadm_ctx(cmd) as ctx:
1193 retval = cd.command_bootstrap(ctx)
1194 assert retval == 0
1195
1196 def test_no_mon_addr(self, cephadm_fs):
1197 cmd = self._get_cmd()
1198 with with_cephadm_ctx(cmd) as ctx:
1199 msg = r'must specify --mon-ip or --mon-addrv'
1200 with pytest.raises(cd.Error, match=msg):
1201 cd.command_bootstrap(ctx)
1202
1203 def test_skip_mon_network(self, cephadm_fs):
1204 cmd = self._get_cmd('--mon-ip', '192.168.1.1')
1205
1206 with with_cephadm_ctx(cmd, list_networks={}) as ctx:
1207 msg = r'--skip-mon-network'
1208 with pytest.raises(cd.Error, match=msg):
1209 cd.command_bootstrap(ctx)
1210
1211 cmd += ['--skip-mon-network']
1212 with with_cephadm_ctx(cmd, list_networks={}) as ctx:
1213 retval = cd.command_bootstrap(ctx)
1214 assert retval == 0
1215
1216 @pytest.mark.parametrize('mon_ip, list_networks, result',
1217 [
1218 # IPv4
1219 (
1220 'eth0',
1221 {'192.168.1.0/24': {'eth0': ['192.168.1.1']}},
1222 False,
1223 ),
1224 (
1225 '0.0.0.0',
1226 {'192.168.1.0/24': {'eth0': ['192.168.1.1']}},
1227 False,
1228 ),
1229 (
1230 '192.168.1.0',
1231 {'192.168.1.0/24': {'eth0': ['192.168.1.1']}},
1232 False,
1233 ),
1234 (
1235 '192.168.1.1',
1236 {'192.168.1.0/24': {'eth0': ['192.168.1.1']}},
1237 True,
1238 ),
1239 (
1240 '192.168.1.1:1234',
1241 {'192.168.1.0/24': {'eth0': ['192.168.1.1']}},
1242 True,
1243 ),
1244 (
1245 '192.168.1.1:0123',
1246 {'192.168.1.0/24': {'eth0': ['192.168.1.1']}},
1247 True,
1248 ),
1249 # IPv6
1250 (
1251 '::',
1252 {'192.168.1.0/24': {'eth0': ['192.168.1.1']}},
1253 False,
1254 ),
1255 (
1256 '::ffff:192.168.1.0',
1257 {"ffff::/64": {"eth0": ["::ffff:c0a8:101"]}},
1258 False,
1259 ),
1260 (
1261 '::ffff:192.168.1.1',
1262 {"ffff::/64": {"eth0": ["::ffff:c0a8:101"]}},
1263 True,
1264 ),
1265 (
1266 '::ffff:c0a8:101',
1267 {"ffff::/64": {"eth0": ["::ffff:c0a8:101"]}},
1268 True,
1269 ),
1270 (
1271 '[::ffff:c0a8:101]:1234',
1272 {"ffff::/64": {"eth0": ["::ffff:c0a8:101"]}},
1273 True,
1274 ),
1275 (
1276 '[::ffff:c0a8:101]:0123',
1277 {"ffff::/64": {"eth0": ["::ffff:c0a8:101"]}},
1278 True,
1279 ),
1280 (
1281 '0000:0000:0000:0000:0000:FFFF:C0A8:0101',
1282 {"ffff::/64": {"eth0": ["::ffff:c0a8:101"]}},
1283 True,
1284 ),
1285 ])
1286 def test_mon_ip(self, mon_ip, list_networks, result, cephadm_fs):
1287 cmd = self._get_cmd('--mon-ip', mon_ip)
1288 if not result:
1289 with with_cephadm_ctx(cmd, list_networks=list_networks) as ctx:
1290 msg = r'--skip-mon-network'
1291 with pytest.raises(cd.Error, match=msg):
1292 cd.command_bootstrap(ctx)
1293 else:
1294 with with_cephadm_ctx(cmd, list_networks=list_networks) as ctx:
1295 retval = cd.command_bootstrap(ctx)
1296 assert retval == 0
1297
1298 @pytest.mark.parametrize('mon_addrv, list_networks, err',
1299 [
1300 # IPv4
1301 (
1302 '192.168.1.1',
1303 {'192.168.1.0/24': {'eth0': ['192.168.1.1']}},
1304 r'must use square backets',
1305 ),
1306 (
1307 '[192.168.1.1]',
1308 {'192.168.1.0/24': {'eth0': ['192.168.1.1']}},
1309 r'must include port number',
1310 ),
1311 (
1312 '[192.168.1.1:1234]',
1313 {'192.168.1.0/24': {'eth0': ['192.168.1.1']}},
1314 None,
1315 ),
1316 (
1317 '[192.168.1.1:0123]',
1318 {'192.168.1.0/24': {'eth0': ['192.168.1.1']}},
1319 None,
1320 ),
1321 (
1322 '[v2:192.168.1.1:3300,v1:192.168.1.1:6789]',
1323 {'192.168.1.0/24': {'eth0': ['192.168.1.1']}},
1324 None,
1325 ),
1326 # IPv6
1327 (
1328 '[::ffff:192.168.1.1:1234]',
1329 {'ffff::/64': {'eth0': ['::ffff:c0a8:101']}},
1330 None,
1331 ),
1332 (
1333 '[::ffff:192.168.1.1:0123]',
1334 {'ffff::/64': {'eth0': ['::ffff:c0a8:101']}},
1335 None,
1336 ),
1337 (
1338 '[0000:0000:0000:0000:0000:FFFF:C0A8:0101:1234]',
1339 {'ffff::/64': {'eth0': ['::ffff:c0a8:101']}},
1340 None,
1341 ),
1342 (
1343 '[v2:0000:0000:0000:0000:0000:FFFF:C0A8:0101:3300,v1:0000:0000:0000:0000:0000:FFFF:C0A8:0101:6789]',
1344 {'ffff::/64': {'eth0': ['::ffff:c0a8:101']}},
1345 None,
1346 ),
1347 ])
1348 def test_mon_addrv(self, mon_addrv, list_networks, err, cephadm_fs):
1349 cmd = self._get_cmd('--mon-addrv', mon_addrv)
1350 if err:
1351 with with_cephadm_ctx(cmd, list_networks=list_networks) as ctx:
1352 with pytest.raises(cd.Error, match=err):
1353 cd.command_bootstrap(ctx)
1354 else:
1355 with with_cephadm_ctx(cmd, list_networks=list_networks) as ctx:
1356 retval = cd.command_bootstrap(ctx)
1357 assert retval == 0
1358
1359 def test_allow_fqdn_hostname(self, cephadm_fs):
1360 hostname = 'foo.bar'
1361 cmd = self._get_cmd(
1362 '--mon-ip', '192.168.1.1',
1363 '--skip-mon-network',
1364 )
1365
1366 with with_cephadm_ctx(cmd, hostname=hostname) as ctx:
1367 msg = r'--allow-fqdn-hostname'
1368 with pytest.raises(cd.Error, match=msg):
1369 cd.command_bootstrap(ctx)
1370
1371 cmd += ['--allow-fqdn-hostname']
1372 with with_cephadm_ctx(cmd, hostname=hostname) as ctx:
1373 retval = cd.command_bootstrap(ctx)
1374 assert retval == 0
1375
1376 @pytest.mark.parametrize('fsid, err',
1377 [
1378 ('', None),
1379 ('00000000-0000-0000-0000-0000deadbeef', None),
1380 ('00000000-0000-0000-0000-0000deadbeez', 'not an fsid'),
1381 ])
1382 def test_fsid(self, fsid, err, cephadm_fs):
1383 cmd = self._get_cmd(
1384 '--mon-ip', '192.168.1.1',
1385 '--skip-mon-network',
1386 '--fsid', fsid,
1387 )
1388
1389 with with_cephadm_ctx(cmd) as ctx:
1390 if err:
1391 with pytest.raises(cd.Error, match=err):
1392 cd.command_bootstrap(ctx)
1393 else:
1394 retval = cd.command_bootstrap(ctx)
1395 assert retval == 0
1396
1397
1398 class TestShell(object):
1399
1400 def test_fsid(self, cephadm_fs):
1401 fsid = '00000000-0000-0000-0000-0000deadbeef'
1402
1403 cmd = ['shell', '--fsid', fsid]
1404 with with_cephadm_ctx(cmd) as ctx:
1405 retval = cd.command_shell(ctx)
1406 assert retval == 0
1407 assert ctx.fsid == fsid
1408
1409 cmd = ['shell', '--fsid', '00000000-0000-0000-0000-0000deadbeez']
1410 with with_cephadm_ctx(cmd) as ctx:
1411 err = 'not an fsid'
1412 with pytest.raises(cd.Error, match=err):
1413 retval = cd.command_shell(ctx)
1414 assert retval == 1
1415 assert ctx.fsid == None
1416
1417 s = get_ceph_conf(fsid=fsid)
1418 f = cephadm_fs.create_file('ceph.conf', contents=s)
1419
1420 cmd = ['shell', '--fsid', fsid, '--config', f.path]
1421 with with_cephadm_ctx(cmd) as ctx:
1422 retval = cd.command_shell(ctx)
1423 assert retval == 0
1424 assert ctx.fsid == fsid
1425
1426 cmd = ['shell', '--fsid', '10000000-0000-0000-0000-0000deadbeef', '--config', f.path]
1427 with with_cephadm_ctx(cmd) as ctx:
1428 err = 'fsid does not match ceph.conf'
1429 with pytest.raises(cd.Error, match=err):
1430 retval = cd.command_shell(ctx)
1431 assert retval == 1
1432 assert ctx.fsid == None
1433
1434 def test_name(self, cephadm_fs):
1435 cmd = ['shell', '--name', 'foo']
1436 with with_cephadm_ctx(cmd) as ctx:
1437 retval = cd.command_shell(ctx)
1438 assert retval == 0
1439
1440 cmd = ['shell', '--name', 'foo.bar']
1441 with with_cephadm_ctx(cmd) as ctx:
1442 err = r'must pass --fsid'
1443 with pytest.raises(cd.Error, match=err):
1444 retval = cd.command_shell(ctx)
1445 assert retval == 1
1446
1447 fsid = '00000000-0000-0000-0000-0000deadbeef'
1448 cmd = ['shell', '--name', 'foo.bar', '--fsid', fsid]
1449 with with_cephadm_ctx(cmd) as ctx:
1450 retval = cd.command_shell(ctx)
1451 assert retval == 0
1452
1453 def test_config(self, cephadm_fs):
1454 cmd = ['shell']
1455 with with_cephadm_ctx(cmd) as ctx:
1456 retval = cd.command_shell(ctx)
1457 assert retval == 0
1458 assert ctx.config == None
1459
1460 cephadm_fs.create_file(cd.CEPH_DEFAULT_CONF)
1461 with with_cephadm_ctx(cmd) as ctx:
1462 retval = cd.command_shell(ctx)
1463 assert retval == 0
1464 assert ctx.config == cd.CEPH_DEFAULT_CONF
1465
1466 cmd = ['shell', '--config', 'foo']
1467 with with_cephadm_ctx(cmd) as ctx:
1468 retval = cd.command_shell(ctx)
1469 assert retval == 0
1470 assert ctx.config == 'foo'
1471
1472 def test_keyring(self, cephadm_fs):
1473 cmd = ['shell']
1474 with with_cephadm_ctx(cmd) as ctx:
1475 retval = cd.command_shell(ctx)
1476 assert retval == 0
1477 assert ctx.keyring == None
1478
1479 cephadm_fs.create_file(cd.CEPH_DEFAULT_KEYRING)
1480 with with_cephadm_ctx(cmd) as ctx:
1481 retval = cd.command_shell(ctx)
1482 assert retval == 0
1483 assert ctx.keyring == cd.CEPH_DEFAULT_KEYRING
1484
1485 cmd = ['shell', '--keyring', 'foo']
1486 with with_cephadm_ctx(cmd) as ctx:
1487 retval = cd.command_shell(ctx)
1488 assert retval == 0
1489 assert ctx.keyring == 'foo'
1490
1491 @mock.patch('cephadm.CephContainer')
1492 def test_mount_no_dst(self, m_ceph_container, cephadm_fs):
1493 cmd = ['shell', '--mount', '/etc/foo']
1494 with with_cephadm_ctx(cmd) as ctx:
1495 retval = cd.command_shell(ctx)
1496 assert retval == 0
1497 assert m_ceph_container.call_args.kwargs['volume_mounts']['/etc/foo'] == '/mnt/foo'
1498
1499 @mock.patch('cephadm.CephContainer')
1500 def test_mount_with_dst_no_opt(self, m_ceph_container, cephadm_fs):
1501 cmd = ['shell', '--mount', '/etc/foo:/opt/foo/bar']
1502 with with_cephadm_ctx(cmd) as ctx:
1503 retval = cd.command_shell(ctx)
1504 assert retval == 0
1505 assert m_ceph_container.call_args.kwargs['volume_mounts']['/etc/foo'] == '/opt/foo/bar'
1506
1507 @mock.patch('cephadm.CephContainer')
1508 def test_mount_with_dst_and_opt(self, m_ceph_container, cephadm_fs):
1509 cmd = ['shell', '--mount', '/etc/foo:/opt/foo/bar:Z']
1510 with with_cephadm_ctx(cmd) as ctx:
1511 retval = cd.command_shell(ctx)
1512 assert retval == 0
1513 assert m_ceph_container.call_args.kwargs['volume_mounts']['/etc/foo'] == '/opt/foo/bar:Z'
1514
1515 class TestCephVolume(object):
1516
1517 @staticmethod
1518 def _get_cmd(*args):
1519 return [
1520 'ceph-volume',
1521 *args,
1522 '--', 'inventory', '--format', 'json'
1523 ]
1524
1525 def test_noop(self, cephadm_fs):
1526 cmd = self._get_cmd()
1527 with with_cephadm_ctx(cmd) as ctx:
1528 cd.command_ceph_volume(ctx)
1529 assert ctx.fsid == None
1530 assert ctx.config == None
1531 assert ctx.keyring == None
1532 assert ctx.config_json == None
1533
1534 def test_fsid(self, cephadm_fs):
1535 fsid = '00000000-0000-0000-0000-0000deadbeef'
1536
1537 cmd = self._get_cmd('--fsid', fsid)
1538 with with_cephadm_ctx(cmd) as ctx:
1539 cd.command_ceph_volume(ctx)
1540 assert ctx.fsid == fsid
1541
1542 cmd = self._get_cmd('--fsid', '00000000-0000-0000-0000-0000deadbeez')
1543 with with_cephadm_ctx(cmd) as ctx:
1544 err = 'not an fsid'
1545 with pytest.raises(cd.Error, match=err):
1546 retval = cd.command_shell(ctx)
1547 assert retval == 1
1548 assert ctx.fsid == None
1549
1550 s = get_ceph_conf(fsid=fsid)
1551 f = cephadm_fs.create_file('ceph.conf', contents=s)
1552
1553 cmd = self._get_cmd('--fsid', fsid, '--config', f.path)
1554 with with_cephadm_ctx(cmd) as ctx:
1555 cd.command_ceph_volume(ctx)
1556 assert ctx.fsid == fsid
1557
1558 cmd = self._get_cmd('--fsid', '10000000-0000-0000-0000-0000deadbeef', '--config', f.path)
1559 with with_cephadm_ctx(cmd) as ctx:
1560 err = 'fsid does not match ceph.conf'
1561 with pytest.raises(cd.Error, match=err):
1562 cd.command_ceph_volume(ctx)
1563 assert ctx.fsid == None
1564
1565 def test_config(self, cephadm_fs):
1566 cmd = self._get_cmd('--config', 'foo')
1567 with with_cephadm_ctx(cmd) as ctx:
1568 err = r'No such file or directory'
1569 with pytest.raises(cd.Error, match=err):
1570 cd.command_ceph_volume(ctx)
1571
1572 cephadm_fs.create_file('bar')
1573 cmd = self._get_cmd('--config', 'bar')
1574 with with_cephadm_ctx(cmd) as ctx:
1575 cd.command_ceph_volume(ctx)
1576 assert ctx.config == 'bar'
1577
1578 def test_keyring(self, cephadm_fs):
1579 cmd = self._get_cmd('--keyring', 'foo')
1580 with with_cephadm_ctx(cmd) as ctx:
1581 err = r'No such file or directory'
1582 with pytest.raises(cd.Error, match=err):
1583 cd.command_ceph_volume(ctx)
1584
1585 cephadm_fs.create_file('bar')
1586 cmd = self._get_cmd('--keyring', 'bar')
1587 with with_cephadm_ctx(cmd) as ctx:
1588 cd.command_ceph_volume(ctx)
1589 assert ctx.keyring == 'bar'
1590
1591
1592 class TestIscsi:
1593 def test_unit_run(self, cephadm_fs):
1594 fsid = '9b9d7609-f4d5-4aba-94c8-effa764d96c9'
1595 config_json = {
1596 'files': {'iscsi-gateway.cfg': ''}
1597 }
1598 with with_cephadm_ctx(['--image=ceph/ceph'], list_networks={}) as ctx:
1599 import json
1600 ctx.config_json = json.dumps(config_json)
1601 ctx.fsid = fsid
1602 cd.get_parm.return_value = config_json
1603 c = cd.get_container(ctx, fsid, 'iscsi', 'daemon_id')
1604
1605 cd.make_data_dir(ctx, fsid, 'iscsi', 'daemon_id')
1606 cd.deploy_daemon_units(
1607 ctx,
1608 fsid,
1609 0, 0,
1610 'iscsi',
1611 'daemon_id',
1612 c,
1613 True, True
1614 )
1615
1616 with open('/var/lib/ceph/9b9d7609-f4d5-4aba-94c8-effa764d96c9/iscsi.daemon_id/unit.run') as f:
1617 assert f.read() == """set -e
1618 if ! grep -qs /var/lib/ceph/9b9d7609-f4d5-4aba-94c8-effa764d96c9/iscsi.daemon_id/configfs /proc/mounts; then mount -t configfs none /var/lib/ceph/9b9d7609-f4d5-4aba-94c8-effa764d96c9/iscsi.daemon_id/configfs; fi
1619 # iscsi tcmu-runner container
1620 ! /usr/bin/podman rm -f ceph-9b9d7609-f4d5-4aba-94c8-effa764d96c9-iscsi.daemon_id-tcmu 2> /dev/null
1621 ! /usr/bin/podman rm -f ceph-9b9d7609-f4d5-4aba-94c8-effa764d96c9-iscsi-daemon_id-tcmu 2> /dev/null
1622 /usr/bin/podman run --rm --ipc=host --stop-signal=SIGTERM --net=host --entrypoint /usr/bin/tcmu-runner --privileged --group-add=disk --init --name ceph-9b9d7609-f4d5-4aba-94c8-effa764d96c9-iscsi-daemon_id-tcmu -e CONTAINER_IMAGE=ceph/ceph -e NODE_NAME=host1 -e CEPH_USE_RANDOM_NONCE=1 -v /var/lib/ceph/9b9d7609-f4d5-4aba-94c8-effa764d96c9/iscsi.daemon_id/config:/etc/ceph/ceph.conf:z -v /var/lib/ceph/9b9d7609-f4d5-4aba-94c8-effa764d96c9/iscsi.daemon_id/keyring:/etc/ceph/keyring:z -v /var/lib/ceph/9b9d7609-f4d5-4aba-94c8-effa764d96c9/iscsi.daemon_id/iscsi-gateway.cfg:/etc/ceph/iscsi-gateway.cfg:z -v /var/lib/ceph/9b9d7609-f4d5-4aba-94c8-effa764d96c9/iscsi.daemon_id/configfs:/sys/kernel/config -v /var/log/ceph/9b9d7609-f4d5-4aba-94c8-effa764d96c9:/var/log:z -v /dev:/dev --mount type=bind,source=/lib/modules,destination=/lib/modules,ro=true ceph/ceph &
1623 # iscsi.daemon_id
1624 ! /usr/bin/podman rm -f ceph-9b9d7609-f4d5-4aba-94c8-effa764d96c9-iscsi.daemon_id 2> /dev/null
1625 ! /usr/bin/podman rm -f ceph-9b9d7609-f4d5-4aba-94c8-effa764d96c9-iscsi-daemon_id 2> /dev/null
1626 /usr/bin/podman run --rm --ipc=host --stop-signal=SIGTERM --net=host --entrypoint /usr/bin/rbd-target-api --privileged --group-add=disk --init --name ceph-9b9d7609-f4d5-4aba-94c8-effa764d96c9-iscsi-daemon_id -e CONTAINER_IMAGE=ceph/ceph -e NODE_NAME=host1 -e CEPH_USE_RANDOM_NONCE=1 -v /var/lib/ceph/9b9d7609-f4d5-4aba-94c8-effa764d96c9/iscsi.daemon_id/config:/etc/ceph/ceph.conf:z -v /var/lib/ceph/9b9d7609-f4d5-4aba-94c8-effa764d96c9/iscsi.daemon_id/keyring:/etc/ceph/keyring:z -v /var/lib/ceph/9b9d7609-f4d5-4aba-94c8-effa764d96c9/iscsi.daemon_id/iscsi-gateway.cfg:/etc/ceph/iscsi-gateway.cfg:z -v /var/lib/ceph/9b9d7609-f4d5-4aba-94c8-effa764d96c9/iscsi.daemon_id/configfs:/sys/kernel/config -v /var/log/ceph/9b9d7609-f4d5-4aba-94c8-effa764d96c9:/var/log:z -v /dev:/dev --mount type=bind,source=/lib/modules,destination=/lib/modules,ro=true ceph/ceph
1627 """
1628
1629 def test_get_container(self):
1630 """
1631 Due to a combination of socket.getfqdn() and podman's behavior to
1632 add the container name into the /etc/hosts file, we cannot use periods
1633 in container names. But we need to be able to detect old existing containers.
1634 Assert this behaviour. I think we can remove this in Ceph R
1635 """
1636 fsid = '9b9d7609-f4d5-4aba-94c8-effa764d96c9'
1637 with with_cephadm_ctx(['--image=ceph/ceph'], list_networks={}) as ctx:
1638 ctx.fsid = fsid
1639 c = cd.get_container(ctx, fsid, 'iscsi', 'something')
1640 assert c.cname == 'ceph-9b9d7609-f4d5-4aba-94c8-effa764d96c9-iscsi-something'
1641 assert c.old_cname == 'ceph-9b9d7609-f4d5-4aba-94c8-effa764d96c9-iscsi.something'
1642
1643
1644 class TestCheckHost:
1645
1646 @mock.patch('cephadm.find_executable', return_value='foo')
1647 @mock.patch('cephadm.check_time_sync', return_value=True)
1648 def test_container_engine(self, find_executable, check_time_sync):
1649 ctx = cd.CephadmContext()
1650
1651 ctx.container_engine = None
1652 err = r'No container engine binary found'
1653 with pytest.raises(cd.Error, match=err):
1654 cd.command_check_host(ctx)
1655
1656 ctx.container_engine = mock_podman()
1657 cd.command_check_host(ctx)
1658
1659 ctx.container_engine = mock_docker()
1660 cd.command_check_host(ctx)
1661
1662
1663 class TestRmRepo:
1664
1665 @pytest.mark.parametrize('os_release',
1666 [
1667 # Apt
1668 dedent("""
1669 NAME="Ubuntu"
1670 VERSION="20.04 LTS (Focal Fossa)"
1671 ID=ubuntu
1672 ID_LIKE=debian
1673 PRETTY_NAME="Ubuntu 20.04 LTS"
1674 VERSION_ID="20.04"
1675 HOME_URL="https://www.ubuntu.com/"
1676 SUPPORT_URL="https://help.ubuntu.com/"
1677 BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
1678 PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
1679 VERSION_CODENAME=focal
1680 UBUNTU_CODENAME=focal
1681 """),
1682
1683 # YumDnf
1684 dedent("""
1685 NAME="CentOS Linux"
1686 VERSION="8 (Core)"
1687 ID="centos"
1688 ID_LIKE="rhel fedora"
1689 VERSION_ID="8"
1690 PLATFORM_ID="platform:el8"
1691 PRETTY_NAME="CentOS Linux 8 (Core)"
1692 ANSI_COLOR="0;31"
1693 CPE_NAME="cpe:/o:centos:centos:8"
1694 HOME_URL="https://www.centos.org/"
1695 BUG_REPORT_URL="https://bugs.centos.org/"
1696
1697 CENTOS_MANTISBT_PROJECT="CentOS-8"
1698 CENTOS_MANTISBT_PROJECT_VERSION="8"
1699 REDHAT_SUPPORT_PRODUCT="centos"
1700 REDHAT_SUPPORT_PRODUCT_VERSION="8"
1701 """),
1702
1703 # Zypper
1704 dedent("""
1705 NAME="openSUSE Tumbleweed"
1706 # VERSION="20210810"
1707 ID="opensuse-tumbleweed"
1708 ID_LIKE="opensuse suse"
1709 VERSION_ID="20210810"
1710 PRETTY_NAME="openSUSE Tumbleweed"
1711 ANSI_COLOR="0;32"
1712 CPE_NAME="cpe:/o:opensuse:tumbleweed:20210810"
1713 BUG_REPORT_URL="https://bugs.opensuse.org"
1714 HOME_URL="https://www.opensuse.org/"
1715 DOCUMENTATION_URL="https://en.opensuse.org/Portal:Tumbleweed"
1716 LOGO="distributor-logo"
1717 """),
1718 ])
1719 @mock.patch('cephadm.find_executable', return_value='foo')
1720 def test_container_engine(self, find_executable, os_release, cephadm_fs):
1721 cephadm_fs.create_file('/etc/os-release', contents=os_release)
1722 ctx = cd.CephadmContext()
1723
1724 ctx.container_engine = None
1725 cd.command_rm_repo(ctx)
1726
1727 ctx.container_engine = mock_podman()
1728 cd.command_rm_repo(ctx)
1729
1730 ctx.container_engine = mock_docker()
1731 cd.command_rm_repo(ctx)
1732
1733
1734 class TestValidateRepo:
1735
1736 @pytest.mark.parametrize('values',
1737 [
1738 # Apt - no checks
1739 dict(
1740 version="",
1741 release="pacific",
1742 err_text="",
1743 os_release=dedent("""
1744 NAME="Ubuntu"
1745 VERSION="20.04 LTS (Focal Fossa)"
1746 ID=ubuntu
1747 ID_LIKE=debian
1748 PRETTY_NAME="Ubuntu 20.04 LTS"
1749 VERSION_ID="20.04"
1750 HOME_URL="https://www.ubuntu.com/"
1751 SUPPORT_URL="https://help.ubuntu.com/"
1752 BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
1753 PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
1754 VERSION_CODENAME=focal
1755 UBUNTU_CODENAME=focal
1756 """)),
1757
1758 # YumDnf on Centos8 - OK
1759 dict(
1760 version="",
1761 release="pacific",
1762 err_text="",
1763 os_release=dedent("""
1764 NAME="CentOS Linux"
1765 VERSION="8 (Core)"
1766 ID="centos"
1767 ID_LIKE="rhel fedora"
1768 VERSION_ID="8"
1769 PLATFORM_ID="platform:el8"
1770 PRETTY_NAME="CentOS Linux 8 (Core)"
1771 ANSI_COLOR="0;31"
1772 CPE_NAME="cpe:/o:centos:centos:8"
1773 HOME_URL="https://www.centos.org/"
1774 BUG_REPORT_URL="https://bugs.centos.org/"
1775
1776 CENTOS_MANTISBT_PROJECT="CentOS-8"
1777 CENTOS_MANTISBT_PROJECT_VERSION="8"
1778 REDHAT_SUPPORT_PRODUCT="centos"
1779 REDHAT_SUPPORT_PRODUCT_VERSION="8"
1780 """)),
1781
1782 # YumDnf on Fedora - Fedora not supported
1783 dict(
1784 version="",
1785 release="pacific",
1786 err_text="does not build Fedora",
1787 os_release=dedent("""
1788 NAME="Fedora Linux"
1789 VERSION="35 (Cloud Edition)"
1790 ID=fedora
1791 VERSION_ID=35
1792 VERSION_CODENAME=""
1793 PLATFORM_ID="platform:f35"
1794 PRETTY_NAME="Fedora Linux 35 (Cloud Edition)"
1795 ANSI_COLOR="0;38;2;60;110;180"
1796 LOGO=fedora-logo-icon
1797 CPE_NAME="cpe:/o:fedoraproject:fedora:35"
1798 HOME_URL="https://fedoraproject.org/"
1799 DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora/f35/system-administrators-guide/"
1800 SUPPORT_URL="https://ask.fedoraproject.org/"
1801 BUG_REPORT_URL="https://bugzilla.redhat.com/"
1802 REDHAT_BUGZILLA_PRODUCT="Fedora"
1803 REDHAT_BUGZILLA_PRODUCT_VERSION=35
1804 REDHAT_SUPPORT_PRODUCT="Fedora"
1805 REDHAT_SUPPORT_PRODUCT_VERSION=35
1806 PRIVACY_POLICY_URL="https://fedoraproject.org/wiki/Legal:PrivacyPolicy"
1807 VARIANT="Cloud Edition"
1808 VARIANT_ID=cloud
1809 """)),
1810
1811 # YumDnf on Centos 7 - no pacific
1812 dict(
1813 version="",
1814 release="pacific",
1815 err_text="does not support pacific",
1816 os_release=dedent("""
1817 NAME="CentOS Linux"
1818 VERSION="7 (Core)"
1819 ID="centos"
1820 ID_LIKE="rhel fedora"
1821 VERSION_ID="7"
1822 PRETTY_NAME="CentOS Linux 7 (Core)"
1823 ANSI_COLOR="0;31"
1824 CPE_NAME="cpe:/o:centos:centos:7"
1825 HOME_URL="https://www.centos.org/"
1826 BUG_REPORT_URL="https://bugs.centos.org/"
1827
1828 CENTOS_MANTISBT_PROJECT="CentOS-7"
1829 CENTOS_MANTISBT_PROJECT_VERSION="7"
1830 REDHAT_SUPPORT_PRODUCT="centos"
1831 REDHAT_SUPPORT_PRODUCT_VERSION="7"
1832 """)),
1833
1834 # YumDnf on Centos 7 - nothing after pacific
1835 dict(
1836 version="",
1837 release="zillions",
1838 err_text="does not support pacific",
1839 os_release=dedent("""
1840 NAME="CentOS Linux"
1841 VERSION="7 (Core)"
1842 ID="centos"
1843 ID_LIKE="rhel fedora"
1844 VERSION_ID="7"
1845 PRETTY_NAME="CentOS Linux 7 (Core)"
1846 ANSI_COLOR="0;31"
1847 CPE_NAME="cpe:/o:centos:centos:7"
1848 HOME_URL="https://www.centos.org/"
1849 BUG_REPORT_URL="https://bugs.centos.org/"
1850
1851 CENTOS_MANTISBT_PROJECT="CentOS-7"
1852 CENTOS_MANTISBT_PROJECT_VERSION="7"
1853 REDHAT_SUPPORT_PRODUCT="centos"
1854 REDHAT_SUPPORT_PRODUCT_VERSION="7"
1855 """)),
1856
1857 # YumDnf on Centos 7 - nothing v16 or higher
1858 dict(
1859 version="v16.1.3",
1860 release="",
1861 err_text="does not support",
1862 os_release=dedent("""
1863 NAME="CentOS Linux"
1864 VERSION="7 (Core)"
1865 ID="centos"
1866 ID_LIKE="rhel fedora"
1867 VERSION_ID="7"
1868 PRETTY_NAME="CentOS Linux 7 (Core)"
1869 ANSI_COLOR="0;31"
1870 CPE_NAME="cpe:/o:centos:centos:7"
1871 HOME_URL="https://www.centos.org/"
1872 BUG_REPORT_URL="https://bugs.centos.org/"
1873
1874 CENTOS_MANTISBT_PROJECT="CentOS-7"
1875 CENTOS_MANTISBT_PROJECT_VERSION="7"
1876 REDHAT_SUPPORT_PRODUCT="centos"
1877 REDHAT_SUPPORT_PRODUCT_VERSION="7"
1878 """)),
1879 ])
1880 @mock.patch('cephadm.find_executable', return_value='foo')
1881 def test_distro_validation(self, find_executable, values, cephadm_fs):
1882 os_release = values['os_release']
1883 release = values['release']
1884 version = values['version']
1885 err_text = values['err_text']
1886
1887 cephadm_fs.create_file('/etc/os-release', contents=os_release)
1888 ctx = cd.CephadmContext()
1889 ctx.repo_url = 'http://localhost'
1890 pkg = cd.create_packager(ctx, stable=release, version=version)
1891
1892 if err_text:
1893 with pytest.raises(cd.Error, match=err_text):
1894 pkg.validate()
1895 else:
1896 with mock.patch('cephadm.urlopen', return_value=None):
1897 pkg.validate()
1898
1899 @pytest.mark.parametrize('values',
1900 [
1901 # Apt - not checked
1902 dict(
1903 version="",
1904 release="pacific",
1905 err_text="",
1906 os_release=dedent("""
1907 NAME="Ubuntu"
1908 VERSION="20.04 LTS (Focal Fossa)"
1909 ID=ubuntu
1910 ID_LIKE=debian
1911 PRETTY_NAME="Ubuntu 20.04 LTS"
1912 VERSION_ID="20.04"
1913 HOME_URL="https://www.ubuntu.com/"
1914 SUPPORT_URL="https://help.ubuntu.com/"
1915 BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
1916 PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
1917 VERSION_CODENAME=focal
1918 UBUNTU_CODENAME=focal
1919 """)),
1920
1921 # YumDnf on Centos8 - force failure
1922 dict(
1923 version="",
1924 release="foobar",
1925 err_text="failed to fetch repository metadata",
1926 os_release=dedent("""
1927 NAME="CentOS Linux"
1928 VERSION="8 (Core)"
1929 ID="centos"
1930 ID_LIKE="rhel fedora"
1931 VERSION_ID="8"
1932 PLATFORM_ID="platform:el8"
1933 PRETTY_NAME="CentOS Linux 8 (Core)"
1934 ANSI_COLOR="0;31"
1935 CPE_NAME="cpe:/o:centos:centos:8"
1936 HOME_URL="https://www.centos.org/"
1937 BUG_REPORT_URL="https://bugs.centos.org/"
1938
1939 CENTOS_MANTISBT_PROJECT="CentOS-8"
1940 CENTOS_MANTISBT_PROJECT_VERSION="8"
1941 REDHAT_SUPPORT_PRODUCT="centos"
1942 REDHAT_SUPPORT_PRODUCT_VERSION="8"
1943 """)),
1944 ])
1945 @mock.patch('cephadm.find_executable', return_value='foo')
1946 def test_http_validation(self, find_executable, values, cephadm_fs):
1947 from urllib.error import HTTPError
1948
1949 os_release = values['os_release']
1950 release = values['release']
1951 version = values['version']
1952 err_text = values['err_text']
1953
1954 cephadm_fs.create_file('/etc/os-release', contents=os_release)
1955 ctx = cd.CephadmContext()
1956 ctx.repo_url = 'http://localhost'
1957 pkg = cd.create_packager(ctx, stable=release, version=version)
1958
1959 with mock.patch('cephadm.urlopen') as _urlopen:
1960 _urlopen.side_effect = HTTPError(ctx.repo_url, 404, "not found", None, fp=None)
1961 if err_text:
1962 with pytest.raises(cd.Error, match=err_text):
1963 pkg.validate()
1964 else:
1965 pkg.validate()
1966
1967
1968 class TestPull:
1969
1970 @mock.patch('time.sleep')
1971 @mock.patch('cephadm.call', return_value=('', '', 0))
1972 @mock.patch('cephadm.get_image_info_from_inspect', return_value={})
1973 def test_error(self, get_image_info_from_inspect, call, sleep):
1974 ctx = cd.CephadmContext()
1975 ctx.container_engine = mock_podman()
1976 ctx.insecure = False
1977
1978 call.return_value = ('', '', 0)
1979 retval = cd.command_pull(ctx)
1980 assert retval == 0
1981
1982 err = 'maximum retries reached'
1983
1984 call.return_value = ('', 'foobar', 1)
1985 with pytest.raises(cd.Error) as e:
1986 cd.command_pull(ctx)
1987 assert err not in str(e.value)
1988
1989 call.return_value = ('', 'net/http: TLS handshake timeout', 1)
1990 with pytest.raises(cd.Error) as e:
1991 cd.command_pull(ctx)
1992 assert err in str(e.value)
1993
1994 @mock.patch('cephadm.logger')
1995 @mock.patch('cephadm.get_image_info_from_inspect', return_value={})
1996 @mock.patch('cephadm.infer_local_ceph_image', return_value='last_local_ceph_image')
1997 def test_image(self, infer_local_ceph_image, get_image_info_from_inspect, logger):
1998 cmd = ['pull']
1999 with with_cephadm_ctx(cmd) as ctx:
2000 retval = cd.command_pull(ctx)
2001 assert retval == 0
2002 assert ctx.image == cd.DEFAULT_IMAGE
2003
2004 with mock.patch.dict(os.environ, {"CEPHADM_IMAGE": 'cephadm_image_environ'}):
2005 cmd = ['pull']
2006 with with_cephadm_ctx(cmd) as ctx:
2007 retval = cd.command_pull(ctx)
2008 assert retval == 0
2009 assert ctx.image == 'cephadm_image_environ'
2010
2011 cmd = ['--image', 'cephadm_image_param', 'pull']
2012 with with_cephadm_ctx(cmd) as ctx:
2013 retval = cd.command_pull(ctx)
2014 assert retval == 0
2015 assert ctx.image == 'cephadm_image_param'
2016
2017
2018 class TestApplySpec:
2019
2020 def test_parse_yaml(self, cephadm_fs):
2021 yaml = '''---
2022 service_type: host
2023 hostname: vm-00
2024 addr: 192.168.122.44
2025 labels:
2026 - example1
2027 - example2
2028 ---
2029 service_type: host
2030 hostname: vm-01
2031 addr: 192.168.122.247
2032 labels:
2033 - grafana
2034 ---
2035 service_type: host
2036 hostname: vm-02
2037 addr: 192.168.122.165
2038 ---
2039 ---
2040 service_type: rgw
2041 service_id: myrgw
2042 spec:
2043 rgw_frontend_ssl_certificate: |
2044 -----BEGIN PRIVATE KEY-----
2045 V2VyIGRhcyBsaWVzdCBpc3QgZG9vZi4gTG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFt
2046 ZXQsIGNvbnNldGV0dXIgc2FkaXBzY2luZyBlbGl0ciwgc2VkIGRpYW0gbm9udW15
2047 IGVpcm1vZCB0ZW1wb3IgaW52aWR1bnQgdXQgbGFib3JlIGV0IGRvbG9yZSBtYWdu
2048 YSBhbGlxdXlhbSBlcmF0LCBzZWQgZGlhbSB2b2x1cHR1YS4gQXQgdmVybyBlb3Mg
2049 ZXQgYWNjdXNhbSBldCBqdXN0byBkdW8=
2050 -----END PRIVATE KEY-----
2051 -----BEGIN CERTIFICATE-----
2052 V2VyIGRhcyBsaWVzdCBpc3QgZG9vZi4gTG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFt
2053 ZXQsIGNvbnNldGV0dXIgc2FkaXBzY2luZyBlbGl0ciwgc2VkIGRpYW0gbm9udW15
2054 IGVpcm1vZCB0ZW1wb3IgaW52aWR1bnQgdXQgbGFib3JlIGV0IGRvbG9yZSBtYWdu
2055 YSBhbGlxdXlhbSBlcmF0LCBzZWQgZGlhbSB2b2x1cHR1YS4gQXQgdmVybyBlb3Mg
2056 ZXQgYWNjdXNhbSBldCBqdXN0byBkdW8=
2057 -----END CERTIFICATE-----
2058 ssl: true
2059 ---
2060 '''
2061
2062 cephadm_fs.create_file('spec.yml', contents=yaml)
2063 retdic = [{'service_type': 'host', 'hostname': 'vm-00', 'addr': '192.168.122.44', 'labels': '- example1- example2'},
2064 {'service_type': 'host', 'hostname': 'vm-01', 'addr': '192.168.122.247', 'labels': '- grafana'},
2065 {'service_type': 'host', 'hostname': 'vm-02', 'addr': '192.168.122.165'},
2066 {'service_id': 'myrgw',
2067 'service_type': 'rgw',
2068 'spec':
2069 'rgw_frontend_ssl_certificate: |-----BEGIN PRIVATE '
2070 'KEY-----V2VyIGRhcyBsaWVzdCBpc3QgZG9vZi4gTG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNldGV0dXIgc2FkaXBzY2luZyBlbGl0ciwgc2VkIGRpYW0gbm9udW15IGVpcm1vZCB0ZW1wb3IgaW52aWR1bnQgdXQgbGFib3JlIGV0IGRvbG9yZSBtYWduYSBhbGlxdXlhbSBlcmF0LCBzZWQgZGlhbSB2b2x1cHR1YS4gQXQgdmVybyBlb3MgZXQgYWNjdXNhbSBldCBqdXN0byBkdW8=-----END '
2071 'PRIVATE KEY----------BEGIN '
2072 'CERTIFICATE-----V2VyIGRhcyBsaWVzdCBpc3QgZG9vZi4gTG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNldGV0dXIgc2FkaXBzY2luZyBlbGl0ciwgc2VkIGRpYW0gbm9udW15IGVpcm1vZCB0ZW1wb3IgaW52aWR1bnQgdXQgbGFib3JlIGV0IGRvbG9yZSBtYWduYSBhbGlxdXlhbSBlcmF0LCBzZWQgZGlhbSB2b2x1cHR1YS4gQXQgdmVybyBlb3MgZXQgYWNjdXNhbSBldCBqdXN0byBkdW8=-----END '
2073 'CERTIFICATE-----ssl: true'}]
2074
2075 with open('spec.yml') as f:
2076 dic = cd.parse_yaml_objs(f)
2077 assert dic == retdic
2078
2079 @mock.patch('cephadm.call', return_value=('', '', 0))
2080 def test_distribute_ssh_keys(self, call):
2081 ctx = cd.CephadmContext()
2082 ctx.ssh_public_key = None
2083 ctx.ssh_user = 'root'
2084
2085 host_spec = {'service_type': 'host', 'hostname': 'vm-02', 'addr': '192.168.122.165'}
2086
2087 retval = cd._distribute_ssh_keys(ctx, host_spec, 'bootstrap_hostname')
2088
2089 assert retval == 0
2090
2091 call.return_value = ('', '', 1)
2092
2093 retval = cd._distribute_ssh_keys(ctx, host_spec, 'bootstrap_hostname')
2094
2095 assert retval == 1
2096
2097
2098 class TestSNMPGateway:
2099 V2c_config = {
2100 'snmp_community': 'public',
2101 'destination': '192.168.1.10:162',
2102 'snmp_version': 'V2c',
2103 }
2104 V3_no_priv_config = {
2105 'destination': '192.168.1.10:162',
2106 'snmp_version': 'V3',
2107 'snmp_v3_auth_username': 'myuser',
2108 'snmp_v3_auth_password': 'mypassword',
2109 'snmp_v3_auth_protocol': 'SHA',
2110 'snmp_v3_engine_id': '8000C53F00000000',
2111 }
2112 V3_priv_config = {
2113 'destination': '192.168.1.10:162',
2114 'snmp_version': 'V3',
2115 'snmp_v3_auth_username': 'myuser',
2116 'snmp_v3_auth_password': 'mypassword',
2117 'snmp_v3_auth_protocol': 'SHA',
2118 'snmp_v3_priv_protocol': 'DES',
2119 'snmp_v3_priv_password': 'mysecret',
2120 'snmp_v3_engine_id': '8000C53F00000000',
2121 }
2122 no_destination_config = {
2123 'snmp_version': 'V3',
2124 'snmp_v3_auth_username': 'myuser',
2125 'snmp_v3_auth_password': 'mypassword',
2126 'snmp_v3_auth_protocol': 'SHA',
2127 'snmp_v3_priv_protocol': 'DES',
2128 'snmp_v3_priv_password': 'mysecret',
2129 'snmp_v3_engine_id': '8000C53F00000000',
2130 }
2131 bad_version_config = {
2132 'snmp_community': 'public',
2133 'destination': '192.168.1.10:162',
2134 'snmp_version': 'V1',
2135 }
2136
2137 def test_unit_run_V2c(self, cephadm_fs):
2138 fsid = 'ca734440-3dc6-11ec-9b98-5254002537a6'
2139 with with_cephadm_ctx(['--image=docker.io/maxwo/snmp-notifier:v1.2.1'], list_networks={}) as ctx:
2140 import json
2141 ctx.config_json = json.dumps(self.V2c_config)
2142 ctx.fsid = fsid
2143 ctx.tcp_ports = '9464'
2144 cd.get_parm.return_value = self.V2c_config
2145 c = cd.get_container(ctx, fsid, 'snmp-gateway', 'daemon_id')
2146
2147 cd.make_data_dir(ctx, fsid, 'snmp-gateway', 'daemon_id')
2148
2149 cd.create_daemon_dirs(ctx, fsid, 'snmp-gateway', 'daemon_id', 0, 0)
2150 with open(f'/var/lib/ceph/{fsid}/snmp-gateway.daemon_id/snmp-gateway.conf', 'r') as f:
2151 conf = f.read().rstrip()
2152 assert conf == 'SNMP_NOTIFIER_COMMUNITY=public'
2153
2154 cd.deploy_daemon_units(
2155 ctx,
2156 fsid,
2157 0, 0,
2158 'snmp-gateway',
2159 'daemon_id',
2160 c,
2161 True, True
2162 )
2163 with open(f'/var/lib/ceph/{fsid}/snmp-gateway.daemon_id/unit.run', 'r') as f:
2164 run_cmd = f.readlines()[-1].rstrip()
2165 assert run_cmd.endswith('docker.io/maxwo/snmp-notifier:v1.2.1 --web.listen-address=:9464 --snmp.destination=192.168.1.10:162 --snmp.version=V2c --log.level=info --snmp.trap-description-template=/etc/snmp_notifier/description-template.tpl')
2166
2167 def test_unit_run_V3_noPriv(self, cephadm_fs):
2168 fsid = 'ca734440-3dc6-11ec-9b98-5254002537a6'
2169 with with_cephadm_ctx(['--image=docker.io/maxwo/snmp-notifier:v1.2.1'], list_networks={}) as ctx:
2170 import json
2171 ctx.config_json = json.dumps(self.V3_no_priv_config)
2172 ctx.fsid = fsid
2173 ctx.tcp_ports = '9465'
2174 cd.get_parm.return_value = self.V3_no_priv_config
2175 c = cd.get_container(ctx, fsid, 'snmp-gateway', 'daemon_id')
2176
2177 cd.make_data_dir(ctx, fsid, 'snmp-gateway', 'daemon_id')
2178
2179 cd.create_daemon_dirs(ctx, fsid, 'snmp-gateway', 'daemon_id', 0, 0)
2180 with open(f'/var/lib/ceph/{fsid}/snmp-gateway.daemon_id/snmp-gateway.conf', 'r') as f:
2181 conf = f.read()
2182 assert conf == 'SNMP_NOTIFIER_AUTH_USERNAME=myuser\nSNMP_NOTIFIER_AUTH_PASSWORD=mypassword\n'
2183
2184 cd.deploy_daemon_units(
2185 ctx,
2186 fsid,
2187 0, 0,
2188 'snmp-gateway',
2189 'daemon_id',
2190 c,
2191 True, True
2192 )
2193 with open(f'/var/lib/ceph/{fsid}/snmp-gateway.daemon_id/unit.run', 'r') as f:
2194 run_cmd = f.readlines()[-1].rstrip()
2195 assert run_cmd.endswith('docker.io/maxwo/snmp-notifier:v1.2.1 --web.listen-address=:9465 --snmp.destination=192.168.1.10:162 --snmp.version=V3 --log.level=info --snmp.trap-description-template=/etc/snmp_notifier/description-template.tpl --snmp.authentication-enabled --snmp.authentication-protocol=SHA --snmp.security-engine-id=8000C53F00000000')
2196
2197 def test_unit_run_V3_Priv(self, cephadm_fs):
2198 fsid = 'ca734440-3dc6-11ec-9b98-5254002537a6'
2199 with with_cephadm_ctx(['--image=docker.io/maxwo/snmp-notifier:v1.2.1'], list_networks={}) as ctx:
2200 import json
2201 ctx.config_json = json.dumps(self.V3_priv_config)
2202 ctx.fsid = fsid
2203 ctx.tcp_ports = '9464'
2204 cd.get_parm.return_value = self.V3_priv_config
2205 c = cd.get_container(ctx, fsid, 'snmp-gateway', 'daemon_id')
2206
2207 cd.make_data_dir(ctx, fsid, 'snmp-gateway', 'daemon_id')
2208
2209 cd.create_daemon_dirs(ctx, fsid, 'snmp-gateway', 'daemon_id', 0, 0)
2210 with open(f'/var/lib/ceph/{fsid}/snmp-gateway.daemon_id/snmp-gateway.conf', 'r') as f:
2211 conf = f.read()
2212 assert conf == 'SNMP_NOTIFIER_AUTH_USERNAME=myuser\nSNMP_NOTIFIER_AUTH_PASSWORD=mypassword\nSNMP_NOTIFIER_PRIV_PASSWORD=mysecret\n'
2213
2214 cd.deploy_daemon_units(
2215 ctx,
2216 fsid,
2217 0, 0,
2218 'snmp-gateway',
2219 'daemon_id',
2220 c,
2221 True, True
2222 )
2223 with open(f'/var/lib/ceph/{fsid}/snmp-gateway.daemon_id/unit.run', 'r') as f:
2224 run_cmd = f.readlines()[-1].rstrip()
2225 assert run_cmd.endswith('docker.io/maxwo/snmp-notifier:v1.2.1 --web.listen-address=:9464 --snmp.destination=192.168.1.10:162 --snmp.version=V3 --log.level=info --snmp.trap-description-template=/etc/snmp_notifier/description-template.tpl --snmp.authentication-enabled --snmp.authentication-protocol=SHA --snmp.security-engine-id=8000C53F00000000 --snmp.private-enabled --snmp.private-protocol=DES')
2226
2227 def test_unit_run_no_dest(self, cephadm_fs):
2228 fsid = 'ca734440-3dc6-11ec-9b98-5254002537a6'
2229 with with_cephadm_ctx(['--image=docker.io/maxwo/snmp-notifier:v1.2.1'], list_networks={}) as ctx:
2230 import json
2231 ctx.config_json = json.dumps(self.no_destination_config)
2232 ctx.fsid = fsid
2233 ctx.tcp_ports = '9464'
2234 cd.get_parm.return_value = self.no_destination_config
2235
2236 with pytest.raises(Exception) as e:
2237 c = cd.get_container(ctx, fsid, 'snmp-gateway', 'daemon_id')
2238 assert str(e.value) == "config is missing destination attribute(<ip>:<port>) of the target SNMP listener"
2239
2240 def test_unit_run_bad_version(self, cephadm_fs):
2241 fsid = 'ca734440-3dc6-11ec-9b98-5254002537a6'
2242 with with_cephadm_ctx(['--image=docker.io/maxwo/snmp-notifier:v1.2.1'], list_networks={}) as ctx:
2243 import json
2244 ctx.config_json = json.dumps(self.bad_version_config)
2245 ctx.fsid = fsid
2246 ctx.tcp_ports = '9464'
2247 cd.get_parm.return_value = self.bad_version_config
2248
2249 with pytest.raises(Exception) as e:
2250 c = cd.get_container(ctx, fsid, 'snmp-gateway', 'daemon_id')
2251 assert str(e.value) == 'not a valid snmp version: V1'
2252
2253 class TestNetworkValidation:
2254
2255 def test_ipv4_subnet(self):
2256 rc, v, msg = cd.check_subnet('192.168.1.0/24')
2257 assert rc == 0 and v[0] == 4
2258
2259 def test_ipv4_subnet_list(self):
2260 rc, v, msg = cd.check_subnet('192.168.1.0/24,10.90.90.0/24')
2261 assert rc == 0 and not msg
2262
2263 def test_ipv4_subnet_list_with_spaces(self):
2264 rc, v, msg = cd.check_subnet('192.168.1.0/24, 10.90.90.0/24 ')
2265 assert rc == 0 and not msg
2266
2267 def test_ipv4_subnet_badlist(self):
2268 rc, v, msg = cd.check_subnet('192.168.1.0/24,192.168.1.1')
2269 assert rc == 1 and msg
2270
2271 def test_ipv4_subnet_mixed(self):
2272 rc, v, msg = cd.check_subnet('192.168.100.0/24,fe80::/64')
2273 assert rc == 0 and v == [4,6]
2274
2275 def test_ipv6_subnet(self):
2276 rc, v, msg = cd.check_subnet('fe80::/64')
2277 assert rc == 0 and v[0] == 6
2278
2279 def test_subnet_mask_missing(self):
2280 rc, v, msg = cd.check_subnet('192.168.1.58')
2281 assert rc == 1 and msg
2282
2283 def test_subnet_mask_junk(self):
2284 rc, v, msg = cd.check_subnet('wah')
2285 assert rc == 1 and msg
2286
2287 def test_ip_in_subnet(self):
2288 # valid ip and only one valid subnet
2289 rc = cd.ip_in_subnets('192.168.100.1', '192.168.100.0/24')
2290 assert rc is True
2291
2292 # valid ip and valid subnets list without spaces
2293 rc = cd.ip_in_subnets('192.168.100.1', '192.168.100.0/24,10.90.90.0/24')
2294 assert rc is True
2295
2296 # valid ip and valid subnets list with spaces
2297 rc = cd.ip_in_subnets('10.90.90.2', '192.168.1.0/24, 192.168.100.0/24, 10.90.90.0/24')
2298 assert rc is True
2299
2300 # valid ip that doesn't belong to any subnet
2301 rc = cd.ip_in_subnets('192.168.100.2', '192.168.50.0/24, 10.90.90.0/24')
2302 assert rc is False
2303
2304 # valid ip that doesn't belong to the subnet (only 14 hosts)
2305 rc = cd.ip_in_subnets('192.168.100.20', '192.168.100.0/28')
2306 assert rc is False
2307
2308 # valid ip and valid IPV6 network
2309 rc = cd.ip_in_subnets('fe80::5054:ff:fef4:873a', 'fe80::/64')
2310 assert rc is True
2311
2312 # valid wrapped ip and valid IPV6 network
2313 rc = cd.ip_in_subnets('[fe80::5054:ff:fef4:873a]', 'fe80::/64')
2314 assert rc is True
2315
2316 # valid ip and that doesn't belong to IPV6 network
2317 rc = cd.ip_in_subnets('fe80::5054:ff:fef4:873a', '2001:db8:85a3::/64')
2318 assert rc is False
2319
2320 # invalid IPv4 and valid subnets list
2321 with pytest.raises(Exception):
2322 rc = cd.ip_in_sublets('10.90.200.', '192.168.1.0/24, 192.168.100.0/24, 10.90.90.0/24')
2323
2324 # invalid IPv6 and valid subnets list
2325 with pytest.raises(Exception):
2326 rc = cd.ip_in_sublets('fe80:2030:31:24', 'fe80::/64')