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

db: Adds alembic revision 025 for missing unique constraints

models.py has declared UniqueConstraints on task_progress_update,
minion_pool_progress_update, and service, but no migration script created
them in the database.

This revision adds them so deployed schemas match the ORM model.
Claudiu Belu 1 неделя назад
Родитель
Сommit
2f32c1ace6

+ 6 - 0
coriolis/api/wsgi.py

@@ -660,6 +660,12 @@ class ResourceExceptionHandler(object):
                     code=ex_value.code, explanation=ex_value.msg
                 )
             )
+        elif isinstance(ex_value, exception.Conflict):
+            raise Fault(
+                exception.ConvertedException(
+                    code=ex_value.code, explanation=ex_value.msg
+                )
+            )
         elif isinstance(ex_value, TypeError):
             exc_info = (ex_type, ex_value, ex_traceback)
             LOG.error(

+ 8 - 1
coriolis/conductor/rpc/server.py

@@ -8,6 +8,7 @@ import uuid
 
 from oslo_concurrency import lockutils
 from oslo_config import cfg
+from oslo_db import exception as db_exception
 from oslo_log import log as logging
 
 from coriolis import constants, context, exception, keystone, schemas, utils
@@ -4568,7 +4569,13 @@ class ConductorServerEndpoint(object):
             service.specs = specs
 
         # create the service:
-        db_api.add_service(ctxt, service)
+        try:
+            db_api.add_service(ctxt, service)
+        except db_exception.DBDuplicateEntry:
+            raise exception.Conflict(
+                "A Service with the specified parameters (host %s, binary %s, "
+                "topic %s) has already been registered." % (host, binary, topic)
+            )
         LOG.debug("Added new service to DB: %s", service.id)
 
         # add region associations:

+ 43 - 0
coriolis/db/sqlalchemy/alembic/versions/025_add_missing_unique_constraints.py

@@ -0,0 +1,43 @@
+# Copyright 2026 Cloudbase Solutions Srl
+# All Rights Reserved.
+
+"""add unique constraints
+
+Revision ID: 025
+Revises: 024
+Create Date: 2026-08-27 17:21:00.000000
+"""
+
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = "025"
+down_revision = "024"
+branch_labels = None
+depends_on = None
+
+# These constraints have been declared on the SQLAlchemy models, but no migration
+# script has actually created them in the database.
+_CONSTRAINTS = (
+    (
+        "uniq_task_progress_update0task_id0index0deleted",
+        "task_progress_update",
+        ["task_id", "index", "deleted"],
+    ),
+    (
+        "uniq_minion_pool_progress_update0pool_id0index0deleted",
+        "minion_pool_progress_update",
+        ["pool_id", "index", "deleted"],
+    ),
+    ("uniq_services0host0topic0deleted", "service", ["host", "topic", "deleted"]),
+    ("uniq_services0host0binary0deleted", "service", ["host", "binary", "deleted"]),
+)
+
+
+def upgrade():
+    for name, table, columns in _CONSTRAINTS:
+        op.create_unique_constraint(name, table, columns)
+
+
+def downgrade():
+    raise NotImplementedError()

+ 5 - 0
coriolis/tests/api/test_wsgi.py

@@ -30,6 +30,11 @@ class ResourceExceptionHandlerTestCase(test_base.CoriolisBaseTestCase):
         raised = self.assertRaises(wsgi.Fault, self._run, exc)
         self.assertEqual(exc.code, raised.status_int)
 
+    def test_conflict(self):
+        exc = exception.Conflict("already exists")
+        raised = self.assertRaises(wsgi.Fault, self._run, exc)
+        self.assertEqual(exc.code, raised.status_int)
+
     def test_type_error(self):
         exc = TypeError("wrong type")
         raised = self.assertRaises(wsgi.Fault, self._run, exc)

+ 22 - 0
coriolis/tests/conductor/rpc/test_server.py

@@ -9,6 +9,7 @@ from unittest import mock
 import ddt
 from oslo_concurrency import lockutils
 from oslo_config import cfg
+from oslo_db import exception as db_exception
 
 from coriolis import constants, context, exception, keystone, schemas, utils
 from coriolis.conductor.rpc import server
@@ -4914,6 +4915,27 @@ class ConductorServerEndpointTestCase(test_base.CoriolisBaseTestCase):
         )
         mock_get_service.assert_not_called()
 
+    @mock.patch.object(db_api, "add_service")
+    @mock.patch.object(models, "Service")
+    @mock.patch.object(db_api, "find_service")
+    def test_register_service_db_duplicate_entry(
+        self, mock_find_service, mock_Service, mock_add_service
+    ):
+        mock_find_service.return_value = None
+        mock_add_service.side_effect = db_exception.DBDuplicateEntry()
+
+        self.assertRaises(
+            exception.Conflict,
+            self.server.register_service,
+            mock.sentinel.context,
+            mock.sentinel.host,
+            mock.sentinel.binary,
+            mock.sentinel.topic,
+            mock.sentinel.enabled,
+            providers=mock.sentinel.providers,
+            specs=mock.sentinel.specs,
+        )
+
     @mock.patch.object(db_api, "find_service")
     def test_check_service_registered(self, mock_find_service):
         result = self.server.check_service_registered(

+ 4 - 4
coriolis/tests/integration/management/test_service.py

@@ -32,7 +32,7 @@ class ServiceTests(base.CoriolisIntegrationTestBase):
 
         # Create.
         hostname = socket.gethostname()
-        svc = self._create_service(hostname, "foo-binary", "coriolis_worker")
+        svc = self._create_service(hostname, "foo-binary", "lish-topic")
 
         # Get.
         fetched = self._client.services.get(svc.id)
@@ -61,14 +61,14 @@ class ServiceTests(base.CoriolisIntegrationTestBase):
         # ConductorServerEndpoint.register_service raises Conflict when a
         # service with the same host / binary / topic is already registered.
         hostname = socket.gethostname()
-        svc = self._create_service(hostname, "conflict-binary", "coriolis_worker")
+        svc = self._create_service(hostname, "conflict-binary", "conflict-topic")
 
         self.assertRaises(
             http_exc.Conflict,
             self._client.services.create,
             host=hostname,
             binary="conflict-binary",
-            topic="coriolis_worker",
+            topic="conflict-topic",
             regions=[],
         )
 
@@ -83,7 +83,7 @@ class ServiceTests(base.CoriolisIntegrationTestBase):
         svc = self._client.services.create(
             host=hostname,
             binary="region-binary",
-            topic="coriolis_worker",
+            topic="region-topic",
             regions=[region.id],
         )
         self.addCleanup(self._ignoreExc(self._client.services.delete), svc.id)