Skip to content

Commit ea1ebc3

Browse files
Merge branch 'zmc:main' into main
2 parents 9113182 + 1922584 commit ea1ebc3

File tree

11 files changed

+205
-111
lines changed

11 files changed

+205
-111
lines changed

.github/workflows/ci.yml

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@ jobs:
1717
steps:
1818
- uses: actions/checkout@v2
1919
- name: Install packages
20-
run: sudo apt install podman golang-github-containernetworking-plugin-dnsname
20+
run: sudo apt install podman golang-github-containernetworking-plugin-dnsname sqlite3 jq
2121
- name: Create virtualenv
2222
run: python3 -m venv venv
2323
- name: Install
2424
run: ./venv/bin/pip3 install -e .
2525
- name: Set owner for /dev/loop-control
2626
run: sudo chown $(whoami) /dev/loop-control
27+
- name: Configure
28+
run: ./venv/bin/ceph-devstack config set containers.postgres.count 0
2729
- name: Doctor
2830
run: ./venv/bin/ceph-devstack -v doctor --fix
2931
- name: Build
@@ -37,6 +39,31 @@ jobs:
3739
- name: Dump logs
3840
if: success() || failure()
3941
run: podman logs -f teuthology
42+
- name: Create archive
43+
if: success() || failure()
44+
run: |
45+
mkdir -p /tmp/artifacts
46+
- name: Dump job data
47+
if: success() || failure()
48+
run: |
49+
podman cp paddles:/paddles/dev.db /tmp/
50+
sqlite3 /tmp/dev.db ".output stdout" ".mode json" "select * from jobs" | jq | tee /tmp/artifacts/jobs.json
51+
- name: Upload jobs.json
52+
if: success() || failure()
53+
uses: actions/upload-artifact@v4
54+
with:
55+
name: jobs
56+
path: /tmp/artifacts/jobs.json
57+
- name: Create tarball of log archive
58+
if: success() || failure()
59+
run: |
60+
tar -czf /tmp/artifacts/archive.tar ~/.local/share/ceph-devstack/archive/
61+
- name: Upload log archive
62+
if: success() || failure()
63+
uses: actions/upload-artifact@v4
64+
with:
65+
name: archive
66+
path: /tmp/artifacts/archive.tar
4067
- name: Stop
4168
run: ./venv/bin/ceph-devstack -v stop
4269
- name: Remove

Jenkinsfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ pipeline {
5050
else
5151
python3 -c "import yaml; print(yaml.safe_dump({'containers': {'teuthology': {'repo': '${env.WORKSPACE}/teuthology'}}, 'data_dir': '${env.WORKSPACE}/data'}))" > ${env.CDS_CONF}
5252
fi
53-
ceph-devstack --config-file ${env.CDS_CONF} show-conf
53+
ceph-devstack --config-file ${env.CDS_CONF} config dump
5454
ceph-devstack --config-file ${env.CDS_CONF} doctor
5555
"""
5656
}

README.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ ceph-devstack is a tool that can deploy and manage containerized versions of [te
77
- Accessing Ceph's [Sepia lab](https://wiki.sepia.ceph.com/)
88
- Needing dedicated storage devices to test Ceph OSDs
99

10+
Basically, the goal is that you can test your Ceph branch locally using containers
11+
as storage test nodes.
12+
1013
It is currently under active development and has not yet had a formal release.
1114

1215
## Supported Operating Systems
@@ -54,7 +57,7 @@ python3 -m pip install git+https://github.com/zmc/ceph-devstack.git
5457
## Configuration
5558
`ceph-devstack` 's default configuration is [here](./ceph_devstack/config.yml). It can be extended by placing a file at `~/.config/ceph-devstack/config.yml` or by using the `--config-file` flag.
5659

57-
`ceph-devstack show-conf` will output the current configuration.
60+
`ceph-devstack config dump` will output the current configuration.
5861

5962
As an example, the following configuration will use a local image for paddles with the tag `TEST`; it will also create ten testnode containers; and will build its teuthology container from the git repo at `~/src/teuthology`:
6063
```
@@ -183,6 +186,14 @@ Also add a flag to this command to output filename (full path) instead of conten
183186

184187
Write unit tests for the above feature.
185188

189+
#### Problem Statement
190+
191+
Implement a feature that allows ceph-devstack to to configured to use an arbitrary number of storage devices per testnode container. This will enable us to deploy multiple [Ceph OSDs](https://docs.ceph.com/en/latest/glossary/#term-Ceph-OSD) per testnode - bringing us closer to how we use teuthology in production. Right now, ceph-devstack supports 1 OSD per testnode.
192+
193+
If you have extra time, you might consider also allowing the _size_ of the storage devices to be configurable. The same size can be used for all.
194+
195+
In the future, we may also want to implement a feature that allows ceph-devstack to discover and directly consume unused storage devices on the host machine, as opposed to using loop devices. This would enable more performance-sensitive testing.
196+
186197
#### Connect
187198

188199
Feel free to reach out to us on the [#gsoc-2025-teuthology](https://ceph-storage.slack.com/archives/C08GR4Q8YS0) Slack channel under ceph-storage.slack.com. Use slack invite link at the bottom of [this page](https://ceph.io/en/community/connect/) to join ceph-storage.slack.com workspace.

ceph_devstack/__init__.py

Lines changed: 67 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import argparse
22
import logging.config
3-
import yaml
3+
import tomlkit
4+
import tomlkit.items
5+
import tomlkit.exceptions
46

5-
from pathlib import Path, PosixPath
6-
from typing import List, Optional
7+
from pathlib import Path
8+
from typing import List, Optional, Union
79

810

911
VERBOSE = 15
@@ -12,17 +14,7 @@
1214
logger = logging.getLogger("ceph-devstack")
1315

1416
PROJECT_ROOT = Path(__file__).parent
15-
DEFAULT_CONFIG_PATH = Path("~/.config/ceph-devstack/config.yml")
16-
17-
18-
def represent_path(dumper: yaml.dumper.SafeDumper, data: PosixPath) -> yaml.Node:
19-
return dumper.represent_scalar("tag:yaml.org,2002:str", str(data))
20-
21-
22-
yaml.SafeDumper.add_representer(
23-
PosixPath,
24-
represent_path,
25-
)
17+
DEFAULT_CONFIG_PATH = Path("~/.config/ceph-devstack/config.toml")
2618

2719

2820
def parse_args(args: List[str]) -> argparse.Namespace:
@@ -50,6 +42,14 @@ def parse_args(args: List[str]) -> argparse.Namespace:
5042
help="Path to the ceph-devstack config file",
5143
)
5244
subparsers = parser.add_subparsers(dest="command")
45+
parser_config = subparsers.add_parser("config", help="Get or set config items")
46+
subparsers_config = parser_config.add_subparsers(dest="config_op")
47+
subparsers_config.add_parser("dump", help="show the configuration")
48+
parser_config_get = subparsers_config.add_parser("get")
49+
parser_config_get.add_argument("name")
50+
parser_config_set = subparsers_config.add_parser("set")
51+
parser_config_set.add_argument("name")
52+
parser_config_set.add_argument("value")
5353
parser_doc = subparsers.add_parser(
5454
"doctor", help="Check that the system meets requirements"
5555
)
@@ -113,7 +113,6 @@ def parse_args(args: List[str]) -> argparse.Namespace:
113113
"container",
114114
help="The container to wait for",
115115
)
116-
subparsers.add_parser("show-conf", help="show the configuration")
117116
return parser.parse_args(args)
118117

119118

@@ -128,21 +127,61 @@ def deep_merge(*maps):
128127

129128

130129
class Config(dict):
130+
__slots__ = ["user_obj", "user_path"]
131+
131132
def load(self, config_path: Optional[Path] = None):
132-
self.update(yaml.safe_load((Path(__file__).parent / "config.yml").read_text()))
133+
parsed = tomlkit.parse((Path(__file__).parent / "config.toml").read_text())
134+
self.update(parsed)
133135
if config_path:
134-
user_path = config_path.expanduser()
135-
if user_path.exists():
136-
user_obj = yaml.safe_load(user_path.read_text()) or {}
137-
self.update(deep_merge(config, user_obj))
138-
elif user_path != DEFAULT_CONFIG_PATH.expanduser():
139-
raise OSError(f"Config file at {user_path} not found!")
140-
141-
142-
yaml.SafeDumper.add_representer(
143-
Config,
144-
yaml.representer.SafeRepresenter.represent_dict,
145-
)
136+
self.user_path = config_path.expanduser()
137+
if self.user_path.exists():
138+
self.user_obj: dict = tomlkit.parse(self.user_path.read_text()) or {}
139+
self.update(deep_merge(config, self.user_obj))
140+
elif self.user_path != DEFAULT_CONFIG_PATH.expanduser():
141+
raise OSError(f"Config file at {self.user_path} not found!")
142+
else:
143+
self.user_obj = {}
144+
145+
def dump(self):
146+
return tomlkit.dumps(self)
147+
148+
def get_value(self, name: str) -> str:
149+
path = name.split(".")
150+
obj = config
151+
i = 0
152+
while i < len(path):
153+
sub_path = path[i]
154+
try:
155+
obj = obj[sub_path]
156+
except KeyError:
157+
logger.error(f"{name} not found in config")
158+
raise
159+
i += 1
160+
if isinstance(obj, (str, int, bool)):
161+
return str(obj)
162+
return tomlkit.dumps(obj).strip()
163+
164+
def set_value(self, name: str, value: str):
165+
path = name.split(".")
166+
obj = self.user_obj
167+
i = 0
168+
last_index = len(path) - 1
169+
item: Union[tomlkit.items.Item, str] = value
170+
try:
171+
item = tomlkit.value(item)
172+
except tomlkit.exceptions.UnexpectedCharError:
173+
pass
174+
except tomlkit.exceptions.InternalParserError:
175+
pass
176+
while i <= last_index:
177+
if i < last_index:
178+
obj = obj.setdefault(path[i], {})
179+
elif i == last_index:
180+
obj[path[i]] = item
181+
self.update(self.user_obj)
182+
self.user_path.parent.mkdir(exist_ok=True)
183+
self.user_path.write_text(tomlkit.dumps(self.user_obj).strip())
184+
i += 1
146185

147186

148187
config = Config()

ceph_devstack/cli.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import asyncio
22
import logging
33
import sys
4-
import yaml
54

65
from pathlib import Path
76

@@ -17,8 +16,13 @@ def main():
1716
for handler in logging.getLogger("root").handlers:
1817
if not isinstance(handler, logging.FileHandler):
1918
handler.setLevel(VERBOSE)
20-
if args.command == "show-conf":
21-
print(yaml.safe_dump(config))
19+
if args.command == "config":
20+
if args.config_op == "dump":
21+
print(config.dump())
22+
if args.config_op == "get":
23+
print(config.get_value(args.name))
24+
elif args.config_op == "set":
25+
config.set_value(args.name, args.value)
2226
return
2327
config["args"] = vars(args)
2428
data_path = Path(config["data_dir"]).expanduser()

ceph_devstack/config.toml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
data_dir = "~/.local/share/ceph-devstack"
2+
3+
[containers.archive]
4+
image = "python:alpine"
5+
6+
[containers.beanstalk]
7+
image = "quay.io/ceph-infra/teuthology-beanstalkd:latest"
8+
9+
[containers.paddles]
10+
image = "quay.io/ceph-infra/paddles:latest"
11+
12+
[containers.postgres]
13+
image = "quay.io/ceph-infra/teuthology-postgresql:latest"
14+
15+
[containers.pulpito]
16+
image = "quay.io/ceph-infra/pulpito:latest"
17+
18+
[containers.testnode]
19+
count = 3
20+
image = "quay.io/ceph-infra/teuthology-testnode:latest"
21+
22+
[containers.teuthology]
23+
image = "quay.io/ceph-infra/teuthology-dev:latest"

ceph_devstack/config.yml

Lines changed: 0 additions & 17 deletions
This file was deleted.

0 commit comments

Comments
 (0)