10 from textwrap
import dedent
12 from .fixtures
import (
20 from pyfakefs
import fake_filesystem
21 from pyfakefs
import fake_filesystem_unittest
23 with mock
.patch('builtins.open', create
=True):
24 from importlib
.machinery
import SourceFileLoader
25 cd
= SourceFileLoader('cephadm', 'cephadm').load_module()
29 fsid
='00000000-0000-0000-0000-0000deadbeef',
30 mon_host
='[v2:192.168.1.1:3300/0,v1:192.168.1.1:6789/0]'):
32 # minimal ceph.conf for {fsid}
38 class TestCephAdm(object):
40 def test_docker_unit_file(self
):
41 ctx
= cd
.CephadmContext()
42 ctx
.container_engine
= mock_docker()
43 r
= cd
.get_unit_file(ctx
, '9b9d7609-f4d5-4aba-94c8-effa764d96c9')
44 assert 'Requires=docker.service' in r
45 ctx
.container_engine
= mock_podman()
46 r
= cd
.get_unit_file(ctx
, '9b9d7609-f4d5-4aba-94c8-effa764d96c9')
47 assert 'Requires=docker.service' not in r
49 @mock.patch('cephadm.logger')
50 def test_attempt_bind(self
, logger
):
57 _os_error
.errno
= errno
60 for side_effect
, expected_exception
in (
61 (os_error(errno
.EADDRINUSE
), cd
.PortOccupiedError
),
62 (os_error(errno
.EAFNOSUPPORT
), cd
.Error
),
63 (os_error(errno
.EADDRNOTAVAIL
), cd
.Error
),
67 _socket
.bind
.side_effect
= side_effect
69 cd
.attempt_bind(ctx
, _socket
, address
, port
)
70 except Exception as e
:
71 assert isinstance(e
, expected_exception
)
73 if expected_exception
is not None:
76 @mock.patch('cephadm.attempt_bind')
77 @mock.patch('cephadm.logger')
78 def test_port_in_use(self
, logger
, attempt_bind
):
81 assert cd
.port_in_use(empty_ctx
, 9100) == False
83 attempt_bind
.side_effect
= cd
.PortOccupiedError('msg')
84 assert cd
.port_in_use(empty_ctx
, 9100) == True
87 os_error
.errno
= errno
.EADDRNOTAVAIL
88 attempt_bind
.side_effect
= os_error
89 assert cd
.port_in_use(empty_ctx
, 9100) == False
92 os_error
.errno
= errno
.EAFNOSUPPORT
93 attempt_bind
.side_effect
= os_error
94 assert cd
.port_in_use(empty_ctx
, 9100) == False
96 @mock.patch('socket.socket')
97 @mock.patch('cephadm.logger')
98 def test_check_ip_port_success(self
, logger
, _socket
):
99 ctx
= cd
.CephadmContext()
100 ctx
.skip_ping_check
= False # enables executing port check with `check_ip_port`
102 for address
, address_family
in (
103 ('0.0.0.0', socket
.AF_INET
),
104 ('::', socket
.AF_INET6
),
107 cd
.check_ip_port(ctx
, cd
.EndPoint(address
, 9100))
111 assert _socket
.call_args
== mock
.call(address_family
, socket
.SOCK_STREAM
)
113 @mock.patch('socket.socket')
114 @mock.patch('cephadm.logger')
115 def test_check_ip_port_failure(self
, logger
, _socket
):
116 ctx
= cd
.CephadmContext()
117 ctx
.skip_ping_check
= False # enables executing port check with `check_ip_port`
120 _os_error
= OSError()
121 _os_error
.errno
= errno
124 for address
, address_family
in (
125 ('0.0.0.0', socket
.AF_INET
),
126 ('::', socket
.AF_INET6
),
128 for side_effect
, expected_exception
in (
129 (os_error(errno
.EADDRINUSE
), cd
.PortOccupiedError
),
130 (os_error(errno
.EADDRNOTAVAIL
), cd
.Error
),
131 (os_error(errno
.EAFNOSUPPORT
), cd
.Error
),
134 mock_socket_obj
= mock
.Mock()
135 mock_socket_obj
.bind
.side_effect
= side_effect
136 _socket
.return_value
= mock_socket_obj
138 cd
.check_ip_port(ctx
, cd
.EndPoint(address
, 9100))
139 except Exception as e
:
140 assert isinstance(e
, expected_exception
)
142 if side_effect
is not None:
146 def test_is_not_fsid(self
):
147 assert not cd
.is_fsid('no-uuid')
149 def test_is_fsid(self
):
150 assert cd
.is_fsid('e863154d-33c7-4350-bca5-921e0467e55b')
152 def test__get_parser_image(self
):
153 args
= cd
._parse
_args
(['--image', 'foo', 'version'])
154 assert args
.image
== 'foo'
156 def test_parse_mem_usage(self
):
157 cd
.logger
= mock
.Mock()
158 len, summary
= cd
._parse
_mem
_usage
(0, 'c6290e3f1489,-- / --')
161 def test_CustomValidation(self
):
162 assert cd
._parse
_args
(['deploy', '--name', 'mon.a', '--fsid', 'fsid'])
164 with pytest
.raises(SystemExit):
165 cd
._parse
_args
(['deploy', '--name', 'wrong', '--fsid', 'fsid'])
167 @pytest.mark
.parametrize("test_input, expected", [
169 ("1.6.2-stable2", (1,6,2)),
171 def test_parse_podman_version(self
, test_input
, expected
):
172 assert cd
._parse
_podman
_version
(test_input
) == expected
174 def test_parse_podman_version_invalid(self
):
175 with pytest
.raises(ValueError) as res
:
176 cd
._parse
_podman
_version
('inval.id')
177 assert 'inval' in str(res
.value
)
179 def test_is_ipv6(self
):
180 cd
.logger
= mock
.Mock()
181 for good
in ("[::1]", "::1",
182 "fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"):
183 assert cd
.is_ipv6(good
)
184 for bad
in ("127.0.0.1",
185 "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffg",
186 "1:2:3:4:5:6:7:8:9", "fd00::1::1", "[fg::1]"):
187 assert not cd
.is_ipv6(bad
)
189 def test_unwrap_ipv6(self
):
190 def unwrap_test(address
, expected
):
191 assert cd
.unwrap_ipv6(address
) == expected
194 ('::1', '::1'), ('[::1]', '::1'),
195 ('[fde4:8dba:82e1:0:5054:ff:fe6a:357]', 'fde4:8dba:82e1:0:5054:ff:fe6a:357'),
196 ('can actually be any string', 'can actually be any string'),
197 ('[but needs to be stripped] ', '[but needs to be stripped] ')]
198 for address
, expected
in tests
:
199 unwrap_test(address
, expected
)
201 def test_wrap_ipv6(self
):
202 def wrap_test(address
, expected
):
203 assert cd
.wrap_ipv6(address
) == expected
206 ('::1', '[::1]'), ('[::1]', '[::1]'),
207 ('fde4:8dba:82e1:0:5054:ff:fe6a:357',
208 '[fde4:8dba:82e1:0:5054:ff:fe6a:357]'),
209 ('myhost.example.com', 'myhost.example.com'),
210 ('192.168.0.1', '192.168.0.1'),
211 ('', ''), ('fd00::1::1', 'fd00::1::1')]
212 for address
, expected
in tests
:
213 wrap_test(address
, expected
)
215 @mock.patch('cephadm.Firewalld', mock_bad_firewalld
)
216 @mock.patch('cephadm.logger')
217 def test_skip_firewalld(self
, logger
, cephadm_fs
):
219 test --skip-firewalld actually skips changing firewall
222 ctx
= cd
.CephadmContext()
223 with pytest
.raises(Exception):
224 cd
.update_firewalld(ctx
, 'mon')
226 ctx
.skip_firewalld
= True
227 cd
.update_firewalld(ctx
, 'mon')
229 ctx
.skip_firewalld
= False
230 with pytest
.raises(Exception):
231 cd
.update_firewalld(ctx
, 'mon')
233 ctx
= cd
.CephadmContext()
234 ctx
.ssl_dashboard_port
= 8888
235 ctx
.dashboard_key
= None
236 ctx
.dashboard_password_noupdate
= True
237 ctx
.initial_dashboard_password
= 'password'
238 ctx
.initial_dashboard_user
= 'User'
239 with pytest
.raises(Exception):
240 cd
.prepare_dashboard(ctx
, 0, 0, lambda _
, extra_mounts
=None, ___
=None : '5', lambda : None)
242 ctx
.skip_firewalld
= True
243 cd
.prepare_dashboard(ctx
, 0, 0, lambda _
, extra_mounts
=None, ___
=None : '5', lambda : None)
245 ctx
.skip_firewalld
= False
246 with pytest
.raises(Exception):
247 cd
.prepare_dashboard(ctx
, 0, 0, lambda _
, extra_mounts
=None, ___
=None : '5', lambda : None)
249 @mock.patch('cephadm.logger')
250 @mock.patch('cephadm.get_custom_config_files')
251 @mock.patch('cephadm.get_container')
252 def test_get_deployment_container(self
, _get_container
, _get_config
, logger
):
254 test get_deployment_container properly makes use of extra container args and custom conf files
257 ctx
= cd
.CephadmContext()
258 ctx
.config_json
= '-'
259 ctx
.extra_container_args
= [
260 '--pids-limit=12345',
263 ctx
.data_dir
= 'data'
264 _get_config
.return_value
= {'custom_config_files': [
266 'mount_path': '/etc/testing.str',
267 'content': 'this\nis\na\nstring',
270 _get_container
.return_value
= cd
.CephContainer
.for_daemon(
272 fsid
='9b9d7609-f4d5-4aba-94c8-effa764d96c9',
273 daemon_type
='grafana',
285 c
= cd
.get_deployment_container(ctx
,
286 '9b9d7609-f4d5-4aba-94c8-effa764d96c9',
290 assert '--pids-limit=12345' in c
.container_args
291 assert '--something' in c
.container_args
292 assert os
.path
.join('data', '9b9d7609-f4d5-4aba-94c8-effa764d96c9', 'custom_config_files', 'grafana.host1', 'testing.str') in c
.volume_mounts
293 assert c
.volume_mounts
[os
.path
.join('data', '9b9d7609-f4d5-4aba-94c8-effa764d96c9', 'custom_config_files', 'grafana.host1', 'testing.str')] == '/etc/testing.str'
295 @mock.patch('cephadm.logger')
296 @mock.patch('cephadm.get_custom_config_files')
297 def test_write_custom_conf_files(self
, _get_config
, logger
, cephadm_fs
):
299 test _write_custom_conf_files writes the conf files correctly
302 ctx
= cd
.CephadmContext()
303 ctx
.config_json
= '-'
304 ctx
.data_dir
= cd
.DATA_DIR
305 _get_config
.return_value
= {'custom_config_files': [
307 'mount_path': '/etc/testing.str',
308 'content': 'this\nis\na\nstring',
311 'mount_path': '/etc/testing.conf',
312 'content': 'very_cool_conf_setting: very_cool_conf_value\nx: y',
315 'mount_path': '/etc/no-content.conf',
318 cd
._write
_custom
_conf
_files
(ctx
, 'mon', 'host1', 'fsid', 0, 0)
319 with
open(os
.path
.join(cd
.DATA_DIR
, 'fsid', 'custom_config_files', 'mon.host1', 'testing.str'), 'r') as f
:
320 assert 'this\nis\na\nstring' == f
.read()
321 with
open(os
.path
.join(cd
.DATA_DIR
, 'fsid', 'custom_config_files', 'mon.host1', 'testing.conf'), 'r') as f
:
322 assert 'very_cool_conf_setting: very_cool_conf_value\nx: y' == f
.read()
323 with pytest
.raises(FileNotFoundError
):
324 open(os
.path
.join(cd
.DATA_DIR
, 'fsid', 'custom_config_files', 'mon.host1', 'no-content.conf'), 'r')
326 @mock.patch('cephadm.call_throws')
327 @mock.patch('cephadm.get_parm')
328 def test_registry_login(self
, get_parm
, call_throws
):
329 # test normal valid login with url, username and password specified
330 call_throws
.return_value
= '', '', 0
331 ctx
: cd
.CephadmContext
= cd
.cephadm_init_ctx(
332 ['registry-login', '--registry-url', 'sample-url',
333 '--registry-username', 'sample-user', '--registry-password',
335 ctx
.container_engine
= mock_docker()
336 retval
= cd
.command_registry_login(ctx
)
339 # test bad login attempt with invalid arguments given
340 ctx
: cd
.CephadmContext
= cd
.cephadm_init_ctx(
341 ['registry-login', '--registry-url', 'bad-args-url'])
342 with pytest
.raises(Exception) as e
:
343 assert cd
.command_registry_login(ctx
)
344 assert str(e
.value
) == ('Invalid custom registry arguments received. To login to a custom registry include '
345 '--registry-url, --registry-username and --registry-password options or --registry-json option')
347 # test normal valid login with json file
348 get_parm
.return_value
= {"url": "sample-url", "username": "sample-username", "password": "sample-password"}
349 ctx
: cd
.CephadmContext
= cd
.cephadm_init_ctx(
350 ['registry-login', '--registry-json', 'sample-json'])
351 ctx
.container_engine
= mock_docker()
352 retval
= cd
.command_registry_login(ctx
)
355 # test bad login attempt with bad json file
356 get_parm
.return_value
= {"bad-json": "bad-json"}
357 ctx
: cd
.CephadmContext
= cd
.cephadm_init_ctx(
358 ['registry-login', '--registry-json', 'sample-json'])
359 with pytest
.raises(Exception) as e
:
360 assert cd
.command_registry_login(ctx
)
361 assert str(e
.value
) == ("json provided for custom registry login did not include all necessary fields. "
362 "Please setup json file as\n"
364 " \"url\": \"REGISTRY_URL\",\n"
365 " \"username\": \"REGISTRY_USERNAME\",\n"
366 " \"password\": \"REGISTRY_PASSWORD\"\n"
369 # test login attempt with valid arguments where login command fails
370 call_throws
.side_effect
= Exception
371 ctx
: cd
.CephadmContext
= cd
.cephadm_init_ctx(
372 ['registry-login', '--registry-url', 'sample-url',
373 '--registry-username', 'sample-user', '--registry-password',
375 with pytest
.raises(Exception) as e
:
376 cd
.command_registry_login(ctx
)
377 assert str(e
.value
) == "Failed to login to custom registry @ sample-url as sample-user with given password"
379 def test_get_image_info_from_inspect(self
):
381 out
= """204a01f9b0b6710dd0c0af7f37ce7139c47ff0f0105d778d7104c69282dfbbf1,[docker.io/ceph/ceph@sha256:1cc9b824e1b076cdff52a9aa3f0cc8557d879fb2fbbba0cafed970aca59a3992]"""
382 r
= cd
.get_image_info_from_inspect(out
, 'registry/ceph/ceph:latest')
385 'image_id': '204a01f9b0b6710dd0c0af7f37ce7139c47ff0f0105d778d7104c69282dfbbf1',
386 'repo_digests': ['docker.io/ceph/ceph@sha256:1cc9b824e1b076cdff52a9aa3f0cc8557d879fb2fbbba0cafed970aca59a3992']
390 out
= """sha256:16f4549cf7a8f112bbebf7946749e961fbbd1b0838627fe619aab16bc17ce552,[quay.ceph.io/ceph-ci/ceph@sha256:4e13da36c1bd6780b312a985410ae678984c37e6a9493a74c87e4a50b9bda41f]"""
391 r
= cd
.get_image_info_from_inspect(out
, 'registry/ceph/ceph:latest')
393 'image_id': '16f4549cf7a8f112bbebf7946749e961fbbd1b0838627fe619aab16bc17ce552',
394 'repo_digests': ['quay.ceph.io/ceph-ci/ceph@sha256:4e13da36c1bd6780b312a985410ae678984c37e6a9493a74c87e4a50b9bda41f']
397 # multiple digests (podman)
398 out
= """e935122ab143a64d92ed1fbb27d030cf6e2f0258207be1baf1b509c466aeeb42,[docker.io/prom/prometheus@sha256:e4ca62c0d62f3e886e684806dfe9d4e0cda60d54986898173c1083856cfda0f4 docker.io/prom/prometheus@sha256:efd99a6be65885c07c559679a0df4ec709604bcdd8cd83f0d00a1a683b28fb6a]"""
399 r
= cd
.get_image_info_from_inspect(out
, 'registry/prom/prometheus:latest')
401 'image_id': 'e935122ab143a64d92ed1fbb27d030cf6e2f0258207be1baf1b509c466aeeb42',
403 'docker.io/prom/prometheus@sha256:e4ca62c0d62f3e886e684806dfe9d4e0cda60d54986898173c1083856cfda0f4',
404 'docker.io/prom/prometheus@sha256:efd99a6be65885c07c559679a0df4ec709604bcdd8cd83f0d00a1a683b28fb6a',
409 def test_dict_get(self
):
410 result
= cd
.dict_get({'a': 1}, 'a', require
=True)
412 result
= cd
.dict_get({'a': 1}, 'b')
413 assert result
is None
414 result
= cd
.dict_get({'a': 1}, 'b', default
=2)
417 def test_dict_get_error(self
):
418 with pytest
.raises(cd
.Error
):
419 cd
.dict_get({'a': 1}, 'b', require
=True)
421 def test_dict_get_join(self
):
422 result
= cd
.dict_get_join({'foo': ['a', 'b']}, 'foo')
423 assert result
== 'a\nb'
424 result
= cd
.dict_get_join({'foo': [1, 2]}, 'foo')
425 assert result
== '1\n2'
426 result
= cd
.dict_get_join({'bar': 'a'}, 'bar')
428 result
= cd
.dict_get_join({'a': 1}, 'a')
431 @mock.patch('os.listdir', return_value
=[])
432 @mock.patch('cephadm.logger')
433 def test_infer_local_ceph_image(self
, _logger
, _listdir
):
434 ctx
= cd
.CephadmContext()
435 ctx
.fsid
= '00000000-0000-0000-0000-0000deadbeez'
436 ctx
.container_engine
= mock_podman()
438 # make sure the right image is selected when container is found
439 cinfo
= cd
.ContainerInfo('935b549714b8f007c6a4e29c758689cf9e8e69f2e0f51180506492974b90a972',
440 'registry.hub.docker.com/rkachach/ceph:custom-v0.5',
441 '514e6a882f6e74806a5856468489eeff8d7106095557578da96935e4d0ba4d9d',
442 '2022-04-19 13:45:20.97146228 +0000 UTC',
444 out
= '''quay.ceph.io/ceph-ci/ceph@sha256:87f200536bb887b36b959e887d5984dd7a3f008a23aa1f283ab55d48b22c6185|dad864ee21e9|main|2022-03-23 16:29:19 +0000 UTC
445 quay.ceph.io/ceph-ci/ceph@sha256:b50b130fcda2a19f8507ddde3435bb4722266956e1858ac395c838bc1dcf1c0e|514e6a882f6e|pacific|2022-03-23 15:58:34 +0000 UTC
446 docker.io/ceph/ceph@sha256:939a46c06b334e094901560c8346de33c00309e3e3968a2db240eb4897c6a508|666bbfa87e8d|v15.2.5|2020-09-16 14:15:15 +0000 UTC'''
447 with mock
.patch('cephadm.call_throws', return_value
=(out
, '', '')):
448 with mock
.patch('cephadm.get_container_info', return_value
=cinfo
):
449 image
= cd
.infer_local_ceph_image(ctx
, ctx
.container_engine
)
450 assert image
== 'quay.ceph.io/ceph-ci/ceph@sha256:b50b130fcda2a19f8507ddde3435bb4722266956e1858ac395c838bc1dcf1c0e'
452 # make sure first valid image is used when no container_info is found
453 out
= '''quay.ceph.io/ceph-ci/ceph@sha256:87f200536bb887b36b959e887d5984dd7a3f008a23aa1f283ab55d48b22c6185|dad864ee21e9|main|2022-03-23 16:29:19 +0000 UTC
454 quay.ceph.io/ceph-ci/ceph@sha256:b50b130fcda2a19f8507ddde3435bb4722266956e1858ac395c838bc1dcf1c0e|514e6a882f6e|pacific|2022-03-23 15:58:34 +0000 UTC
455 docker.io/ceph/ceph@sha256:939a46c06b334e094901560c8346de33c00309e3e3968a2db240eb4897c6a508|666bbfa87e8d|v15.2.5|2020-09-16 14:15:15 +0000 UTC'''
456 with mock
.patch('cephadm.call_throws', return_value
=(out
, '', '')):
457 with mock
.patch('cephadm.get_container_info', return_value
=None):
458 image
= cd
.infer_local_ceph_image(ctx
, ctx
.container_engine
)
459 assert image
== 'quay.ceph.io/ceph-ci/ceph@sha256:87f200536bb887b36b959e887d5984dd7a3f008a23aa1f283ab55d48b22c6185'
461 # make sure images without digest are discarded (no container_info is found)
462 out
= '''quay.ceph.io/ceph-ci/ceph@|||
463 docker.io/ceph/ceph@|||
464 docker.io/ceph/ceph@sha256:939a46c06b334e094901560c8346de33c00309e3e3968a2db240eb4897c6a508|666bbfa87e8d|v15.2.5|2020-09-16 14:15:15 +0000 UTC'''
465 with mock
.patch('cephadm.call_throws', return_value
=(out
, '', '')):
466 with mock
.patch('cephadm.get_container_info', return_value
=None):
467 image
= cd
.infer_local_ceph_image(ctx
, ctx
.container_engine
)
468 assert image
== 'docker.io/ceph/ceph@sha256:939a46c06b334e094901560c8346de33c00309e3e3968a2db240eb4897c6a508'
472 @pytest.mark
.parametrize('daemon_filter, by_name, daemon_list, container_stats, output',
474 # get container info by type ('mon')
479 {'name': 'mon.ceph-node-0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'},
480 {'name': 'mgr.ceph-node-0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'},
482 ("935b549714b8f007c6a4e29c758689cf9e8e69f2e0f51180506492974b90a972,registry.hub.docker.com/rkachach/ceph:custom-v0.5,666bbfa87e8df05702d6172cae11dd7bc48efb1d94f1b9e492952f19647199a4,2022-04-19 13:45:20.97146228 +0000 UTC,",
485 cd
.ContainerInfo('935b549714b8f007c6a4e29c758689cf9e8e69f2e0f51180506492974b90a972',
486 'registry.hub.docker.com/rkachach/ceph:custom-v0.5',
487 '666bbfa87e8df05702d6172cae11dd7bc48efb1d94f1b9e492952f19647199a4',
488 '2022-04-19 13:45:20.97146228 +0000 UTC',
491 # get container info by name ('mon.ceph-node-0')
496 {'name': 'mgr.ceph-node-0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'},
497 {'name': 'mon.ceph-node-0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'},
499 ("935b549714b8f007c6a4e29c758689cf9e8e69f2e0f51180506492974b90a972,registry.hub.docker.com/rkachach/ceph:custom-v0.5,666bbfa87e8df05702d6172cae11dd7bc48efb1d94f1b9e492952f19647199a4,2022-04-19 13:45:20.97146228 +0000 UTC,",
502 cd
.ContainerInfo('935b549714b8f007c6a4e29c758689cf9e8e69f2e0f51180506492974b90a972',
503 'registry.hub.docker.com/rkachach/ceph:custom-v0.5',
504 '666bbfa87e8df05702d6172cae11dd7bc48efb1d94f1b9e492952f19647199a4',
505 '2022-04-19 13:45:20.97146228 +0000 UTC',
508 # get container info by name (same daemon but two different fsids)
513 {'name': 'mon.ceph-node-0', 'fsid': '10000000-0000-0000-0000-0000deadbeef'},
514 {'name': 'mon.ceph-node-0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'},
516 ("935b549714b8f007c6a4e29c758689cf9e8e69f2e0f51180506492974b90a972,registry.hub.docker.com/rkachach/ceph:custom-v0.5,666bbfa87e8df05702d6172cae11dd7bc48efb1d94f1b9e492952f19647199a4,2022-04-19 13:45:20.97146228 +0000 UTC,",
519 cd
.ContainerInfo('935b549714b8f007c6a4e29c758689cf9e8e69f2e0f51180506492974b90a972',
520 'registry.hub.docker.com/rkachach/ceph:custom-v0.5',
521 '666bbfa87e8df05702d6172cae11dd7bc48efb1d94f1b9e492952f19647199a4',
522 '2022-04-19 13:45:20.97146228 +0000 UTC',
525 # get container info by type (bad container stats: 127 code)
530 {'name': 'mon.ceph-node-0', 'fsid': '00000000-FFFF-0000-0000-0000deadbeef'},
531 {'name': 'mon.ceph-node-0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'},
538 # get container info by name (bad container stats: 127 code)
543 {'name': 'mgr.ceph-node-0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'},
544 {'name': 'mon.ceph-node-0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'},
551 # get container info by invalid name (doens't contain '.')
556 {'name': 'mon.ceph-node-0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'},
557 {'name': 'mon.ceph-node-0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'},
559 ("935b549714b8f007c6a4e29c758689cf9e8e69f2e0f51180506492974b90a972,registry.hub.docker.com/rkachach/ceph:custom-v0.5,666bbfa87e8df05702d6172cae11dd7bc48efb1d94f1b9e492952f19647199a4,2022-04-19 13:45:20.97146228 +0000 UTC,",
564 # get container info by invalid name (empty)
569 {'name': 'mon.ceph-node-0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'},
570 {'name': 'mon.ceph-node-0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'},
572 ("935b549714b8f007c6a4e29c758689cf9e8e69f2e0f51180506492974b90a972,registry.hub.docker.com/rkachach/ceph:custom-v0.5,666bbfa87e8df05702d6172cae11dd7bc48efb1d94f1b9e492952f19647199a4,2022-04-19 13:45:20.97146228 +0000 UTC,",
577 # get container info by invalid type (empty)
582 {'name': 'mon.ceph-node-0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'},
583 {'name': 'mon.ceph-node-0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'},
585 ("935b549714b8f007c6a4e29c758689cf9e8e69f2e0f51180506492974b90a972,registry.hub.docker.com/rkachach/ceph:custom-v0.5,666bbfa87e8df05702d6172cae11dd7bc48efb1d94f1b9e492952f19647199a4,2022-04-19 13:45:20.97146228 +0000 UTC,",
590 # get container info by name: no match (invalid fsid)
595 {'name': 'mon.ceph-node-0', 'fsid': '00000000-1111-0000-0000-0000deadbeef'},
596 {'name': 'mon.ceph-node-0', 'fsid': '00000000-2222-0000-0000-0000deadbeef'},
598 ("935b549714b8f007c6a4e29c758689cf9e8e69f2e0f51180506492974b90a972,registry.hub.docker.com/rkachach/ceph:custom-v0.5,666bbfa87e8df05702d6172cae11dd7bc48efb1d94f1b9e492952f19647199a4,2022-04-19 13:45:20.97146228 +0000 UTC,",
603 # get container info by name: no match
611 # get container info by type: no match
620 def test_get_container_info(self
, daemon_filter
, by_name
, daemon_list
, container_stats
, output
):
621 cd
.logger
= mock
.Mock()
622 ctx
= cd
.CephadmContext()
623 ctx
.fsid
= '00000000-0000-0000-0000-0000deadbeef'
624 ctx
.container_engine
= mock_podman()
625 with mock
.patch('cephadm.list_daemons', return_value
=daemon_list
):
626 with mock
.patch('cephadm.get_container_stats', return_value
=container_stats
):
627 assert cd
.get_container_info(ctx
, daemon_filter
, by_name
) == output
629 def test_should_log_to_journald(self
):
630 ctx
= cd
.CephadmContext()
632 ctx
.log_to_journald
= True
633 assert cd
.should_log_to_journald(ctx
)
635 ctx
.log_to_journald
= None
636 # enable if podman support --cgroup=split
637 ctx
.container_engine
= mock_podman()
638 ctx
.container_engine
.version
= (2, 1, 0)
639 assert cd
.should_log_to_journald(ctx
)
641 # disable on old podman
642 ctx
.container_engine
.version
= (2, 0, 0)
643 assert not cd
.should_log_to_journald(ctx
)
646 ctx
.container_engine
= mock_docker()
647 assert not cd
.should_log_to_journald(ctx
)
649 def test_normalize_image_digest(self
):
650 s
= 'myhostname:5000/ceph/ceph@sha256:753886ad9049004395ae990fbb9b096923b5a518b819283141ee8716ddf55ad1'
651 assert cd
.normalize_image_digest(s
) == s
653 s
= 'ceph/ceph:latest'
654 assert cd
.normalize_image_digest(s
) == f
'{cd.DEFAULT_REGISTRY}/{s}'
656 @pytest.mark
.parametrize('fsid, ceph_conf, list_daemons, result, err, ',
666 '00000000-0000-0000-0000-0000deadbeef',
669 '00000000-0000-0000-0000-0000deadbeef',
673 '00000000-0000-0000-0000-0000deadbeef',
676 {'fsid': '10000000-0000-0000-0000-0000deadbeef'},
677 {'fsid': '20000000-0000-0000-0000-0000deadbeef'},
679 '00000000-0000-0000-0000-0000deadbeef',
686 {'fsid': '00000000-0000-0000-0000-0000deadbeef'},
688 '00000000-0000-0000-0000-0000deadbeef',
695 {'fsid': '10000000-0000-0000-0000-0000deadbeef'},
696 {'fsid': '20000000-0000-0000-0000-0000deadbeef'},
699 r
'Cannot infer an fsid',
703 get_ceph_conf(fsid
='00000000-0000-0000-0000-0000deadbeef'),
705 '00000000-0000-0000-0000-0000deadbeef',
710 get_ceph_conf(fsid
='00000000-0000-0000-0000-0000deadbeef'),
712 {'fsid': '00000000-0000-0000-0000-0000deadbeef'},
714 '00000000-0000-0000-0000-0000deadbeef',
719 get_ceph_conf(fsid
='00000000-0000-0000-0000-0000deadbeef'),
721 {'fsid': '10000000-0000-0000-0000-0000deadbeef'},
722 {'fsid': '20000000-0000-0000-0000-0000deadbeef'},
725 r
'Cannot infer an fsid',
728 @mock.patch('cephadm.call')
729 def test_infer_fsid(self
, _call
, fsid
, ceph_conf
, list_daemons
, result
, err
, cephadm_fs
):
731 ctx
= cd
.CephadmContext()
735 mock_fn
= mock
.Mock()
736 mock_fn
.return_value
= 0
737 infer_fsid
= cd
.infer_fsid(mock_fn
)
739 # mock the ceph.conf file content
741 f
= cephadm_fs
.create_file('ceph.conf', contents
=ceph_conf
)
745 with mock
.patch('cephadm.list_daemons', return_value
=list_daemons
):
747 with pytest
.raises(cd
.Error
, match
=err
):
751 assert ctx
.fsid
== result
753 @pytest.mark
.parametrize('fsid, other_conf_files, config, name, list_daemons, result, ',
755 # per cluster conf has more precedence than default conf
757 '00000000-0000-0000-0000-0000deadbeef',
758 [cd
.CEPH_DEFAULT_CONF
],
762 '/var/lib/ceph/00000000-0000-0000-0000-0000deadbeef/config/ceph.conf',
764 # mon daemon conf has more precedence than cluster conf and default conf
766 '00000000-0000-0000-0000-0000deadbeef',
767 ['/var/lib/ceph/00000000-0000-0000-0000-0000deadbeef/config/ceph.conf',
768 cd
.CEPH_DEFAULT_CONF
],
771 [{'name': 'mon.a', 'fsid': '00000000-0000-0000-0000-0000deadbeef', 'style': 'cephadm:v1'}],
772 '/var/lib/ceph/00000000-0000-0000-0000-0000deadbeef/mon.a/config',
774 # daemon conf (--name option) has more precedence than cluster, default and mon conf
776 '00000000-0000-0000-0000-0000deadbeef',
777 ['/var/lib/ceph/00000000-0000-0000-0000-0000deadbeef/config/ceph.conf',
778 '/var/lib/ceph/00000000-0000-0000-0000-0000deadbeef/mon.a/config',
779 cd
.CEPH_DEFAULT_CONF
],
782 [{'name': 'mon.a', 'fsid': '00000000-0000-0000-0000-0000deadbeef', 'style': 'cephadm:v1'},
783 {'name': 'osd.0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'}],
784 '/var/lib/ceph/00000000-0000-0000-0000-0000deadbeef/osd.0/config',
786 # user provided conf ('/foo/ceph.conf') more precedence than any other conf
788 '00000000-0000-0000-0000-0000deadbeef',
789 ['/var/lib/ceph/00000000-0000-0000-0000-0000deadbeef/config/ceph.conf',
790 cd
.CEPH_DEFAULT_CONF
,
791 '/var/lib/ceph/00000000-0000-0000-0000-0000deadbeef/mon.a/config'],
794 [{'name': 'mon.a', 'fsid': '00000000-0000-0000-0000-0000deadbeef', 'style': 'cephadm:v1'}],
798 @mock.patch('cephadm.call')
799 @mock.patch('cephadm.logger')
800 def test_infer_config_precedence(self
, logger
, _call
, other_conf_files
, fsid
, config
, name
, list_daemons
, result
, cephadm_fs
):
802 ctx
= cd
.CephadmContext()
808 mock_fn
= mock
.Mock()
809 mock_fn
.return_value
= 0
810 infer_config
= cd
.infer_config(mock_fn
)
812 # mock the config file
813 cephadm_fs
.create_file(result
)
815 # mock other potential config files
816 for f
in other_conf_files
:
817 cephadm_fs
.create_file(f
)
820 with mock
.patch('cephadm.list_daemons', return_value
=list_daemons
):
822 assert ctx
.config
== result
824 @pytest.mark
.parametrize('fsid, config, name, list_daemons, result, ',
834 '00000000-0000-0000-0000-0000deadbeef',
838 cd
.CEPH_DEFAULT_CONF
,
841 '00000000-0000-0000-0000-0000deadbeef',
845 '/var/lib/ceph/00000000-0000-0000-0000-0000deadbeef/config/ceph.conf',
848 '00000000-0000-0000-0000-0000deadbeef',
851 [{'name': 'mon.a', 'fsid': '00000000-0000-0000-0000-0000deadbeef', 'style': 'cephadm:v1'}],
852 '/var/lib/ceph/00000000-0000-0000-0000-0000deadbeef/mon.a/config',
855 '00000000-0000-0000-0000-0000deadbeef',
858 [{'name': 'mon.a', 'fsid': 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'style': 'cephadm:v1'}],
859 cd
.CEPH_DEFAULT_CONF
,
862 '00000000-0000-0000-0000-0000deadbeef',
865 [{'name': 'mon.a', 'fsid': '00000000-0000-0000-0000-0000deadbeef', 'style': 'legacy'}],
866 cd
.CEPH_DEFAULT_CONF
,
869 '00000000-0000-0000-0000-0000deadbeef',
873 cd
.CEPH_DEFAULT_CONF
,
876 '00000000-0000-0000-0000-0000deadbeef',
879 [{'name': 'mon.a', 'style': 'cephadm:v1'}],
883 '00000000-0000-0000-0000-0000deadbeef',
887 '/var/lib/ceph/00000000-0000-0000-0000-0000deadbeef/mon.a/config',
890 '00000000-0000-0000-0000-0000deadbeef',
894 '/var/lib/ceph/00000000-0000-0000-0000-0000deadbeef/osd.0/config',
901 cd
.CEPH_DEFAULT_CONF
,
904 @mock.patch('cephadm.call')
905 @mock.patch('cephadm.logger')
906 def test_infer_config(self
, logger
, _call
, fsid
, config
, name
, list_daemons
, result
, cephadm_fs
):
908 ctx
= cd
.CephadmContext()
914 mock_fn
= mock
.Mock()
915 mock_fn
.return_value
= 0
916 infer_config
= cd
.infer_config(mock_fn
)
918 # mock the config file
919 cephadm_fs
.create_file(result
)
922 with mock
.patch('cephadm.list_daemons', return_value
=list_daemons
):
924 assert ctx
.config
== result
926 @mock.patch('cephadm.call')
927 def test_extract_uid_gid_fail(self
, _call
):
928 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
929 ff792c06d8544b983.scope not found.: OCI runtime error"""
930 _call
.return_value
= ('', err
, 127)
931 ctx
= cd
.CephadmContext()
932 ctx
.container_engine
= mock_podman()
933 with pytest
.raises(cd
.Error
, match
='OCI'):
934 cd
.extract_uid_gid(ctx
)
936 @pytest.mark
.parametrize('test_input, expected', [
937 ([cd
.make_fsid(), cd
.make_fsid(), cd
.make_fsid()], 3),
938 ([cd
.make_fsid(), 'invalid-fsid', cd
.make_fsid(), '0b87e50c-8e77-11ec-b890-'], 2),
939 (['f6860ec2-8e76-11ec-', '0b87e50c-8e77-11ec-b890-', ''], 0),
942 def test_get_ceph_cluster_count(self
, test_input
, expected
):
943 ctx
= cd
.CephadmContext()
944 with mock
.patch('os.listdir', return_value
=test_input
):
945 assert cd
.get_ceph_cluster_count(ctx
) == expected
947 def test_set_image_minimize_config(self
):
949 raise cd
.Error(' '.join(cmd
))
950 ctx
= cd
.CephadmContext()
951 ctx
.image
= 'test_image'
952 ctx
.no_minimize_config
= True
953 fake_cli
= lambda cmd
, __
=None, ___
=None: throw_cmd(cmd
)
954 with pytest
.raises(cd
.Error
, match
='config set global container_image test_image'):
955 cd
.finish_bootstrap_config(
959 mon_id
='a', mon_dir
='mon_dir',
960 mon_network
=None, ipv6
=False,
962 cluster_network
=None,
963 ipv6_cluster_network
=False
967 class TestCustomContainer(unittest
.TestCase
):
968 cc
: cd
.CustomContainer
971 self
.cc
= cd
.CustomContainer(
972 'e863154d-33c7-4350-bca5-921e0467e55b',
975 'entrypoint': 'bash',
981 'envs': ['SECRET=password'],
982 'ports': [8080, 8443],
984 '/CONFIG_DIR': '/foo/conf',
985 'bar/config': '/bar:ro'
990 'source=/CONFIG_DIR',
991 'destination=/foo/conf',
997 'destination=/bar:ro',
1002 image
='docker.io/library/hello-world:latest'
1005 def test_entrypoint(self
):
1006 self
.assertEqual(self
.cc
.entrypoint
, 'bash')
1008 def test_uid_gid(self
):
1009 self
.assertEqual(self
.cc
.uid
, 65534)
1010 self
.assertEqual(self
.cc
.gid
, 1000)
1012 def test_ports(self
):
1013 self
.assertEqual(self
.cc
.ports
, [8080, 8443])
1015 def test_get_container_args(self
):
1016 result
= self
.cc
.get_container_args()
1017 self
.assertEqual(result
, [
1022 def test_get_container_envs(self
):
1023 result
= self
.cc
.get_container_envs()
1024 self
.assertEqual(result
, ['SECRET=password'])
1026 def test_get_container_mounts(self
):
1027 result
= self
.cc
.get_container_mounts('/xyz')
1028 self
.assertDictEqual(result
, {
1029 '/CONFIG_DIR': '/foo/conf',
1030 '/xyz/bar/config': '/bar:ro'
1033 def test_get_container_binds(self
):
1034 result
= self
.cc
.get_container_binds('/xyz')
1035 self
.assertEqual(result
, [
1038 'source=/CONFIG_DIR',
1039 'destination=/foo/conf',
1044 'source=/xyz/bar/config',
1045 'destination=/bar:ro',
1051 class TestMaintenance
:
1052 systemd_target
= "ceph.00000000-0000-0000-0000-000000c0ffee.target"
1053 fsid
= '0ea8cdd0-1bbf-11ec-a9c7-5254002763fa'
1055 def test_systemd_target_OK(self
, tmp_path
):
1057 wants
= base
/ "ceph.target.wants"
1059 target
= wants
/ TestMaintenance
.systemd_target
1061 ctx
= cd
.CephadmContext()
1062 ctx
.unit_dir
= str(base
)
1064 assert cd
.systemd_target_state(ctx
, target
.name
)
1066 def test_systemd_target_NOTOK(self
, tmp_path
):
1068 ctx
= cd
.CephadmContext()
1069 ctx
.unit_dir
= str(base
)
1070 assert not cd
.systemd_target_state(ctx
, TestMaintenance
.systemd_target
)
1072 def test_parser_OK(self
):
1073 args
= cd
._parse
_args
(['host-maintenance', 'enter'])
1074 assert args
.maintenance_action
== 'enter'
1076 def test_parser_BAD(self
):
1077 with pytest
.raises(SystemExit):
1078 cd
._parse
_args
(['host-maintenance', 'wah'])
1080 @mock.patch('os.listdir', return_value
=[])
1081 @mock.patch('cephadm.call')
1082 @mock.patch('cephadm.systemd_target_state')
1083 def test_enter_failure_1(self
, _target_state
, _call
, _listdir
):
1084 _call
.return_value
= '', '', 999
1085 _target_state
.return_value
= True
1086 ctx
: cd
.CephadmContext
= cd
.cephadm_init_ctx(
1087 ['host-maintenance', 'enter', '--fsid', TestMaintenance
.fsid
])
1088 ctx
.container_engine
= mock_podman()
1089 retval
= cd
.command_maintenance(ctx
)
1090 assert retval
.startswith('failed')
1092 @mock.patch('os.listdir', return_value
=[])
1093 @mock.patch('cephadm.call')
1094 @mock.patch('cephadm.systemd_target_state')
1095 def test_enter_failure_2(self
, _target_state
, _call
, _listdir
):
1096 _call
.side_effect
= [('', '', 0), ('', '', 999), ('', '', 0), ('', '', 999)]
1097 _target_state
.return_value
= True
1098 ctx
: cd
.CephadmContext
= cd
.cephadm_init_ctx(
1099 ['host-maintenance', 'enter', '--fsid', TestMaintenance
.fsid
])
1100 ctx
.container_engine
= mock_podman()
1101 retval
= cd
.command_maintenance(ctx
)
1102 assert retval
.startswith('failed')
1104 @mock.patch('os.listdir', return_value
=[])
1105 @mock.patch('cephadm.call')
1106 @mock.patch('cephadm.systemd_target_state')
1107 @mock.patch('cephadm.target_exists')
1108 def test_exit_failure_1(self
, _target_exists
, _target_state
, _call
, _listdir
):
1109 _call
.return_value
= '', '', 999
1110 _target_state
.return_value
= False
1111 _target_exists
.return_value
= True
1112 ctx
: cd
.CephadmContext
= cd
.cephadm_init_ctx(
1113 ['host-maintenance', 'exit', '--fsid', TestMaintenance
.fsid
])
1114 ctx
.container_engine
= mock_podman()
1115 retval
= cd
.command_maintenance(ctx
)
1116 assert retval
.startswith('failed')
1118 @mock.patch('os.listdir', return_value
=[])
1119 @mock.patch('cephadm.call')
1120 @mock.patch('cephadm.systemd_target_state')
1121 @mock.patch('cephadm.target_exists')
1122 def test_exit_failure_2(self
, _target_exists
, _target_state
, _call
, _listdir
):
1123 _call
.side_effect
= [('', '', 0), ('', '', 999), ('', '', 0), ('', '', 999)]
1124 _target_state
.return_value
= False
1125 _target_exists
.return_value
= True
1126 ctx
: cd
.CephadmContext
= cd
.cephadm_init_ctx(
1127 ['host-maintenance', 'exit', '--fsid', TestMaintenance
.fsid
])
1128 ctx
.container_engine
= mock_podman()
1129 retval
= cd
.command_maintenance(ctx
)
1130 assert retval
.startswith('failed')
1133 class TestMonitoring(object):
1134 @mock.patch('cephadm.call')
1135 def test_get_version_alertmanager(self
, _call
):
1136 ctx
= cd
.CephadmContext()
1137 ctx
.container_engine
= mock_podman()
1138 daemon_type
= 'alertmanager'
1140 # binary `prometheus`
1141 _call
.return_value
= '', '{}, version 0.16.1'.format(daemon_type
), 0
1142 version
= cd
.Monitoring
.get_version(ctx
, 'container_id', daemon_type
)
1143 assert version
== '0.16.1'
1145 # binary `prometheus-alertmanager`
1146 _call
.side_effect
= (
1148 ('', '{}, version 0.16.1'.format(daemon_type
), 0),
1150 version
= cd
.Monitoring
.get_version(ctx
, 'container_id', daemon_type
)
1151 assert version
== '0.16.1'
1153 @mock.patch('cephadm.call')
1154 def test_get_version_prometheus(self
, _call
):
1155 ctx
= cd
.CephadmContext()
1156 ctx
.container_engine
= mock_podman()
1157 daemon_type
= 'prometheus'
1158 _call
.return_value
= '', '{}, version 0.16.1'.format(daemon_type
), 0
1159 version
= cd
.Monitoring
.get_version(ctx
, 'container_id', daemon_type
)
1160 assert version
== '0.16.1'
1162 def test_prometheus_external_url(self
):
1163 ctx
= cd
.CephadmContext()
1164 ctx
.config_json
= json
.dumps({'files': {}, 'retention_time': '15d'})
1165 daemon_type
= 'prometheus'
1167 fsid
= 'aaf5a720-13fe-4a3b-82b9-2d99b7fd9704'
1168 args
= cd
.get_daemon_args(ctx
, fsid
, daemon_type
, daemon_id
)
1169 assert any([x
.startswith('--web.external-url=http://') for x
in args
])
1171 @mock.patch('cephadm.call')
1172 def test_get_version_node_exporter(self
, _call
):
1173 ctx
= cd
.CephadmContext()
1174 ctx
.container_engine
= mock_podman()
1175 daemon_type
= 'node-exporter'
1176 _call
.return_value
= '', '{}, version 0.16.1'.format(daemon_type
.replace('-', '_')), 0
1177 version
= cd
.Monitoring
.get_version(ctx
, 'container_id', daemon_type
)
1178 assert version
== '0.16.1'
1180 def test_create_daemon_dirs_prometheus(self
, cephadm_fs
):
1182 Ensures the required and optional files given in the configuration are
1183 created and mapped correctly inside the container. Tests absolute and
1184 relative file paths given in the configuration.
1187 fsid
= 'aaf5a720-13fe-4a3b-82b9-2d99b7fd9704'
1188 daemon_type
= 'prometheus'
1191 ctx
= cd
.CephadmContext()
1192 ctx
.data_dir
= '/somedir'
1193 ctx
.config_json
= json
.dumps({
1195 'prometheus.yml': 'foo',
1196 '/etc/prometheus/alerting/ceph_alerts.yml': 'bar'
1200 cd
.create_daemon_dirs(ctx
,
1209 prefix
= '{data_dir}/{fsid}/{daemon_type}.{daemon_id}'.format(
1210 data_dir
=ctx
.data_dir
,
1212 daemon_type
=daemon_type
,
1217 'etc/prometheus/prometheus.yml': 'foo',
1218 'etc/prometheus/alerting/ceph_alerts.yml': 'bar',
1221 for file,content
in expected
.items():
1222 file = os
.path
.join(prefix
, file)
1223 assert os
.path
.exists(file)
1224 with
open(file) as f
:
1225 assert f
.read() == content
1227 # assert uid/gid after redeploy
1230 cd
.create_daemon_dirs(ctx
,
1238 for file,content
in expected
.items():
1239 file = os
.path
.join(prefix
, file)
1240 assert os
.stat(file).st_uid
== new_uid
1241 assert os
.stat(file).st_gid
== new_gid
1244 class TestBootstrap(object):
1247 def _get_cmd(*args
):
1250 '--allow-mismatched-release',
1251 '--skip-prepare-host',
1257 ###############################################3
1259 def test_config(self
, cephadm_fs
):
1261 cmd
= self
._get
_cmd
(
1262 '--mon-ip', '192.168.1.1',
1263 '--skip-mon-network',
1264 '--config', conf_file
,
1267 with
with_cephadm_ctx(cmd
) as ctx
:
1268 msg
= r
'No such file or directory'
1269 with pytest
.raises(cd
.Error
, match
=msg
):
1270 cd
.command_bootstrap(ctx
)
1272 cephadm_fs
.create_file(conf_file
)
1273 with
with_cephadm_ctx(cmd
) as ctx
:
1274 retval
= cd
.command_bootstrap(ctx
)
1277 def test_no_mon_addr(self
, cephadm_fs
):
1278 cmd
= self
._get
_cmd
()
1279 with
with_cephadm_ctx(cmd
) as ctx
:
1280 msg
= r
'must specify --mon-ip or --mon-addrv'
1281 with pytest
.raises(cd
.Error
, match
=msg
):
1282 cd
.command_bootstrap(ctx
)
1284 def test_skip_mon_network(self
, cephadm_fs
):
1285 cmd
= self
._get
_cmd
('--mon-ip', '192.168.1.1')
1287 with
with_cephadm_ctx(cmd
, list_networks
={}) as ctx
:
1288 msg
= r
'--skip-mon-network'
1289 with pytest
.raises(cd
.Error
, match
=msg
):
1290 cd
.command_bootstrap(ctx
)
1292 cmd
+= ['--skip-mon-network']
1293 with
with_cephadm_ctx(cmd
, list_networks
={}) as ctx
:
1294 retval
= cd
.command_bootstrap(ctx
)
1297 @pytest.mark
.parametrize('mon_ip, list_networks, result',
1302 {'192.168.1.0/24': {'eth0': ['192.168.1.1']}},
1307 {'192.168.1.0/24': {'eth0': ['192.168.1.1']}},
1312 {'192.168.1.0/24': {'eth0': ['192.168.1.1']}},
1317 {'192.168.1.0/24': {'eth0': ['192.168.1.1']}},
1322 {'192.168.1.0/24': {'eth0': ['192.168.1.1']}},
1327 {'192.168.1.0/24': {'eth0': ['192.168.1.1']}},
1333 {'192.168.1.0/24': {'eth0': ['192.168.1.1']}},
1337 '::ffff:192.168.1.0',
1338 {"ffff::/64": {"eth0": ["::ffff:c0a8:101"]}},
1342 '::ffff:192.168.1.1',
1343 {"ffff::/64": {"eth0": ["::ffff:c0a8:101"]}},
1348 {"ffff::/64": {"eth0": ["::ffff:c0a8:101"]}},
1352 '[::ffff:c0a8:101]:1234',
1353 {"ffff::/64": {"eth0": ["::ffff:c0a8:101"]}},
1357 '[::ffff:c0a8:101]:0123',
1358 {"ffff::/64": {"eth0": ["::ffff:c0a8:101"]}},
1362 '0000:0000:0000:0000:0000:FFFF:C0A8:0101',
1363 {"ffff::/64": {"eth0": ["::ffff:c0a8:101"]}},
1367 def test_mon_ip(self
, mon_ip
, list_networks
, result
, cephadm_fs
):
1368 cmd
= self
._get
_cmd
('--mon-ip', mon_ip
)
1370 with
with_cephadm_ctx(cmd
, list_networks
=list_networks
) as ctx
:
1371 msg
= r
'--skip-mon-network'
1372 with pytest
.raises(cd
.Error
, match
=msg
):
1373 cd
.command_bootstrap(ctx
)
1375 with
with_cephadm_ctx(cmd
, list_networks
=list_networks
) as ctx
:
1376 retval
= cd
.command_bootstrap(ctx
)
1379 @pytest.mark
.parametrize('mon_addrv, list_networks, err',
1384 {'192.168.1.0/24': {'eth0': ['192.168.1.1']}},
1385 r
'must use square backets',
1389 {'192.168.1.0/24': {'eth0': ['192.168.1.1']}},
1390 r
'must include port number',
1393 '[192.168.1.1:1234]',
1394 {'192.168.1.0/24': {'eth0': ['192.168.1.1']}},
1398 '[192.168.1.1:0123]',
1399 {'192.168.1.0/24': {'eth0': ['192.168.1.1']}},
1403 '[v2:192.168.1.1:3300,v1:192.168.1.1:6789]',
1404 {'192.168.1.0/24': {'eth0': ['192.168.1.1']}},
1409 '[::ffff:192.168.1.1:1234]',
1410 {'ffff::/64': {'eth0': ['::ffff:c0a8:101']}},
1414 '[::ffff:192.168.1.1:0123]',
1415 {'ffff::/64': {'eth0': ['::ffff:c0a8:101']}},
1419 '[0000:0000:0000:0000:0000:FFFF:C0A8:0101:1234]',
1420 {'ffff::/64': {'eth0': ['::ffff:c0a8:101']}},
1424 '[v2:0000:0000:0000:0000:0000:FFFF:C0A8:0101:3300,v1:0000:0000:0000:0000:0000:FFFF:C0A8:0101:6789]',
1425 {'ffff::/64': {'eth0': ['::ffff:c0a8:101']}},
1429 def test_mon_addrv(self
, mon_addrv
, list_networks
, err
, cephadm_fs
):
1430 cmd
= self
._get
_cmd
('--mon-addrv', mon_addrv
)
1432 with
with_cephadm_ctx(cmd
, list_networks
=list_networks
) as ctx
:
1433 with pytest
.raises(cd
.Error
, match
=err
):
1434 cd
.command_bootstrap(ctx
)
1436 with
with_cephadm_ctx(cmd
, list_networks
=list_networks
) as ctx
:
1437 retval
= cd
.command_bootstrap(ctx
)
1440 def test_allow_fqdn_hostname(self
, cephadm_fs
):
1441 hostname
= 'foo.bar'
1442 cmd
= self
._get
_cmd
(
1443 '--mon-ip', '192.168.1.1',
1444 '--skip-mon-network',
1447 with
with_cephadm_ctx(cmd
, hostname
=hostname
) as ctx
:
1448 msg
= r
'--allow-fqdn-hostname'
1449 with pytest
.raises(cd
.Error
, match
=msg
):
1450 cd
.command_bootstrap(ctx
)
1452 cmd
+= ['--allow-fqdn-hostname']
1453 with
with_cephadm_ctx(cmd
, hostname
=hostname
) as ctx
:
1454 retval
= cd
.command_bootstrap(ctx
)
1457 @pytest.mark
.parametrize('fsid, err',
1460 ('00000000-0000-0000-0000-0000deadbeef', None),
1461 ('00000000-0000-0000-0000-0000deadbeez', 'not an fsid'),
1463 def test_fsid(self
, fsid
, err
, cephadm_fs
):
1464 cmd
= self
._get
_cmd
(
1465 '--mon-ip', '192.168.1.1',
1466 '--skip-mon-network',
1470 with
with_cephadm_ctx(cmd
) as ctx
:
1472 with pytest
.raises(cd
.Error
, match
=err
):
1473 cd
.command_bootstrap(ctx
)
1475 retval
= cd
.command_bootstrap(ctx
)
1479 class TestShell(object):
1481 def test_fsid(self
, cephadm_fs
):
1482 fsid
= '00000000-0000-0000-0000-0000deadbeef'
1484 cmd
= ['shell', '--fsid', fsid
]
1485 with
with_cephadm_ctx(cmd
) as ctx
:
1486 retval
= cd
.command_shell(ctx
)
1488 assert ctx
.fsid
== fsid
1490 cmd
= ['shell', '--fsid', '00000000-0000-0000-0000-0000deadbeez']
1491 with
with_cephadm_ctx(cmd
) as ctx
:
1493 with pytest
.raises(cd
.Error
, match
=err
):
1494 retval
= cd
.command_shell(ctx
)
1496 assert ctx
.fsid
== None
1498 s
= get_ceph_conf(fsid
=fsid
)
1499 f
= cephadm_fs
.create_file('ceph.conf', contents
=s
)
1501 cmd
= ['shell', '--fsid', fsid
, '--config', f
.path
]
1502 with
with_cephadm_ctx(cmd
) as ctx
:
1503 retval
= cd
.command_shell(ctx
)
1505 assert ctx
.fsid
== fsid
1507 cmd
= ['shell', '--fsid', '10000000-0000-0000-0000-0000deadbeef', '--config', f
.path
]
1508 with
with_cephadm_ctx(cmd
) as ctx
:
1509 err
= 'fsid does not match ceph.conf'
1510 with pytest
.raises(cd
.Error
, match
=err
):
1511 retval
= cd
.command_shell(ctx
)
1513 assert ctx
.fsid
== None
1515 def test_name(self
, cephadm_fs
):
1516 cmd
= ['shell', '--name', 'foo']
1517 with
with_cephadm_ctx(cmd
) as ctx
:
1518 retval
= cd
.command_shell(ctx
)
1521 cmd
= ['shell', '--name', 'foo.bar']
1522 with
with_cephadm_ctx(cmd
) as ctx
:
1523 err
= r
'must pass --fsid'
1524 with pytest
.raises(cd
.Error
, match
=err
):
1525 retval
= cd
.command_shell(ctx
)
1528 fsid
= '00000000-0000-0000-0000-0000deadbeef'
1529 cmd
= ['shell', '--name', 'foo.bar', '--fsid', fsid
]
1530 with
with_cephadm_ctx(cmd
) as ctx
:
1531 retval
= cd
.command_shell(ctx
)
1534 def test_config(self
, cephadm_fs
):
1536 with
with_cephadm_ctx(cmd
) as ctx
:
1537 retval
= cd
.command_shell(ctx
)
1539 assert ctx
.config
== None
1541 cephadm_fs
.create_file(cd
.CEPH_DEFAULT_CONF
)
1542 with
with_cephadm_ctx(cmd
) as ctx
:
1543 retval
= cd
.command_shell(ctx
)
1545 assert ctx
.config
== cd
.CEPH_DEFAULT_CONF
1547 cmd
= ['shell', '--config', 'foo']
1548 with
with_cephadm_ctx(cmd
) as ctx
:
1549 retval
= cd
.command_shell(ctx
)
1551 assert ctx
.config
== 'foo'
1553 def test_keyring(self
, cephadm_fs
):
1555 with
with_cephadm_ctx(cmd
) as ctx
:
1556 retval
= cd
.command_shell(ctx
)
1558 assert ctx
.keyring
== None
1560 cephadm_fs
.create_file(cd
.CEPH_DEFAULT_KEYRING
)
1561 with
with_cephadm_ctx(cmd
) as ctx
:
1562 retval
= cd
.command_shell(ctx
)
1564 assert ctx
.keyring
== cd
.CEPH_DEFAULT_KEYRING
1566 cmd
= ['shell', '--keyring', 'foo']
1567 with
with_cephadm_ctx(cmd
) as ctx
:
1568 retval
= cd
.command_shell(ctx
)
1570 assert ctx
.keyring
== 'foo'
1572 @mock.patch('cephadm.CephContainer')
1573 def test_mount_no_dst(self
, m_ceph_container
, cephadm_fs
):
1574 cmd
= ['shell', '--mount', '/etc/foo']
1575 with
with_cephadm_ctx(cmd
) as ctx
:
1576 retval
= cd
.command_shell(ctx
)
1578 assert m_ceph_container
.call_args
.kwargs
['volume_mounts']['/etc/foo'] == '/mnt/foo'
1580 @mock.patch('cephadm.CephContainer')
1581 def test_mount_with_dst_no_opt(self
, m_ceph_container
, cephadm_fs
):
1582 cmd
= ['shell', '--mount', '/etc/foo:/opt/foo/bar']
1583 with
with_cephadm_ctx(cmd
) as ctx
:
1584 retval
= cd
.command_shell(ctx
)
1586 assert m_ceph_container
.call_args
.kwargs
['volume_mounts']['/etc/foo'] == '/opt/foo/bar'
1588 @mock.patch('cephadm.CephContainer')
1589 def test_mount_with_dst_and_opt(self
, m_ceph_container
, cephadm_fs
):
1590 cmd
= ['shell', '--mount', '/etc/foo:/opt/foo/bar:Z']
1591 with
with_cephadm_ctx(cmd
) as ctx
:
1592 retval
= cd
.command_shell(ctx
)
1594 assert m_ceph_container
.call_args
.kwargs
['volume_mounts']['/etc/foo'] == '/opt/foo/bar:Z'
1596 class TestCephVolume(object):
1599 def _get_cmd(*args
):
1603 '--', 'inventory', '--format', 'json'
1606 def test_noop(self
, cephadm_fs
):
1607 cmd
= self
._get
_cmd
()
1608 with
with_cephadm_ctx(cmd
) as ctx
:
1609 cd
.command_ceph_volume(ctx
)
1610 assert ctx
.fsid
== None
1611 assert ctx
.config
== None
1612 assert ctx
.keyring
== None
1613 assert ctx
.config_json
== None
1615 def test_fsid(self
, cephadm_fs
):
1616 fsid
= '00000000-0000-0000-0000-0000deadbeef'
1618 cmd
= self
._get
_cmd
('--fsid', fsid
)
1619 with
with_cephadm_ctx(cmd
) as ctx
:
1620 cd
.command_ceph_volume(ctx
)
1621 assert ctx
.fsid
== fsid
1623 cmd
= self
._get
_cmd
('--fsid', '00000000-0000-0000-0000-0000deadbeez')
1624 with
with_cephadm_ctx(cmd
) as ctx
:
1626 with pytest
.raises(cd
.Error
, match
=err
):
1627 retval
= cd
.command_shell(ctx
)
1629 assert ctx
.fsid
== None
1631 s
= get_ceph_conf(fsid
=fsid
)
1632 f
= cephadm_fs
.create_file('ceph.conf', contents
=s
)
1634 cmd
= self
._get
_cmd
('--fsid', fsid
, '--config', f
.path
)
1635 with
with_cephadm_ctx(cmd
) as ctx
:
1636 cd
.command_ceph_volume(ctx
)
1637 assert ctx
.fsid
== fsid
1639 cmd
= self
._get
_cmd
('--fsid', '10000000-0000-0000-0000-0000deadbeef', '--config', f
.path
)
1640 with
with_cephadm_ctx(cmd
) as ctx
:
1641 err
= 'fsid does not match ceph.conf'
1642 with pytest
.raises(cd
.Error
, match
=err
):
1643 cd
.command_ceph_volume(ctx
)
1644 assert ctx
.fsid
== None
1646 def test_config(self
, cephadm_fs
):
1647 cmd
= self
._get
_cmd
('--config', 'foo')
1648 with
with_cephadm_ctx(cmd
) as ctx
:
1649 err
= r
'No such file or directory'
1650 with pytest
.raises(cd
.Error
, match
=err
):
1651 cd
.command_ceph_volume(ctx
)
1653 cephadm_fs
.create_file('bar')
1654 cmd
= self
._get
_cmd
('--config', 'bar')
1655 with
with_cephadm_ctx(cmd
) as ctx
:
1656 cd
.command_ceph_volume(ctx
)
1657 assert ctx
.config
== 'bar'
1659 def test_keyring(self
, cephadm_fs
):
1660 cmd
= self
._get
_cmd
('--keyring', 'foo')
1661 with
with_cephadm_ctx(cmd
) as ctx
:
1662 err
= r
'No such file or directory'
1663 with pytest
.raises(cd
.Error
, match
=err
):
1664 cd
.command_ceph_volume(ctx
)
1666 cephadm_fs
.create_file('bar')
1667 cmd
= self
._get
_cmd
('--keyring', 'bar')
1668 with
with_cephadm_ctx(cmd
) as ctx
:
1669 cd
.command_ceph_volume(ctx
)
1670 assert ctx
.keyring
== 'bar'
1674 def test_unit_run(self
, cephadm_fs
):
1675 fsid
= '9b9d7609-f4d5-4aba-94c8-effa764d96c9'
1677 'files': {'iscsi-gateway.cfg': ''}
1679 with
with_cephadm_ctx(['--image=ceph/ceph'], list_networks
={}) as ctx
:
1681 ctx
.config_json
= json
.dumps(config_json
)
1683 cd
.get_parm
.return_value
= config_json
1684 c
= cd
.get_container(ctx
, fsid
, 'iscsi', 'daemon_id')
1686 cd
.make_data_dir(ctx
, fsid
, 'iscsi', 'daemon_id')
1687 cd
.deploy_daemon_units(
1697 with
open('/var/lib/ceph/9b9d7609-f4d5-4aba-94c8-effa764d96c9/iscsi.daemon_id/unit.run') as f
:
1698 assert f
.read() == """set -e
1699 if ! grep -qs /var/lib/ceph/9b9d7609-f4d5-4aba-94c8-effa764d96c9/iscsi.daemon_id/configfs /proc/mounts; then mount -t configfs none /var/lib/ceph/9b9d7609-f4d5-4aba-94c8-effa764d96c9/iscsi.daemon_id/configfs; fi
1700 # iscsi tcmu-runner container
1701 ! /usr/bin/podman rm -f ceph-9b9d7609-f4d5-4aba-94c8-effa764d96c9-iscsi.daemon_id-tcmu 2> /dev/null
1702 ! /usr/bin/podman rm -f ceph-9b9d7609-f4d5-4aba-94c8-effa764d96c9-iscsi-daemon_id-tcmu 2> /dev/null
1703 /usr/bin/podman run --rm --ipc=host --stop-signal=SIGTERM --net=host --entrypoint /usr/bin/tcmu-runner --privileged --group-add=disk --init --name ceph-9b9d7609-f4d5-4aba-94c8-effa764d96c9-iscsi-daemon_id-tcmu --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 &
1705 ! /usr/bin/podman rm -f ceph-9b9d7609-f4d5-4aba-94c8-effa764d96c9-iscsi.daemon_id 2> /dev/null
1706 ! /usr/bin/podman rm -f ceph-9b9d7609-f4d5-4aba-94c8-effa764d96c9-iscsi-daemon_id 2> /dev/null
1707 /usr/bin/podman run --rm --ipc=host --stop-signal=SIGTERM --net=host --entrypoint /usr/bin/rbd-target-api --privileged --group-add=disk --init --name ceph-9b9d7609-f4d5-4aba-94c8-effa764d96c9-iscsi-daemon_id --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
1710 def test_get_container(self
):
1712 Due to a combination of socket.getfqdn() and podman's behavior to
1713 add the container name into the /etc/hosts file, we cannot use periods
1714 in container names. But we need to be able to detect old existing containers.
1715 Assert this behaviour. I think we can remove this in Ceph R
1717 fsid
= '9b9d7609-f4d5-4aba-94c8-effa764d96c9'
1718 with
with_cephadm_ctx(['--image=ceph/ceph'], list_networks
={}) as ctx
:
1720 c
= cd
.get_container(ctx
, fsid
, 'iscsi', 'something')
1721 assert c
.cname
== 'ceph-9b9d7609-f4d5-4aba-94c8-effa764d96c9-iscsi-something'
1722 assert c
.old_cname
== 'ceph-9b9d7609-f4d5-4aba-94c8-effa764d96c9-iscsi.something'
1725 class TestCheckHost
:
1727 @mock.patch('cephadm.find_executable', return_value
='foo')
1728 @mock.patch('cephadm.check_time_sync', return_value
=True)
1729 def test_container_engine(self
, find_executable
, check_time_sync
):
1730 ctx
= cd
.CephadmContext()
1732 ctx
.container_engine
= None
1733 err
= r
'No container engine binary found'
1734 with pytest
.raises(cd
.Error
, match
=err
):
1735 cd
.command_check_host(ctx
)
1737 ctx
.container_engine
= mock_podman()
1738 cd
.command_check_host(ctx
)
1740 ctx
.container_engine
= mock_docker()
1741 cd
.command_check_host(ctx
)
1746 @pytest.mark
.parametrize('os_release',
1751 VERSION="20.04 LTS (Focal Fossa)"
1754 PRETTY_NAME="Ubuntu 20.04 LTS"
1756 HOME_URL="https://www.ubuntu.com/"
1757 SUPPORT_URL="https://help.ubuntu.com/"
1758 BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
1759 PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
1760 VERSION_CODENAME=focal
1761 UBUNTU_CODENAME=focal
1769 ID_LIKE="rhel fedora"
1771 PLATFORM_ID="platform:el8"
1772 PRETTY_NAME="CentOS Linux 8 (Core)"
1774 CPE_NAME="cpe:/o:centos:centos:8"
1775 HOME_URL="https://www.centos.org/"
1776 BUG_REPORT_URL="https://bugs.centos.org/"
1778 CENTOS_MANTISBT_PROJECT="CentOS-8"
1779 CENTOS_MANTISBT_PROJECT_VERSION="8"
1780 REDHAT_SUPPORT_PRODUCT="centos"
1781 REDHAT_SUPPORT_PRODUCT_VERSION="8"
1786 NAME="openSUSE Tumbleweed"
1787 # VERSION="20210810"
1788 ID="opensuse-tumbleweed"
1789 ID_LIKE="opensuse suse"
1790 VERSION_ID="20210810"
1791 PRETTY_NAME="openSUSE Tumbleweed"
1793 CPE_NAME="cpe:/o:opensuse:tumbleweed:20210810"
1794 BUG_REPORT_URL="https://bugs.opensuse.org"
1795 HOME_URL="https://www.opensuse.org/"
1796 DOCUMENTATION_URL="https://en.opensuse.org/Portal:Tumbleweed"
1797 LOGO="distributor-logo"
1800 @mock.patch('cephadm.find_executable', return_value
='foo')
1801 def test_container_engine(self
, find_executable
, os_release
, cephadm_fs
):
1802 cephadm_fs
.create_file('/etc/os-release', contents
=os_release
)
1803 ctx
= cd
.CephadmContext()
1805 ctx
.container_engine
= None
1806 cd
.command_rm_repo(ctx
)
1808 ctx
.container_engine
= mock_podman()
1809 cd
.command_rm_repo(ctx
)
1811 ctx
.container_engine
= mock_docker()
1812 cd
.command_rm_repo(ctx
)
1815 class TestValidateRepo
:
1817 @pytest.mark
.parametrize('values',
1824 os_release
=dedent("""
1826 VERSION="20.04 LTS (Focal Fossa)"
1829 PRETTY_NAME="Ubuntu 20.04 LTS"
1831 HOME_URL="https://www.ubuntu.com/"
1832 SUPPORT_URL="https://help.ubuntu.com/"
1833 BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
1834 PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
1835 VERSION_CODENAME=focal
1836 UBUNTU_CODENAME=focal
1839 # YumDnf on Centos8 - OK
1844 os_release
=dedent("""
1848 ID_LIKE="rhel fedora"
1850 PLATFORM_ID="platform:el8"
1851 PRETTY_NAME="CentOS Linux 8 (Core)"
1853 CPE_NAME="cpe:/o:centos:centos:8"
1854 HOME_URL="https://www.centos.org/"
1855 BUG_REPORT_URL="https://bugs.centos.org/"
1857 CENTOS_MANTISBT_PROJECT="CentOS-8"
1858 CENTOS_MANTISBT_PROJECT_VERSION="8"
1859 REDHAT_SUPPORT_PRODUCT="centos"
1860 REDHAT_SUPPORT_PRODUCT_VERSION="8"
1863 # YumDnf on Fedora - Fedora not supported
1867 err_text
="does not build Fedora",
1868 os_release
=dedent("""
1870 VERSION="35 (Cloud Edition)"
1874 PLATFORM_ID="platform:f35"
1875 PRETTY_NAME="Fedora Linux 35 (Cloud Edition)"
1876 ANSI_COLOR="0;38;2;60;110;180"
1877 LOGO=fedora-logo-icon
1878 CPE_NAME="cpe:/o:fedoraproject:fedora:35"
1879 HOME_URL="https://fedoraproject.org/"
1880 DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora/f35/system-administrators-guide/"
1881 SUPPORT_URL="https://ask.fedoraproject.org/"
1882 BUG_REPORT_URL="https://bugzilla.redhat.com/"
1883 REDHAT_BUGZILLA_PRODUCT="Fedora"
1884 REDHAT_BUGZILLA_PRODUCT_VERSION=35
1885 REDHAT_SUPPORT_PRODUCT="Fedora"
1886 REDHAT_SUPPORT_PRODUCT_VERSION=35
1887 PRIVACY_POLICY_URL="https://fedoraproject.org/wiki/Legal:PrivacyPolicy"
1888 VARIANT="Cloud Edition"
1892 # YumDnf on Centos 7 - no pacific
1896 err_text
="does not support pacific",
1897 os_release
=dedent("""
1901 ID_LIKE="rhel fedora"
1903 PRETTY_NAME="CentOS Linux 7 (Core)"
1905 CPE_NAME="cpe:/o:centos:centos:7"
1906 HOME_URL="https://www.centos.org/"
1907 BUG_REPORT_URL="https://bugs.centos.org/"
1909 CENTOS_MANTISBT_PROJECT="CentOS-7"
1910 CENTOS_MANTISBT_PROJECT_VERSION="7"
1911 REDHAT_SUPPORT_PRODUCT="centos"
1912 REDHAT_SUPPORT_PRODUCT_VERSION="7"
1915 # YumDnf on Centos 7 - nothing after pacific
1919 err_text
="does not support pacific",
1920 os_release
=dedent("""
1924 ID_LIKE="rhel fedora"
1926 PRETTY_NAME="CentOS Linux 7 (Core)"
1928 CPE_NAME="cpe:/o:centos:centos:7"
1929 HOME_URL="https://www.centos.org/"
1930 BUG_REPORT_URL="https://bugs.centos.org/"
1932 CENTOS_MANTISBT_PROJECT="CentOS-7"
1933 CENTOS_MANTISBT_PROJECT_VERSION="7"
1934 REDHAT_SUPPORT_PRODUCT="centos"
1935 REDHAT_SUPPORT_PRODUCT_VERSION="7"
1938 # YumDnf on Centos 7 - nothing v16 or higher
1942 err_text
="does not support",
1943 os_release
=dedent("""
1947 ID_LIKE="rhel fedora"
1949 PRETTY_NAME="CentOS Linux 7 (Core)"
1951 CPE_NAME="cpe:/o:centos:centos:7"
1952 HOME_URL="https://www.centos.org/"
1953 BUG_REPORT_URL="https://bugs.centos.org/"
1955 CENTOS_MANTISBT_PROJECT="CentOS-7"
1956 CENTOS_MANTISBT_PROJECT_VERSION="7"
1957 REDHAT_SUPPORT_PRODUCT="centos"
1958 REDHAT_SUPPORT_PRODUCT_VERSION="7"
1961 @mock.patch('cephadm.find_executable', return_value
='foo')
1962 def test_distro_validation(self
, find_executable
, values
, cephadm_fs
):
1963 os_release
= values
['os_release']
1964 release
= values
['release']
1965 version
= values
['version']
1966 err_text
= values
['err_text']
1968 cephadm_fs
.create_file('/etc/os-release', contents
=os_release
)
1969 ctx
= cd
.CephadmContext()
1970 ctx
.repo_url
= 'http://localhost'
1971 pkg
= cd
.create_packager(ctx
, stable
=release
, version
=version
)
1974 with pytest
.raises(cd
.Error
, match
=err_text
):
1977 with mock
.patch('cephadm.urlopen', return_value
=None):
1980 @pytest.mark
.parametrize('values',
1987 os_release
=dedent("""
1989 VERSION="20.04 LTS (Focal Fossa)"
1992 PRETTY_NAME="Ubuntu 20.04 LTS"
1994 HOME_URL="https://www.ubuntu.com/"
1995 SUPPORT_URL="https://help.ubuntu.com/"
1996 BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
1997 PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
1998 VERSION_CODENAME=focal
1999 UBUNTU_CODENAME=focal
2002 # YumDnf on Centos8 - force failure
2006 err_text
="failed to fetch repository metadata",
2007 os_release
=dedent("""
2011 ID_LIKE="rhel fedora"
2013 PLATFORM_ID="platform:el8"
2014 PRETTY_NAME="CentOS Linux 8 (Core)"
2016 CPE_NAME="cpe:/o:centos:centos:8"
2017 HOME_URL="https://www.centos.org/"
2018 BUG_REPORT_URL="https://bugs.centos.org/"
2020 CENTOS_MANTISBT_PROJECT="CentOS-8"
2021 CENTOS_MANTISBT_PROJECT_VERSION="8"
2022 REDHAT_SUPPORT_PRODUCT="centos"
2023 REDHAT_SUPPORT_PRODUCT_VERSION="8"
2026 @mock.patch('cephadm.find_executable', return_value
='foo')
2027 def test_http_validation(self
, find_executable
, values
, cephadm_fs
):
2028 from urllib
.error
import HTTPError
2030 os_release
= values
['os_release']
2031 release
= values
['release']
2032 version
= values
['version']
2033 err_text
= values
['err_text']
2035 cephadm_fs
.create_file('/etc/os-release', contents
=os_release
)
2036 ctx
= cd
.CephadmContext()
2037 ctx
.repo_url
= 'http://localhost'
2038 pkg
= cd
.create_packager(ctx
, stable
=release
, version
=version
)
2040 with mock
.patch('cephadm.urlopen') as _urlopen
:
2041 _urlopen
.side_effect
= HTTPError(ctx
.repo_url
, 404, "not found", None, fp
=None)
2043 with pytest
.raises(cd
.Error
, match
=err_text
):
2051 @mock.patch('time.sleep')
2052 @mock.patch('cephadm.call', return_value
=('', '', 0))
2053 @mock.patch('cephadm.get_image_info_from_inspect', return_value
={})
2054 def test_error(self
, get_image_info_from_inspect
, call
, sleep
):
2055 ctx
= cd
.CephadmContext()
2056 ctx
.container_engine
= mock_podman()
2057 ctx
.insecure
= False
2059 call
.return_value
= ('', '', 0)
2060 retval
= cd
.command_pull(ctx
)
2063 err
= 'maximum retries reached'
2065 call
.return_value
= ('', 'foobar', 1)
2066 with pytest
.raises(cd
.Error
) as e
:
2067 cd
.command_pull(ctx
)
2068 assert err
not in str(e
.value
)
2070 call
.return_value
= ('', 'net/http: TLS handshake timeout', 1)
2071 with pytest
.raises(cd
.Error
) as e
:
2072 cd
.command_pull(ctx
)
2073 assert err
in str(e
.value
)
2075 @mock.patch('cephadm.logger')
2076 @mock.patch('cephadm.get_image_info_from_inspect', return_value
={})
2077 @mock.patch('cephadm.infer_local_ceph_image', return_value
='last_local_ceph_image')
2078 def test_image(self
, infer_local_ceph_image
, get_image_info_from_inspect
, logger
):
2080 with
with_cephadm_ctx(cmd
) as ctx
:
2081 retval
= cd
.command_pull(ctx
)
2083 assert ctx
.image
== cd
.DEFAULT_IMAGE
2085 with mock
.patch
.dict(os
.environ
, {"CEPHADM_IMAGE": 'cephadm_image_environ'}):
2087 with
with_cephadm_ctx(cmd
) as ctx
:
2088 retval
= cd
.command_pull(ctx
)
2090 assert ctx
.image
== 'cephadm_image_environ'
2092 cmd
= ['--image', 'cephadm_image_param', 'pull']
2093 with
with_cephadm_ctx(cmd
) as ctx
:
2094 retval
= cd
.command_pull(ctx
)
2096 assert ctx
.image
== 'cephadm_image_param'
2099 class TestApplySpec
:
2101 def test_extract_host_info_from_applied_spec(self
, cephadm_fs
):
2105 addr: 192.168.122.44
2112 addr: 192.168.122.247
2123 rgw_frontend_ssl_certificate: |
2124 -----BEGIN PRIVATE KEY-----
2125 V2VyIGRhcyBsaWVzdCBpc3QgZG9vZi4gTG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFt
2126 ZXQsIGNvbnNldGV0dXIgc2FkaXBzY2luZyBlbGl0ciwgc2VkIGRpYW0gbm9udW15
2127 IGVpcm1vZCB0ZW1wb3IgaW52aWR1bnQgdXQgbGFib3JlIGV0IGRvbG9yZSBtYWdu
2128 YSBhbGlxdXlhbSBlcmF0LCBzZWQgZGlhbSB2b2x1cHR1YS4gQXQgdmVybyBlb3Mg
2129 ZXQgYWNjdXNhbSBldCBqdXN0byBkdW8=
2130 -----END PRIVATE KEY-----
2131 -----BEGIN CERTIFICATE-----
2132 V2VyIGRhcyBsaWVzdCBpc3QgZG9vZi4gTG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFt
2133 ZXQsIGNvbnNldGV0dXIgc2FkaXBzY2luZyBlbGl0ciwgc2VkIGRpYW0gbm9udW15
2134 IGVpcm1vZCB0ZW1wb3IgaW52aWR1bnQgdXQgbGFib3JlIGV0IGRvbG9yZSBtYWdu
2135 YSBhbGlxdXlhbSBlcmF0LCBzZWQgZGlhbSB2b2x1cHR1YS4gQXQgdmVybyBlb3Mg
2136 ZXQgYWNjdXNhbSBldCBqdXN0byBkdW8=
2137 -----END CERTIFICATE-----
2142 cephadm_fs
.create_file('spec.yml', contents
=yaml
)
2143 retdic
= [{'hostname': 'vm-00', 'addr': '192.168.122.44'},
2144 {'hostname': 'vm-01', 'addr': '192.168.122.247'},
2145 {'hostname': 'vm-02',}]
2147 with
open('spec.yml') as f
:
2148 dic
= cd
._extract
_host
_info
_from
_applied
_spec
(f
)
2149 assert dic
== retdic
2151 @mock.patch('cephadm.call', return_value
=('', '', 0))
2152 def test_distribute_ssh_keys(self
, call
):
2153 ctx
= cd
.CephadmContext()
2154 ctx
.ssh_public_key
= None
2155 ctx
.ssh_user
= 'root'
2157 host_spec
= {'service_type': 'host', 'hostname': 'vm-02', 'addr': '192.168.122.165'}
2159 retval
= cd
._distribute
_ssh
_keys
(ctx
, host_spec
, 'bootstrap_hostname')
2163 call
.return_value
= ('', '', 1)
2165 retval
= cd
._distribute
_ssh
_keys
(ctx
, host_spec
, 'bootstrap_hostname')
2170 class TestSNMPGateway
:
2172 'snmp_community': 'public',
2173 'destination': '192.168.1.10:162',
2174 'snmp_version': 'V2c',
2176 V3_no_priv_config
= {
2177 'destination': '192.168.1.10:162',
2178 'snmp_version': 'V3',
2179 'snmp_v3_auth_username': 'myuser',
2180 'snmp_v3_auth_password': 'mypassword',
2181 'snmp_v3_auth_protocol': 'SHA',
2182 'snmp_v3_engine_id': '8000C53F00000000',
2185 'destination': '192.168.1.10:162',
2186 'snmp_version': 'V3',
2187 'snmp_v3_auth_username': 'myuser',
2188 'snmp_v3_auth_password': 'mypassword',
2189 'snmp_v3_auth_protocol': 'SHA',
2190 'snmp_v3_priv_protocol': 'DES',
2191 'snmp_v3_priv_password': 'mysecret',
2192 'snmp_v3_engine_id': '8000C53F00000000',
2194 no_destination_config
= {
2195 'snmp_version': 'V3',
2196 'snmp_v3_auth_username': 'myuser',
2197 'snmp_v3_auth_password': 'mypassword',
2198 'snmp_v3_auth_protocol': 'SHA',
2199 'snmp_v3_priv_protocol': 'DES',
2200 'snmp_v3_priv_password': 'mysecret',
2201 'snmp_v3_engine_id': '8000C53F00000000',
2203 bad_version_config
= {
2204 'snmp_community': 'public',
2205 'destination': '192.168.1.10:162',
2206 'snmp_version': 'V1',
2209 def test_unit_run_V2c(self
, cephadm_fs
):
2210 fsid
= 'ca734440-3dc6-11ec-9b98-5254002537a6'
2211 with
with_cephadm_ctx(['--image=docker.io/maxwo/snmp-notifier:v1.2.1'], list_networks
={}) as ctx
:
2213 ctx
.config_json
= json
.dumps(self
.V2c_config
)
2215 ctx
.tcp_ports
= '9464'
2216 cd
.get_parm
.return_value
= self
.V2c_config
2217 c
= cd
.get_container(ctx
, fsid
, 'snmp-gateway', 'daemon_id')
2219 cd
.make_data_dir(ctx
, fsid
, 'snmp-gateway', 'daemon_id')
2221 cd
.create_daemon_dirs(ctx
, fsid
, 'snmp-gateway', 'daemon_id', 0, 0)
2222 with
open(f
'/var/lib/ceph/{fsid}/snmp-gateway.daemon_id/snmp-gateway.conf', 'r') as f
:
2223 conf
= f
.read().rstrip()
2224 assert conf
== 'SNMP_NOTIFIER_COMMUNITY=public'
2226 cd
.deploy_daemon_units(
2235 with
open(f
'/var/lib/ceph/{fsid}/snmp-gateway.daemon_id/unit.run', 'r') as f
:
2236 run_cmd
= f
.readlines()[-1].rstrip()
2237 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')
2239 def test_unit_run_V3_noPriv(self
, cephadm_fs
):
2240 fsid
= 'ca734440-3dc6-11ec-9b98-5254002537a6'
2241 with
with_cephadm_ctx(['--image=docker.io/maxwo/snmp-notifier:v1.2.1'], list_networks
={}) as ctx
:
2243 ctx
.config_json
= json
.dumps(self
.V3_no_priv_config
)
2245 ctx
.tcp_ports
= '9465'
2246 cd
.get_parm
.return_value
= self
.V3_no_priv_config
2247 c
= cd
.get_container(ctx
, fsid
, 'snmp-gateway', 'daemon_id')
2249 cd
.make_data_dir(ctx
, fsid
, 'snmp-gateway', 'daemon_id')
2251 cd
.create_daemon_dirs(ctx
, fsid
, 'snmp-gateway', 'daemon_id', 0, 0)
2252 with
open(f
'/var/lib/ceph/{fsid}/snmp-gateway.daemon_id/snmp-gateway.conf', 'r') as f
:
2254 assert conf
== 'SNMP_NOTIFIER_AUTH_USERNAME=myuser\nSNMP_NOTIFIER_AUTH_PASSWORD=mypassword\n'
2256 cd
.deploy_daemon_units(
2265 with
open(f
'/var/lib/ceph/{fsid}/snmp-gateway.daemon_id/unit.run', 'r') as f
:
2266 run_cmd
= f
.readlines()[-1].rstrip()
2267 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')
2269 def test_unit_run_V3_Priv(self
, cephadm_fs
):
2270 fsid
= 'ca734440-3dc6-11ec-9b98-5254002537a6'
2271 with
with_cephadm_ctx(['--image=docker.io/maxwo/snmp-notifier:v1.2.1'], list_networks
={}) as ctx
:
2273 ctx
.config_json
= json
.dumps(self
.V3_priv_config
)
2275 ctx
.tcp_ports
= '9464'
2276 cd
.get_parm
.return_value
= self
.V3_priv_config
2277 c
= cd
.get_container(ctx
, fsid
, 'snmp-gateway', 'daemon_id')
2279 cd
.make_data_dir(ctx
, fsid
, 'snmp-gateway', 'daemon_id')
2281 cd
.create_daemon_dirs(ctx
, fsid
, 'snmp-gateway', 'daemon_id', 0, 0)
2282 with
open(f
'/var/lib/ceph/{fsid}/snmp-gateway.daemon_id/snmp-gateway.conf', 'r') as f
:
2284 assert conf
== 'SNMP_NOTIFIER_AUTH_USERNAME=myuser\nSNMP_NOTIFIER_AUTH_PASSWORD=mypassword\nSNMP_NOTIFIER_PRIV_PASSWORD=mysecret\n'
2286 cd
.deploy_daemon_units(
2295 with
open(f
'/var/lib/ceph/{fsid}/snmp-gateway.daemon_id/unit.run', 'r') as f
:
2296 run_cmd
= f
.readlines()[-1].rstrip()
2297 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')
2299 def test_unit_run_no_dest(self
, cephadm_fs
):
2300 fsid
= 'ca734440-3dc6-11ec-9b98-5254002537a6'
2301 with
with_cephadm_ctx(['--image=docker.io/maxwo/snmp-notifier:v1.2.1'], list_networks
={}) as ctx
:
2303 ctx
.config_json
= json
.dumps(self
.no_destination_config
)
2305 ctx
.tcp_ports
= '9464'
2306 cd
.get_parm
.return_value
= self
.no_destination_config
2308 with pytest
.raises(Exception) as e
:
2309 c
= cd
.get_container(ctx
, fsid
, 'snmp-gateway', 'daemon_id')
2310 assert str(e
.value
) == "config is missing destination attribute(<ip>:<port>) of the target SNMP listener"
2312 def test_unit_run_bad_version(self
, cephadm_fs
):
2313 fsid
= 'ca734440-3dc6-11ec-9b98-5254002537a6'
2314 with
with_cephadm_ctx(['--image=docker.io/maxwo/snmp-notifier:v1.2.1'], list_networks
={}) as ctx
:
2316 ctx
.config_json
= json
.dumps(self
.bad_version_config
)
2318 ctx
.tcp_ports
= '9464'
2319 cd
.get_parm
.return_value
= self
.bad_version_config
2321 with pytest
.raises(Exception) as e
:
2322 c
= cd
.get_container(ctx
, fsid
, 'snmp-gateway', 'daemon_id')
2323 assert str(e
.value
) == 'not a valid snmp version: V1'
2325 class TestNetworkValidation
:
2327 def test_ipv4_subnet(self
):
2328 rc
, v
, msg
= cd
.check_subnet('192.168.1.0/24')
2329 assert rc
== 0 and v
[0] == 4
2331 def test_ipv4_subnet_list(self
):
2332 rc
, v
, msg
= cd
.check_subnet('192.168.1.0/24,10.90.90.0/24')
2333 assert rc
== 0 and not msg
2335 def test_ipv4_subnet_list_with_spaces(self
):
2336 rc
, v
, msg
= cd
.check_subnet('192.168.1.0/24, 10.90.90.0/24 ')
2337 assert rc
== 0 and not msg
2339 def test_ipv4_subnet_badlist(self
):
2340 rc
, v
, msg
= cd
.check_subnet('192.168.1.0/24,192.168.1.1')
2341 assert rc
== 1 and msg
2343 def test_ipv4_subnet_mixed(self
):
2344 rc
, v
, msg
= cd
.check_subnet('192.168.100.0/24,fe80::/64')
2345 assert rc
== 0 and v
== [4,6]
2347 def test_ipv6_subnet(self
):
2348 rc
, v
, msg
= cd
.check_subnet('fe80::/64')
2349 assert rc
== 0 and v
[0] == 6
2351 def test_subnet_mask_missing(self
):
2352 rc
, v
, msg
= cd
.check_subnet('192.168.1.58')
2353 assert rc
== 1 and msg
2355 def test_subnet_mask_junk(self
):
2356 rc
, v
, msg
= cd
.check_subnet('wah')
2357 assert rc
== 1 and msg
2359 def test_ip_in_subnet(self
):
2360 # valid ip and only one valid subnet
2361 rc
= cd
.ip_in_subnets('192.168.100.1', '192.168.100.0/24')
2364 # valid ip and valid subnets list without spaces
2365 rc
= cd
.ip_in_subnets('192.168.100.1', '192.168.100.0/24,10.90.90.0/24')
2368 # valid ip and valid subnets list with spaces
2369 rc
= cd
.ip_in_subnets('10.90.90.2', '192.168.1.0/24, 192.168.100.0/24, 10.90.90.0/24')
2372 # valid ip that doesn't belong to any subnet
2373 rc
= cd
.ip_in_subnets('192.168.100.2', '192.168.50.0/24, 10.90.90.0/24')
2376 # valid ip that doesn't belong to the subnet (only 14 hosts)
2377 rc
= cd
.ip_in_subnets('192.168.100.20', '192.168.100.0/28')
2380 # valid ip and valid IPV6 network
2381 rc
= cd
.ip_in_subnets('fe80::5054:ff:fef4:873a', 'fe80::/64')
2384 # valid wrapped ip and valid IPV6 network
2385 rc
= cd
.ip_in_subnets('[fe80::5054:ff:fef4:873a]', 'fe80::/64')
2388 # valid ip and that doesn't belong to IPV6 network
2389 rc
= cd
.ip_in_subnets('fe80::5054:ff:fef4:873a', '2001:db8:85a3::/64')
2392 # invalid IPv4 and valid subnets list
2393 with pytest
.raises(Exception):
2394 rc
= cd
.ip_in_sublets('10.90.200.', '192.168.1.0/24, 192.168.100.0/24, 10.90.90.0/24')
2396 # invalid IPv6 and valid subnets list
2397 with pytest
.raises(Exception):
2398 rc
= cd
.ip_in_sublets('fe80:2030:31:24', 'fe80::/64')
2401 @pytest.mark
.parametrize("conf", [
2403 public_network='1.1.1.0/24,2.2.2.0/24'
2404 cluster_network="3.3.3.0/24, 4.4.4.0/24"
2407 public_network=" 1.1.1.0/24,2.2.2.0/24 "
2408 cluster_network=3.3.3.0/24, 4.4.4.0/24
2411 public_network= 1.1.1.0/24, 2.2.2.0/24
2412 cluster_network='3.3.3.0/24,4.4.4.0/24'
2414 @mock.patch('cephadm.list_networks')
2415 def test_get_networks_from_conf(self
, _list_networks
, conf
, cephadm_fs
):
2416 cephadm_fs
.create_file('ceph.conf', contents
=conf
)
2417 _list_networks
.return_value
= {'1.1.1.0/24': {'eth0': ['1.1.1.1']},
2418 '2.2.2.0/24': {'eth1': ['2.2.2.2']},
2419 '3.3.3.0/24': {'eth2': ['3.3.3.3']},
2420 '4.4.4.0/24': {'eth3': ['4.4.4.4']}}
2421 ctx
= cd
.CephadmContext()
2422 ctx
.config
= 'ceph.conf'
2423 ctx
.mon_ip
= '1.1.1.1'
2424 ctx
.cluster_network
= None
2425 # what the cephadm module does with the public network string is
2426 # [x.strip() for x in out.split(',')]
2427 # so we must make sure our output, through that alteration,
2428 # generates correctly formatted networks
2429 def _str_to_networks(s
):
2430 return [x
.strip() for x
in s
.split(',')]
2431 public_network
= cd
.get_public_net_from_cfg(ctx
)
2432 assert _str_to_networks(public_network
) == ['1.1.1.0/24', '2.2.2.0/24']
2433 cluster_network
, ipv6
= cd
.prepare_cluster_network(ctx
)
2435 assert _str_to_networks(cluster_network
) == ['3.3.3.0/24', '4.4.4.0/24']
2437 class TestRescan(fake_filesystem_unittest
.TestCase
):
2440 self
.setUpPyfakefs()
2441 if not fake_filesystem
.is_root():
2442 fake_filesystem
.set_uid(0)
2444 self
.fs
.create_dir('/sys/class')
2445 self
.ctx
= cd
.CephadmContext()
2446 self
.ctx
.func
= cd
.command_rescan_disks
2448 def test_no_hbas(self
):
2449 out
= cd
.command_rescan_disks(self
.ctx
)
2450 assert out
== 'Ok. No compatible HBAs found'
2452 def test_success(self
):
2453 self
.fs
.create_file('/sys/class/scsi_host/host0/scan')
2454 self
.fs
.create_file('/sys/class/scsi_host/host1/scan')
2455 out
= cd
.command_rescan_disks(self
.ctx
)
2456 assert out
.startswith('Ok. 2 adapters detected: 2 rescanned, 0 skipped, 0 failed')
2458 def test_skip_usb_adapter(self
):
2459 self
.fs
.create_file('/sys/class/scsi_host/host0/scan')
2460 self
.fs
.create_file('/sys/class/scsi_host/host1/scan')
2461 self
.fs
.create_file('/sys/class/scsi_host/host1/proc_name', contents
='usb-storage')
2462 out
= cd
.command_rescan_disks(self
.ctx
)
2463 assert out
.startswith('Ok. 2 adapters detected: 1 rescanned, 1 skipped, 0 failed')
2465 def test_skip_unknown_adapter(self
):
2466 self
.fs
.create_file('/sys/class/scsi_host/host0/scan')
2467 self
.fs
.create_file('/sys/class/scsi_host/host1/scan')
2468 self
.fs
.create_file('/sys/class/scsi_host/host1/proc_name', contents
='unknown')
2469 out
= cd
.command_rescan_disks(self
.ctx
)
2470 assert out
.startswith('Ok. 2 adapters detected: 1 rescanned, 1 skipped, 0 failed')