ceph-devstack 0.2.2__py3-none-any.whl → 0.3.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.
- ceph_devstack/__init__.py +28 -3
- ceph_devstack/cli.py +1 -0
- ceph_devstack/resources/ceph/__init__.py +14 -13
- ceph_devstack/resources/ceph/containers.py +1 -1
- ceph_devstack/resources/ceph/utils.py +24 -31
- {ceph_devstack-0.2.2.dist-info → ceph_devstack-0.3.0.dist-info}/METADATA +2 -2
- {ceph_devstack-0.2.2.dist-info → ceph_devstack-0.3.0.dist-info}/RECORD +23 -22
- ceph_devstack-0.3.0.dist-info/scm_file_list.json +55 -0
- ceph_devstack-0.3.0.dist-info/scm_version.json +8 -0
- tests/conftest.py +34 -0
- tests/resources/ceph/test_cephdevstack_core.py +15 -50
- tests/resources/ceph/test_devstack.py +43 -93
- tests/resources/ceph/test_env_vars.py +14 -0
- tests/resources/ceph/test_testnode.py +1 -0
- tests/resources/test_container.py +1 -1
- tests/resources/test_misc.py +2 -1
- tests/resources/test_podmanresource.py +2 -1
- tests/test_config.py +27 -25
- tests/test_requirements_core.py +21 -4
- ceph_devstack/resources/ceph/exceptions.py +0 -3
- {ceph_devstack-0.2.2.dist-info → ceph_devstack-0.3.0.dist-info}/WHEEL +0 -0
- {ceph_devstack-0.2.2.dist-info → ceph_devstack-0.3.0.dist-info}/entry_points.txt +0 -0
- {ceph_devstack-0.2.2.dist-info → ceph_devstack-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {ceph_devstack-0.2.2.dist-info → ceph_devstack-0.3.0.dist-info}/top_level.txt +0 -0
ceph_devstack/__init__.py
CHANGED
|
@@ -50,6 +50,8 @@ def parse_args(args: List[str]) -> argparse.Namespace:
|
|
|
50
50
|
parser_config_set = subparsers_config.add_parser("set")
|
|
51
51
|
parser_config_set.add_argument("name")
|
|
52
52
|
parser_config_set.add_argument("value")
|
|
53
|
+
parser_config_unset = subparsers_config.add_parser("unset")
|
|
54
|
+
parser_config_unset.add_argument("name")
|
|
53
55
|
parser_doc = subparsers.add_parser(
|
|
54
56
|
"doctor", help="Check that the system meets requirements"
|
|
55
57
|
)
|
|
@@ -129,6 +131,8 @@ class Config(dict):
|
|
|
129
131
|
__slots__ = ["user_obj", "user_path"]
|
|
130
132
|
|
|
131
133
|
def load(self, config_path: Path | None = None):
|
|
134
|
+
args = self.get("args")
|
|
135
|
+
self.clear()
|
|
132
136
|
parsed = tomlkit.parse((Path(__file__).parent / "config.toml").read_text())
|
|
133
137
|
self.update(parsed)
|
|
134
138
|
if config_path:
|
|
@@ -140,6 +144,8 @@ class Config(dict):
|
|
|
140
144
|
raise OSError(f"Config file at {self.user_path} not found!")
|
|
141
145
|
else:
|
|
142
146
|
self.user_obj = {}
|
|
147
|
+
if args:
|
|
148
|
+
self["args"] = args
|
|
143
149
|
|
|
144
150
|
def dump(self):
|
|
145
151
|
return tomlkit.dumps(self)
|
|
@@ -153,14 +159,14 @@ class Config(dict):
|
|
|
153
159
|
try:
|
|
154
160
|
obj = obj[sub_path]
|
|
155
161
|
except KeyError:
|
|
156
|
-
logger.
|
|
157
|
-
|
|
162
|
+
logger.debug(f"{name} not found in config")
|
|
163
|
+
return ""
|
|
158
164
|
i += 1
|
|
159
165
|
if isinstance(obj, (str, int, bool)):
|
|
160
166
|
return str(obj)
|
|
161
167
|
return tomlkit.dumps(obj).strip()
|
|
162
168
|
|
|
163
|
-
def set_value(self, name: str, value: str) ->
|
|
169
|
+
def set_value(self, name: str, value: str) -> str:
|
|
164
170
|
path = name.split(".")
|
|
165
171
|
obj = self.user_obj
|
|
166
172
|
i = 0
|
|
@@ -181,6 +187,25 @@ class Config(dict):
|
|
|
181
187
|
self.user_path.parent.mkdir(exist_ok=True)
|
|
182
188
|
self.user_path.write_text(tomlkit.dumps(self.user_obj).strip())
|
|
183
189
|
i += 1
|
|
190
|
+
return str(item)
|
|
191
|
+
|
|
192
|
+
def unset_value(self, name: str) -> None:
|
|
193
|
+
path = name.split(".")
|
|
194
|
+
obj = self.user_obj
|
|
195
|
+
i = 0
|
|
196
|
+
last_index = len(path) - 1
|
|
197
|
+
while i <= last_index:
|
|
198
|
+
if i < last_index:
|
|
199
|
+
if path[i] not in obj:
|
|
200
|
+
break
|
|
201
|
+
obj = obj[path[i]]
|
|
202
|
+
elif i == last_index:
|
|
203
|
+
obj.pop(path[i])
|
|
204
|
+
self.update(self.user_obj)
|
|
205
|
+
self.user_path.parent.mkdir(exist_ok=True)
|
|
206
|
+
self.user_path.write_text(tomlkit.dumps(self.user_obj).strip())
|
|
207
|
+
i += 1
|
|
208
|
+
self.load(self.user_path)
|
|
184
209
|
|
|
185
210
|
|
|
186
211
|
config = Config()
|
ceph_devstack/cli.py
CHANGED
|
@@ -12,6 +12,7 @@ CONFIG_HANDLERS = {
|
|
|
12
12
|
"dump": lambda config, args: print(config.dump()),
|
|
13
13
|
"get": lambda config, args: print(config.get_value(args.name)),
|
|
14
14
|
"set": lambda config, args: print(config.set_value(args.name, args.value)),
|
|
15
|
+
"unset": lambda config, args: config.unset_value(args.name),
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
COMMAND_HANDLERS = {
|
|
@@ -24,8 +24,7 @@ from ceph_devstack.resources.ceph.requirements import (
|
|
|
24
24
|
LoopControlDeviceWriteable,
|
|
25
25
|
SELinuxModule,
|
|
26
26
|
)
|
|
27
|
-
from ceph_devstack.resources.ceph.utils import
|
|
28
|
-
from ceph_devstack.resources.ceph.exceptions import TooManyJobsFound
|
|
27
|
+
from ceph_devstack.resources.ceph.utils import get_runs, get_jobs
|
|
29
28
|
|
|
30
29
|
|
|
31
30
|
class SSHKeyPair(Secret):
|
|
@@ -236,29 +235,31 @@ class CephDevStack:
|
|
|
236
235
|
log_file = self.get_log_file(run_name, job_id)
|
|
237
236
|
except FileNotFoundError:
|
|
238
237
|
logger.error("No log file found")
|
|
239
|
-
except TooManyJobsFound as e:
|
|
240
|
-
msg = "Found too many jobs ({jobs}) for target run. Please pick a job id with -j option.".format(
|
|
241
|
-
jobs=", ".join(e.jobs)
|
|
242
|
-
)
|
|
243
|
-
logger.error(msg)
|
|
244
238
|
else:
|
|
245
239
|
if locate:
|
|
246
|
-
print(log_file)
|
|
240
|
+
print(str(log_file).replace(str(pathlib.Path.home()), "~"))
|
|
247
241
|
else:
|
|
248
242
|
buffer_size = 8 * 1024
|
|
249
243
|
with open(log_file) as f:
|
|
250
244
|
while chunk := f.read(buffer_size):
|
|
251
245
|
print(chunk, end="")
|
|
252
246
|
|
|
253
|
-
def get_log_file(self, run_name: str = "", job_id: str = ""):
|
|
254
|
-
archive_dir = Teuthology().archive_dir
|
|
247
|
+
def get_log_file(self, run_name: str = "", job_id: str = "") -> pathlib.Path:
|
|
248
|
+
archive_dir = Teuthology().archive_dir
|
|
255
249
|
|
|
256
250
|
if not run_name:
|
|
257
|
-
|
|
258
|
-
|
|
251
|
+
runs = get_runs(archive_dir)
|
|
252
|
+
if not runs:
|
|
253
|
+
raise FileNotFoundError
|
|
254
|
+
run_dir = runs[0]
|
|
255
|
+
else:
|
|
256
|
+
run_dir = archive_dir.joinpath(run_name)
|
|
259
257
|
|
|
260
258
|
if not job_id:
|
|
261
|
-
|
|
259
|
+
jobs = get_jobs(run_dir)
|
|
260
|
+
if not jobs:
|
|
261
|
+
raise FileNotFoundError
|
|
262
|
+
job_id = jobs[0].name
|
|
262
263
|
|
|
263
264
|
log_file = run_dir.joinpath(job_id, "teuthology.log")
|
|
264
265
|
if not log_file.exists():
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import pathlib
|
|
1
2
|
import re
|
|
2
3
|
from datetime import datetime
|
|
3
|
-
|
|
4
|
-
from ceph_devstack.resources.ceph.exceptions import TooManyJobsFound
|
|
4
|
+
from typing import List
|
|
5
5
|
|
|
6
6
|
RUN_DIRNAME_PATTERN = re.compile(
|
|
7
7
|
r"^(?P<username>^[a-z_]([a-z0-9_-]{0,31}|[a-z0-9_-]{0,30}))-(?P<timestamp>\d{4}-\d{2}-\d{2}_\d{2}:\d{2}:\d{2})"
|
|
@@ -14,32 +14,25 @@ def get_logtimestamp(dirname: str) -> datetime:
|
|
|
14
14
|
return datetime.strptime(match_.group("timestamp"), "%Y-%m-%d_%H:%M:%S")
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
def
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
dirs = [d for d in jobs if job_dir_pattern.match(d)]
|
|
40
|
-
|
|
41
|
-
if len(dirs) == 0:
|
|
42
|
-
raise FileNotFoundError
|
|
43
|
-
elif len(dirs) > 1:
|
|
44
|
-
raise TooManyJobsFound(dirs)
|
|
45
|
-
return dirs[0]
|
|
17
|
+
def get_runs(directory: pathlib.Path) -> List[pathlib.Path]:
|
|
18
|
+
return sorted(
|
|
19
|
+
(
|
|
20
|
+
dir_
|
|
21
|
+
for dir_ in directory.expanduser().absolute().iterdir()
|
|
22
|
+
if RUN_DIRNAME_PATTERN.search(dir_.name)
|
|
23
|
+
),
|
|
24
|
+
key=lambda dir_: dir_.stat().st_mtime,
|
|
25
|
+
reverse=True,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def get_jobs(directory: pathlib.Path) -> List[pathlib.Path]:
|
|
30
|
+
return sorted(
|
|
31
|
+
(
|
|
32
|
+
dir_
|
|
33
|
+
for dir_ in directory.expanduser().absolute().iterdir()
|
|
34
|
+
if str(dir_.name).isdigit()
|
|
35
|
+
),
|
|
36
|
+
key=lambda dir_: dir_.stat().st_mtime,
|
|
37
|
+
reverse=True,
|
|
38
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ceph-devstack
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Run a full teuthology lab on your laptop!
|
|
5
5
|
Author-email: Zack Cerza <zack@cerza.org>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -11,7 +11,7 @@ Classifier: Natural Language :: English
|
|
|
11
11
|
Classifier: Operating System :: POSIX :: Linux
|
|
12
12
|
Classifier: Programming Language :: Python :: 3
|
|
13
13
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
-
Requires-Python: >=3.
|
|
14
|
+
Requires-Python: >=3.12
|
|
15
15
|
Description-Content-Type: text/markdown
|
|
16
16
|
License-File: LICENSE
|
|
17
17
|
Requires-Dist: packaging
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
ceph_devstack/Dockerfile.selinux,sha256=5i8leCmaa5Hx8w4zIZcLoq7aTbxxwee44e2phWY7_zY,497
|
|
2
|
-
ceph_devstack/__init__.py,sha256=
|
|
2
|
+
ceph_devstack/__init__.py,sha256=P5ySWtncR-pkxC8ju6xZUrU6ClnjLSANgkDPftkkEdU,6870
|
|
3
3
|
ceph_devstack/ceph_devstack.pp,sha256=kiVP5xcwYigjKlNoGHVh2PwPKvr6HZZl5NyjY31jXWM,14537
|
|
4
4
|
ceph_devstack/ceph_devstack.te,sha256=68i9sBb1TAEnCJn2JtyM-ECzPSuZck4vKoymcTbptXc,3461
|
|
5
|
-
ceph_devstack/cli.py,sha256=
|
|
5
|
+
ceph_devstack/cli.py,sha256=Tl4rQXKt61TPGEJSS9rgs2ycRGo6sBGthDu-7V_u-C8,2122
|
|
6
6
|
ceph_devstack/config.toml,sha256=HsxO3FhwZkHomde69QfElztuN8U1bxk7cQk1_iWB4w0,562
|
|
7
7
|
ceph_devstack/exec.py,sha256=gn95GEQttB6xgdWXXvTsxUxcQsx9TUu6PC8QVQ0_Y7w,2674
|
|
8
8
|
ceph_devstack/host.py,sha256=w_Hx912y1g62yXkTuPJsw_UdUwxJr_Pbxrj8iZtEP7Y,5470
|
|
@@ -12,33 +12,34 @@ ceph_devstack/requirements.py,sha256=aE7PjSdcNuD1no12anidTZhjlYxEig8O0iatlz8EeHw
|
|
|
12
12
|
ceph_devstack/resources/__init__.py,sha256=D0vhJBU9Pqslq7hg2qreW_ZhndcRIOHZVVUA0zAIeDM,3386
|
|
13
13
|
ceph_devstack/resources/container.py,sha256=suJNOQ9fMRfCnXQwvEZOcg4jnPtrxb-d17a3Y1s1xG0,5388
|
|
14
14
|
ceph_devstack/resources/misc.py,sha256=iGD_3Xsqako-dOZrHJFEJbMWoQIzy7Q_KkEnkdBb8m0,552
|
|
15
|
-
ceph_devstack/resources/ceph/__init__.py,sha256=
|
|
16
|
-
ceph_devstack/resources/ceph/containers.py,sha256=
|
|
17
|
-
ceph_devstack/resources/ceph/exceptions.py,sha256=C4ldyEA7Cukp-QUOwL-MnQ8kpgMDIQ6r_JQe0E9q4eM,101
|
|
15
|
+
ceph_devstack/resources/ceph/__init__.py,sha256=8XqvUiHE5CPHT0ZZ46lyMWss0YqOzgowWMgPl31fElw,9168
|
|
16
|
+
ceph_devstack/resources/ceph/containers.py,sha256=s_YiiiyJS-URHNf_BANMCnDUirJkzrjyzJjOZuKXMQA,11502
|
|
18
17
|
ceph_devstack/resources/ceph/requirements.py,sha256=ZJt7Eeb5y2bOc7dJN3-15ftAyySi80phD7erBsGdqSU,2691
|
|
19
|
-
ceph_devstack/resources/ceph/utils.py,sha256=
|
|
20
|
-
ceph_devstack-0.
|
|
18
|
+
ceph_devstack/resources/ceph/utils.py,sha256=q82WsEij-D4JVTiLvJmbM1G6ZOuDNI5kIy0wMqkyRQI,1045
|
|
19
|
+
ceph_devstack-0.3.0.dist-info/licenses/LICENSE,sha256=bFeYufyeS1qw0W8pkW1Wj09F2itqX7TAGTnA2NIpApU,1067
|
|
21
20
|
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
|
-
tests/conftest.py,sha256=
|
|
23
|
-
tests/test_config.py,sha256=
|
|
21
|
+
tests/conftest.py,sha256=qHW1Mjr3UtyP7SOPkfZiDfd-6aGcYswWRzahqGLvmb4,1300
|
|
22
|
+
tests/test_config.py,sha256=yXkabts7UK73PZm4sPXPLqK5K8cD1r8RgxFOHz7r2Lk,4338
|
|
24
23
|
tests/test_deep_merge.py,sha256=IP9zzCThmhVghl9XjAAiSrKsPdouH3KbEUaKq8n3Soc,2178
|
|
25
24
|
tests/test_parse_args.py,sha256=2JDAT4Id88Ywl5QmiUHqq5IDGm_j5Yl3Kk7pbl3sOEk,7550
|
|
26
|
-
tests/test_requirements_core.py,sha256=
|
|
25
|
+
tests/test_requirements_core.py,sha256=p3Nw2nyq5lfuyDyifoC6t4Fls3RrH5IOYSM_nF0N8JA,20390
|
|
27
26
|
tests/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
28
|
-
tests/resources/test_container.py,sha256=
|
|
29
|
-
tests/resources/test_misc.py,sha256=
|
|
30
|
-
tests/resources/test_podmanresource.py,sha256=
|
|
27
|
+
tests/resources/test_container.py,sha256=u3hk70BnTES-5oykP5xT06CgC6Ek0OM-br5x3MFJC4Q,9084
|
|
28
|
+
tests/resources/test_misc.py,sha256=b0F67kRrE8FlKtjVHaRfKi4PXm8lxZ24k0Qp9XVfjz8,1580
|
|
29
|
+
tests/resources/test_podmanresource.py,sha256=qtcWTTq-GNo-mvVcUjJlhRQaIEZQ5BfN-03OdZTXAvc,1785
|
|
31
30
|
tests/resources/ceph/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
32
|
-
tests/resources/ceph/test_cephdevstack_core.py,sha256=
|
|
33
|
-
tests/resources/ceph/test_devstack.py,sha256=
|
|
34
|
-
tests/resources/ceph/test_env_vars.py,sha256=
|
|
31
|
+
tests/resources/ceph/test_cephdevstack_core.py,sha256=oTmu21quN-zzEorioow0QKVlawdDvZJrtUBOESajePk,17081
|
|
32
|
+
tests/resources/ceph/test_devstack.py,sha256=U9jYm7wQyqT-MJjO0UidyZhzJOzJEfIdaflwCoPKhcE,3987
|
|
33
|
+
tests/resources/ceph/test_env_vars.py,sha256=NiaMyV9TVy44MuQM4FOoAnY2Ku3lNIwyAcM3lsS_ed4,2979
|
|
35
34
|
tests/resources/ceph/test_requirements_ceph.py,sha256=VLPHrzt1dLk3RnzjqRY3wyPUQbMspUuW6S_9cAWtpYU,9134
|
|
36
35
|
tests/resources/ceph/test_ssh_keypair.py,sha256=M8Tc1CY9zb_1WaccMSfnSN35tyjR-CblNtTP0s4OsYU,4019
|
|
37
|
-
tests/resources/ceph/test_testnode.py,sha256=
|
|
36
|
+
tests/resources/ceph/test_testnode.py,sha256=jfoixCR2Dr6X273VPSSkv-H8ribB_B9Fz5izypLRWUQ,1270
|
|
38
37
|
tests/resources/ceph/fixtures/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
39
38
|
tests/resources/ceph/fixtures/testnode-config.toml,sha256=XaI4VACYHeshb7LLxN3viKBNxOBLOUpPuwlcQJyQO-U,44
|
|
40
|
-
ceph_devstack-0.
|
|
41
|
-
ceph_devstack-0.
|
|
42
|
-
ceph_devstack-0.
|
|
43
|
-
ceph_devstack-0.
|
|
44
|
-
ceph_devstack-0.
|
|
39
|
+
ceph_devstack-0.3.0.dist-info/METADATA,sha256=s2P3nQwaVIzVMIxDrtazXLdeZi5_1512ud_Tv9mmpvE,6877
|
|
40
|
+
ceph_devstack-0.3.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
41
|
+
ceph_devstack-0.3.0.dist-info/entry_points.txt,sha256=3cXOGOSb23ATcOZVQFBTY_imM4VFgFddXlYi-m8PA10,57
|
|
42
|
+
ceph_devstack-0.3.0.dist-info/scm_file_list.json,sha256=1BOCnr5hAz_FTSzy5VVWcPqgpOr_lYlAg1Xx54rucd0,1817
|
|
43
|
+
ceph_devstack-0.3.0.dist-info/scm_version.json,sha256=R6cOphoyTBSnU9AyHfeAMIFD0Br57-gBCZ97LYff0jw,160
|
|
44
|
+
ceph_devstack-0.3.0.dist-info/top_level.txt,sha256=NzTr-vAk2OWL08T8PsmfqkJZNEuezF5oaiHCMJBhHPM,20
|
|
45
|
+
ceph_devstack-0.3.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"files": [
|
|
3
|
+
"Jenkinsfile",
|
|
4
|
+
"build_selinux_module.sh",
|
|
5
|
+
".pre-commit-config.yaml",
|
|
6
|
+
"README.md",
|
|
7
|
+
"uv.lock",
|
|
8
|
+
"LICENSE",
|
|
9
|
+
"pyproject.toml",
|
|
10
|
+
"ruff.toml",
|
|
11
|
+
".gitignore",
|
|
12
|
+
".flake8",
|
|
13
|
+
"docs/cgroup_v2.md",
|
|
14
|
+
"ceph_devstack/py.typed",
|
|
15
|
+
"ceph_devstack/__init__.py",
|
|
16
|
+
"ceph_devstack/ceph_devstack.te",
|
|
17
|
+
"ceph_devstack/ceph_devstack.pp",
|
|
18
|
+
"ceph_devstack/logging.conf",
|
|
19
|
+
"ceph_devstack/exec.py",
|
|
20
|
+
"ceph_devstack/host.py",
|
|
21
|
+
"ceph_devstack/config.toml",
|
|
22
|
+
"ceph_devstack/cli.py",
|
|
23
|
+
"ceph_devstack/Dockerfile.selinux",
|
|
24
|
+
"ceph_devstack/requirements.py",
|
|
25
|
+
"ceph_devstack/resources/__init__.py",
|
|
26
|
+
"ceph_devstack/resources/container.py",
|
|
27
|
+
"ceph_devstack/resources/misc.py",
|
|
28
|
+
"ceph_devstack/resources/ceph/containers.py",
|
|
29
|
+
"ceph_devstack/resources/ceph/__init__.py",
|
|
30
|
+
"ceph_devstack/resources/ceph/utils.py",
|
|
31
|
+
"ceph_devstack/resources/ceph/requirements.py",
|
|
32
|
+
"tests/test_config.py",
|
|
33
|
+
"tests/__init__.py",
|
|
34
|
+
"tests/test_requirements_core.py",
|
|
35
|
+
"tests/test_deep_merge.py",
|
|
36
|
+
"tests/conftest.py",
|
|
37
|
+
"tests/test_parse_args.py",
|
|
38
|
+
"tests/resources/__init__.py",
|
|
39
|
+
"tests/resources/test_podmanresource.py",
|
|
40
|
+
"tests/resources/test_misc.py",
|
|
41
|
+
"tests/resources/test_container.py",
|
|
42
|
+
"tests/resources/ceph/test_env_vars.py",
|
|
43
|
+
"tests/resources/ceph/__init__.py",
|
|
44
|
+
"tests/resources/ceph/test_devstack.py",
|
|
45
|
+
"tests/resources/ceph/test_ssh_keypair.py",
|
|
46
|
+
"tests/resources/ceph/test_cephdevstack_core.py",
|
|
47
|
+
"tests/resources/ceph/test_testnode.py",
|
|
48
|
+
"tests/resources/ceph/test_requirements_ceph.py",
|
|
49
|
+
"tests/resources/ceph/fixtures/__init__.py",
|
|
50
|
+
"tests/resources/ceph/fixtures/testnode-config.toml",
|
|
51
|
+
".github/workflows/release.yml",
|
|
52
|
+
".github/workflows/pytest.yml",
|
|
53
|
+
".github/workflows/ci.yml"
|
|
54
|
+
]
|
|
55
|
+
}
|
tests/conftest.py
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import pathlib
|
|
1
3
|
import pytest
|
|
4
|
+
import random
|
|
5
|
+
|
|
6
|
+
from datetime import datetime, timedelta
|
|
2
7
|
|
|
3
8
|
from ceph_devstack import config
|
|
4
9
|
|
|
@@ -7,3 +12,32 @@ from ceph_devstack import config
|
|
|
7
12
|
def reset_config():
|
|
8
13
|
config.load()
|
|
9
14
|
yield
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@pytest.fixture(scope="function")
|
|
18
|
+
def create_log_file():
|
|
19
|
+
def _create_log_file(data_dir: pathlib.Path, **kwargs) -> pathlib.Path:
|
|
20
|
+
parts = {
|
|
21
|
+
"timestamp": (datetime.now() - timedelta(days=random.randint(1, 100))),
|
|
22
|
+
"test_type": random.choice(["ceph", "rgw", "rbd", "mds"]),
|
|
23
|
+
"job_id": random.randint(1, 100),
|
|
24
|
+
"content": "some log data",
|
|
25
|
+
**kwargs,
|
|
26
|
+
}
|
|
27
|
+
timestamp = parts["timestamp"].strftime("%Y-%m-%d_%H:%M:%S")
|
|
28
|
+
test_type = parts["test_type"]
|
|
29
|
+
job_id = parts["job_id"]
|
|
30
|
+
content = parts["content"]
|
|
31
|
+
|
|
32
|
+
run_name = f"root-{timestamp}-orch:cephadm:{test_type}-small-main-distro-default-testnode"
|
|
33
|
+
log_dir = data_dir / "archive" / run_name / str(job_id)
|
|
34
|
+
|
|
35
|
+
os.makedirs(log_dir, exist_ok=True)
|
|
36
|
+
time_ = parts["timestamp"].timestamp()
|
|
37
|
+
os.utime(log_dir, times=(time_, time_))
|
|
38
|
+
log_file = log_dir / "teuthology.log"
|
|
39
|
+
log_file.write_text(content)
|
|
40
|
+
os.utime(log_file, times=(time_, time_))
|
|
41
|
+
return log_file
|
|
42
|
+
|
|
43
|
+
return _create_log_file
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from datetime import datetime
|
|
1
2
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
2
3
|
|
|
3
4
|
import pytest
|
|
@@ -13,7 +14,6 @@ from ceph_devstack.resources.ceph.containers import (
|
|
|
13
14
|
TestNode as _TestNode,
|
|
14
15
|
Teuthology,
|
|
15
16
|
)
|
|
16
|
-
from ceph_devstack.resources.ceph.exceptions import TooManyJobsFound
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class TestCephDevStackServiceSpecs:
|
|
@@ -205,46 +205,21 @@ class TestCephDevStackGetLogFile:
|
|
|
205
205
|
with pytest.raises(FileNotFoundError):
|
|
206
206
|
devstack.get_log_file(run_name, "1")
|
|
207
207
|
|
|
208
|
-
def test_get_log_file_uses_most_recent_when_no_run_name(
|
|
208
|
+
def test_get_log_file_uses_most_recent_when_no_run_name(
|
|
209
|
+
self, tmp_path, create_log_file
|
|
210
|
+
):
|
|
211
|
+
config["data_dir"] = str(tmp_path)
|
|
212
|
+
create_log_file(
|
|
213
|
+
tmp_path, timestamp=datetime(year=2024, month=1, day=1), content="old log"
|
|
214
|
+
)
|
|
215
|
+
new_log_file = create_log_file(
|
|
216
|
+
tmp_path, timestamp=datetime(year=2025, month=1, day=1), content="new log"
|
|
217
|
+
)
|
|
209
218
|
devstack = CephDevStack()
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
# Create two runs
|
|
214
|
-
older_run = "root-2024-01-01_00:00:00-orch:cephadm:smoke-small-main-distro-default-testnode"
|
|
215
|
-
newer_run = "root-2025-01-01_00:00:00-orch:cephadm:smoke-small-main-distro-default-testnode"
|
|
216
|
-
|
|
217
|
-
older_dir = archive_dir / older_run
|
|
218
|
-
older_dir.mkdir()
|
|
219
|
-
older_job = older_dir / "1"
|
|
220
|
-
older_job.mkdir()
|
|
221
|
-
(older_job / "teuthology.log").write_text("old log")
|
|
222
|
-
|
|
223
|
-
newer_dir = archive_dir / newer_run
|
|
224
|
-
newer_dir.mkdir()
|
|
225
|
-
newer_job = newer_dir / "1"
|
|
226
|
-
newer_job.mkdir()
|
|
227
|
-
log_file = newer_job / "teuthology.log"
|
|
228
|
-
log_file.write_text("new log")
|
|
229
|
-
|
|
230
|
-
# Override listdir behavior
|
|
231
|
-
def mock_listdir(path):
|
|
232
|
-
if str(path) == str(archive_dir):
|
|
233
|
-
return [older_run, newer_run]
|
|
234
|
-
if str(path) == str(newer_dir):
|
|
235
|
-
return ["1"]
|
|
236
|
-
return []
|
|
237
|
-
|
|
238
|
-
with patch("ceph_devstack.resources.ceph.Teuthology") as MockTeuthology:
|
|
239
|
-
mock_teuthology = MagicMock()
|
|
240
|
-
mock_teuthology.archive_dir = archive_dir
|
|
241
|
-
MockTeuthology.return_value = mock_teuthology
|
|
219
|
+
result = devstack.get_log_file("", "")
|
|
220
|
+
assert str(result) == str(new_log_file)
|
|
242
221
|
|
|
243
|
-
|
|
244
|
-
result = devstack.get_log_file("", "")
|
|
245
|
-
assert str(result) == str(log_file)
|
|
246
|
-
|
|
247
|
-
def test_get_log_file_raises_too_many_jobs_when_multiple_and_no_job_id(
|
|
222
|
+
def test_get_log_file_returns_latest_job_log_when_multiple_and_no_job_id(
|
|
248
223
|
self, tmp_path
|
|
249
224
|
):
|
|
250
225
|
devstack = CephDevStack()
|
|
@@ -269,17 +244,7 @@ class TestCephDevStackGetLogFile:
|
|
|
269
244
|
mock_teuthology = MagicMock()
|
|
270
245
|
mock_teuthology.archive_dir = archive_dir
|
|
271
246
|
MockTeuthology.return_value = mock_teuthology
|
|
272
|
-
|
|
273
|
-
def mock_listdir(path):
|
|
274
|
-
if str(path) == str(run_dir):
|
|
275
|
-
return ["1", "2"]
|
|
276
|
-
return []
|
|
277
|
-
|
|
278
|
-
with (
|
|
279
|
-
patch("os.listdir", side_effect=mock_listdir),
|
|
280
|
-
pytest.raises(TooManyJobsFound),
|
|
281
|
-
):
|
|
282
|
-
devstack.get_log_file(run_name, "")
|
|
247
|
+
assert devstack.get_log_file(run_name, "").parent.name == "2"
|
|
283
248
|
|
|
284
249
|
|
|
285
250
|
class TestCephDevStackRemove:
|
|
@@ -1,20 +1,18 @@
|
|
|
1
|
-
import os
|
|
2
1
|
import io
|
|
3
2
|
import contextlib
|
|
4
|
-
import
|
|
5
|
-
from datetime import datetime, timedelta
|
|
3
|
+
import pathlib
|
|
6
4
|
import secrets
|
|
7
5
|
import string
|
|
8
6
|
|
|
7
|
+
from datetime import datetime, timedelta
|
|
8
|
+
|
|
9
9
|
import pytest
|
|
10
10
|
|
|
11
11
|
from ceph_devstack import config
|
|
12
12
|
from ceph_devstack.resources.ceph.utils import (
|
|
13
13
|
get_logtimestamp,
|
|
14
|
-
|
|
15
|
-
get_job_id,
|
|
14
|
+
get_jobs,
|
|
16
15
|
)
|
|
17
|
-
from ceph_devstack.resources.ceph.exceptions import TooManyJobsFound
|
|
18
16
|
from ceph_devstack.resources.ceph import CephDevStack
|
|
19
17
|
|
|
20
18
|
|
|
@@ -23,47 +21,30 @@ class TestDevStack:
|
|
|
23
21
|
dirname = "root-2025-03-20_18:34:43-orch:cephadm:smoke-small-main-distro-default-testnode"
|
|
24
22
|
assert get_logtimestamp(dirname) == datetime(2025, 3, 20, 18, 34, 43)
|
|
25
23
|
|
|
26
|
-
def
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
]
|
|
33
|
-
assert (
|
|
34
|
-
get_most_recent_run(runs)
|
|
35
|
-
== "root-2025-03-20_18:34:43-orch:cephadm:smoke-small-main-distro-default-testnode"
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
def test_get_job_id_returns_job_on_unique_job(self):
|
|
39
|
-
jobs = ["97"]
|
|
40
|
-
assert get_job_id(jobs) == "97"
|
|
24
|
+
def test_get_jobs_returns_job_on_unique_job(self, tmp_path):
|
|
25
|
+
temp_dir_path = pathlib.Path(tmp_path)
|
|
26
|
+
job_path = temp_dir_path / "97"
|
|
27
|
+
job_path.mkdir()
|
|
28
|
+
result = get_jobs(temp_dir_path)
|
|
29
|
+
assert len(result) == 1
|
|
30
|
+
assert result[0].name == "97"
|
|
41
31
|
|
|
42
|
-
def
|
|
43
|
-
jobs = []
|
|
32
|
+
def test_get_jobs_throws_filenotfound_on_missing_job(self):
|
|
44
33
|
with pytest.raises(FileNotFoundError):
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def test_get_job_id_throws_toomanyjobsfound_on_more_than_one_job(self):
|
|
48
|
-
jobs = ["1", "2"]
|
|
49
|
-
with pytest.raises(TooManyJobsFound) as exc:
|
|
50
|
-
get_job_id(jobs)
|
|
51
|
-
assert exc.value.jobs == jobs
|
|
34
|
+
get_jobs(pathlib.Path("/fake/path"))
|
|
52
35
|
|
|
53
36
|
async def test_logs_command_display_log_file_of_latest_run(
|
|
54
37
|
self, tmp_path, create_log_file
|
|
55
38
|
):
|
|
56
|
-
data_dir = str(tmp_path)
|
|
57
|
-
config["data_dir"] = data_dir
|
|
39
|
+
config["data_dir"] = str(tmp_path)
|
|
58
40
|
f = io.StringIO()
|
|
59
41
|
content = "custom log content"
|
|
60
|
-
now = datetime.now().strftime("%Y-%m-%d_%H:%M:%S")
|
|
61
|
-
forty_days_ago = (datetime.now() - timedelta(days=40)).strftime(
|
|
62
|
-
"%Y-%m-%d_%H:%M:%S"
|
|
63
|
-
)
|
|
64
42
|
|
|
65
|
-
create_log_file(
|
|
66
|
-
|
|
43
|
+
create_log_file(
|
|
44
|
+
tmp_path,
|
|
45
|
+
timestamp=datetime.now() - timedelta(days=40),
|
|
46
|
+
)
|
|
47
|
+
create_log_file(tmp_path, timestamp=datetime.now(), content=content)
|
|
67
48
|
|
|
68
49
|
with contextlib.redirect_stdout(f):
|
|
69
50
|
devstack = CephDevStack()
|
|
@@ -73,15 +54,17 @@ class TestDevStack:
|
|
|
73
54
|
async def test_logs_display_roughly_contents_of_log_file(
|
|
74
55
|
self, tmp_path, create_log_file
|
|
75
56
|
):
|
|
76
|
-
data_dir = str(tmp_path)
|
|
77
|
-
config["data_dir"] = data_dir
|
|
57
|
+
config["data_dir"] = str(tmp_path)
|
|
78
58
|
f = io.StringIO()
|
|
79
59
|
content = "".join(
|
|
80
60
|
secrets.choice(string.ascii_letters + string.digits)
|
|
81
61
|
for _ in range(6 * 8 * 1024)
|
|
82
62
|
)
|
|
83
|
-
|
|
84
|
-
|
|
63
|
+
create_log_file(
|
|
64
|
+
tmp_path,
|
|
65
|
+
timestamp=datetime.now(),
|
|
66
|
+
content=content,
|
|
67
|
+
)
|
|
85
68
|
|
|
86
69
|
with contextlib.redirect_stdout(f):
|
|
87
70
|
devstack = CephDevStack()
|
|
@@ -91,21 +74,24 @@ class TestDevStack:
|
|
|
91
74
|
async def test_logs_command_display_log_file_of_given_job_id(
|
|
92
75
|
self, tmp_path, create_log_file
|
|
93
76
|
):
|
|
94
|
-
data_dir = str(tmp_path)
|
|
95
|
-
config["data_dir"] = data_dir
|
|
77
|
+
config["data_dir"] = str(tmp_path)
|
|
96
78
|
f = io.StringIO()
|
|
97
79
|
content = "custom log message"
|
|
98
|
-
now = datetime.now()
|
|
80
|
+
now = datetime.now()
|
|
99
81
|
|
|
100
82
|
create_log_file(
|
|
101
|
-
|
|
83
|
+
tmp_path,
|
|
102
84
|
timestamp=now,
|
|
103
85
|
test_type="ceph",
|
|
104
86
|
job_id="1",
|
|
105
87
|
content="another log",
|
|
106
88
|
)
|
|
107
89
|
create_log_file(
|
|
108
|
-
|
|
90
|
+
tmp_path,
|
|
91
|
+
timestamp=now,
|
|
92
|
+
test_type="ceph",
|
|
93
|
+
job_id="2",
|
|
94
|
+
content=content,
|
|
109
95
|
)
|
|
110
96
|
|
|
111
97
|
with contextlib.redirect_stdout(f):
|
|
@@ -116,24 +102,18 @@ class TestDevStack:
|
|
|
116
102
|
async def test_logs_display_content_of_provided_run_name(
|
|
117
103
|
self, tmp_path, create_log_file
|
|
118
104
|
):
|
|
119
|
-
data_dir = str(tmp_path)
|
|
120
|
-
config["data_dir"] = data_dir
|
|
105
|
+
config["data_dir"] = str(tmp_path)
|
|
121
106
|
f = io.StringIO()
|
|
122
107
|
content = "custom content"
|
|
123
|
-
now = datetime.now().strftime("%Y-%m-%d_%H:%M:%S")
|
|
124
|
-
three_days_ago = (datetime.now() - timedelta(days=3)).strftime(
|
|
125
|
-
"%Y-%m-%d_%H:%M:%S"
|
|
126
|
-
)
|
|
127
|
-
|
|
128
108
|
create_log_file(
|
|
129
|
-
|
|
130
|
-
timestamp=now,
|
|
109
|
+
tmp_path,
|
|
110
|
+
timestamp=datetime.now(),
|
|
131
111
|
)
|
|
132
|
-
run_name = create_log_file(
|
|
133
|
-
|
|
134
|
-
timestamp=
|
|
112
|
+
run_name: pathlib.Path = create_log_file(
|
|
113
|
+
tmp_path,
|
|
114
|
+
timestamp=datetime.now() - timedelta(days=3),
|
|
135
115
|
content=content,
|
|
136
|
-
).
|
|
116
|
+
).parent.parent
|
|
137
117
|
|
|
138
118
|
with contextlib.redirect_stdout(f):
|
|
139
119
|
devstack = CephDevStack()
|
|
@@ -143,40 +123,10 @@ class TestDevStack:
|
|
|
143
123
|
async def test_logs_locate_display_file_path_instead_of_config(
|
|
144
124
|
self, tmp_path, create_log_file
|
|
145
125
|
):
|
|
146
|
-
data_dir = str(tmp_path)
|
|
147
|
-
|
|
148
|
-
config["data_dir"] = data_dir
|
|
126
|
+
config["data_dir"] = str(tmp_path)
|
|
149
127
|
f = io.StringIO()
|
|
150
|
-
log_file = create_log_file(
|
|
128
|
+
log_file = create_log_file(tmp_path)
|
|
151
129
|
with contextlib.redirect_stdout(f):
|
|
152
130
|
devstack = CephDevStack()
|
|
153
131
|
await devstack.logs(locate=True)
|
|
154
|
-
assert log_file in f.getvalue()
|
|
155
|
-
|
|
156
|
-
@pytest.fixture(scope="class")
|
|
157
|
-
def create_log_file(self):
|
|
158
|
-
def _create_log_file(data_dir: str, **kwargs):
|
|
159
|
-
parts = {
|
|
160
|
-
"timestamp": (
|
|
161
|
-
datetime.now() - timedelta(days=rd.randint(1, 100))
|
|
162
|
-
).strftime("%Y-%m-%d_%H:%M:%S"),
|
|
163
|
-
"test_type": rd.choice(["ceph", "rgw", "rbd", "mds"]),
|
|
164
|
-
"job_id": rd.randint(1, 100),
|
|
165
|
-
"content": "some log data",
|
|
166
|
-
**kwargs,
|
|
167
|
-
}
|
|
168
|
-
timestamp = parts["timestamp"]
|
|
169
|
-
test_type = parts["test_type"]
|
|
170
|
-
job_id = parts["job_id"]
|
|
171
|
-
content = parts["content"]
|
|
172
|
-
|
|
173
|
-
run_name = f"root-{timestamp}-orch:cephadm:{test_type}-small-main-distro-default-testnode"
|
|
174
|
-
log_dir = f"{data_dir}/archive/{run_name}/{job_id}"
|
|
175
|
-
|
|
176
|
-
os.makedirs(log_dir, exist_ok=True)
|
|
177
|
-
log_file = f"{log_dir}/teuthology.log"
|
|
178
|
-
with open(log_file, "w") as f:
|
|
179
|
-
f.write(content)
|
|
180
|
-
return log_file
|
|
181
|
-
|
|
182
|
-
return _create_log_file
|
|
132
|
+
assert str(log_file) in f.getvalue()
|
|
@@ -8,10 +8,12 @@ ANY_VALUE = "ANY_VALUE"
|
|
|
8
8
|
|
|
9
9
|
class _TestContainerEnvVars:
|
|
10
10
|
@pytest.fixture(scope="class")
|
|
11
|
+
@classmethod
|
|
11
12
|
def cls(self):
|
|
12
13
|
raise NotImplementedError
|
|
13
14
|
|
|
14
15
|
@pytest.fixture(scope="class")
|
|
16
|
+
@classmethod
|
|
15
17
|
def env_vars(self):
|
|
16
18
|
# return {}
|
|
17
19
|
raise NotImplementedError
|
|
@@ -28,10 +30,12 @@ class _TestContainerEnvVars:
|
|
|
28
30
|
|
|
29
31
|
class TestPostgres(_TestContainerEnvVars):
|
|
30
32
|
@pytest.fixture(scope="class")
|
|
33
|
+
@classmethod
|
|
31
34
|
def cls(self):
|
|
32
35
|
return containers.Postgres
|
|
33
36
|
|
|
34
37
|
@pytest.fixture(scope="class")
|
|
38
|
+
@classmethod
|
|
35
39
|
def env_vars(self):
|
|
36
40
|
return {
|
|
37
41
|
"POSTGRES_USER": "root",
|
|
@@ -44,10 +48,12 @@ class TestPostgres(_TestContainerEnvVars):
|
|
|
44
48
|
|
|
45
49
|
class TestPaddles(_TestContainerEnvVars):
|
|
46
50
|
@pytest.fixture(scope="class")
|
|
51
|
+
@classmethod
|
|
47
52
|
def cls(self):
|
|
48
53
|
return containers.Paddles
|
|
49
54
|
|
|
50
55
|
@pytest.fixture(scope="class")
|
|
56
|
+
@classmethod
|
|
51
57
|
def env_vars(self):
|
|
52
58
|
return {
|
|
53
59
|
"PADDLES_SERVER_HOST": "0.0.0.0",
|
|
@@ -56,10 +62,12 @@ class TestPaddles(_TestContainerEnvVars):
|
|
|
56
62
|
|
|
57
63
|
class TestPulpito(_TestContainerEnvVars):
|
|
58
64
|
@pytest.fixture(scope="class")
|
|
65
|
+
@classmethod
|
|
59
66
|
def cls(self):
|
|
60
67
|
return containers.Pulpito
|
|
61
68
|
|
|
62
69
|
@pytest.fixture(scope="class")
|
|
70
|
+
@classmethod
|
|
63
71
|
def env_vars(self):
|
|
64
72
|
return {
|
|
65
73
|
"PULPITO_PADDLES_ADDRESS": "http://paddles:8080",
|
|
@@ -68,10 +76,12 @@ class TestPulpito(_TestContainerEnvVars):
|
|
|
68
76
|
|
|
69
77
|
class TestTestNode(_TestContainerEnvVars):
|
|
70
78
|
@pytest.fixture(scope="class")
|
|
79
|
+
@classmethod
|
|
71
80
|
def cls(self):
|
|
72
81
|
return containers.TestNode
|
|
73
82
|
|
|
74
83
|
@pytest.fixture(scope="class")
|
|
84
|
+
@classmethod
|
|
75
85
|
def env_vars(self):
|
|
76
86
|
return {
|
|
77
87
|
"CEPH_VOLUME_ALLOW_LOOP_DEVICES": "true",
|
|
@@ -80,10 +90,12 @@ class TestTestNode(_TestContainerEnvVars):
|
|
|
80
90
|
|
|
81
91
|
class TestTeuthology(_TestContainerEnvVars):
|
|
82
92
|
@pytest.fixture(scope="class")
|
|
93
|
+
@classmethod
|
|
83
94
|
def cls(self):
|
|
84
95
|
return containers.Teuthology
|
|
85
96
|
|
|
86
97
|
@pytest.fixture(scope="class")
|
|
98
|
+
@classmethod
|
|
87
99
|
def env_vars(self):
|
|
88
100
|
return {
|
|
89
101
|
"SSH_PRIVKEY": "",
|
|
@@ -102,9 +114,11 @@ class TestTeuthology(_TestContainerEnvVars):
|
|
|
102
114
|
|
|
103
115
|
class TestBeanstalk(_TestContainerEnvVars):
|
|
104
116
|
@pytest.fixture(scope="class")
|
|
117
|
+
@classmethod
|
|
105
118
|
def cls(self):
|
|
106
119
|
return containers.Beanstalk
|
|
107
120
|
|
|
108
121
|
@pytest.fixture(scope="class")
|
|
122
|
+
@classmethod
|
|
109
123
|
def env_vars(self):
|
|
110
124
|
return {}
|
|
@@ -27,7 +27,7 @@ class TestContainerResource(_TestPodmanResource, _TestContainerBase):
|
|
|
27
27
|
return Container
|
|
28
28
|
|
|
29
29
|
@pytest.fixture(
|
|
30
|
-
scope="
|
|
30
|
+
scope="function", params=["build", "create", "start", "stop", "remove"]
|
|
31
31
|
)
|
|
32
32
|
def action(self, request):
|
|
33
33
|
return request.param
|
tests/resources/test_misc.py
CHANGED
|
@@ -11,10 +11,11 @@ from .test_podmanresource import (
|
|
|
11
11
|
|
|
12
12
|
class TestMiscResource(_TestPodmanResource):
|
|
13
13
|
@pytest.fixture(scope="class", params=[Network, Secret])
|
|
14
|
+
@classmethod
|
|
14
15
|
def cls(self, request):
|
|
15
16
|
return request.param
|
|
16
17
|
|
|
17
|
-
@pytest.fixture(scope="
|
|
18
|
+
@pytest.fixture(scope="function", params=["create", "exists", "remove"])
|
|
18
19
|
def action(self, request):
|
|
19
20
|
return request.param
|
|
20
21
|
|
|
@@ -10,10 +10,11 @@ from ceph_devstack.resources import PodmanResource
|
|
|
10
10
|
|
|
11
11
|
class TestPodmanResource:
|
|
12
12
|
@pytest.fixture(scope="class")
|
|
13
|
+
@classmethod
|
|
13
14
|
def cls(self):
|
|
14
15
|
return PodmanResource
|
|
15
16
|
|
|
16
|
-
@pytest.fixture(scope="
|
|
17
|
+
@pytest.fixture(scope="function", params=["create", "remove"])
|
|
17
18
|
def action(self, request):
|
|
18
19
|
return request.param
|
|
19
20
|
|
tests/test_config.py
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
|
+
import pytest
|
|
1
2
|
import tomlkit
|
|
2
3
|
|
|
3
4
|
from ceph_devstack import config, Config
|
|
4
5
|
|
|
5
6
|
|
|
7
|
+
@pytest.fixture(scope="function")
|
|
8
|
+
def test_config(tmp_path) -> Config:
|
|
9
|
+
test_config = Config()
|
|
10
|
+
config_file = tmp_path / "test_config.toml"
|
|
11
|
+
config_file.write_text("")
|
|
12
|
+
test_config.load(config_file)
|
|
13
|
+
return test_config
|
|
14
|
+
|
|
15
|
+
|
|
6
16
|
class TestConfigDump:
|
|
7
17
|
def test_config_dump_returns_string(self):
|
|
8
18
|
result = config.dump()
|
|
@@ -54,43 +64,26 @@ class TestConfigGetValue:
|
|
|
54
64
|
|
|
55
65
|
|
|
56
66
|
class TestConfigSet:
|
|
57
|
-
def test_set_value_simple_key(self,
|
|
58
|
-
test_config = Config()
|
|
59
|
-
config_file = tmp_path / "test_config.toml"
|
|
60
|
-
config_file.write_text("")
|
|
61
|
-
test_config.load(config_file)
|
|
67
|
+
def test_set_value_simple_key(self, test_config):
|
|
62
68
|
test_config.set_value("test_key", "test_value")
|
|
63
69
|
assert test_config["test_key"] == "test_value"
|
|
64
70
|
|
|
65
|
-
def
|
|
66
|
-
test_config
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
test_config.load(config_file)
|
|
71
|
+
def test_set_value_returns_value(self, test_config):
|
|
72
|
+
assert test_config.set_value("test_key", "test_value") == "test_value"
|
|
73
|
+
|
|
74
|
+
def test_set_value_nested_key(self, test_config):
|
|
70
75
|
test_config.set_value("test_section.test_key", "test_value")
|
|
71
76
|
assert test_config["test_section"]["test_key"] == "test_value"
|
|
72
77
|
|
|
73
|
-
def test_set_value_updates_user_obj(self,
|
|
74
|
-
test_config = Config()
|
|
75
|
-
config_file = tmp_path / "test_config.toml"
|
|
76
|
-
config_file.write_text("")
|
|
77
|
-
test_config.load(config_file)
|
|
78
|
+
def test_set_value_updates_user_obj(self, test_config):
|
|
78
79
|
test_config.set_value("new_key", "new_value")
|
|
79
80
|
assert "new_key" in test_config.user_obj
|
|
80
81
|
|
|
81
|
-
def test_set_value_creates_intermediate_sections(self,
|
|
82
|
-
test_config = Config()
|
|
83
|
-
config_file = tmp_path / "test_config.toml"
|
|
84
|
-
config_file.write_text("")
|
|
85
|
-
test_config.load(config_file)
|
|
82
|
+
def test_set_value_creates_intermediate_sections(self, test_config):
|
|
86
83
|
test_config.set_value("deep.nested.key", "value")
|
|
87
84
|
assert test_config.user_obj["deep"]["nested"]["key"] == "value"
|
|
88
85
|
|
|
89
|
-
def test_set_value_overrides_existing(self,
|
|
90
|
-
test_config = Config()
|
|
91
|
-
config_file = tmp_path / "test_config.toml"
|
|
92
|
-
config_file.write_text("")
|
|
93
|
-
test_config.load(config_file)
|
|
86
|
+
def test_set_value_overrides_existing(self, test_config):
|
|
94
87
|
original_count = test_config["containers"]["testnode"]["count"]
|
|
95
88
|
new_count = original_count + 2
|
|
96
89
|
test_config.set_value("containers.testnode.count", str(new_count))
|
|
@@ -98,6 +91,15 @@ class TestConfigSet:
|
|
|
98
91
|
assert test_config["containers"]["testnode"]["count"] == new_count
|
|
99
92
|
|
|
100
93
|
|
|
94
|
+
class TestConfigUnset:
|
|
95
|
+
def test_unset_value_simple_key(self, test_config):
|
|
96
|
+
test_config.set_value("test_key", "test_value")
|
|
97
|
+
assert "test_key" in test_config
|
|
98
|
+
assert test_config["test_key"] == "test_value"
|
|
99
|
+
test_config.unset_value("test_key")
|
|
100
|
+
assert "test_key" not in test_config
|
|
101
|
+
|
|
102
|
+
|
|
101
103
|
class TestConfigDefaults:
|
|
102
104
|
def test_config_defaults(self):
|
|
103
105
|
assert config == {
|
tests/test_requirements_core.py
CHANGED
|
@@ -24,6 +24,7 @@ def os_type(request):
|
|
|
24
24
|
|
|
25
25
|
class TestRequirement:
|
|
26
26
|
@pytest.fixture(scope="class")
|
|
27
|
+
@classmethod
|
|
27
28
|
def cls(self):
|
|
28
29
|
class TestReq(requirements.Requirement):
|
|
29
30
|
check_cmd = ["test", "command"]
|
|
@@ -53,6 +54,7 @@ class TestRequirement:
|
|
|
53
54
|
|
|
54
55
|
class TestFixableRequirement:
|
|
55
56
|
@pytest.fixture(scope="class")
|
|
57
|
+
@classmethod
|
|
56
58
|
def cls(self):
|
|
57
59
|
class TestReq(requirements.FixableRequirement):
|
|
58
60
|
check_cmd = ["test", "-f", "/tmp/testfile"]
|
|
@@ -108,6 +110,7 @@ class TestFixableRequirement:
|
|
|
108
110
|
|
|
109
111
|
class TestLocalRequirement:
|
|
110
112
|
@pytest.fixture(scope="class")
|
|
113
|
+
@classmethod
|
|
111
114
|
def cls(self):
|
|
112
115
|
class TestReq(requirements.LocalRequirement):
|
|
113
116
|
check_cmd = ["test"]
|
|
@@ -120,6 +123,7 @@ class TestLocalRequirement:
|
|
|
120
123
|
|
|
121
124
|
class TestPodmanPlatform:
|
|
122
125
|
@pytest.fixture(scope="class")
|
|
126
|
+
@classmethod
|
|
123
127
|
def cls(self):
|
|
124
128
|
return requirements.PodmanPlatform
|
|
125
129
|
|
|
@@ -150,6 +154,7 @@ class TestPodmanPlatform:
|
|
|
150
154
|
|
|
151
155
|
class TestPodmanMachinePresent:
|
|
152
156
|
@pytest.fixture(scope="class")
|
|
157
|
+
@classmethod
|
|
153
158
|
def cls(self):
|
|
154
159
|
return requirements.PodmanMachinePresent
|
|
155
160
|
|
|
@@ -165,6 +170,7 @@ class TestPodmanMachinePresent:
|
|
|
165
170
|
|
|
166
171
|
class TestPodmanMachineRunning:
|
|
167
172
|
@pytest.fixture(scope="class")
|
|
173
|
+
@classmethod
|
|
168
174
|
def cls(self):
|
|
169
175
|
return requirements.PodmanMachineRunning
|
|
170
176
|
|
|
@@ -181,16 +187,19 @@ class TestPodmanMachineRunning:
|
|
|
181
187
|
|
|
182
188
|
class TestPodmanRuntime:
|
|
183
189
|
@pytest.fixture(scope="class")
|
|
190
|
+
@classmethod
|
|
184
191
|
def cls(self):
|
|
185
192
|
return requirements.PodmanRuntime
|
|
186
193
|
|
|
187
194
|
|
|
188
195
|
class TestPodmanVersionInit:
|
|
189
196
|
@pytest.fixture(scope="class")
|
|
197
|
+
@classmethod
|
|
190
198
|
def cls(self):
|
|
191
199
|
return requirements.PodmanVersion
|
|
192
200
|
|
|
193
|
-
@pytest.fixture(scope="
|
|
201
|
+
@pytest.fixture(scope="function")
|
|
202
|
+
@classmethod
|
|
194
203
|
def req(self, cls):
|
|
195
204
|
return cls("4.0.0")
|
|
196
205
|
|
|
@@ -204,10 +213,11 @@ class TestPodmanVersionInit:
|
|
|
204
213
|
|
|
205
214
|
class TestSysctlValueInit:
|
|
206
215
|
@pytest.fixture(scope="class")
|
|
216
|
+
@classmethod
|
|
207
217
|
def cls(self):
|
|
208
218
|
return requirements.SysctlValue
|
|
209
219
|
|
|
210
|
-
@pytest.fixture(scope="
|
|
220
|
+
@pytest.fixture(scope="function")
|
|
211
221
|
def req(self, cls):
|
|
212
222
|
return cls("fs.aio-max-nr", 2097152)
|
|
213
223
|
|
|
@@ -223,10 +233,11 @@ class TestSysctlValueInit:
|
|
|
223
233
|
|
|
224
234
|
class TestSELinuxBooleanInit:
|
|
225
235
|
@pytest.fixture(scope="class")
|
|
236
|
+
@classmethod
|
|
226
237
|
def cls(self):
|
|
227
238
|
return requirements.SELinuxBoolean
|
|
228
239
|
|
|
229
|
-
@pytest.fixture(scope="
|
|
240
|
+
@pytest.fixture(scope="function")
|
|
230
241
|
def req(self, cls):
|
|
231
242
|
return cls("test_bool")
|
|
232
243
|
|
|
@@ -242,6 +253,7 @@ class TestSELinuxBooleanInit:
|
|
|
242
253
|
|
|
243
254
|
class TestFuseOverlayfsPresence:
|
|
244
255
|
@pytest.fixture(scope="class")
|
|
256
|
+
@classmethod
|
|
245
257
|
def cls(self):
|
|
246
258
|
return requirements.FuseOverlayfsPresence
|
|
247
259
|
|
|
@@ -257,6 +269,7 @@ class TestFuseOverlayfsPresence:
|
|
|
257
269
|
|
|
258
270
|
class TestCgroupV2Properties:
|
|
259
271
|
@pytest.fixture(scope="class")
|
|
272
|
+
@classmethod
|
|
260
273
|
def cls(self):
|
|
261
274
|
return requirements.CgroupV2
|
|
262
275
|
|
|
@@ -274,6 +287,7 @@ class TestCgroupV2Properties:
|
|
|
274
287
|
|
|
275
288
|
class TestCgroupV2Check:
|
|
276
289
|
@pytest.fixture(scope="class")
|
|
290
|
+
@classmethod
|
|
277
291
|
def cls(self):
|
|
278
292
|
return requirements.CgroupV2
|
|
279
293
|
|
|
@@ -292,10 +306,11 @@ class TestCgroupV2Check:
|
|
|
292
306
|
|
|
293
307
|
class TestPodmanDNSPluginInit:
|
|
294
308
|
@pytest.fixture(scope="class")
|
|
309
|
+
@classmethod
|
|
295
310
|
def cls(self):
|
|
296
311
|
return requirements.PodmanDNSPlugin
|
|
297
312
|
|
|
298
|
-
@pytest.fixture(scope="
|
|
313
|
+
@pytest.fixture(scope="function")
|
|
299
314
|
def dns_plugin_path(self, os_type):
|
|
300
315
|
if os_type == "centos":
|
|
301
316
|
return "/usr/libexec/cni/dnsname"
|
|
@@ -313,6 +328,7 @@ class TestPodmanDNSPluginInit:
|
|
|
313
328
|
|
|
314
329
|
class TestAppArmorProfile:
|
|
315
330
|
@pytest.fixture(scope="class")
|
|
331
|
+
@classmethod
|
|
316
332
|
def cls(self):
|
|
317
333
|
return requirements.AppArmorProfile
|
|
318
334
|
|
|
@@ -328,6 +344,7 @@ class TestAppArmorProfile:
|
|
|
328
344
|
|
|
329
345
|
class TestFixableRequirementSuggestMsg:
|
|
330
346
|
@pytest.fixture(scope="class")
|
|
347
|
+
@classmethod
|
|
331
348
|
def cls(self):
|
|
332
349
|
class TestReq(requirements.FixableRequirement):
|
|
333
350
|
check_cmd = ["test"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|