ceph-devstack 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. ceph_devstack/Dockerfile.selinux +20 -0
  2. ceph_devstack/__init__.py +187 -0
  3. ceph_devstack/ceph_devstack.pp +0 -0
  4. ceph_devstack/ceph_devstack.te +127 -0
  5. ceph_devstack/cli.py +64 -0
  6. ceph_devstack/config.toml +24 -0
  7. ceph_devstack/exec.py +93 -0
  8. ceph_devstack/host.py +154 -0
  9. ceph_devstack/logging.conf +30 -0
  10. ceph_devstack/py.typed +0 -0
  11. ceph_devstack/requirements.py +277 -0
  12. ceph_devstack/resources/__init__.py +115 -0
  13. ceph_devstack/resources/ceph/__init__.py +266 -0
  14. ceph_devstack/resources/ceph/containers.py +419 -0
  15. ceph_devstack/resources/ceph/exceptions.py +3 -0
  16. ceph_devstack/resources/ceph/requirements.py +90 -0
  17. ceph_devstack/resources/ceph/utils.py +45 -0
  18. ceph_devstack/resources/container.py +171 -0
  19. ceph_devstack/resources/misc.py +15 -0
  20. ceph_devstack-0.1.0.dist-info/METADATA +222 -0
  21. ceph_devstack-0.1.0.dist-info/RECORD +44 -0
  22. ceph_devstack-0.1.0.dist-info/WHEEL +5 -0
  23. ceph_devstack-0.1.0.dist-info/entry_points.txt +2 -0
  24. ceph_devstack-0.1.0.dist-info/licenses/LICENSE +21 -0
  25. ceph_devstack-0.1.0.dist-info/top_level.txt +2 -0
  26. tests/__init__.py +0 -0
  27. tests/conftest.py +9 -0
  28. tests/resources/__init__.py +0 -0
  29. tests/resources/ceph/__init__.py +0 -0
  30. tests/resources/ceph/fixtures/__init__.py +0 -0
  31. tests/resources/ceph/fixtures/testnode-config.toml +2 -0
  32. tests/resources/ceph/test_cephdevstack_core.py +459 -0
  33. tests/resources/ceph/test_devstack.py +182 -0
  34. tests/resources/ceph/test_env_vars.py +110 -0
  35. tests/resources/ceph/test_requirements_ceph.py +262 -0
  36. tests/resources/ceph/test_ssh_keypair.py +109 -0
  37. tests/resources/ceph/test_testnode.py +36 -0
  38. tests/resources/test_container.py +247 -0
  39. tests/resources/test_misc.py +46 -0
  40. tests/resources/test_podmanresource.py +59 -0
  41. tests/test_config.py +120 -0
  42. tests/test_deep_merge.py +71 -0
  43. tests/test_parse_args.py +228 -0
  44. tests/test_requirements_core.py +495 -0
