Просмотр исходного кода

integration: Remove hardcoded mysql port and add unique id to loop devices

Currently, the mysql container spawned for the integration tests is
created with a static host port mapping. That hinders up from running
multiple integration tests jobs on the same host with no good reason.
This will resolve this issue.

Additionally, adds a unique prefix to the loop devices to easily
distinguish them from another parallel job's devices.
Claudiu Belu 1 неделя назад
Родитель
Сommit
2a9ab47336
2 измененных файлов с 41 добавлено и 15 удалено
  1. 12 13
      coriolis/tests/integration/harness.py
  2. 29 2
      coriolis/tests/integration/utils.py

+ 12 - 13
coriolis/tests/integration/harness.py

@@ -321,9 +321,8 @@ class _IntegrationHarness:
         self.lock_path = os.path.join(self.workdir, "locks")
         self.lock_path = os.path.join(self.workdir, "locks")
         os.makedirs(self.lock_path)
         os.makedirs(self.lock_path)
 
 
-        self._mysql_container_name = (
-            "coriolis-test-mysql-%s" % str(uuid.uuid4()).split("-")[0]
-        )
+        session_tag = str(uuid.uuid4()).split("-")[0]
+        self._mysql_container_name = "coriolis-test-mysql-%s" % session_tag
         self._mysql_username = "root"
         self._mysql_username = "root"
         self._mysql_password = "coriolis"
         self._mysql_password = "coriolis"
         self._mysql_database = "coriolis"
         self._mysql_database = "coriolis"
@@ -362,14 +361,6 @@ class _IntegrationHarness:
         exp_provider = providers_config["source"]["provider"]
         exp_provider = providers_config["source"]["provider"]
         imp_provider = providers_config["destination"]["provider"]
         imp_provider = providers_config["destination"]["provider"]
         cfg.CONF.set_override('providers', [exp_provider, imp_provider])
         cfg.CONF.set_override('providers', [exp_provider, imp_provider])
-        db_url = (
-            'mysql+pymysql://%(user)s:%(password)s@localhost:13306/%(database)s'
-        ) % {
-            "user": self._mysql_username,
-            "password": self._mysql_password,
-            "database": self._mysql_database,
-        }
-        cfg.CONF.set_override('connection', db_url, group='database')
         cfg.CONF.set_override('retry_interval', 1, group='database')
         cfg.CONF.set_override('retry_interval', 1, group='database')
         cfg.CONF.set_override('lock_path', self.lock_path, group='oslo_concurrency')
         cfg.CONF.set_override('lock_path', self.lock_path, group='oslo_concurrency')
 
 
@@ -466,11 +457,19 @@ class _IntegrationHarness:
                 f"MYSQL_ROOT_PASSWORD={self._mysql_password}",
                 f"MYSQL_ROOT_PASSWORD={self._mysql_password}",
                 "-e",
                 "-e",
                 f"MYSQL_DATABASE={self._mysql_database}",
                 f"MYSQL_DATABASE={self._mysql_database}",
-                "-p",
-                "13306:3306",
                 "mariadb:10-jammy",
                 "mariadb:10-jammy",
             ]
             ]
         )
         )
+        self._mysql_ip = test_utils.get_container_ip(self._mysql_container_name)
+        db_url = (
+            'mysql+pymysql://%(user)s:%(password)s@%(host)s:3306/%(database)s'
+        ) % {
+            "user": self._mysql_username,
+            "password": self._mysql_password,
+            "host": self._mysql_ip,
+            "database": self._mysql_database,
+        }
+        cfg.CONF.set_override('connection', db_url, group='database')
 
 
     def _start_coriolis_services(self):
     def _start_coriolis_services(self):
         """Start conductor, scheduler, worker, and API in-process."""
         """Start conductor, scheduler, worker, and API in-process."""

+ 29 - 2
coriolis/tests/integration/utils.py

@@ -11,6 +11,7 @@ import socket
 import subprocess
 import subprocess
 import tempfile
 import tempfile
 import time
 import time
+import uuid
 
 
 import paramiko
 import paramiko
 from oslo_log import log as logging
 from oslo_log import log as logging
@@ -21,6 +22,10 @@ LOG = logging.getLogger(__name__)
 
 
 DATA_MINION_IMAGE = "coriolis-data-minion:test"
 DATA_MINION_IMAGE = "coriolis-data-minion:test"
 
 
+# Unique per test-process prefix for loop device backing files, so that
+# destroy_leaked_loop_devices() only ever touches devices created by this session.
+_LOOPDEV_BACKING_PREFIX = "coriolis-loopdev-%s-" % uuid.uuid4().hex[:8]
+
 # device_path: backing_sparse_file, for devices created by create_loop_device().
 # device_path: backing_sparse_file, for devices created by create_loop_device().
 _loop_backing_files = {}
 _loop_backing_files = {}
 
 
@@ -44,7 +49,7 @@ def create_loop_device(size_bytes) -> str:
 
 
     :returns: the /dev/loopN path.
     :returns: the /dev/loopN path.
     """
     """
-    fd, backing_file = tempfile.mkstemp(prefix="coriolis-loopdev-")
+    fd, backing_file = tempfile.mkstemp(prefix=_LOOPDEV_BACKING_PREFIX)
     os.close(fd)
     os.close(fd)
     _run(["truncate", "-s", str(size_bytes), backing_file])
     _run(["truncate", "-s", str(size_bytes), backing_file])
 
 
@@ -69,10 +74,32 @@ def remove_loop_device(device_path):
 
 
 
 
 def destroy_leaked_loop_devices():
 def destroy_leaked_loop_devices():
-    """Detach and remove any loop devices left over from a previous run."""
+    """Detach and remove any loop devices left over from this session.
+
+    First cleans up everything still tracked in-process, then scans losetup for devices
+    this session created (matched via _LOOPDEV_BACKING_PREFIX on the backing file) but
+    lost track of..
+    """
     for device_path in list(_loop_backing_files):
     for device_path in list(_loop_backing_files):
         remove_loop_device(device_path)
         remove_loop_device(device_path)
 
 
+    result = _run(["losetup", "-J"], check=False)
+    if result.returncode != 0:
+        return
+
+    for entry in json.loads(result.stdout).get("loopdevices", []):
+        back_file = entry.get("back-file") or ""
+        if not os.path.basename(back_file).startswith(_LOOPDEV_BACKING_PREFIX):
+            continue
+
+        device_path = entry["name"]
+        LOG.warning("Destroying leaked loop device: %s", device_path)
+        _run(["losetup", "-d", device_path], check=False)
+        try:
+            os.unlink(back_file)
+        except OSError:
+            pass
+
 
 
 def write_test_pattern(device_path, chunk_size=4096):
 def write_test_pattern(device_path, chunk_size=4096):
     """Fill *device_path* with a repeating 4-byte test pattern.
     """Fill *device_path* with a repeating 4-byte test pattern.