10 from textwrap
import dedent
12 from .fixtures
import (
20 with mock
.patch('builtins.open', create
=True):
21 from importlib
.machinery
import SourceFileLoader
22 cd
= SourceFileLoader('cephadm', 'cephadm').load_module()
26 fsid
='00000000-0000-0000-0000-0000deadbeef',
27 mon_host
='[v2:192.168.1.1:3300/0,v1:192.168.1.1:6789/0]'):
29 # minimal ceph.conf for {fsid}
35 class TestCephAdm(object):
37 def test_docker_unit_file(self
):
38 ctx
= cd
.CephadmContext()
39 ctx
.container_engine
= mock_docker()
40 r
= cd
.get_unit_file(ctx
, '9b9d7609-f4d5-4aba-94c8-effa764d96c9')
41 assert 'Requires=docker.service' in r
42 ctx
.container_engine
= mock_podman()
43 r
= cd
.get_unit_file(ctx
, '9b9d7609-f4d5-4aba-94c8-effa764d96c9')
44 assert 'Requires=docker.service' not in r
46 @mock.patch('cephadm.logger')
47 def test_attempt_bind(self
, logger
):
54 _os_error
.errno
= errno
57 for side_effect
, expected_exception
in (
58 (os_error(errno
.EADDRINUSE
), cd
.PortOccupiedError
),
59 (os_error(errno
.EAFNOSUPPORT
), cd
.Error
),
60 (os_error(errno
.EADDRNOTAVAIL
), cd
.Error
),
64 _socket
.bind
.side_effect
= side_effect
66 cd
.attempt_bind(ctx
, _socket
, address
, port
)
67 except Exception as e
:
68 assert isinstance(e
, expected_exception
)
70 if expected_exception
is not None:
73 @mock.patch('cephadm.attempt_bind')
74 @mock.patch('cephadm.logger')
75 def test_port_in_use(self
, logger
, attempt_bind
):
78 assert cd
.port_in_use(empty_ctx
, 9100) == False
80 attempt_bind
.side_effect
= cd
.PortOccupiedError('msg')
81 assert cd
.port_in_use(empty_ctx
, 9100) == True
84 os_error
.errno
= errno
.EADDRNOTAVAIL
85 attempt_bind
.side_effect
= os_error
86 assert cd
.port_in_use(empty_ctx
, 9100) == False
89 os_error
.errno
= errno
.EAFNOSUPPORT
90 attempt_bind
.side_effect
= os_error
91 assert cd
.port_in_use(empty_ctx
, 9100) == False
93 @mock.patch('socket.socket')
94 @mock.patch('cephadm.logger')
95 def test_check_ip_port_success(self
, logger
, _socket
):
96 ctx
= cd
.CephadmContext()
97 ctx
.skip_ping_check
= False # enables executing port check with `check_ip_port`
99 for address
, address_family
in (
100 ('0.0.0.0', socket
.AF_INET
),
101 ('::', socket
.AF_INET6
),
104 cd
.check_ip_port(ctx
, cd
.EndPoint(address
, 9100))
108 assert _socket
.call_args
== mock
.call(address_family
, socket
.SOCK_STREAM
)
110 @mock.patch('socket.socket')
111 @mock.patch('cephadm.logger')
112 def test_check_ip_port_failure(self
, logger
, _socket
):
113 ctx
= cd
.CephadmContext()
114 ctx
.skip_ping_check
= False # enables executing port check with `check_ip_port`
117 _os_error
= OSError()
118 _os_error
.errno
= errno
121 for address
, address_family
in (
122 ('0.0.0.0', socket
.AF_INET
),
123 ('::', socket
.AF_INET6
),
125 for side_effect
, expected_exception
in (
126 (os_error(errno
.EADDRINUSE
), cd
.PortOccupiedError
),
127 (os_error(errno
.EADDRNOTAVAIL
), cd
.Error
),
128 (os_error(errno
.EAFNOSUPPORT
), cd
.Error
),
131 mock_socket_obj
= mock
.Mock()
132 mock_socket_obj
.bind
.side_effect
= side_effect
133 _socket
.return_value
= mock_socket_obj
135 cd
.check_ip_port(ctx
, cd
.EndPoint(address
, 9100))
136 except Exception as e
:
137 assert isinstance(e
, expected_exception
)
139 if side_effect
is not None:
143 def test_is_not_fsid(self
):
144 assert not cd
.is_fsid('no-uuid')
146 def test_is_fsid(self
):
147 assert cd
.is_fsid('e863154d-33c7-4350-bca5-921e0467e55b')
149 def test__get_parser_image(self
):
150 args
= cd
._parse
_args
(['--image', 'foo', 'version'])
151 assert args
.image
== 'foo'
153 def test_parse_mem_usage(self
):
154 cd
.logger
= mock
.Mock()
155 len, summary
= cd
._parse
_mem
_usage
(0, 'c6290e3f1489,-- / --')
158 def test_CustomValidation(self
):
159 assert cd
._parse
_args
(['deploy', '--name', 'mon.a', '--fsid', 'fsid'])
161 with pytest
.raises(SystemExit):
162 cd
._parse
_args
(['deploy', '--name', 'wrong', '--fsid', 'fsid'])
164 @pytest.mark
.parametrize("test_input, expected", [
166 ("1.6.2-stable2", (1,6,2)),
168 def test_parse_podman_version(self
, test_input
, expected
):
169 assert cd
._parse
_podman
_version
(test_input
) == expected
171 def test_parse_podman_version_invalid(self
):
172 with pytest
.raises(ValueError) as res
:
173 cd
._parse
_podman
_version
('inval.id')
174 assert 'inval' in str(res
.value
)
176 def test_is_ipv6(self
):
177 cd
.logger
= mock
.Mock()
178 for good
in ("[::1]", "::1",
179 "fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"):
180 assert cd
.is_ipv6(good
)
181 for bad
in ("127.0.0.1",
182 "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffg",
183 "1:2:3:4:5:6:7:8:9", "fd00::1::1", "[fg::1]"):
184 assert not cd
.is_ipv6(bad
)
186 def test_unwrap_ipv6(self
):
187 def unwrap_test(address
, expected
):
188 assert cd
.unwrap_ipv6(address
) == expected
191 ('::1', '::1'), ('[::1]', '::1'),
192 ('[fde4:8dba:82e1:0:5054:ff:fe6a:357]', 'fde4:8dba:82e1:0:5054:ff:fe6a:357'),
193 ('can actually be any string', 'can actually be any string'),
194 ('[but needs to be stripped] ', '[but needs to be stripped] ')]
195 for address
, expected
in tests
:
196 unwrap_test(address
, expected
)
198 def test_wrap_ipv6(self
):
199 def wrap_test(address
, expected
):
200 assert cd
.wrap_ipv6(address
) == expected
203 ('::1', '[::1]'), ('[::1]', '[::1]'),
204 ('fde4:8dba:82e1:0:5054:ff:fe6a:357',
205 '[fde4:8dba:82e1:0:5054:ff:fe6a:357]'),
206 ('myhost.example.com', 'myhost.example.com'),
207 ('192.168.0.1', '192.168.0.1'),
208 ('', ''), ('fd00::1::1', 'fd00::1::1')]
209 for address
, expected
in tests
:
210 wrap_test(address
, expected
)
212 @mock.patch('cephadm.Firewalld', mock_bad_firewalld
)
213 @mock.patch('cephadm.logger')
214 def test_skip_firewalld(self
, logger
, cephadm_fs
):
216 test --skip-firewalld actually skips changing firewall
219 ctx
= cd
.CephadmContext()
220 with pytest
.raises(Exception):
221 cd
.update_firewalld(ctx
, 'mon')
223 ctx
.skip_firewalld
= True
224 cd
.update_firewalld(ctx
, 'mon')
226 ctx
.skip_firewalld
= False
227 with pytest
.raises(Exception):
228 cd
.update_firewalld(ctx
, 'mon')
230 ctx
= cd
.CephadmContext()
231 ctx
.ssl_dashboard_port
= 8888
232 ctx
.dashboard_key
= None
233 ctx
.dashboard_password_noupdate
= True
234 ctx
.initial_dashboard_password
= 'password'
235 ctx
.initial_dashboard_user
= 'User'
236 with pytest
.raises(Exception):
237 cd
.prepare_dashboard(ctx
, 0, 0, lambda _
, extra_mounts
=None, ___
=None : '5', lambda : None)
239 ctx
.skip_firewalld
= True
240 cd
.prepare_dashboard(ctx
, 0, 0, lambda _
, extra_mounts
=None, ___
=None : '5', lambda : None)
242 ctx
.skip_firewalld
= False
243 with pytest
.raises(Exception):
244 cd
.prepare_dashboard(ctx
, 0, 0, lambda _
, extra_mounts
=None, ___
=None : '5', lambda : None)
246 @mock.patch('cephadm.call_throws')
247 @mock.patch('cephadm.get_parm')
248 def test_registry_login(self
, get_parm
, call_throws
):
249 # test normal valid login with url, username and password specified
250 call_throws
.return_value
= '', '', 0
251 ctx
: cd
.CephadmContext
= cd
.cephadm_init_ctx(
252 ['registry-login', '--registry-url', 'sample-url',
253 '--registry-username', 'sample-user', '--registry-password',
255 ctx
.container_engine
= mock_docker()
256 retval
= cd
.command_registry_login(ctx
)
259 # test bad login attempt with invalid arguments given
260 ctx
: cd
.CephadmContext
= cd
.cephadm_init_ctx(
261 ['registry-login', '--registry-url', 'bad-args-url'])
262 with pytest
.raises(Exception) as e
:
263 assert cd
.command_registry_login(ctx
)
264 assert str(e
.value
) == ('Invalid custom registry arguments received. To login to a custom registry include '
265 '--registry-url, --registry-username and --registry-password options or --registry-json option')
267 # test normal valid login with json file
268 get_parm
.return_value
= {"url": "sample-url", "username": "sample-username", "password": "sample-password"}
269 ctx
: cd
.CephadmContext
= cd
.cephadm_init_ctx(
270 ['registry-login', '--registry-json', 'sample-json'])
271 ctx
.container_engine
= mock_docker()
272 retval
= cd
.command_registry_login(ctx
)
275 # test bad login attempt with bad json file
276 get_parm
.return_value
= {"bad-json": "bad-json"}
277 ctx
: cd
.CephadmContext
= cd
.cephadm_init_ctx(
278 ['registry-login', '--registry-json', 'sample-json'])
279 with pytest
.raises(Exception) as e
:
280 assert cd
.command_registry_login(ctx
)
281 assert str(e
.value
) == ("json provided for custom registry login did not include all necessary fields. "
282 "Please setup json file as\n"
284 " \"url\": \"REGISTRY_URL\",\n"
285 " \"username\": \"REGISTRY_USERNAME\",\n"
286 " \"password\": \"REGISTRY_PASSWORD\"\n"
289 # test login attempt with valid arguments where login command fails
290 call_throws
.side_effect
= Exception
291 ctx
: cd
.CephadmContext
= cd
.cephadm_init_ctx(
292 ['registry-login', '--registry-url', 'sample-url',
293 '--registry-username', 'sample-user', '--registry-password',
295 with pytest
.raises(Exception) as e
:
296 cd
.command_registry_login(ctx
)
297 assert str(e
.value
) == "Failed to login to custom registry @ sample-url as sample-user with given password"
299 def test_get_image_info_from_inspect(self
):
301 out
= """204a01f9b0b6710dd0c0af7f37ce7139c47ff0f0105d778d7104c69282dfbbf1,[docker.io/ceph/ceph@sha256:1cc9b824e1b076cdff52a9aa3f0cc8557d879fb2fbbba0cafed970aca59a3992]"""
302 r
= cd
.get_image_info_from_inspect(out
, 'registry/ceph/ceph:latest')
305 'image_id': '204a01f9b0b6710dd0c0af7f37ce7139c47ff0f0105d778d7104c69282dfbbf1',
306 'repo_digests': ['docker.io/ceph/ceph@sha256:1cc9b824e1b076cdff52a9aa3f0cc8557d879fb2fbbba0cafed970aca59a3992']
310 out
= """sha256:16f4549cf7a8f112bbebf7946749e961fbbd1b0838627fe619aab16bc17ce552,[quay.ceph.io/ceph-ci/ceph@sha256:4e13da36c1bd6780b312a985410ae678984c37e6a9493a74c87e4a50b9bda41f]"""
311 r
= cd
.get_image_info_from_inspect(out
, 'registry/ceph/ceph:latest')
313 'image_id': '16f4549cf7a8f112bbebf7946749e961fbbd1b0838627fe619aab16bc17ce552',
314 'repo_digests': ['quay.ceph.io/ceph-ci/ceph@sha256:4e13da36c1bd6780b312a985410ae678984c37e6a9493a74c87e4a50b9bda41f']
317 # multiple digests (podman)
318 out
= """e935122ab143a64d92ed1fbb27d030cf6e2f0258207be1baf1b509c466aeeb42,[docker.io/prom/prometheus@sha256:e4ca62c0d62f3e886e684806dfe9d4e0cda60d54986898173c1083856cfda0f4 docker.io/prom/prometheus@sha256:efd99a6be65885c07c559679a0df4ec709604bcdd8cd83f0d00a1a683b28fb6a]"""
319 r
= cd
.get_image_info_from_inspect(out
, 'registry/prom/prometheus:latest')
321 'image_id': 'e935122ab143a64d92ed1fbb27d030cf6e2f0258207be1baf1b509c466aeeb42',
323 'docker.io/prom/prometheus@sha256:e4ca62c0d62f3e886e684806dfe9d4e0cda60d54986898173c1083856cfda0f4',
324 'docker.io/prom/prometheus@sha256:efd99a6be65885c07c559679a0df4ec709604bcdd8cd83f0d00a1a683b28fb6a',
329 def test_dict_get(self
):
330 result
= cd
.dict_get({'a': 1}, 'a', require
=True)
332 result
= cd
.dict_get({'a': 1}, 'b')
333 assert result
is None
334 result
= cd
.dict_get({'a': 1}, 'b', default
=2)
337 def test_dict_get_error(self
):
338 with pytest
.raises(cd
.Error
):
339 cd
.dict_get({'a': 1}, 'b', require
=True)
341 def test_dict_get_join(self
):
342 result
= cd
.dict_get_join({'foo': ['a', 'b']}, 'foo')
343 assert result
== 'a\nb'
344 result
= cd
.dict_get_join({'foo': [1, 2]}, 'foo')
345 assert result
== '1\n2'
346 result
= cd
.dict_get_join({'bar': 'a'}, 'bar')
348 result
= cd
.dict_get_join({'a': 1}, 'a')
351 @mock.patch('os.listdir', return_value
=[])
352 @mock.patch('cephadm.logger')
353 def test_infer_local_ceph_image(self
, _logger
, _listdir
):
354 ctx
= cd
.CephadmContext()
355 ctx
.fsid
= '00000000-0000-0000-0000-0000deadbeez'
356 ctx
.container_engine
= mock_podman()
358 # make sure the right image is selected when container is found
359 cinfo
= cd
.ContainerInfo('935b549714b8f007c6a4e29c758689cf9e8e69f2e0f51180506492974b90a972',
360 'registry.hub.docker.com/rkachach/ceph:custom-v0.5',
361 '514e6a882f6e74806a5856468489eeff8d7106095557578da96935e4d0ba4d9d',
362 '2022-04-19 13:45:20.97146228 +0000 UTC',
364 out
= '''quay.ceph.io/ceph-ci/ceph@sha256:87f200536bb887b36b959e887d5984dd7a3f008a23aa1f283ab55d48b22c6185|dad864ee21e9|master|2022-03-23 16:29:19 +0000 UTC
365 quay.ceph.io/ceph-ci/ceph@sha256:b50b130fcda2a19f8507ddde3435bb4722266956e1858ac395c838bc1dcf1c0e|514e6a882f6e|pacific|2022-03-23 15:58:34 +0000 UTC
366 docker.io/ceph/ceph@sha256:939a46c06b334e094901560c8346de33c00309e3e3968a2db240eb4897c6a508|666bbfa87e8d|v15.2.5|2020-09-16 14:15:15 +0000 UTC'''
367 with mock
.patch('cephadm.call_throws', return_value
=(out
, '', '')):
368 with mock
.patch('cephadm.get_container_info', return_value
=cinfo
):
369 image
= cd
.infer_local_ceph_image(ctx
, ctx
.container_engine
)
370 assert image
== 'quay.ceph.io/ceph-ci/ceph@sha256:b50b130fcda2a19f8507ddde3435bb4722266956e1858ac395c838bc1dcf1c0e'
372 # make sure first valid image is used when no container_info is found
373 out
= '''quay.ceph.io/ceph-ci/ceph@sha256:87f200536bb887b36b959e887d5984dd7a3f008a23aa1f283ab55d48b22c6185|dad864ee21e9|master|2022-03-23 16:29:19 +0000 UTC
374 quay.ceph.io/ceph-ci/ceph@sha256:b50b130fcda2a19f8507ddde3435bb4722266956e1858ac395c838bc1dcf1c0e|514e6a882f6e|pacific|2022-03-23 15:58:34 +0000 UTC
375 docker.io/ceph/ceph@sha256:939a46c06b334e094901560c8346de33c00309e3e3968a2db240eb4897c6a508|666bbfa87e8d|v15.2.5|2020-09-16 14:15:15 +0000 UTC'''
376 with mock
.patch('cephadm.call_throws', return_value
=(out
, '', '')):
377 with mock
.patch('cephadm.get_container_info', return_value
=None):
378 image
= cd
.infer_local_ceph_image(ctx
, ctx
.container_engine
)
379 assert image
== 'quay.ceph.io/ceph-ci/ceph@sha256:87f200536bb887b36b959e887d5984dd7a3f008a23aa1f283ab55d48b22c6185'
381 # make sure images without digest are discarded (no container_info is found)
382 out
= '''quay.ceph.io/ceph-ci/ceph@|||
383 docker.io/ceph/ceph@|||
384 docker.io/ceph/ceph@sha256:939a46c06b334e094901560c8346de33c00309e3e3968a2db240eb4897c6a508|666bbfa87e8d|v15.2.5|2020-09-16 14:15:15 +0000 UTC'''
385 with mock
.patch('cephadm.call_throws', return_value
=(out
, '', '')):
386 with mock
.patch('cephadm.get_container_info', return_value
=None):
387 image
= cd
.infer_local_ceph_image(ctx
, ctx
.container_engine
)
388 assert image
== 'docker.io/ceph/ceph@sha256:939a46c06b334e094901560c8346de33c00309e3e3968a2db240eb4897c6a508'
392 @pytest.mark
.parametrize('daemon_filter, by_name, daemon_list, container_stats, output',
394 # get container info by type ('mon')
399 {'name': 'mon.ceph-node-0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'},
400 {'name': 'mgr.ceph-node-0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'},
402 ("935b549714b8f007c6a4e29c758689cf9e8e69f2e0f51180506492974b90a972,registry.hub.docker.com/rkachach/ceph:custom-v0.5,666bbfa87e8df05702d6172cae11dd7bc48efb1d94f1b9e492952f19647199a4,2022-04-19 13:45:20.97146228 +0000 UTC,",
405 cd
.ContainerInfo('935b549714b8f007c6a4e29c758689cf9e8e69f2e0f51180506492974b90a972',
406 'registry.hub.docker.com/rkachach/ceph:custom-v0.5',
407 '666bbfa87e8df05702d6172cae11dd7bc48efb1d94f1b9e492952f19647199a4',
408 '2022-04-19 13:45:20.97146228 +0000 UTC',
411 # get container info by name ('mon.ceph-node-0')
416 {'name': 'mgr.ceph-node-0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'},
417 {'name': 'mon.ceph-node-0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'},
419 ("935b549714b8f007c6a4e29c758689cf9e8e69f2e0f51180506492974b90a972,registry.hub.docker.com/rkachach/ceph:custom-v0.5,666bbfa87e8df05702d6172cae11dd7bc48efb1d94f1b9e492952f19647199a4,2022-04-19 13:45:20.97146228 +0000 UTC,",
422 cd
.ContainerInfo('935b549714b8f007c6a4e29c758689cf9e8e69f2e0f51180506492974b90a972',
423 'registry.hub.docker.com/rkachach/ceph:custom-v0.5',
424 '666bbfa87e8df05702d6172cae11dd7bc48efb1d94f1b9e492952f19647199a4',
425 '2022-04-19 13:45:20.97146228 +0000 UTC',
428 # get container info by name (same daemon but two different fsids)
433 {'name': 'mon.ceph-node-0', 'fsid': '10000000-0000-0000-0000-0000deadbeef'},
434 {'name': 'mon.ceph-node-0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'},
436 ("935b549714b8f007c6a4e29c758689cf9e8e69f2e0f51180506492974b90a972,registry.hub.docker.com/rkachach/ceph:custom-v0.5,666bbfa87e8df05702d6172cae11dd7bc48efb1d94f1b9e492952f19647199a4,2022-04-19 13:45:20.97146228 +0000 UTC,",
439 cd
.ContainerInfo('935b549714b8f007c6a4e29c758689cf9e8e69f2e0f51180506492974b90a972',
440 'registry.hub.docker.com/rkachach/ceph:custom-v0.5',
441 '666bbfa87e8df05702d6172cae11dd7bc48efb1d94f1b9e492952f19647199a4',
442 '2022-04-19 13:45:20.97146228 +0000 UTC',
445 # get container info by type (bad container stats: 127 code)
450 {'name': 'mon.ceph-node-0', 'fsid': '00000000-FFFF-0000-0000-0000deadbeef'},
451 {'name': 'mon.ceph-node-0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'},
458 # get container info by name (bad container stats: 127 code)
463 {'name': 'mgr.ceph-node-0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'},
464 {'name': 'mon.ceph-node-0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'},
471 # get container info by invalid name (doens't contain '.')
476 {'name': 'mon.ceph-node-0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'},
477 {'name': 'mon.ceph-node-0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'},
479 ("935b549714b8f007c6a4e29c758689cf9e8e69f2e0f51180506492974b90a972,registry.hub.docker.com/rkachach/ceph:custom-v0.5,666bbfa87e8df05702d6172cae11dd7bc48efb1d94f1b9e492952f19647199a4,2022-04-19 13:45:20.97146228 +0000 UTC,",
484 # get container info by invalid name (empty)
489 {'name': 'mon.ceph-node-0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'},
490 {'name': 'mon.ceph-node-0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'},
492 ("935b549714b8f007c6a4e29c758689cf9e8e69f2e0f51180506492974b90a972,registry.hub.docker.com/rkachach/ceph:custom-v0.5,666bbfa87e8df05702d6172cae11dd7bc48efb1d94f1b9e492952f19647199a4,2022-04-19 13:45:20.97146228 +0000 UTC,",
497 # get container info by invalid type (empty)
502 {'name': 'mon.ceph-node-0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'},
503 {'name': 'mon.ceph-node-0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'},
505 ("935b549714b8f007c6a4e29c758689cf9e8e69f2e0f51180506492974b90a972,registry.hub.docker.com/rkachach/ceph:custom-v0.5,666bbfa87e8df05702d6172cae11dd7bc48efb1d94f1b9e492952f19647199a4,2022-04-19 13:45:20.97146228 +0000 UTC,",
510 # get container info by name: no match (invalid fsid)
515 {'name': 'mon.ceph-node-0', 'fsid': '00000000-1111-0000-0000-0000deadbeef'},
516 {'name': 'mon.ceph-node-0', 'fsid': '00000000-2222-0000-0000-0000deadbeef'},
518 ("935b549714b8f007c6a4e29c758689cf9e8e69f2e0f51180506492974b90a972,registry.hub.docker.com/rkachach/ceph:custom-v0.5,666bbfa87e8df05702d6172cae11dd7bc48efb1d94f1b9e492952f19647199a4,2022-04-19 13:45:20.97146228 +0000 UTC,",
523 # get container info by name: no match
531 # get container info by type: no match
540 def test_get_container_info(self
, daemon_filter
, by_name
, daemon_list
, container_stats
, output
):
541 cd
.logger
= mock
.Mock()
542 ctx
= cd
.CephadmContext()
543 ctx
.fsid
= '00000000-0000-0000-0000-0000deadbeef'
544 ctx
.container_engine
= mock_podman()
545 with mock
.patch('cephadm.list_daemons', return_value
=daemon_list
):
546 with mock
.patch('cephadm.get_container_stats', return_value
=container_stats
):
547 assert cd
.get_container_info(ctx
, daemon_filter
, by_name
) == output
549 def test_should_log_to_journald(self
):
550 ctx
= cd
.CephadmContext()
552 ctx
.log_to_journald
= True
553 assert cd
.should_log_to_journald(ctx
)
555 ctx
.log_to_journald
= None
556 # enable if podman support --cgroup=split
557 ctx
.container_engine
= mock_podman()
558 ctx
.container_engine
.version
= (2, 1, 0)
559 assert cd
.should_log_to_journald(ctx
)
561 # disable on old podman
562 ctx
.container_engine
.version
= (2, 0, 0)
563 assert not cd
.should_log_to_journald(ctx
)
566 ctx
.container_engine
= mock_docker()
567 assert not cd
.should_log_to_journald(ctx
)
569 def test_normalize_image_digest(self
):
570 s
= 'myhostname:5000/ceph/ceph@sha256:753886ad9049004395ae990fbb9b096923b5a518b819283141ee8716ddf55ad1'
571 assert cd
.normalize_image_digest(s
) == s
573 s
= 'ceph/ceph:latest'
574 assert cd
.normalize_image_digest(s
) == f
'{cd.DEFAULT_REGISTRY}/{s}'
576 @pytest.mark
.parametrize('fsid, ceph_conf, list_daemons, result, err, ',
586 '00000000-0000-0000-0000-0000deadbeef',
589 '00000000-0000-0000-0000-0000deadbeef',
593 '00000000-0000-0000-0000-0000deadbeef',
596 {'fsid': '10000000-0000-0000-0000-0000deadbeef'},
597 {'fsid': '20000000-0000-0000-0000-0000deadbeef'},
599 '00000000-0000-0000-0000-0000deadbeef',
606 {'fsid': '00000000-0000-0000-0000-0000deadbeef'},
608 '00000000-0000-0000-0000-0000deadbeef',
615 {'fsid': '10000000-0000-0000-0000-0000deadbeef'},
616 {'fsid': '20000000-0000-0000-0000-0000deadbeef'},
619 r
'Cannot infer an fsid',
623 get_ceph_conf(fsid
='00000000-0000-0000-0000-0000deadbeef'),
625 '00000000-0000-0000-0000-0000deadbeef',
630 get_ceph_conf(fsid
='00000000-0000-0000-0000-0000deadbeef'),
632 {'fsid': '00000000-0000-0000-0000-0000deadbeef'},
634 '00000000-0000-0000-0000-0000deadbeef',
639 get_ceph_conf(fsid
='00000000-0000-0000-0000-0000deadbeef'),
641 {'fsid': '10000000-0000-0000-0000-0000deadbeef'},
642 {'fsid': '20000000-0000-0000-0000-0000deadbeef'},
645 r
'Cannot infer an fsid',
648 @mock.patch('cephadm.call')
649 def test_infer_fsid(self
, _call
, fsid
, ceph_conf
, list_daemons
, result
, err
, cephadm_fs
):
651 ctx
= cd
.CephadmContext()
655 mock_fn
= mock
.Mock()
656 mock_fn
.return_value
= 0
657 infer_fsid
= cd
.infer_fsid(mock_fn
)
659 # mock the ceph.conf file content
661 f
= cephadm_fs
.create_file('ceph.conf', contents
=ceph_conf
)
665 with mock
.patch('cephadm.list_daemons', return_value
=list_daemons
):
667 with pytest
.raises(cd
.Error
, match
=err
):
671 assert ctx
.fsid
== result
673 @pytest.mark
.parametrize('fsid, other_conf_files, config, name, list_daemons, result, ',
675 # per cluster conf has more precedence than default conf
677 '00000000-0000-0000-0000-0000deadbeef',
678 [cd
.CEPH_DEFAULT_CONF
],
682 '/var/lib/ceph/00000000-0000-0000-0000-0000deadbeef/config/ceph.conf',
684 # mon daemon conf has more precedence than cluster conf and default conf
686 '00000000-0000-0000-0000-0000deadbeef',
687 ['/var/lib/ceph/00000000-0000-0000-0000-0000deadbeef/config/ceph.conf',
688 cd
.CEPH_DEFAULT_CONF
],
691 [{'name': 'mon.a', 'fsid': '00000000-0000-0000-0000-0000deadbeef', 'style': 'cephadm:v1'}],
692 '/var/lib/ceph/00000000-0000-0000-0000-0000deadbeef/mon.a/config',
694 # daemon conf (--name option) has more precedence than cluster, default and mon conf
696 '00000000-0000-0000-0000-0000deadbeef',
697 ['/var/lib/ceph/00000000-0000-0000-0000-0000deadbeef/config/ceph.conf',
698 '/var/lib/ceph/00000000-0000-0000-0000-0000deadbeef/mon.a/config',
699 cd
.CEPH_DEFAULT_CONF
],
702 [{'name': 'mon.a', 'fsid': '00000000-0000-0000-0000-0000deadbeef', 'style': 'cephadm:v1'},
703 {'name': 'osd.0', 'fsid': '00000000-0000-0000-0000-0000deadbeef'}],
704 '/var/lib/ceph/00000000-0000-0000-0000-0000deadbeef/osd.0/config',
706 # user provided conf ('/foo/ceph.conf') more precedence than any other conf
708 '00000000-0000-0000-0000-0000deadbeef',
709 ['/var/lib/ceph/00000000-0000-0000-0000-0000deadbeef/config/ceph.conf',
710 cd
.CEPH_DEFAULT_CONF
,
711 '/var/lib/ceph/00000000-0000-0000-0000-0000deadbeef/mon.a/config'],
714 [{'name': 'mon.a', 'fsid': '00000000-0000-0000-0000-0000deadbeef', 'style': 'cephadm:v1'}],
718 @mock.patch('cephadm.call')
719 @mock.patch('cephadm.logger')
720 def test_infer_config_precedence(self
, logger
, _call
, other_conf_files
, fsid
, config
, name
, list_daemons
, result
, cephadm_fs
):
722 ctx
= cd
.CephadmContext()
728 mock_fn
= mock
.Mock()
729 mock_fn
.return_value
= 0
730 infer_config
= cd
.infer_config(mock_fn
)
732 # mock the config file
733 cephadm_fs
.create_file(result
)
735 # mock other potential config files
736 for f
in other_conf_files
:
737 cephadm_fs
.create_file(f
)
740 with mock
.patch('cephadm.list_daemons', return_value
=list_daemons
):
742 assert ctx
.config
== result
744 @pytest.mark
.parametrize('fsid, config, name, list_daemons, result, ',
754 '00000000-0000-0000-0000-0000deadbeef',
758 cd
.CEPH_DEFAULT_CONF
,
761 '00000000-0000-0000-0000-0000deadbeef',
765 '/var/lib/ceph/00000000-0000-0000-0000-0000deadbeef/config/ceph.conf',
768 '00000000-0000-0000-0000-0000deadbeef',
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',
775 '00000000-0000-0000-0000-0000deadbeef',
778 [{'name': 'mon.a', 'fsid': 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'style': 'cephadm:v1'}],
779 cd
.CEPH_DEFAULT_CONF
,
782 '00000000-0000-0000-0000-0000deadbeef',
785 [{'name': 'mon.a', 'fsid': '00000000-0000-0000-0000-0000deadbeef', 'style': 'legacy'}],
786 cd
.CEPH_DEFAULT_CONF
,
789 '00000000-0000-0000-0000-0000deadbeef',
793 cd
.CEPH_DEFAULT_CONF
,
796 '00000000-0000-0000-0000-0000deadbeef',
799 [{'name': 'mon.a', 'style': 'cephadm:v1'}],
803 '00000000-0000-0000-0000-0000deadbeef',
807 '/var/lib/ceph/00000000-0000-0000-0000-0000deadbeef/mon.a/config',
810 '00000000-0000-0000-0000-0000deadbeef',
814 '/var/lib/ceph/00000000-0000-0000-0000-0000deadbeef/osd.0/config',
821 cd
.CEPH_DEFAULT_CONF
,
824 @mock.patch('cephadm.call')
825 @mock.patch('cephadm.logger')
826 def test_infer_config(self
, logger
, _call
, fsid
, config
, name
, list_daemons
, result
, cephadm_fs
):
828 ctx
= cd
.CephadmContext()
834 mock_fn
= mock
.Mock()
835 mock_fn
.return_value
= 0
836 infer_config
= cd
.infer_config(mock_fn
)
838 # mock the config file
839 cephadm_fs
.create_file(result
)
842 with mock
.patch('cephadm.list_daemons', return_value
=list_daemons
):
844 assert ctx
.config
== result
846 @mock.patch('cephadm.call')
847 def test_extract_uid_gid_fail(self
, _call
):
848 err
= """Error: container_linux.go:370: starting container process caused: process_linux.go:459: container init caused: process_linux.go:422: setting cgroup config for procHooks process caused: Unit libpod-056038e1126191fba41d8a037275136f2d7aeec9710b9ee
849 ff792c06d8544b983.scope not found.: OCI runtime error"""
850 _call
.return_value
= ('', err
, 127)
851 ctx
= cd
.CephadmContext()
852 ctx
.container_engine
= mock_podman()
853 with pytest
.raises(cd
.Error
, match
='OCI'):
854 cd
.extract_uid_gid(ctx
)
856 @pytest.mark
.parametrize('test_input, expected', [
857 ([cd
.make_fsid(), cd
.make_fsid(), cd
.make_fsid()], 3),
858 ([cd
.make_fsid(), 'invalid-fsid', cd
.make_fsid(), '0b87e50c-8e77-11ec-b890-'], 2),
859 (['f6860ec2-8e76-11ec-', '0b87e50c-8e77-11ec-b890-', ''], 0),
862 def test_get_ceph_cluster_count(self
, test_input
, expected
):
863 ctx
= cd
.CephadmContext()
864 with mock
.patch('os.listdir', return_value
=test_input
):
865 assert cd
.get_ceph_cluster_count(ctx
) == expected
867 def test_set_image_minimize_config(self
):
869 raise cd
.Error(' '.join(cmd
))
870 ctx
= cd
.CephadmContext()
871 ctx
.image
= 'test_image'
872 ctx
.no_minimize_config
= True
873 fake_cli
= lambda cmd
, __
=None, ___
=None: throw_cmd(cmd
)
874 with pytest
.raises(cd
.Error
, match
='config set global container_image test_image'):
875 cd
.finish_bootstrap_config(
879 mon_id
='a', mon_dir
='mon_dir',
880 mon_network
=None, ipv6
=False,
882 cluster_network
=None,
883 ipv6_cluster_network
=False
887 class TestCustomContainer(unittest
.TestCase
):
888 cc
: cd
.CustomContainer
891 self
.cc
= cd
.CustomContainer(
892 'e863154d-33c7-4350-bca5-921e0467e55b',
895 'entrypoint': 'bash',
901 'envs': ['SECRET=password'],
902 'ports': [8080, 8443],
904 '/CONFIG_DIR': '/foo/conf',
905 'bar/config': '/bar:ro'
910 'source=/CONFIG_DIR',
911 'destination=/foo/conf',
917 'destination=/bar:ro',
922 image
='docker.io/library/hello-world:latest'
925 def test_entrypoint(self
):
926 self
.assertEqual(self
.cc
.entrypoint
, 'bash')
928 def test_uid_gid(self
):
929 self
.assertEqual(self
.cc
.uid
, 65534)
930 self
.assertEqual(self
.cc
.gid
, 1000)
932 def test_ports(self
):
933 self
.assertEqual(self
.cc
.ports
, [8080, 8443])
935 def test_get_container_args(self
):
936 result
= self
.cc
.get_container_args()
937 self
.assertEqual(result
, [
942 def test_get_container_envs(self
):
943 result
= self
.cc
.get_container_envs()
944 self
.assertEqual(result
, ['SECRET=password'])
946 def test_get_container_mounts(self
):
947 result
= self
.cc
.get_container_mounts('/xyz')
948 self
.assertDictEqual(result
, {
949 '/CONFIG_DIR': '/foo/conf',
950 '/xyz/bar/config': '/bar:ro'
953 def test_get_container_binds(self
):
954 result
= self
.cc
.get_container_binds('/xyz')
955 self
.assertEqual(result
, [
958 'source=/CONFIG_DIR',
959 'destination=/foo/conf',
964 'source=/xyz/bar/config',
965 'destination=/bar:ro',
971 class TestMaintenance
:
972 systemd_target
= "ceph.00000000-0000-0000-0000-000000c0ffee.target"
973 fsid
= '0ea8cdd0-1bbf-11ec-a9c7-5254002763fa'
975 def test_systemd_target_OK(self
, tmp_path
):
977 wants
= base
/ "ceph.target.wants"
979 target
= wants
/ TestMaintenance
.systemd_target
981 ctx
= cd
.CephadmContext()
982 ctx
.unit_dir
= str(base
)
984 assert cd
.systemd_target_state(ctx
, target
.name
)
986 def test_systemd_target_NOTOK(self
, tmp_path
):
988 ctx
= cd
.CephadmContext()
989 ctx
.unit_dir
= str(base
)
990 assert not cd
.systemd_target_state(ctx
, TestMaintenance
.systemd_target
)
992 def test_parser_OK(self
):
993 args
= cd
._parse
_args
(['host-maintenance', 'enter'])
994 assert args
.maintenance_action
== 'enter'
996 def test_parser_BAD(self
):
997 with pytest
.raises(SystemExit):
998 cd
._parse
_args
(['host-maintenance', 'wah'])
1000 @mock.patch('os.listdir', return_value
=[])
1001 @mock.patch('cephadm.call')
1002 @mock.patch('cephadm.systemd_target_state')
1003 def test_enter_failure_1(self
, _target_state
, _call
, _listdir
):
1004 _call
.return_value
= '', '', 999
1005 _target_state
.return_value
= True
1006 ctx
: cd
.CephadmContext
= cd
.cephadm_init_ctx(
1007 ['host-maintenance', 'enter', '--fsid', TestMaintenance
.fsid
])
1008 ctx
.container_engine
= mock_podman()
1009 retval
= cd
.command_maintenance(ctx
)
1010 assert retval
.startswith('failed')
1012 @mock.patch('os.listdir', return_value
=[])
1013 @mock.patch('cephadm.call')
1014 @mock.patch('cephadm.systemd_target_state')
1015 def test_enter_failure_2(self
, _target_state
, _call
, _listdir
):
1016 _call
.side_effect
= [('', '', 0), ('', '', 999), ('', '', 0), ('', '', 999)]
1017 _target_state
.return_value
= True
1018 ctx
: cd
.CephadmContext
= cd
.cephadm_init_ctx(
1019 ['host-maintenance', 'enter', '--fsid', TestMaintenance
.fsid
])
1020 ctx
.container_engine
= mock_podman()
1021 retval
= cd
.command_maintenance(ctx
)
1022 assert retval
.startswith('failed')
1024 @mock.patch('os.listdir', return_value
=[])
1025 @mock.patch('cephadm.call')
1026 @mock.patch('cephadm.systemd_target_state')
1027 @mock.patch('cephadm.target_exists')
1028 def test_exit_failure_1(self
, _target_exists
, _target_state
, _call
, _listdir
):
1029 _call
.return_value
= '', '', 999
1030 _target_state
.return_value
= False
1031 _target_exists
.return_value
= True
1032 ctx
: cd
.CephadmContext
= cd
.cephadm_init_ctx(
1033 ['host-maintenance', 'exit', '--fsid', TestMaintenance
.fsid
])
1034 ctx
.container_engine
= mock_podman()
1035 retval
= cd
.command_maintenance(ctx
)
1036 assert retval
.startswith('failed')
1038 @mock.patch('os.listdir', return_value
=[])
1039 @mock.patch('cephadm.call')
1040 @mock.patch('cephadm.systemd_target_state')
1041 @mock.patch('cephadm.target_exists')
1042 def test_exit_failure_2(self
, _target_exists
, _target_state
, _call
, _listdir
):
1043 _call
.side_effect
= [('', '', 0), ('', '', 999), ('', '', 0), ('', '', 999)]
1044 _target_state
.return_value
= False
1045 _target_exists
.return_value
= True
1046 ctx
: cd
.CephadmContext
= cd
.cephadm_init_ctx(
1047 ['host-maintenance', 'exit', '--fsid', TestMaintenance
.fsid
])
1048 ctx
.container_engine
= mock_podman()
1049 retval
= cd
.command_maintenance(ctx
)
1050 assert retval
.startswith('failed')
1053 class TestMonitoring(object):
1054 @mock.patch('cephadm.call')
1055 def test_get_version_alertmanager(self
, _call
):
1056 ctx
= cd
.CephadmContext()
1057 ctx
.container_engine
= mock_podman()
1058 daemon_type
= 'alertmanager'
1060 # binary `prometheus`
1061 _call
.return_value
= '', '{}, version 0.16.1'.format(daemon_type
), 0
1062 version
= cd
.Monitoring
.get_version(ctx
, 'container_id', daemon_type
)
1063 assert version
== '0.16.1'
1065 # binary `prometheus-alertmanager`
1066 _call
.side_effect
= (
1068 ('', '{}, version 0.16.1'.format(daemon_type
), 0),
1070 version
= cd
.Monitoring
.get_version(ctx
, 'container_id', daemon_type
)
1071 assert version
== '0.16.1'
1073 @mock.patch('cephadm.call')
1074 def test_get_version_prometheus(self
, _call
):
1075 ctx
= cd
.CephadmContext()
1076 ctx
.container_engine
= mock_podman()
1077 daemon_type
= 'prometheus'
1078 _call
.return_value
= '', '{}, version 0.16.1'.format(daemon_type
), 0
1079 version
= cd
.Monitoring
.get_version(ctx
, 'container_id', daemon_type
)
1080 assert version
== '0.16.1'
1082 def test_prometheus_external_url(self
):
1083 ctx
= cd
.CephadmContext()
1084 daemon_type
= 'prometheus'
1086 fsid
= 'aaf5a720-13fe-4a3b-82b9-2d99b7fd9704'
1087 args
= cd
.get_daemon_args(ctx
, fsid
, daemon_type
, daemon_id
)
1088 assert any([x
.startswith('--web.external-url=http://') for x
in args
])
1090 @mock.patch('cephadm.call')
1091 def test_get_version_node_exporter(self
, _call
):
1092 ctx
= cd
.CephadmContext()
1093 ctx
.container_engine
= mock_podman()
1094 daemon_type
= 'node-exporter'
1095 _call
.return_value
= '', '{}, version 0.16.1'.format(daemon_type
.replace('-', '_')), 0
1096 version
= cd
.Monitoring
.get_version(ctx
, 'container_id', daemon_type
)
1097 assert version
== '0.16.1'
1099 def test_create_daemon_dirs_prometheus(self
, cephadm_fs
):
1101 Ensures the required and optional files given in the configuration are
1102 created and mapped correctly inside the container. Tests absolute and
1103 relative file paths given in the configuration.
1106 fsid
= 'aaf5a720-13fe-4a3b-82b9-2d99b7fd9704'
1107 daemon_type
= 'prometheus'
1110 ctx
= cd
.CephadmContext()
1111 ctx
.data_dir
= '/somedir'
1112 ctx
.config_json
= json
.dumps({
1114 'prometheus.yml': 'foo',
1115 '/etc/prometheus/alerting/ceph_alerts.yml': 'bar'
1119 cd
.create_daemon_dirs(ctx
,
1128 prefix
= '{data_dir}/{fsid}/{daemon_type}.{daemon_id}'.format(
1129 data_dir
=ctx
.data_dir
,
1131 daemon_type
=daemon_type
,
1136 'etc/prometheus/prometheus.yml': 'foo',
1137 'etc/prometheus/alerting/ceph_alerts.yml': 'bar',
1140 for file,content
in expected
.items():
1141 file = os
.path
.join(prefix
, file)
1142 assert os
.path
.exists(file)
1143 with
open(file) as f
:
1144 assert f
.read() == content
1146 # assert uid/gid after redeploy
1149 cd
.create_daemon_dirs(ctx
,
1157 for file,content
in expected
.items():
1158 file = os
.path
.join(prefix
, file)
1159 assert os
.stat(file).st_uid
== new_uid
1160 assert os
.stat(file).st_gid
== new_gid
1163 class TestBootstrap(object):
1166 def _get_cmd(*args
):
1169 '--allow-mismatched-release',
1170 '--skip-prepare-host',
1176 ###############################################3
1178 def test_config(self
, cephadm_fs
):
1180 cmd
= self
._get
_cmd
(
1181 '--mon-ip', '192.168.1.1',
1182 '--skip-mon-network',
1183 '--config', conf_file
,
1186 with
with_cephadm_ctx(cmd
) as ctx
:
1187 msg
= r
'No such file or directory'
1188 with pytest
.raises(cd
.Error
, match
=msg
):
1189 cd
.command_bootstrap(ctx
)
1191 cephadm_fs
.create_file(conf_file
)
1192 with
with_cephadm_ctx(cmd
) as ctx
:
1193 retval
= cd
.command_bootstrap(ctx
)
1196 def test_no_mon_addr(self
, cephadm_fs
):
1197 cmd
= self
._get
_cmd
()
1198 with
with_cephadm_ctx(cmd
) as ctx
:
1199 msg
= r
'must specify --mon-ip or --mon-addrv'
1200 with pytest
.raises(cd
.Error
, match
=msg
):
1201 cd
.command_bootstrap(ctx
)
1203 def test_skip_mon_network(self
, cephadm_fs
):
1204 cmd
= self
._get
_cmd
('--mon-ip', '192.168.1.1')
1206 with
with_cephadm_ctx(cmd
, list_networks
={}) as ctx
:
1207 msg
= r
'--skip-mon-network'
1208 with pytest
.raises(cd
.Error
, match
=msg
):
1209 cd
.command_bootstrap(ctx
)
1211 cmd
+= ['--skip-mon-network']
1212 with
with_cephadm_ctx(cmd
, list_networks
={}) as ctx
:
1213 retval
= cd
.command_bootstrap(ctx
)
1216 @pytest.mark
.parametrize('mon_ip, list_networks, result',
1221 {'192.168.1.0/24': {'eth0': ['192.168.1.1']}},
1226 {'192.168.1.0/24': {'eth0': ['192.168.1.1']}},
1231 {'192.168.1.0/24': {'eth0': ['192.168.1.1']}},
1236 {'192.168.1.0/24': {'eth0': ['192.168.1.1']}},
1241 {'192.168.1.0/24': {'eth0': ['192.168.1.1']}},
1246 {'192.168.1.0/24': {'eth0': ['192.168.1.1']}},
1252 {'192.168.1.0/24': {'eth0': ['192.168.1.1']}},
1256 '::ffff:192.168.1.0',
1257 {"ffff::/64": {"eth0": ["::ffff:c0a8:101"]}},
1261 '::ffff:192.168.1.1',
1262 {"ffff::/64": {"eth0": ["::ffff:c0a8:101"]}},
1267 {"ffff::/64": {"eth0": ["::ffff:c0a8:101"]}},
1271 '[::ffff:c0a8:101]:1234',
1272 {"ffff::/64": {"eth0": ["::ffff:c0a8:101"]}},
1276 '[::ffff:c0a8:101]:0123',
1277 {"ffff::/64": {"eth0": ["::ffff:c0a8:101"]}},
1281 '0000:0000:0000:0000:0000:FFFF:C0A8:0101',
1282 {"ffff::/64": {"eth0": ["::ffff:c0a8:101"]}},
1286 def test_mon_ip(self
, mon_ip
, list_networks
, result
, cephadm_fs
):
1287 cmd
= self
._get
_cmd
('--mon-ip', mon_ip
)
1289 with
with_cephadm_ctx(cmd
, list_networks
=list_networks
) as ctx
:
1290 msg
= r
'--skip-mon-network'
1291 with pytest
.raises(cd
.Error
, match
=msg
):
1292 cd
.command_bootstrap(ctx
)
1294 with
with_cephadm_ctx(cmd
, list_networks
=list_networks
) as ctx
:
1295 retval
= cd
.command_bootstrap(ctx
)
1298 @pytest.mark
.parametrize('mon_addrv, list_networks, err',
1303 {'192.168.1.0/24': {'eth0': ['192.168.1.1']}},
1304 r
'must use square backets',
1308 {'192.168.1.0/24': {'eth0': ['192.168.1.1']}},
1309 r
'must include port number',
1312 '[192.168.1.1:1234]',
1313 {'192.168.1.0/24': {'eth0': ['192.168.1.1']}},
1317 '[192.168.1.1:0123]',
1318 {'192.168.1.0/24': {'eth0': ['192.168.1.1']}},
1322 '[v2:192.168.1.1:3300,v1:192.168.1.1:6789]',
1323 {'192.168.1.0/24': {'eth0': ['192.168.1.1']}},
1328 '[::ffff:192.168.1.1:1234]',
1329 {'ffff::/64': {'eth0': ['::ffff:c0a8:101']}},
1333 '[::ffff:192.168.1.1:0123]',
1334 {'ffff::/64': {'eth0': ['::ffff:c0a8:101']}},
1338 '[0000:0000:0000:0000:0000:FFFF:C0A8:0101:1234]',
1339 {'ffff::/64': {'eth0': ['::ffff:c0a8:101']}},
1343 '[v2:0000:0000:0000:0000:0000:FFFF:C0A8:0101:3300,v1:0000:0000:0000:0000:0000:FFFF:C0A8:0101:6789]',
1344 {'ffff::/64': {'eth0': ['::ffff:c0a8:101']}},
1348 def test_mon_addrv(self
, mon_addrv
, list_networks
, err
, cephadm_fs
):
1349 cmd
= self
._get
_cmd
('--mon-addrv', mon_addrv
)
1351 with
with_cephadm_ctx(cmd
, list_networks
=list_networks
) as ctx
:
1352 with pytest
.raises(cd
.Error
, match
=err
):
1353 cd
.command_bootstrap(ctx
)
1355 with
with_cephadm_ctx(cmd
, list_networks
=list_networks
) as ctx
:
1356 retval
= cd
.command_bootstrap(ctx
)
1359 def test_allow_fqdn_hostname(self
, cephadm_fs
):
1360 hostname
= 'foo.bar'
1361 cmd
= self
._get
_cmd
(
1362 '--mon-ip', '192.168.1.1',
1363 '--skip-mon-network',
1366 with
with_cephadm_ctx(cmd
, hostname
=hostname
) as ctx
:
1367 msg
= r
'--allow-fqdn-hostname'
1368 with pytest
.raises(cd
.Error
, match
=msg
):
1369 cd
.command_bootstrap(ctx
)
1371 cmd
+= ['--allow-fqdn-hostname']
1372 with
with_cephadm_ctx(cmd
, hostname
=hostname
) as ctx
:
1373 retval
= cd
.command_bootstrap(ctx
)
1376 @pytest.mark
.parametrize('fsid, err',
1379 ('00000000-0000-0000-0000-0000deadbeef', None),
1380 ('00000000-0000-0000-0000-0000deadbeez', 'not an fsid'),
1382 def test_fsid(self
, fsid
, err
, cephadm_fs
):
1383 cmd
= self
._get
_cmd
(
1384 '--mon-ip', '192.168.1.1',
1385 '--skip-mon-network',
1389 with
with_cephadm_ctx(cmd
) as ctx
:
1391 with pytest
.raises(cd
.Error
, match
=err
):
1392 cd
.command_bootstrap(ctx
)
1394 retval
= cd
.command_bootstrap(ctx
)
1398 class TestShell(object):
1400 def test_fsid(self
, cephadm_fs
):
1401 fsid
= '00000000-0000-0000-0000-0000deadbeef'
1403 cmd
= ['shell', '--fsid', fsid
]
1404 with
with_cephadm_ctx(cmd
) as ctx
:
1405 retval
= cd
.command_shell(ctx
)
1407 assert ctx
.fsid
== fsid
1409 cmd
= ['shell', '--fsid', '00000000-0000-0000-0000-0000deadbeez']
1410 with
with_cephadm_ctx(cmd
) as ctx
:
1412 with pytest
.raises(cd
.Error
, match
=err
):
1413 retval
= cd
.command_shell(ctx
)
1415 assert ctx
.fsid
== None
1417 s
= get_ceph_conf(fsid
=fsid
)
1418 f
= cephadm_fs
.create_file('ceph.conf', contents
=s
)
1420 cmd
= ['shell', '--fsid', fsid
, '--config', f
.path
]
1421 with
with_cephadm_ctx(cmd
) as ctx
:
1422 retval
= cd
.command_shell(ctx
)
1424 assert ctx
.fsid
== fsid
1426 cmd
= ['shell', '--fsid', '10000000-0000-0000-0000-0000deadbeef', '--config', f
.path
]
1427 with
with_cephadm_ctx(cmd
) as ctx
:
1428 err
= 'fsid does not match ceph.conf'
1429 with pytest
.raises(cd
.Error
, match
=err
):
1430 retval
= cd
.command_shell(ctx
)
1432 assert ctx
.fsid
== None
1434 def test_name(self
, cephadm_fs
):
1435 cmd
= ['shell', '--name', 'foo']
1436 with
with_cephadm_ctx(cmd
) as ctx
:
1437 retval
= cd
.command_shell(ctx
)
1440 cmd
= ['shell', '--name', 'foo.bar']
1441 with
with_cephadm_ctx(cmd
) as ctx
:
1442 err
= r
'must pass --fsid'
1443 with pytest
.raises(cd
.Error
, match
=err
):
1444 retval
= cd
.command_shell(ctx
)
1447 fsid
= '00000000-0000-0000-0000-0000deadbeef'
1448 cmd
= ['shell', '--name', 'foo.bar', '--fsid', fsid
]
1449 with
with_cephadm_ctx(cmd
) as ctx
:
1450 retval
= cd
.command_shell(ctx
)
1453 def test_config(self
, cephadm_fs
):
1455 with
with_cephadm_ctx(cmd
) as ctx
:
1456 retval
= cd
.command_shell(ctx
)
1458 assert ctx
.config
== None
1460 cephadm_fs
.create_file(cd
.CEPH_DEFAULT_CONF
)
1461 with
with_cephadm_ctx(cmd
) as ctx
:
1462 retval
= cd
.command_shell(ctx
)
1464 assert ctx
.config
== cd
.CEPH_DEFAULT_CONF
1466 cmd
= ['shell', '--config', 'foo']
1467 with
with_cephadm_ctx(cmd
) as ctx
:
1468 retval
= cd
.command_shell(ctx
)
1470 assert ctx
.config
== 'foo'
1472 def test_keyring(self
, cephadm_fs
):
1474 with
with_cephadm_ctx(cmd
) as ctx
:
1475 retval
= cd
.command_shell(ctx
)
1477 assert ctx
.keyring
== None
1479 cephadm_fs
.create_file(cd
.CEPH_DEFAULT_KEYRING
)
1480 with
with_cephadm_ctx(cmd
) as ctx
:
1481 retval
= cd
.command_shell(ctx
)
1483 assert ctx
.keyring
== cd
.CEPH_DEFAULT_KEYRING
1485 cmd
= ['shell', '--keyring', 'foo']
1486 with
with_cephadm_ctx(cmd
) as ctx
:
1487 retval
= cd
.command_shell(ctx
)
1489 assert ctx
.keyring
== 'foo'
1491 @mock.patch('cephadm.CephContainer')
1492 def test_mount_no_dst(self
, m_ceph_container
, cephadm_fs
):
1493 cmd
= ['shell', '--mount', '/etc/foo']
1494 with
with_cephadm_ctx(cmd
) as ctx
:
1495 retval
= cd
.command_shell(ctx
)
1497 assert m_ceph_container
.call_args
.kwargs
['volume_mounts']['/etc/foo'] == '/mnt/foo'
1499 @mock.patch('cephadm.CephContainer')
1500 def test_mount_with_dst_no_opt(self
, m_ceph_container
, cephadm_fs
):
1501 cmd
= ['shell', '--mount', '/etc/foo:/opt/foo/bar']
1502 with
with_cephadm_ctx(cmd
) as ctx
:
1503 retval
= cd
.command_shell(ctx
)
1505 assert m_ceph_container
.call_args
.kwargs
['volume_mounts']['/etc/foo'] == '/opt/foo/bar'
1507 @mock.patch('cephadm.CephContainer')
1508 def test_mount_with_dst_and_opt(self
, m_ceph_container
, cephadm_fs
):
1509 cmd
= ['shell', '--mount', '/etc/foo:/opt/foo/bar:Z']
1510 with
with_cephadm_ctx(cmd
) as ctx
:
1511 retval
= cd
.command_shell(ctx
)
1513 assert m_ceph_container
.call_args
.kwargs
['volume_mounts']['/etc/foo'] == '/opt/foo/bar:Z'
1515 class TestCephVolume(object):
1518 def _get_cmd(*args
):
1522 '--', 'inventory', '--format', 'json'
1525 def test_noop(self
, cephadm_fs
):
1526 cmd
= self
._get
_cmd
()
1527 with
with_cephadm_ctx(cmd
) as ctx
:
1528 cd
.command_ceph_volume(ctx
)
1529 assert ctx
.fsid
== None
1530 assert ctx
.config
== None
1531 assert ctx
.keyring
== None
1532 assert ctx
.config_json
== None
1534 def test_fsid(self
, cephadm_fs
):
1535 fsid
= '00000000-0000-0000-0000-0000deadbeef'
1537 cmd
= self
._get
_cmd
('--fsid', fsid
)
1538 with
with_cephadm_ctx(cmd
) as ctx
:
1539 cd
.command_ceph_volume(ctx
)
1540 assert ctx
.fsid
== fsid
1542 cmd
= self
._get
_cmd
('--fsid', '00000000-0000-0000-0000-0000deadbeez')
1543 with
with_cephadm_ctx(cmd
) as ctx
:
1545 with pytest
.raises(cd
.Error
, match
=err
):
1546 retval
= cd
.command_shell(ctx
)
1548 assert ctx
.fsid
== None
1550 s
= get_ceph_conf(fsid
=fsid
)
1551 f
= cephadm_fs
.create_file('ceph.conf', contents
=s
)
1553 cmd
= self
._get
_cmd
('--fsid', fsid
, '--config', f
.path
)
1554 with
with_cephadm_ctx(cmd
) as ctx
:
1555 cd
.command_ceph_volume(ctx
)
1556 assert ctx
.fsid
== fsid
1558 cmd
= self
._get
_cmd
('--fsid', '10000000-0000-0000-0000-0000deadbeef', '--config', f
.path
)
1559 with
with_cephadm_ctx(cmd
) as ctx
:
1560 err
= 'fsid does not match ceph.conf'
1561 with pytest
.raises(cd
.Error
, match
=err
):
1562 cd
.command_ceph_volume(ctx
)
1563 assert ctx
.fsid
== None
1565 def test_config(self
, cephadm_fs
):
1566 cmd
= self
._get
_cmd
('--config', 'foo')
1567 with
with_cephadm_ctx(cmd
) as ctx
:
1568 err
= r
'No such file or directory'
1569 with pytest
.raises(cd
.Error
, match
=err
):
1570 cd
.command_ceph_volume(ctx
)
1572 cephadm_fs
.create_file('bar')
1573 cmd
= self
._get
_cmd
('--config', 'bar')
1574 with
with_cephadm_ctx(cmd
) as ctx
:
1575 cd
.command_ceph_volume(ctx
)
1576 assert ctx
.config
== 'bar'
1578 def test_keyring(self
, cephadm_fs
):
1579 cmd
= self
._get
_cmd
('--keyring', 'foo')
1580 with
with_cephadm_ctx(cmd
) as ctx
:
1581 err
= r
'No such file or directory'
1582 with pytest
.raises(cd
.Error
, match
=err
):
1583 cd
.command_ceph_volume(ctx
)
1585 cephadm_fs
.create_file('bar')
1586 cmd
= self
._get
_cmd
('--keyring', 'bar')
1587 with
with_cephadm_ctx(cmd
) as ctx
:
1588 cd
.command_ceph_volume(ctx
)
1589 assert ctx
.keyring
== 'bar'
1593 def test_unit_run(self
, cephadm_fs
):
1594 fsid
= '9b9d7609-f4d5-4aba-94c8-effa764d96c9'
1596 'files': {'iscsi-gateway.cfg': ''}
1598 with
with_cephadm_ctx(['--image=ceph/ceph'], list_networks
={}) as ctx
:
1600 ctx
.config_json
= json
.dumps(config_json
)
1602 cd
.get_parm
.return_value
= config_json
1603 c
= cd
.get_container(ctx
, fsid
, 'iscsi', 'daemon_id')
1605 cd
.make_data_dir(ctx
, fsid
, 'iscsi', 'daemon_id')
1606 cd
.deploy_daemon_units(
1616 with
open('/var/lib/ceph/9b9d7609-f4d5-4aba-94c8-effa764d96c9/iscsi.daemon_id/unit.run') as f
:
1617 assert f
.read() == """set -e
1618 if ! grep -qs /var/lib/ceph/9b9d7609-f4d5-4aba-94c8-effa764d96c9/iscsi.daemon_id/configfs /proc/mounts; then mount -t configfs none /var/lib/ceph/9b9d7609-f4d5-4aba-94c8-effa764d96c9/iscsi.daemon_id/configfs; fi
1619 # iscsi tcmu-runner container
1620 ! /usr/bin/podman rm -f ceph-9b9d7609-f4d5-4aba-94c8-effa764d96c9-iscsi.daemon_id-tcmu 2> /dev/null
1621 ! /usr/bin/podman rm -f ceph-9b9d7609-f4d5-4aba-94c8-effa764d96c9-iscsi-daemon_id-tcmu 2> /dev/null
1622 /usr/bin/podman run --rm --ipc=host --stop-signal=SIGTERM --net=host --entrypoint /usr/bin/tcmu-runner --privileged --group-add=disk --init --name ceph-9b9d7609-f4d5-4aba-94c8-effa764d96c9-iscsi-daemon_id-tcmu -e CONTAINER_IMAGE=ceph/ceph -e NODE_NAME=host1 -e CEPH_USE_RANDOM_NONCE=1 -v /var/lib/ceph/9b9d7609-f4d5-4aba-94c8-effa764d96c9/iscsi.daemon_id/config:/etc/ceph/ceph.conf:z -v /var/lib/ceph/9b9d7609-f4d5-4aba-94c8-effa764d96c9/iscsi.daemon_id/keyring:/etc/ceph/keyring:z -v /var/lib/ceph/9b9d7609-f4d5-4aba-94c8-effa764d96c9/iscsi.daemon_id/iscsi-gateway.cfg:/etc/ceph/iscsi-gateway.cfg:z -v /var/lib/ceph/9b9d7609-f4d5-4aba-94c8-effa764d96c9/iscsi.daemon_id/configfs:/sys/kernel/config -v /var/log/ceph/9b9d7609-f4d5-4aba-94c8-effa764d96c9:/var/log:z -v /dev:/dev --mount type=bind,source=/lib/modules,destination=/lib/modules,ro=true ceph/ceph &
1624 ! /usr/bin/podman rm -f ceph-9b9d7609-f4d5-4aba-94c8-effa764d96c9-iscsi.daemon_id 2> /dev/null
1625 ! /usr/bin/podman rm -f ceph-9b9d7609-f4d5-4aba-94c8-effa764d96c9-iscsi-daemon_id 2> /dev/null
1626 /usr/bin/podman run --rm --ipc=host --stop-signal=SIGTERM --net=host --entrypoint /usr/bin/rbd-target-api --privileged --group-add=disk --init --name ceph-9b9d7609-f4d5-4aba-94c8-effa764d96c9-iscsi-daemon_id -e CONTAINER_IMAGE=ceph/ceph -e NODE_NAME=host1 -e CEPH_USE_RANDOM_NONCE=1 -v /var/lib/ceph/9b9d7609-f4d5-4aba-94c8-effa764d96c9/iscsi.daemon_id/config:/etc/ceph/ceph.conf:z -v /var/lib/ceph/9b9d7609-f4d5-4aba-94c8-effa764d96c9/iscsi.daemon_id/keyring:/etc/ceph/keyring:z -v /var/lib/ceph/9b9d7609-f4d5-4aba-94c8-effa764d96c9/iscsi.daemon_id/iscsi-gateway.cfg:/etc/ceph/iscsi-gateway.cfg:z -v /var/lib/ceph/9b9d7609-f4d5-4aba-94c8-effa764d96c9/iscsi.daemon_id/configfs:/sys/kernel/config -v /var/log/ceph/9b9d7609-f4d5-4aba-94c8-effa764d96c9:/var/log:z -v /dev:/dev --mount type=bind,source=/lib/modules,destination=/lib/modules,ro=true ceph/ceph
1629 def test_get_container(self
):
1631 Due to a combination of socket.getfqdn() and podman's behavior to
1632 add the container name into the /etc/hosts file, we cannot use periods
1633 in container names. But we need to be able to detect old existing containers.
1634 Assert this behaviour. I think we can remove this in Ceph R
1636 fsid
= '9b9d7609-f4d5-4aba-94c8-effa764d96c9'
1637 with
with_cephadm_ctx(['--image=ceph/ceph'], list_networks
={}) as ctx
:
1639 c
= cd
.get_container(ctx
, fsid
, 'iscsi', 'something')
1640 assert c
.cname
== 'ceph-9b9d7609-f4d5-4aba-94c8-effa764d96c9-iscsi-something'
1641 assert c
.old_cname
== 'ceph-9b9d7609-f4d5-4aba-94c8-effa764d96c9-iscsi.something'
1644 class TestCheckHost
:
1646 @mock.patch('cephadm.find_executable', return_value
='foo')
1647 @mock.patch('cephadm.check_time_sync', return_value
=True)
1648 def test_container_engine(self
, find_executable
, check_time_sync
):
1649 ctx
= cd
.CephadmContext()
1651 ctx
.container_engine
= None
1652 err
= r
'No container engine binary found'
1653 with pytest
.raises(cd
.Error
, match
=err
):
1654 cd
.command_check_host(ctx
)
1656 ctx
.container_engine
= mock_podman()
1657 cd
.command_check_host(ctx
)
1659 ctx
.container_engine
= mock_docker()
1660 cd
.command_check_host(ctx
)
1665 @pytest.mark
.parametrize('os_release',
1670 VERSION="20.04 LTS (Focal Fossa)"
1673 PRETTY_NAME="Ubuntu 20.04 LTS"
1675 HOME_URL="https://www.ubuntu.com/"
1676 SUPPORT_URL="https://help.ubuntu.com/"
1677 BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
1678 PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
1679 VERSION_CODENAME=focal
1680 UBUNTU_CODENAME=focal
1688 ID_LIKE="rhel fedora"
1690 PLATFORM_ID="platform:el8"
1691 PRETTY_NAME="CentOS Linux 8 (Core)"
1693 CPE_NAME="cpe:/o:centos:centos:8"
1694 HOME_URL="https://www.centos.org/"
1695 BUG_REPORT_URL="https://bugs.centos.org/"
1697 CENTOS_MANTISBT_PROJECT="CentOS-8"
1698 CENTOS_MANTISBT_PROJECT_VERSION="8"
1699 REDHAT_SUPPORT_PRODUCT="centos"
1700 REDHAT_SUPPORT_PRODUCT_VERSION="8"
1705 NAME="openSUSE Tumbleweed"
1706 # VERSION="20210810"
1707 ID="opensuse-tumbleweed"
1708 ID_LIKE="opensuse suse"
1709 VERSION_ID="20210810"
1710 PRETTY_NAME="openSUSE Tumbleweed"
1712 CPE_NAME="cpe:/o:opensuse:tumbleweed:20210810"
1713 BUG_REPORT_URL="https://bugs.opensuse.org"
1714 HOME_URL="https://www.opensuse.org/"
1715 DOCUMENTATION_URL="https://en.opensuse.org/Portal:Tumbleweed"
1716 LOGO="distributor-logo"
1719 @mock.patch('cephadm.find_executable', return_value
='foo')
1720 def test_container_engine(self
, find_executable
, os_release
, cephadm_fs
):
1721 cephadm_fs
.create_file('/etc/os-release', contents
=os_release
)
1722 ctx
= cd
.CephadmContext()
1724 ctx
.container_engine
= None
1725 cd
.command_rm_repo(ctx
)
1727 ctx
.container_engine
= mock_podman()
1728 cd
.command_rm_repo(ctx
)
1730 ctx
.container_engine
= mock_docker()
1731 cd
.command_rm_repo(ctx
)
1734 class TestValidateRepo
:
1736 @pytest.mark
.parametrize('values',
1743 os_release
=dedent("""
1745 VERSION="20.04 LTS (Focal Fossa)"
1748 PRETTY_NAME="Ubuntu 20.04 LTS"
1750 HOME_URL="https://www.ubuntu.com/"
1751 SUPPORT_URL="https://help.ubuntu.com/"
1752 BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
1753 PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
1754 VERSION_CODENAME=focal
1755 UBUNTU_CODENAME=focal
1758 # YumDnf on Centos8 - OK
1763 os_release
=dedent("""
1767 ID_LIKE="rhel fedora"
1769 PLATFORM_ID="platform:el8"
1770 PRETTY_NAME="CentOS Linux 8 (Core)"
1772 CPE_NAME="cpe:/o:centos:centos:8"
1773 HOME_URL="https://www.centos.org/"
1774 BUG_REPORT_URL="https://bugs.centos.org/"
1776 CENTOS_MANTISBT_PROJECT="CentOS-8"
1777 CENTOS_MANTISBT_PROJECT_VERSION="8"
1778 REDHAT_SUPPORT_PRODUCT="centos"
1779 REDHAT_SUPPORT_PRODUCT_VERSION="8"
1782 # YumDnf on Fedora - Fedora not supported
1786 err_text
="does not build Fedora",
1787 os_release
=dedent("""
1789 VERSION="35 (Cloud Edition)"
1793 PLATFORM_ID="platform:f35"
1794 PRETTY_NAME="Fedora Linux 35 (Cloud Edition)"
1795 ANSI_COLOR="0;38;2;60;110;180"
1796 LOGO=fedora-logo-icon
1797 CPE_NAME="cpe:/o:fedoraproject:fedora:35"
1798 HOME_URL="https://fedoraproject.org/"
1799 DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora/f35/system-administrators-guide/"
1800 SUPPORT_URL="https://ask.fedoraproject.org/"
1801 BUG_REPORT_URL="https://bugzilla.redhat.com/"
1802 REDHAT_BUGZILLA_PRODUCT="Fedora"
1803 REDHAT_BUGZILLA_PRODUCT_VERSION=35
1804 REDHAT_SUPPORT_PRODUCT="Fedora"
1805 REDHAT_SUPPORT_PRODUCT_VERSION=35
1806 PRIVACY_POLICY_URL="https://fedoraproject.org/wiki/Legal:PrivacyPolicy"
1807 VARIANT="Cloud Edition"
1811 # YumDnf on Centos 7 - no pacific
1815 err_text
="does not support pacific",
1816 os_release
=dedent("""
1820 ID_LIKE="rhel fedora"
1822 PRETTY_NAME="CentOS Linux 7 (Core)"
1824 CPE_NAME="cpe:/o:centos:centos:7"
1825 HOME_URL="https://www.centos.org/"
1826 BUG_REPORT_URL="https://bugs.centos.org/"
1828 CENTOS_MANTISBT_PROJECT="CentOS-7"
1829 CENTOS_MANTISBT_PROJECT_VERSION="7"
1830 REDHAT_SUPPORT_PRODUCT="centos"
1831 REDHAT_SUPPORT_PRODUCT_VERSION="7"
1834 # YumDnf on Centos 7 - nothing after pacific
1838 err_text
="does not support pacific",
1839 os_release
=dedent("""
1843 ID_LIKE="rhel fedora"
1845 PRETTY_NAME="CentOS Linux 7 (Core)"
1847 CPE_NAME="cpe:/o:centos:centos:7"
1848 HOME_URL="https://www.centos.org/"
1849 BUG_REPORT_URL="https://bugs.centos.org/"
1851 CENTOS_MANTISBT_PROJECT="CentOS-7"
1852 CENTOS_MANTISBT_PROJECT_VERSION="7"
1853 REDHAT_SUPPORT_PRODUCT="centos"
1854 REDHAT_SUPPORT_PRODUCT_VERSION="7"
1857 # YumDnf on Centos 7 - nothing v16 or higher
1861 err_text
="does not support",
1862 os_release
=dedent("""
1866 ID_LIKE="rhel fedora"
1868 PRETTY_NAME="CentOS Linux 7 (Core)"
1870 CPE_NAME="cpe:/o:centos:centos:7"
1871 HOME_URL="https://www.centos.org/"
1872 BUG_REPORT_URL="https://bugs.centos.org/"
1874 CENTOS_MANTISBT_PROJECT="CentOS-7"
1875 CENTOS_MANTISBT_PROJECT_VERSION="7"
1876 REDHAT_SUPPORT_PRODUCT="centos"
1877 REDHAT_SUPPORT_PRODUCT_VERSION="7"
1880 @mock.patch('cephadm.find_executable', return_value
='foo')
1881 def test_distro_validation(self
, find_executable
, values
, cephadm_fs
):
1882 os_release
= values
['os_release']
1883 release
= values
['release']
1884 version
= values
['version']
1885 err_text
= values
['err_text']
1887 cephadm_fs
.create_file('/etc/os-release', contents
=os_release
)
1888 ctx
= cd
.CephadmContext()
1889 ctx
.repo_url
= 'http://localhost'
1890 pkg
= cd
.create_packager(ctx
, stable
=release
, version
=version
)
1893 with pytest
.raises(cd
.Error
, match
=err_text
):
1896 with mock
.patch('cephadm.urlopen', return_value
=None):
1899 @pytest.mark
.parametrize('values',
1906 os_release
=dedent("""
1908 VERSION="20.04 LTS (Focal Fossa)"
1911 PRETTY_NAME="Ubuntu 20.04 LTS"
1913 HOME_URL="https://www.ubuntu.com/"
1914 SUPPORT_URL="https://help.ubuntu.com/"
1915 BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
1916 PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
1917 VERSION_CODENAME=focal
1918 UBUNTU_CODENAME=focal
1921 # YumDnf on Centos8 - force failure
1925 err_text
="failed to fetch repository metadata",
1926 os_release
=dedent("""
1930 ID_LIKE="rhel fedora"
1932 PLATFORM_ID="platform:el8"
1933 PRETTY_NAME="CentOS Linux 8 (Core)"
1935 CPE_NAME="cpe:/o:centos:centos:8"
1936 HOME_URL="https://www.centos.org/"
1937 BUG_REPORT_URL="https://bugs.centos.org/"
1939 CENTOS_MANTISBT_PROJECT="CentOS-8"
1940 CENTOS_MANTISBT_PROJECT_VERSION="8"
1941 REDHAT_SUPPORT_PRODUCT="centos"
1942 REDHAT_SUPPORT_PRODUCT_VERSION="8"
1945 @mock.patch('cephadm.find_executable', return_value
='foo')
1946 def test_http_validation(self
, find_executable
, values
, cephadm_fs
):
1947 from urllib
.error
import HTTPError
1949 os_release
= values
['os_release']
1950 release
= values
['release']
1951 version
= values
['version']
1952 err_text
= values
['err_text']
1954 cephadm_fs
.create_file('/etc/os-release', contents
=os_release
)
1955 ctx
= cd
.CephadmContext()
1956 ctx
.repo_url
= 'http://localhost'
1957 pkg
= cd
.create_packager(ctx
, stable
=release
, version
=version
)
1959 with mock
.patch('cephadm.urlopen') as _urlopen
:
1960 _urlopen
.side_effect
= HTTPError(ctx
.repo_url
, 404, "not found", None, fp
=None)
1962 with pytest
.raises(cd
.Error
, match
=err_text
):
1970 @mock.patch('time.sleep')
1971 @mock.patch('cephadm.call', return_value
=('', '', 0))
1972 @mock.patch('cephadm.get_image_info_from_inspect', return_value
={})
1973 def test_error(self
, get_image_info_from_inspect
, call
, sleep
):
1974 ctx
= cd
.CephadmContext()
1975 ctx
.container_engine
= mock_podman()
1976 ctx
.insecure
= False
1978 call
.return_value
= ('', '', 0)
1979 retval
= cd
.command_pull(ctx
)
1982 err
= 'maximum retries reached'
1984 call
.return_value
= ('', 'foobar', 1)
1985 with pytest
.raises(cd
.Error
) as e
:
1986 cd
.command_pull(ctx
)
1987 assert err
not in str(e
.value
)
1989 call
.return_value
= ('', 'net/http: TLS handshake timeout', 1)
1990 with pytest
.raises(cd
.Error
) as e
:
1991 cd
.command_pull(ctx
)
1992 assert err
in str(e
.value
)
1994 @mock.patch('cephadm.logger')
1995 @mock.patch('cephadm.get_image_info_from_inspect', return_value
={})
1996 @mock.patch('cephadm.infer_local_ceph_image', return_value
='last_local_ceph_image')
1997 def test_image(self
, infer_local_ceph_image
, get_image_info_from_inspect
, logger
):
1999 with
with_cephadm_ctx(cmd
) as ctx
:
2000 retval
= cd
.command_pull(ctx
)
2002 assert ctx
.image
== cd
.DEFAULT_IMAGE
2004 with mock
.patch
.dict(os
.environ
, {"CEPHADM_IMAGE": 'cephadm_image_environ'}):
2006 with
with_cephadm_ctx(cmd
) as ctx
:
2007 retval
= cd
.command_pull(ctx
)
2009 assert ctx
.image
== 'cephadm_image_environ'
2011 cmd
= ['--image', 'cephadm_image_param', 'pull']
2012 with
with_cephadm_ctx(cmd
) as ctx
:
2013 retval
= cd
.command_pull(ctx
)
2015 assert ctx
.image
== 'cephadm_image_param'
2018 class TestApplySpec
:
2020 def test_parse_yaml(self
, cephadm_fs
):
2024 addr: 192.168.122.44
2031 addr: 192.168.122.247
2037 addr: 192.168.122.165
2043 rgw_frontend_ssl_certificate: |
2044 -----BEGIN PRIVATE KEY-----
2045 V2VyIGRhcyBsaWVzdCBpc3QgZG9vZi4gTG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFt
2046 ZXQsIGNvbnNldGV0dXIgc2FkaXBzY2luZyBlbGl0ciwgc2VkIGRpYW0gbm9udW15
2047 IGVpcm1vZCB0ZW1wb3IgaW52aWR1bnQgdXQgbGFib3JlIGV0IGRvbG9yZSBtYWdu
2048 YSBhbGlxdXlhbSBlcmF0LCBzZWQgZGlhbSB2b2x1cHR1YS4gQXQgdmVybyBlb3Mg
2049 ZXQgYWNjdXNhbSBldCBqdXN0byBkdW8=
2050 -----END PRIVATE KEY-----
2051 -----BEGIN CERTIFICATE-----
2052 V2VyIGRhcyBsaWVzdCBpc3QgZG9vZi4gTG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFt
2053 ZXQsIGNvbnNldGV0dXIgc2FkaXBzY2luZyBlbGl0ciwgc2VkIGRpYW0gbm9udW15
2054 IGVpcm1vZCB0ZW1wb3IgaW52aWR1bnQgdXQgbGFib3JlIGV0IGRvbG9yZSBtYWdu
2055 YSBhbGlxdXlhbSBlcmF0LCBzZWQgZGlhbSB2b2x1cHR1YS4gQXQgdmVybyBlb3Mg
2056 ZXQgYWNjdXNhbSBldCBqdXN0byBkdW8=
2057 -----END CERTIFICATE-----
2062 cephadm_fs
.create_file('spec.yml', contents
=yaml
)
2063 retdic
= [{'service_type': 'host', 'hostname': 'vm-00', 'addr': '192.168.122.44', 'labels': '- example1- example2'},
2064 {'service_type': 'host', 'hostname': 'vm-01', 'addr': '192.168.122.247', 'labels': '- grafana'},
2065 {'service_type': 'host', 'hostname': 'vm-02', 'addr': '192.168.122.165'},
2066 {'service_id': 'myrgw',
2067 'service_type': 'rgw',
2069 'rgw_frontend_ssl_certificate: |-----BEGIN PRIVATE '
2070 'KEY-----V2VyIGRhcyBsaWVzdCBpc3QgZG9vZi4gTG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNldGV0dXIgc2FkaXBzY2luZyBlbGl0ciwgc2VkIGRpYW0gbm9udW15IGVpcm1vZCB0ZW1wb3IgaW52aWR1bnQgdXQgbGFib3JlIGV0IGRvbG9yZSBtYWduYSBhbGlxdXlhbSBlcmF0LCBzZWQgZGlhbSB2b2x1cHR1YS4gQXQgdmVybyBlb3MgZXQgYWNjdXNhbSBldCBqdXN0byBkdW8=-----END '
2071 'PRIVATE KEY----------BEGIN '
2072 'CERTIFICATE-----V2VyIGRhcyBsaWVzdCBpc3QgZG9vZi4gTG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNldGV0dXIgc2FkaXBzY2luZyBlbGl0ciwgc2VkIGRpYW0gbm9udW15IGVpcm1vZCB0ZW1wb3IgaW52aWR1bnQgdXQgbGFib3JlIGV0IGRvbG9yZSBtYWduYSBhbGlxdXlhbSBlcmF0LCBzZWQgZGlhbSB2b2x1cHR1YS4gQXQgdmVybyBlb3MgZXQgYWNjdXNhbSBldCBqdXN0byBkdW8=-----END '
2073 'CERTIFICATE-----ssl: true'}]
2075 with
open('spec.yml') as f
:
2076 dic
= cd
.parse_yaml_objs(f
)
2077 assert dic
== retdic
2079 @mock.patch('cephadm.call', return_value
=('', '', 0))
2080 def test_distribute_ssh_keys(self
, call
):
2081 ctx
= cd
.CephadmContext()
2082 ctx
.ssh_public_key
= None
2083 ctx
.ssh_user
= 'root'
2085 host_spec
= {'service_type': 'host', 'hostname': 'vm-02', 'addr': '192.168.122.165'}
2087 retval
= cd
._distribute
_ssh
_keys
(ctx
, host_spec
, 'bootstrap_hostname')
2091 call
.return_value
= ('', '', 1)
2093 retval
= cd
._distribute
_ssh
_keys
(ctx
, host_spec
, 'bootstrap_hostname')
2098 class TestSNMPGateway
:
2100 'snmp_community': 'public',
2101 'destination': '192.168.1.10:162',
2102 'snmp_version': 'V2c',
2104 V3_no_priv_config
= {
2105 'destination': '192.168.1.10:162',
2106 'snmp_version': 'V3',
2107 'snmp_v3_auth_username': 'myuser',
2108 'snmp_v3_auth_password': 'mypassword',
2109 'snmp_v3_auth_protocol': 'SHA',
2110 'snmp_v3_engine_id': '8000C53F00000000',
2113 'destination': '192.168.1.10:162',
2114 'snmp_version': 'V3',
2115 'snmp_v3_auth_username': 'myuser',
2116 'snmp_v3_auth_password': 'mypassword',
2117 'snmp_v3_auth_protocol': 'SHA',
2118 'snmp_v3_priv_protocol': 'DES',
2119 'snmp_v3_priv_password': 'mysecret',
2120 'snmp_v3_engine_id': '8000C53F00000000',
2122 no_destination_config
= {
2123 'snmp_version': 'V3',
2124 'snmp_v3_auth_username': 'myuser',
2125 'snmp_v3_auth_password': 'mypassword',
2126 'snmp_v3_auth_protocol': 'SHA',
2127 'snmp_v3_priv_protocol': 'DES',
2128 'snmp_v3_priv_password': 'mysecret',
2129 'snmp_v3_engine_id': '8000C53F00000000',
2131 bad_version_config
= {
2132 'snmp_community': 'public',
2133 'destination': '192.168.1.10:162',
2134 'snmp_version': 'V1',
2137 def test_unit_run_V2c(self
, cephadm_fs
):
2138 fsid
= 'ca734440-3dc6-11ec-9b98-5254002537a6'
2139 with
with_cephadm_ctx(['--image=docker.io/maxwo/snmp-notifier:v1.2.1'], list_networks
={}) as ctx
:
2141 ctx
.config_json
= json
.dumps(self
.V2c_config
)
2143 ctx
.tcp_ports
= '9464'
2144 cd
.get_parm
.return_value
= self
.V2c_config
2145 c
= cd
.get_container(ctx
, fsid
, 'snmp-gateway', 'daemon_id')
2147 cd
.make_data_dir(ctx
, fsid
, 'snmp-gateway', 'daemon_id')
2149 cd
.create_daemon_dirs(ctx
, fsid
, 'snmp-gateway', 'daemon_id', 0, 0)
2150 with
open(f
'/var/lib/ceph/{fsid}/snmp-gateway.daemon_id/snmp-gateway.conf', 'r') as f
:
2151 conf
= f
.read().rstrip()
2152 assert conf
== 'SNMP_NOTIFIER_COMMUNITY=public'
2154 cd
.deploy_daemon_units(
2163 with
open(f
'/var/lib/ceph/{fsid}/snmp-gateway.daemon_id/unit.run', 'r') as f
:
2164 run_cmd
= f
.readlines()[-1].rstrip()
2165 assert run_cmd
.endswith('docker.io/maxwo/snmp-notifier:v1.2.1 --web.listen-address=:9464 --snmp.destination=192.168.1.10:162 --snmp.version=V2c --log.level=info --snmp.trap-description-template=/etc/snmp_notifier/description-template.tpl')
2167 def test_unit_run_V3_noPriv(self
, cephadm_fs
):
2168 fsid
= 'ca734440-3dc6-11ec-9b98-5254002537a6'
2169 with
with_cephadm_ctx(['--image=docker.io/maxwo/snmp-notifier:v1.2.1'], list_networks
={}) as ctx
:
2171 ctx
.config_json
= json
.dumps(self
.V3_no_priv_config
)
2173 ctx
.tcp_ports
= '9465'
2174 cd
.get_parm
.return_value
= self
.V3_no_priv_config
2175 c
= cd
.get_container(ctx
, fsid
, 'snmp-gateway', 'daemon_id')
2177 cd
.make_data_dir(ctx
, fsid
, 'snmp-gateway', 'daemon_id')
2179 cd
.create_daemon_dirs(ctx
, fsid
, 'snmp-gateway', 'daemon_id', 0, 0)
2180 with
open(f
'/var/lib/ceph/{fsid}/snmp-gateway.daemon_id/snmp-gateway.conf', 'r') as f
:
2182 assert conf
== 'SNMP_NOTIFIER_AUTH_USERNAME=myuser\nSNMP_NOTIFIER_AUTH_PASSWORD=mypassword\n'
2184 cd
.deploy_daemon_units(
2193 with
open(f
'/var/lib/ceph/{fsid}/snmp-gateway.daemon_id/unit.run', 'r') as f
:
2194 run_cmd
= f
.readlines()[-1].rstrip()
2195 assert run_cmd
.endswith('docker.io/maxwo/snmp-notifier:v1.2.1 --web.listen-address=:9465 --snmp.destination=192.168.1.10:162 --snmp.version=V3 --log.level=info --snmp.trap-description-template=/etc/snmp_notifier/description-template.tpl --snmp.authentication-enabled --snmp.authentication-protocol=SHA --snmp.security-engine-id=8000C53F00000000')
2197 def test_unit_run_V3_Priv(self
, cephadm_fs
):
2198 fsid
= 'ca734440-3dc6-11ec-9b98-5254002537a6'
2199 with
with_cephadm_ctx(['--image=docker.io/maxwo/snmp-notifier:v1.2.1'], list_networks
={}) as ctx
:
2201 ctx
.config_json
= json
.dumps(self
.V3_priv_config
)
2203 ctx
.tcp_ports
= '9464'
2204 cd
.get_parm
.return_value
= self
.V3_priv_config
2205 c
= cd
.get_container(ctx
, fsid
, 'snmp-gateway', 'daemon_id')
2207 cd
.make_data_dir(ctx
, fsid
, 'snmp-gateway', 'daemon_id')
2209 cd
.create_daemon_dirs(ctx
, fsid
, 'snmp-gateway', 'daemon_id', 0, 0)
2210 with
open(f
'/var/lib/ceph/{fsid}/snmp-gateway.daemon_id/snmp-gateway.conf', 'r') as f
:
2212 assert conf
== 'SNMP_NOTIFIER_AUTH_USERNAME=myuser\nSNMP_NOTIFIER_AUTH_PASSWORD=mypassword\nSNMP_NOTIFIER_PRIV_PASSWORD=mysecret\n'
2214 cd
.deploy_daemon_units(
2223 with
open(f
'/var/lib/ceph/{fsid}/snmp-gateway.daemon_id/unit.run', 'r') as f
:
2224 run_cmd
= f
.readlines()[-1].rstrip()
2225 assert run_cmd
.endswith('docker.io/maxwo/snmp-notifier:v1.2.1 --web.listen-address=:9464 --snmp.destination=192.168.1.10:162 --snmp.version=V3 --log.level=info --snmp.trap-description-template=/etc/snmp_notifier/description-template.tpl --snmp.authentication-enabled --snmp.authentication-protocol=SHA --snmp.security-engine-id=8000C53F00000000 --snmp.private-enabled --snmp.private-protocol=DES')
2227 def test_unit_run_no_dest(self
, cephadm_fs
):
2228 fsid
= 'ca734440-3dc6-11ec-9b98-5254002537a6'
2229 with
with_cephadm_ctx(['--image=docker.io/maxwo/snmp-notifier:v1.2.1'], list_networks
={}) as ctx
:
2231 ctx
.config_json
= json
.dumps(self
.no_destination_config
)
2233 ctx
.tcp_ports
= '9464'
2234 cd
.get_parm
.return_value
= self
.no_destination_config
2236 with pytest
.raises(Exception) as e
:
2237 c
= cd
.get_container(ctx
, fsid
, 'snmp-gateway', 'daemon_id')
2238 assert str(e
.value
) == "config is missing destination attribute(<ip>:<port>) of the target SNMP listener"
2240 def test_unit_run_bad_version(self
, cephadm_fs
):
2241 fsid
= 'ca734440-3dc6-11ec-9b98-5254002537a6'
2242 with
with_cephadm_ctx(['--image=docker.io/maxwo/snmp-notifier:v1.2.1'], list_networks
={}) as ctx
:
2244 ctx
.config_json
= json
.dumps(self
.bad_version_config
)
2246 ctx
.tcp_ports
= '9464'
2247 cd
.get_parm
.return_value
= self
.bad_version_config
2249 with pytest
.raises(Exception) as e
:
2250 c
= cd
.get_container(ctx
, fsid
, 'snmp-gateway', 'daemon_id')
2251 assert str(e
.value
) == 'not a valid snmp version: V1'
2253 class TestNetworkValidation
:
2255 def test_ipv4_subnet(self
):
2256 rc
, v
, msg
= cd
.check_subnet('192.168.1.0/24')
2257 assert rc
== 0 and v
[0] == 4
2259 def test_ipv4_subnet_list(self
):
2260 rc
, v
, msg
= cd
.check_subnet('192.168.1.0/24,10.90.90.0/24')
2261 assert rc
== 0 and not msg
2263 def test_ipv4_subnet_list_with_spaces(self
):
2264 rc
, v
, msg
= cd
.check_subnet('192.168.1.0/24, 10.90.90.0/24 ')
2265 assert rc
== 0 and not msg
2267 def test_ipv4_subnet_badlist(self
):
2268 rc
, v
, msg
= cd
.check_subnet('192.168.1.0/24,192.168.1.1')
2269 assert rc
== 1 and msg
2271 def test_ipv4_subnet_mixed(self
):
2272 rc
, v
, msg
= cd
.check_subnet('192.168.100.0/24,fe80::/64')
2273 assert rc
== 0 and v
== [4,6]
2275 def test_ipv6_subnet(self
):
2276 rc
, v
, msg
= cd
.check_subnet('fe80::/64')
2277 assert rc
== 0 and v
[0] == 6
2279 def test_subnet_mask_missing(self
):
2280 rc
, v
, msg
= cd
.check_subnet('192.168.1.58')
2281 assert rc
== 1 and msg
2283 def test_subnet_mask_junk(self
):
2284 rc
, v
, msg
= cd
.check_subnet('wah')
2285 assert rc
== 1 and msg
2287 def test_ip_in_subnet(self
):
2288 # valid ip and only one valid subnet
2289 rc
= cd
.ip_in_subnets('192.168.100.1', '192.168.100.0/24')
2292 # valid ip and valid subnets list without spaces
2293 rc
= cd
.ip_in_subnets('192.168.100.1', '192.168.100.0/24,10.90.90.0/24')
2296 # valid ip and valid subnets list with spaces
2297 rc
= cd
.ip_in_subnets('10.90.90.2', '192.168.1.0/24, 192.168.100.0/24, 10.90.90.0/24')
2300 # valid ip that doesn't belong to any subnet
2301 rc
= cd
.ip_in_subnets('192.168.100.2', '192.168.50.0/24, 10.90.90.0/24')
2304 # valid ip that doesn't belong to the subnet (only 14 hosts)
2305 rc
= cd
.ip_in_subnets('192.168.100.20', '192.168.100.0/28')
2308 # valid ip and valid IPV6 network
2309 rc
= cd
.ip_in_subnets('fe80::5054:ff:fef4:873a', 'fe80::/64')
2312 # valid wrapped ip and valid IPV6 network
2313 rc
= cd
.ip_in_subnets('[fe80::5054:ff:fef4:873a]', 'fe80::/64')
2316 # valid ip and that doesn't belong to IPV6 network
2317 rc
= cd
.ip_in_subnets('fe80::5054:ff:fef4:873a', '2001:db8:85a3::/64')
2320 # invalid IPv4 and valid subnets list
2321 with pytest
.raises(Exception):
2322 rc
= cd
.ip_in_sublets('10.90.200.', '192.168.1.0/24, 192.168.100.0/24, 10.90.90.0/24')
2324 # invalid IPv6 and valid subnets list
2325 with pytest
.raises(Exception):
2326 rc
= cd
.ip_in_sublets('fe80:2030:31:24', 'fe80::/64')