Skip to content

Support multiple external addresses in database inventory tables - #11114

Merged
bnaecker merged 3 commits into
mainfrom
ben/multiple-eips-in-database-inventory
Aug 28, 2026
Merged

Support multiple external addresses in database inventory tables#11114
bnaecker merged 3 commits into
mainfrom
ben/multiple-eips-in-database-inventory

Conversation

@bnaecker

Copy link
Copy Markdown
Collaborator
  • Add a new table inv_omicron_sled_config_zone_external_ip to store the external IP information for inventoried zones in inv_omicron_sled_config_zone. This lets us support multiple IPs in the table, each with a reference back to the inventory collection and zone it came from.
  • Migrate schema and old data. This moves the "second service IP" information to the new table, only for the zones where that IP is an external address (Nexus, External DNS, and Boundary NTP). This also drops the now-unused SNAT related columns, but leaves the second_service_ip column for things like internal DNS's additional underlay address.
  • Add model types and update a bunch of callsites and tests.

@bnaecker
bnaecker force-pushed the ben/multiple-eips-in-database-inventory branch 2 times, most recently from c22a827 to 1ed5074 Compare August 20, 2026 02:55
- Add a new table `inv_omicron_sled_config_zone_external_ip` to store
  the external IP information for inventoried zones in
  `inv_omicron_sled_config_zone`. This lets us support multiple IPs in
  the table, each with a reference back to the inventory collection and
  zone it came from.
- Migrate schema and old data. This moves the "second service IP"
  information to the new table, only for the zones where that IP is an
  external address (Nexus, External DNS, and Boundary NTP). This also
  drops the now-unused SNAT related columns, but leaves the
  `second_service_ip` column for things like internal DNS's additional
  underlay address.
