]> git.proxmox.com Git - ceph.git/blob - ceph/src/cephadm/tests/test_cephadm.py
5487f43b3a88ccb85a6233fe41659f3bd6fe549f
[ceph.git] / ceph / src / cephadm / tests / test_cephadm.py
1 # type: ignore
2 import mock
3 from mock import patch
4 import os
5 import sys
6 import unittest
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 def test_is_not_fsid(self):
16 assert not cd.is_fsid('no-uuid')
17
18 def test_is_fsid(self):
19 assert cd.is_fsid('e863154d-33c7-4350-bca5-921e0467e55b')
20
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 """
46 default via 192.168.178.1 dev enxd89ef3f34260 proto dhcp metric 100
47 10.0.0.0/8 via 10.4.0.1 dev tun0 proto static metric 50
48 10.3.0.0/21 via 10.4.0.1 dev tun0 proto static metric 50
49 10.4.0.1 dev tun0 proto kernel scope link src 10.4.0.2 metric 50
50 137.1.0.0/16 via 10.4.0.1 dev tun0 proto static metric 50
51 138.1.0.0/16 via 10.4.0.1 dev tun0 proto static metric 50
52 139.1.0.0/16 via 10.4.0.1 dev tun0 proto static metric 50
53 140.1.0.0/17 via 10.4.0.1 dev tun0 proto static metric 50
54 141.1.0.0/16 via 10.4.0.1 dev tun0 proto static metric 50
55 169.254.0.0/16 dev docker0 scope link metric 1000
56 172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
57 192.168.39.0/24 dev virbr1 proto kernel scope link src 192.168.39.1 linkdown
58 192.168.122.0/24 dev virbr0 proto kernel scope link src 192.168.122.1 linkdown
59 192.168.178.0/24 dev enxd89ef3f34260 proto kernel scope link src 192.168.178.28 metric 100
60 192.168.178.1 dev enxd89ef3f34260 proto static scope link metric 100
61 195.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 """
72 default via 10.3.64.1 dev eno1 proto static metric 100
73 10.3.64.0/24 dev eno1 proto kernel scope link src 10.3.64.23 metric 100
74 10.3.64.0/24 dev eno1 proto kernel scope link src 10.3.64.27 metric 100
75 10.88.0.0/16 dev cni-podman0 proto kernel scope link src 10.88.0.1 linkdown
76 172.21.0.0/20 via 172.21.3.189 dev tun0
77 172.21.1.0/20 via 172.21.3.189 dev tun0
78 172.21.2.1 via 172.21.3.189 dev tun0
79 172.21.3.1 dev tun0 proto kernel scope link src 172.21.3.2
80 172.21.4.0/24 via 172.21.3.1 dev tun0
81 172.21.5.0/24 via 172.21.3.1 dev tun0
82 172.21.6.0/24 via 172.21.3.1 dev tun0
83 172.21.7.0/24 via 172.21.3.1 dev tun0
84 192.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 ])
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
100 fdbc:7574:21fe:9200::/64 dev wlp2s0 proto ra metric 600 pref medium
101 fdd8:591e:4969:6363::/64 dev wlp2s0 proto ra metric 600 pref medium
102 fde4:8dba:82e1::/64 dev eth1 proto kernel metric 256 expires 1844sec pref medium
103 fe80::/64 dev tun0 proto kernel metric 256 pref medium
104 fe80::/64 dev wlp2s0 proto kernel metric 600 pref medium
105 default dev tun0 proto static metric 50 pref medium
106 default via fe80::2480:28ec:5097:3fe2 dev wlp2s0 proto ra metric 20600 pref medium
107 """,
108 """
109 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 state UNKNOWN qlen 1000
110 inet6 ::1/128 scope host
111 valid_lft forever preferred_lft forever
112 2: 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
135 12: 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
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
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"
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
281 def test_last_local_images(self):
282 out = '''
283 docker.io/ceph/daemon-base@
284 docker.io/ceph/ceph:v15.2.5
285 docker.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
290
291 class 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 ])