]>
Commit | Line | Data |
---|---|---|
e306af50 | 1 | # type: ignore |
b3b6e05e TL |
2 | |
3 | import errno | |
9f95a23c TL |
4 | import mock |
5 | import os | |
b3b6e05e TL |
6 | import pytest |
7 | import socket | |
9f95a23c | 8 | import sys |
f67539c2 | 9 | import time |
b3b6e05e TL |
10 | import threading |
11 | import unittest | |
12 | ||
f67539c2 TL |
13 | from http.server import HTTPServer |
14 | from urllib.request import Request, urlopen | |
15 | from urllib.error import HTTPError | |
9f95a23c | 16 | |
b3b6e05e | 17 | from typing import List, Optional |
9f95a23c | 18 | |
b3b6e05e TL |
19 | from .fixtures import ( |
20 | cephadm_fs, | |
21 | exporter, | |
22 | mock_docker, | |
23 | mock_podman, | |
24 | with_cephadm_ctx, | |
25 | ) | |
f67539c2 | 26 | |
b3b6e05e TL |
27 | |
28 | with mock.patch('builtins.open', create=True): | |
9f95a23c TL |
29 | from importlib.machinery import SourceFileLoader |
30 | cd = SourceFileLoader('cephadm', 'cephadm').load_module() | |
9f95a23c | 31 | |
f67539c2 | 32 | |
b3b6e05e | 33 | class TestCephAdm(object): |
f67539c2 TL |
34 | |
35 | def test_docker_unit_file(self): | |
36 | ctx = mock.Mock() | |
b3b6e05e | 37 | ctx.container_engine = mock_docker() |
f67539c2 TL |
38 | r = cd.get_unit_file(ctx, '9b9d7609-f4d5-4aba-94c8-effa764d96c9') |
39 | assert 'Requires=docker.service' in r | |
b3b6e05e | 40 | ctx.container_engine = mock_podman() |
f67539c2 TL |
41 | r = cd.get_unit_file(ctx, '9b9d7609-f4d5-4aba-94c8-effa764d96c9') |
42 | assert 'Requires=docker.service' not in r | |
43 | ||
44 | @mock.patch('cephadm.logger') | |
45 | def test_attempt_bind(self, logger): | |
46 | ctx = None | |
47 | address = None | |
48 | port = 0 | |
49 | ||
50 | def os_error(errno): | |
51 | _os_error = OSError() | |
52 | _os_error.errno = errno | |
53 | return _os_error | |
54 | ||
55 | for side_effect, expected_exception in ( | |
56 | (os_error(errno.EADDRINUSE), cd.PortOccupiedError), | |
b3b6e05e TL |
57 | (os_error(errno.EAFNOSUPPORT), cd.Error), |
58 | (os_error(errno.EADDRNOTAVAIL), cd.Error), | |
f67539c2 TL |
59 | (None, None), |
60 | ): | |
61 | _socket = mock.Mock() | |
62 | _socket.bind.side_effect = side_effect | |
63 | try: | |
64 | cd.attempt_bind(ctx, _socket, address, port) | |
65 | except Exception as e: | |
66 | assert isinstance(e, expected_exception) | |
67 | else: | |
68 | if expected_exception is not None: | |
69 | assert False | |
70 | ||
71 | @mock.patch('cephadm.attempt_bind') | |
72 | @mock.patch('cephadm.logger') | |
73 | def test_port_in_use(self, logger, attempt_bind): | |
74 | empty_ctx = None | |
75 | ||
76 | assert cd.port_in_use(empty_ctx, 9100) == False | |
77 | ||
78 | attempt_bind.side_effect = cd.PortOccupiedError('msg') | |
79 | assert cd.port_in_use(empty_ctx, 9100) == True | |
80 | ||
81 | os_error = OSError() | |
82 | os_error.errno = errno.EADDRNOTAVAIL | |
83 | attempt_bind.side_effect = os_error | |
84 | assert cd.port_in_use(empty_ctx, 9100) == False | |
85 | ||
86 | os_error = OSError() | |
87 | os_error.errno = errno.EAFNOSUPPORT | |
88 | attempt_bind.side_effect = os_error | |
89 | assert cd.port_in_use(empty_ctx, 9100) == False | |
90 | ||
91 | @mock.patch('socket.socket') | |
92 | @mock.patch('cephadm.logger') | |
93 | def test_check_ip_port_success(self, logger, _socket): | |
94 | ctx = mock.Mock() | |
95 | ctx.skip_ping_check = False # enables executing port check with `check_ip_port` | |
96 | ||
97 | for address, address_family in ( | |
98 | ('0.0.0.0', socket.AF_INET), | |
99 | ('::', socket.AF_INET6), | |
100 | ): | |
101 | try: | |
102 | cd.check_ip_port(ctx, address, 9100) | |
103 | except: | |
104 | assert False | |
105 | else: | |
b3b6e05e | 106 | assert _socket.call_args == mock.call(address_family, socket.SOCK_STREAM) |
f67539c2 TL |
107 | |
108 | @mock.patch('socket.socket') | |
109 | @mock.patch('cephadm.logger') | |
110 | def test_check_ip_port_failure(self, logger, _socket): | |
111 | ctx = mock.Mock() | |
112 | ctx.skip_ping_check = False # enables executing port check with `check_ip_port` | |
113 | ||
114 | def os_error(errno): | |
115 | _os_error = OSError() | |
116 | _os_error.errno = errno | |
117 | return _os_error | |
118 | ||
119 | for address, address_family in ( | |
120 | ('0.0.0.0', socket.AF_INET), | |
121 | ('::', socket.AF_INET6), | |
122 | ): | |
123 | for side_effect, expected_exception in ( | |
124 | (os_error(errno.EADDRINUSE), cd.PortOccupiedError), | |
b3b6e05e TL |
125 | (os_error(errno.EADDRNOTAVAIL), cd.Error), |
126 | (os_error(errno.EAFNOSUPPORT), cd.Error), | |
f67539c2 TL |
127 | (None, None), |
128 | ): | |
129 | mock_socket_obj = mock.Mock() | |
130 | mock_socket_obj.bind.side_effect = side_effect | |
131 | _socket.return_value = mock_socket_obj | |
132 | try: | |
133 | cd.check_ip_port(ctx, address, 9100) | |
134 | except Exception as e: | |
135 | assert isinstance(e, expected_exception) | |
136 | else: | |
137 | if side_effect is not None: | |
138 | assert False | |
139 | ||
140 | ||
f91f0fd5 | 141 | def test_is_not_fsid(self): |
9f95a23c TL |
142 | assert not cd.is_fsid('no-uuid') |
143 | ||
f91f0fd5 TL |
144 | def test_is_fsid(self): |
145 | assert cd.is_fsid('e863154d-33c7-4350-bca5-921e0467e55b') | |
146 | ||
9f95a23c TL |
147 | def test__get_parser_image(self): |
148 | args = cd._parse_args(['--image', 'foo', 'version']) | |
149 | assert args.image == 'foo' | |
150 | ||
151 | def test_CustomValidation(self): | |
152 | assert cd._parse_args(['deploy', '--name', 'mon.a', '--fsid', 'fsid']) | |
153 | ||
154 | with pytest.raises(SystemExit): | |
155 | cd._parse_args(['deploy', '--name', 'wrong', '--fsid', 'fsid']) | |
156 | ||
157 | @pytest.mark.parametrize("test_input, expected", [ | |
f67539c2 TL |
158 | ("1.6.2", (1,6,2)), |
159 | ("1.6.2-stable2", (1,6,2)), | |
9f95a23c TL |
160 | ]) |
161 | def test_parse_podman_version(self, test_input, expected): | |
162 | assert cd._parse_podman_version(test_input) == expected | |
163 | ||
164 | def test_parse_podman_version_invalid(self): | |
165 | with pytest.raises(ValueError) as res: | |
f67539c2 | 166 | cd._parse_podman_version('inval.id') |
9f95a23c TL |
167 | assert 'inval' in str(res.value) |
168 | ||
169 | @pytest.mark.parametrize("test_input, expected", [ | |
170 | ( | |
171 | """ | |
172 | default via 192.168.178.1 dev enxd89ef3f34260 proto dhcp metric 100 | |
173 | 10.0.0.0/8 via 10.4.0.1 dev tun0 proto static metric 50 | |
174 | 10.3.0.0/21 via 10.4.0.1 dev tun0 proto static metric 50 | |
175 | 10.4.0.1 dev tun0 proto kernel scope link src 10.4.0.2 metric 50 | |
176 | 137.1.0.0/16 via 10.4.0.1 dev tun0 proto static metric 50 | |
177 | 138.1.0.0/16 via 10.4.0.1 dev tun0 proto static metric 50 | |
178 | 139.1.0.0/16 via 10.4.0.1 dev tun0 proto static metric 50 | |
179 | 140.1.0.0/17 via 10.4.0.1 dev tun0 proto static metric 50 | |
180 | 141.1.0.0/16 via 10.4.0.1 dev tun0 proto static metric 50 | |
181 | 169.254.0.0/16 dev docker0 scope link metric 1000 | |
182 | 172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 | |
183 | 192.168.39.0/24 dev virbr1 proto kernel scope link src 192.168.39.1 linkdown | |
184 | 192.168.122.0/24 dev virbr0 proto kernel scope link src 192.168.122.1 linkdown | |
185 | 192.168.178.0/24 dev enxd89ef3f34260 proto kernel scope link src 192.168.178.28 metric 100 | |
186 | 192.168.178.1 dev enxd89ef3f34260 proto static scope link metric 100 | |
187 | 195.135.221.12 via 192.168.178.1 dev enxd89ef3f34260 proto static metric 100 | |
188 | """, | |
189 | { | |
f67539c2 TL |
190 | '10.4.0.1': {'tun0': ['10.4.0.2']}, |
191 | '172.17.0.0/16': {'docker0': ['172.17.0.1']}, | |
192 | '192.168.39.0/24': {'virbr1': ['192.168.39.1']}, | |
193 | '192.168.122.0/24': {'virbr0': ['192.168.122.1']}, | |
194 | '192.168.178.0/24': {'enxd89ef3f34260': ['192.168.178.28']} | |
9f95a23c TL |
195 | } |
196 | ), ( | |
197 | """ | |
198 | default via 10.3.64.1 dev eno1 proto static metric 100 | |
199 | 10.3.64.0/24 dev eno1 proto kernel scope link src 10.3.64.23 metric 100 | |
200 | 10.3.64.0/24 dev eno1 proto kernel scope link src 10.3.64.27 metric 100 | |
201 | 10.88.0.0/16 dev cni-podman0 proto kernel scope link src 10.88.0.1 linkdown | |
202 | 172.21.0.0/20 via 172.21.3.189 dev tun0 | |
203 | 172.21.1.0/20 via 172.21.3.189 dev tun0 | |
204 | 172.21.2.1 via 172.21.3.189 dev tun0 | |
205 | 172.21.3.1 dev tun0 proto kernel scope link src 172.21.3.2 | |
206 | 172.21.4.0/24 via 172.21.3.1 dev tun0 | |
207 | 172.21.5.0/24 via 172.21.3.1 dev tun0 | |
208 | 172.21.6.0/24 via 172.21.3.1 dev tun0 | |
209 | 172.21.7.0/24 via 172.21.3.1 dev tun0 | |
210 | 192.168.122.0/24 dev virbr0 proto kernel scope link src 192.168.122.1 linkdown | |
211 | """, | |
212 | { | |
f67539c2 TL |
213 | '10.3.64.0/24': {'eno1': ['10.3.64.23', '10.3.64.27']}, |
214 | '10.88.0.0/16': {'cni-podman0': ['10.88.0.1']}, | |
215 | '172.21.3.1': {'tun0': ['172.21.3.2']}, | |
216 | '192.168.122.0/24': {'virbr0': ['192.168.122.1']}} | |
9f95a23c TL |
217 | ), |
218 | ]) | |
f6b5b4d7 TL |
219 | def test_parse_ipv4_route(self, test_input, expected): |
220 | assert cd._parse_ipv4_route(test_input) == expected | |
221 | ||
222 | @pytest.mark.parametrize("test_routes, test_ips, expected", [ | |
223 | ( | |
224 | """ | |
225 | ::1 dev lo proto kernel metric 256 pref medium | |
f67539c2 TL |
226 | fe80::/64 dev eno1 proto kernel metric 100 pref medium |
227 | fe80::/64 dev br-3d443496454c proto kernel metric 256 linkdown pref medium | |
f6b5b4d7 | 228 | fe80::/64 dev tun0 proto kernel metric 256 pref medium |
f67539c2 TL |
229 | fe80::/64 dev br-4355f5dbb528 proto kernel metric 256 pref medium |
230 | fe80::/64 dev docker0 proto kernel metric 256 linkdown pref medium | |
231 | fe80::/64 dev cni-podman0 proto kernel metric 256 linkdown pref medium | |
232 | fe80::/64 dev veth88ba1e8 proto kernel metric 256 pref medium | |
233 | fe80::/64 dev vethb6e5fc7 proto kernel metric 256 pref medium | |
234 | fe80::/64 dev vethaddb245 proto kernel metric 256 pref medium | |
235 | fe80::/64 dev vethbd14d6b proto kernel metric 256 pref medium | |
236 | fe80::/64 dev veth13e8fd2 proto kernel metric 256 pref medium | |
237 | fe80::/64 dev veth1d3aa9e proto kernel metric 256 pref medium | |
238 | fe80::/64 dev vethe485ca9 proto kernel metric 256 pref medium | |
f6b5b4d7 TL |
239 | """, |
240 | """ | |
241 | 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 state UNKNOWN qlen 1000 | |
f67539c2 TL |
242 | inet6 ::1/128 scope host |
243 | valid_lft forever preferred_lft forever | |
244 | 2: eno1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000 | |
245 | inet6 fe80::225:90ff:fee5:26e8/64 scope link noprefixroute | |
246 | valid_lft forever preferred_lft forever | |
247 | 6: br-3d443496454c: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 state DOWN | |
248 | inet6 fe80::42:23ff:fe9d:ee4/64 scope link | |
249 | valid_lft forever preferred_lft forever | |
250 | 7: br-4355f5dbb528: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP | |
251 | inet6 fe80::42:6eff:fe35:41fe/64 scope link | |
252 | valid_lft forever preferred_lft forever | |
253 | 8: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 state DOWN | |
254 | inet6 fe80::42:faff:fee6:40a0/64 scope link | |
f6b5b4d7 | 255 | valid_lft forever preferred_lft forever |
f67539c2 TL |
256 | 11: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 state UNKNOWN qlen 100 |
257 | inet6 fe80::98a6:733e:dafd:350/64 scope link stable-privacy | |
f6b5b4d7 | 258 | valid_lft forever preferred_lft forever |
f67539c2 TL |
259 | 28: cni-podman0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 state DOWN qlen 1000 |
260 | inet6 fe80::3449:cbff:fe89:b87e/64 scope link | |
261 | valid_lft forever preferred_lft forever | |
262 | 31: vethaddb245@if30: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP | |
263 | inet6 fe80::90f7:3eff:feed:a6bb/64 scope link | |
264 | valid_lft forever preferred_lft forever | |
265 | 33: veth88ba1e8@if32: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP | |
266 | inet6 fe80::d:f5ff:fe73:8c82/64 scope link | |
267 | valid_lft forever preferred_lft forever | |
268 | 35: vethbd14d6b@if34: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP | |
269 | inet6 fe80::b44f:8ff:fe6f:813d/64 scope link | |
270 | valid_lft forever preferred_lft forever | |
271 | 37: vethb6e5fc7@if36: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP | |
272 | inet6 fe80::4869:c6ff:feaa:8afe/64 scope link | |
273 | valid_lft forever preferred_lft forever | |
274 | 39: veth13e8fd2@if38: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP | |
275 | inet6 fe80::78f4:71ff:fefe:eb40/64 scope link | |
276 | valid_lft forever preferred_lft forever | |
277 | 41: veth1d3aa9e@if40: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP | |
278 | inet6 fe80::24bd:88ff:fe28:5b18/64 scope link | |
279 | valid_lft forever preferred_lft forever | |
280 | 43: vethe485ca9@if42: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP | |
281 | inet6 fe80::6425:87ff:fe42:b9f0/64 scope link | |
f6b5b4d7 TL |
282 | valid_lft forever preferred_lft forever |
283 | """, | |
284 | { | |
f67539c2 TL |
285 | "fe80::/64": { |
286 | "eno1": [ | |
287 | "fe80::225:90ff:fee5:26e8" | |
288 | ], | |
289 | "br-3d443496454c": [ | |
290 | "fe80::42:23ff:fe9d:ee4" | |
291 | ], | |
292 | "tun0": [ | |
293 | "fe80::98a6:733e:dafd:350" | |
294 | ], | |
295 | "br-4355f5dbb528": [ | |
296 | "fe80::42:6eff:fe35:41fe" | |
297 | ], | |
298 | "docker0": [ | |
299 | "fe80::42:faff:fee6:40a0" | |
300 | ], | |
301 | "cni-podman0": [ | |
302 | "fe80::3449:cbff:fe89:b87e" | |
303 | ], | |
304 | "veth88ba1e8": [ | |
305 | "fe80::d:f5ff:fe73:8c82" | |
306 | ], | |
307 | "vethb6e5fc7": [ | |
308 | "fe80::4869:c6ff:feaa:8afe" | |
309 | ], | |
310 | "vethaddb245": [ | |
311 | "fe80::90f7:3eff:feed:a6bb" | |
312 | ], | |
313 | "vethbd14d6b": [ | |
314 | "fe80::b44f:8ff:fe6f:813d" | |
315 | ], | |
316 | "veth13e8fd2": [ | |
317 | "fe80::78f4:71ff:fefe:eb40" | |
318 | ], | |
319 | "veth1d3aa9e": [ | |
320 | "fe80::24bd:88ff:fe28:5b18" | |
321 | ], | |
322 | "vethe485ca9": [ | |
323 | "fe80::6425:87ff:fe42:b9f0" | |
324 | ] | |
325 | } | |
f6b5b4d7 | 326 | } |
f67539c2 TL |
327 | ), |
328 | ( | |
329 | """ | |
330 | ::1 dev lo proto kernel metric 256 pref medium | |
331 | 2001:1458:301:eb::100:1a dev ens20f0 proto kernel metric 100 pref medium | |
332 | 2001:1458:301:eb::/64 dev ens20f0 proto ra metric 100 pref medium | |
333 | fd01:1458:304:5e::/64 dev ens20f0 proto ra metric 100 pref medium | |
334 | fe80::/64 dev ens20f0 proto kernel metric 100 pref medium | |
335 | default proto ra metric 100 | |
336 | nexthop via fe80::46ec:ce00:b8a0:d3c8 dev ens20f0 weight 1 | |
337 | nexthop via fe80::46ec:ce00:b8a2:33c8 dev ens20f0 weight 1 pref medium | |
338 | """, | |
339 | """ | |
340 | 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 state UNKNOWN qlen 1000 | |
341 | inet6 ::1/128 scope host | |
342 | valid_lft forever preferred_lft forever | |
343 | 2: ens20f0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000 | |
344 | inet6 2001:1458:301:eb::100:1a/128 scope global dynamic noprefixroute | |
345 | valid_lft 590879sec preferred_lft 590879sec | |
346 | inet6 fe80::2e60:cff:fef8:da41/64 scope link noprefixroute | |
347 | valid_lft forever preferred_lft forever | |
348 | """, | |
349 | { | |
350 | '2001:1458:301:eb::/64': { | |
351 | 'ens20f0': [ | |
352 | '2001:1458:301:eb::100:1a' | |
353 | ], | |
354 | }, | |
355 | 'fe80::/64': { | |
356 | 'ens20f0': ['fe80::2e60:cff:fef8:da41'], | |
357 | }, | |
358 | 'fd01:1458:304:5e::/64': { | |
359 | 'ens20f0': [] | |
360 | }, | |
361 | } | |
362 | ), | |
363 | ]) | |
f6b5b4d7 TL |
364 | def test_parse_ipv6_route(self, test_routes, test_ips, expected): |
365 | assert cd._parse_ipv6_route(test_routes, test_ips) == expected | |
366 | ||
367 | def test_is_ipv6(self): | |
368 | cd.logger = mock.Mock() | |
369 | for good in ("[::1]", "::1", | |
370 | "fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"): | |
371 | assert cd.is_ipv6(good) | |
372 | for bad in ("127.0.0.1", | |
373 | "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffg", | |
374 | "1:2:3:4:5:6:7:8:9", "fd00::1::1", "[fg::1]"): | |
375 | assert not cd.is_ipv6(bad) | |
376 | ||
377 | def test_unwrap_ipv6(self): | |
378 | def unwrap_test(address, expected): | |
379 | assert cd.unwrap_ipv6(address) == expected | |
380 | ||
381 | tests = [ | |
382 | ('::1', '::1'), ('[::1]', '::1'), | |
383 | ('[fde4:8dba:82e1:0:5054:ff:fe6a:357]', 'fde4:8dba:82e1:0:5054:ff:fe6a:357'), | |
384 | ('can actually be any string', 'can actually be any string'), | |
385 | ('[but needs to be stripped] ', '[but needs to be stripped] ')] | |
386 | for address, expected in tests: | |
387 | unwrap_test(address, expected) | |
388 | ||
f91f0fd5 TL |
389 | def test_wrap_ipv6(self): |
390 | def wrap_test(address, expected): | |
391 | assert cd.wrap_ipv6(address) == expected | |
392 | ||
393 | tests = [ | |
394 | ('::1', '[::1]'), ('[::1]', '[::1]'), | |
395 | ('fde4:8dba:82e1:0:5054:ff:fe6a:357', | |
396 | '[fde4:8dba:82e1:0:5054:ff:fe6a:357]'), | |
397 | ('myhost.example.com', 'myhost.example.com'), | |
398 | ('192.168.0.1', '192.168.0.1'), | |
399 | ('', ''), ('fd00::1::1', 'fd00::1::1')] | |
400 | for address, expected in tests: | |
401 | wrap_test(address, expected) | |
402 | ||
f6b5b4d7 TL |
403 | @mock.patch('cephadm.call_throws') |
404 | @mock.patch('cephadm.get_parm') | |
405 | def test_registry_login(self, get_parm, call_throws): | |
406 | ||
407 | # test normal valid login with url, username and password specified | |
408 | call_throws.return_value = '', '', 0 | |
b3b6e05e | 409 | ctx: cd.CephadmContext = cd.cephadm_init_ctx( |
f67539c2 TL |
410 | ['registry-login', '--registry-url', 'sample-url', |
411 | '--registry-username', 'sample-user', '--registry-password', | |
412 | 'sample-pass']) | |
b3b6e05e | 413 | ctx.container_engine = mock_docker() |
f67539c2 | 414 | retval = cd.command_registry_login(ctx) |
f6b5b4d7 TL |
415 | assert retval == 0 |
416 | ||
417 | # test bad login attempt with invalid arguments given | |
b3b6e05e | 418 | ctx: cd.CephadmContext = cd.cephadm_init_ctx( |
f67539c2 | 419 | ['registry-login', '--registry-url', 'bad-args-url']) |
f6b5b4d7 | 420 | with pytest.raises(Exception) as e: |
f67539c2 | 421 | assert cd.command_registry_login(ctx) |
f6b5b4d7 TL |
422 | assert str(e.value) == ('Invalid custom registry arguments received. To login to a custom registry include ' |
423 | '--registry-url, --registry-username and --registry-password options or --registry-json option') | |
424 | ||
425 | # test normal valid login with json file | |
426 | get_parm.return_value = {"url": "sample-url", "username": "sample-username", "password": "sample-password"} | |
b3b6e05e | 427 | ctx: cd.CephadmContext = cd.cephadm_init_ctx( |
f67539c2 | 428 | ['registry-login', '--registry-json', 'sample-json']) |
b3b6e05e | 429 | ctx.container_engine = mock_docker() |
f67539c2 | 430 | retval = cd.command_registry_login(ctx) |
f6b5b4d7 TL |
431 | assert retval == 0 |
432 | ||
433 | # test bad login attempt with bad json file | |
434 | get_parm.return_value = {"bad-json": "bad-json"} | |
b3b6e05e | 435 | ctx: cd.CephadmContext = cd.cephadm_init_ctx( |
f67539c2 | 436 | ['registry-login', '--registry-json', 'sample-json']) |
f6b5b4d7 | 437 | with pytest.raises(Exception) as e: |
f67539c2 | 438 | assert cd.command_registry_login(ctx) |
f6b5b4d7 TL |
439 | assert str(e.value) == ("json provided for custom registry login did not include all necessary fields. " |
440 | "Please setup json file as\n" | |
441 | "{\n" | |
442 | " \"url\": \"REGISTRY_URL\",\n" | |
443 | " \"username\": \"REGISTRY_USERNAME\",\n" | |
444 | " \"password\": \"REGISTRY_PASSWORD\"\n" | |
445 | "}\n") | |
446 | ||
447 | # test login attempt with valid arguments where login command fails | |
448 | call_throws.side_effect = Exception | |
b3b6e05e | 449 | ctx: cd.CephadmContext = cd.cephadm_init_ctx( |
f67539c2 TL |
450 | ['registry-login', '--registry-url', 'sample-url', |
451 | '--registry-username', 'sample-user', '--registry-password', | |
452 | 'sample-pass']) | |
f6b5b4d7 | 453 | with pytest.raises(Exception) as e: |
f67539c2 | 454 | cd.command_registry_login(ctx) |
f6b5b4d7 | 455 | assert str(e.value) == "Failed to login to custom registry @ sample-url as sample-user with given password" |
f91f0fd5 TL |
456 | |
457 | def test_get_image_info_from_inspect(self): | |
458 | # podman | |
cd265ab1 | 459 | out = """204a01f9b0b6710dd0c0af7f37ce7139c47ff0f0105d778d7104c69282dfbbf1,[docker.io/ceph/ceph@sha256:1cc9b824e1b076cdff52a9aa3f0cc8557d879fb2fbbba0cafed970aca59a3992]""" |
f91f0fd5 | 460 | r = cd.get_image_info_from_inspect(out, 'registry/ceph/ceph:latest') |
f67539c2 | 461 | print(r) |
f91f0fd5 TL |
462 | assert r == { |
463 | 'image_id': '204a01f9b0b6710dd0c0af7f37ce7139c47ff0f0105d778d7104c69282dfbbf1', | |
f67539c2 | 464 | 'repo_digests': ['docker.io/ceph/ceph@sha256:1cc9b824e1b076cdff52a9aa3f0cc8557d879fb2fbbba0cafed970aca59a3992'] |
f91f0fd5 TL |
465 | } |
466 | ||
467 | # docker | |
cd265ab1 | 468 | out = """sha256:16f4549cf7a8f112bbebf7946749e961fbbd1b0838627fe619aab16bc17ce552,[quay.ceph.io/ceph-ci/ceph@sha256:4e13da36c1bd6780b312a985410ae678984c37e6a9493a74c87e4a50b9bda41f]""" |
f91f0fd5 TL |
469 | r = cd.get_image_info_from_inspect(out, 'registry/ceph/ceph:latest') |
470 | assert r == { | |
471 | 'image_id': '16f4549cf7a8f112bbebf7946749e961fbbd1b0838627fe619aab16bc17ce552', | |
f67539c2 | 472 | 'repo_digests': ['quay.ceph.io/ceph-ci/ceph@sha256:4e13da36c1bd6780b312a985410ae678984c37e6a9493a74c87e4a50b9bda41f'] |
f91f0fd5 TL |
473 | } |
474 | ||
f67539c2 TL |
475 | # multiple digests (podman) |
476 | out = """e935122ab143a64d92ed1fbb27d030cf6e2f0258207be1baf1b509c466aeeb42,[docker.io/prom/prometheus@sha256:e4ca62c0d62f3e886e684806dfe9d4e0cda60d54986898173c1083856cfda0f4 docker.io/prom/prometheus@sha256:efd99a6be65885c07c559679a0df4ec709604bcdd8cd83f0d00a1a683b28fb6a]""" | |
477 | r = cd.get_image_info_from_inspect(out, 'registry/prom/prometheus:latest') | |
478 | assert r == { | |
479 | 'image_id': 'e935122ab143a64d92ed1fbb27d030cf6e2f0258207be1baf1b509c466aeeb42', | |
480 | 'repo_digests': [ | |
481 | 'docker.io/prom/prometheus@sha256:e4ca62c0d62f3e886e684806dfe9d4e0cda60d54986898173c1083856cfda0f4', | |
482 | 'docker.io/prom/prometheus@sha256:efd99a6be65885c07c559679a0df4ec709604bcdd8cd83f0d00a1a683b28fb6a', | |
483 | ] | |
484 | } | |
485 | ||
486 | ||
f91f0fd5 TL |
487 | def test_dict_get(self): |
488 | result = cd.dict_get({'a': 1}, 'a', require=True) | |
489 | assert result == 1 | |
490 | result = cd.dict_get({'a': 1}, 'b') | |
491 | assert result is None | |
492 | result = cd.dict_get({'a': 1}, 'b', default=2) | |
493 | assert result == 2 | |
494 | ||
495 | def test_dict_get_error(self): | |
496 | with pytest.raises(cd.Error): | |
497 | cd.dict_get({'a': 1}, 'b', require=True) | |
498 | ||
499 | def test_dict_get_join(self): | |
500 | result = cd.dict_get_join({'foo': ['a', 'b']}, 'foo') | |
501 | assert result == 'a\nb' | |
502 | result = cd.dict_get_join({'foo': [1, 2]}, 'foo') | |
503 | assert result == '1\n2' | |
504 | result = cd.dict_get_join({'bar': 'a'}, 'bar') | |
505 | assert result == 'a' | |
506 | result = cd.dict_get_join({'a': 1}, 'a') | |
507 | assert result == 1 | |
508 | ||
adb31ebb TL |
509 | def test_last_local_images(self): |
510 | out = ''' | |
511 | docker.io/ceph/daemon-base@ | |
512 | docker.io/ceph/ceph:v15.2.5 | |
513 | docker.io/ceph/daemon-base:octopus | |
514 | ''' | |
515 | image = cd._filter_last_local_ceph_image(out) | |
516 | assert image == 'docker.io/ceph/ceph:v15.2.5' | |
517 | ||
b3b6e05e TL |
518 | def test_normalize_image_digest(self): |
519 | s = 'myhostname:5000/ceph/ceph@sha256:753886ad9049004395ae990fbb9b096923b5a518b819283141ee8716ddf55ad1' | |
520 | assert cd.normalize_image_digest(s) == s | |
521 | ||
522 | s = 'ceph/ceph:latest' | |
523 | assert cd.normalize_image_digest(s) == f'{cd.DEFAULT_REGISTRY}/{s}' | |
f91f0fd5 TL |
524 | |
525 | class TestCustomContainer(unittest.TestCase): | |
526 | cc: cd.CustomContainer | |
527 | ||
528 | def setUp(self): | |
529 | self.cc = cd.CustomContainer( | |
530 | 'e863154d-33c7-4350-bca5-921e0467e55b', | |
531 | 'container', | |
532 | config_json={ | |
533 | 'entrypoint': 'bash', | |
534 | 'gid': 1000, | |
535 | 'args': [ | |
536 | '--no-healthcheck', | |
537 | '-p 6800:6800' | |
538 | ], | |
539 | 'envs': ['SECRET=password'], | |
540 | 'ports': [8080, 8443], | |
541 | 'volume_mounts': { | |
542 | '/CONFIG_DIR': '/foo/conf', | |
543 | 'bar/config': '/bar:ro' | |
544 | }, | |
545 | 'bind_mounts': [ | |
546 | [ | |
547 | 'type=bind', | |
548 | 'source=/CONFIG_DIR', | |
549 | 'destination=/foo/conf', | |
550 | '' | |
551 | ], | |
552 | [ | |
553 | 'type=bind', | |
554 | 'source=bar/config', | |
555 | 'destination=/bar:ro', | |
556 | 'ro=true' | |
557 | ] | |
558 | ] | |
559 | }, | |
560 | image='docker.io/library/hello-world:latest' | |
561 | ) | |
562 | ||
563 | def test_entrypoint(self): | |
564 | self.assertEqual(self.cc.entrypoint, 'bash') | |
565 | ||
566 | def test_uid_gid(self): | |
567 | self.assertEqual(self.cc.uid, 65534) | |
568 | self.assertEqual(self.cc.gid, 1000) | |
569 | ||
570 | def test_ports(self): | |
571 | self.assertEqual(self.cc.ports, [8080, 8443]) | |
572 | ||
573 | def test_get_container_args(self): | |
574 | result = self.cc.get_container_args() | |
575 | self.assertEqual(result, [ | |
576 | '--no-healthcheck', | |
577 | '-p 6800:6800' | |
578 | ]) | |
579 | ||
580 | def test_get_container_envs(self): | |
581 | result = self.cc.get_container_envs() | |
582 | self.assertEqual(result, ['SECRET=password']) | |
583 | ||
584 | def test_get_container_mounts(self): | |
585 | result = self.cc.get_container_mounts('/xyz') | |
586 | self.assertDictEqual(result, { | |
587 | '/CONFIG_DIR': '/foo/conf', | |
588 | '/xyz/bar/config': '/bar:ro' | |
589 | }) | |
590 | ||
591 | def test_get_container_binds(self): | |
592 | result = self.cc.get_container_binds('/xyz') | |
593 | self.assertEqual(result, [ | |
594 | [ | |
595 | 'type=bind', | |
596 | 'source=/CONFIG_DIR', | |
597 | 'destination=/foo/conf', | |
598 | '' | |
599 | ], | |
600 | [ | |
601 | 'type=bind', | |
602 | 'source=/xyz/bar/config', | |
603 | 'destination=/bar:ro', | |
604 | 'ro=true' | |
605 | ] | |
606 | ]) | |
f67539c2 TL |
607 | |
608 | ||
609 | class TestCephadmExporter(object): | |
610 | exporter: cd.CephadmDaemon | |
611 | files_created: List[str] = [] | |
612 | crt = """-----BEGIN CERTIFICATE----- | |
613 | MIIC1zCCAb8CEFHoZE2MfUVzo53fzzBKAT0wDQYJKoZIhvcNAQENBQAwKjENMAsG | |
614 | A1UECgwEQ2VwaDEZMBcGA1UECwwQY2VwaGFkbS1leHBvcnRlcjAeFw0yMDExMjUy | |
615 | MzEwNTVaFw0zMDExMjMyMzEwNTVaMCoxDTALBgNVBAoMBENlcGgxGTAXBgNVBAsM | |
616 | EGNlcGhhZG0tZXhwb3J0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB | |
617 | AQCsTfcJcXbREqfx1zTUuEmK+lJn9WWjk0URRF1Z+QgPkascNdkX16PnvhbGwXmF | |
618 | BTdAcNl7V0U+z4EsGJ7hJsB7qTq6Rb6wNl7r0OxjeWOmB9xbF4Q/KR5yrbM1DA9A | |
619 | B5fNswrUXViku5Y2jlOAz+ZMBhYxMx0edqhxSn297j04Z6RF4Mvkc43v0FH7Ju7k | |
620 | O5+0VbdzcOdu37DFpoE4Ll2MZ/GuAHcJ8SD06sEdzFEjRCraav976743XcUlhZGX | |
621 | ZTTG/Zf/a+wuCjtMG3od7vRFfuRrM5oTE133DuQ5deR7ybcZNDyopDjHF8xB1bAk | |
622 | IOz4SbP6Q25K99Czm1K+3kMLAgMBAAEwDQYJKoZIhvcNAQENBQADggEBACmtvZb8 | |
623 | dJGHx/WC0/JHxnEJCJM2qnn87ELzbbIQL1w1Yb/I6JQYPgq+WiQPaHaLL9eYsm0l | |
624 | dFwvrh+WC0JpXDfADnUnkTSB/WpZ2nC+2JxBptrQEuIcqNXpcJd0bKDiHunv04JI | |
625 | uEVpTAK05dBV38qNmIlu4HyB4OEnuQpyOr9xpIhdxuJ95O9K0j5BIw98ZaEwYNUP | |
626 | Rm3YlQwfS6R5xaBvL9kyfxyAD2joNj44q6w/5zj4egXVIA5VpkQm8DmMtu0Pd2NG | |
627 | dzfYRmqrDolh+rty8HiyIxzeDJQ5bj6LKbUkmABvX50nDySVyMfHmt461/n7W65R | |
628 | CHFLoOmfJJik+Uc=\n-----END CERTIFICATE----- | |
629 | """ | |
630 | key = """-----BEGIN PRIVATE KEY----- | |
631 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCsTfcJcXbREqfx | |
632 | 1zTUuEmK+lJn9WWjk0URRF1Z+QgPkascNdkX16PnvhbGwXmFBTdAcNl7V0U+z4Es | |
633 | GJ7hJsB7qTq6Rb6wNl7r0OxjeWOmB9xbF4Q/KR5yrbM1DA9AB5fNswrUXViku5Y2 | |
634 | jlOAz+ZMBhYxMx0edqhxSn297j04Z6RF4Mvkc43v0FH7Ju7kO5+0VbdzcOdu37DF | |
635 | poE4Ll2MZ/GuAHcJ8SD06sEdzFEjRCraav976743XcUlhZGXZTTG/Zf/a+wuCjtM | |
636 | G3od7vRFfuRrM5oTE133DuQ5deR7ybcZNDyopDjHF8xB1bAkIOz4SbP6Q25K99Cz | |
637 | m1K+3kMLAgMBAAECggEASnAwToMXWsGdjqxzpYasNv9oBIOO0nk4OHp5ffpJUjiT | |
638 | XM+ip1tA80g7HMjPD/mt4gge3NtaDgWlf4Bve0O7mnEE7x5cgFIs9eG/jkYOF9eD | |
639 | ilMBjivcfJywNDWujPH60iIMhqyBNEHaZl1ck+S9UJC8m6rCZLvMj40n/5riFfBy | |
640 | 1sjf2uOwcfWrjSj9Ju4wlMI6khSSz2aYC7glQQ/fo2+YArbEUcy60iloPQ6wEgZK | |
641 | okoVWZA9AehwLcnRjkwd9EVmMMtRGPE/AcP4s/kKA0tRDRicPLN727Ke/yxv+Ppo | |
642 | hbIZIcOn7soOFAENcodJ4YRSCd++QfCNaVAi7vwWWQKBgQDeBY4vvr+H0brbSjQg | |
643 | O7Fpqub/fxZY3UoHWDqWs2X4o3qhDqaTQODpuYtCm8YQE//55JoLWKAD0evq5dLS | |
644 | YLrtC1Vyxf+TA7opCUjWBe+liyndbJdB5q0zF7qdWUtQKGVSWyUWhK8gHa6M64fP | |
645 | oi83DD7F0OGusTWGtfbceErk/wKBgQDGrJLRo/5xnAH5VmPfNu+S6h0M2qM6CYwe | |
646 | Y5wHFG2uQQct73adf53SkhvZVmOzJsWQbVnlDOKMhqazcs+7VWRgO5X3naWVcctE | |
647 | Hggw9MgpbXAWFOI5sNYsCYE58E+fTHjE6O4A3MhMCsze+CIC3sKuPQBBiL9bWSOX | |
648 | 8POswqfl9QKBgDe/nVxPwTgRaaH2l/AgDQRDbY1qE+psZlJBzTRaB5jPM9ONIjaH | |
649 | a/JELLuk8a7H1tagmC2RK1zKMTriSnWY5FbxKZuQLAR2QyBavHdBNlOTBggbZD+f | |
650 | 9I2Hv8wSx95wxkBPsphc6Lxft5ya55czWjewU3LIaGK9DHuu5TWm3udxAoGBAJGP | |
651 | PsJ59KIoOwoDUYjpJv3sqPwR9CVBeXeKY3aMcQ+KdUgiejVKmsb8ZYsG0GUhsv3u | |
652 | ID7BAfsTbG9tXuVR2wjmnymcRwUHKnXtyvKTZVN06vpCsryx4zjAff2FI9ECpjke | |
653 | r8HSAK41+4QhKEoSC3C9IMLi/dBfrsRTtTSOKZVBAoGBAI2dl5HEIFpufaI4toWM | |
654 | LO5HFrlXgRDGoc/+Byr5/8ZZpYpU115Ol/q6M+l0koV2ygJ9jeJJEllFWykIDS6F | |
655 | XxazFI74swAqobHb2ZS/SLhoVxE82DdSeXrjkTvUjNtrW5zs1gIMKBR4nD6H8AqL | |
656 | iMN28C2bKGao5UHvdER1rGy7 | |
657 | -----END PRIVATE KEY----- | |
658 | """ | |
659 | token = "MyAccessToken" | |
660 | ||
661 | @classmethod | |
662 | def setup_class(cls): | |
663 | # create the ssl files | |
664 | fname = os.path.join(os.getcwd(), 'crt') | |
665 | with open(fname, 'w') as crt: | |
666 | crt.write(cls.crt) | |
667 | cls.files_created.append(fname) | |
668 | fname = os.path.join(os.getcwd(), 'key') | |
669 | with open(fname, 'w') as crt: | |
670 | crt.write(cls.key) | |
671 | cls.files_created.append(fname) | |
672 | fname = os.path.join(os.getcwd(), 'token') | |
673 | with open(fname, 'w') as crt: | |
674 | crt.write(cls.token) | |
675 | cls.files_created.append(fname) | |
676 | # start a simple http instance to test the requesthandler | |
677 | cls.server = HTTPServer(('0.0.0.0', 9443), cd.CephadmDaemonHandler) | |
678 | cls.server.cephadm_cache = cd.CephadmCache() | |
679 | cls.server.token = cls.token | |
680 | t = threading.Thread(target=cls.server.serve_forever) | |
681 | t.daemon = True | |
682 | t.start() | |
683 | ||
684 | @classmethod | |
685 | def teardown_class(cls): | |
686 | cls.server.shutdown() | |
687 | assert len(cls.files_created) > 0 | |
688 | for f in cls.files_created: | |
689 | os.remove(f) | |
690 | ||
691 | def setup_method(self): | |
692 | # re-init the cache for every test | |
693 | TestCephadmExporter.server.cephadm_cache = cd.CephadmCache() | |
694 | ||
695 | def teardown_method(self): | |
696 | pass | |
697 | ||
698 | def test_files_ready(self): | |
699 | assert os.path.exists(os.path.join(os.getcwd(), 'crt')) | |
700 | assert os.path.exists(os.path.join(os.getcwd(), 'key')) | |
701 | assert os.path.exists(os.path.join(os.getcwd(), 'token')) | |
702 | ||
703 | def test_can_run(self, exporter): | |
704 | assert exporter.can_run | |
705 | ||
706 | def test_token_valid(self, exporter): | |
707 | assert exporter.token == self.token | |
708 | ||
709 | def test_unit_name(self,exporter): | |
710 | assert exporter.unit_name | |
711 | assert exporter.unit_name == "ceph-foobar-cephadm-exporter.test.service" | |
712 | ||
713 | def test_unit_run(self,exporter): | |
714 | assert exporter.unit_run | |
715 | lines = exporter.unit_run.split('\n') | |
716 | assert len(lines) == 2 | |
717 | assert "cephadm exporter --fsid foobar --id test --port 9443 &" in lines[1] | |
718 | ||
719 | def test_binary_path(self, exporter): | |
720 | assert os.path.isfile(exporter.binary_path) | |
721 | ||
722 | def test_systemd_unit(self, exporter): | |
723 | assert exporter.unit_file | |
724 | ||
725 | def test_validate_passes(self, exporter): | |
726 | config = { | |
727 | "crt": self.crt, | |
728 | "key": self.key, | |
729 | "token": self.token, | |
730 | } | |
731 | cd.CephadmDaemon.validate_config(config) | |
732 | ||
733 | def test_validate_fails(self, exporter): | |
734 | config = { | |
735 | "key": self.key, | |
736 | "token": self.token, | |
737 | } | |
738 | with pytest.raises(cd.Error): | |
739 | cd.CephadmDaemon.validate_config(config) | |
740 | ||
741 | def test_port_active(self, exporter): | |
742 | assert exporter.port_active == True | |
743 | ||
744 | def test_rqst_health_200(self): | |
745 | hdrs={"Authorization":f"Bearer {TestCephadmExporter.token}"} | |
746 | req=Request("http://localhost:9443/v1/metadata/health",headers=hdrs) | |
747 | r = urlopen(req) | |
748 | assert r.status == 200 | |
749 | ||
750 | def test_rqst_all_inactive_500(self): | |
751 | hdrs={"Authorization":f"Bearer {TestCephadmExporter.token}"} | |
752 | req=Request("http://localhost:9443/v1/metadata",headers=hdrs) | |
753 | try: | |
754 | r = urlopen(req) | |
755 | except HTTPError as e: | |
756 | assert e.code == 500 | |
757 | ||
758 | def test_rqst_no_auth_401(self): | |
759 | req=Request("http://localhost:9443/v1/metadata") | |
760 | try: | |
761 | urlopen(req) | |
762 | except HTTPError as e: | |
763 | assert e.code == 401 | |
764 | ||
765 | def test_rqst_bad_auth_401(self): | |
766 | hdrs={"Authorization":f"Bearer BogusAuthToken"} | |
767 | req=Request("http://localhost:9443/v1/metadata",headers=hdrs) | |
768 | try: | |
769 | urlopen(req) | |
770 | except HTTPError as e: | |
771 | assert e.code == 401 | |
772 | ||
773 | def test_rqst_badURL_404(self): | |
774 | hdrs={"Authorization":f"Bearer {TestCephadmExporter.token}"} | |
775 | req=Request("http://localhost:9443/v1/metazoic",headers=hdrs) | |
776 | try: | |
777 | urlopen(req) | |
778 | except HTTPError as e: | |
779 | assert e.code == 404 | |
780 | ||
781 | def test_rqst_inactive_task_204(self): | |
782 | # all tasks initialise as inactive, and then 'go' active as their thread starts | |
783 | # so we can pick any task to check for an inactive response (no content) | |
784 | hdrs={"Authorization":f"Bearer {TestCephadmExporter.token}"} | |
785 | req=Request("http://localhost:9443/v1/metadata/disks",headers=hdrs) | |
786 | r = urlopen(req) | |
787 | assert r.status == 204 | |
788 | ||
789 | def test_rqst_active_task_200(self): | |
790 | TestCephadmExporter.server.cephadm_cache.tasks['host'] = 'active' | |
791 | hdrs={"Authorization":f"Bearer {TestCephadmExporter.token}"} | |
792 | req=Request("http://localhost:9443/v1/metadata/host",headers=hdrs) | |
793 | r = urlopen(req) | |
794 | assert r.status == 200 | |
795 | ||
796 | def test_rqst_all_206(self): | |
797 | TestCephadmExporter.server.cephadm_cache.tasks['disks'] = 'active' | |
798 | hdrs={"Authorization":f"Bearer {TestCephadmExporter.token}"} | |
799 | req=Request("http://localhost:9443/v1/metadata",headers=hdrs) | |
800 | r = urlopen(req) | |
801 | assert r.status == 206 | |
802 | ||
803 | def test_rqst_disks_200(self): | |
804 | TestCephadmExporter.server.cephadm_cache.tasks['disks'] = 'active' | |
805 | hdrs={"Authorization":f"Bearer {TestCephadmExporter.token}"} | |
806 | req=Request("http://localhost:9443/v1/metadata/disks",headers=hdrs) | |
807 | r = urlopen(req) | |
808 | assert r.status == 200 | |
809 | ||
810 | def test_thread_exception(self, exporter): | |
811 | # run is patched to invoke a mocked scrape_host thread that will raise so | |
812 | # we check here that the exception handler updates the cache object as we'd | |
813 | # expect with the error | |
814 | exporter.run() | |
815 | assert exporter.cephadm_cache.host['scrape_errors'] | |
816 | assert exporter.cephadm_cache.host['scrape_errors'] == ['ValueError exception: wah'] | |
817 | assert exporter.cephadm_cache.errors == ['host thread stopped'] | |
818 | ||
819 | # Test the requesthandler does the right thing with invalid methods... | |
820 | # ie. return a "501" - Not Implemented / Unsupported Method | |
821 | def test_invalid_method_HEAD(self): | |
822 | hdrs={"Authorization":f"Bearer {TestCephadmExporter.token}"} | |
823 | req=Request("http://localhost:9443/v1/metadata/health",headers=hdrs, method="HEAD") | |
824 | with pytest.raises(HTTPError, match=r"HTTP Error 501: .*") as e: | |
825 | urlopen(req) | |
826 | ||
827 | def test_invalid_method_DELETE(self): | |
828 | hdrs={"Authorization":f"Bearer {TestCephadmExporter.token}"} | |
829 | req=Request("http://localhost:9443/v1/metadata/health",headers=hdrs, method="DELETE") | |
830 | with pytest.raises(HTTPError, match=r"HTTP Error 501: .*") as e: | |
831 | urlopen(req) | |
832 | ||
833 | def test_invalid_method_POST(self): | |
834 | hdrs={"Authorization":f"Bearer {TestCephadmExporter.token}"} | |
835 | req=Request("http://localhost:9443/v1/metadata/health",headers=hdrs, method="POST") | |
836 | with pytest.raises(HTTPError, match=r"HTTP Error 501: .*") as e: | |
837 | urlopen(req) | |
838 | ||
839 | def test_invalid_method_PUT(self): | |
840 | hdrs={"Authorization":f"Bearer {TestCephadmExporter.token}"} | |
841 | req=Request("http://localhost:9443/v1/metadata/health",headers=hdrs, method="PUT") | |
842 | with pytest.raises(HTTPError, match=r"HTTP Error 501: .*") as e: | |
843 | urlopen(req) | |
844 | ||
845 | def test_invalid_method_CONNECT(self): | |
846 | hdrs={"Authorization":f"Bearer {TestCephadmExporter.token}"} | |
847 | req=Request("http://localhost:9443/v1/metadata/health",headers=hdrs, method="CONNECT") | |
848 | with pytest.raises(HTTPError, match=r"HTTP Error 501: .*") as e: | |
849 | urlopen(req) | |
850 | ||
851 | def test_invalid_method_TRACE(self): | |
852 | hdrs={"Authorization":f"Bearer {TestCephadmExporter.token}"} | |
853 | req=Request("http://localhost:9443/v1/metadata/health",headers=hdrs, method="TRACE") | |
854 | with pytest.raises(HTTPError, match=r"HTTP Error 501: .*") as e: | |
855 | urlopen(req) | |
856 | ||
857 | def test_invalid_method_OPTIONS(self): | |
858 | hdrs={"Authorization":f"Bearer {TestCephadmExporter.token}"} | |
859 | req=Request("http://localhost:9443/v1/metadata/health",headers=hdrs, method="OPTIONS") | |
860 | with pytest.raises(HTTPError, match=r"HTTP Error 501: .*") as e: | |
861 | urlopen(req) | |
862 | ||
863 | def test_invalid_method_PATCH(self): | |
864 | hdrs={"Authorization":f"Bearer {TestCephadmExporter.token}"} | |
865 | req=Request("http://localhost:9443/v1/metadata/health",headers=hdrs, method="PATCH") | |
866 | with pytest.raises(HTTPError, match=r"HTTP Error 501: .*") as e: | |
867 | urlopen(req) | |
868 | ||
869 | def test_ipv4_subnet(self): | |
870 | rc, v, msg = cd.check_subnet('192.168.1.0/24') | |
871 | assert rc == 0 and v[0] == 4 | |
872 | ||
873 | def test_ipv4_subnet_list(self): | |
874 | rc, v, msg = cd.check_subnet('192.168.1.0/24,10.90.90.0/24') | |
875 | assert rc == 0 and not msg | |
876 | ||
877 | def test_ipv4_subnet_badlist(self): | |
878 | rc, v, msg = cd.check_subnet('192.168.1.0/24,192.168.1.1') | |
879 | assert rc == 1 and msg | |
880 | ||
881 | def test_ipv4_subnet_mixed(self): | |
882 | rc, v, msg = cd.check_subnet('192.168.100.0/24,fe80::/64') | |
883 | assert rc == 0 and v == [4,6] | |
884 | ||
885 | def test_ipv6_subnet(self): | |
886 | rc, v, msg = cd.check_subnet('fe80::/64') | |
887 | assert rc == 0 and v[0] == 6 | |
888 | ||
889 | def test_subnet_mask_missing(self): | |
890 | rc, v, msg = cd.check_subnet('192.168.1.58') | |
891 | assert rc == 1 and msg | |
892 | ||
893 | def test_subnet_mask_junk(self): | |
894 | rc, v, msg = cd.check_subnet('wah') | |
895 | assert rc == 1 and msg | |
896 | ||
897 | ||
898 | class TestMaintenance: | |
899 | systemd_target = "ceph.00000000-0000-0000-0000-000000c0ffee.target" | |
900 | ||
901 | def test_systemd_target_OK(self, tmp_path): | |
902 | base = tmp_path | |
903 | wants = base / "ceph.target.wants" | |
904 | wants.mkdir() | |
905 | target = wants / TestMaintenance.systemd_target | |
906 | target.touch() | |
907 | cd.UNIT_DIR = str(base) | |
908 | ||
909 | assert cd.systemd_target_state(target.name) | |
910 | ||
911 | def test_systemd_target_NOTOK(self, tmp_path): | |
912 | base = tmp_path | |
913 | cd.UNIT_DIR = str(base) | |
914 | assert not cd.systemd_target_state(TestMaintenance.systemd_target) | |
915 | ||
916 | def test_parser_OK(self): | |
917 | args = cd._parse_args(['host-maintenance', 'enter']) | |
918 | assert args.maintenance_action == 'enter' | |
919 | ||
920 | def test_parser_BAD(self): | |
921 | with pytest.raises(SystemExit): | |
922 | cd._parse_args(['host-maintenance', 'wah']) | |
923 | ||
924 | ||
925 | class TestMonitoring(object): | |
926 | @mock.patch('cephadm.call') | |
927 | def test_get_version_alertmanager(self, _call): | |
928 | ctx = mock.Mock() | |
929 | daemon_type = 'alertmanager' | |
930 | ||
931 | # binary `prometheus` | |
932 | _call.return_value = '', '{}, version 0.16.1'.format(daemon_type), 0 | |
933 | version = cd.Monitoring.get_version(ctx, 'container_id', daemon_type) | |
934 | assert version == '0.16.1' | |
935 | ||
936 | # binary `prometheus-alertmanager` | |
937 | _call.side_effect = ( | |
938 | ('', '', 1), | |
939 | ('', '{}, version 0.16.1'.format(daemon_type), 0), | |
940 | ) | |
941 | version = cd.Monitoring.get_version(ctx, 'container_id', daemon_type) | |
942 | assert version == '0.16.1' | |
943 | ||
944 | @mock.patch('cephadm.call') | |
945 | def test_get_version_prometheus(self, _call): | |
946 | ctx = mock.Mock() | |
947 | daemon_type = 'prometheus' | |
948 | _call.return_value = '', '{}, version 0.16.1'.format(daemon_type), 0 | |
949 | version = cd.Monitoring.get_version(ctx, 'container_id', daemon_type) | |
950 | assert version == '0.16.1' | |
951 | ||
952 | @mock.patch('cephadm.call') | |
953 | def test_get_version_node_exporter(self, _call): | |
954 | ctx = mock.Mock() | |
955 | daemon_type = 'node-exporter' | |
956 | _call.return_value = '', '{}, version 0.16.1'.format(daemon_type.replace('-', '_')), 0 | |
957 | version = cd.Monitoring.get_version(ctx, 'container_id', daemon_type) | |
958 | assert version == '0.16.1' | |
b3b6e05e TL |
959 | |
960 | @mock.patch('cephadm.os.fchown') | |
961 | @mock.patch('cephadm.get_parm') | |
962 | @mock.patch('cephadm.makedirs') | |
963 | @mock.patch('cephadm.open') | |
964 | @mock.patch('cephadm.make_log_dir') | |
965 | @mock.patch('cephadm.make_data_dir') | |
966 | def test_create_daemon_dirs_prometheus(self, make_data_dir, make_log_dir, _open, makedirs, | |
967 | get_parm, fchown): | |
968 | """ | |
969 | Ensures the required and optional files given in the configuration are | |
970 | created and mapped correctly inside the container. Tests absolute and | |
971 | relative file paths given in the configuration. | |
972 | """ | |
973 | ||
974 | fsid = 'aaf5a720-13fe-4a3b-82b9-2d99b7fd9704' | |
975 | daemon_type = 'prometheus' | |
976 | uid, gid = 50, 50 | |
977 | daemon_id = 'home' | |
978 | ctx = mock.Mock() | |
979 | ctx.data_dir = '/somedir' | |
980 | files = { | |
981 | 'files': { | |
982 | 'prometheus.yml': 'foo', | |
983 | '/etc/prometheus/alerting/ceph_alerts.yml': 'bar' | |
984 | } | |
985 | } | |
986 | get_parm.return_value = files | |
987 | ||
988 | cd.create_daemon_dirs(ctx, | |
989 | fsid, | |
990 | daemon_type, | |
991 | daemon_id, | |
992 | uid, | |
993 | gid, | |
994 | config=None, | |
995 | keyring=None) | |
996 | ||
997 | prefix = '{data_dir}/{fsid}/{daemon_type}.{daemon_id}'.format( | |
998 | data_dir=ctx.data_dir, | |
999 | fsid=fsid, | |
1000 | daemon_type=daemon_type, | |
1001 | daemon_id=daemon_id | |
1002 | ) | |
1003 | assert _open.call_args_list == [ | |
1004 | mock.call('{}/etc/prometheus/prometheus.yml'.format(prefix), 'w', | |
1005 | encoding='utf-8'), | |
1006 | mock.call('{}/etc/prometheus/alerting/ceph_alerts.yml'.format(prefix), 'w', | |
1007 | encoding='utf-8'), | |
1008 | ] | |
1009 | assert mock.call().__enter__().write('foo') in _open.mock_calls | |
1010 | assert mock.call().__enter__().write('bar') in _open.mock_calls | |
1011 | ||
1012 | ||
1013 | class TestBootstrap(object): | |
1014 | ||
1015 | @staticmethod | |
1016 | def _get_cmd(*args): | |
1017 | return [ | |
1018 | 'bootstrap', | |
1019 | '--allow-mismatched-release', | |
1020 | '--skip-prepare-host', | |
1021 | '--skip-dashboard', | |
1022 | *args, | |
1023 | ] | |
1024 | ||
1025 | def test_config(self, cephadm_fs): | |
1026 | conf_file = 'foo' | |
1027 | cmd = self._get_cmd( | |
1028 | '--mon-ip', '192.168.1.1', | |
1029 | '--skip-mon-network', | |
1030 | '--config', conf_file, | |
1031 | ) | |
1032 | ||
1033 | with with_cephadm_ctx(cmd) as ctx: | |
1034 | msg = r'No such file or directory' | |
1035 | with pytest.raises(cd.Error, match=msg): | |
1036 | cd.command_bootstrap(ctx) | |
1037 | ||
1038 | cephadm_fs.create_file(conf_file) | |
1039 | with with_cephadm_ctx(cmd) as ctx: | |
1040 | retval = cd.command_bootstrap(ctx) | |
1041 | assert retval == 0 | |
1042 | ||
1043 | def test_no_mon_addr(self, cephadm_fs): | |
1044 | cmd = self._get_cmd() | |
1045 | with with_cephadm_ctx(cmd) as ctx: | |
1046 | msg = r'must specify --mon-ip or --mon-addrv' | |
1047 | with pytest.raises(cd.Error, match=msg): | |
1048 | cd.command_bootstrap(ctx) | |
1049 | ||
1050 | def test_skip_mon_network(self, cephadm_fs): | |
1051 | cmd = self._get_cmd('--mon-ip', '192.168.1.1') | |
1052 | ||
1053 | with with_cephadm_ctx(cmd, list_networks={}) as ctx: | |
1054 | msg = r'--skip-mon-network' | |
1055 | with pytest.raises(cd.Error, match=msg): | |
1056 | cd.command_bootstrap(ctx) | |
1057 | ||
1058 | cmd += ['--skip-mon-network'] | |
1059 | with with_cephadm_ctx(cmd, list_networks={}) as ctx: | |
1060 | retval = cd.command_bootstrap(ctx) | |
1061 | assert retval == 0 | |
1062 | ||
1063 | @pytest.mark.parametrize('mon_ip, list_networks, result', | |
1064 | [ | |
1065 | # IPv4 | |
1066 | ( | |
1067 | 'eth0', | |
1068 | {'192.168.1.0/24': {'eth0': ['192.168.1.1']}}, | |
1069 | False, | |
1070 | ), | |
1071 | ( | |
1072 | '0.0.0.0', | |
1073 | {'192.168.1.0/24': {'eth0': ['192.168.1.1']}}, | |
1074 | False, | |
1075 | ), | |
1076 | ( | |
1077 | '192.168.1.0', | |
1078 | {'192.168.1.0/24': {'eth0': ['192.168.1.1']}}, | |
1079 | False, | |
1080 | ), | |
1081 | ( | |
1082 | '192.168.1.1', | |
1083 | {'192.168.1.0/24': {'eth0': ['192.168.1.1']}}, | |
1084 | True, | |
1085 | ), | |
1086 | ( | |
1087 | '192.168.1.1:1234', | |
1088 | {'192.168.1.0/24': {'eth0': ['192.168.1.1']}}, | |
1089 | True, | |
1090 | ), | |
1091 | # IPv6 | |
1092 | ( | |
1093 | '::', | |
1094 | {'192.168.1.0/24': {'eth0': ['192.168.1.1']}}, | |
1095 | False, | |
1096 | ), | |
1097 | ( | |
1098 | '::ffff:192.168.1.0', | |
1099 | {"ffff::/64": {"eth0": ["::ffff:c0a8:101"]}}, | |
1100 | False, | |
1101 | ), | |
1102 | ( | |
1103 | '::ffff:192.168.1.1', | |
1104 | {"ffff::/64": {"eth0": ["::ffff:c0a8:101"]}}, | |
1105 | True, | |
1106 | ), | |
1107 | ( | |
1108 | '::ffff:c0a8:101', | |
1109 | {"ffff::/64": {"eth0": ["::ffff:c0a8:101"]}}, | |
1110 | True, | |
1111 | ), | |
1112 | ( | |
1113 | '0000:0000:0000:0000:0000:FFFF:C0A8:0101', | |
1114 | {"ffff::/64": {"eth0": ["::ffff:c0a8:101"]}}, | |
1115 | True, | |
1116 | ), | |
1117 | ]) | |
1118 | def test_mon_ip(self, mon_ip, list_networks, result, cephadm_fs): | |
1119 | cmd = self._get_cmd('--mon-ip', mon_ip) | |
1120 | if not result: | |
1121 | with with_cephadm_ctx(cmd, list_networks=list_networks) as ctx: | |
1122 | msg = r'--skip-mon-network' | |
1123 | with pytest.raises(cd.Error, match=msg): | |
1124 | cd.command_bootstrap(ctx) | |
1125 | else: | |
1126 | with with_cephadm_ctx(cmd, list_networks=list_networks) as ctx: | |
1127 | retval = cd.command_bootstrap(ctx) | |
1128 | assert retval == 0 | |
1129 | ||
1130 | def test_allow_fqdn_hostname(self, cephadm_fs): | |
1131 | hostname = 'foo.bar' | |
1132 | cmd = self._get_cmd( | |
1133 | '--mon-ip', '192.168.1.1', | |
1134 | '--skip-mon-network', | |
1135 | ) | |
1136 | ||
1137 | with with_cephadm_ctx(cmd, hostname=hostname) as ctx: | |
1138 | msg = r'--allow-fqdn-hostname' | |
1139 | with pytest.raises(cd.Error, match=msg): | |
1140 | cd.command_bootstrap(ctx) | |
1141 | ||
1142 | cmd += ['--allow-fqdn-hostname'] | |
1143 | with with_cephadm_ctx(cmd, hostname=hostname) as ctx: | |
1144 | retval = cd.command_bootstrap(ctx) | |
1145 | assert retval == 0 | |
1146 | ||
1147 | @pytest.mark.parametrize('fsid, err', | |
1148 | [ | |
1149 | ('', None), | |
1150 | ('00000000-0000-0000-0000-0000deadbeef', None), | |
1151 | ('00000000-0000-0000-0000-0000deadbeez', 'not an fsid'), | |
1152 | ]) | |
1153 | def test_fsid(self, fsid, err, cephadm_fs): | |
1154 | cmd = self._get_cmd( | |
1155 | '--mon-ip', '192.168.1.1', | |
1156 | '--skip-mon-network', | |
1157 | '--fsid', fsid, | |
1158 | ) | |
1159 | ||
1160 | with with_cephadm_ctx(cmd) as ctx: | |
1161 | if err: | |
1162 | with pytest.raises(cd.Error, match=err): | |
1163 | cd.command_bootstrap(ctx) | |
1164 | else: | |
1165 | retval = cd.command_bootstrap(ctx) | |
1166 | assert retval == 0 |