- Add model types and update a bunch of callsites and tests.
@bnaecker
bnaecker force-pushed the ben/multiple-eips-in-database-inventory branch from 1ed5074 to e5d565d Compare August 25, 2026 21:47
@bnaecker
bnaecker requested a review from jgallagher August 25, 2026 21:48
@jgallagher
jgallagher requested a review from davepacheco August 26, 2026 17:47
inv_collection_id, sled_config_id, id, snat_ip,
NULL::INT4, snat_first_port, snat_last_port
FROM omicron.public.inv_omicron_sled_config_zone
WHERE zone_type = 'boundary_ntp' AND snat_ip IS NOT NULL;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need an ON CONFLICT DO NOTHING to be idempotent? (up02.sql runs and commits, then Nexus crashes before we move to up03.sql - I think we'll rerun up02.sql again and try to insert the same rows?)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so. The update_since_base_has_idempotent_up() test applies the schema migrations twice, and we don't fail here. I think that's because each schema migration, as a whole, is applied inside a BEGIN/COMMIT transaction. When we parse the update SQL files, we determine if they're transactional based on a special suffix to the filenames. This doesn't have that, so everything is run in one transaction.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I'm not sure.

The update_since_base_has_idempotent_up() test applies the schema migrations twice, and we don't fail here.

I don't think there's any relevant data in the tables during that test, so this INSERT wouldn't insert anything that could possible conflict. That test is really only checking that structural changes are idempotent.

I think that's because each schema migration, as a whole, is applied inside a BEGIN/COMMIT transaction. When we parse the update SQL files, we determine if they're transactional based on a special suffix to the filenames. This doesn't have that, so everything is run in one transaction.

My concern is that Nexus recording which step of the migration it's on is not in the same transaction as the migration itself, so any upXX.sql can be run multiple times (even if it already committed successfully). E.g., this sequence:

  1. Nexus prepares to run up01.sql.
  2. Nexus applies up01.sql.
  3. Nexus prepare up02.sql.
  4. Nexus applies up02.sql. This inserts all the relevant rows, transactionally.
  5. Nexus crashes before it prepares up03.sql.

When Nexus restarts, I believe it will not know whether up02.sql was applied or not, so it will rerun it. At this point migration will fail because trying to insert the same rows again will conflict, right?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point about the test, I agree there's no data and so nothing to conflict either way.

When Nexus restarts, I believe it will not know whether up02.sql was applied or not, so it will rerun it. At this point migration will fail because trying to insert the same rows again will conflict, right?

Ah I think I confused myself into believing the whole schema migration is transactional. But it's only each step. I agree, this is a problem. I might try to add a test, but will also add an ON CONFLICT clause either way.

Comment thread schema/crdb/dbinit.sql
Comment thread nexus/db-queries/src/db/datastore/inventory.rs
Comment thread schema/crdb/dbinit.sql
Comment thread nexus/db-queries/src/db/datastore/inventory.rs Outdated
Comment thread schema/crdb/inventory-zone-multiple-external-ips/up03.sql Outdated
Comment thread schema/crdb/dbinit.sql
- Add contraints around new table's port columns
- Some typos
Comment thread schema/crdb/inventory-zone-multiple-external-ips/up02.sql
Comment thread schema/crdb/inventory-zone-multiple-external-ips/up02.sql
@bnaecker

Copy link
Copy Markdown
Collaborator Author

Adding a specific test to catch conflicts in the data migration is essentially John's #11193, and it requires figuring out what to do about the past migrations that need an ON CONFLICT clause. But I did add one to the new migration in this PR.

jgallagher added a commit that referenced this pull request Aug 28, 2026
This came up in
#11114 (comment);
if I run this change against that branch in its initial state, we do see
a test failure as described in that conversation:

```
    thread 'integration_tests::schema::validate_data_migrations' (1556916) panicked at nexus/tests/integration_tests/schema.rs:71:9:
    Failed to execute update step up02.sql: db error: ERROR: duplicate key value violates unique constraint "inv_omicron_sled_config_zone_external_ip_pkey"
    DETAIL: Key (inv_collection_id,sled_config_id,zone_id,ip)=('d29f7b65-9b2b-4650-9b80-9ba968468083','1fda886f-b3e4-4068-9e20-b29d7d4c9190','ab1d1dc9-2737-4c60-8aeb-f1da4ac92d93','192.0.2.1') already exists.
```

Unfortunately, this also causes some already-shipped migrations to fail.
I patched those up by adding `ON CONFLICT DO NOTHING`; retroactively
changing migrations seems quite spicy, so I welcome other suggestions.
(We could trim our start point up past these two instead?)
@bnaecker
bnaecker enabled auto-merge (squash) August 28, 2026 22:05
@bnaecker
bnaecker merged commit 17e6fee into main Aug 28, 2026
19 checks passed
@bnaecker
bnaecker deleted the ben/multiple-eips-in-database-inventory branch August 28, 2026 22:10
jgallagher added a commit that referenced this pull request Aug 31, 2026
This came up in
#11114 (comment);
if I run this change against that branch in its initial state, we do see
a test failure as described in that conversation:

```
    thread 'integration_tests::schema::validate_data_migrations' (1556916) panicked at nexus/tests/integration_tests/schema.rs:71:9:
    Failed to execute update step up02.sql: db error: ERROR: duplicate key value violates unique constraint "inv_omicron_sled_config_zone_external_ip_pkey"
    DETAIL: Key (inv_collection_id,sled_config_id,zone_id,ip)=('d29f7b65-9b2b-4650-9b80-9ba968468083','1fda886f-b3e4-4068-9e20-b29d7d4c9190','ab1d1dc9-2737-4c60-8aeb-f1da4ac92d93','192.0.2.1') already exists.
```

Unfortunately, this also causes some already-shipped migrations to fail.
I patched those up by adding `ON CONFLICT DO NOTHING`; retroactively
changing migrations seems quite spicy, so I welcome other suggestions.
(We could trim our start point up past these two instead?)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants