]> git.proxmox.com Git - ceph.git/blob - ceph/src/cephadm/tests/test_cephadm.py
import ceph 15.2.14
[ceph.git] / ceph / src / cephadm / tests / test_cephadm.py
1 # type: ignore
2 import mock
3 from mock import patch, call
4 import unittest
5 import errno
6 import socket
7
8 import pytest
9
10 with patch('builtins.open', create=True):
11 from importlib.machinery import SourceFileLoader
12 cd = SourceFileLoader('cephadm', 'cephadm').load_module()
13
14 class TestCephAdm(object):
15
16 def test_docker_unit_file(self):
17 cd.args = mock.Mock()
18 cd.container_path = '/usr/bin/docker'
19 r = cd.get_unit_file('9b9d7609-f4d5-4aba-94c8-effa764d96c9')
20 assert 'Requires=docker.service' in r
21 cd.container_path = '/usr/sbin/podman'
22 r = cd.get_unit_file('9b9d7609-f4d5-4aba-94c8-effa764d96c9')
23 assert 'Requires=docker.service' not in r
24
25 def test_attempt_bind(self):
26 cd.logger = mock.Mock()
27 address = None
28 port = 0
29
30 def os_error(errno):
31 _os_error = OSError()
32 _os_error.errno = errno
33 return _os_error
34
35 for side_effect, expected_exception in (
36 (os_error(errno.EADDRINUSE), cd.PortOccupiedError),
37 (os_error(errno.EAFNOSUPPORT), OSError),
38 (os_error(errno.EADDRNOTAVAIL), OSError),
39 (None, None),
40 ):
41 _socket = mock.Mock()
42 _socket.bind.side_effect = side_effect
43 try:
44 cd.attempt_bind(_socket, address, port)
45 except Exception as e:
46 assert isinstance(e, expected_exception)
47 else:
48 if expected_exception is not None:
49 assert False, '{} should not be None'.format(expected_exception)
50
51 @mock.patch('cephadm.attempt_bind')
52 def test_port_in_use(self, attempt_bind):
53
54 assert cd.port_in_use(9100) == False
55
56 attempt_bind.side_effect = cd.PortOccupiedError('msg')
57 assert cd.port_in_use(9100) == True
58
59 os_error = OSError()
60 os_error.errno = errno.EADDRNOTAVAIL
61 attempt_bind.side_effect = os_error
62 assert cd.port_in_use(9100) == False
63
64 os_error = OSError()
65 os_error.errno = errno.EAFNOSUPPORT
66 attempt_bind.side_effect = os_error
67 assert cd.port_in_use(9100) == False
68
69 @mock.patch('socket.socket')
70 @mock.patch('cephadm.args')
71 def test_check_ip_port_success(self, args, _socket):
72 args.skip_ping_check = False
73
74 for address, address_family in (
75 ('0.0.0.0', socket.AF_INET),
76 ('::', socket.AF_INET6),
77 ):
78 try:
79 cd.check_ip_port(address, 9100)
80 except:
81 assert False
82 else:
83 assert _socket.call_args == call(address_family, socket.SOCK_STREAM)
84
85 @mock.patch('socket.socket')
86 @mock.patch('cephadm.args')
87 def test_check_ip_port_failure(self, args, _socket):
88 args.skip_ping_check = False
89
90 def os_error(errno):
91 _os_error = OSError()
92 _os_error.errno = errno
93 return _os_error
94
95 for address, address_family in (
96 ('0.0.0.0', socket.AF_INET),
97 ('::', socket.AF_INET6),
98 ):
99 for side_effect, expected_exception in (
100 (os_error(errno.EADDRINUSE), cd.PortOccupiedError),
101 (os_error(errno.EADDRNOTAVAIL), OSError),
102 (os_error(errno.EAFNOSUPPORT), OSError),
103 (None, None),
104 ):
105 mock_socket_obj = mock.Mock()
106 mock_socket_obj.bind.side_effect = side_effect
107 _socket.return_value = mock_socket_obj
108 try:
109 cd.check_ip_port(address, 9100)
110 except Exception as e:
111 assert isinstance(e, expected_exception)
112 else:
113 assert side_effect is None
114
115
116 def test_is_not_fsid(self):
117 assert not cd.is_fsid('no-uuid')
118
119 def test_is_fsid(self):
120 assert cd.is_fsid('e863154d-33c7-4350-bca5-921e0467e55b')
121
122 def test__get_parser_image(self):
123 args = cd._parse_args(['--image', 'foo', 'version'])
124 assert args.image == 'foo'
125
126 def test_CustomValidation(self):
127 assert cd._parse_args(['deploy', '--name', 'mon.a', '--fsid', 'fsid'])
128
129 with pytest.raises(SystemExit):
130 cd._parse_args(['deploy', '--name', 'wrong', '--fsid', 'fsid'])
131
132 @pytest.mark.parametrize("test_input, expected", [
133 ("podman version 1.6.2", (1,6,2)),
134 ("podman version 1.6.2-stable2", (1,6,2)),
135 ])
136 def test_parse_podman_version(self, test_input, expected):
137 assert cd._parse_podman_version(test_input) == expected
138
139 def test_parse_podman_version_invalid(self):
140 with pytest.raises(ValueError) as res:
141 cd._parse_podman_version('podman version inval.id')
142 assert 'inval' in str(res.value)
143
144 @pytest.mark.parametrize("test_input, expected", [
145 (
146 """
147 default via 192.168.178.1 dev enxd89ef3f34260 proto dhcp metric 100
148 10.0.0.0/8 via 10.4.0.1 dev tun0 proto static metric 50
149 10.3.0.0/21 via 10.4.0.1 dev tun0 proto static metric 50
150 10.4.0.1 dev tun0 proto kernel scope link src 10.4.0.2 metric 50
151 137.1.0.0/16 via 10.4.0.1 dev tun0 proto static metric 50
152 138.1.0.0/16 via 10.4.0.1 dev tun0 proto static metric 50
153 139.1.0.0/16 via 10.4.0.1 dev tun0 proto static metric 50
154 140.1.0.0/17 via 10.4.0.1 dev tun0 proto static metric 50
155 141.1.0.0/16 via 10.4.0.1 dev tun0 proto static metric 50
156 169.254.0.0/16 dev docker0 scope link metric 1000
157 172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
158 192.168.39.0/24 dev virbr1 proto kernel scope link src 192.168.39.1 linkdown
159 192.168.122.0/24 dev virbr0 proto kernel scope link src 192.168.122.1 linkdown
160 192.168.178.0/24 dev enxd89ef3f34260 proto kernel scope link src 192.168.178.28 metric 100
161 192.168.178.1 dev enxd89ef3f34260 proto static scope link metric 100
162 195.135.221.12 via 192.168.178.1 dev enxd89ef3f34260 proto static metric 100
163 """,
164 {
165 '10.4.0.1': ['10.4.0.2'],
166 '172.17.0.0/16': ['172.17.0.1'],
167 '192.168.39.0/24': ['192.168.39.1'],
168 '192.168.122.0/24': ['192.168.122.1'],
169 '192.168.178.0/24': ['192.168.178.28']
170 }
171 ), (
172 """
173 default via 10.3.64.1 dev eno1 proto static metric 100
174 10.3.64.0/24 dev eno1 proto kernel scope link src 10.3.64.23 metric 100
175 10.3.64.0/24 dev eno1 proto kernel scope link src 10.3.64.27 metric 100
176 10.88.0.0/16 dev cni-podman0 proto kernel scope link src 10.88.0.1 linkdown
177 172.21.0.0/20 via 172.21.3.189 dev tun0
178 172.21.1.0/20 via 172.21.3.189 dev tun0
179 172.21.2.1 via 172.21.3.189 dev tun0
180 172.21.3.1 dev tun0 proto kernel scope link src 172.21.3.2
181 172.21.4.0/24 via 172.21.3.1 dev tun0
182 172.21.5.0/24 via 172.21.3.1 dev tun0
183 172.21.6.0/24 via 172.21.3.1 dev tun0
184 172.21.7.0/24 via 172.21.3.1 dev tun0
185 192.168.122.0/24 dev virbr0 proto kernel scope link src 192.168.122.1 linkdown
186 """,
187 {
188 '10.3.64.0/24': ['10.3.64.23', '10.3.64.27'],
189 '10.88.0.0/16': ['10.88.0.1'],
190 '172.21.3.1': ['172.21.3.2'],
191 '192.168.122.0/24': ['192.168.122.1']}
192 ),
193 ])
194 def test_parse_ipv4_route(self, test_input, expected):
195 assert cd._parse_ipv4_route(test_input) == expected
196
197 @pytest.mark.parametrize("test_routes, test_ips, expected", [
198 (
199 """
200 ::1 dev lo proto kernel metric 256 pref medium
201 fdbc:7574:21fe:9200::/64 dev wlp2s0 proto ra metric 600 pref medium
202 fdd8:591e:4969:6363::/64 dev wlp2s0 proto ra metric 600 pref medium
203 fde4:8dba:82e1::/64 dev eth1 proto kernel metric 256 expires 1844sec pref medium
204 fe80::/64 dev tun0 proto kernel metric 256 pref medium
205 fe80::/64 dev wlp2s0 proto kernel metric 600 pref medium
206 default dev tun0 proto static metric 50 pref medium
207 default via fe80::2480:28ec:5097:3fe2 dev wlp2s0 proto ra metric 20600 pref medium
208 """,
209 """
210 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 state UNKNOWN qlen 1000
211 inet6 ::1/128 scope host
212 valid_lft forever preferred_lft forever
213 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
214 inet6 fdd8:591e:4969:6363:4c52:cafe:8dd4:dc4/64 scope global temporary dynamic
215 valid_lft 86394sec preferred_lft 14394sec
216 inet6 fdbc:7574:21fe:9200:4c52:cafe:8dd4:dc4/64 scope global temporary dynamic
217 valid_lft 6745sec preferred_lft 3145sec
218 inet6 fdd8:591e:4969:6363:103a:abcd:af1f:57f3/64 scope global temporary deprecated dynamic
219 valid_lft 86394sec preferred_lft 0sec
220 inet6 fdbc:7574:21fe:9200:103a:abcd:af1f:57f3/64 scope global temporary deprecated dynamic
221 valid_lft 6745sec preferred_lft 0sec
222 inet6 fdd8:591e:4969:6363:a128:1234:2bdd:1b6f/64 scope global temporary deprecated dynamic
223 valid_lft 86394sec preferred_lft 0sec
224 inet6 fdbc:7574:21fe:9200:a128:1234:2bdd:1b6f/64 scope global temporary deprecated dynamic
225 valid_lft 6745sec preferred_lft 0sec
226 inet6 fdd8:591e:4969:6363:d581:4321:380b:3905/64 scope global temporary deprecated dynamic
227 valid_lft 86394sec preferred_lft 0sec
228 inet6 fdbc:7574:21fe:9200:d581:4321:380b:3905/64 scope global temporary deprecated dynamic
229 valid_lft 6745sec preferred_lft 0sec
230 inet6 fe80::1111:2222:3333:4444/64 scope link noprefixroute
231 valid_lft forever preferred_lft forever
232 inet6 fde4:8dba:82e1:0:ec4a:e402:e9df:b357/64 scope global temporary dynamic
233 valid_lft 1074sec preferred_lft 1074sec
234 inet6 fde4:8dba:82e1:0:5054:ff:fe72:61af/64 scope global dynamic mngtmpaddr
235 valid_lft 1074sec preferred_lft 1074sec
236 12: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 state UNKNOWN qlen 100
237 inet6 fe80::cafe:cafe:cafe:cafe/64 scope link stable-privacy
238 valid_lft forever preferred_lft forever
239 """,
240 {
241 "::1": ["::1"],
242 "fdbc:7574:21fe:9200::/64": ["fdbc:7574:21fe:9200:4c52:cafe:8dd4:dc4",
243 "fdbc:7574:21fe:9200:103a:abcd:af1f:57f3",
244 "fdbc:7574:21fe:9200:a128:1234:2bdd:1b6f",
245 "fdbc:7574:21fe:9200:d581:4321:380b:3905"],
246 "fdd8:591e:4969:6363::/64": ["fdd8:591e:4969:6363:4c52:cafe:8dd4:dc4",
247 "fdd8:591e:4969:6363:103a:abcd:af1f:57f3",
248 "fdd8:591e:4969:6363:a128:1234:2bdd:1b6f",
249 "fdd8:591e:4969:6363:d581:4321:380b:3905"],
250 "fde4:8dba:82e1::/64": ["fde4:8dba:82e1:0:ec4a:e402:e9df:b357",
251 "fde4:8dba:82e1:0:5054:ff:fe72:61af"],
252 "fe80::/64": ["fe80::1111:2222:3333:4444",
253 "fe80::cafe:cafe:cafe:cafe"]
254 }
255 )])
256 def test_parse_ipv6_route(self, test_routes, test_ips, expected):
257 assert cd._parse_ipv6_route(test_routes, test_ips) == expected
258
259 def test_is_ipv6(self):
260 cd.logger = mock.Mock()
261 for good in ("[::1]", "::1",
262 "fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"):
263 assert cd.is_ipv6(good)
264 for bad in ("127.0.0.1",
265 "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffg",
266 "1:2:3:4:5:6:7:8:9", "fd00::1::1", "[fg::1]"):
267 assert not cd.is_ipv6(bad)
268
269 def test_unwrap_ipv6(self):
270 def unwrap_test(address, expected):
271 assert cd.unwrap_ipv6(address) == expected
272
273 tests = [
274 ('::1', '::1'), ('[::1]', '::1'),
275 ('[fde4:8dba:82e1:0:5054:ff:fe6a:357]', 'fde4:8dba:82e1:0:5054:ff:fe6a:357'),
276 ('can actually be any string', 'can actually be any string'),
277 ('[but needs to be stripped] ', '[but needs to be stripped] ')]
278 for address, expected in tests:
279 unwrap_test(address, expected)
280
281 def test_wrap_ipv6(self):
282 def wrap_test(address, expected):
283 assert cd.wrap_ipv6(address) == expected
284
285 tests = [
286 ('::1', '[::1]'), ('[::1]', '[::1]'),
287 ('fde4:8dba:82e1:0:5054:ff:fe6a:357',
288 '[fde4:8dba:82e1:0:5054:ff:fe6a:357]'),
289 ('myhost.example.com', 'myhost.example.com'),
290 ('192.168.0.1', '192.168.0.1'),
291 ('', ''), ('fd00::1::1', 'fd00::1::1')]
292 for address, expected in tests:
293 wrap_test(address, expected)
294
295 @mock.patch('cephadm.call_throws')
296 @mock.patch('cephadm.get_parm')
297 def test_registry_login(self, get_parm, call_throws):
298
299 # test normal valid login with url, username and password specified
300 call_throws.return_value = '', '', 0
301 args = cd._parse_args(['registry-login', '--registry-url', 'sample-url', '--registry-username', 'sample-user', '--registry-password', 'sample-pass'])
302 cd.args = args
303 retval = cd.command_registry_login()
304 assert retval == 0
305
306 # test bad login attempt with invalid arguments given
307 args = cd._parse_args(['registry-login', '--registry-url', 'bad-args-url'])
308 cd.args = args
309 with pytest.raises(Exception) as e:
310 assert cd.command_registry_login()
311 assert str(e.value) == ('Invalid custom registry arguments received. To login to a custom registry include '
312 '--registry-url, --registry-username and --registry-password options or --registry-json option')
313
314 # test normal valid login with json file
315 get_parm.return_value = {"url": "sample-url", "username": "sample-username", "password": "sample-password"}
316 args = cd._parse_args(['registry-login', '--registry-json', 'sample-json'])
317 cd.args = args
318 retval = cd.command_registry_login()
319 assert retval == 0
320
321 # test bad login attempt with bad json file
322 get_parm.return_value = {"bad-json": "bad-json"}
323 args = cd._parse_args(['registry-login', '--registry-json', 'sample-json'])
324 cd.args = args
325 with pytest.raises(Exception) as e:
326 assert cd.command_registry_login()
327 assert str(e.value) == ("json provided for custom registry login did not include all necessary fields. "
328 "Please setup json file as\n"
329 "{\n"
330 " \"url\": \"REGISTRY_URL\",\n"
331 " \"username\": \"REGISTRY_USERNAME\",\n"
332 " \"password\": \"REGISTRY_PASSWORD\"\n"
333 "}\n")
334
335 # test login attempt with valid arguments where login command fails
336 call_throws.side_effect = Exception
337 args = cd._parse_args(['registry-login', '--registry-url', 'sample-url', '--registry-username', 'sample-user', '--registry-password', 'sample-pass'])
338 cd.args = args
339 with pytest.raises(Exception) as e:
340 cd.command_registry_login()
341 assert str(e.value) == "Failed to login to custom registry @ sample-url as sample-user with given password"
342
343 def test_get_image_info_from_inspect(self):
344 # podman
345 out = """204a01f9b0b6710dd0c0af7f37ce7139c47ff0f0105d778d7104c69282dfbbf1,[docker.io/ceph/ceph@sha256:1cc9b824e1b076cdff52a9aa3f0cc8557d879fb2fbbba0cafed970aca59a3992]"""
346 r = cd.get_image_info_from_inspect(out, 'registry/ceph/ceph:latest')
347 assert r == {
348 'image_id': '204a01f9b0b6710dd0c0af7f37ce7139c47ff0f0105d778d7104c69282dfbbf1',
349 'repo_digest': 'docker.io/ceph/ceph@sha256:1cc9b824e1b076cdff52a9aa3f0cc8557d879fb2fbbba0cafed970aca59a3992'
350 }
351
352 # docker
353 out = """sha256:16f4549cf7a8f112bbebf7946749e961fbbd1b0838627fe619aab16bc17ce552,[quay.ceph.io/ceph-ci/ceph@sha256:4e13da36c1bd6780b312a985410ae678984c37e6a9493a74c87e4a50b9bda41f]"""
354 r = cd.get_image_info_from_inspect(out, 'registry/ceph/ceph:latest')
355 assert r == {
356 'image_id': '16f4549cf7a8f112bbebf7946749e961fbbd1b0838627fe619aab16bc17ce552',
357 'repo_digest': 'quay.ceph.io/ceph-ci/ceph@sha256:4e13da36c1bd6780b312a985410ae678984c37e6a9493a74c87e4a50b9bda41f'
358 }
359
360 def test_dict_get(self):
361 result = cd.dict_get({'a': 1}, 'a', require=True)
362 assert result == 1
363 result = cd.dict_get({'a': 1}, 'b')
364 assert result is None
365 result = cd.dict_get({'a': 1}, 'b', default=2)
366 assert result == 2
367
368 def test_dict_get_error(self):
369 with pytest.raises(cd.Error):
370 cd.dict_get({'a': 1}, 'b', require=True)
371
372 def test_dict_get_join(self):
373 result = cd.dict_get_join({'foo': ['a', 'b']}, 'foo')
374 assert result == 'a\nb'
375 result = cd.dict_get_join({'foo': [1, 2]}, 'foo')
376 assert result == '1\n2'
377 result = cd.dict_get_join({'bar': 'a'}, 'bar')
378 assert result == 'a'
379 result = cd.dict_get_join({'a': 1}, 'a')
380 assert result == 1
381
382 def test_last_local_images(self):
383 out = '''
384 docker.io/ceph/daemon-base@
385 docker.io/ceph/ceph:v15.2.5
386 docker.io/ceph/daemon-base:octopus
387 '''
388 image = cd._filter_last_local_ceph_image(out)
389 assert image == 'docker.io/ceph/ceph:v15.2.5'
390
391
392 class TestCustomContainer(unittest.TestCase):
393 cc: cd.CustomContainer
394
395 def setUp(self):
396 self.cc = cd.CustomContainer(
397 'e863154d-33c7-4350-bca5-921e0467e55b',
398 'container',
399 config_json={
400 'entrypoint': 'bash',
401 'gid': 1000,
402 'args': [
403 '--no-healthcheck',
404 '-p 6800:6800'
405 ],
406 'envs': ['SECRET=password'],
407 'ports': [8080, 8443],
408 'volume_mounts': {
409 '/CONFIG_DIR': '/foo/conf',
410 'bar/config': '/bar:ro'
411 },
412 'bind_mounts': [
413 [
414 'type=bind',
415 'source=/CONFIG_DIR',
416 'destination=/foo/conf',
417 ''
418 ],
419 [
420 'type=bind',
421 'source=bar/config',
422 'destination=/bar:ro',
423 'ro=true'
424 ]
425 ]
426 },
427 image='docker.io/library/hello-world:latest'
428 )
429
430 def test_entrypoint(self):
431 self.assertEqual(self.cc.entrypoint, 'bash')
432
433 def test_uid_gid(self):
434 self.assertEqual(self.cc.uid, 65534)
435 self.assertEqual(self.cc.gid, 1000)
436
437 def test_ports(self):
438 self.assertEqual(self.cc.ports, [8080, 8443])
439
440 def test_get_container_args(self):
441 result = self.cc.get_container_args()
442 self.assertEqual(result, [
443 '--no-healthcheck',
444 '-p 6800:6800'
445 ])
446
447 def test_get_container_envs(self):
448 result = self.cc.get_container_envs()
449 self.assertEqual(result, ['SECRET=password'])
450
451 def test_get_container_mounts(self):
452 result = self.cc.get_container_mounts('/xyz')
453 self.assertDictEqual(result, {
454 '/CONFIG_DIR': '/foo/conf',
455 '/xyz/bar/config': '/bar:ro'
456 })
457
458 def test_get_container_binds(self):
459 result = self.cc.get_container_binds('/xyz')
460 self.assertEqual(result, [
461 [
462 'type=bind',
463 'source=/CONFIG_DIR',
464 'destination=/foo/conf',
465 ''
466 ],
467 [
468 'type=bind',
469 'source=/xyz/bar/config',
470 'destination=/bar:ro',
471 'ro=true'
472 ]
473 ])
474
475
476 class TestMonitoring(object):
477 @mock.patch('cephadm.call')
478 def test_get_version_alertmanager(self, _call):
479 ctx = mock.Mock()
480 daemon_type = 'alertmanager'
481
482 # binary `prometheus`
483 _call.return_value = '', '{}, version 0.16.1'.format(daemon_type), 0
484 version = cd.Monitoring.get_version(ctx, 'container_id', daemon_type)
485 assert version == '0.16.1'
486
487 # binary `prometheus-alertmanager`
488 _call.side_effect = (
489 ('', '', 1),
490 ('', '{}, version 0.16.1'.format(daemon_type), 0),
491 )
492 version = cd.Monitoring.get_version(ctx, 'container_id', daemon_type)
493 assert version == '0.16.1'
494
495 @mock.patch('cephadm.call')
496 def test_get_version_prometheus(self, _call):
497 ctx = mock.Mock()
498 daemon_type = 'prometheus'
499 _call.return_value = '', '{}, version 0.16.1'.format(daemon_type), 0
500 version = cd.Monitoring.get_version(ctx, 'container_id', daemon_type)
501 assert version == '0.16.1'
502
503 @mock.patch('cephadm.call')
504 def test_get_version_node_exporter(self, _call):
505 ctx = mock.Mock()
506 daemon_type = 'node-exporter'
507 _call.return_value = '', '{}, version 0.16.1'.format(daemon_type.replace('-', '_')), 0
508 version = cd.Monitoring.get_version(ctx, 'container_id', daemon_type)
509 assert version == '0.16.1'
510
511 @mock.patch('cephadm.os.fchown')
512 @mock.patch('cephadm.get_parm')
513 @mock.patch('cephadm.makedirs')
514 @mock.patch('cephadm.open')
515 @mock.patch('cephadm.make_log_dir')
516 @mock.patch('cephadm.make_data_dir')
517 @mock.patch('cephadm.args')
518 def test_create_daemon_dirs_prometheus(self, args, make_data_dir, make_log_dir, _open, makedirs,
519 get_parm, fchown):
520 """
521 Ensures the required and optional files given in the configuration are
522 created and mapped correctly inside the container. Tests absolute and
523 relative file paths given in the configuration.
524 """
525 args.data_dir = '/somedir'
526 fsid = 'aaf5a720-13fe-4a3b-82b9-2d99b7fd9704'
527 daemon_type = 'prometheus'
528 uid, gid = 50, 50
529 daemon_id = 'home'
530 files = {
531 'files': {
532 'prometheus.yml': 'foo',
533 '/etc/prometheus/alerting/ceph_alerts.yml': 'bar'
534 }
535 }
536 get_parm.return_value = files
537
538 cd.create_daemon_dirs(fsid,
539 daemon_type,
540 daemon_id,
541 uid,
542 gid,
543 config=None,
544 keyring=None)
545
546 prefix = '{data_dir}/{fsid}/{daemon_type}.{daemon_id}'.format(
547 data_dir=args.data_dir,
548 fsid=fsid,
549 daemon_type=daemon_type,
550 daemon_id=daemon_id
551 )
552 assert _open.call_args_list == [
553 call('{}/etc/prometheus/prometheus.yml'.format(prefix), 'w',
554 encoding='utf-8'),
555 call('{}/etc/prometheus/alerting/ceph_alerts.yml'.format(prefix), 'w',
556 encoding='utf-8'),
557 ]
558 assert call().__enter__().write('foo') in _open.mock_calls
559 assert call().__enter__().write('bar') in _open.mock_calls