ceph-devstack 0.2.1__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 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.error(f"{name} not found in config")
157
- raise
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) -> None:
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 = {
@@ -1,6 +1,5 @@
1
1
  import shlex
2
2
 
3
- from pathlib import Path
4
3
  from packaging.version import parse as parse_version, Version
5
4
  from typing import List
6
5
 
@@ -161,7 +160,9 @@ class PodmanVersion(Requirement):
161
160
  return True
162
161
 
163
162
 
164
- class PodmanRuntime(Requirement):
163
+ class PodmanRuntime(FixableRequirement):
164
+ suggest_msg = "Could not find the 'crun' container runtime"
165
+
165
166
  @property
166
167
  def fix_cmd(self):
167
168
  if self.host.os_type != "darwin":
@@ -170,19 +171,8 @@ class PodmanRuntime(Requirement):
170
171
 
171
172
  async def check(self):
172
173
  podman_info = await self.host.podman_info()
173
- storage_conf_path = podman_info["store"]["configFile"]
174
174
  runtime = podman_info["host"]["ociRuntime"]["name"]
175
- if runtime == "crun":
176
- return True
177
- else:
178
- containers_conf_path = Path(storage_conf_path).parent / "containers.conf"
179
- cmd = host.cmd(["podman", "system", "reset"])
180
- logger.error(
181
- f"The configured runtime is '{runtime}'. "
182
- f"It must be set to 'crun' in {containers_conf_path}. "
183
- f"Afterward, run '{cmd}'."
184
- )
185
- return False
175
+ return runtime == "crun"
186
176
 
187
177
 
188
178
  class SELinuxBoolean(FixableRequirement):
@@ -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 get_most_recent_run, get_job_id
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.expanduser()
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
- run_name = get_most_recent_run(os.listdir(archive_dir))
258
- run_dir = archive_dir.joinpath(run_name)
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
- job_id = get_job_id(os.listdir(run_dir))
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():
@@ -411,7 +411,7 @@ class Teuthology(Container):
411
411
  }
412
412
 
413
413
  @property
414
- def archive_dir(self):
414
+ def archive_dir(self) -> Path:
415
415
  return Path(config["data_dir"]) / "archive"
416
416
 
417
417
  async def create(self):
@@ -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 get_most_recent_run(runs: list[str]) -> str:
18
- try:
19
- run_name = next(
20
- iter(
21
- sorted(
22
- (
23
- dirname
24
- for dirname in runs
25
- if RUN_DIRNAME_PATTERN.search(dirname)
26
- ),
27
- key=lambda dirname: get_logtimestamp(dirname),
28
- reverse=True,
29
- )
30
- )
31
- )
32
- return run_name
33
- except StopIteration as e:
34
- raise FileNotFoundError from e
35
-
36
-
37
- def get_job_id(jobs: list[str]):
38
- job_dir_pattern = re.compile(r"^\d+$")
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.2.1
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.10
14
+ Requires-Python: >=3.12
15
15
  Description-Content-Type: text/markdown
16
16
  License-File: LICENSE
17
17
  Requires-Dist: packaging
