mirror of
https://github.com/openSUSE/osc.git
synced 2025-02-23 02:32:13 +01:00
258 lines
8.4 KiB
Python
258 lines
8.4 KiB
Python
import contextlib
|
|
import queue
|
|
import socket
|
|
import subprocess
|
|
import threading
|
|
|
|
import behave
|
|
|
|
from steps.common import debug
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def get_free_port():
|
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
s.bind(("0.0.0.0", 0))
|
|
s.listen()
|
|
port = s.getsockname()[1]
|
|
yield port
|
|
|
|
|
|
class Podman:
|
|
def __init__(self, context, container_name):
|
|
self.context = context
|
|
self.container_name = container_name
|
|
self.container = None
|
|
debug(self.context, "Podman.__init__()")
|
|
|
|
self.new_container()
|
|
|
|
def __del__(self):
|
|
debug(self.context, "Podman.__del__()")
|
|
try:
|
|
self.kill()
|
|
except Exception:
|
|
pass
|
|
|
|
def kill(self):
|
|
debug(self.context, "Podman.kill()")
|
|
if not self.container:
|
|
return
|
|
self.container.kill()
|
|
self.container = None
|
|
|
|
def new_container(self):
|
|
debug(self.context, "Podman.new_container()")
|
|
# no need to stop the running container
|
|
# becuse the new container replaces an old container with the identical name
|
|
self.container = Container(self.context, name=self.container_name)
|
|
self.container.wait_on_systemd()
|
|
debug(self.context, f"> {self.container}")
|
|
|
|
|
|
class ThreadedPodman:
|
|
def __init__(self, context, container_name_prefix, max_containers=1):
|
|
self.context = context
|
|
self.container = None
|
|
debug(self.context, "ThreadedPodman.__init__()")
|
|
|
|
self.max_containers = max_containers
|
|
self.container_name_prefix = container_name_prefix
|
|
self.container_name_num = 0
|
|
|
|
# produce new containers
|
|
self.container_producer_queue = queue.Queue(maxsize=self.max_containers)
|
|
self.container_producer_queue_is_stopping = threading.Event()
|
|
self.container_producer_queue_is_stopped = threading.Event()
|
|
self.container_producer_thread = threading.Thread(target=self.container_producer, daemon=True)
|
|
self.container_producer_thread.start()
|
|
|
|
# consume (kill) used containers
|
|
self.container_consumer_queue = queue.Queue()
|
|
self.container_consumer_thread = threading.Thread(target=self.container_consumer, daemon=True)
|
|
self.container_consumer_thread.start()
|
|
|
|
self.new_container()
|
|
|
|
def __del__(self):
|
|
debug(self.context, "ThreadedPodman.__del__()")
|
|
try:
|
|
self.kill()
|
|
except Exception:
|
|
pass
|
|
|
|
def kill(self):
|
|
debug(self.context, "ThreadedPodman.kill()")
|
|
self.container_producer_queue_is_stopping.set()
|
|
|
|
container = getattr(self, "container", None)
|
|
if container:
|
|
self.container_consumer_queue.put(container)
|
|
self.container = None
|
|
|
|
while not self.container_producer_queue_is_stopped.is_set():
|
|
try:
|
|
container = self.container_producer_queue.get(block=True, timeout=1)
|
|
self.container_consumer_queue.put(container)
|
|
except queue.Empty:
|
|
continue
|
|
|
|
# 'None' is a signal to finish processing the queue
|
|
self.container_consumer_queue.put(None)
|
|
|
|
self.container_producer_thread.join()
|
|
self.container_consumer_thread.join()
|
|
|
|
def container_producer(self):
|
|
while not self.container_producer_queue_is_stopping.is_set():
|
|
if self.container_name_prefix:
|
|
self.container_name_num += 1
|
|
container_name = f"{self.container_name_prefix}{self.container_name_num}"
|
|
else:
|
|
container_name = None
|
|
container = Container(self.context, name=container_name)
|
|
debug(self.context, f"ThreadedPodman.container_producer() - container created: {self.container_name_num}")
|
|
self.container_producer_queue.put(container, block=True)
|
|
self.container_producer_queue_is_stopped.set()
|
|
|
|
def container_consumer(self):
|
|
while True:
|
|
container = self.container_consumer_queue.get(block=True)
|
|
if container is None:
|
|
break
|
|
container.kill()
|
|
|
|
def new_container(self):
|
|
debug(self.context, "ThreadedPodman.new_container()")
|
|
if getattr(self, "container", None):
|
|
self.container_consumer_queue.put(self.container)
|
|
self.container = self.container_producer_queue.get(block=True)
|
|
self.container.wait_on_systemd()
|
|
debug(self.context, f"> {self.container}")
|
|
|
|
|
|
class Container:
|
|
def __init__(self, context, name=None):
|
|
self.context = context
|
|
debug(self.context, "Container.__init__()")
|
|
self.container_name = name
|
|
self.container_id = None
|
|
self.ports = {}
|
|
self.start()
|
|
|
|
def __del__(self):
|
|
try:
|
|
self.kill()
|
|
except Exception:
|
|
pass
|
|
|
|
def __repr__(self):
|
|
result = super().__repr__()
|
|
result += f"(id:{self.container_id}, name:{self.container_name})"
|
|
return result
|
|
|
|
def _run(self, args, check=True):
|
|
cmd = ["podman"] + args
|
|
debug(self.context, "Running command:", cmd)
|
|
proc = subprocess.run(
|
|
cmd,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
encoding="utf-8",
|
|
check=check,
|
|
)
|
|
debug(self.context, "> return code:", proc.returncode)
|
|
debug(self.context, "> stdout:", proc.stdout)
|
|
debug(self.context, "> stderr:", proc.stderr)
|
|
return proc
|
|
|
|
def start(self, use_proxy_auth: bool = True):
|
|
debug(self.context, "Container.start()")
|
|
args = [
|
|
"run",
|
|
"--hostname", "obs-server-behave",
|
|
]
|
|
if self.container_name:
|
|
args += [
|
|
"--name", self.container_name,
|
|
"--replace",
|
|
"--stop-signal", "SIGKILL",
|
|
]
|
|
args += [
|
|
"--rm",
|
|
"--detach",
|
|
"--interactive",
|
|
"--tty",
|
|
]
|
|
|
|
with get_free_port() as obs_https, get_free_port() as gitea_http, get_free_port() as gitea_ssh:
|
|
# we're using all context managers to reserve all ports at once
|
|
# and close the gap between releasing them and using again in podman
|
|
self.ports = {
|
|
"obs_https": obs_https,
|
|
"gitea_http": gitea_http,
|
|
"gitea_ssh": gitea_ssh,
|
|
}
|
|
|
|
if use_proxy_auth:
|
|
args += [
|
|
# enable proxy auth to bypass http auth that is slow
|
|
"--env", "OBS_PROXY_AUTH=1",
|
|
]
|
|
|
|
args += [
|
|
# obs runs always on 443 in the container
|
|
"-p", f"{obs_https}:443",
|
|
|
|
# gitea runs on random free ports
|
|
# it is configured via env variables and running gitea-configure-from-env.service inside the container
|
|
"-p", f"{gitea_http}:{gitea_http}",
|
|
"--env", f"GITEA_SERVER_HTTP_PORT={gitea_http}",
|
|
"-p", f"{gitea_ssh}:{gitea_ssh}",
|
|
"--env", f"GITEA_SERVER_SSH_PORT={gitea_ssh}",
|
|
]
|
|
|
|
args += [
|
|
"obs-server"
|
|
]
|
|
proc = self._run(args)
|
|
lines = proc.stdout.strip().splitlines()
|
|
self.container_id = lines[-1]
|
|
|
|
def exec(self, args, check=True, interactive=False):
|
|
podman_args = ["exec"]
|
|
if interactive:
|
|
podman_args += ["-it"]
|
|
podman_args += [self.container_id]
|
|
podman_args += args
|
|
return self._run(podman_args, check=check)
|
|
|
|
def kill(self):
|
|
if not self.container_id:
|
|
return
|
|
debug(self.context, "Container.kill()")
|
|
args = ["kill", self.container_id]
|
|
self._run(args)
|
|
self.container_id = None
|
|
|
|
def restart(self):
|
|
debug(self.context, "Container.restart()")
|
|
self.kill()
|
|
self.start()
|
|
|
|
def wait_on_systemd(self):
|
|
debug(self.context, "Container.wait_on_systemd() - start")
|
|
self.exec(["/usr/bin/systemctl", "is-system-running", "--wait"], check=False)
|
|
debug(self.context, "Container.wait_on_systemd() - done")
|
|
|
|
|
|
@behave.step("I start a new container without proxy auth")
|
|
def step_impl(context):
|
|
context.podman.container.kill()
|
|
context.podman.container.container_id = None
|
|
context.podman.container.ports = {}
|
|
context.podman.container.start(use_proxy_auth=False)
|
|
context.podman.container.wait_on_systemd()
|
|
context.osc.write_config()
|
|
context.git_obs.write_config()
|