diff --git a/orchagent/routeorch.h b/orchagent/routeorch.h index 9142f031..e0dfc67b 100644 --- a/orchagent/routeorch.h +++ b/orchagent/routeorch.h @@ -157,9 +157,11 @@ struct RouteBulkContext { } - // Disable any copy constructors + // Disable copy but allow move RouteBulkContext(const RouteBulkContext&) = delete; - RouteBulkContext(RouteBulkContext&&) = delete; + RouteBulkContext& operator=(const RouteBulkContext&) = delete; + RouteBulkContext(RouteBulkContext&&) = default; + RouteBulkContext& operator=(RouteBulkContext&&) = default; void clear() { @@ -267,6 +269,10 @@ class RouteOrch : public ZmqOrch, public Subject bool checkNextHopGroupCount(); const RouteTables& getSyncdRoutes() const { return m_syncdRoutes; } + EntityBulker gRouteBulker; + EntityBulker gLabelRouteBulker; + ObjectBulker gNextHopGroupMemberBulker; + private: SwitchOrch *m_switchOrch; NeighOrch *m_neighOrch; @@ -298,10 +304,6 @@ class RouteOrch : public ZmqOrch, public Subject NextHopObserverTable m_nextHopObservers; - EntityBulker gRouteBulker; - EntityBulker gLabelRouteBulker; - ObjectBulker gNextHopGroupMemberBulker; - void addTempRoute(RouteBulkContext& ctx, const NextHopGroupKey&); void addTempLabelRoute(LabelRouteBulkContext& ctx, const NextHopGroupKey&); diff --git a/orchagent/vnetorch.cpp b/orchagent/vnetorch.cpp index cf5a8739..0b1988b2 100644 --- a/orchagent/vnetorch.cpp +++ b/orchagent/vnetorch.cpp @@ -35,6 +35,7 @@ extern sai_next_hop_api_t* sai_next_hop_api; extern sai_next_hop_group_api_t* sai_next_hop_group_api; extern sai_object_id_t gSwitchId; extern sai_object_id_t gVirtualRouterId; +extern size_t gMaxBulkSize; extern Directory gDirectory; extern PortsOrch *gPortsOrch; extern IntfsOrch *gIntfsOrch; @@ -726,7 +727,8 @@ static bool update_route(sai_object_id_t vr_id, sai_ip_prefix_t& ip_pfx, sai_obj VNetRouteOrch::VNetRouteOrch(DBConnector *db, vector &tableNames, VNetOrch *vnetOrch) : Orch2(db, tableNames, request_), vnet_orch_(vnetOrch), bfd_session_producer_(db, APP_BFD_SESSION_TABLE_NAME), - app_tunnel_decap_term_producer_(db, APP_TUNNEL_DECAP_TERM_TABLE_NAME) + app_tunnel_decap_term_producer_(db, APP_TUNNEL_DECAP_TERM_TABLE_NAME), + tunnel_route_bulker_(sai_route_api, gMaxBulkSize) { SWSS_LOG_ENTER(); @@ -1181,8 +1183,6 @@ bool VNetRouteOrch::doRouteTask(const string& vnet, IpPrefix& ipP auto it_route = syncd_tunnel_routes_[vnet].find(ipPrefix); for (auto vr_id : vr_set) { - bool route_status = true; - // Remove route if the nexthop group has no active endpoint if (syncd_nexthop_groups_[vnet][active_nhg].active_members.empty()) { @@ -1192,9 +1192,19 @@ bool VNetRouteOrch::doRouteTask(const string& vnet, IpPrefix& ipP // Remove route when updating from a nhg with active member to another nhg without if (!syncd_nexthop_groups_[vnet][nhg].active_members.empty()) { - del_route(vr_id, pfx); + delTunnelRouteBulk(vnet, vr_id, ipPrefix); + } + else + { + object_statuses_.emplace_back(SAI_STATUS_SUCCESS); + tunnel_route_contexts_.emplace_back(vnet, ipPrefix, true, object_statuses_.size() - 1); } } + else + { + object_statuses_.emplace_back(SAI_STATUS_SUCCESS); + tunnel_route_contexts_.emplace_back(vnet, ipPrefix, true, object_statuses_.size() - 1); + } } else { @@ -1216,133 +1226,33 @@ bool VNetRouteOrch::doRouteTask(const string& vnet, IpPrefix& ipP } if (it_route == syncd_tunnel_routes_[vnet].end()) { - route_status = add_route(vr_id, pfx, nh_id); + addTunnelRouteBulk(vnet, vr_id, ipPrefix, nh_id); } else { NextHopGroupKey nhg = it_route->second.nhg_key; if (syncd_nexthop_groups_[vnet][nhg].active_members.empty()) { - route_status = add_route(vr_id, pfx, nh_id); + addTunnelRouteBulk(vnet, vr_id, ipPrefix, nh_id); } else { - route_status = update_route(vr_id, pfx, nh_id); + updateTunnelRouteBulk(vnet, vr_id, ipPrefix, nh_id); } } } - if (!route_status) + if (!tunnel_route_contexts_.empty()) { - SWSS_LOG_ERROR("Route add/update failed for %s, vr_id '0x%" PRIx64, ipPrefix.to_string().c_str(), vr_id); - /* Clean up the newly created next hop group entry */ - if (active_nhg.getSize() > 1) - { - removeNextHopGroup(vnet, active_nhg, vrf_obj); - } - return false; + auto& ctx = tunnel_route_contexts_.back(); + ctx.nhg = active_nhg; + ctx.profile = profile; + ctx.monitoring = monitoring; + ctx.primary = nexthops; + ctx.secondary = nexthops_secondary; + ctx.adv_prefix = adv_prefix; } } - bool route_updated = false; - bool priority_route_updated = false; - if (it_route != syncd_tunnel_routes_[vnet].end() && - ((monitoring == "" && it_route->second.nhg_key != nexthops) || - ((monitoring == VNET_MONITORING_TYPE_CUSTOM || monitoring == VNET_MONITORING_TYPE_CUSTOM_BFD) && (it_route->second.primary != nexthops || it_route->second.secondary != nexthops_secondary)))) - { - route_updated = true; - NextHopGroupKey nhg = it_route->second.nhg_key; - if (monitoring == VNET_MONITORING_TYPE_CUSTOM || monitoring == VNET_MONITORING_TYPE_CUSTOM_BFD) - { - // if the previously active NHG is same as the newly created active NHG.case of primary secondary swap or - //when primary is active and secondary is changed or vice versa. In these cases we dont remove the NHG - // but only remove the monitors for the set which has changed. - if (it_route->second.primary != nexthops) - { - delEndpointMonitor(vnet, it_route->second.primary, ipPrefix); - } - if (it_route->second.secondary != nexthops_secondary) - { - delEndpointMonitor(vnet, it_route->second.secondary, ipPrefix); - } - if (monitor_info_[vnet][ipPrefix].empty()) - { - monitor_info_[vnet].erase(ipPrefix); - } - priority_route_updated = true; - } - else - { - // In case of updating an existing route, decrease the reference count for the previous nexthop group - if (--syncd_nexthop_groups_[vnet][nhg].ref_count == 0) - { - if (nhg.getSize() > 1) - { - removeNextHopGroup(vnet, nhg, vrf_obj); - } - else - { - syncd_nexthop_groups_[vnet].erase(nhg); - if(nhg.getSize() == 1) - { - NextHopKey nexthop = *nhg.getNextHops().begin(); - if (!isLocalEndpoint(vnet, nexthop.ip_address)) - { - vrf_obj->removeTunnelNextHop(nexthop); - } - } - } - if (monitoring != VNET_MONITORING_TYPE_CUSTOM && monitoring != VNET_MONITORING_TYPE_CUSTOM_BFD) - { - delEndpointMonitor(vnet, nhg, ipPrefix); - } - } - else - { - syncd_nexthop_groups_[vnet][nhg].tunnel_routes.erase(ipPrefix); - } - vrf_obj->removeRoute(ipPrefix); - vrf_obj->removeProfile(ipPrefix); - } - } - if (!profile.empty()) - { - vrf_obj->addProfile(ipPrefix, profile); - } - if (it_route == syncd_tunnel_routes_[vnet].end() || route_updated) - { - syncd_nexthop_groups_[vnet][active_nhg].tunnel_routes.insert(ipPrefix); - VNetTunnelRouteEntry tunnel_route_entry; - tunnel_route_entry.nhg_key = active_nhg; - tunnel_route_entry.primary = nexthops; - tunnel_route_entry.secondary = nexthops_secondary; - syncd_tunnel_routes_[vnet][ipPrefix] = tunnel_route_entry; - syncd_nexthop_groups_[vnet][active_nhg].ref_count++; - - if (priority_route_updated) - { - MonitorUpdate update; - update.prefix = ipPrefix; - update.state = MONITOR_SESSION_STATE_UNKNOWN; - update.vnet = vnet; - updateVnetTunnelCustomMonitor(update); - return true; - } - - if (adv_prefix.to_string() != ipPrefix.to_string() && prefix_to_adv_prefix_.find(ipPrefix) == prefix_to_adv_prefix_.end()) - { - prefix_to_adv_prefix_[ipPrefix] = adv_prefix; - if (adv_prefix_refcount_.find(adv_prefix) == adv_prefix_refcount_.end()) - { - adv_prefix_refcount_[adv_prefix] = 0; - } - if(active_nhg.getSize() > 0) - { - adv_prefix_refcount_[adv_prefix] += 1; - } - } - vrf_obj->addRoute(ipPrefix, active_nhg); - } - postRouteState(vnet, ipPrefix, active_nhg, profile); } else if (op == DEL_COMMAND) { @@ -1354,82 +1264,281 @@ bool VNetRouteOrch::doRouteTask(const string& vnet, IpPrefix& ipP return true; } NextHopGroupKey nhg = it_route->second.nhg_key; - auto last_nhg_size = nhg.getSize(); for (auto vr_id : vr_set) { // If an nhg has no active member, the route should already be removed if (!syncd_nexthop_groups_[vnet][nhg].active_members.empty()) { - if (!del_route(vr_id, pfx)) - { - SWSS_LOG_ERROR("Route del failed for %s, vr_id '0x%" PRIx64, ipPrefix.to_string().c_str(), vr_id); - return false; - } - SWSS_LOG_INFO("Successfully deleted the route for prefix: %s", ipPrefix.to_string().c_str()); - + delTunnelRouteBulk(vnet, vr_id, ipPrefix); + + SWSS_LOG_INFO("Successfully queued tunnel route deletion for prefix: %s", ipPrefix.to_string().c_str()); + } + else + { + object_statuses_.emplace_back(SAI_STATUS_SUCCESS); + tunnel_route_contexts_.emplace_back(vnet, ipPrefix, false, object_statuses_.size() - 1); + + SWSS_LOG_INFO("Route %s already removed (no active endpoints), creating no-op context for cleanup", + ipPrefix.to_string().c_str()); + } + + if (!tunnel_route_contexts_.empty()) + { + auto& ctx = tunnel_route_contexts_.back(); + ctx.nhg = nhg; + ctx.profile = vrf_obj->getProfile(ipPrefix); + ctx.primary = it_route->second.primary; + ctx.secondary = it_route->second.secondary; + ctx.adv_prefix = adv_prefix; } } - if(--syncd_nexthop_groups_[vnet][nhg].ref_count == 0) + } + return true; +} + +bool VNetRouteOrch::addTunnelRouteBulk(const string& vnet, sai_object_id_t vr_id, const IpPrefix& ipPrefix, sai_object_id_t nh_id) +{ + SWSS_LOG_ENTER(); + + sai_route_entry_t route_entry; + route_entry.vr_id = vr_id; + route_entry.switch_id = gSwitchId; + copy(route_entry.destination, ipPrefix); + + sai_attribute_t route_attr; + route_attr.id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; + route_attr.value.oid = nh_id; + + object_statuses_.emplace_back(); + tunnel_route_bulker_.create_entry(&object_statuses_.back(), &route_entry, 1, &route_attr); + tunnel_route_contexts_.emplace_back(vnet, ipPrefix, true, object_statuses_.size() - 1); + + return true; +} + +bool VNetRouteOrch::delTunnelRouteBulk(const string& vnet, sai_object_id_t vr_id, const IpPrefix& ipPrefix) +{ + SWSS_LOG_ENTER(); + + sai_route_entry_t route_entry; + route_entry.vr_id = vr_id; + route_entry.switch_id = gSwitchId; + copy(route_entry.destination, ipPrefix); + + object_statuses_.emplace_back(); + tunnel_route_bulker_.remove_entry(&object_statuses_.back(), &route_entry); + tunnel_route_contexts_.emplace_back(vnet, ipPrefix, false, object_statuses_.size() - 1); + + return true; +} + +bool VNetRouteOrch::updateTunnelRouteBulk(const string& vnet, sai_object_id_t vr_id, const IpPrefix& ipPrefix, sai_object_id_t nh_id) +{ + SWSS_LOG_ENTER(); + + sai_route_entry_t route_entry; + route_entry.vr_id = vr_id; + route_entry.switch_id = gSwitchId; + copy(route_entry.destination, ipPrefix); + + sai_attribute_t route_attr; + route_attr.id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; + route_attr.value.oid = nh_id; + + object_statuses_.emplace_back(); + tunnel_route_bulker_.set_entry_attribute(&object_statuses_.back(), &route_entry, &route_attr); + tunnel_route_contexts_.emplace_back(vnet, ipPrefix, false, object_statuses_.size() - 1, true); + + return true; +} + +bool VNetRouteOrch::addTunnelRoutePost(const TunnelRouteContext& tr_ctx) +{ + SWSS_LOG_ENTER(); + + auto *vrf_obj = vnet_orch_->getTypePtr(tr_ctx.vnet); + auto it_route = syncd_tunnel_routes_[tr_ctx.vnet].find(tr_ctx.ip_prefix); + bool route_updated = false; + bool priority_route_updated = false; + IpPrefix ip_prefix = tr_ctx.ip_prefix; + NextHopGroupKey nhg = tr_ctx.nhg; + string profile = tr_ctx.profile; + + if (it_route != syncd_tunnel_routes_[tr_ctx.vnet].end() && + ((tr_ctx.monitoring == "" && it_route->second.nhg_key != tr_ctx.nhg) || + ((tr_ctx.monitoring == VNET_MONITORING_TYPE_CUSTOM || tr_ctx.monitoring == VNET_MONITORING_TYPE_CUSTOM_BFD) && (it_route->second.primary != tr_ctx.primary || it_route->second.secondary != tr_ctx.secondary)))) + { + route_updated = true; + NextHopGroupKey old_nhg = it_route->second.nhg_key; + if (tr_ctx.monitoring == VNET_MONITORING_TYPE_CUSTOM || tr_ctx.monitoring == VNET_MONITORING_TYPE_CUSTOM_BFD) { - if (nhg.getSize() > 1) + // if the previously active NHG is same as the newly created active NHG.case of primary secondary swap or + //when primary is active and secondary is changed or vice versa. In these cases we dont remove the NHG + // but only remove the monitors for the set which has changed. + if (it_route->second.primary != tr_ctx.primary) { - removeNextHopGroup(vnet, nhg, vrf_obj); + delEndpointMonitor(tr_ctx.vnet, it_route->second.primary, ip_prefix); } - else + if (it_route->second.secondary != tr_ctx.secondary) + { + delEndpointMonitor(tr_ctx.vnet, it_route->second.secondary, ip_prefix); + } + if (monitor_info_[tr_ctx.vnet][tr_ctx.ip_prefix].empty()) + { + monitor_info_[tr_ctx.vnet].erase(tr_ctx.ip_prefix); + } + priority_route_updated = true; + } + else + { + // In case of updating an existing route, decrease the reference count for the previous nexthop group + if (--syncd_nexthop_groups_[tr_ctx.vnet][old_nhg].ref_count == 0) { - syncd_nexthop_groups_[vnet].erase(nhg); - // We need to check specifically if there is only one next hop active. - // In case of Priority routes we can end up in a situation where the active NHG has 0 nexthops. - if(nhg.getSize() == 1) + if (old_nhg.getSize() > 1) { - NextHopKey nexthop = *nhg.getNextHops().begin(); - if (!isLocalEndpoint(vnet, nexthop.ip_address)) + removeNextHopGroup(tr_ctx.vnet, old_nhg, vrf_obj); + } + else + { + syncd_nexthop_groups_[tr_ctx.vnet].erase(old_nhg); + if (old_nhg.getSize() == 1) { - vrf_obj->removeTunnelNextHop(nexthop); + NextHopKey nexthop = *old_nhg.getNextHops().begin(); + if (!isLocalEndpoint(tr_ctx.vnet, nexthop.ip_address)) + { + vrf_obj->removeTunnelNextHop(nexthop); + } } } + if (tr_ctx.monitoring != VNET_MONITORING_TYPE_CUSTOM && tr_ctx.monitoring != VNET_MONITORING_TYPE_CUSTOM_BFD) + { + delEndpointMonitor(tr_ctx.vnet, old_nhg, ip_prefix); + } } - if (monitor_info_[vnet].find(ipPrefix) == monitor_info_[vnet].end()) + else { - delEndpointMonitor(vnet, nhg, ipPrefix); + syncd_nexthop_groups_[tr_ctx.vnet][old_nhg].tunnel_routes.erase(tr_ctx.ip_prefix); } + vrf_obj->removeRoute(ip_prefix); + vrf_obj->removeProfile(ip_prefix); } - else + } + if (!profile.empty()) + { + vrf_obj->addProfile(ip_prefix, profile); + } + if (it_route == syncd_tunnel_routes_[tr_ctx.vnet].end() || route_updated) + { + syncd_nexthop_groups_[tr_ctx.vnet][nhg].tunnel_routes.insert(tr_ctx.ip_prefix); + VNetTunnelRouteEntry tunnel_route_entry; + tunnel_route_entry.nhg_key = nhg; + tunnel_route_entry.primary = tr_ctx.primary; + tunnel_route_entry.secondary = tr_ctx.secondary; + syncd_tunnel_routes_[tr_ctx.vnet][tr_ctx.ip_prefix] = tunnel_route_entry; + syncd_nexthop_groups_[tr_ctx.vnet][nhg].ref_count++; + + if (priority_route_updated) { - syncd_nexthop_groups_[vnet][nhg].tunnel_routes.erase(ipPrefix); + MonitorUpdate update; + update.prefix = tr_ctx.ip_prefix; + update.state = MONITOR_SESSION_STATE_UNKNOWN; + update.vnet = tr_ctx.vnet; + updateVnetTunnelCustomMonitor(update); + return true; } - if (monitor_info_[vnet].find(ipPrefix) != monitor_info_[vnet].end()) + + if (tr_ctx.adv_prefix.to_string() != ip_prefix.to_string() && + prefix_to_adv_prefix_.find(ip_prefix) == prefix_to_adv_prefix_.end()) { - delEndpointMonitor(vnet, it_route->second.primary, ipPrefix); - delEndpointMonitor(vnet, it_route->second.secondary, ipPrefix); - monitor_info_[vnet].erase(ipPrefix); + prefix_to_adv_prefix_[ip_prefix] = tr_ctx.adv_prefix; + if (adv_prefix_refcount_.find(tr_ctx.adv_prefix) == adv_prefix_refcount_.end()) + { + adv_prefix_refcount_[tr_ctx.adv_prefix] = 0; + } + if (nhg.getSize() > 0) + { + adv_prefix_refcount_[tr_ctx.adv_prefix] += 1; + } } + vrf_obj->addRoute(ip_prefix, nhg); + } + postRouteState(tr_ctx.vnet, ip_prefix, nhg, profile); + return true; +} - syncd_tunnel_routes_[vnet].erase(ipPrefix); - if (syncd_tunnel_routes_[vnet].empty()) +bool VNetRouteOrch::delTunnelRoutePost(const TunnelRouteContext& tr_ctx) +{ + SWSS_LOG_ENTER(); + + IpPrefix ip_prefix = tr_ctx.ip_prefix; + NextHopGroupKey nhg = tr_ctx.nhg; + NextHopGroupKey primary = tr_ctx.primary; + NextHopGroupKey secondary = tr_ctx.secondary; + + auto *vrf_obj = vnet_orch_->getTypePtr(tr_ctx.vnet); + + auto& nhg_info = syncd_nexthop_groups_[tr_ctx.vnet][nhg]; + + if (--nhg_info.ref_count == 0) + { + if (nhg.getSize() > 1) { - syncd_tunnel_routes_.erase(vnet); + removeNextHopGroup(tr_ctx.vnet, nhg, vrf_obj); } - - vrf_obj->removeRoute(ipPrefix); - vrf_obj->removeProfile(ipPrefix); - - removeRouteState(vnet, ipPrefix); - if (prefix_to_adv_prefix_.find(ipPrefix) != prefix_to_adv_prefix_.end()) + else { - auto adv_pfx = prefix_to_adv_prefix_[ipPrefix]; - prefix_to_adv_prefix_.erase(ipPrefix); - - if (last_nhg_size > 0) + syncd_nexthop_groups_[tr_ctx.vnet].erase(nhg); + // We need to check specifically if there is only one next hop active. + // In case of Priority routes we can end up in a situation where the active NHG has 0 nexthops. + if (nhg.getSize() == 1) { - adv_prefix_refcount_[adv_pfx] -= 1; - if (adv_prefix_refcount_[adv_pfx] == 0) + NextHopKey nexthop = *nhg.getNextHops().begin(); + if (!isLocalEndpoint(tr_ctx.vnet, nexthop.ip_address)) { - adv_prefix_refcount_.erase(adv_pfx); + vrf_obj->removeTunnelNextHop(nexthop); } } } + if (monitor_info_[tr_ctx.vnet].find(ip_prefix) == monitor_info_[tr_ctx.vnet].end()) + { + delEndpointMonitor(tr_ctx.vnet, nhg, ip_prefix); + } + } + else + { + nhg_info.tunnel_routes.erase(ip_prefix); + } + if (monitor_info_[tr_ctx.vnet].find(ip_prefix) != monitor_info_[tr_ctx.vnet].end()) + { + delEndpointMonitor(tr_ctx.vnet, primary, ip_prefix); + delEndpointMonitor(tr_ctx.vnet, secondary, ip_prefix); + monitor_info_[tr_ctx.vnet].erase(ip_prefix); + } + + syncd_tunnel_routes_[tr_ctx.vnet].erase(ip_prefix); + if (syncd_tunnel_routes_[tr_ctx.vnet].empty()) + { + syncd_tunnel_routes_.erase(tr_ctx.vnet); + } + + vrf_obj->removeRoute(ip_prefix); + vrf_obj->removeProfile(ip_prefix); + + removeRouteState(tr_ctx.vnet, ip_prefix); + if (prefix_to_adv_prefix_.find(ip_prefix) != prefix_to_adv_prefix_.end()) + { + auto adv_pfx = prefix_to_adv_prefix_[ip_prefix]; + prefix_to_adv_prefix_.erase(ip_prefix); + + if (nhg.getSize() > 0) + { + adv_prefix_refcount_[adv_pfx] -= 1; + if (adv_prefix_refcount_[adv_pfx] == 0) + { + adv_prefix_refcount_.erase(adv_pfx); + } + } } return true; } @@ -1546,8 +1655,6 @@ inline void VNetRouteOrch::removeSubnetDecapTerm(const IpPrefix &ipPrefix) bool VNetRouteOrch::setAndDeleteRoutesWithRouteOrch(const sai_object_id_t vr_id, const IpPrefix& ipPrefix, const NextHopGroupKey& nhg, const string& op) { - auto& bulkNhgReducedRefCnt = gRouteOrch->getBulkNhgReducedRefCnt(); - // Get vnet name from vrf id std::string vnet_name; if (!vnet_orch_->getVnetNameByVrfId(vr_id, vnet_name)) @@ -1565,59 +1672,32 @@ bool VNetRouteOrch::setAndDeleteRoutesWithRouteOrch(const sai_object_id_t vr_id, if (op == SET_COMMAND) { - // Add route via route orch + // Queue non-subnet route if (gRouteOrch->addRoute(ctx, nhg)) { return true; } - // Flush the route bulker, so routes will be written to syncd and ASIC - gRouteOrch->flushRouteBulker(); - bulkNhgReducedRefCnt.clear(); - - // Post add route via route orch - if (gRouteOrch->addRoutePost(ctx, nhg)) + if (ctx.object_statuses.empty()) { - SWSS_LOG_NOTICE("Route %s added via routeorch for vnet %s", ipPrefix.to_string().c_str(), vnet_name.c_str()); - } - else - { - SWSS_LOG_ERROR("Route %s add failed in routeorch for vnet %s", ipPrefix.to_string().c_str(), vnet_name.c_str()); - return false; + SWSS_LOG_ERROR("Route %s not queued in bulker (missing dependencies), will retry", ipPrefix.to_string().c_str()); + return false; } + routeorch_contexts_.emplace_back(key, true, nhg); + routeorch_contexts_.back().ctx = std::move(ctx); } else if (op == DEL_COMMAND) { - // Remove route via route orch - if (gRouteOrch->removeRoute(ctx)) + // Queue non-subnet route + gRouteOrch->removeRoute(ctx); + + if (ctx.object_statuses.empty()) { return true; } - - // Flush the route bulker, so routes will be written to syncd and ASIC - gRouteOrch->flushRouteBulker(); - bulkNhgReducedRefCnt.clear(); - - // Post remove route via route orch - if (gRouteOrch->removeRoutePost(ctx)) - { - SWSS_LOG_NOTICE("Route %s removed via routeorch for vnet %s", ipPrefix.to_string().c_str(), vnet_name.c_str()); - } - else - { - SWSS_LOG_ERROR("Route %s remove failed in routeorch for vnet %s", ipPrefix.to_string().c_str(), vnet_name.c_str()); - return false; - } - } - - // Remove next hop groups with 0 ref count - for (auto& it : bulkNhgReducedRefCnt) - { - if (gRouteOrch->getNextHopGroupRefCount(it.first) == 0) - { - gRouteOrch->removeNextHopGroup(it.first); - SWSS_LOG_INFO("Next hop group %s has 0 references, removed via routeorch", it.first.to_string().c_str()); - } + + routeorch_contexts_.emplace_back(key, false, nhg); + routeorch_contexts_.back().ctx = std::move(ctx); } return true; @@ -3324,6 +3404,240 @@ bool VNetRouteOrch::isPartiallyLocal(const std::vector& ip_list return !(all_true || all_false); } +void VNetRouteOrch::doTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + toBulk_.clear(); + auto it = consumer.m_toSync.begin(); + while (it != consumer.m_toSync.end()) + { + KeyOpFieldsValuesTuple t = it->second; + string key = kfvKey(t); + string op = kfvOp(t); + auto& bulk_ctx = toBulk_[make_pair(key, op)]; + bulk_ctx.key = key; + bulk_ctx.op = op; + + request_.parse(t); + auto table_name = consumer.getTableName(); + request_.setTableName(table_name); + + bool can_process = true; + if (op == SET_COMMAND) + { + can_process = addOperation(request_); + } + else if (op == DEL_COMMAND) + { + can_process = delOperation(request_); + } + else + { + SWSS_LOG_ERROR("Wrong operation. Check RequestParser: %s", op.c_str()); + it = consumer.m_toSync.erase(it); + toBulk_.erase(make_pair(key, op)); + request_.clear(); + continue; + } + + request_.clear(); + + if (can_process) + { + bulk_ctx.non_subnet_contexts = std::move(routeorch_contexts_); + bulk_ctx.tunnel_contexts = std::move(tunnel_route_contexts_); + routeorch_contexts_.clear(); + tunnel_route_contexts_.clear(); + it++; + } + else + { + it++; + toBulk_.erase(make_pair(key, op)); + } + } + + if (toBulk_.empty()) + { + return; + } + + gRouteOrch->gRouteBulker.flush(); + tunnel_route_bulker_.flush(); + + size_t total_non_subnet_routes = 0; + size_t total_tunnel_routes = 0; + for (const auto& entry : toBulk_) + { + total_non_subnet_routes += entry.second.non_subnet_contexts.size(); + total_tunnel_routes += entry.second.tunnel_contexts.size(); + } + SWSS_LOG_NOTICE("Bulk flush: %zu tunnel routes + %zu non-subnet routes", + total_tunnel_routes, total_non_subnet_routes); + + auto& bulkNhgReducedRefCnt = gRouteOrch->getBulkNhgReducedRefCnt(); + auto it_prev = consumer.m_toSync.begin(); + + while (it_prev != consumer.m_toSync.end()) + { + KeyOpFieldsValuesTuple t = it_prev->second; + string key = kfvKey(t); + string op = kfvOp(t); + + auto found = toBulk_.find(make_pair(key, op)); + if (found == toBulk_.end()) + { + it_prev++; + continue; + } + + auto& bulk_ctx = found->second; + bool all_success = true; + + for (auto& ro_ctx : bulk_ctx.non_subnet_contexts) + { + sai_status_t status = SAI_STATUS_FAILURE; + if (!ro_ctx.ctx.object_statuses.empty()) + { + status = ro_ctx.ctx.object_statuses[0]; + } + + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Non-subnet route %s failed (status %d), will retry", + ro_ctx.ctx.ip_prefix.to_string().c_str(), status); + all_success = false; + continue; + } + + bool post_success = false; + if (ro_ctx.is_add) + { + post_success = gRouteOrch->addRoutePost(ro_ctx.ctx, ro_ctx.nhg); + } + else + { + post_success = gRouteOrch->removeRoutePost(ro_ctx.ctx); + } + + if (!post_success) + { + SWSS_LOG_ERROR("Non-subnet route %s post-processing failed", + ro_ctx.ctx.ip_prefix.to_string().c_str()); + all_success = false; + } + } + + std::set> processed_routes; + + for (auto& tr_ctx : bulk_ctx.tunnel_contexts) + { + if (tr_ctx.status_index >= object_statuses_.size()) + { + SWSS_LOG_ERROR("Tunnel route %s has invalid status index %zu", + tr_ctx.ip_prefix.to_string().c_str(), tr_ctx.status_index); + all_success = false; + continue; + } + + sai_status_t status = object_statuses_[tr_ctx.status_index]; + + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Tunnel route %s failed (status %d), will retry", + tr_ctx.ip_prefix.to_string().c_str(), status); + /* Clean up the newly created next hop group entry */ + if (tr_ctx.is_add || tr_ctx.is_update) + { + auto *vrf_obj = vnet_orch_->getTypePtr(tr_ctx.vnet); + if (tr_ctx.nhg.getSize() > 1) + { + removeNextHopGroup(tr_ctx.vnet, tr_ctx.nhg, vrf_obj); + } + } + all_success = false; + continue; + } + + if (tr_ctx.is_add && !tr_ctx.is_update) + { + sai_ip_prefix_t sai_pfx; + copy(sai_pfx, tr_ctx.ip_prefix); + if (sai_pfx.addr_family == SAI_IP_ADDR_FAMILY_IPV4) + { + gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_IPV4_ROUTE); + } + else + { + gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_IPV6_ROUTE); + } + + auto *vrf_obj = vnet_orch_->getTypePtr(tr_ctx.vnet); + gFlowCounterRouteOrch->onAddMiscRouteEntry(vrf_obj->getVRidIngress(), sai_pfx, false); + } + else if (!tr_ctx.is_add && !tr_ctx.is_update) + { + sai_ip_prefix_t sai_pfx; + copy(sai_pfx, tr_ctx.ip_prefix); + if (sai_pfx.addr_family == SAI_IP_ADDR_FAMILY_IPV4) + { + gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_IPV4_ROUTE); + } + else + { + gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_IPV6_ROUTE); + } + + auto *vrf_obj = vnet_orch_->getTypePtr(tr_ctx.vnet); + gFlowCounterRouteOrch->onRemoveMiscRouteEntry(vrf_obj->getVRidIngress(), sai_pfx, false); + } + + auto route_key = std::make_pair(tr_ctx.vnet, tr_ctx.ip_prefix); + if (processed_routes.find(route_key) != processed_routes.end()) + { + continue; + } + processed_routes.insert(route_key); + + bool post_success = false; + if (tr_ctx.is_add || tr_ctx.is_update) + { + post_success = addTunnelRoutePost(tr_ctx); + } + else + { + post_success = delTunnelRoutePost(tr_ctx); + } + + if (!post_success) + { + SWSS_LOG_ERROR("Tunnel route %s post-processing failed", + tr_ctx.ip_prefix.to_string().c_str()); + all_success = false; + } + } + + if (all_success) + { + it_prev = consumer.m_toSync.erase(it_prev); + } + else + { + it_prev++; + } + } + + for (auto& it : bulkNhgReducedRefCnt) + { + if (gRouteOrch->getNextHopGroupRefCount(it.first) == 0) + { + gRouteOrch->removeNextHopGroup(it.first); + } + } + bulkNhgReducedRefCnt.clear(); + object_statuses_.clear(); +} VNetCfgRouteOrch::VNetCfgRouteOrch(DBConnector *db, DBConnector *appDb, vector &tableNames) : Orch(db, tableNames), diff --git a/orchagent/vnetorch.h b/orchagent/vnetorch.h index f51ad970..a5bf9d99 100644 --- a/orchagent/vnetorch.h +++ b/orchagent/vnetorch.h @@ -443,6 +443,40 @@ struct MonitorUpdate std::string vnet; }; +struct RouteOrchContext +{ + RouteBulkContext ctx; + NextHopGroupKey nhg; + bool is_add; + RouteOrchContext(const std::string& key, bool is_set, const NextHopGroupKey& nexthops) + : ctx(key, is_set), nhg(nexthops), is_add(is_set) {} +}; + +struct TunnelRouteContext +{ + IpPrefix ip_prefix; + string vnet; + NextHopGroupKey nhg; + NextHopGroupKey primary; + NextHopGroupKey secondary; + string profile; + IpPrefix adv_prefix; + string monitoring; + bool is_add; + bool is_update; + size_t status_index; + TunnelRouteContext(const string& vnet_name, const IpPrefix& pfx, bool add_op, size_t idx, bool update_op = false) + : ip_prefix(pfx), vnet(vnet_name), nhg("", true), primary("", true), secondary("", true), + is_add(add_op), is_update(update_op), status_index(idx) {} +}; + +struct VNetRouteBulkContext { + std::string key; + std::string op; + std::vector non_subnet_contexts; + std::vector tunnel_contexts; +}; + struct VNetTunnelRouteEntry { // The nhg_key is the key for the next hop group which is currently active in hardware. @@ -506,6 +540,7 @@ class VNetRouteOrch : public Orch2, public Subject, public Observer void updateMonitorState(string& op, const IpPrefix& prefix , const IpAddress& endpoint, string state); void updateCustomBfdState(const IpAddress& monitoring_ip, const string& state); void updateAllMonitoringSession(const string& vnet); + virtual void doTask(Consumer &consumer) override; private: virtual bool addOperation(const Request& request); @@ -552,6 +587,11 @@ class VNetRouteOrch : public Orch2, public Subject, public Observer bool setAndDeleteRoutesWithRouteOrch(const sai_object_id_t vr_id, const IpPrefix& ipPrefix, const NextHopGroupKey& nhg, const string& op); + bool addTunnelRouteBulk(const string& vnet, sai_object_id_t vr_id, const IpPrefix& ipPrefix, sai_object_id_t nh_id); + bool delTunnelRouteBulk(const string& vnet, sai_object_id_t vr_id, const IpPrefix& ipPrefix); + bool addTunnelRoutePost(const TunnelRouteContext& tr_ctx); + bool delTunnelRoutePost(const TunnelRouteContext& tr_ctx); + bool updateTunnelRouteBulk(const std::string& vnet, sai_object_id_t vr_id, const IpPrefix& ipPrefix, sai_object_id_t nh_id); template bool doRouteTask(const string& vnet, IpPrefix& ipPrefix, NextHopGroupKey& nexthops, string& op, string& profile, @@ -582,6 +622,12 @@ class VNetRouteOrch : public Orch2, public Subject, public Observer std::set subnet_decap_terms_created_; ProducerStateTable bfd_session_producer_; ProducerStateTable app_tunnel_decap_term_producer_; + std::deque object_statuses_; + std::vector routeorch_contexts_; + std::vector tunnel_route_contexts_; + std::map, VNetRouteBulkContext> toBulk_; + EntityBulker tunnel_route_bulker_; + unique_ptr monitor_session_producer_; shared_ptr config_db_; shared_ptr state_db_; diff --git a/tests/test_vnet.py b/tests/test_vnet.py index 3a7db86b..2fde4990 100644 --- a/tests/test_vnet.py +++ b/tests/test_vnet.py @@ -3267,6 +3267,375 @@ def test_vnet_orch_32(self, dvs, testlog): self.remove_ip_address("Ethernet12", "9.1.0.4/32") self.set_admin_status("Ethernet12", "down") + ''' + Test 33 - Bulk route scale validation + ''' + def test_vnet_orch_33_bulk_route_scale(self, dvs, testlog): + self.setup_db(dvs) + + vnet_obj = self.get_vnet_obj() + + tunnel_name = 'tunnel_bulk_scale' + vnet_name = 'Vnet_bulk_scale' + num_routes = 50 + + # Shared endpoints for all routes (should create only 1 NHG) + endpoints = ['10.1.0.1', '10.1.0.2', '10.1.0.3'] + + vnet_obj.fetch_exist_entries(dvs) + + # Setup infrastructure + create_vxlan_tunnel(dvs, tunnel_name, '10.10.10.10') + create_vnet_entry(dvs, vnet_name, tunnel_name, '7000', "") + + vnet_obj.check_vnet_entry(dvs, vnet_name) + vnet_obj.check_vxlan_tunnel_entry(dvs, tunnel_name, vnet_name, '7000') + + # Get baseline counts + vnet_obj.fetch_exist_entries(dvs) + routes_baseline = len(vnet_obj.routes) + nhgs_baseline = get_exist_entries(dvs, vnet_obj.ASIC_NEXT_HOP_GROUP) + + # Create 50 routes to shared endpoints + route_prefixes = [f'192.168.{i}.0/24' for i in range(num_routes)] + + for prefix in route_prefixes: + create_vnet_routes(dvs, prefix, vnet_name, ','.join(endpoints)) + + time.sleep(2) + + # Verify all routes programmed in ASIC_DB + vnet_obj.fetch_exist_entries(dvs) + routes_after_add = len(vnet_obj.routes) + routes_added = routes_after_add - routes_baseline + + assert routes_added == num_routes, \ + f"Expected {num_routes} routes in ASIC_DB, got {routes_added}" + + # Verify NHG sharing (should be only 1 NHG for all routes) + nhgs_after_add = get_exist_entries(dvs, vnet_obj.ASIC_NEXT_HOP_GROUP) + nhgs_new = [nhg for nhg in nhgs_after_add if nhg not in nhgs_baseline] + assert len(nhgs_new) == 1, \ + f"All routes should share one NHG, got {len(nhgs_new)} NHGs" + + # Verify all routes in STATE_DB + for prefix in route_prefixes: + check_state_db_routes(dvs, vnet_name, prefix, endpoints) + + # Cleanup - bulk delete + for prefix in route_prefixes: + delete_vnet_routes(dvs, prefix, vnet_name) + + time.sleep(2) + + # Verify cleanup + vnet_obj.fetch_exist_entries(dvs) + routes_final = len(vnet_obj.routes) + nhgs_final = get_exist_entries(dvs, vnet_obj.ASIC_NEXT_HOP_GROUP) + nhgs_final_filtered = [nhg for nhg in nhgs_final if nhg not in nhgs_baseline] + + assert routes_final == routes_baseline, \ + f"All routes should be deleted, expected {routes_baseline}, got {routes_final}" + assert len(nhgs_final_filtered) == 0, \ + f"NHG should be deleted when all routes removed, got {len(nhgs_final_filtered)}" + + # Cleanup infrastructure + delete_vnet_entry(dvs, vnet_name) + delete_vxlan_tunnel(dvs, tunnel_name) + + ''' + Test 34 - Bulk mixed operations + ''' + def test_vnet_orch_34_bulk_mixed_operations(self, dvs, testlog): + self.setup_db(dvs) + + vnet_obj = self.get_vnet_obj() + + tunnel_name = 'tunnel_mixed' + vnet_name = 'Vnet_mixed' + + vnet_obj.fetch_exist_entries(dvs) + + # Setup infrastructure + create_vxlan_tunnel(dvs, tunnel_name, '10.10.10.10') + create_vnet_entry(dvs, vnet_name, tunnel_name, '8000', "") + + vnet_obj.check_vnet_entry(dvs, vnet_name) + vnet_obj.check_vxlan_tunnel_entry(dvs, tunnel_name, vnet_name, '8000') + + # Get baseline route count + vnet_obj.fetch_exist_entries(dvs) + routes_baseline = len(vnet_obj.routes) + + # Setup: create initial 10 routes + initial_routes = [f'10.0.{i}.0/24' for i in range(10)] + for prefix in initial_routes: + create_vnet_routes(dvs, prefix, vnet_name, '10.1.0.1') + + time.sleep(2) + + # Verify initial routes created + for prefix in initial_routes: + check_state_db_routes(dvs, vnet_name, prefix, ['10.1.0.1']) + + # Batch operation: add + update + delete + new_routes = [f'20.0.{i}.0/24' for i in range(10)] + update_routes = initial_routes[:5] # Change endpoints + delete_routes = initial_routes[5:] # Delete these + + # Execute mixed operations + for prefix in new_routes: + create_vnet_routes(dvs, prefix, vnet_name, '10.1.0.2') + + for prefix in update_routes: + set_vnet_routes(dvs, prefix, vnet_name, '10.1.0.3') # Change endpoint + + for prefix in delete_routes: + delete_vnet_routes(dvs, prefix, vnet_name) + + time.sleep(2) + + # Verify: new routes added + for prefix in new_routes: + check_state_db_routes(dvs, vnet_name, prefix, ['10.1.0.2']) + + # Verify: updated routes have new endpoint + for prefix in update_routes: + check_state_db_routes(dvs, vnet_name, prefix, ['10.1.0.3']) + + # Verify: deleted routes gone + for prefix in delete_routes: + check_remove_state_db_routes(dvs, vnet_name, prefix) + + # Verify route count in ASIC_DB + # Started with 10, added 10, deleted 5 = 15 routes (relative to baseline) + vnet_obj.fetch_exist_entries(dvs) + routes_current = len(vnet_obj.routes) + routes_delta = routes_current - routes_baseline + assert routes_delta == 15, \ + f"Expected 15 routes relative to baseline (10 initial + 10 new - 5 deleted), got {routes_delta}" + + # Cleanup: delete all remaining routes + for prefix in new_routes + update_routes: + delete_vnet_routes(dvs, prefix, vnet_name) + + time.sleep(2) + + # Cleanup infrastructure + delete_vnet_entry(dvs, vnet_name) + delete_vxlan_tunnel(dvs, tunnel_name) + + ''' + Test 35 - Route programming deferred until dependency is resolved + ''' + def test_vnet_orch_35_route_dependency_retry(self, dvs, testlog): + self.setup_db(dvs) + + vnet_obj = self.get_vnet_obj() + + tunnel_name = 'tunnel_dep' + vnet_name = 'Vnet_dep' + route_prefixes = [f'192.168.100.{i}/32' for i in range(1, 21)] + endpoint = '10.100.100.1' + + # Ensure dependencies do NOT exist + vnet_obj.fetch_exist_entries(dvs) + + for route_prefix in route_prefixes: + create_vnet_routes(dvs, route_prefix, vnet_name, endpoint) + + time.sleep(2) + + # Routes should NOT be programmed yet (ASIC DB should not have them) + asic_db = swsscommon.DBConnector(swsscommon.ASIC_DB, dvs.redis_sock, 0) + route_tbl = swsscommon.Table(asic_db, vnet_obj.ASIC_ROUTE_ENTRY) + route_keys = set(route_tbl.getKeys()) + for route_prefix in route_prefixes: + found = False + for key in route_keys: + if route_prefix in key: + found = True + assert not found, f"Route {route_prefix} should not be programmed before dependencies are resolved" + + # Now create dependencies using vnet_lib API + create_vxlan_tunnel(dvs, tunnel_name, '10.100.100.100') + create_vnet_entry(dvs, vnet_name, tunnel_name, '10001', "") + + time.sleep(2) + + # Now all routes should be programmed + route_tbl = swsscommon.Table(asic_db, vnet_obj.ASIC_ROUTE_ENTRY) + route_keys = set(route_tbl.getKeys()) + for route_prefix in route_prefixes: + found = False + for key in route_keys: + if route_prefix in key: + found = True + assert found, f"Route {route_prefix} should be programmed after dependencies are resolved" + + # Clean up using vnet_lib API + for route_prefix in route_prefixes: + delete_vnet_routes(dvs, route_prefix, vnet_name) + delete_vnet_entry(dvs, vnet_name) + delete_vxlan_tunnel(dvs, tunnel_name) + + ''' + Test 36 - Bulk route delete with custom monitoring cleanup + ''' + def test_vnet_orch_36_bulk_custom_monitor_cleanup(self, dvs, testlog): + self.setup_db(dvs) + + vnet_obj = self.get_vnet_obj() + + tunnel_name = 'tunnel_mbulk' + vnet_name = 'Vnet_mbulk' + num_routes = 20 + + vnet_obj.fetch_exist_entries(dvs) + + # Setup infrastructure + create_vxlan_tunnel(dvs, tunnel_name, '10.10.10.10') + create_vnet_entry(dvs, vnet_name, tunnel_name, '5000', "") + + vnet_obj.check_vnet_entry(dvs, vnet_name) + vnet_obj.check_vxlan_tunnel_entry(dvs, tunnel_name, vnet_name, '5000') + + # Create routes with custom monitoring + routes = [f'100.{i}.1.1/32' for i in range(num_routes)] + for i, prefix in enumerate(routes): + primary = f'9.{i}.0.1,9.{i}.0.2' + secondary = f'9.{i}.0.3,9.{i}.0.4' + monitors = f'9.{i}.1.1,9.{i}.1.2,9.{i}.1.3,9.{i}.1.4' + create_vnet_routes(dvs, prefix, vnet_name, + primary + ',' + secondary, + ep_monitor=monitors, + primary=primary, + monitoring='custom') + + time.sleep(2) + + # Bring monitors up + for i, prefix in enumerate(routes): + for j in range(1, 5): + update_monitor_session_state(dvs, f'9.{i}.1.{j}', prefix, 'up') + + time.sleep(2) + + # Verify routes programmed in STATE_DB + for prefix in routes: + check_vnet_route_exists(dvs, vnet_name, prefix) + + # Bulk delete all routes + for prefix in routes: + delete_vnet_routes(dvs, prefix, vnet_name) + + time.sleep(2) + + # CRITICAL: Verify ALL monitor sessions deleted + app_db = dvs.get_app_db() + monitor_keys = app_db.get_keys("VNET_MONITOR_TABLE") + + for i, prefix in enumerate(routes): + for j in range(1, 5): + monitor_key = f'9.{i}.1.{j}:{prefix}' + assert monitor_key not in monitor_keys, \ + f"Monitor {monitor_key} should be deleted after bulk route deletion" + + # Verify routes deleted from STATE_DB + for prefix in routes: + check_remove_state_db_routes(dvs, vnet_name, prefix) + + # Cleanup + delete_vnet_entry(dvs, vnet_name) + delete_vxlan_tunnel(dvs, tunnel_name) + + ''' + Test 37 - Bulk NHG reference counting + ''' + def test_vnet_orch_37_bulk_nhg_reference_counting(self, dvs, testlog): + self.setup_db(dvs) + + vnet_obj = self.get_vnet_obj() + + tunnel_name = 'tunnel_nhg_bulk' + vnet_name = 'Vnet_nhg_bulk' + + endpoints_A = ['10.1.0.1', '10.1.0.2'] + endpoints_B = ['10.2.0.1', '10.2.0.2'] + + routes_A = [f'100.0.{i}.0/24' for i in range(50)] + routes_B = [f'200.0.{i}.0/24' for i in range(50)] + + vnet_obj.fetch_exist_entries(dvs) + + # Setup infrastructure + create_vxlan_tunnel(dvs, tunnel_name, '10.10.10.10') + create_vnet_entry(dvs, vnet_name, tunnel_name, '6000', "") + + vnet_obj.check_vnet_entry(dvs, vnet_name) + vnet_obj.check_vxlan_tunnel_entry(dvs, tunnel_name, vnet_name, '6000') + + # Get baseline NHG count + vnet_obj.fetch_exist_entries(dvs) + nhgs_baseline = get_exist_entries(dvs, vnet_obj.ASIC_NEXT_HOP_GROUP) + + # Create routes to A + for prefix in routes_A: + create_vnet_routes(dvs, prefix, vnet_name, ','.join(endpoints_A)) + + time.sleep(2) + + # Create routes to B + for prefix in routes_B: + create_vnet_routes(dvs, prefix, vnet_name, ','.join(endpoints_B)) + + time.sleep(2) + + # Should have 2 NHGs (one for A, one for B) + nhgs_initial = get_exist_entries(dvs, vnet_obj.ASIC_NEXT_HOP_GROUP) + nhgs_initial_filtered = [nhg for nhg in nhgs_initial if nhg not in nhgs_baseline] + assert len(nhgs_initial_filtered) == 2, f"Expected 2 NHGs, got {len(nhgs_initial_filtered)}" + + # Update 25 routes from A to B + for prefix in routes_A[:25]: + set_vnet_routes(dvs, prefix, vnet_name, ','.join(endpoints_B)) + + time.sleep(2) + + # Still 2 NHGs, but refcounts changed + # NHG_A: 25 routes, NHG_B: 75 routes + nhgs_after_update = get_exist_entries(dvs, vnet_obj.ASIC_NEXT_HOP_GROUP) + nhgs_after_update_filtered = [nhg for nhg in nhgs_after_update if nhg not in nhgs_baseline] + assert len(nhgs_after_update_filtered) == 2, f"Expected 2 NHGs after update, got {len(nhgs_after_update_filtered)}" + + # Delete remaining 25 routes to A (routes_A[25:]) + for prefix in routes_A[25:]: + delete_vnet_routes(dvs, prefix, vnet_name) + + time.sleep(2) + + # NHG_A should be deleted (refcount=0) + nhgs_after_delete = get_exist_entries(dvs, vnet_obj.ASIC_NEXT_HOP_GROUP) + nhgs_after_delete_filtered = [nhg for nhg in nhgs_after_delete if nhg not in nhgs_baseline] + assert len(nhgs_after_delete_filtered) == 1, \ + f"Expected 1 NHG after deleting all routes to A (NHG A should be removed), got {len(nhgs_after_delete_filtered)}" + + # Cleanup: delete remaining routes + for prefix in routes_A[:25] + routes_B: + delete_vnet_routes(dvs, prefix, vnet_name) + + time.sleep(2) + + # All NHGs should be gone + nhgs_cleanup = get_exist_entries(dvs, vnet_obj.ASIC_NEXT_HOP_GROUP) + nhgs_cleanup_filtered = [nhg for nhg in nhgs_cleanup if nhg not in nhgs_baseline] + assert len(nhgs_cleanup_filtered) == 0, \ + f"Expected 0 NHGs after cleanup, got {len(nhgs_cleanup_filtered)}" + + # Cleanup infrastructure + delete_vnet_entry(dvs, vnet_name) + delete_vxlan_tunnel(dvs, tunnel_name) + # Add Dummy always-pass test at end as workaroud # for issue when Flaky fail on final test it invokes module tear-down before retrying diff --git a/tests/vnet_lib.py b/tests/vnet_lib.py index 2eb01621..a4f0096c 100644 --- a/tests/vnet_lib.py +++ b/tests/vnet_lib.py @@ -515,6 +515,14 @@ def check_state_db_routes(dvs, vnet, prefix, endpoints): assert fvs['state'] == 'inactive' +def check_vnet_route_exists(dvs, vnet, prefix): + state_db = swsscommon.DBConnector(swsscommon.STATE_DB, dvs.redis_sock, 0) + tbl = swsscommon.Table(state_db, "VNET_ROUTE_TUNNEL_TABLE") + + status, fvs = tbl.get(vnet + '|' + prefix) + assert status, f"Route {vnet}|{prefix} not found in STATE_DB" + + def check_remove_state_db_routes(dvs, vnet, prefix): state_db = swsscommon.DBConnector(swsscommon.STATE_DB, dvs.redis_sock, 0) tbl = swsscommon.Table(state_db, "VNET_ROUTE_TUNNEL_TABLE")