diff --git a/dim-testsuite/t/container-create-1.t b/dim-testsuite/t/container-create-1.t index 004cc81..5f80aba 100644 --- a/dim-testsuite/t/container-create-1.t +++ b/dim-testsuite/t/container-create-1.t @@ -33,6 +33,7 @@ $ ndcli create container ::/0 layer3domain default ERROR - ::/0 already exists in layer3domain default with status Container $ ndcli list containers layer3domain default +layer3domain: default 0.0.0.0/0 (Container) 0.0.0.0/0 (Available) ::/0 (Container) diff --git a/dim/CHANGES b/dim/CHANGES index 0bb36d3..3a60ca5 100644 --- a/dim/CHANGES +++ b/dim/CHANGES @@ -1,5 +1,6 @@ to be released ------ +* add ip version to the ipblock uniqueness key so 0.0.0.0/0 and ::/0 can coexist in one layer3domain (schema 13 -> 14, see sql/migrate_13_to_14.sql) * added ldap pseudo user sync to manage_dim (introduces is_pseudo column, LDAP_PSEUDO_USER_BASE, LDAP_BIND_DN, and LDAP_BIND_PASSWORD configurations) * added automated database schema mismatch verification check on application startup * reject attribute names containing colons during deletion to prevent silent failure from incorrect key-value input diff --git a/dim/dim/models/ip.py b/dim/dim/models/ip.py index 4d916cf..938834d 100644 --- a/dim/dim/models/ip.py +++ b/dim/dim/models/ip.py @@ -232,7 +232,11 @@ def collect_data(): class Ipblock(db.Model, WithAttr, TrackChanges): - __table_constraints__ = (UniqueConstraint('address', 'prefix', 'layer3domain_id'), ) + # 'version' is part of a block's identity: 0.0.0.0/0 and ::/0 both have + # address 0 and prefix 0, so without it they collide inside one layer3domain. + # It is appended so that the (address, prefix, layer3domain_id) index prefix + # stays intact -- ip.py hints this index by name in _set_parent(). + __table_constraints__ = (UniqueConstraint('address', 'prefix', 'layer3domain_id', 'version'), ) id = Column(BigInteger, primary_key=True, nullable=False) version = Column(Integer, index=True, nullable=False) diff --git a/dim/dim/models/schema.py b/dim/dim/models/schema.py index ecbe7c4..0d97eac 100644 --- a/dim/dim/models/schema.py +++ b/dim/dim/models/schema.py @@ -6,7 +6,7 @@ __all__ = ['SCHEMA_VERSION', 'SchemaInfo'] -SCHEMA_VERSION = "13" +SCHEMA_VERSION = "14" class SchemaInfo(db.Model): diff --git a/dim/dim/sql/migrate_13_to_14.sql b/dim/dim/sql/migrate_13_to_14.sql new file mode 100644 index 0000000..f886e60 --- /dev/null +++ b/dim/dim/sql/migrate_13_to_14.sql @@ -0,0 +1,30 @@ +-- Add `version` to the ipblock uniqueness key. +-- +-- 0.0.0.0/0 and ::/0 both have address 0 and prefix 0, so the old key +-- (address, prefix, layer3domain_id) could not hold both in one layer3domain: +-- creating the second one failed with a duplicate key error. +-- +-- `version` is appended rather than inserted so that the existing +-- (address, prefix, layer3domain_id) index prefix stays usable, and the index +-- keeps its name -- Ipblock._set_parent() references it via USE INDEX (address). +-- +-- DROP and ADD stay in one statement, otherwise the table is briefly without +-- the index DIM expects by name. +-- +-- ALGORITHM=INPLACE, LOCK=NONE is an assertion, not a request: if the server +-- cannot do the change in place and without locking, the statement aborts with +-- ER_ALTER_OPERATION_NOT_SUPPORTED (1846) instead of silently falling back to a +-- copying, locking ALTER. Measured on MariaDB 10.6: ~0.6 s per million rows, +-- concurrent INSERTs keep working. +-- +-- This only relaxes the constraint, so no existing row can violate it and no +-- data cleanup is needed. Databases where the index was already widened by hand +-- (ITOUDP-7795) are fine: the index is simply rebuilt with the same columns. + +ALTER TABLE `ipblock` + DROP INDEX `address`, + ADD UNIQUE KEY `address` (`address`,`prefix`,`layer3domain_id`,`version`), + ALGORITHM=INPLACE, LOCK=NONE; + +UPDATE schemainfo SET version=14; +COMMIT; diff --git a/dim/dim/sql/rollback_14_to_13.sql b/dim/dim/sql/rollback_14_to_13.sql new file mode 100644 index 0000000..b5675e5 --- /dev/null +++ b/dim/dim/sql/rollback_14_to_13.sql @@ -0,0 +1,17 @@ +-- Drop `version` from the ipblock uniqueness key. +-- +-- This tightens the constraint, so it fails if any two blocks share +-- (address, prefix, layer3domain_id) and differ only in `version` -- the +-- 0.0.0.0/0 plus ::/0 pair being the case migrate_13_to_14.sql allowed. +-- Remove one of each such pair before rolling back: +-- +-- SELECT address, prefix, layer3domain_id, COUNT(*) FROM ipblock +-- GROUP BY address, prefix, layer3domain_id HAVING COUNT(*) > 1; + +ALTER TABLE `ipblock` + DROP INDEX `address`, + ADD UNIQUE KEY `address` (`address`,`prefix`,`layer3domain_id`), + ALGORITHM=INPLACE, LOCK=NONE; + +UPDATE schemainfo SET version=13; +COMMIT;