"""IP-адрес уникален в пределах VRF и хранится в самом узком префиксе Revision ID: 0007 Revises: 0006 """ from alembic import op import sqlalchemy as sa revision = "0007" down_revision = "0006" branch_labels = None depends_on = None def upgrade() -> None: bind = op.get_bind() dup = bind.execute(sa.text( "SELECT p.vrf_id, host(a.address) AS ip, array_agg(p.prefix::text ORDER BY p.id) AS prefixes " "FROM addresses a JOIN prefixes p ON p.id = a.prefix_id GROUP BY p.vrf_id, host(a.address) HAVING count(*) > 1")).all() if dup: lines = "; ".join(f"VRF {r.vrf_id}: {r.ip} в {', '.join(r.prefixes)}" for r in dup[:20]) raise RuntimeError(f"Один IP записан несколько раз в одном VRF ({len(dup)} шт.): {lines}. Разберите дубли (scripts/find_duplicate_addresses.py) и повторите миграцию") op.create_unique_constraint("uq_prefixes_id_vrf_id", "prefixes", ["id", "vrf_id"]) op.add_column("addresses", sa.Column("vrf_id", sa.Integer(), nullable=True)) bind.execute(sa.text("UPDATE addresses a SET vrf_id = p.vrf_id FROM prefixes p WHERE p.id = a.prefix_id")) op.alter_column("addresses", "vrf_id", nullable=False) # адрес — в самом узком префиксе своего VRF (раньше он мог остаться в родителе) bind.execute(sa.text( "UPDATE addresses a SET prefix_id = t.best FROM (" " SELECT a2.id AS aid, (SELECT p.id FROM prefixes p WHERE p.vrf_id = a2.vrf_id AND p.prefix >>= a2.address " " ORDER BY masklen(p.prefix) DESC LIMIT 1) AS best FROM addresses a2) t " "WHERE a.id = t.aid AND t.best IS NOT NULL AND t.best <> a.prefix_id")) # предупреждение (изменение 024, находка №2): перенос мог сделать адрес адресом сети/broadcast его нового префикса # (правило появилось позже, в изменении 015); миграцию не останавливаем, только сообщаем — найти такие поможет find_unusable_addresses.py bad = bind.execute(sa.text( "SELECT host(a.address) AS ip, p.prefix::text AS cidr FROM addresses a JOIN prefixes p ON p.id = a.prefix_id " "WHERE family(p.prefix) = 4 AND masklen(p.prefix) <= 30 " "AND (host(a.address) = host(network(p.prefix)) OR host(a.address) = host(broadcast(p.prefix)))")).all() if bad: lines = "; ".join(f"{r.ip} в {r.cidr}" for r in bad[:20]) print(f"ВНИМАНИЕ: после переноса в самый узкий префикс {len(bad)} адрес(ов) стали адресом сети/broadcast: {lines}" + (" …" if len(bad) > 20 else "")) op.drop_constraint("addresses_prefix_id_fkey", "addresses", type_="foreignkey") op.create_foreign_key("fk_addresses_prefix_vrf", "addresses", "prefixes", ["prefix_id", "vrf_id"], ["id", "vrf_id"], ondelete="CASCADE", onupdate="CASCADE") op.create_unique_constraint("uq_addresses_vrf_address", "addresses", ["vrf_id", "address"]) def downgrade() -> None: op.drop_constraint("uq_addresses_vrf_address", "addresses", type_="unique") op.drop_constraint("fk_addresses_prefix_vrf", "addresses", type_="foreignkey") op.create_foreign_key("addresses_prefix_id_fkey", "addresses", "prefixes", ["prefix_id"], ["id"], ondelete="CASCADE") op.drop_column("addresses", "vrf_id") op.drop_constraint("uq_prefixes_id_vrf_id", "prefixes", type_="unique")