async def to_thread(func: Callable[..., T], *args: Any, **kwargs: Any) -> T:
# version 3.9 and higher, call directly
if sys.version_info.major >= 3 and sys.version_info.minor >= 9:
- return asyncio.to_thread(func, *args, **kwargs)
+ return await asyncio.to_thread(func, *args, **kwargs)
# earlier versions, run with default executor
else:
# set keyd arguments
for key, val in kwargs.items():
- assert key in anot, f"Key '{key}' not defined with a type annotation"
+ assert key in anot, (
+ f"Constructing dataclass with an argument '{key}' which is not defined with a type"
+ f" annotation in class {cls.__name__}"
+ )
setattr(slf, key, val)
used.add(key)
int_val = ignore_exceptions_optional(int, None, ValueError)(int)(val)
if int_val is not None:
res = KresID(int_val)
- assert res not in _used
+ assert res not in _used, "Force allocating a KresID, which already exists..."
_used.add(res)
return res
else:
await self._stop_gc()
async def stop(self):
+ if self._watchdog_task is not None:
+ self._watchdog_task.cancel()
+
async with self._manager_lock:
await self._ensure_number_of_children(KresConfig(), 0)
await self._controller.shutdown_controller()
- if self._watchdog_task is not None:
- self._watchdog_task.cancel()
-
async def _instability_handler(self) -> None:
logger.error(
"Instability callback invoked. Something is wrong, no idea how to react."
supervisord_subprocess_log_dir,
)
from knot_resolver_manager.datamodel.config_schema import KresConfig
-from knot_resolver_manager.kres_id import KresID, lookup_from_string
+from knot_resolver_manager.kres_id import KresID, alloc_from_string, lookup_from_string
from knot_resolver_manager.kresd_controller.interface import (
Subprocess,
SubprocessController,
Data structure holding data for supervisord config template
"""
+ type: str
logfile: str
id: str
workdir: str
async def _write_config_file(config: KresConfig, instances: Set["SupervisordSubprocess"]):
@dataclass
class SupervisordConfig:
- type: str
unix_http_server: str
pid_file: str
workdir: str
config_string = Template(template).render(
instances=[
_Instance(
- type=i.type,
+ type=i.type.name,
logfile=supervisord_subprocess_log_dir(config) / f"{i.id}.log",
id=str(i.id),
workdir=str(os.getcwd()),
)
for i in instances
],
- config={
- SupervisordConfig(
- unix_http_server=supervisord_sock_file(config),
- pid_file=supervisord_pid_file(config),
- workdir=str(config.server.management.rundir.to_path().absolute()),
- log_file=supervisord_log_file(config),
- )
- },
+ config=SupervisordConfig(
+ unix_http_server=supervisord_sock_file(config),
+ pid_file=supervisord_pid_file(config),
+ workdir=str(config.server.management.rundir.to_path().absolute()),
+ logfile=supervisord_log_file(config),
+ ),
)
await writefile(supervisord_config_file_tmp(config), config_string)
# atomically replace
for section in cp.sections():
if section.startswith("program:"):
program_id = section.replace("program:", "")
- iid = lookup_from_string(program_id)
+ iid = alloc_from_string(program_id)
typ = SubprocessType[cp[section].get("type")]
res.append((typ, iid))
return res
return SupervisordSubprocess(subprocess_config, self, id_hint, subprocess_type)
async def get_subprocess_status(self) -> Dict[KresID, SubprocessStatus]:
- return await to_thread(_list_subprocesses)
+ return await to_thread(_list_subprocesses, self._controller_config)
pidfile = {{ config.pid_file }}
directory = {{ config.workdir }}
nodaemon = false
-logfile = {{ config.log_file }}
+logfile = {{ config.logfile }}
logfile_maxbytes = 50MB
{# user=root #}
version = "0.1.0"
description = "A central management tool for multiple instances of Knot Resolver"
authors = [
- "Vašek Šraier <git@vakabus.cz>",
+ "Václav Šraier <vaclav.sraier@nic.cz>",
"Aleš Mrázek <ales.mrazek@nic.cz>"
]
docs = { cmd = "scripts/docs", help = "Create HTML documentation" }
test = { cmd = "pytest --cov=knot_resolver_manager --show-capture=all tests/", help = "Run tests" }
check = { cmd = "scripts/codecheck", help = "Run static code analysis" }
-format = { shell = "poetry run black knot_resolver_manager/ tests/ integration/; isort .", help = "Run code formatter" }
+format = { shell = "black knot_resolver_manager/ tests/ integration/ scripts/; isort .", help = "Run code formatter" }
fixdeps = { shell = "poetry install; npm install; npm update", help = "Install/update dependencies according to configuration files"}
commit = { shell = "scripts/commit", help = "Invoke every single check before commiting" }
container = { cmd = "scripts/container.py", help = "Manage containers" }
# check formatting using black
echo -e "${yellow}Checking formatting using black...${reset}"
-black knot_resolver_manager tests integration --check --diff
+black knot_resolver_manager tests integration scripts --check --diff
check_rv $?
echo
def _get_git_root() -> Path:
- result = subprocess.run(
- "git rev-parse --show-toplevel", shell=True, stdout=subprocess.PIPE
- )
+ result = subprocess.run("git rev-parse --show-toplevel", shell=True, stdout=subprocess.PIPE)
return Path(str(result.stdout, encoding="utf8").strip())
CACHE_DIR: Path = GIT_ROOT / ".podman-cache"
-
-
-def _start_detached(
- image: str, publish: List[int] = [], ro_mounts: Dict[Path, Path] = {}
-) -> str:
+def _start_detached(image: str, publish: List[int] = [], ro_mounts: Dict[Path, Path] = {}) -> str:
"""Start a detached container"""
options = [f"--publish={port}:{port}/tcp" for port in publish] + [
- f"--volume={str(src)}:{str(dst)}:O"
- for src, dst in ro_mounts.items()
+ f"--volume={str(src)}:{str(dst)}:O" for src, dst in ro_mounts.items()
]
command = ["podman", "run", "--rm", "-d", "--security-opt=seccomp=unconfined", *options, image]
- proc = subprocess.run(
- command, shell=False, executable=PODMAN_EXECUTABLE, stdout=subprocess.PIPE
- )
+ proc = subprocess.run(command, shell=False, executable=PODMAN_EXECUTABLE, stdout=subprocess.PIPE)
assert proc.returncode == 0
return str(proc.stdout, "utf8").strip()
if ":" in name:
s = name.split(":")
if not s[0].endswith("knot-manager"):
- click.secho(f"Unexpected image name \'{s[0]}\', expected \'knot-manager\'", fg="red")
+ click.secho(f"Unexpected image name '{s[0]}', expected 'knot-manager'", fg="red")
sys.exit(1)
name = s[-1]
-
+
if not name in all:
- click.secho(f"Unexpected tag \'{name}\'", fg="red")
+ click.secho(f"Unexpected tag '{name}'", fg="red")
click.secho(f"Available tags are [{' '.join(all)}]", fg="yellow")
sys.exit(1)
-
+
return name
+
def _get_tags_to_work_on(args: List[str]) -> List[str]:
args = list(args)
all = _list_available_image_tags()
# convert to tags, if the user specified full names
- for i,a in enumerate(args):
+ for i, a in enumerate(args):
args[i] = _extract_tag_from_name(a, all)
if len(args) == 0:
def _build(tag: str):
- command = ["podman", "build", "--security-opt=seccomp=unconfined", "-f", str(GIT_ROOT / "containers" / tag / "Containerfile"), "-t", _full_name_from_tag(tag), str(GIT_ROOT)]
+ command = [
+ "podman",
+ "build",
+ "--security-opt=seccomp=unconfined",
+ "-f",
+ str(GIT_ROOT / "containers" / tag / "Containerfile"),
+ "-t",
+ _full_name_from_tag(tag),
+ str(GIT_ROOT),
+ ]
ret = subprocess.call(command, shell=False, executable=PODMAN_EXECUTABLE)
assert ret == 0
def _login_ci():
- command = ["podman", "login", "-u", environ["CI_REGISTRY_USER"], "-p", environ["CI_REGISTRY_PASSWORD"], environ["CI_REGISTRY"]]
+ command = [
+ "podman",
+ "login",
+ "-u",
+ environ["CI_REGISTRY_USER"],
+ "-p",
+ environ["CI_REGISTRY_PASSWORD"],
+ environ["CI_REGISTRY"],
+ ]
ret = subprocess.call(command, shell=False, executable=PODMAN_EXECUTABLE)
assert ret == 0
def _save(tag: str):
CACHE_DIR.mkdir(exist_ok=True)
- command = ["podman", "save", "--format", "oci-archive", "-o", str(CACHE_DIR / (tag + ".tar")), _full_name_from_tag(tag)]
+ command = [
+ "podman",
+ "save",
+ "--format",
+ "oci-archive",
+ "-o",
+ str(CACHE_DIR / (tag + ".tar")),
+ _full_name_from_tag(tag),
+ ]
ret = subprocess.call(command, shell=False, executable=PODMAN_EXECUTABLE)
assert ret == 0
+
def _load(tag: str):
cache_file = CACHE_DIR / (tag + ".tar")
if cache_file.exists():
assert ret == 0
-
@click.group()
def main():
pass
_pull(tag)
-
@main.command(help="Build project containers")
@click.argument("images", nargs=-1)
@click.option("-f", "--fetch", "fetch", is_flag=True, default=False, type=bool, help="Pull before building")
if ci_login:
_login_ci()
-
+
for tag in tags:
if fetch:
click.secho(f"Pulling image with tag {tag}", fg="yellow")
_pull(tag)
-
+
if file_cache:
_load(tag)
if push:
click.secho(f"Pushing image with {tag}", fg="yellow")
_push(tag)
-
+
if file_cache:
_save(tag)
@main.command(help="Run project containers")
@click.argument("image", nargs=1)
@click.argument("command", nargs=-1)
-@click.option(
- "-p", "--publish", "publish", multiple=True, type=int, help="Port which should be published"
-)
+@click.option("-p", "--publish", "publish", multiple=True, type=int, help="Port which should be published")
@click.option(
"-m",
"--mount",
default=False,
is_flag=True,
type=bool,
- help="Drop into interactive shell if the command fails"
+ help="Drop into interactive shell if the command fails",
)
def run(
image: str,
# register cleanup function
def cleanup():
_stop(cont)
+
atexit.register(cleanup)
# wait for the container to boot properly
# poetry when parsing the pyproject.toml.
import os
+import re
import sys
from distutils.version import StrictVersion
-import re
from typing import List
-
# If there is a global installation of poetry, prefer that.
lib = os.path.expanduser("~/.poetry/lib")
vendors = os.path.join(lib, "poetry", "_vendor")
-current_vendors = os.path.join(
- vendors, "py{}".format(".".join(str(v) for v in sys.version_info[:2]))
-)
+current_vendors = os.path.join(vendors, "py{}".format(".".join(str(v) for v in sys.version_info[:2])))
sys.path.insert(0, lib)
sys.path.insert(0, current_vendors)
from poetry.factory import Factory
from poetry.__version__ import __version__
except (ImportError, ModuleNotFoundError) as ee:
- raise ImportError(
- f"install poetry by doing pip install poetry to use this script: {ee}"
- )
+ raise ImportError(f"install poetry by doing pip install poetry to use this script: {ee}")
# Generate a Poetry object that knows about the metadata in pyproject.toml
# patch the result so that it does not contain upper bounds in dependencies
# (but it should contain them in python version)
-setuppy = setuppy_blob.decode('utf8')
-setuppy, _ = re.subn(r'(\'[^\']+>=[^<>=,\']*),<[^<>=,\']*\'', '\\1\'', setuppy)
+setuppy = setuppy_blob.decode("utf8")
+setuppy, _ = re.subn(r"(\'[^\']+>=[^<>=,\']*),<[^<>=,\']*\'", "\\1'", setuppy)
# output the setup.py script to stdout
print(setuppy)
'version': '0.1.0',
'description': 'A central management tool for multiple instances of Knot Resolver',
'long_description': None,
- 'author': 'Vašek Šraier',
- 'author_email': 'git@vakabus.cz',
+ 'author': 'Václav Šraier',
+ 'author_email': 'vaclav.sraier@nic.cz',
'maintainer': None,
'maintainer_email': None,
'url': None,