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