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