]>
Commit | Line | Data |
---|---|---|
e306af50 | 1 | # type: ignore |
9f95a23c | 2 | import mock |
f91f0fd5 | 3 | from mock import patch |
9f95a23c TL |
4 | import os |
5 | import sys | |
6 | import unittest | |
7 | ||
8 | import pytest | |
9 | ||
f91f0fd5 | 10 | with patch('builtins.open', create=True): |
9f95a23c TL |
11 | from importlib.machinery import SourceFileLoader |
12 | cd = SourceFileLoader('cephadm', 'cephadm').load_module() | |
9f95a23c TL |
13 | |
14 | class 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 | """ | |
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 | ]) | |
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 | |
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 | ||
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 = ''' | |
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 | ||
f91f0fd5 TL |
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 | ]) |