|
|
64309b9 |
From c4078c160caad8c019c4e890757fbde34aa83f60 Mon Sep 17 00:00:00 2001
|
|
|
64309b9 |
From: Jan Kaluza <jkaluza@redhat.com>
|
|
|
64309b9 |
Date: Dec 08 2017 13:30:51 +0000
|
|
|
64309b9 |
Subject: If pungi_runroot_enabled is true, use koji runroot also to remove expired composes.
|
|
|
64309b9 |
|
|
|
64309b9 |
|
|
|
64309b9 |
---
|
|
|
64309b9 |
|
|
|
64309b9 |
diff --git a/server/odcs/server/backend.py b/server/odcs/server/backend.py
|
|
|
64309b9 |
index c7c77b9..539b23d 100644
|
|
|
64309b9 |
--- a/server/odcs/server/backend.py
|
|
|
64309b9 |
+++ b/server/odcs/server/backend.py
|
|
|
64309b9 |
@@ -143,6 +143,33 @@ class RemoveExpiredComposesThread(BackendThread):
|
|
|
64309b9 |
else:
|
|
|
64309b9 |
shutil.rmtree(toplevel_dir)
|
|
|
64309b9 |
|
|
|
64309b9 |
+ def _get_remove_compose_dir_cmds(self, toplevel_dir):
|
|
|
64309b9 |
+ """
|
|
|
64309b9 |
+ Returns the commands needed to remove the compose with toplevel_dir
|
|
|
64309b9 |
+ compose dir as list.
|
|
|
64309b9 |
+ """
|
|
|
64309b9 |
+
|
|
|
64309b9 |
+ # Be nice and don't fail when directory does not exist.
|
|
|
64309b9 |
+ if not os.path.exists(toplevel_dir):
|
|
|
64309b9 |
+ log.warn("Cannot remove directory %s, it does not exist",
|
|
|
64309b9 |
+ toplevel_dir)
|
|
|
64309b9 |
+ return []
|
|
|
64309b9 |
+
|
|
|
64309b9 |
+ cmds = []
|
|
|
64309b9 |
+
|
|
|
64309b9 |
+ # If toplevel_dir is a symlink, remove the symlink and
|
|
|
64309b9 |
+ # its target. If toplevel_dir is normal directory, just
|
|
|
64309b9 |
+ # remove it using rmtree.
|
|
|
64309b9 |
+ if os.path.realpath(toplevel_dir) != toplevel_dir:
|
|
|
64309b9 |
+ targetpath = os.path.realpath(toplevel_dir)
|
|
|
64309b9 |
+ cmds += ["rm", "-f", toplevel_dir]
|
|
|
64309b9 |
+ if os.path.exists(targetpath):
|
|
|
64309b9 |
+ cmds += ["&&", "rm", "-rf", targetpath]
|
|
|
64309b9 |
+ else:
|
|
|
64309b9 |
+ cmds += ["rm", "-rf", toplevel_dir]
|
|
|
64309b9 |
+
|
|
|
64309b9 |
+ return cmds
|
|
|
64309b9 |
+
|
|
|
64309b9 |
def _get_compose_id_from_path(self, path):
|
|
|
64309b9 |
"""
|
|
|
64309b9 |
Returns the ID of compose from directory path in conf.target_dir.
|
|
|
64309b9 |
@@ -163,6 +190,15 @@ class RemoveExpiredComposesThread(BackendThread):
|
|
|
64309b9 |
"""
|
|
|
64309b9 |
log.info("Checking for expired composes")
|
|
|
64309b9 |
|
|
|
64309b9 |
+ # We store all the compose directories to remove in this list and
|
|
|
64309b9 |
+ # removes them all in the end of this method.
|
|
|
64309b9 |
+ # The reason to do it this way is that if "conf.pungi_runroot_enabled"
|
|
|
64309b9 |
+ # is True, we spawn Koji runroot task which calls "rm" commands for
|
|
|
64309b9 |
+ # every compose directory we want to remove. We need a list
|
|
|
64309b9 |
+ # of directories to be able to generate that set of rm commands later
|
|
|
64309b9 |
+ # if neede and if not, just remove the directories by this thread.
|
|
|
64309b9 |
+ compose_dirs_to_remove = []
|
|
|
64309b9 |
+
|
|
|
64309b9 |
composes = Compose.composes_to_expire()
|
|
|
64309b9 |
for compose in composes:
|
|
|
64309b9 |
log.info("%r: Removing compose", compose)
|
|
|
64309b9 |
@@ -170,7 +206,7 @@ class RemoveExpiredComposesThread(BackendThread):
|
|
|
64309b9 |
compose.time_removed = datetime.utcnow()
|
|
|
64309b9 |
db.session.commit()
|
|
|
64309b9 |
if not compose.reused_id:
|
|
|
64309b9 |
- self._remove_compose_dir(compose.toplevel_dir)
|
|
|
64309b9 |
+ compose_dirs_to_remove.append(str(compose.toplevel_dir))
|
|
|
64309b9 |
|
|
|
64309b9 |
# In case of ODCS error, there might be left-over directories
|
|
|
64309b9 |
# belonging to already expired composes. Try to find them in the
|
|
|
64309b9 |
@@ -197,16 +233,34 @@ class RemoveExpiredComposesThread(BackendThread):
|
|
|
64309b9 |
if not composes:
|
|
|
64309b9 |
log.info("Removing data of compose %d - it is not in "
|
|
|
64309b9 |
"database: %s", compose_id, path)
|
|
|
64309b9 |
- self._remove_compose_dir(path)
|
|
|
64309b9 |
+ compose_dirs_to_remove.append(path)
|
|
|
64309b9 |
continue
|
|
|
64309b9 |
|
|
|
64309b9 |
compose = composes[0]
|
|
|
64309b9 |
if compose.state == COMPOSE_STATES["removed"]:
|
|
|
64309b9 |
log.info("%r: Removing data of compose - it has already "
|
|
|
64309b9 |
"expired some time ago: %s", compose_id, path)
|
|
|
64309b9 |
- self._remove_compose_dir(path)
|
|
|
64309b9 |
+ compose_dirs_to_remove.append(path)
|
|
|
64309b9 |
continue
|
|
|
64309b9 |
|
|
|
64309b9 |
+ koji_cmds = []
|
|
|
64309b9 |
+ for compose_dir in compose_dirs_to_remove:
|
|
|
64309b9 |
+ if conf.pungi_runroot_enabled:
|
|
|
64309b9 |
+ cmds = self._get_remove_compose_dir_cmds(compose_dir)
|
|
|
64309b9 |
+ if cmds:
|
|
|
64309b9 |
+ koji_cmds += cmds
|
|
|
64309b9 |
+ koji_cmds += ["&&"]
|
|
|
64309b9 |
+ else:
|
|
|
64309b9 |
+ self._remove_compose_dir(compose_dir)
|
|
|
64309b9 |
+
|
|
|
64309b9 |
+ # In case we have some commands to run in Koji, spawn the Koji runroot
|
|
|
64309b9 |
+ # task and wait for result.
|
|
|
64309b9 |
+ if koji_cmds:
|
|
|
64309b9 |
+ # Remove last "&&"
|
|
|
64309b9 |
+ koji_cmds = koji_cmds[:-1]
|
|
|
64309b9 |
+ koji_session = odcs.server.utils.make_koji_session()
|
|
|
64309b9 |
+ odcs.server.utils.run_koji_runroot(koji_session, koji_cmds)
|
|
|
64309b9 |
+
|
|
|
64309b9 |
|
|
|
64309b9 |
def create_koji_session():
|
|
|
64309b9 |
"""
|
|
|
64309b9 |
diff --git a/server/odcs/server/pungi.py b/server/odcs/server/pungi.py
|
|
|
64309b9 |
index aab57e4..9dbd1d3 100644
|
|
|
64309b9 |
--- a/server/odcs/server/pungi.py
|
|
|
64309b9 |
+++ b/server/odcs/server/pungi.py
|
|
|
64309b9 |
@@ -25,8 +25,6 @@ import os
|
|
|
64309b9 |
import shutil
|
|
|
64309b9 |
import tempfile
|
|
|
64309b9 |
import jinja2
|
|
|
64309b9 |
-import koji
|
|
|
64309b9 |
-import munch
|
|
|
64309b9 |
import time
|
|
|
64309b9 |
import random
|
|
|
64309b9 |
import string
|
|
|
64309b9 |
@@ -35,7 +33,8 @@ import odcs.server.utils
|
|
|
64309b9 |
from odcs.server import conf, log
|
|
|
64309b9 |
from odcs.server import comps
|
|
|
64309b9 |
from odcs.common.types import PungiSourceType, COMPOSE_RESULTS
|
|
|
64309b9 |
-from odcs.server.utils import makedirs, download_file
|
|
|
64309b9 |
+from odcs.server.utils import (
|
|
|
64309b9 |
+ makedirs, download_file, make_koji_session, run_koji_runroot)
|
|
|
64309b9 |
|
|
|
64309b9 |
|
|
|
64309b9 |
class PungiConfig(object):
|
|
|
64309b9 |
@@ -185,47 +184,6 @@ class Pungi(object):
|
|
|
64309b9 |
output_path = os.path.join(topdir, "pungi.conf")
|
|
|
64309b9 |
download_file(self.pungi_cfg, output_path)
|
|
|
64309b9 |
|
|
|
64309b9 |
- def make_koji_session(self):
|
|
|
64309b9 |
- """
|
|
|
64309b9 |
- Creates new KojiSession according to odcs.server.conf, logins to
|
|
|
64309b9 |
- Koji using this session and returns it.
|
|
|
64309b9 |
- :rtype: koji.KojiSession
|
|
|
64309b9 |
- :return: KojiSession
|
|
|
64309b9 |
- """
|
|
|
64309b9 |
- koji_config = munch.Munch(koji.read_config(
|
|
|
64309b9 |
- profile_name=conf.koji_profile,
|
|
|
64309b9 |
- user_config=conf.koji_config,
|
|
|
64309b9 |
- ))
|
|
|
64309b9 |
-
|
|
|
64309b9 |
- address = koji_config.server
|
|
|
64309b9 |
- authtype = koji_config.authtype
|
|
|
64309b9 |
- log.info("Connecting to koji %r with %r." % (address, authtype))
|
|
|
64309b9 |
- koji_session = koji.ClientSession(address, opts=koji_config)
|
|
|
64309b9 |
- if authtype == "kerberos":
|
|
|
64309b9 |
- ccache = getattr(conf, "krb_ccache", None)
|
|
|
64309b9 |
- keytab = getattr(conf, "krb_keytab", None)
|
|
|
64309b9 |
- principal = getattr(conf, "krb_principal", None)
|
|
|
64309b9 |
- log.debug(" ccache: %r, keytab: %r, principal: %r" % (
|
|
|
64309b9 |
- ccache, keytab, principal))
|
|
|
64309b9 |
- if keytab and principal:
|
|
|
64309b9 |
- koji_session.krb_login(
|
|
|
64309b9 |
- principal=principal,
|
|
|
64309b9 |
- keytab=keytab,
|
|
|
64309b9 |
- ccache=ccache,
|
|
|
64309b9 |
- )
|
|
|
64309b9 |
- else:
|
|
|
64309b9 |
- koji_session.krb_login(ccache=ccache)
|
|
|
64309b9 |
- elif authtype == "ssl":
|
|
|
64309b9 |
- koji_session.ssl_login(
|
|
|
64309b9 |
- os.path.expanduser(koji_config.cert),
|
|
|
64309b9 |
- None,
|
|
|
64309b9 |
- os.path.expanduser(koji_config.serverca),
|
|
|
64309b9 |
- )
|
|
|
64309b9 |
- else:
|
|
|
64309b9 |
- raise ValueError("Unrecognized koji authtype %r" % authtype)
|
|
|
64309b9 |
-
|
|
|
64309b9 |
- return koji_session
|
|
|
64309b9 |
-
|
|
|
64309b9 |
def get_pungi_cmd(self, conf_topdir, targetdir):
|
|
|
64309b9 |
"""
|
|
|
64309b9 |
Returns list with pungi command line arguments needed to generate
|
|
|
64309b9 |
@@ -305,7 +263,7 @@ class Pungi(object):
|
|
|
64309b9 |
makedirs(conf_topdir)
|
|
|
64309b9 |
self._write_cfgs(conf_topdir)
|
|
|
64309b9 |
|
|
|
64309b9 |
- koji_session = self.make_koji_session()
|
|
|
64309b9 |
+ koji_session = make_koji_session()
|
|
|
64309b9 |
serverdir = self.upload_files_to_koji(koji_session, conf_topdir)
|
|
|
64309b9 |
|
|
|
64309b9 |
# TODO: Copy keytab from secret repo and generate koji profile.
|
|
|
64309b9 |
@@ -313,30 +271,7 @@ class Pungi(object):
|
|
|
64309b9 |
cmd += ["cp", "/mnt/koji/work/%s/*" % serverdir, ".", "&&"]
|
|
|
64309b9 |
cmd += self.get_pungi_cmd("./", conf.pungi_runroot_target_dir)
|
|
|
64309b9 |
|
|
|
64309b9 |
- kwargs = {
|
|
|
64309b9 |
- 'channel': conf.pungi_parent_runroot_channel,
|
|
|
64309b9 |
- 'packages': conf.pungi_parent_runroot_packages,
|
|
|
64309b9 |
- 'mounts': conf.pungi_parent_runroot_mounts,
|
|
|
64309b9 |
- 'weight': conf.pungi_parent_runroot_weight
|
|
|
64309b9 |
- }
|
|
|
64309b9 |
-
|
|
|
64309b9 |
- task_id = koji_session.runroot(
|
|
|
64309b9 |
- conf.pungi_parent_runroot_tag, conf.pungi_parent_runroot_arch,
|
|
|
64309b9 |
- " ".join(cmd), **kwargs)
|
|
|
64309b9 |
-
|
|
|
64309b9 |
- while True:
|
|
|
64309b9 |
- # wait for the task to finish
|
|
|
64309b9 |
- if koji_session.taskFinished(task_id):
|
|
|
64309b9 |
- break
|
|
|
64309b9 |
- log.info("Waiting for Koji runroot task %r to finish...", task_id)
|
|
|
64309b9 |
- time.sleep(60)
|
|
|
64309b9 |
-
|
|
|
64309b9 |
- info = koji_session.getTaskInfo(task_id)
|
|
|
64309b9 |
- if info is None:
|
|
|
64309b9 |
- raise RuntimeError("Cannot get status of Koji task %r" % task_id)
|
|
|
64309b9 |
- state = koji.TASK_STATES[info['state']]
|
|
|
64309b9 |
- if state in ('FAILED', 'CANCELED'):
|
|
|
64309b9 |
- raise RuntimeError("Koji runroot task %r failed." % task_id)
|
|
|
64309b9 |
+ run_koji_runroot(koji_session, cmd)
|
|
|
64309b9 |
|
|
|
64309b9 |
def run(self):
|
|
|
64309b9 |
"""
|
|
|
64309b9 |
diff --git a/server/odcs/server/utils.py b/server/odcs/server/utils.py
|
|
|
64309b9 |
index 63c3574..20e2e50 100644
|
|
|
64309b9 |
--- a/server/odcs/server/utils.py
|
|
|
64309b9 |
+++ b/server/odcs/server/utils.py
|
|
|
64309b9 |
@@ -26,11 +26,86 @@ import os
|
|
|
64309b9 |
import time
|
|
|
64309b9 |
import subprocess
|
|
|
64309b9 |
import requests
|
|
|
64309b9 |
+import munch
|
|
|
64309b9 |
+import koji
|
|
|
64309b9 |
from distutils.spawn import find_executable
|
|
|
64309b9 |
|
|
|
64309b9 |
from odcs.server import conf, log
|
|
|
64309b9 |
|
|
|
64309b9 |
|
|
|
64309b9 |
+def make_koji_session():
|
|
|
64309b9 |
+ """
|
|
|
64309b9 |
+ Creates new KojiSession according to odcs.server.conf, logins to
|
|
|
64309b9 |
+ Koji using this session and returns it.
|
|
|
64309b9 |
+ :rtype: koji.KojiSession
|
|
|
64309b9 |
+ :return: KojiSession
|
|
|
64309b9 |
+ """
|
|
|
64309b9 |
+ koji_config = munch.Munch(koji.read_config(
|
|
|
64309b9 |
+ profile_name=conf.koji_profile,
|
|
|
64309b9 |
+ user_config=conf.koji_config,
|
|
|
64309b9 |
+ ))
|
|
|
64309b9 |
+
|
|
|
64309b9 |
+ address = koji_config.server
|
|
|
64309b9 |
+ authtype = koji_config.authtype
|
|
|
64309b9 |
+ log.info("Connecting to koji %r with %r." % (address, authtype))
|
|
|
64309b9 |
+ koji_session = koji.ClientSession(address, opts=koji_config)
|
|
|
64309b9 |
+ if authtype == "kerberos":
|
|
|
64309b9 |
+ ccache = getattr(conf, "krb_ccache", None)
|
|
|
64309b9 |
+ keytab = getattr(conf, "krb_keytab", None)
|
|
|
64309b9 |
+ principal = getattr(conf, "krb_principal", None)
|
|
|
64309b9 |
+ log.debug(" ccache: %r, keytab: %r, principal: %r" % (
|
|
|
64309b9 |
+ ccache, keytab, principal))
|
|
|
64309b9 |
+ if keytab and principal:
|
|
|
64309b9 |
+ koji_session.krb_login(
|
|
|
64309b9 |
+ principal=principal,
|
|
|
64309b9 |
+ keytab=keytab,
|
|
|
64309b9 |
+ ccache=ccache,
|
|
|
64309b9 |
+ )
|
|
|
64309b9 |
+ else:
|
|
|
64309b9 |
+ koji_session.krb_login(ccache=ccache)
|
|
|
64309b9 |
+ elif authtype == "ssl":
|
|
|
64309b9 |
+ koji_session.ssl_login(
|
|
|
64309b9 |
+ os.path.expanduser(koji_config.cert),
|
|
|
64309b9 |
+ None,
|
|
|
64309b9 |
+ os.path.expanduser(koji_config.serverca),
|
|
|
64309b9 |
+ )
|
|
|
64309b9 |
+ else:
|
|
|
64309b9 |
+ raise ValueError("Unrecognized koji authtype %r" % authtype)
|
|
|
64309b9 |
+
|
|
|
64309b9 |
+ return koji_session
|
|
|
64309b9 |
+
|
|
|
64309b9 |
+
|
|
|
64309b9 |
+def run_koji_runroot(koji_session, cmd):
|
|
|
64309b9 |
+ """
|
|
|
64309b9 |
+ Runs the cmd defined as list of commands in Koji runroot and waits for the
|
|
|
64309b9 |
+ task to finish. Raises RuntimeError in case the task execution failed.
|
|
|
64309b9 |
+ """
|
|
|
64309b9 |
+ kwargs = {
|
|
|
64309b9 |
+ 'channel': conf.pungi_parent_runroot_channel,
|
|
|
64309b9 |
+ 'packages': conf.pungi_parent_runroot_packages,
|
|
|
64309b9 |
+ 'mounts': conf.pungi_parent_runroot_mounts,
|
|
|
64309b9 |
+ 'weight': conf.pungi_parent_runroot_weight
|
|
|
64309b9 |
+ }
|
|
|
64309b9 |
+
|
|
|
64309b9 |
+ task_id = koji_session.runroot(
|
|
|
64309b9 |
+ conf.pungi_parent_runroot_tag, conf.pungi_parent_runroot_arch,
|
|
|
64309b9 |
+ " ".join(cmd), **kwargs)
|
|
|
64309b9 |
+
|
|
|
64309b9 |
+ while True:
|
|
|
64309b9 |
+ # wait for the task to finish
|
|
|
64309b9 |
+ if koji_session.taskFinished(task_id):
|
|
|
64309b9 |
+ break
|
|
|
64309b9 |
+ log.info("Waiting for Koji runroot task %r to finish...", task_id)
|
|
|
64309b9 |
+ time.sleep(60)
|
|
|
64309b9 |
+
|
|
|
64309b9 |
+ info = koji_session.getTaskInfo(task_id)
|
|
|
64309b9 |
+ if info is None:
|
|
|
64309b9 |
+ raise RuntimeError("Cannot get status of Koji task %r" % task_id)
|
|
|
64309b9 |
+ state = koji.TASK_STATES[info['state']]
|
|
|
64309b9 |
+ if state in ('FAILED', 'CANCELED'):
|
|
|
64309b9 |
+ raise RuntimeError("Koji runroot task %r failed." % task_id)
|
|
|
64309b9 |
+
|
|
|
64309b9 |
+
|
|
|
64309b9 |
def download_file(url, output_path):
|
|
|
64309b9 |
"""
|
|
|
64309b9 |
Downloads file from URL `url` to `output_path`.
|
|
|
64309b9 |
diff --git a/server/tests/test_pungi.py b/server/tests/test_pungi.py
|
|
|
64309b9 |
index 4d2591d..8709780 100644
|
|
|
64309b9 |
--- a/server/tests/test_pungi.py
|
|
|
64309b9 |
+++ b/server/tests/test_pungi.py
|
|
|
64309b9 |
@@ -201,7 +201,7 @@ class TestPungiRunroot(unittest.TestCase):
|
|
|
64309b9 |
self.config_patcher.patch('pungi_runroot_target_dir_url', 'http://kojipkgs.fedoraproject.org/compose/odcs')
|
|
|
64309b9 |
self.config_patcher.start()
|
|
|
64309b9 |
|
|
|
64309b9 |
- self.patch_make_koji_session = patch("odcs.server.pungi.Pungi.make_koji_session")
|
|
|
64309b9 |
+ self.patch_make_koji_session = patch("odcs.server.pungi.make_koji_session")
|
|
|
64309b9 |
self.make_koji_session = self.patch_make_koji_session.start()
|
|
|
64309b9 |
self.koji_session = MagicMock()
|
|
|
64309b9 |
self.koji_session.runroot.return_value = 123
|
|
|
64309b9 |
diff --git a/server/tests/test_remove_expired_composes_thread.py b/server/tests/test_remove_expired_composes_thread.py
|
|
|
64309b9 |
index fb387f7..e4551dc 100644
|
|
|
64309b9 |
--- a/server/tests/test_remove_expired_composes_thread.py
|
|
|
64309b9 |
+++ b/server/tests/test_remove_expired_composes_thread.py
|
|
|
64309b9 |
@@ -21,17 +21,18 @@
|
|
|
64309b9 |
# Written by Jan Kaluza <jkaluza@redhat.com>
|
|
|
64309b9 |
|
|
|
64309b9 |
from odcs.server import db, conf
|
|
|
64309b9 |
+import odcs.server.backend
|
|
|
64309b9 |
from odcs.server.models import Compose
|
|
|
64309b9 |
from odcs.common.types import COMPOSE_STATES, COMPOSE_RESULTS
|
|
|
64309b9 |
from odcs.server.backend import RemoveExpiredComposesThread
|
|
|
64309b9 |
from odcs.server.pungi import PungiSourceType
|
|
|
64309b9 |
from datetime import datetime, timedelta
|
|
|
64309b9 |
|
|
|
64309b9 |
-from .utils import ModelsBaseTest
|
|
|
64309b9 |
+from .utils import ModelsBaseTest, ConfigPatcher
|
|
|
64309b9 |
|
|
|
64309b9 |
import os
|
|
|
64309b9 |
import mock
|
|
|
64309b9 |
-from mock import patch
|
|
|
64309b9 |
+from mock import patch, MagicMock
|
|
|
64309b9 |
|
|
|
64309b9 |
|
|
|
64309b9 |
class TestRemoveExpiredComposesThread(ModelsBaseTest):
|
|
|
64309b9 |
@@ -211,3 +212,149 @@ class TestRemoveExpiredComposesThread(ModelsBaseTest):
|
|
|
64309b9 |
self.thread._remove_compose_dir(toplevel_dir)
|
|
|
64309b9 |
unlink.assert_not_called()
|
|
|
64309b9 |
rmtree.assert_called_once()
|
|
|
64309b9 |
+
|
|
|
64309b9 |
+
|
|
|
64309b9 |
+class TestRemoveExpiredComposesThreadPungiRunroot(ModelsBaseTest):
|
|
|
64309b9 |
+ maxDiff = None
|
|
|
64309b9 |
+
|
|
|
64309b9 |
+ def setUp(self):
|
|
|
64309b9 |
+ super(TestRemoveExpiredComposesThreadPungiRunroot, self).setUp()
|
|
|
64309b9 |
+
|
|
|
64309b9 |
+ compose = Compose.create(
|
|
|
64309b9 |
+ db.session, "unknown", PungiSourceType.MODULE, "testmodule-master",
|
|
|
64309b9 |
+ COMPOSE_RESULTS["repository"], 60)
|
|
|
64309b9 |
+ db.session.add(compose)
|
|
|
64309b9 |
+ db.session.commit()
|
|
|
64309b9 |
+
|
|
|
64309b9 |
+ self.config_patcher = ConfigPatcher(odcs.server.backend.conf)
|
|
|
64309b9 |
+ self.config_patcher.patch("pungi_runroot_enabled", True)
|
|
|
64309b9 |
+ self.config_patcher.start()
|
|
|
64309b9 |
+
|
|
|
64309b9 |
+ self.thread = RemoveExpiredComposesThread()
|
|
|
64309b9 |
+
|
|
|
64309b9 |
+ self.patch_make_koji_session = patch("odcs.server.utils.make_koji_session")
|
|
|
64309b9 |
+ self.make_koji_session = self.patch_make_koji_session.start()
|
|
|
64309b9 |
+ self.koji_session = MagicMock()
|
|
|
64309b9 |
+ self.make_koji_session.return_value = self.koji_session
|
|
|
64309b9 |
+
|
|
|
64309b9 |
+ self.patch_run_koji_runroot = patch("odcs.server.utils.run_koji_runroot")
|
|
|
64309b9 |
+ self.run_koji_runroot = self.patch_run_koji_runroot.start()
|
|
|
64309b9 |
+
|
|
|
64309b9 |
+ def tearDown(self):
|
|
|
64309b9 |
+ super(TestRemoveExpiredComposesThreadPungiRunroot, self).tearDown()
|
|
|
64309b9 |
+ self.config_patcher.stop()
|
|
|
64309b9 |
+ self.patch_make_koji_session.stop()
|
|
|
64309b9 |
+
|
|
|
64309b9 |
+ def _mock_glob(self, glob, dirs):
|
|
|
64309b9 |
+ glob_ret_values = [[], []]
|
|
|
64309b9 |
+ for d in dirs:
|
|
|
64309b9 |
+ path = os.path.join(conf.target_dir, d)
|
|
|
64309b9 |
+ if d.startswith("latest-"):
|
|
|
64309b9 |
+ glob_ret_values[0].append(path)
|
|
|
64309b9 |
+ else:
|
|
|
64309b9 |
+ glob_ret_values[1].append(path)
|
|
|
64309b9 |
+ glob.side_effect = glob_ret_values
|
|
|
64309b9 |
+
|
|
|
64309b9 |
+ @patch("os.path.isdir")
|
|
|
64309b9 |
+ @patch("glob.glob")
|
|
|
64309b9 |
+ def test_remove_left_composes(self, glob, isdir):
|
|
|
64309b9 |
+ isdir.return_value = True
|
|
|
64309b9 |
+ self._mock_glob(glob, ["latest-odcs-96-1", "odcs-96-1-20171005.n.0"])
|
|
|
64309b9 |
+ self.thread.do_work()
|
|
|
64309b9 |
+
|
|
|
64309b9 |
+ self.run_koji_runroot.assert_not_called()
|
|
|
64309b9 |
+
|
|
|
64309b9 |
+ @patch("os.path.isdir")
|
|
|
64309b9 |
+ @patch("glob.glob")
|
|
|
64309b9 |
+ def test_remove_left_composes_not_dir(
|
|
|
64309b9 |
+ self, glob, isdir):
|
|
|
64309b9 |
+ isdir.return_value = False
|
|
|
64309b9 |
+ self._mock_glob(glob, ["latest-odcs-96-1"])
|
|
|
64309b9 |
+ self.thread.do_work()
|
|
|
64309b9 |
+ self.run_koji_runroot.assert_not_called()
|
|
|
64309b9 |
+
|
|
|
64309b9 |
+ @patch("os.path.isdir")
|
|
|
64309b9 |
+ @patch("glob.glob")
|
|
|
64309b9 |
+ def test_remove_left_composes_wrong_dir(
|
|
|
64309b9 |
+ self, glob, isdir):
|
|
|
64309b9 |
+ isdir.return_value = True
|
|
|
64309b9 |
+ self._mock_glob(glob, ["latest-odcs-", "odcs-", "odcs-abc"])
|
|
|
64309b9 |
+ self.thread.do_work()
|
|
|
64309b9 |
+ self.run_koji_runroot.assert_not_called()
|
|
|
64309b9 |
+
|
|
|
64309b9 |
+ @patch("os.path.isdir")
|
|
|
64309b9 |
+ @patch("glob.glob")
|
|
|
64309b9 |
+ def test_remove_left_composes_valid_compose(
|
|
|
64309b9 |
+ self, glob, isdir):
|
|
|
64309b9 |
+ isdir.return_value = True
|
|
|
64309b9 |
+ self._mock_glob(glob, ["latest-odcs-1-1", "odcs-1-1-2017.n.0"])
|
|
|
64309b9 |
+ c = db.session.query(Compose).filter(Compose.id == 1).one()
|
|
|
64309b9 |
+ c.state = COMPOSE_STATES["done"]
|
|
|
64309b9 |
+ db.session.add(c)
|
|
|
64309b9 |
+ db.session.commit()
|
|
|
64309b9 |
+ self.thread.do_work()
|
|
|
64309b9 |
+ self.run_koji_runroot.assert_not_called()
|
|
|
64309b9 |
+
|
|
|
64309b9 |
+ @patch("os.path.isdir")
|
|
|
64309b9 |
+ @patch("glob.glob")
|
|
|
64309b9 |
+ @patch("os.path.realpath")
|
|
|
64309b9 |
+ @patch("os.path.exists")
|
|
|
64309b9 |
+ def test_remove_left_composes_expired_compose(
|
|
|
64309b9 |
+ self, exists, realpath, glob, isdir):
|
|
|
64309b9 |
+ exists.return_value = True
|
|
|
64309b9 |
+ realpath.return_value = "/odcs-real"
|
|
|
64309b9 |
+ isdir.return_value = True
|
|
|
64309b9 |
+ self._mock_glob(glob, ["latest-odcs-1-1", "odcs-1-1-2017.n.0"])
|
|
|
64309b9 |
+ c = db.session.query(Compose).filter(Compose.id == 1).one()
|
|
|
64309b9 |
+ c.state = COMPOSE_STATES["removed"]
|
|
|
64309b9 |
+ db.session.add(c)
|
|
|
64309b9 |
+ db.session.commit()
|
|
|
64309b9 |
+ self.thread.do_work()
|
|
|
64309b9 |
+ self.run_koji_runroot.assert_called_once_with(
|
|
|
64309b9 |
+ self.koji_session,
|
|
|
64309b9 |
+ ['rm', '-f', os.path.join(conf.target_dir, 'latest-odcs-1-1'), '&&',
|
|
|
64309b9 |
+ 'rm', '-rf', '/odcs-real', '&&',
|
|
|
64309b9 |
+ 'rm', '-f', os.path.join(conf.target_dir, 'odcs-1-1-2017.n.0'), '&&',
|
|
|
64309b9 |
+ 'rm', '-rf', '/odcs-real'])
|
|
|
64309b9 |
+
|
|
|
64309b9 |
+ @patch("os.path.realpath")
|
|
|
64309b9 |
+ @patch("os.path.exists")
|
|
|
64309b9 |
+ def test_remove_compose_dir_symlink(
|
|
|
64309b9 |
+ self, exists, realpath):
|
|
|
64309b9 |
+ exists.return_value = True
|
|
|
64309b9 |
+ toplevel_dir = "/odcs"
|
|
|
64309b9 |
+ realpath.return_value = "/odcs-real"
|
|
|
64309b9 |
+
|
|
|
64309b9 |
+ cmds = self.thread._get_remove_compose_dir_cmds(toplevel_dir)
|
|
|
64309b9 |
+ self.assertEqual(
|
|
|
64309b9 |
+ cmds, ['rm', '-f', '/odcs', '&&', 'rm', '-rf', '/odcs-real'])
|
|
|
64309b9 |
+
|
|
|
64309b9 |
+ @patch("shutil.rmtree")
|
|
|
64309b9 |
+ @patch("os.unlink")
|
|
|
64309b9 |
+ @patch("os.path.realpath")
|
|
|
64309b9 |
+ @patch("os.path.exists")
|
|
|
64309b9 |
+ def test_remove_compose_dir_broken_symlink(
|
|
|
64309b9 |
+ self, exists, realpath, unlink, rmtree):
|
|
|
64309b9 |
+ def mocked_exists(p):
|
|
|
64309b9 |
+ return p != "/odcs-real"
|
|
|
64309b9 |
+ exists.side_effect = mocked_exists
|
|
|
64309b9 |
+ toplevel_dir = "/odcs"
|
|
|
64309b9 |
+ realpath.return_value = "/odcs-real"
|
|
|
64309b9 |
+
|
|
|
64309b9 |
+ cmds = self.thread._get_remove_compose_dir_cmds(toplevel_dir)
|
|
|
64309b9 |
+ self.assertEqual(
|
|
|
64309b9 |
+ cmds, ['rm', '-f', '/odcs'])
|
|
|
64309b9 |
+
|
|
|
64309b9 |
+ @patch("shutil.rmtree")
|
|
|
64309b9 |
+ @patch("os.unlink")
|
|
|
64309b9 |
+ @patch("os.path.realpath")
|
|
|
64309b9 |
+ @patch("os.path.exists")
|
|
|
64309b9 |
+ def test_remove_compose_dir_real_dir(
|
|
|
64309b9 |
+ self, exists, realpath, unlink, rmtree):
|
|
|
64309b9 |
+ exists.return_value = True
|
|
|
64309b9 |
+ toplevel_dir = "/odcs"
|
|
|
64309b9 |
+ realpath.return_value = "/odcs"
|
|
|
64309b9 |
+
|
|
|
64309b9 |
+ cmds = self.thread._get_remove_compose_dir_cmds(toplevel_dir)
|
|
|
64309b9 |
+ self.assertEqual(
|
|
|
64309b9 |
+ cmds, ['rm', '-rf', '/odcs'])
|
|
|
64309b9 |
|