]>
git.proxmox.com Git - ceph.git/blob - ceph/src/cephadm/tests/test_cephadm.py
3 from mock
import patch
, call
10 with
patch ( 'builtins.open' , create
= True ):
11 from importlib
. machinery
import SourceFileLoader
12 cd
= SourceFileLoader ( 'cephadm' , 'cephadm' ). load_module ()
14 class TestCephAdm ( object ):
16 def test_docker_unit_file ( self
):
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
25 def test_attempt_bind ( self
):
26 cd
. logger
= mock
. Mock ()
32 _os_error
. errno
= errno
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 ),
42 _socket
. bind
. side_effect
= side_effect
44 cd
. attempt_bind ( _socket
, address
, port
)
45 except Exception as e
:
46 assert isinstance ( e
, expected_exception
)
48 if expected_exception
is not None :
49 assert False , '{} should not be None' . format ( expected_exception
)
51 @mock . patch ( 'cephadm.attempt_bind' )
52 def test_port_in_use ( self
, attempt_bind
):
54 assert cd
. port_in_use ( 9100 ) == False
56 attempt_bind
. side_effect
= cd
. PortOccupiedError ( 'msg' )
57 assert cd
. port_in_use ( 9100 ) == True
60 os_error
. errno
= errno
. EADDRNOTAVAIL
61 attempt_bind
. side_effect
= os_error
62 assert cd
. port_in_use ( 9100 ) == False
65 os_error
. errno
= errno
. EAFNOSUPPORT
66 attempt_bind
. side_effect
= os_error
67 assert cd
. port_in_use ( 9100 ) == False
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
74 for address
, address_family
in (
75 ( '0.0.0.0' , socket
. AF_INET
),
76 ( '::' , socket
. AF_INET6
),
79 cd
. check_ip_port ( address
, 9100 )
83 assert _socket
. call_args
== call ( address_family
, socket
. SOCK_STREAM
)
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
92 _os_error
. errno
= errno
95 for address
, address_family
in (
96 ( '0.0.0.0' , socket
. AF_INET
),
97 ( '::' , socket
. AF_INET6
),
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 ),
105 mock_socket_obj
= mock
. Mock ()
106 mock_socket_obj
. bind
. side_effect
= side_effect
107 _socket
. return_value
= mock_socket_obj
109 cd
. check_ip_port ( address
, 9100 )
110 except Exception as e
:
111 assert isinstance ( e
, expected_exception
)
113 assert side_effect
is None
116 def test_is_not_fsid ( self
):
117 assert not cd
. is_fsid ( 'no-uuid' )
119 def test_is_fsid ( self
):
120 assert cd
. is_fsid ( 'e863154d-33c7-4350-bca5-921e0467e55b' )
122 def test__get_parser_image ( self
):
123 args
= cd
._ parse
_ args
([ '--image' , 'foo' , 'version' ])
124 assert args
. image
== 'foo'
126 def test_CustomValidation ( self
):
127 assert cd
._ parse
_ args
([ 'deploy' , '--name' , 'mon.a' , '--fsid' , 'fsid' ])
129 with pytest
. raises ( SystemExit ):
130 cd
._ parse
_ args
([ 'deploy' , '--name' , 'wrong' , '--fsid' , 'fsid' ])
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 )),
136 def test_parse_podman_version ( self
, test_input
, expected
):
137 assert cd
._ parse
_ podman
_ version
( test_input
) == expected
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
)
144 @pytest . mark
. parametrize ( "test_input, expected" , [
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
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' ]
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
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' ]}
194 def test_parse_ipv4_route ( self
, test_input
, expected
):
195 assert cd
._ parse
_ ipv
4_ route
( test_input
) == expected
197 @pytest . mark
. parametrize ( "test_routes, test_ips, expected" , [
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
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
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" ]
256 def test_parse_ipv6_route ( self
, test_routes
, test_ips
, expected
):
257 assert cd
._ parse
_ ipv
6_ route
( test_routes
, test_ips
) == expected
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
)
269 def test_unwrap_ipv6 ( self
):
270 def unwrap_test ( address
, expected
):
271 assert cd
. unwrap_ipv6 ( address
) == expected
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
)
281 def test_wrap_ipv6 ( self
):
282 def wrap_test ( address
, expected
):
283 assert cd
. wrap_ipv6 ( address
) == expected
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
)
295 @mock . patch ( 'cephadm.call_throws' )
296 @mock . patch ( 'cephadm.get_parm' )
297 def test_registry_login ( self
, get_parm
, call_throws
):
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' ])
303 retval
= cd
. command_registry_login ()
306 # test bad login attempt with invalid arguments given
307 args
= cd
._ parse
_ args
([ 'registry-login' , '--registry-url' , 'bad-args-url' ])
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' )
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' ])
318 retval
= cd
. command_registry_login ()
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' ])
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 "
330 " \" url \" : \" REGISTRY_URL \" , \n "
331 " \" username \" : \" REGISTRY_USERNAME \" , \n "
332 " \" password \" : \" REGISTRY_PASSWORD \"\n "
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' ])
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"
343 def test_get_image_info_from_inspect ( self
):
345 out
= """204a01f9b0b6710dd0c0af7f37ce7139c47ff0f0105d778d7104c69282dfbbf1,[docker.io/ceph/ceph@sha256:1cc9b824e1b076cdff52a9aa3f0cc8557d879fb2fbbba0cafed970aca59a3992]"""
346 r
= cd
. get_image_info_from_inspect ( out
, 'registry/ceph/ceph:latest' )
348 'image_id' : '204a01f9b0b6710dd0c0af7f37ce7139c47ff0f0105d778d7104c69282dfbbf1' ,
349 'repo_digest' : 'docker.io/ceph/ceph@sha256:1cc9b824e1b076cdff52a9aa3f0cc8557d879fb2fbbba0cafed970aca59a3992'
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' )
356 'image_id' : '16f4549cf7a8f112bbebf7946749e961fbbd1b0838627fe619aab16bc17ce552' ,
357 'repo_digest' : 'quay.ceph.io/ceph-ci/ceph@sha256:4e13da36c1bd6780b312a985410ae678984c37e6a9493a74c87e4a50b9bda41f'
360 def test_dict_get ( self
):
361 result
= cd
. dict_get ({ 'a' : 1 }, 'a' , require
= True )
363 result
= cd
. dict_get ({ 'a' : 1 }, 'b' )
364 assert result
is None
365 result
= cd
. dict_get ({ 'a' : 1 }, 'b' , default
= 2 )
368 def test_dict_get_error ( self
):
369 with pytest
. raises ( cd
. Error
):
370 cd
. dict_get ({ 'a' : 1 }, 'b' , require
= True )
372 def test_dict_get_join ( self
):
373 result
= cd
. dict_get_join ({ 'foo' : [ 'a' , 'b' ]}, 'foo' )
374 assert result
== 'a \n b'
375 result
= cd
. dict_get_join ({ 'foo' : [ 1 , 2 ]}, 'foo' )
376 assert result
== '1 \n 2'
377 result
= cd
. dict_get_join ({ 'bar' : 'a' }, 'bar' )
379 result
= cd
. dict_get_join ({ 'a' : 1 }, 'a' )
382 def test_last_local_images ( self
):
384 docker.io/ceph/daemon-base@
385 docker.io/ceph/ceph:v15.2.5
386 docker.io/ceph/daemon-base:octopus
388 image
= cd
._ filter
_l ast
_l ocal
_ ceph
_ image
( out
)
389 assert image
== 'docker.io/ceph/ceph:v15.2.5'
392 class TestCustomContainer ( unittest
. TestCase
):
393 cc
: cd
. CustomContainer
396 self
. cc
= cd
. CustomContainer (
397 'e863154d-33c7-4350-bca5-921e0467e55b' ,
400 'entrypoint' : 'bash' ,
406 'envs' : [ 'SECRET=password' ],
407 'ports' : [ 8080 , 8443 ],
409 '/CONFIG_DIR' : '/foo/conf' ,
410 'bar/config' : '/bar:ro'
415 'source=/CONFIG_DIR' ,
416 'destination=/foo/conf' ,
422 'destination=/bar:ro' ,
427 image
= 'docker.io/library/hello-world:latest'
430 def test_entrypoint ( self
):
431 self
. assertEqual ( self
. cc
. entrypoint
, 'bash' )
433 def test_uid_gid ( self
):
434 self
. assertEqual ( self
. cc
. uid
, 65534 )
435 self
. assertEqual ( self
. cc
. gid
, 1000 )
437 def test_ports ( self
):
438 self
. assertEqual ( self
. cc
. ports
, [ 8080 , 8443 ])
440 def test_get_container_args ( self
):
441 result
= self
. cc
. get_container_args ()
442 self
. assertEqual ( result
, [
447 def test_get_container_envs ( self
):
448 result
= self
. cc
. get_container_envs ()
449 self
. assertEqual ( result
, [ 'SECRET=password' ])
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'
458 def test_get_container_binds ( self
):
459 result
= self
. cc
. get_container_binds ( '/xyz' )
460 self
. assertEqual ( result
, [
463 'source=/CONFIG_DIR' ,
464 'destination=/foo/conf' ,
469 'source=/xyz/bar/config' ,
470 'destination=/bar:ro' ,
476 class TestMonitoring ( object ):
477 @mock . patch ( 'cephadm.call' )
478 def test_get_version_alertmanager ( self
, _call
):
480 daemon_type
= 'alertmanager'
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'
487 # binary `prometheus-alertmanager`
488 _call
. side_effect
= (
490 ( '' , '{}, version 0.16.1' . format ( daemon_type
), 0 ),
492 version
= cd
. Monitoring
. get_version ( ctx
, 'container_id' , daemon_type
)
493 assert version
== '0.16.1'
495 @mock . patch ( 'cephadm.call' )
496 def test_get_version_prometheus ( self
, _call
):
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'
503 @mock . patch ( 'cephadm.call' )
504 def test_get_version_node_exporter ( self
, _call
):
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'
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
,
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.
525 args
. data_dir
= '/somedir'
526 fsid
= 'aaf5a720-13fe-4a3b-82b9-2d99b7fd9704'
527 daemon_type
= 'prometheus'
532 'prometheus.yml' : 'foo' ,
533 '/etc/prometheus/alerting/ceph_alerts.yml' : 'bar'
536 get_parm
. return_value
= files
538 cd
. create_daemon_dirs ( fsid
,
546 prefix
= ' {data_dir} / {fsid} / {daemon_type} . {daemon_id} ' . format (
547 data_dir
= args
. data_dir
,
549 daemon_type
= daemon_type
,
552 assert _open
. call_args_list
== [
553 call ( '{}/etc/prometheus/prometheus.yml' . format ( prefix
), 'w' ,
555 call ( '{}/etc/prometheus/alerting/ceph_alerts.yml' . format ( prefix
), 'w' ,
558 assert call () .__ enter
__ (). write ( 'foo' ) in _open
. mock_calls
559 assert call () .__ enter
__ (). write ( 'bar' ) in _open
. mock_calls