Skip to content

OVN NB drift on router gateways: missing localnet uplink port + unrepairable router-peer LSP corruption #2330

Description

@cardoe

Summary

Investigating a "no outbound connectivity" report on gateway (staging, PUBLICNET) turned up two separate OVN NB drift bugs that left a router's external gateway completely non-functional, with no automated tooling able to detect or repair either one.

What we found

1. Missing OVN localnet uplink port, with no reconciliation path

routers.create_uplink_port/delete_uplink_port (neutron_understack/routers.py) create/delete a raw type=localnet Logical_Switch_Port directly via ovn_client()._nb_idl.create_lswitch_port(...), bypassing Neutron's revision-number tracking entirely (empty external_ids, no ovn_revision_numbers row).

Neutron's core OVN maintenance worker (check_for_inconsistencies / get_deleted_resources) can only detect drift for resources that were ever inserted into ovn_revision_numbers — confirmed by reading neutron/db/ovn_revision_numbers_db.py:get_deleted_resources, which filters ovn_revision_numbers rows with standard_attr_id IS NULL. Since this LSP was never in that table, the maintenance worker has no way to know it exists, let alone repair it if it goes missing.

In this incident, the localnet port for PUBLICNET's VLAN segment (1806/f20-5-network) was simply gone. Nothing noticed. Outbound traffic through the router's SNAT had no physical uplink to leave on.

2. Router gateway's switch-side peer LSP had type/options blanked, and neutron-ovn-db-sync-util --repair couldn't fix it

The switch-side Logical_Switch_Port peer for the router's gateway LRP (name = the Neutron port ID, external_ids correctly showing device_owner=network:router_gateway) had type="" and options={} instead of type=router + options:router-port=.../nat-addresses/exclude-lb-vips-from-garp. This is set by SetLRouterPortInLSwitchPortCommand (core Neutron OVN driver, commands.py), called from create_router_port/_update_lrouter_port. We don't have a confirmed trigger for how it got blanked (candidate: ovn_client.update_port()'s upsert-style path skipping the type=router re-assertion — see ovn_client.py:709-719 — though the is_ovn_provider_router() gate should have passed for this non-flavored router, so this isn't fully proven).

More importantly: running neutron-ovn-db-sync-util's full repair did not fix it. ovn_db_sync.py:_is_router_port_changed (used by sync_routers_and_rports to decide whether a router port needs re-syncing) only compares networks and ipv6_ra_configs — the LRP-side data. It never inspects the switch-side peer LSP's type/options at all. Since this router's LRP-side data was never corrupted (only the switch-side peer was), the sync tool concluded "no change needed" and silently skipped the exact field that was broken. The repair run visibly refreshed external_ids/revision_number (via the generic ports-sync pass) but left type/options untouched.

Both were fixed manually via direct ovn-nbctl writes (not via any existing tool):

# 1. recreate the missing localnet uplink port
ovn-nbctl lsp-add <switch> uplink-<segment_id> -- \
  lsp-set-type uplink-<segment_id> localnet -- \
  lsp-set-addresses uplink-<segment_id> unknown -- \
  set Logical_Switch_Port uplink-<segment_id> tag=<vlan> \
    options:network_name=<physnet> options:mcast_flood_reports=true \
    options:mcast_flood=false options:localnet_learn_fdb=false

# 2. restore the router-gateway peer LSP's type/options
ovn-nbctl set Logical_Switch_Port <router_port_id> \
  type=router \
  options:router-port=lrp-<router_port_id> \
  options:nat-addresses=router \
  options:exclude-lb-vips-from-garp=true

Ask

  • For (1): either make create_uplink_port/delete_uplink_port go through Neutron's revision-tracked port-creation path so the existing maintenance-worker machinery covers them for free, or add a dedicated periodic reconciliation task in neutron-understack that walks router-owned networks and recreates whichever half (Neutron trunk-subport vs. OVN localnet LSP) is missing.
  • For (2): this is upstream/core Neutron behavior (neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_db_sync.py), not neutron-understack — worth deciding whether we carry a local patch, or report/patch upstream, so _is_router_port_changed also compares the switch-side peer LSP's type/options, not just the LRP's networks.

Note: we weren't able to confirm the exact trigger event for (2) — it happened around the same time as an unrelated RabbitMQ queue-durability cleanup + neutron-server restart, but that's circumstantial, not confirmed from logs.

Environment

  • rax-staging-iad3-staging
  • Router: 1533c5be-e717-45d3-a082-f84f18063016
  • Network: PUBLICNET (b420e863-7d26-4c87-8974-76f6c8750640), VLAN 1806 / f20-5-network

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions