ceph-devstack 0.2.2__tar.gz → 0.3.0__tar.gz
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-0.2.2 → ceph_devstack-0.3.0}/.github/workflows/ci.yml +10 -5
- ceph_devstack-0.2.2/.github/workflows/tox.yml → ceph_devstack-0.3.0/.github/workflows/pytest.yml +7 -11
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/.github/workflows/release.yml +1 -1
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/PKG-INFO +2 -2
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/ceph_devstack/__init__.py +28 -3
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/ceph_devstack/cli.py +1 -0
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/ceph_devstack/resources/ceph/__init__.py +14 -13
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/ceph_devstack/resources/ceph/containers.py +1 -1
- ceph_devstack-0.3.0/ceph_devstack/resources/ceph/utils.py +38 -0
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/ceph_devstack.egg-info/PKG-INFO +2 -2
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/ceph_devstack.egg-info/SOURCES.txt +3 -4
- ceph_devstack-0.3.0/ceph_devstack.egg-info/scm_file_list.json +55 -0
- ceph_devstack-0.3.0/ceph_devstack.egg-info/scm_version.json +8 -0
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/pyproject.toml +6 -1
- ceph_devstack-0.3.0/tests/conftest.py +43 -0
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/tests/resources/ceph/test_cephdevstack_core.py +15 -50
- ceph_devstack-0.3.0/tests/resources/ceph/test_devstack.py +132 -0
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/tests/resources/ceph/test_env_vars.py +14 -0
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/tests/resources/ceph/test_testnode.py +1 -0
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/tests/resources/test_container.py +1 -1
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/tests/resources/test_misc.py +2 -1
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/tests/resources/test_podmanresource.py +2 -1
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/tests/test_config.py +27 -25
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/tests/test_requirements_core.py +21 -4
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/uv.lock +1 -102
- ceph_devstack-0.2.2/ceph_devstack/resources/ceph/exceptions.py +0 -3
- ceph_devstack-0.2.2/ceph_devstack/resources/ceph/utils.py +0 -45
- ceph_devstack-0.2.2/pytest.ini +0 -3
- ceph_devstack-0.2.2/tests/conftest.py +0 -9
- ceph_devstack-0.2.2/tests/resources/ceph/test_devstack.py +0 -182
- ceph_devstack-0.2.2/tox.ini +0 -9
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/.flake8 +0 -0
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/.gitignore +0 -0
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/.pre-commit-config.yaml +0 -0
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/Jenkinsfile +0 -0
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/LICENSE +0 -0
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/README.md +0 -0
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/build_selinux_module.sh +0 -0
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/ceph_devstack/Dockerfile.selinux +0 -0
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/ceph_devstack/ceph_devstack.pp +0 -0
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/ceph_devstack/ceph_devstack.te +0 -0
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/ceph_devstack/config.toml +0 -0
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/ceph_devstack/exec.py +0 -0
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/ceph_devstack/host.py +0 -0
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/ceph_devstack/logging.conf +0 -0
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/ceph_devstack/py.typed +0 -0
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/ceph_devstack/requirements.py +0 -0
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/ceph_devstack/resources/__init__.py +0 -0
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/ceph_devstack/resources/ceph/requirements.py +0 -0
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/ceph_devstack/resources/container.py +0 -0
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/ceph_devstack/resources/misc.py +0 -0
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/ceph_devstack.egg-info/dependency_links.txt +0 -0
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/ceph_devstack.egg-info/entry_points.txt +0 -0
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/ceph_devstack.egg-info/requires.txt +0 -0
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/ceph_devstack.egg-info/top_level.txt +0 -0
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/docs/cgroup_v2.md +0 -0
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/ruff.toml +0 -0
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/setup.cfg +0 -0
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/tests/__init__.py +0 -0
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/tests/resources/__init__.py +0 -0
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/tests/resources/ceph/__init__.py +0 -0
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/tests/resources/ceph/fixtures/__init__.py +0 -0
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/tests/resources/ceph/fixtures/testnode-config.toml +0 -0
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/tests/resources/ceph/test_requirements_ceph.py +0 -0
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/tests/resources/ceph/test_ssh_keypair.py +0 -0
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/tests/test_deep_merge.py +0 -0
- {ceph_devstack-0.2.2 → ceph_devstack-0.3.0}/tests/test_parse_args.py +0 -0
|
@@ -10,7 +10,7 @@ jobs:
|
|
|
10
10
|
name: Check code style
|
|
11
11
|
runs-on: ubuntu-24.04
|
|
12
12
|
steps:
|
|
13
|
-
- uses: actions/checkout@
|
|
13
|
+
- uses: actions/checkout@v7
|
|
14
14
|
- name: Install precommit
|
|
15
15
|
run: pip install pre-commit
|
|
16
16
|
- name: Check lint
|
|
@@ -26,8 +26,10 @@ jobs:
|
|
|
26
26
|
include:
|
|
27
27
|
- os: ubuntu-24.04
|
|
28
28
|
python: "3.12"
|
|
29
|
+
- os: ubuntu-26.04
|
|
30
|
+
python: "3.14"
|
|
29
31
|
steps:
|
|
30
|
-
- uses: actions/checkout@
|
|
32
|
+
- uses: actions/checkout@v7
|
|
31
33
|
- name: Install packages
|
|
32
34
|
run: sudo apt-get update && sudo apt-get install podman golang-github-containernetworking-plugin-dnsname sqlite3 jq
|
|
33
35
|
- name: Install uv
|
|
@@ -53,9 +55,12 @@ jobs:
|
|
|
53
55
|
run: podman exec testnode_0 journalctl
|
|
54
56
|
- name: Wait
|
|
55
57
|
run: uv run ceph-devstack wait teuthology
|
|
56
|
-
- name: Dump
|
|
58
|
+
- name: Dump dispatcher log
|
|
57
59
|
if: success() || failure()
|
|
58
60
|
run: podman logs -f teuthology
|
|
61
|
+
- name: Dump job log
|
|
62
|
+
if: success() || failure()
|
|
63
|
+
run: uv run ceph-devstack logs
|
|
59
64
|
- name: Create archive
|
|
60
65
|
if: success() || failure()
|
|
61
66
|
run: |
|
|
@@ -67,7 +72,7 @@ jobs:
|
|
|
67
72
|
sqlite3 /tmp/dev.db ".output stdout" ".mode json" "select * from jobs" | jq | tee /tmp/artifacts/jobs.json
|
|
68
73
|
- name: Upload jobs.json
|
|
69
74
|
if: success() || failure()
|
|
70
|
-
uses: actions/upload-artifact@
|
|
75
|
+
uses: actions/upload-artifact@v7.0.1
|
|
71
76
|
with:
|
|
72
77
|
name: jobs
|
|
73
78
|
path: /tmp/artifacts/jobs.json
|
|
@@ -77,7 +82,7 @@ jobs:
|
|
|
77
82
|
tar -czf /tmp/artifacts/archive.tar ~/.local/share/ceph-devstack/archive/
|
|
78
83
|
- name: Upload log archive
|
|
79
84
|
if: success() || failure()
|
|
80
|
-
uses: actions/upload-artifact@
|
|
85
|
+
uses: actions/upload-artifact@v7.0.1
|
|
81
86
|
with:
|
|
82
87
|
name: archive
|
|
83
88
|
path: /tmp/artifacts/archive.tar
|
ceph_devstack-0.2.2/.github/workflows/tox.yml → ceph_devstack-0.3.0/.github/workflows/pytest.yml
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
name:
|
|
1
|
+
name: pytest
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
4
|
pull_request:
|
|
@@ -8,26 +8,22 @@ on:
|
|
|
8
8
|
|
|
9
9
|
jobs:
|
|
10
10
|
test:
|
|
11
|
-
name:
|
|
11
|
+
name: pytest on python${{ matrix.python }} via ${{ matrix.os }}
|
|
12
12
|
runs-on: ${{ matrix.os }}
|
|
13
13
|
strategy:
|
|
14
14
|
matrix:
|
|
15
15
|
include:
|
|
16
|
-
- os: ubuntu-22.04
|
|
17
|
-
python: "3.10"
|
|
18
|
-
- os: ubuntu-22.04
|
|
19
|
-
python: "3.11"
|
|
20
16
|
- os: ubuntu-24.04
|
|
21
17
|
python: "3.12"
|
|
22
|
-
- os: ubuntu-
|
|
23
|
-
python: "3.
|
|
18
|
+
- os: ubuntu-26.04
|
|
19
|
+
python: "3.14"
|
|
24
20
|
steps:
|
|
25
|
-
- uses: actions/checkout@
|
|
21
|
+
- uses: actions/checkout@v7
|
|
26
22
|
- name: Setup Python
|
|
27
|
-
uses: actions/setup-python@
|
|
23
|
+
uses: actions/setup-python@v6.3.0
|
|
28
24
|
with:
|
|
29
25
|
python-version: ${{ matrix.python }}
|
|
30
26
|
- name: Install uv
|
|
31
27
|
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
|
32
28
|
- name: Run unit tests
|
|
33
|
-
run: uv run --
|
|
29
|
+
run: uv run --all-extras pytest
|
|
@@ -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
|
|
@@ -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()
|
|
@@ -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():
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import pathlib
|
|
2
|
+
import re
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
RUN_DIRNAME_PATTERN = re.compile(
|
|
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})"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_logtimestamp(dirname: str) -> datetime:
|
|
12
|
+
match_ = RUN_DIRNAME_PATTERN.search(dirname)
|
|
13
|
+
assert match_
|
|
14
|
+
return datetime.strptime(match_.group("timestamp"), "%Y-%m-%d_%H:%M:%S")
|
|
15
|
+
|
|
16
|
+
|
|
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
|
|
@@ -6,13 +6,11 @@ LICENSE
|
|
|
6
6
|
README.md
|
|
7
7
|
build_selinux_module.sh
|
|
8
8
|
pyproject.toml
|
|
9
|
-
pytest.ini
|
|
10
9
|
ruff.toml
|
|
11
|
-
tox.ini
|
|
12
10
|
uv.lock
|
|
13
11
|
.github/workflows/ci.yml
|
|
12
|
+
.github/workflows/pytest.yml
|
|
14
13
|
.github/workflows/release.yml
|
|
15
|
-
.github/workflows/tox.yml
|
|
16
14
|
ceph_devstack/Dockerfile.selinux
|
|
17
15
|
ceph_devstack/__init__.py
|
|
18
16
|
ceph_devstack/ceph_devstack.pp
|
|
@@ -29,13 +27,14 @@ ceph_devstack.egg-info/SOURCES.txt
|
|
|
29
27
|
ceph_devstack.egg-info/dependency_links.txt
|
|
30
28
|
ceph_devstack.egg-info/entry_points.txt
|
|
31
29
|
ceph_devstack.egg-info/requires.txt
|
|
30
|
+
ceph_devstack.egg-info/scm_file_list.json
|
|
31
|
+
ceph_devstack.egg-info/scm_version.json
|
|
32
32
|
ceph_devstack.egg-info/top_level.txt
|
|
33
33
|
ceph_devstack/resources/__init__.py
|
|
34
34
|
ceph_devstack/resources/container.py
|
|
35
35
|
ceph_devstack/resources/misc.py
|
|
36
36
|
ceph_devstack/resources/ceph/__init__.py
|
|
37
37
|
ceph_devstack/resources/ceph/containers.py
|
|
38
|
-
ceph_devstack/resources/ceph/exceptions.py
|
|
39
38
|
ceph_devstack/resources/ceph/requirements.py
|
|
40
39
|
ceph_devstack/resources/ceph/utils.py
|
|
41
40
|
docs/cgroup_v2.md
|
|
@@ -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
|
+
}
|
|
@@ -12,7 +12,7 @@ classifiers = [
|
|
|
12
12
|
]
|
|
13
13
|
keywords = ["podman", "ceph"]
|
|
14
14
|
description = "Run a full teuthology lab on your laptop!"
|
|
15
|
-
requires-python = ">=3.
|
|
15
|
+
requires-python = ">=3.12"
|
|
16
16
|
dependencies = ["packaging", "pre-commit", "PyYAML", "tomlkit"]
|
|
17
17
|
dynamic = ["version"]
|
|
18
18
|
|
|
@@ -45,3 +45,8 @@ requires = [
|
|
|
45
45
|
"wheel",
|
|
46
46
|
"setuptools-scm>=8",
|
|
47
47
|
]
|
|
48
|
+
|
|
49
|
+
[tool.pytest.ini_options]
|
|
50
|
+
testpaths = ["tests"]
|
|
51
|
+
asyncio_mode = "auto"
|
|
52
|
+
asyncio_default_fixture_loop_scope = "function"
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import pathlib
|
|
3
|
+
import pytest
|
|
4
|
+
import random
|
|
5
|
+
|
|
6
|
+
from datetime import datetime, timedelta
|
|
7
|
+
|
|
8
|
+
from ceph_devstack import config
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.fixture(autouse=True)
|
|
12
|
+
def reset_config():
|
|
13
|
+
config.load()
|
|
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:
|