diff --git a/clients/sled-agent-client/src/lib.rs b/clients/sled-agent-client/src/lib.rs index 58a1fc0d14..9df5230779 100644 --- a/clients/sled-agent-client/src/lib.rs +++ b/clients/sled-agent-client/src/lib.rs @@ -56,6 +56,7 @@ progenitor::generate_api!( ReifiedVpcRoute = omicron_common::api::internal::shared::ReifiedVpcRoute, ReifiedVpcRouteSet = omicron_common::api::internal::shared::ReifiedVpcRouteSet, RouterTarget = omicron_common::api::internal::shared::RouterTarget, + RouterVersion = omicron_common::api::internal::shared::RouterVersion, SourceNatConfig = omicron_common::api::internal::shared::SourceNatConfig, Vni = omicron_common::api::external::Vni, NetworkInterface = omicron_common::api::internal::shared::NetworkInterface, diff --git a/nexus/db-queries/src/db/datastore/vpc.rs b/nexus/db-queries/src/db/datastore/vpc.rs index 69972a701c..d6b9645f93 100644 --- a/nexus/db-queries/src/db/datastore/vpc.rs +++ b/nexus/db-queries/src/db/datastore/vpc.rs @@ -1684,6 +1684,58 @@ impl DataStore { Ok(out) } + + /// Trigger an RPW version bump on a single VPC router in response + /// to CRUD operations on individual routes. + pub async fn vpc_router_increment_rpw_version( + &self, + opctx: &OpContext, + authz_router: &authz::VpcRouter, + ) -> UpdateResult<()> { + opctx.authorize(authz::Action::Modify, authz_router).await?; + + use db::schema::vpc_router::dsl; + diesel::update(dsl::vpc_router) + .filter(dsl::time_deleted.is_null()) + .filter(dsl::id.eq(authz_router.id())) + .set(dsl::resolved_version.eq(dsl::resolved_version + 1)) + .execute_async(&*self.pool_connection_authorized(opctx).await?) + .await + .map_err(|e| { + public_error_from_diesel( + e, + ErrorHandler::NotFoundByResource(authz_router), + ) + })?; + + Ok(()) + } + + /// Trigger an RPW version bump on all routers within a VPC in + /// response to changes to named entities (e.g., subnets, instances). + pub async fn vpc_increment_rpw_version( + &self, + opctx: &OpContext, + authz_vpc: &authz::Vpc, + ) -> UpdateResult<()> { + opctx.authorize(authz::Action::CreateChild, authz_vpc).await?; + + use db::schema::vpc_router::dsl; + diesel::update(dsl::vpc_router) + .filter(dsl::time_deleted.is_null()) + .filter(dsl::vpc_id.eq(authz_vpc.id())) + .set(dsl::resolved_version.eq(dsl::resolved_version + 1)) + .execute_async(&*self.pool_connection_authorized(opctx).await?) + .await + .map_err(|e| { + public_error_from_diesel( + e, + ErrorHandler::NotFoundByResource(authz_vpc), + ) + })?; + + Ok(()) + } } #[cfg(test)] diff --git a/nexus/src/app/background/vpc_routes.rs b/nexus/src/app/background/vpc_routes.rs index d4d6478c93..eb19753c27 100644 --- a/nexus/src/app/background/vpc_routes.rs +++ b/nexus/src/app/background/vpc_routes.rs @@ -192,7 +192,7 @@ impl BackgroundTask for VpcRouteManager { let Some(db_router) = db_routers.get(&set.id) else { // The sled wants to know about rules for a VPC // subnet with no custom router set. Send them - // the empty list, unset its table version. + // the empty list, and unset its table version. set_rules(set.id, None, HashSet::new()); continue; }; @@ -203,12 +203,11 @@ impl BackgroundTask for VpcRouteManager { router_id, }; - // Only attempt to resolve/push a ruleset if we have a different - // router ID than the sled, or a higher version number. + // Only attempt to resolve/push a ruleset if we have a + // different router ID than the sled, or a higher version + // number. match &set.version { - Some(v) - if v.router_id == router_id - && v.generation >= version.generation => + Some(v) if !v.is_replaced_by(&version) => { continue; }