@@ -0,0 +1,495 @@
1
+ import asyncio
2
+ import pytest
3
+ from packaging.version import parse as parse_version
4
+ from unittest.mock import AsyncMock, patch
5
+
6
+
7
+ from ceph_devstack import config, requirements
8
+
9
+
10
+ @pytest.fixture(scope="class")
11
+ def cls():
12
+ return requirements.Requirement
13
+
14
+
15
+ @pytest.fixture(scope="class")
16
+ def req(cls):
17
+ return cls()
18
+
19
+
20
+ class TestRequirement:
21
+ @pytest.fixture(scope="class")
22
+ def cls(self):
23
+ class TestReq(requirements.Requirement):
24
+ check_cmd = ["test", "command"]
25
+
26
+ return TestReq
27
+
28
+ async def test_check_returns_true_on_zero_rc(self, req):
29
+ mock_proc = AsyncMock()
30
+ mock_proc.wait = AsyncMock(return_value=0)
31
+ with patch.object(req.host, "arun", return_value=mock_proc):
32
+ result = await req.check()
33
+ assert result is True
34
+
35
+ async def test_check_returns_false_on_nonzero_rc(self, req):
36
+ mock_proc = AsyncMock()
37
+ mock_proc.wait = AsyncMock(return_value=1)
38
+ with patch.object(req.host, "arun", return_value=mock_proc):
39
+ result = await req.check()
40
+ assert result is False
41
+
42
+ async def test_evaluate_delegates_to_check(self, req):
43
+ with patch.object(req, "check", return_value=True) as mock_check:
44
+ result = await req.evaluate()
45
+ mock_check.assert_called_once()
46
+ assert result is True
47
+
48
+
49
+ class TestFixableRequirement:
50
+ @pytest.fixture(scope="class")
51
+ def cls(self):
52
+ class TestReq(requirements.FixableRequirement):
53
+ check_cmd = ["test", "-f", "/tmp/testfile"]
54
+ fix_cmd = ["touch", "/tmp/testfile"]
55
+ suggest_msg = "Test file missing"
56
+
57
+ return TestReq
58
+
59
+ async def test_evaluate_returns_true_when_check_passes(self, req):
60
+ with patch.object(req, "check", return_value=True):
61
+ result = await req.evaluate()
62
+ assert result is True
63
+
64
+ async def test_evaluate_returns_false_when_check_fails(self, req):
65
+ config.setdefault("args", {})
66
+ config["args"]["fix"] = False
67
+ with (
68
+ patch.object(req, "check", return_value=False),
69
+ patch.object(req, "suggest") as mock_suggest,
70
+ ):
71
+ result = await req.evaluate()
72
+ assert result is False
73
+ mock_suggest.assert_called_once()
74
+
75
+ async def test_evaluate_calls_fix_when_fix_flag_set(self, req):
76
+ config.setdefault("args", {})
77
+ config["args"]["fix"] = True
78
+ with (
79
+ patch.object(req, "check", return_value=False),
80
+ patch.object(req, "fix", return_value=True) as mock_fix,
81
+ ):
82
+ result = await req.evaluate()
83
+ assert result is True
84
+ mock_fix.assert_called_once()
85
+
86
+ async def test_evaluate_returns_false_when_fix_fails(self, req):
87
+ config.setdefault("args", {})
88
+ config["args"]["fix"] = True
89
+ with (
90
+ patch.object(req, "check", return_value=False),
91
+ patch.object(req, "fix", return_value=False) as mock_fix,
92
+ ):
93
+ result = await req.evaluate()
94
+ assert result is False
95
+ mock_fix.assert_called_once()
96
+
97
+ async def test_fix_requires_fix_cmd(self, req):
98
+ req.fix_cmd = []
99
+ with pytest.raises(AssertionError):
100
+ await req.fix()
101
+ asyncio.run(req.fix())
102
+
103
+
104
+ class TestLocalRequirement:
105
+ @pytest.fixture(scope="class")
106
+ def cls(self):
107
+ class TestReq(requirements.LocalRequirement):
108
+ check_cmd = ["test"]
109
+
110
+ return TestReq
111
+
112
+ def test_local_requirement_uses_local_host(self, req):
113
+ assert req.host == requirements.local_host
114
+
115
+
116
+ class TestPodmanVersionInit:
117
+ @pytest.fixture(scope="class")
118
+ def cls(self):
119
+ return requirements.PodmanVersion
120
+
121
+ @pytest.fixture(scope="class")
122
+ def req(self, cls):
123
+ return cls("4.0.0")
124
+
125
+ def test_podman_version_init_sets_version(self, req):
126
+ assert req.required_version == parse_version("4.0.0")
127
+
128
+ def test_podman_version_init_sets_msg(self, cls):
129
+ req = cls("4.0.0", "Custom message")
130
+ assert req.msg == "Custom message"
131
+
132
+
133
+ class TestSysctlValueInit:
134
+ @pytest.fixture(scope="class")
135
+ def cls(self):
136
+ return requirements.SysctlValue
137
+
138
+ @pytest.fixture(scope="class")
139
+ def req(self, cls):
140
+ return cls("fs.aio-max-nr", 2097152)
141
+
142
+ def test_sysctl_value_init_sets_key(self, req):
143
+ assert req.key == "fs.aio-max-nr"
144
+
145
+ def test_sysctl_value_init_sets_min_value(self, req):
146
+ assert req.min_value == 2097152
147
+
148
+ def test_sysctl_value_init_fix_cmd(self, req):
149
+ assert req.fix_cmd == ["sudo", "sysctl", "fs.aio-max-nr=2097152"]
150
+
151
+
152
+ class TestSELinuxBooleanInit:
153
+ @pytest.fixture(scope="class")
154
+ def cls(self):
155
+ return requirements.SELinuxBoolean
156
+
157
+ @pytest.fixture(scope="class")
158
+ def req(self, cls):
159
+ return cls("test_bool")
160
+
161
+ def test_selinux_boolean_init_sets_boolean_name(self, req):
162
+ assert req.boolean_name == "test_bool"
163
+
164
+ def test_selinux_boolean_init_fix_cmd(self, req):
165
+ assert req.fix_cmd == ["sudo", "setsebool", "-P", "test_bool=true"]
166
+
167
+ def test_selinux_boolean_init_suggest_msg(self, req):
168
+ assert "test_bool" in req.suggest_msg
169
+
170
+
171
+ class TestFuseOverlayfsPresence:
172
+ @pytest.fixture(scope="class")
173
+ def cls(self):
174
+ return requirements.FuseOverlayfsPresence
175
+
176
+ def test_fuse_overlayfs_presence_check_cmd(self, req):
177
+ assert req.check_cmd == ["command", "-v", "fuse-overlayfs"]
178
+
179
+ def test_fuse_overlayfs_presence_fix_cmd(self, req):
180
+ assert req.fix_cmd == ["sudo", "dnf", "install", "-y", "fuse-overlayfs"]
181
+
182
+ def test_fuse_overlayfs_presence_suggest_msg(self, req):
183
+ assert req.suggest_msg == "Could not find fuse-overlayfs"
184
+
185
+
186
+ class TestCgroupV2Properties:
187
+ @pytest.fixture(scope="class")
188
+ def cls(self):
189
+ return requirements.CgroupV2
190
+
191
+ def test_cgroup_v2_suggest_msg(self, req):
192
+ assert req.suggest_msg == "cgroup v2 is not enabled"
193
+
194
+ def test_cgroup_v2_fix_cmd(self, req):
195
+ assert req.fix_cmd == [
196
+ "sudo",
197
+ "grubby",
198
+ "--update-kernel=ALL",
199
+ "--args='systemd.unified_cgroup_hierarchy=1'",
200
+ ]
201
+
202
+
203
+ class TestCgroupV2Check:
204
+ @pytest.fixture(scope="class")
205
+ def cls(self):
206
+ return requirements.CgroupV2
207
+
208
+ async def test_cgroup_v2_check_true(self, req):
209
+ mock_info = {"host": {"cgroupVersion": "v2"}}
210
+ with patch.object(req.host, "podman_info", return_value=mock_info):
211
+ result = await req.check()
212
+ assert result is True
213
+
214
+ async def test_cgroup_v2_check_false(self, req):
215
+ mock_info = {"host": {"cgroupVersion": "v1"}}
216
+ with patch.object(req.host, "podman_info", return_value=mock_info):
217
+ result = await req.check()
218
+ assert result is False
219
+
220
+
221
+ class TestPodmanDNSPluginInit:
222
+ @pytest.fixture(scope="class")
223
+ def cls(self):
224
+ return requirements.PodmanDNSPlugin
225
+
226
+ @pytest.fixture(scope="class", params=["centos", "ubuntu", "debian"])
227
+ def os_type(self, request):
228
+ return request.param
229
+
230
+ @pytest.fixture(scope="class")
231
+ def dns_plugin_path(self, os_type):
232
+ if os_type == "centos":
233
+ return "/usr/libexec/cni/dnsname"
234
+ elif os_type in ["ubuntu", "debian"]:
235
+ return "/usr/lib/cni/dnsname"
236
+
237
+ def test_podman_dns_plugin_config(self, cls, os_type, dns_plugin_path):
238
+ with patch.object(cls.host, "os_type", return_value=os_type):
239
+ req = cls()
240
+ assert req.check_cmd == ["test", "-x", dns_plugin_path]
241
+
242
+
243
+ class TestAppArmorProfile:
244
+ @pytest.fixture(scope="class")
245
+ def cls(self):
246
+ return requirements.AppArmorProfile
247
+
248
+ def test_apparmor_profile_check_cmd(self, req):
249
+ assert req.check_cmd == ["test", "-f", "/etc/apparmor.d/local/unix-chkpwd"]
250
+
251
+ def test_apparmor_profile_fix_cmd(self, req):
252
+ assert req.fix_cmd[-1].endswith("&& systemctl reload apparmor")
253
+
254
+ def test_apparmor_profile_suggest_msg(self, req):
255
+ assert req.suggest_msg == "Did not find required apparmor profile"
256
+
257
+
258
+ class TestFixableRequirementSuggestMsg:
259
+ @pytest.fixture(scope="class")
260
+ def cls(self):
261
+ class TestReq(requirements.FixableRequirement):
262
+ check_cmd = ["test"]
263
+ fix_cmd = ["fix"]
264
+ suggest_msg = "Please fix this"
265
+
266
+ return TestReq
267
+
268
+ def test_fixable_requirement_has_suggest_msg(self, req):
269
+ assert req.suggest_msg == "Please fix this"
270
+
271
+
272
+ class TestCheckRequirements:
273
+ async def test_check_requirements_returns_false_when_podman_not_platform(self):
274
+ with patch("ceph_devstack.requirements.PodmanPlatform") as MockPlatform:
275
+ mock_platform = AsyncMock()
276
+ mock_platform.evaluate = AsyncMock(return_value=False)
277
+ MockPlatform.return_value = mock_platform
278
+ result = await requirements.check_requirements()
279
+ assert result is False
280
+ mock_platform.evaluate.assert_called_once()
281
+
282
+ async def test_check_requirements_returns_false_on_overlay_failure(self):
283
+ with (
284
+ patch("ceph_devstack.requirements.PodmanPlatform") as MockPlatform,
285
+ patch("ceph_devstack.requirements.PodmanGraphDriver") as MockGraph,
286
+ ):
287
+ mock_platform = AsyncMock()
288
+ mock_platform.evaluate = AsyncMock(return_value=True)
289
+ MockPlatform.return_value = mock_platform
290
+
291
+ mock_graph = AsyncMock()
292
+ mock_graph.evaluate = AsyncMock(return_value=False)
293
+ MockGraph.return_value = mock_graph
294
+
295
+ result = await requirements.check_requirements()
296
+ assert result is False
297
+
298
+ async def test_check_requirements_returns_true_when_all_pass(self):
299
+ with (
300
+ patch("ceph_devstack.requirements.PodmanPlatform") as MockPlatform,
301
+ patch("ceph_devstack.requirements.PodmanGraphDriver") as MockGraph,
302
+ patch("ceph_devstack.requirements.PodmanVersion") as MockVersion,
303
+ patch("ceph_devstack.requirements.KernelVersionForOverlay") as MockKernel,
304
+ patch("ceph_devstack.requirements.CgroupV2") as MockCgroup,
305
+ patch(
306
+ "ceph_devstack.requirements.KernelVersionForCgroupV2"
307
+ ) as MockKernelCgroup,
308
+ patch("ceph_devstack.requirements.PodmanRuntime") as MockRuntime,
309
+ patch("ceph_devstack.requirements.host.selinux_enforcing") as mock_selinux,
310
+ patch("ceph_devstack.requirements.SysctlValue") as MockSysctl,
311
+ ):
312
+ mock_platform = AsyncMock()
313
+ mock_platform.evaluate = AsyncMock(return_value=True)
314
+ MockPlatform.return_value = mock_platform
315
+
316
+ mock_graph = AsyncMock()
317
+ mock_graph.evaluate = AsyncMock(return_value=True)
318
+ MockGraph.return_value = mock_graph
319
+
320
+ mock_version = AsyncMock()
321
+ mock_version.evaluate = AsyncMock(return_value=True)
322
+ MockVersion.return_value = mock_version
323
+
324
+ mock_kernel = AsyncMock()
325
+ mock_kernel.evaluate = AsyncMock(return_value=True)
326
+ MockKernel.return_value = mock_kernel
327
+
328
+ mock_cgroup = AsyncMock()
329
+ mock_cgroup.evaluate = AsyncMock(return_value=True)
330
+ MockCgroup.return_value = mock_cgroup
331
+
332
+ mock_kernel_cgroup = AsyncMock()
333
+ mock_kernel_cgroup.evaluate = AsyncMock(return_value=True)
334
+ MockKernelCgroup.return_value = mock_kernel_cgroup
335
+
336
+ mock_runtime = AsyncMock()
337
+ mock_runtime.evaluate = AsyncMock(return_value=True)
338
+ MockRuntime.return_value = mock_runtime
339
+
340
+ mock_selinux.return_value = False
341
+
342
+ mock_sysctl = AsyncMock()
343
+ mock_sysctl.evaluate = AsyncMock(return_value=True)
344
+ MockSysctl.return_value = mock_sysctl
345
+
346
+ result = await requirements.check_requirements()
347
+ assert result is True
348
+
349
+ async def test_check_requirements_returns_false_on_runtime_failure(self):
350
+ with (
351
+ patch("ceph_devstack.requirements.PodmanPlatform") as MockPlatform,
352
+ patch("ceph_devstack.requirements.PodmanGraphDriver") as MockGraph,
353
+ patch("ceph_devstack.requirements.PodmanVersion") as MockVersion,
354
+ patch("ceph_devstack.requirements.KernelVersionForOverlay") as MockKernel,
355
+ patch("ceph_devstack.requirements.CgroupV2") as MockCgroup,
356
+ patch(
357
+ "ceph_devstack.requirements.KernelVersionForCgroupV2"
358
+ ) as MockKernelCgroup,
359
+ patch("ceph_devstack.requirements.PodmanRuntime") as MockRuntime,
360
+ patch("ceph_devstack.requirements.host.selinux_enforcing") as mock_selinux,
361
+ ):
362
+ mock_platform = AsyncMock()
363
+ mock_platform.evaluate = AsyncMock(return_value=True)
364
+ MockPlatform.return_value = mock_platform
365
+
366
+ mock_graph = AsyncMock()
367
+ mock_graph.evaluate = AsyncMock(return_value=True)
368
+ MockGraph.return_value = mock_graph
369
+
370
+ mock_version = AsyncMock()
371
+ mock_version.evaluate = AsyncMock(return_value=True)
372
+ MockVersion.return_value = mock_version
373
+
374
+ mock_kernel = AsyncMock()
375
+ mock_kernel.evaluate = AsyncMock(return_value=True)
376
+ MockKernel.return_value = mock_kernel
377
+
378
+ mock_cgroup = AsyncMock()
379
+ mock_cgroup.evaluate = AsyncMock(return_value=True)
380
+ MockCgroup.return_value = mock_cgroup
381
+
382
+ mock_kernel_cgroup = AsyncMock()
383
+ mock_kernel_cgroup.evaluate = AsyncMock(return_value=True)
384
+ MockKernelCgroup.return_value = mock_kernel_cgroup
385
+
386
+ mock_runtime = AsyncMock()
387
+ mock_runtime.evaluate = AsyncMock(return_value=False)
388
+ MockRuntime.return_value = mock_runtime
389
+
390
+ mock_selinux.return_value = False
391
+
392
+ result = await requirements.check_requirements()
393
+ assert result is False
394
+
395
+ async def test_check_requirements_returns_false_on_selinux_bool_failure(self):
396
+ with (
397
+ patch("ceph_devstack.requirements.PodmanPlatform") as MockPlatform,
398
+ patch("ceph_devstack.requirements.PodmanGraphDriver") as MockGraph,
399
+ patch("ceph_devstack.requirements.KernelVersionForOverlay") as MockKernel,
400
+ patch("ceph_devstack.requirements.CgroupV2") as MockCgroup,
401
+ patch(
402
+ "ceph_devstack.requirements.KernelVersionForCgroupV2"
403
+ ) as MockKernelCgroup,
404
+ patch("ceph_devstack.requirements.PodmanRuntime") as MockRuntime,
405
+ patch("ceph_devstack.requirements.host.selinux_enforcing") as mock_selinux,
406
+ patch("ceph_devstack.requirements.SELinuxBoolean") as MockSELinuxBoolean,
407
+ ):
408
+ mock_platform = AsyncMock()
409
+ mock_platform.evaluate = AsyncMock(return_value=True)
410
+ MockPlatform.return_value = mock_platform
411
+
412
+ mock_graph = AsyncMock()
413
+ mock_graph.evaluate = AsyncMock(return_value=True)
414
+ MockGraph.return_value = mock_graph
415
+
416
+ with patch("ceph_devstack.requirements.PodmanVersion") as MockVersion:
417
+ mock_version = AsyncMock()
418
+ mock_version.evaluate = AsyncMock(return_value=True)
419
+ MockVersion.return_value = mock_version
420
+
421
+ mock_kernel = AsyncMock()
422
+ mock_kernel.evaluate = AsyncMock(return_value=True)
423
+ MockKernel.return_value = mock_kernel
424
+
425
+ mock_cgroup = AsyncMock()
426
+ mock_cgroup.evaluate = AsyncMock(return_value=True)
427
+ MockCgroup.return_value = mock_cgroup
428
+
429
+ mock_kernel_cgroup = AsyncMock()
430
+ mock_kernel_cgroup.evaluate = AsyncMock(return_value=True)
431
+ MockKernelCgroup.return_value = mock_kernel_cgroup
432
+
433
+ mock_runtime = AsyncMock()
434
+ mock_runtime.evaluate = AsyncMock(return_value=True)
435
+ MockRuntime.return_value = mock_runtime
436
+
437
+ mock_selinux.return_value = True
438
+
439
+ mock_sel = AsyncMock()
440
+ mock_sel.evaluate = AsyncMock(return_value=False)
441
+ MockSELinuxBoolean.return_value = mock_sel
442
+
443
+ result = await requirements.check_requirements()
444
+ assert result is False
445
+
446
+ async def test_check_requirements_returns_false_on_sysctl_failure(self):
447
+ with (
448
+ patch("ceph_devstack.requirements.PodmanPlatform") as MockPlatform,
449
+ patch("ceph_devstack.requirements.PodmanGraphDriver") as MockGraph,
450
+ patch("ceph_devstack.requirements.PodmanVersion") as MockVersion,
451
+ patch("ceph_devstack.requirements.KernelVersionForOverlay") as MockKernel,
452
+ patch("ceph_devstack.requirements.CgroupV2") as MockCgroup,
453
+ patch(
454
+ "ceph_devstack.requirements.KernelVersionForCgroupV2"
455
+ ) as MockKernelCgroup,
456
+ patch("ceph_devstack.requirements.PodmanRuntime") as MockRuntime,
457
+ patch("ceph_devstack.requirements.host.selinux_enforcing") as mock_selinux,
458
+ patch("ceph_devstack.requirements.SysctlValue") as MockSysctl,
459
+ ):
460
+ mock_platform = AsyncMock()
461
+ mock_platform.evaluate = AsyncMock(return_value=True)
462
+ MockPlatform.return_value = mock_platform
463
+
464
+ mock_graph = AsyncMock()
465
+ mock_graph.evaluate = AsyncMock(return_value=True)
466
+ MockGraph.return_value = mock_graph
467
+
468
+ mock_version = AsyncMock()
469
+ mock_version.evaluate = AsyncMock(return_value=True)
470
+ MockVersion.return_value = mock_version
471
+
472
+ mock_kernel = AsyncMock()
473
+ mock_kernel.evaluate = AsyncMock(return_value=True)
474
+ MockKernel.return_value = mock_kernel
475
+
476
+ mock_cgroup = AsyncMock()
477
+ mock_cgroup.evaluate = AsyncMock(return_value=True)
478
+ MockCgroup.return_value = mock_cgroup
479
+
480
+ mock_kernel_cgroup = AsyncMock()
481
+ mock_kernel_cgroup.evaluate = AsyncMock(return_value=True)
482
+ MockKernelCgroup.return_value = mock_kernel_cgroup
483
+
484
+ mock_runtime = AsyncMock()
485
+ mock_runtime.evaluate = AsyncMock(return_value=True)
486
+ MockRuntime.return_value = mock_runtime
487
+
488
+ mock_selinux.return_value = False
489
+
490
+ mock_sysctl = AsyncMock()
491
+ mock_sysctl.evaluate = AsyncMock(return_value=False)
492
+ MockSysctl.return_value = mock_sysctl
493
+
494
+ result = await requirements.check_requirements()
495
+ assert result is False