@@ -0,0 +1,45 @@
1
+ ceph_devstack/Dockerfile.selinux,sha256=5i8leCmaa5Hx8w4zIZcLoq7aTbxxwee44e2phWY7_zY,497
2
+ ceph_devstack/__init__.py,sha256=P5ySWtncR-pkxC8ju6xZUrU6ClnjLSANgkDPftkkEdU,6870
3
+ ceph_devstack/ceph_devstack.pp,sha256=kiVP5xcwYigjKlNoGHVh2PwPKvr6HZZl5NyjY31jXWM,14537
4
+ ceph_devstack/ceph_devstack.te,sha256=68i9sBb1TAEnCJn2JtyM-ECzPSuZck4vKoymcTbptXc,3461
5
+ ceph_devstack/cli.py,sha256=Tl4rQXKt61TPGEJSS9rgs2ycRGo6sBGthDu-7V_u-C8,2122
6
+ ceph_devstack/config.toml,sha256=HsxO3FhwZkHomde69QfElztuN8U1bxk7cQk1_iWB4w0,562
7
+ ceph_devstack/exec.py,sha256=gn95GEQttB6xgdWXXvTsxUxcQsx9TUu6PC8QVQ0_Y7w,2674
8
+ ceph_devstack/host.py,sha256=w_Hx912y1g62yXkTuPJsw_UdUwxJr_Pbxrj8iZtEP7Y,5470
9
+ ceph_devstack/logging.conf,sha256=bGlswYcL_fz522y9EoKZOLInrSss8D44glNq0FievFY,521
10
+ ceph_devstack/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ ceph_devstack/requirements.py,sha256=aE7PjSdcNuD1no12anidTZhjlYxEig8O0iatlz8EeHw,9536
12
+ ceph_devstack/resources/__init__.py,sha256=D0vhJBU9Pqslq7hg2qreW_ZhndcRIOHZVVUA0zAIeDM,3386
13
+ ceph_devstack/resources/container.py,sha256=suJNOQ9fMRfCnXQwvEZOcg4jnPtrxb-d17a3Y1s1xG0,5388
14
+ ceph_devstack/resources/misc.py,sha256=iGD_3Xsqako-dOZrHJFEJbMWoQIzy7Q_KkEnkdBb8m0,552
15
+ ceph_devstack/resources/ceph/__init__.py,sha256=8XqvUiHE5CPHT0ZZ46lyMWss0YqOzgowWMgPl31fElw,9168
16
+ ceph_devstack/resources/ceph/containers.py,sha256=s_YiiiyJS-URHNf_BANMCnDUirJkzrjyzJjOZuKXMQA,11502
17
+ ceph_devstack/resources/ceph/requirements.py,sha256=ZJt7Eeb5y2bOc7dJN3-15ftAyySi80phD7erBsGdqSU,2691
18
+ ceph_devstack/resources/ceph/utils.py,sha256=q82WsEij-D4JVTiLvJmbM1G6ZOuDNI5kIy0wMqkyRQI,1045
19
+ ceph_devstack-0.3.0.dist-info/licenses/LICENSE,sha256=bFeYufyeS1qw0W8pkW1Wj09F2itqX7TAGTnA2NIpApU,1067
20
+ tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
+ tests/conftest.py,sha256=qHW1Mjr3UtyP7SOPkfZiDfd-6aGcYswWRzahqGLvmb4,1300
22
+ tests/test_config.py,sha256=yXkabts7UK73PZm4sPXPLqK5K8cD1r8RgxFOHz7r2Lk,4338
23
+ tests/test_deep_merge.py,sha256=IP9zzCThmhVghl9XjAAiSrKsPdouH3KbEUaKq8n3Soc,2178
24
+ tests/test_parse_args.py,sha256=2JDAT4Id88Ywl5QmiUHqq5IDGm_j5Yl3Kk7pbl3sOEk,7550
25
+ tests/test_requirements_core.py,sha256=p3Nw2nyq5lfuyDyifoC6t4Fls3RrH5IOYSM_nF0N8JA,20390
26
+ tests/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
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
30
+ tests/resources/ceph/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
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
34
+ tests/resources/ceph/test_requirements_ceph.py,sha256=VLPHrzt1dLk3RnzjqRY3wyPUQbMspUuW6S_9cAWtpYU,9134
35
+ tests/resources/ceph/test_ssh_keypair.py,sha256=M8Tc1CY9zb_1WaccMSfnSN35tyjR-CblNtTP0s4OsYU,4019
36
+ tests/resources/ceph/test_testnode.py,sha256=jfoixCR2Dr6X273VPSSkv-H8ribB_B9Fz5izypLRWUQ,1270
37
+ tests/resources/ceph/fixtures/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
+ tests/resources/ceph/fixtures/testnode-config.toml,sha256=XaI4VACYHeshb7LLxN3viKBNxOBLOUpPuwlcQJyQO-U,44
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
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "0.3.0",
3
+ "distance": 0,
4
+ "node": "g74b5f8b0878922261edc5ba01b8937dc4bd02d7c",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-06-26"
8
+ }
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(self, tmp_path):
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
- archive_dir = tmp_path / "archive"
211
- archive_dir.mkdir()
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
- with patch("os.listdir", side_effect=mock_listdir):
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 random as rd
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
- get_most_recent_run,
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 test_get_most_recent_run_returns_most_recent_run(self):
27
- runs = [
28
- "root-2024-02-07_12:23:43-orch:cephadm:smoke-small-devlop-distro-smithi-testnode",
29
- "root-2025-02-20_11:23:43-orch:cephadm:smoke-small-devlop-distro-smithi-testnode",
30
- "root-2025-03-20_18:34:43-orch:cephadm:smoke-small-main-distro-default-testnode",
31
- "root-2025-01-18_18:34:43-orch:cephadm:smoke-small-main-distro-default-testnode",
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 test_get_job_id_throws_filenotfound_on_missing_job(self):
43
- jobs = []
32
+ def test_get_jobs_throws_filenotfound_on_missing_job(self):
44
33
  with pytest.raises(FileNotFoundError):
45
- get_job_id(jobs)
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(data_dir, timestamp=now, content=content)
66
- create_log_file(data_dir, timestamp=forty_days_ago)
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
- now = datetime.now().strftime("%Y-%m-%d_%H:%M:%S")
84
- create_log_file(data_dir, timestamp=now, content=content)
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().strftime("%Y-%m-%d_%H:%M:%S")
80
+ now = datetime.now()
99
81
 
100
82
  create_log_file(
101
- data_dir,
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
- data_dir, timestamp=now, test_type="ceph", job_id="2", content=content
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
- data_dir,
130
- timestamp=now,
109
+ tmp_path,
110
+ timestamp=datetime.now(),
131
111
  )
132
- run_name = create_log_file(
133
- data_dir,
134
- timestamp=three_days_ago,
112
+ run_name: pathlib.Path = create_log_file(
113
+ tmp_path,
114
+ timestamp=datetime.now() - timedelta(days=3),
135
115
  content=content,
136
- ).split("/")[-3]
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(data_dir)
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 {}
@@ -8,6 +8,7 @@ from ceph_devstack import config
8
8
 
9
9
  class TestTestnode:
10
10
  @pytest.fixture(scope="class")
11
+ @classmethod
11
12
  def cls(self) -> type[_TestNode]:
12
13
  return _TestNode
13
14
 
@@ -27,7 +27,7 @@ class TestContainerResource(_TestPodmanResource, _TestContainerBase):
27
27
  return Container
28
28
 
29
29
  @pytest.fixture(
30
- scope="class", params=["build", "create", "start", "stop", "remove"]
30
+ scope="function", params=["build", "create", "start", "stop", "remove"]
31
31
  )
32
32
  def action(self, request):
33
33
  return request.param
@@ -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="class", params=["create", "exists", "remove"])
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="class", params=["create", "remove"])
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, tmp_path):
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 test_set_value_nested_key(self, tmp_path):
66
- test_config = Config()
67
- config_file = tmp_path / "test_config.toml"
68
- config_file.write_text("")
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, tmp_path):
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, tmp_path):
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, tmp_path):
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 == {
@@ -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="class")
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="class")
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="class")
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="class")
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"]
@@ -1,3 +0,0 @@
1
- class TooManyJobsFound(Exception):
2
- def __init__(self, jobs: list[str]):
3
- self.jobs = jobs
@@ -1,44 +0,0 @@
1
- ceph_devstack/Dockerfile.selinux,sha256=5i8leCmaa5Hx8w4zIZcLoq7aTbxxwee44e2phWY7_zY,497
2
- ceph_devstack/__init__.py,sha256=16mCW0UIlCWviTfBOKtWtE6G36xgaYpnno7QjUC2Eg8,6012
3
- ceph_devstack/ceph_devstack.pp,sha256=kiVP5xcwYigjKlNoGHVh2PwPKvr6HZZl5NyjY31jXWM,14537
4
- ceph_devstack/ceph_devstack.te,sha256=68i9sBb1TAEnCJn2JtyM-ECzPSuZck4vKoymcTbptXc,3461
5
- ceph_devstack/cli.py,sha256=lSYwXrwfP8D-k60XuwSqhwZSZNKJFFQfvRM-av1rePM,2057
6
- ceph_devstack/config.toml,sha256=HsxO3FhwZkHomde69QfElztuN8U1bxk7cQk1_iWB4w0,562
7
- ceph_devstack/exec.py,sha256=gn95GEQttB6xgdWXXvTsxUxcQsx9TUu6PC8QVQ0_Y7w,2674
8
- ceph_devstack/host.py,sha256=w_Hx912y1g62yXkTuPJsw_UdUwxJr_Pbxrj8iZtEP7Y,5470
9
- ceph_devstack/logging.conf,sha256=bGlswYcL_fz522y9EoKZOLInrSss8D44glNq0FievFY,521
10
- ceph_devstack/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- ceph_devstack/requirements.py,sha256=tQIk4TVl-1s5vOBgt5oML6Zk9-ZRtPLzAQ3foBdQpMk,9970
12
- ceph_devstack/resources/__init__.py,sha256=D0vhJBU9Pqslq7hg2qreW_ZhndcRIOHZVVUA0zAIeDM,3386
13
- ceph_devstack/resources/container.py,sha256=suJNOQ9fMRfCnXQwvEZOcg4jnPtrxb-d17a3Y1s1xG0,5388
14
- ceph_devstack/resources/misc.py,sha256=iGD_3Xsqako-dOZrHJFEJbMWoQIzy7Q_KkEnkdBb8m0,552
15
- ceph_devstack/resources/ceph/__init__.py,sha256=EAm6dJZ9Hq7HoA2TeInsBd12YnI_zxqHOL_gBoE0IMQ,9265
16
- ceph_devstack/resources/ceph/containers.py,sha256=3QEwkh3I7f3r47SnlnMB-_mU8fYGR08WyUrrbdohmto,11494
17
- ceph_devstack/resources/ceph/exceptions.py,sha256=C4ldyEA7Cukp-QUOwL-MnQ8kpgMDIQ6r_JQe0E9q4eM,101
18
- ceph_devstack/resources/ceph/requirements.py,sha256=ZJt7Eeb5y2bOc7dJN3-15ftAyySi80phD7erBsGdqSU,2691
19
- ceph_devstack/resources/ceph/utils.py,sha256=6-1sageEiLMcx53KHo_GKlIjaGw3swoZk8cQ-e-tDP0,1276
20
- ceph_devstack-0.2.1.dist-info/licenses/LICENSE,sha256=bFeYufyeS1qw0W8pkW1Wj09F2itqX7TAGTnA2NIpApU,1067
21
- tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
- tests/conftest.py,sha256=qulfsMZz3PyAFMdWQ_VWpOFT9L_BiavRax_yQF2Ne24,128
23
- tests/test_config.py,sha256=RLAjt9DAcAo8GQXgv9dc3Ka3hhtongMs6ioz5YQwKhY,4394
24
- tests/test_deep_merge.py,sha256=IP9zzCThmhVghl9XjAAiSrKsPdouH3KbEUaKq8n3Soc,2178
25
- tests/test_parse_args.py,sha256=2JDAT4Id88Ywl5QmiUHqq5IDGm_j5Yl3Kk7pbl3sOEk,7550
26
- tests/test_requirements_core.py,sha256=MnhENRBFQkhIYD9sI1HBYBEocqiDBChRw7bOuSI-kPk,20089
27
- tests/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
- tests/resources/test_container.py,sha256=Xc4IGLf0PViSBIhUQXZHZQtRc-uI2R3PyNjU8lpX6BE,9081
29
- tests/resources/test_misc.py,sha256=dtOAPngMCTE9bYuYjNuxBiRaxvEibXuBq5VwVsWBdnY,1560
30
- tests/resources/test_podmanresource.py,sha256=bQz77l4AosIDyuG1cG89egY9NLuAl-OMvlrkPEJt7us,1765
31
- tests/resources/ceph/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
- tests/resources/ceph/test_cephdevstack_core.py,sha256=2Zopu-3TkXyj1amQbMG1KpsVXyRDJts73e6TLYNyz3Y,18340
33
- tests/resources/ceph/test_devstack.py,sha256=PRsEyYy9EKFIiZKkHYZh6vGffIzlZuW9jA9XEt7xz5M,6201
34
- tests/resources/ceph/test_env_vars.py,sha256=bLTcEJQJSQp0iiJBunPdGHtVQ94KRWSNEkK8HtXZQn0,2741
35
- tests/resources/ceph/test_requirements_ceph.py,sha256=VLPHrzt1dLk3RnzjqRY3wyPUQbMspUuW6S_9cAWtpYU,9134
36
- tests/resources/ceph/test_ssh_keypair.py,sha256=M8Tc1CY9zb_1WaccMSfnSN35tyjR-CblNtTP0s4OsYU,4019
37
- tests/resources/ceph/test_testnode.py,sha256=wiftdOU058z9IKp5LeUNlv81U9XjojRUI0fYt8mrbgg,1253
38
- tests/resources/ceph/fixtures/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
- tests/resources/ceph/fixtures/testnode-config.toml,sha256=XaI4VACYHeshb7LLxN3viKBNxOBLOUpPuwlcQJyQO-U,44
40
- ceph_devstack-0.2.1.dist-info/METADATA,sha256=0sxnNB5VBwU61eY7AlPi14s0LrsR6qUWGzblLEus7uA,6877
41
- ceph_devstack-0.2.1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
42
- ceph_devstack-0.2.1.dist-info/entry_points.txt,sha256=3cXOGOSb23ATcOZVQFBTY_imM4VFgFddXlYi-m8PA10,57
43
- ceph_devstack-0.2.1.dist-info/top_level.txt,sha256=NzTr-vAk2OWL08T8PsmfqkJZNEuezF5oaiHCMJBhHPM,20
44
- ceph_devstack-0.2.1.dist-info/RECORD,,