]> git.proxmox.com Git - ceph.git/blame - ceph/src/cephadm/tests/test_cephadm.py
import 15.2.9
[ceph.git] / ceph / src / cephadm / tests / test_cephadm.py
CommitLineData
e306af50 1# type: ignore
9f95a23c 2import mock
f91f0fd5 3from mock import patch
9f95a23c
TL
4import os
5import sys
6import unittest
7
8import pytest
9
f91f0fd5 10with patch('builtins.open', create=True):
9f95a23c
TL
11 from importlib.machinery import SourceFileLoader
12 cd = SourceFileLoader('cephadm', 'cephadm').load_module()
9f95a23c
TL
13
14class TestCephAdm(object):
f91f0fd5 15 def test_is_not_fsid(self):
9f95a23c
TL
16 assert not cd.is_fsid('no-uuid')
17
f91f0fd5
TL
18 def test_is_fsid(self):
19 assert cd.is_fsid('e863154d-33c7-4350-bca5-921e0467e55b')
20
9f95a23c
TL
21 def test__get_parser_image(self):
22 args = cd._parse_args(['--image', 'foo', 'version'])
23 assert args.image == 'foo'
24
25 def test_CustomValidation(self):
26 assert cd._parse_args(['deploy', '--name', 'mon.a', '--fsid', 'fsid'])
27
28 with pytest.raises(SystemExit):
29 cd._parse_args(['deploy', '--name', 'wrong', '--fsid', 'fsid'])
30
31 @pytest.mark.parametrize("test_input, expected", [
32 ("podman version 1.6.2", (1,6,2)),
33 ("podman version 1.6.2-stable2", (1,6,2)),
34 ])
35 def test_parse_podman_version(self, test_input, expected):
36 assert cd._parse_podman_version(test_input) == expected
37
38 def test_parse_podman_version_invalid(self):
39 with pytest.raises(ValueError) as res:
40 cd._parse_podman_version('podman version inval.id')
41 assert 'inval' in str(res.value)
42
43 @pytest.mark.parametrize("test_input, expected", [
44 (
45"""
46default via 192.168.178.1 dev enxd89ef3f34260 proto dhcp metric 100
4710.0.0.0/8 via 10.4.0.1 dev tun0 proto static metric 50
4810.3.0.0/21 via 10.4.0.1 dev tun0 proto static metric 50
4910.4.0.1 dev tun0 proto kernel scope link src 10.4.0.2 metric 50
50137.1.0.0/16 via 10.4.0.1 dev tun0 proto static metric 50
51138.1.0.0/16 via 10.4.0.1 dev tun0 proto static metric 50
52139.1.0.0/16 via 10.4.0.1 dev tun0 proto static metric 50
53140.1.0.0/17 via 10.4.0.1 dev tun0 proto static metric 50
54141.1.0.0/16 via 10.4.0.1 dev tun0 proto static metric 50
55169.254.0.0/16 dev docker0 scope link metric 1000
56172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
57192.168.39.0/24 dev virbr1 proto kernel scope link src 192.168.39.1 linkdown
58192.168.122.0/24 dev virbr0 proto kernel scope link src 192.168.122.1 linkdown
59192.168.178.0/24 dev enxd89ef3f34260 proto kernel scope link src 192.168.178.28 metric 100
60192.168.178.1 dev enxd89ef3f34260 proto static scope link metric 100
61195.135.221.12 via 192.168.178.1 dev enxd89ef3f34260 proto static metric 100
62""",
63 {
64 '10.4.0.1': ['10.4.0.2'],
65 '172.17.0.0/16': ['172.17.0.1'],
66 '192.168.39.0/24': ['192.168.39.1'],
67 '192.168.122.0/24': ['192.168.122.1'],
68 '192.168.178.0/24': ['192.168.178.28']
69 }
70 ), (
71"""
72default via 10.3.64.1 dev eno1 proto static metric 100
7310.3.64.0/24 dev eno1 proto kernel scope link src 10.3.64.23 metric 100
7410.3.64.0/24 dev eno1 proto kernel scope link src 10.3.64.27 metric 100
7510.88.0.0/16 dev cni-podman0 proto kernel scope link src 10.88.0.1 linkdown
76172.21.0.0/20 via 172.21.3.189 dev tun0
77172.21.1.0/20 via 172.21.3.189 dev tun0
78172.21.2.1 via 172.21.3.189 dev tun0
79172.21.3.1 dev tun0 proto kernel scope link src 172.21.3.2
80172.21.4.0/24 via 172.21.3.1 dev tun0
81172.21.5.0/24 via 172.21.3.1 dev tun0
82172.21.6.0/24 via 172.21.3.1 dev tun0
83172.21.7.0/24 via 172.21.3.1 dev tun0
84192.168.122.0/24 dev virbr0 proto kernel scope link src 192.168.122.1 linkdown
85""",
86 {
87 '10.3.64.0/24': ['10.3.64.23', '10.3.64.27'],
88 '10.88.0.0/16': ['10.88.0.1'],
89 '172.21.3.1': ['172.21.3.2'],
90 '192.168.122.0/24': ['192.168.122.1']}
91 ),
92 ])
f6b5b4d7
TL
93 def test_parse_ipv4_route(self, test_input, expected):
94 assert cd._parse_ipv4_route(test_input) == expected
95
96 @pytest.mark.parametrize("test_routes, test_ips, expected", [
97 (
98"""
99::1 dev lo proto kernel metric 256 pref medium
100fdbc:7574:21fe:9200::/64 dev wlp2s0 proto ra metric 600 pref medium
101fdd8:591e:4969:6363::/64 dev wlp2s0 proto ra metric 600 pref medium
102fde4:8dba:82e1::/64 dev eth1 proto kernel metric 256 expires 1844sec pref medium
103fe80::/64 dev tun0 proto kernel metric 256 pref medium
104fe80::/64 dev wlp2s0 proto kernel metric 600 pref medium
105default dev tun0 proto static metric 50 pref medium
106default via fe80::2480:28ec:5097:3fe2 dev wlp2s0 proto ra metric 20600 pref medium
107""",
108"""
1091: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 state UNKNOWN qlen 1000
110 inet6 ::1/128 scope host
111 valid_lft forever preferred_lft forever
1122: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
113 inet6 fdd8:591e:4969:6363:4c52:cafe:8dd4:dc4/64 scope global temporary dynamic
114 valid_lft 86394sec preferred_lft 14394sec
115 inet6 fdbc:7574:21fe:9200:4c52:cafe:8dd4:dc4/64 scope global temporary dynamic
116 valid_lft 6745sec preferred_lft 3145sec
117 inet6 fdd8:591e:4969:6363:103a:abcd:af1f:57f3/64 scope global temporary deprecated dynamic
118 valid_lft 86394sec preferred_lft 0sec
119 inet6 fdbc:7574:21fe:9200:103a:abcd:af1f:57f3/64 scope global temporary deprecated dynamic
120 valid_lft 6745sec preferred_lft 0sec
121 inet6 fdd8:591e:4969:6363:a128:1234:2bdd:1b6f/64 scope global temporary deprecated dynamic
122 valid_lft 86394sec preferred_lft 0sec
123 inet6 fdbc:7574:21fe:9200:a128:1234:2bdd:1b6f/64 scope global temporary deprecated dynamic
124 valid_lft 6745sec preferred_lft 0sec
125 inet6 fdd8:591e:4969:6363:d581:4321:380b:3905/64 scope global temporary deprecated dynamic
126 valid_lft 86394sec preferred_lft 0sec
127 inet6 fdbc:7574:21fe:9200:d581:4321:380b:3905/64 scope global temporary deprecated dynamic
128 valid_lft 6745sec preferred_lft 0sec
129 inet6 fe80::1111:2222:3333:4444/64 scope link noprefixroute
130 valid_lft forever preferred_lft forever
131 inet6 fde4:8dba:82e1:0:ec4a:e402:e9df:b357/64 scope global temporary dynamic
132 valid_lft 1074sec preferred_lft 1074sec
133 inet6 fde4:8dba:82e1:0:5054:ff:fe72:61af/64 scope global dynamic mngtmpaddr
134 valid_lft 1074sec preferred_lft 1074sec
13512: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 state UNKNOWN qlen 100
136 inet6 fe80::cafe:cafe:cafe:cafe/64 scope link stable-privacy
137 valid_lft forever preferred_lft forever
138""",
139 {
140 "::1": ["::1"],
141 "fdbc:7574:21fe:9200::/64": ["fdbc:7574:21fe:9200:4c52:cafe:8dd4:dc4",
142 "fdbc:7574:21fe:9200:103a:abcd:af1f:57f3",
143 "fdbc:7574:21fe:9200:a128:1234:2bdd:1b6f",
144 "fdbc:7574:21fe:9200:d581:4321:380b:3905"],
145 "fdd8:591e:4969:6363::/64": ["fdd8:591e:4969:6363:4c52:cafe:8dd4:dc4",
146 "fdd8:591e:4969:6363:103a:abcd:af1f:57f3",
147 "fdd8:591e:4969:6363:a128:1234:2bdd:1b6f",
148 "fdd8:591e:4969:6363:d581:4321:380b:3905"],
149 "fde4:8dba:82e1::/64": ["fde4:8dba:82e1:0:ec4a:e402:e9df:b357",
150 "fde4:8dba:82e1:0:5054:ff:fe72:61af"],
151 "fe80::/64": ["fe80::1111:2222:3333:4444",
152 "fe80::cafe:cafe:cafe:cafe"]
153 }
154 )])
155 def test_parse_ipv6_route(self, test_routes, test_ips, expected):
156 assert cd._parse_ipv6_route(test_routes, test_ips) == expected
157
158 def test_is_ipv6(self):
159 cd.logger = mock.Mock()
160 for good in ("[::1]", "::1",
161 "fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"):
162 assert cd.is_ipv6(good)
163 for bad in ("127.0.0.1",
164 "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffg",
165 "1:2:3:4:5:6:7:8:9", "fd00::1::1", "[fg::1]"):
166 assert not cd.is_ipv6(bad)
167
168 def test_unwrap_ipv6(self):
169 def unwrap_test(address, expected):
170 assert cd.unwrap_ipv6(address) == expected
171
172 tests = [
173 ('::1', '::1'), ('[::1]', '::1'),
174 ('[fde4:8dba:82e1:0:5054:ff:fe6a:357]', 'fde4:8dba:82e1:0:5054:ff:fe6a:357'),
175 ('can actually be any string', 'can actually be any string'),
176 ('[but needs to be stripped] ', '[but needs to be stripped] ')]
177 for address, expected in tests:
178 unwrap_test(address, expected)
179
f91f0fd5
TL
180 def test_wrap_ipv6(self):
181 def wrap_test(address, expected):
182 assert cd.wrap_ipv6(address) == expected
183
184 tests = [
185 ('::1', '[::1]'), ('[::1]', '[::1]'),
186 ('fde4:8dba:82e1:0:5054:ff:fe6a:357',
187 '[fde4:8dba:82e1:0:5054:ff:fe6a:357]'),
188 ('myhost.example.com', 'myhost.example.com'),
189 ('192.168.0.1', '192.168.0.1'),
190 ('', ''), ('fd00::1::1', 'fd00::1::1')]
191 for address, expected in tests:
192 wrap_test(address, expected)
193
f6b5b4d7
TL
194 @mock.patch('cephadm.call_throws')
195 @mock.patch('cephadm.get_parm')
196 def test_registry_login(self, get_parm, call_throws):
197
198 # test normal valid login with url, username and password specified
199 call_throws.return_value = '', '', 0
200 args = cd._parse_args(['registry-login', '--registry-url', 'sample-url', '--registry-username', 'sample-user', '--registry-password', 'sample-pass'])
201 cd.args = args
202 retval = cd.command_registry_login()
203 assert retval == 0
204
205 # test bad login attempt with invalid arguments given
206 args = cd._parse_args(['registry-login', '--registry-url', 'bad-args-url'])
207 cd.args = args
208 with pytest.raises(Exception) as e:
209 assert cd.command_registry_login()
210 assert str(e.value) == ('Invalid custom registry arguments received. To login to a custom registry include '
211 '--registry-url, --registry-username and --registry-password options or --registry-json option')
212
213 # test normal valid login with json file
214 get_parm.return_value = {"url": "sample-url", "username": "sample-username", "password": "sample-password"}
215 args = cd._parse_args(['registry-login', '--registry-json', 'sample-json'])
216 cd.args = args
217 retval = cd.command_registry_login()
218 assert retval == 0
219
220 # test bad login attempt with bad json file
221 get_parm.return_value = {"bad-json": "bad-json"}
222 args = cd._parse_args(['registry-login', '--registry-json', 'sample-json'])
223 cd.args = args
224 with pytest.raises(Exception) as e:
225 assert cd.command_registry_login()
226 assert str(e.value) == ("json provided for custom registry login did not include all necessary fields. "
227 "Please setup json file as\n"
228 "{\n"
229 " \"url\": \"REGISTRY_URL\",\n"
230 " \"username\": \"REGISTRY_USERNAME\",\n"
231 " \"password\": \"REGISTRY_PASSWORD\"\n"
232 "}\n")
233
234 # test login attempt with valid arguments where login command fails
235 call_throws.side_effect = Exception
236 args = cd._parse_args(['registry-login', '--registry-url', 'sample-url', '--registry-username', 'sample-user', '--registry-password', 'sample-pass'])
237 cd.args = args
238 with pytest.raises(Exception) as e:
239 cd.command_registry_login()
240 assert str(e.value) == "Failed to login to custom registry @ sample-url as sample-user with given password"
f91f0fd5
TL
241
242 def test_get_image_info_from_inspect(self):
243 # podman
244 out = """204a01f9b0b6710dd0c0af7f37ce7139c47ff0f0105d778d7104c69282dfbbf1,["docker.io/ceph/ceph@sha256:1cc9b824e1b076cdff52a9aa3f0cc8557d879fb2fbbba0cafed970aca59a3992"]"""
245 r = cd.get_image_info_from_inspect(out, 'registry/ceph/ceph:latest')
246 assert r == {
247 'image_id': '204a01f9b0b6710dd0c0af7f37ce7139c47ff0f0105d778d7104c69282dfbbf1',
248 'repo_digest': 'docker.io/ceph/ceph@sha256:1cc9b824e1b076cdff52a9aa3f0cc8557d879fb2fbbba0cafed970aca59a3992'
249 }
250
251 # docker
252 out = """sha256:16f4549cf7a8f112bbebf7946749e961fbbd1b0838627fe619aab16bc17ce552,["quay.ceph.io/ceph-ci/ceph@sha256:4e13da36c1bd6780b312a985410ae678984c37e6a9493a74c87e4a50b9bda41f"]"""
253 r = cd.get_image_info_from_inspect(out, 'registry/ceph/ceph:latest')
254 assert r == {
255 'image_id': '16f4549cf7a8f112bbebf7946749e961fbbd1b0838627fe619aab16bc17ce552',
256 'repo_digest': 'quay.ceph.io/ceph-ci/ceph@sha256:4e13da36c1bd6780b312a985410ae678984c37e6a9493a74c87e4a50b9bda41f'
257 }
258
259 def test_dict_get(self):
260 result = cd.dict_get({'a': 1}, 'a', require=True)
261 assert result == 1
262 result = cd.dict_get({'a': 1}, 'b')
263 assert result is None
264 result = cd.dict_get({'a': 1}, 'b', default=2)
265 assert result == 2
266
267 def test_dict_get_error(self):
268 with pytest.raises(cd.Error):
269 cd.dict_get({'a': 1}, 'b', require=True)
270
271 def test_dict_get_join(self):
272 result = cd.dict_get_join({'foo': ['a', 'b']}, 'foo')
273 assert result == 'a\nb'
274 result = cd.dict_get_join({'foo': [1, 2]}, 'foo')
275 assert result == '1\n2'
276 result = cd.dict_get_join({'bar': 'a'}, 'bar')
277 assert result == 'a'
278 result = cd.dict_get_join({'a': 1}, 'a')
279 assert result == 1
280
adb31ebb
TL
281 def test_last_local_images(self):
282 out = '''
283docker.io/ceph/daemon-base@
284docker.io/ceph/ceph:v15.2.5
285docker.io/ceph/daemon-base:octopus
286 '''
287 image = cd._filter_last_local_ceph_image(out)
288 assert image == 'docker.io/ceph/ceph:v15.2.5'
289
f91f0fd5
TL
290
291class TestCustomContainer(unittest.TestCase):
292 cc: cd.CustomContainer
293
294 def setUp(self):
295 self.cc = cd.CustomContainer(
296 'e863154d-33c7-4350-bca5-921e0467e55b',
297 'container',
298 config_json={
299 'entrypoint': 'bash',
300 'gid': 1000,
301 'args': [
302 '--no-healthcheck',
303 '-p 6800:6800'
304 ],
305 'envs': ['SECRET=password'],
306 'ports': [8080, 8443],
307 'volume_mounts': {
308 '/CONFIG_DIR': '/foo/conf',
309 'bar/config': '/bar:ro'
310 },
311 'bind_mounts': [
312 [
313 'type=bind',
314 'source=/CONFIG_DIR',
315 'destination=/foo/conf',
316 ''
317 ],
318 [
319 'type=bind',
320 'source=bar/config',
321 'destination=/bar:ro',
322 'ro=true'
323 ]
324 ]
325 },
326 image='docker.io/library/hello-world:latest'
327 )
328
329 def test_entrypoint(self):
330 self.assertEqual(self.cc.entrypoint, 'bash')
331
332 def test_uid_gid(self):
333 self.assertEqual(self.cc.uid, 65534)
334 self.assertEqual(self.cc.gid, 1000)
335
336 def test_ports(self):
337 self.assertEqual(self.cc.ports, [8080, 8443])
338
339 def test_get_container_args(self):
340 result = self.cc.get_container_args()
341 self.assertEqual(result, [
342 '--no-healthcheck',
343 '-p 6800:6800'
344 ])
345
346 def test_get_container_envs(self):
347 result = self.cc.get_container_envs()
348 self.assertEqual(result, ['SECRET=password'])
349
350 def test_get_container_mounts(self):
351 result = self.cc.get_container_mounts('/xyz')
352 self.assertDictEqual(result, {
353 '/CONFIG_DIR': '/foo/conf',
354 '/xyz/bar/config': '/bar:ro'
355 })
356
357 def test_get_container_binds(self):
358 result = self.cc.get_container_binds('/xyz')
359 self.assertEqual(result, [
360 [
361 'type=bind',
362 'source=/CONFIG_DIR',
363 'destination=/foo/conf',
364 ''
365 ],
366 [
367 'type=bind',
368 'source=/xyz/bar/config',
369 'destination=/bar:ro',
370 'ro=true'
371 ]
372 ])