From 72b58251c96386cbbef6a3e2e997048f713177b4 Mon Sep 17 00:00:00 2001 From: Abhishek Dosi Date: Sun, 24 Nov 2024 06:05:24 +0000 Subject: [PATCH 01/16] Changes Signed-off-by: Abhishek Dosi --- orchagent/routeorch.cpp | 218 +++++++++++++++++++++++++++++++++++++++- orchagent/routeorch.h | 21 +++- 2 files changed, 232 insertions(+), 7 deletions(-) diff --git a/orchagent/routeorch.cpp b/orchagent/routeorch.cpp index 803f71bb1e0..d28a2f1e48c 100644 --- a/orchagent/routeorch.cpp +++ b/orchagent/routeorch.cpp @@ -381,6 +381,133 @@ void RouteOrch::detach(Observer *observer, const IpAddress& dstAddr, sai_object_ } } +void RouteOrch::updateDefaultRouteSwapSet(const NextHopGroupKey default_nhg_key, std::set& active_default_route_nhops) +{ + std::set current_default_route_nhops; + current_default_route_nhops.clear(); + + if (default_nhg_key.getSize() == 1) + { + current_default_route_nhops.insert(*default_nhg_key.getNextHops().begin()); + } + else + { + auto nhgm = m_syncdNextHopGroups[default_nhg_key].nhopgroup_members; + for (auto nhop = nhgm.begin(); nhop != nhgm.end(); ++nhop) + { + current_default_route_nhops.insert(nhop->first); + } + } + + if (!active_default_route_nhops.empty()) + { + // Not empty . For all the NexhopGroup that are still present with swap to default property + // we need to remove the nexthops that are no more present and add the one that are new. + compareAndResolveDefaultRouteNhSwapNextHopGroup(active_default_route_nhops, current_default_route_nhops); + } + active_default_route_nhops.clear(); + std::copy(current_default_route_nhops.begin(), current_default_route_nhops.end(), std::inserter(active_default_route_nhops, active_default_route_nhops.begin())); +} + +bool RouteOrch::compareAndResolveDefaultRouteNhSwapNextHopGroup(const std::set& prev_default_route_next_hop_set, + const std::set& curr_default_route_next_hop_set) +{ + std::set default_route_nh_delete_set; + std::set default_route_nh_create_set; + + std::set_difference(prev_default_route_next_hop_set.begin(), prev_default_route_next_hop_set.end(), + curr_default_route_next_hop_set.begin(), curr_default_route_next_hop_set.end(), + std::inserter(default_route_nh_delete_set, default_route_nh_delete_set.begin())); + + std::set_difference(curr_default_route_next_hop_set.begin(), curr_default_route_next_hop_set.end(), + prev_default_route_next_hop_set.begin(), prev_default_route_next_hop_set.end(), + std::inserter(default_route_nh_create_set, default_route_nh_create_set.begin())); + + + for (auto nhopgroup = m_syncdNextHopGroups.begin(); nhopgroup != m_syncdNextHopGroups.end(); ++nhopgroup) + { + if (!nhopgroup->second.is_default_route_nh_swap) + { + continue; + } + + addDefaultRouteNexthopsInNextHopGroup(nhopgroup->second, default_route_nh_create_set); + removeDefaultRouteNexthopsInNextHopGroup(nhopgroup->second, default_route_nh_delete_set); + } + return true; +} + +bool RouteOrch::addDefaultRouteNexthopsInNextHopGroup(NextHopGroupEntry& original_next_hop_group, std::set& default_route_next_hop_set) +{ + SWSS_LOG_ENTER(); + sai_object_id_t nexthop_group_member_id; + sai_status_t status; + + for (auto it : default_route_next_hop_set) + { + vector nhgm_attrs; + sai_attribute_t nhgm_attr; + nhgm_attr.id = SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID; + nhgm_attr.value.oid = original_next_hop_group.next_hop_group_id; + nhgm_attrs.push_back(nhgm_attr); + + nhgm_attr.id = SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID; + nhgm_attr.value.oid = m_neighOrch->getNextHopId(it); + nhgm_attrs.push_back(nhgm_attr); + + status = sai_next_hop_group_api->create_next_hop_group_member(&nexthop_group_member_id, gSwitchId, + (uint32_t)nhgm_attrs.size(), + nhgm_attrs.data()); + + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Default Route Swap Failed to add next hop member to group %" PRIx64 ": %d\n", + original_next_hop_group.next_hop_group_id, status); + task_process_status handle_status = handleSaiCreateStatus(SAI_API_NEXT_HOP_GROUP, status); + if (handle_status != task_success) + { + return parseHandleSaiStatusFailure(handle_status); + } + } + // Increment the Default Route Active NH Reference Count + m_neighOrch->increaseNextHopRefCount(it); + gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_NEXTHOP_GROUP_MEMBER); + original_next_hop_group.default_route_nhopgroup_members[it].next_hop_id = nexthop_group_member_id; + original_next_hop_group.default_route_nhopgroup_members[it].seq_id = 0; + } + return true; +} + +bool RouteOrch::removeDefaultRouteNexthopsInNextHopGroup(NextHopGroupEntry& original_next_hop_group, std::set& default_route_next_hop_set) +{ + SWSS_LOG_ENTER(); + sai_status_t status; + + for (auto it : default_route_next_hop_set) + { + if (original_next_hop_group.default_route_nhopgroup_members.find(it) == original_next_hop_group.default_route_nhopgroup_members.end()) + { + continue; + } + status = sai_next_hop_group_api->remove_next_hop_group_member(original_next_hop_group.default_route_nhopgroup_members[it].next_hop_id); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to remove next hop member %" PRIx64 " from group %" PRIx64 ": %d\n", + original_next_hop_group.default_route_nhopgroup_members[it].next_hop_id, original_next_hop_group.next_hop_group_id, status); + task_process_status handle_status = handleSaiRemoveStatus(SAI_API_NEXT_HOP_GROUP, status); + if (handle_status != task_success) + { + return parseHandleSaiStatusFailure(handle_status); + } + } + + gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_NEXTHOP_GROUP_MEMBER); + m_neighOrch->decreaseNextHopRefCount(it); + original_next_hop_group.default_route_nhopgroup_members.erase(it); + } + return true; +} + bool RouteOrch::validnexthopinNextHopGroup(const NextHopKey &nexthop, uint32_t& count) { SWSS_LOG_ENTER(); @@ -398,6 +525,13 @@ bool RouteOrch::validnexthopinNextHopGroup(const NextHopKey &nexthop, uint32_t& continue; } + // AZNG Route NHOP Group is swapped by default route nh memeber . do not add Nexthop again. + // Wait for Nexthop Group Cleanup + if (nhopgroup->second.is_default_route_nh_swap) + { + continue; + } + vector nhgm_attrs; sai_attribute_t nhgm_attr; @@ -444,6 +578,7 @@ bool RouteOrch::validnexthopinNextHopGroup(const NextHopKey &nexthop, uint32_t& ++count; gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_NEXTHOP_GROUP_MEMBER); nhopgroup->second.nhopgroup_members[nexthop].next_hop_id = nexthop_id; + nhopgroup->second.nh_member_install_count++; } if (!m_fgNhgOrch->validNextHopInNextHopGroup(nexthop)) @@ -484,7 +619,23 @@ bool RouteOrch::invalidnexthopinNextHopGroup(const NextHopKey &nexthop, uint32_t return parseHandleSaiStatusFailure(handle_status); } } - + if (nhopgroup->second.nh_member_install_count) + { + nhopgroup->second.nh_member_install_count--; + } + + if (nhopgroup->second.nh_member_install_count == 0 && nhopgroup->second.eligible_for_default_route_nh_swap && !nhopgroup->second.is_default_route_nh_swap) + { + if(nexthop.ip_address.isV4()) + { + addDefaultRouteNexthopsInNextHopGroup(nhopgroup->second, v4_active_default_route_nhops); + } + else + { + addDefaultRouteNexthopsInNextHopGroup(nhopgroup->second, v6_active_default_route_nhops); + } + nhopgroup->second.is_default_route_nh_swap = true; + } ++count; gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_NEXTHOP_GROUP_MEMBER); } @@ -632,6 +783,7 @@ void RouteOrch::doTask(Consumer& consumer) string srv6_segments; string srv6_source; bool srv6_nh = false; + bool fallback_to_default_route = false; for (auto i : kfvFieldsValues(t)) { @@ -673,6 +825,10 @@ void RouteOrch::doTask(Consumer& consumer) { ctx.protocol = fvValue(i); } + if (fvField(i) == "fallback_to_default_route") + { + fallback_to_default_route = fvValue(i) == "true"; + } } /* @@ -687,6 +843,7 @@ void RouteOrch::doTask(Consumer& consumer) } ctx.nhg_index = nhg_index; + ctx.fallback_to_default_route = fallback_to_default_route; /* * If the nexthop_group is empty, create the next hop group key @@ -975,6 +1132,8 @@ void RouteOrch::doTask(Consumer& consumer) // Go through the bulker results auto it_prev = consumer.m_toSync.begin(); m_bulkNhgReducedRefCnt.clear(); + NextHopGroupKey v4_default_nhg_key; + NextHopGroupKey v6_default_nhg_key; while (it_prev != it) { KeyOpFieldsValuesTuple t = it_prev->second; @@ -1032,6 +1191,18 @@ void RouteOrch::doTask(Consumer& consumer) it_prev = consumer.m_toSync.erase(it_prev); else it_prev++; + + if (ip_prefix.isDefaultRoute() && vrf_id == gVirtualRouterId) + { + if (ip_prefix.isV4()) + { + v4_default_nhg_key = getSyncdRouteNhgKey(gVirtualRouterId, ip_prefix); + } + else + { + v6_default_nhg_key = getSyncdRouteNhgKey(gVirtualRouterId, ip_prefix); + } + } } } else if (op == DEL_COMMAND) @@ -1067,9 +1238,19 @@ void RouteOrch::doTask(Consumer& consumer) } else if (m_syncdNextHopGroups[it_nhg.first].ref_count == 0) { - removeNextHopGroup(it_nhg.first); + // Pass the flag to indicate if the NextHop Group as Default Route NH Members as swapped. + removeNextHopGroup(it_nhg.first, m_syncdNextHopGroups[it_nhg.first].is_default_route_nh_swap); } } + + if (!(v4_default_nhg_key.getSize()) && !(v6_default_nhg_key.getSize())) + return; + + if (v4_default_nhg_key.getSize()) + updateDefaultRouteSwapSet(v4_default_nhg_key, v4_active_default_route_nhops); + + if (v6_default_nhg_key.getSize()) + updateDefaultRouteSwapSet(v6_default_nhg_key, v6_active_default_route_nhops); } } @@ -1363,6 +1544,7 @@ bool RouteOrch::addNextHopGroup(const NextHopGroupKey &nexthops) NextHopGroupEntry next_hop_group_entry; next_hop_group_entry.next_hop_group_id = next_hop_group_id; + next_hop_group_entry.nh_member_install_count = 0; size_t npid_count = next_hop_ids.size(); vector nhgm_ids(npid_count); @@ -1433,6 +1615,7 @@ bool RouteOrch::addNextHopGroup(const NextHopGroupKey &nexthops) { next_hop_group_entry.nhopgroup_members[nhopgroup_members_set.find(nhid)->second].next_hop_id = nhgm_id; next_hop_group_entry.nhopgroup_members[nhopgroup_members_set.find(nhid)->second].seq_id = ((uint32_t)i) + 1; + next_hop_group_entry.nh_member_install_count++; } } @@ -1450,7 +1633,7 @@ bool RouteOrch::addNextHopGroup(const NextHopGroupKey &nexthops) return true; } -bool RouteOrch::removeNextHopGroup(const NextHopGroupKey &nexthops) +bool RouteOrch::removeNextHopGroup(const NextHopGroupKey &nexthops, const bool is_default_route_nh_swap) { SWSS_LOG_ENTER(); @@ -1471,10 +1654,10 @@ bool RouteOrch::removeNextHopGroup(const NextHopGroupKey &nexthops) SWSS_LOG_NOTICE("Delete next hop group %s", nexthops.to_string().c_str()); vector next_hop_ids; - auto& nhgm = next_hop_group_entry->second.nhopgroup_members; + auto &nhgm = is_default_route_nh_swap ? next_hop_group_entry->second.default_route_nhopgroup_members : next_hop_group_entry->second.nhopgroup_members; for (auto nhop = nhgm.begin(); nhop != nhgm.end();) { - if (m_neighOrch->isNextHopFlagSet(nhop->first, NHFLAGS_IFDOWN)) + if (m_neighOrch->isNextHopFlagSet(nhop->first, NHFLAGS_IFDOWN) && (!is_default_route_nh_swap)) { SWSS_LOG_WARN("NHFLAGS_IFDOWN set for next hop group member %s with next_hop_id %" PRIx64, nhop->first.to_string().c_str(), nhop->second.next_hop_id); @@ -1565,6 +1748,16 @@ bool RouteOrch::removeNextHopGroup(const NextHopGroupKey &nexthops) } } + // Decrement Nexthop Reference Count for Default Route NH Member used as swapped + if (is_default_route_nh_swap) + { + auto& nhgm = next_hop_group_entry->second.default_route_nhopgroup_members; + for (auto nhop = nhgm.begin(); nhop != nhgm.end(); ++nhop) + { + m_neighOrch->decreaseNextHopRefCount(nhop->first); + } + } + m_syncdNextHopGroups.erase(nexthops); return true; @@ -1717,6 +1910,11 @@ void RouteOrch::addTempRoute(RouteBulkContext& ctx, const NextHopGroupKey &nextH (*it).to_string().c_str(), ipPrefix.to_string().c_str()); it = next_hop_set.erase(it); } + else if(m_neighOrch->isNextHopFlagSet(*it, NHFLAGS_IFDOWN)) + { + SWSS_LOG_INFO("Interface down for NH %s, skip this NH", (*it).to_string().c_str()); + it = next_hop_set.erase(it); + } else it++; } @@ -1949,6 +2147,11 @@ bool RouteOrch::addRoute(RouteBulkContext& ctx, const NextHopGroupKey &nextHops) /* Return false since the original route is not successfully added */ return false; } + else + { + m_syncdNextHopGroups[nextHops].eligible_for_default_route_nh_swap = ctx.fallback_to_default_route; + m_syncdNextHopGroups[nextHops].is_default_route_nh_swap = false; + } } next_hop_id = m_syncdNextHopGroups[nextHops].next_hop_group_id; @@ -2504,6 +2707,11 @@ bool RouteOrch::removeRoutePost(const RouteBulkContext& ctx) updateDefRouteState(ipPrefix.to_string()); SWSS_LOG_INFO("Set route %s next hop ID to NULL", ipPrefix.to_string().c_str()); + + if (ipPrefix.isV4()) + v4_active_default_route_nhops.clear(); + else + v6_active_default_route_nhops.clear(); } else { diff --git a/orchagent/routeorch.h b/orchagent/routeorch.h index 577d966a265..77731c97772 100644 --- a/orchagent/routeorch.h +++ b/orchagent/routeorch.h @@ -40,6 +40,10 @@ struct NextHopGroupEntry sai_object_id_t next_hop_group_id; // next hop group id int ref_count; // reference count NextHopGroupMembers nhopgroup_members; // ids of members indexed by + NextHopGroupMembers default_route_nhopgroup_members; // ids of members indexed by + uint32_t nh_member_install_count; + bool eligible_for_default_route_nh_swap; + bool is_default_route_nh_swap; }; struct NextHopUpdate @@ -122,13 +126,15 @@ struct RouteBulkContext bool excp_intfs_flag; // using_temp_nhg will track if the NhgOrch's owned NHG is temporary or not bool using_temp_nhg; + bool fallback_to_default_route; std::string key; // Key in database table std::string protocol; // Protocol string bool is_set; // True if set operation RouteBulkContext(const std::string& key, bool is_set) - : key(key), excp_intfs_flag(false), using_temp_nhg(false), is_set(is_set) + : key(key), excp_intfs_flag(false), using_temp_nhg(false), is_set(is_set), + fallback_to_default_route(false) { } @@ -146,6 +152,7 @@ struct RouteBulkContext using_temp_nhg = false; key.clear(); protocol.clear(); + fallback_to_default_route = false; } }; @@ -197,12 +204,13 @@ class RouteOrch : public Orch, public Subject bool isRefCounterZero(const NextHopGroupKey&) const; bool addNextHopGroup(const NextHopGroupKey&); - bool removeNextHopGroup(const NextHopGroupKey&); + bool removeNextHopGroup(const NextHopGroupKey&, const bool is_default_route_nh_swap=false); void addNextHopRoute(const NextHopKey&, const RouteKey&); void removeNextHopRoute(const NextHopKey&, const RouteKey&); bool updateNextHopRoutes(const NextHopKey&, uint32_t&); bool getRoutesForNexthop(std::set&, const NextHopKey&); + bool swapnexthopinNextHopGroup(sai_object_id_t next_hop_group_id, sai_object_id_t default_next_hop_id); bool validnexthopinNextHopGroup(const NextHopKey&, uint32_t&); bool invalidnexthopinNextHopGroup(const NextHopKey&, uint32_t&); @@ -240,6 +248,8 @@ class RouteOrch : public Orch, public Subject unsigned int m_maxNextHopGroupCount; bool m_resync; + std::set v4_active_default_route_nhops; + std::set v6_active_default_route_nhops; shared_ptr m_stateDb; unique_ptr m_stateDefaultRouteTb; @@ -286,6 +296,13 @@ class RouteOrch : public Orch, public Subject bool isVipRoute(const IpPrefix &ipPrefix, const NextHopGroupKey &nextHops); void createVipRouteSubnetDecapTerm(const IpPrefix &ipPrefix); void removeVipRouteSubnetDecapTerm(const IpPrefix &ipPrefix); + bool addDefaultRouteNexthopsInNextHopGroup(NextHopGroupEntry& original_next_hop_group, std::set& default_route_next_hop_set); + bool removeDefaultRouteNexthopsInNextHopGroup(NextHopGroupEntry& original_next_hop_group, std::set& default_route_next_hop_set); + bool compareAndResolveDefaultRouteNhSwapNextHopGroup(const std::set& prev_default_route_next_hop_set, + const std::set& curr_default_route_next_hop_set); + + void updateDefaultRouteSwapSet(const NextHopGroupKey default_nhg_key, std::set& active_default_route_nhops); + }; #endif /* SWSS_ROUTEORCH_H */ From e3b48ae673d989c38c22210869c4ea7141cc3a44 Mon Sep 17 00:00:00 2001 From: Abhishek Dosi Date: Sun, 24 Nov 2024 23:40:10 +0000 Subject: [PATCH 02/16] More changes Signed-off-by: Abhishek Dosi --- orchagent/routeorch.cpp | 64 ----------------------------------------- orchagent/routeorch.h | 4 --- 2 files changed, 68 deletions(-) diff --git a/orchagent/routeorch.cpp b/orchagent/routeorch.cpp index d28a2f1e48c..161f64a03d1 100644 --- a/orchagent/routeorch.cpp +++ b/orchagent/routeorch.cpp @@ -399,44 +399,10 @@ void RouteOrch::updateDefaultRouteSwapSet(const NextHopGroupKey default_nhg_key, } } - if (!active_default_route_nhops.empty()) - { - // Not empty . For all the NexhopGroup that are still present with swap to default property - // we need to remove the nexthops that are no more present and add the one that are new. - compareAndResolveDefaultRouteNhSwapNextHopGroup(active_default_route_nhops, current_default_route_nhops); - } active_default_route_nhops.clear(); std::copy(current_default_route_nhops.begin(), current_default_route_nhops.end(), std::inserter(active_default_route_nhops, active_default_route_nhops.begin())); } -bool RouteOrch::compareAndResolveDefaultRouteNhSwapNextHopGroup(const std::set& prev_default_route_next_hop_set, - const std::set& curr_default_route_next_hop_set) -{ - std::set default_route_nh_delete_set; - std::set default_route_nh_create_set; - - std::set_difference(prev_default_route_next_hop_set.begin(), prev_default_route_next_hop_set.end(), - curr_default_route_next_hop_set.begin(), curr_default_route_next_hop_set.end(), - std::inserter(default_route_nh_delete_set, default_route_nh_delete_set.begin())); - - std::set_difference(curr_default_route_next_hop_set.begin(), curr_default_route_next_hop_set.end(), - prev_default_route_next_hop_set.begin(), prev_default_route_next_hop_set.end(), - std::inserter(default_route_nh_create_set, default_route_nh_create_set.begin())); - - - for (auto nhopgroup = m_syncdNextHopGroups.begin(); nhopgroup != m_syncdNextHopGroups.end(); ++nhopgroup) - { - if (!nhopgroup->second.is_default_route_nh_swap) - { - continue; - } - - addDefaultRouteNexthopsInNextHopGroup(nhopgroup->second, default_route_nh_create_set); - removeDefaultRouteNexthopsInNextHopGroup(nhopgroup->second, default_route_nh_delete_set); - } - return true; -} - bool RouteOrch::addDefaultRouteNexthopsInNextHopGroup(NextHopGroupEntry& original_next_hop_group, std::set& default_route_next_hop_set) { SWSS_LOG_ENTER(); @@ -478,36 +444,6 @@ bool RouteOrch::addDefaultRouteNexthopsInNextHopGroup(NextHopGroupEntry& origina return true; } -bool RouteOrch::removeDefaultRouteNexthopsInNextHopGroup(NextHopGroupEntry& original_next_hop_group, std::set& default_route_next_hop_set) -{ - SWSS_LOG_ENTER(); - sai_status_t status; - - for (auto it : default_route_next_hop_set) - { - if (original_next_hop_group.default_route_nhopgroup_members.find(it) == original_next_hop_group.default_route_nhopgroup_members.end()) - { - continue; - } - status = sai_next_hop_group_api->remove_next_hop_group_member(original_next_hop_group.default_route_nhopgroup_members[it].next_hop_id); - if (status != SAI_STATUS_SUCCESS) - { - SWSS_LOG_ERROR("Failed to remove next hop member %" PRIx64 " from group %" PRIx64 ": %d\n", - original_next_hop_group.default_route_nhopgroup_members[it].next_hop_id, original_next_hop_group.next_hop_group_id, status); - task_process_status handle_status = handleSaiRemoveStatus(SAI_API_NEXT_HOP_GROUP, status); - if (handle_status != task_success) - { - return parseHandleSaiStatusFailure(handle_status); - } - } - - gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_NEXTHOP_GROUP_MEMBER); - m_neighOrch->decreaseNextHopRefCount(it); - original_next_hop_group.default_route_nhopgroup_members.erase(it); - } - return true; -} - bool RouteOrch::validnexthopinNextHopGroup(const NextHopKey &nexthop, uint32_t& count) { SWSS_LOG_ENTER(); diff --git a/orchagent/routeorch.h b/orchagent/routeorch.h index 77731c97772..527f8cc7256 100644 --- a/orchagent/routeorch.h +++ b/orchagent/routeorch.h @@ -297,10 +297,6 @@ class RouteOrch : public Orch, public Subject void createVipRouteSubnetDecapTerm(const IpPrefix &ipPrefix); void removeVipRouteSubnetDecapTerm(const IpPrefix &ipPrefix); bool addDefaultRouteNexthopsInNextHopGroup(NextHopGroupEntry& original_next_hop_group, std::set& default_route_next_hop_set); - bool removeDefaultRouteNexthopsInNextHopGroup(NextHopGroupEntry& original_next_hop_group, std::set& default_route_next_hop_set); - bool compareAndResolveDefaultRouteNhSwapNextHopGroup(const std::set& prev_default_route_next_hop_set, - const std::set& curr_default_route_next_hop_set); - void updateDefaultRouteSwapSet(const NextHopGroupKey default_nhg_key, std::set& active_default_route_nhops); }; From 9c5c2e71fda8be8e655247e6e0a6eca1929edad1 Mon Sep 17 00:00:00 2001 From: Abhishek Dosi Date: Mon, 25 Nov 2024 03:45:32 +0000 Subject: [PATCH 03/16] Added UT Signed-off-by: Abhishek Dosi --- orchagent/routeorch.cpp | 2 +- tests/test_nhg.py | 811 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 809 insertions(+), 4 deletions(-) diff --git a/orchagent/routeorch.cpp b/orchagent/routeorch.cpp index 161f64a03d1..f0b7010752c 100644 --- a/orchagent/routeorch.cpp +++ b/orchagent/routeorch.cpp @@ -440,6 +440,7 @@ bool RouteOrch::addDefaultRouteNexthopsInNextHopGroup(NextHopGroupEntry& origina gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_NEXTHOP_GROUP_MEMBER); original_next_hop_group.default_route_nhopgroup_members[it].next_hop_id = nexthop_group_member_id; original_next_hop_group.default_route_nhopgroup_members[it].seq_id = 0; + original_next_hop_group.is_default_route_nh_swap = true; } return true; } @@ -570,7 +571,6 @@ bool RouteOrch::invalidnexthopinNextHopGroup(const NextHopKey &nexthop, uint32_t { addDefaultRouteNexthopsInNextHopGroup(nhopgroup->second, v6_active_default_route_nhops); } - nhopgroup->second.is_default_route_nh_swap = true; } ++count; gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_NEXTHOP_GROUP_MEMBER); diff --git a/tests/test_nhg.py b/tests/test_nhg.py index b9e125f7609..23b730e0602 100644 --- a/tests/test_nhg.py +++ b/tests/test_nhg.py @@ -125,22 +125,38 @@ def port_name(self, i): def port_ip(self, i): return "10.0.0." + str(i * 2) + def port_ipv6(self, i): + return "fc00::" + str(hex((i * 2)))[2:] + def port_ipprefix(self, i): return self.port_ip(i) + "/31" + def port_ipv6prefix(self, i): + return self.port_ipv6(i) + "/126" + def peer_ip(self, i): return "10.0.0." + str(i * 2 + 1) + def peer_ipv6(self, i): + return "fc00::" + str(hex((i * 2 + 1)))[2:] + def port_mac(self, i): return "00:00:00:00:00:0" + str(i + 1) - def config_intf(self, i): + def config_intf(self, i, is_ipv6_needed=False): fvs = {'NULL': 'NULL'} self.config_db.create_entry("INTERFACE", self.port_name(i), fvs) self.config_db.create_entry("INTERFACE", "{}|{}".format(self.port_name(i), self.port_ipprefix(i)), fvs) + if is_ipv6_needed: + self.config_db.create_entry("INTERFACE", "{}|{}".format(self.port_name(i), self.port_ipv6prefix(i)), fvs) + self.dvs.port_admin_set(self.port_name(i), "up") self.dvs.runcmd("arp -s {} {}".format(self.peer_ip(i), self.port_mac(i))) + if is_ipv6_needed: + command = "ip -6 neighbor replace {} lladdr {} dev {}".format(self.peer_ipv6(i), + self.port_mac(i), self.port_name(i)) + self.dvs.runcmd(command) assert self.dvs.servers[i].runcmd("ip link set down dev eth0") == 0 assert self.dvs.servers[i].runcmd("ip link set up dev eth0") == 0 @@ -188,7 +204,7 @@ def update_bfd_session_state(self, dvs, session, state): ntf.send("bfd_session_state_change", ntf_data, fvp) # BFD utilities for static route BFD and ecmp acceleration -- end - def init_test(self, dvs, num_intfs): + def init_test(self, dvs, num_intfs, is_ipv6_needed=False): self.dvs = dvs self.app_db = self.dvs.get_app_db() self.asic_db = self.dvs.get_asic_db() @@ -205,7 +221,7 @@ def init_test(self, dvs, num_intfs): self.dvs.setReadOnlyAttr('SAI_OBJECT_TYPE_SWITCH', 'SAI_SWITCH_ATTR_MAX_NUMBER_OF_FORWARDING_CLASSES', '63') for i in range(num_intfs): - self.config_intf(i) + self.config_intf(i, is_ipv6_needed) self.asic_nhgs_count = len(self.asic_db.get_keys(self.ASIC_NHG_STR)) self.asic_nhgms_count = len(self.asic_db.get_keys(self.ASIC_NHGM_STR)) @@ -1198,6 +1214,795 @@ def test_route_nhg(self, ordered_ecmp, dvs, dvs_route, testlog): if ordered_ecmp == 'true': self.disble_ordered_ecmp() + @pytest.mark.parametrize('is_ipv6_needed', [False, True]) + def test_route_fallback_to_default(self, is_ipv6_needed, dvs, dvs_route, testlog): + self.init_test(dvs, 6, True) + if is_ipv6_needed: + rtprefix = "2603:10b0::1/120" + defaultprefix = "::/0" + nexthop_str = "fc00::1,fc00::3,fc00::5" + default_nexthop_str = "fc00::7,fc00::9,fc00::b" + else: + rtprefix = "2.2.2.0/24" + defaultprefix = "0.0.0.0/0" + nexthop_str = "10.0.0.1,10.0.0.3,10.0.0.5" + default_nexthop_str = "10.0.0.7,10.0.0.9,10.0.0.11" + + dvs_route.check_asicdb_deleted_route_entries([rtprefix]) + + try: + dvs.disable_fpmsyncd() + # Program Regular Rouute with fallback to default + fvs = swsscommon.FieldValuePairs([("nexthop",nexthop_str), + ("ifname", "Ethernet0,Ethernet4,Ethernet8"), + ("fallback_to_default_route", "true")]) + self.rt_ps.set(rtprefix, fvs) + time.sleep(1) + + # Program default route + fvs = swsscommon.FieldValuePairs([("nexthop", default_nexthop_str), + ("ifname", "Ethernet12,Ethernet16,Ethernet20")]) + + self.rt_ps.set(defaultprefix, fvs) + time.sleep(1) + + # check if route was propagated to ASIC DB + rtkeys = dvs_route.check_asicdb_route_entries([rtprefix]) + + # check if default route was propagated to ASIC DB + defaultrtkeys = dvs_route.check_asicdb_route_entries([defaultprefix]) + + default_nhgid = None + default_nhops = set() + default_nhgmids = set() + default_nhopsids = set() + default_parentnhgid = set() + + rt_nhgid = None + rt_nhops = set() + rt_nhopsids = set() + rt_nhgmids = set() + rt_parentnhgid = set() + + # assert the route points to next hop group + for idx, rtkey in enumerate([rtkeys, defaultrtkeys]): + fvs = self.asic_db.get_entry(self.ASIC_RT_STR, rtkey[0]) + + if idx == 0: + rt_nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] + else: + default_nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] + + nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] + + fvs = self.asic_db.get_entry(self.ASIC_NHG_STR, nhgid) + + assert bool(fvs) + + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + + assert len(keys) == 6 + + for k in keys: + fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) + nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] + nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) + + if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == rt_nhgid: + rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) + rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) + rt_nhgmids.add(k) + rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) + elif fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == default_nhgid: + default_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) + default_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) + default_nhgmids.add(k) + default_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) + + assert len(rt_parentnhgid) == 1 + assert len(rt_nhopsids) == 3 + assert len(rt_nhops) == 3 + assert len(rt_nhgmids) == 3 + + assert len(default_parentnhgid) == 1 + assert len(default_nhopsids) == 3 + assert len(default_nhops) == 3 + assert len(default_nhgmids) == 3 + + assert rt_nhops != default_nhops + assert rt_nhopsids != default_nhopsids + assert rt_parentnhgid != default_parentnhgid + assert rt_nhgmids != default_nhgmids + + # bring links down one-by-one + for i in [0, 1, 2]: + self.flap_intf(i, 'down') + time.sleep(1) + + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + + if i != 2: + assert len(keys) == 5 - i + else: + # Last Link down so we will fallback to default eoute 3 members + assert len(keys) == 6 + rt_nhops.clear() + rt_nhgmids.clear() + rt_nhopsids.clear() + for k in keys: + fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) + nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] + nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) + + if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == rt_nhgid: + rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) + rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) + rt_nhgmids.add(k) + rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) + elif fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == default_nhgid: + default_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) + default_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) + default_nhgmids.add(k) + default_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) + + assert len(rt_parentnhgid) == 1 + assert len(rt_nhopsids) == 3 + assert len(rt_nhops) == 3 + assert len(rt_nhgmids) == 3 + + assert len(default_parentnhgid) == 1 + assert len(default_nhopsids) == 3 + assert len(default_nhops) == 3 + assert len(default_nhgmids) == 3 + + assert rt_nhops == default_nhops + assert rt_nhopsids == default_nhopsids + assert rt_nhgmids != default_nhgmids + + # bring links up one-by-one + # Bring link up in random order to verify sequence id is as per order + for i, val in enumerate([2,1,0]): + self.flap_intf(i, 'up') + time.sleep(1) + + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + assert len(keys) == 6 + + for k in keys: + fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) + nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] + nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) + + if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == rt_nhgid: + rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) + rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) + rt_nhgmids.add(k) + rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) + elif fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == default_nhgid: + default_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) + default_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) + default_nhgmids.add(k) + default_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) + + assert len(rt_parentnhgid) == 1 + assert len(rt_nhopsids) == 3 + assert len(rt_nhops) == 3 + assert len(rt_nhgmids) == 3 + + assert len(default_parentnhgid) == 1 + assert len(default_nhopsids) == 3 + assert len(default_nhops) == 3 + assert len(default_nhgmids) == 3 + + assert rt_nhops == default_nhops + assert rt_nhopsids == default_nhopsids + assert rt_nhgmids != default_nhgmids + + # Remove route 2.2.2.0/24 + self.rt_ps._del(rtprefix) + time.sleep(1) + + # Wait for route 2.2.2.0/24 to be removed + dvs_route.check_asicdb_deleted_route_entries([rtprefix]) + + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + + assert len(keys) == 3 + + # Remove route 0.0.0.0/0 + self.rt_ps._del(defaultprefix) + time.sleep(1) + + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + + assert len(keys) == 0 + + finally: + dvs.start_fpmsyncd() + + @pytest.mark.parametrize('is_ipv6_needed', [False, True]) + def test_route_fallback_to_default_update(self, is_ipv6_needed, dvs, dvs_route, testlog): + self.init_test(dvs, 6, True) + + if is_ipv6_needed: + rtprefix = "2603:10b0::1/120" + defaultprefix = "::/0" + nexthop_str = "fc00::1,fc00::3,fc00::5" + default_nexthop_str = "fc00::7,fc00::9,fc00::b" + updated_default_nexthop_str = "fc00::7,fc00::b" + else: + rtprefix = "2.2.2.0/24" + defaultprefix = "0.0.0.0/0" + nexthop_str = "10.0.0.1,10.0.0.3,10.0.0.5" + default_nexthop_str = "10.0.0.7,10.0.0.9,10.0.0.11" + updated_default_nexthop_str = "10.0.0.7,10.0.0.11" + + + dvs_route.check_asicdb_deleted_route_entries([rtprefix]) + + try: + dvs.disable_fpmsyncd() + + # Program default route + fvs = swsscommon.FieldValuePairs([("nexthop", default_nexthop_str), + ("ifname", "Ethernet12,Ethernet16,Ethernet20")]) + + self.rt_ps.set(defaultprefix, fvs) + time.sleep(1) + + # check if default route was propagated to ASIC DB + defaultrtkeys = dvs_route.check_asicdb_route_entries([defaultprefix]) + + fvs = self.asic_db.get_entry(self.ASIC_RT_STR, defaultrtkeys[0]) + + nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] + + fvs = self.asic_db.get_entry(self.ASIC_NHG_STR, nhgid) + + assert bool(fvs) + + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + + assert len(keys) == 3 + + + # Program default route + fvs = swsscommon.FieldValuePairs([("nexthop", updated_default_nexthop_str), + ("ifname", "Ethernet12,Ethernet20")]) + + + self.rt_ps.set(defaultprefix, fvs) + time.sleep(1) + + # check if default route was propagated to ASIC DB + defaultrtkeys = dvs_route.check_asicdb_route_entries([defaultprefix]) + + fvs = self.asic_db.get_entry(self.ASIC_RT_STR, defaultrtkeys[0]) + + nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] + + fvs = self.asic_db.get_entry(self.ASIC_NHG_STR, nhgid) + + assert bool(fvs) + + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + + assert len(keys) == 2 + + # Program Regular Rouute with fallback to default + fvs = swsscommon.FieldValuePairs([("nexthop", nexthop_str), + ("ifname", "Ethernet0,Ethernet4,Ethernet8"), + ("fallback_to_default_route", "true")]) + self.rt_ps.set(rtprefix, fvs) + time.sleep(1) + + # check if route was propagated to ASIC DB + rtkeys = dvs_route.check_asicdb_route_entries([rtprefix]) + + + default_nhgid = None + default_nhops = set() + default_nhgmids = set() + default_nhopsids = set() + default_parentnhgid = set() + + rt_nhgid = None + rt_nhops = set() + rt_nhopsids = set() + rt_nhgmids = set() + rt_parentnhgid = set() + + # assert the route points to next hop group + for idx, rtkey in enumerate([rtkeys, defaultrtkeys]): + fvs = self.asic_db.get_entry(self.ASIC_RT_STR, rtkey[0]) + + if idx == 0: + rt_nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] + else: + default_nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] + + nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] + + fvs = self.asic_db.get_entry(self.ASIC_NHG_STR, nhgid) + + assert bool(fvs) + + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + + assert len(keys) == 5 + + for k in keys: + fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) + nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] + nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) + + if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == rt_nhgid: + rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) + rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) + rt_nhgmids.add(k) + rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) + elif fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == default_nhgid: + default_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) + default_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) + default_nhgmids.add(k) + default_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) + + assert len(rt_parentnhgid) == 1 + assert len(rt_nhopsids) == 3 + assert len(rt_nhops) == 3 + assert len(rt_nhgmids) == 3 + + assert len(default_parentnhgid) == 1 + assert len(default_nhopsids) == 2 + assert len(default_nhops) == 2 + assert len(default_nhgmids) == 2 + + assert rt_nhops != default_nhops + assert rt_nhopsids != default_nhopsids + assert rt_parentnhgid != default_parentnhgid + assert rt_nhgmids != default_nhgmids + + # bring links down one-by-one + for i in [0, 1, 2]: + self.flap_intf(i, 'down') + time.sleep(1) + + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + + if i != 2: + assert len(keys) == 4 - i + else: + # Last Link down so we will fallback to default eoute 3 members + assert len(keys) == 4 + rt_nhops.clear() + rt_nhgmids.clear() + rt_nhopsids.clear() + for k in keys: + fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) + nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] + nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) + + if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == rt_nhgid: + rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) + rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) + rt_nhgmids.add(k) + rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) + elif fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == default_nhgid: + default_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) + default_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) + default_nhgmids.add(k) + default_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) + + assert len(rt_parentnhgid) == 1 + assert len(rt_nhopsids) == 2 + assert len(rt_nhops) == 2 + assert len(rt_nhgmids) == 2 + + assert len(default_parentnhgid) == 1 + assert len(default_nhopsids) == 2 + assert len(default_nhops) == 2 + assert len(default_nhgmids) == 2 + + assert rt_nhops == default_nhops + assert rt_nhopsids == default_nhopsids + assert rt_nhgmids != default_nhgmids + + # bring links up one-by-one + # Bring link up in random order to verify sequence id is as per order + for i, val in enumerate([2,1,0]): + self.flap_intf(i, 'up') + time.sleep(1) + + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + assert len(keys) == 4 + + for k in keys: + fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) + nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] + nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) + + if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == rt_nhgid: + rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) + rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) + rt_nhgmids.add(k) + rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) + elif fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == default_nhgid: + default_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) + default_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) + default_nhgmids.add(k) + default_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) + + assert len(rt_parentnhgid) == 1 + assert len(rt_nhopsids) == 2 + assert len(rt_nhops) == 2 + assert len(rt_nhgmids) == 2 + + assert len(default_parentnhgid) == 1 + assert len(default_nhopsids) == 2 + assert len(default_nhops) == 2 + assert len(default_nhgmids) == 2 + + assert rt_nhops == default_nhops + assert rt_nhopsids == default_nhopsids + assert rt_nhgmids != default_nhgmids + + # Remove route 2.2.2.0/24 + self.rt_ps._del(rtprefix) + time.sleep(1) + + # Wait for route 2.2.2.0/24 to be removed + dvs_route.check_asicdb_deleted_route_entries([rtprefix]) + + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + + assert len(keys) == 2 + + # Remove route 0.0.0.0/0 + self.rt_ps._del(defaultprefix) + time.sleep(1) + + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + + assert len(keys) == 0 + + finally: + dvs.start_fpmsyncd() + + @pytest.mark.parametrize('is_ipv6_needed', [False, True]) + def test_route_fallback_to_default_negative(self, is_ipv6_needed, dvs, dvs_route, testlog): + self.init_test(dvs, 6, True) + if is_ipv6_needed: + rtprefix = "2603:10b0::1/120" + nexthop_str = "fc00::1,fc00::3,fc00::5" + else: + rtprefix = "2.2.2.0/24" + nexthop_str = "10.0.0.1,10.0.0.3,10.0.0.5" + + dvs_route.check_asicdb_deleted_route_entries([rtprefix]) + + try: + dvs.disable_fpmsyncd() + # Program Regular Rouute with fallback to default + fvs = swsscommon.FieldValuePairs([("nexthop",nexthop_str), + ("ifname", "Ethernet0,Ethernet4,Ethernet8"), + ("fallback_to_default_route", "true")]) + self.rt_ps.set(rtprefix, fvs) + time.sleep(1) + + + # check if route was propagated to ASIC DB + rtkeys = dvs_route.check_asicdb_route_entries([rtprefix]) + + rt_nhgid = None + rt_nhops = set() + rt_nhopsids = set() + rt_nhgmids = set() + rt_parentnhgid = set() + + # assert the route points to next hop group + for rtkey in ([rtkeys]): + fvs = self.asic_db.get_entry(self.ASIC_RT_STR, rtkey[0]) + + rt_nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] + nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] + + fvs = self.asic_db.get_entry(self.ASIC_NHG_STR, nhgid) + + assert bool(fvs) + + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + + assert len(keys) == 3 + + for k in keys: + fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) + nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] + nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) + + if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == rt_nhgid: + rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) + rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) + rt_nhgmids.add(k) + rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) + + assert len(rt_parentnhgid) == 1 + assert len(rt_nhopsids) == 3 + assert len(rt_nhops) == 3 + assert len(rt_nhgmids) == 3 + + # bring links down one-by-one + for i in [0, 1, 2]: + self.flap_intf(i, 'down') + time.sleep(1) + + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + assert len(keys) == 2 - i + + # bring links up one-by-one + # Bring link up in random order to verify sequence id is as per order + for i, val in enumerate([2,1,0]): + self.flap_intf(i, 'up') + time.sleep(1) + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + assert len(keys) == 3 + + for k in keys: + fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) + nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] + nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) + + if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == rt_nhgid: + rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) + rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) + rt_nhgmids.add(k) + rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) + + assert len(rt_parentnhgid) == 1 + assert len(rt_nhopsids) == 3 + assert len(rt_nhops) == 3 + assert len(rt_nhgmids) == 3 + + # Remove route 2.2.2.0/24 + self.rt_ps._del(rtprefix) + time.sleep(1) + + # Wait for route 2.2.2.0/24 to be removed + dvs_route.check_asicdb_deleted_route_entries([rtprefix]) + + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + + assert len(keys) == 0 + + finally: + dvs.start_fpmsyncd() + + def test_route_fallback_to_default_bothv4v6(self, dvs, dvs_route, testlog): + self.init_test(dvs, 6, True) + rtprefix_v6 = "2603:10b0::1/120" + defaultprefix_v6 = "::/0" + nexthop_str_v6 = "fc00::1,fc00::3,fc00::5" + default_nexthop_str_v6 = "fc00::7,fc00::9,fc00::b" + rtprefix = "2.2.2.0/24" + defaultprefix = "0.0.0.0/0" + nexthop_str = "10.0.0.1,10.0.0.3,10.0.0.5" + default_nexthop_str = "10.0.0.7,10.0.0.9,10.0.0.11" + + dvs_route.check_asicdb_deleted_route_entries([rtprefix, rtprefix_v6]) + + try: + dvs.disable_fpmsyncd() + # Program Regular Rouute with fallback to default + fvs = swsscommon.FieldValuePairs([("nexthop",nexthop_str), + ("ifname", "Ethernet0,Ethernet4,Ethernet8"), + ("fallback_to_default_route", "true")]) + self.rt_ps.set(rtprefix, fvs) + time.sleep(1) + + fvs = swsscommon.FieldValuePairs([("nexthop",nexthop_str_v6), + ("ifname", "Ethernet0,Ethernet4,Ethernet8"), + ("fallback_to_default_route", "true")]) + self.rt_ps.set(rtprefix_v6, fvs) + time.sleep(1) + + # Program default route + fvs = swsscommon.FieldValuePairs([("nexthop", default_nexthop_str), + ("ifname", "Ethernet12,Ethernet16,Ethernet20")]) + + self.rt_ps.set(defaultprefix, fvs) + time.sleep(1) + + fvs = swsscommon.FieldValuePairs([("nexthop", default_nexthop_str_v6), + ("ifname", "Ethernet12,Ethernet16,Ethernet20")]) + + self.rt_ps.set(defaultprefix_v6, fvs) + time.sleep(1) + + # check if route was propagated to ASIC DB + rtkeys = dvs_route.check_asicdb_route_entries([rtprefix, rtprefix_v6]) + + # check if default route was propagated to ASIC DB + defaultrtkeys = dvs_route.check_asicdb_route_entries([defaultprefix, defaultprefix_v6]) + + default_nhgid = [] + default_nhops = set() + default_nhgmids = set() + default_nhopsids = set() + default_parentnhgid = set() + + rt_nhgid = [] + rt_nhops = set() + rt_nhopsids = set() + rt_nhgmids = set() + rt_parentnhgid = set() + + # assert the route points to next hop group + flat_list = [] + for x in rtkeys: + flat_list.append(x) + for x in defaultrtkeys: + flat_list.append(x) + for idx, rtkey in enumerate(flat_list): + fvs = self.asic_db.get_entry(self.ASIC_RT_STR, rtkey) + if idx in [0,1]: + rt_nhgid.append(fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"]) + else: + default_nhgid.append(fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"]) + + nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] + + fvs = self.asic_db.get_entry(self.ASIC_NHG_STR, nhgid) + + assert bool(fvs) + + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + + assert len(keys) == 12 + + for k in keys: + fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) + nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] + nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) + + if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] in rt_nhgid: + rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) + rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) + rt_nhgmids.add(k) + rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) + elif fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] in default_nhgid: + default_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) + default_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) + default_nhgmids.add(k) + default_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) + + assert len(rt_parentnhgid) == 2 + assert len(rt_nhopsids) == 6 + assert len(rt_nhops) == 6 + assert len(rt_nhgmids) == 6 + + assert len(default_parentnhgid) == 2 + assert len(default_nhopsids) == 6 + assert len(default_nhops) == 6 + assert len(default_nhgmids) == 6 + + assert rt_nhops != default_nhops + assert rt_nhopsids != default_nhopsids + assert rt_parentnhgid != default_parentnhgid + assert rt_nhgmids != default_nhgmids + + # bring links down one-by-one + for i in [0, 1, 2]: + self.flap_intf(i, 'down') + time.sleep(1) + + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + + if i != 2: + assert len(keys) == 10 - (i * 2) + else: + # Last Link down so we will fallback to default eoute 3 members + assert len(keys) == 12 + rt_nhops.clear() + rt_nhgmids.clear() + rt_nhopsids.clear() + for k in keys: + fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) + nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] + nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) + + if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] in rt_nhgid: + rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) + rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) + rt_nhgmids.add(k) + rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) + elif fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] in default_nhgid: + default_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) + default_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) + default_nhgmids.add(k) + default_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) + + assert len(rt_parentnhgid) == 2 + assert len(rt_nhopsids) == 6 + assert len(rt_nhops) == 6 + assert len(rt_nhgmids) == 6 + + assert len(default_parentnhgid) == 2 + assert len(default_nhopsids) == 6 + assert len(default_nhops) == 6 + assert len(default_nhgmids) == 6 + + assert rt_nhops == default_nhops + assert rt_nhopsids == default_nhopsids + assert rt_nhgmids != default_nhgmids + + # bring links up one-by-one + # Bring link up in random order to verify sequence id is as per order + for i, val in enumerate([2,1,0]): + self.flap_intf(i, 'up') + time.sleep(1) + + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + + assert len(keys) == 12 + + for k in keys: + fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) + nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] + nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) + + if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] in rt_nhgid: + rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) + rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) + rt_nhgmids.add(k) + rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) + elif fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] in default_nhgid: + default_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) + default_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) + default_nhgmids.add(k) + default_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) + + assert len(rt_parentnhgid) == 2 + assert len(rt_nhopsids) == 6 + assert len(rt_nhops) == 6 + assert len(rt_nhgmids) == 6 + + assert len(default_parentnhgid) == 2 + assert len(default_nhopsids) == 6 + assert len(default_nhops) == 6 + assert len(default_nhgmids) == 6 + + assert rt_nhops == default_nhops + assert rt_nhopsids == default_nhopsids + assert rt_nhgmids != default_nhgmids + + # Remove route 2.2.2.0/24 + self.rt_ps._del(rtprefix) + time.sleep(1) + + self.rt_ps._del(rtprefix_v6) + time.sleep(1) + + # Wait for route 2.2.2.0/24 to be removed + dvs_route.check_asicdb_deleted_route_entries([rtprefix, rtprefix_v6]) + + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + + assert len(keys) == 6 + + # Remove route 0.0.0.0/0 + self.rt_ps._del(defaultprefix) + time.sleep(1) + + self.rt_ps._del(defaultprefix_v6) + time.sleep(1) + + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + + assert len(keys) == 0 + + finally: + dvs.start_fpmsyncd() + + def test_label_route_nhg(self, dvs, testlog): self.init_test(dvs, 3) From 126c2018985fbdf100c01a80f1830eb94ce001de Mon Sep 17 00:00:00 2001 From: Abhishek Dosi Date: Mon, 25 Nov 2024 21:08:57 +0000 Subject: [PATCH 04/16] Fix indenation and missed file changes Signed-off-by: Abhishek Dosi --- orchagent/routeorch.cpp | 20 ++++++++++---------- orchagent/routeorch.h | 5 ++--- tests/conftest.py | 6 ++++++ 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/orchagent/routeorch.cpp b/orchagent/routeorch.cpp index f0b7010752c..c24d9f06253 100644 --- a/orchagent/routeorch.cpp +++ b/orchagent/routeorch.cpp @@ -388,7 +388,7 @@ void RouteOrch::updateDefaultRouteSwapSet(const NextHopGroupKey default_nhg_key, if (default_nhg_key.getSize() == 1) { - current_default_route_nhops.insert(*default_nhg_key.getNextHops().begin()); + current_default_route_nhops.insert(*default_nhg_key.getNextHops().begin()); } else { @@ -462,7 +462,7 @@ bool RouteOrch::validnexthopinNextHopGroup(const NextHopKey &nexthop, uint32_t& continue; } - // AZNG Route NHOP Group is swapped by default route nh memeber . do not add Nexthop again. + // Route NHOP Group is swapped by default route nh memeber . do not add Nexthop again. // Wait for Nexthop Group Cleanup if (nhopgroup->second.is_default_route_nh_swap) { @@ -762,9 +762,9 @@ void RouteOrch::doTask(Consumer& consumer) ctx.protocol = fvValue(i); } if (fvField(i) == "fallback_to_default_route") - { + { fallback_to_default_route = fvValue(i) == "true"; - } + } } /* @@ -1132,11 +1132,11 @@ void RouteOrch::doTask(Consumer& consumer) { if (ip_prefix.isV4()) { - v4_default_nhg_key = getSyncdRouteNhgKey(gVirtualRouterId, ip_prefix); + v4_default_nhg_key = getSyncdRouteNhgKey(gVirtualRouterId, ip_prefix); } else { - v6_default_nhg_key = getSyncdRouteNhgKey(gVirtualRouterId, ip_prefix); + v6_default_nhg_key = getSyncdRouteNhgKey(gVirtualRouterId, ip_prefix); } } } @@ -1174,8 +1174,8 @@ void RouteOrch::doTask(Consumer& consumer) } else if (m_syncdNextHopGroups[it_nhg.first].ref_count == 0) { - // Pass the flag to indicate if the NextHop Group as Default Route NH Members as swapped. - removeNextHopGroup(it_nhg.first, m_syncdNextHopGroups[it_nhg.first].is_default_route_nh_swap); + // Pass the flag to indicate if the NextHop Group as Default Route NH Members as swapped. + removeNextHopGroup(it_nhg.first, m_syncdNextHopGroups[it_nhg.first].is_default_route_nh_swap); } } @@ -1186,7 +1186,7 @@ void RouteOrch::doTask(Consumer& consumer) updateDefaultRouteSwapSet(v4_default_nhg_key, v4_active_default_route_nhops); if (v6_default_nhg_key.getSize()) - updateDefaultRouteSwapSet(v6_default_nhg_key, v6_active_default_route_nhops); + updateDefaultRouteSwapSet(v6_default_nhg_key, v6_active_default_route_nhops); } } @@ -1590,7 +1590,7 @@ bool RouteOrch::removeNextHopGroup(const NextHopGroupKey &nexthops, const bool i SWSS_LOG_NOTICE("Delete next hop group %s", nexthops.to_string().c_str()); vector next_hop_ids; - auto &nhgm = is_default_route_nh_swap ? next_hop_group_entry->second.default_route_nhopgroup_members : next_hop_group_entry->second.nhopgroup_members; + auto& nhgm = is_default_route_nh_swap ? next_hop_group_entry->second.default_route_nhopgroup_members : next_hop_group_entry->second.nhopgroup_members; for (auto nhop = nhgm.begin(); nhop != nhgm.end();) { if (m_neighOrch->isNextHopFlagSet(nhop->first, NHFLAGS_IFDOWN) && (!is_default_route_nh_swap)) diff --git a/orchagent/routeorch.h b/orchagent/routeorch.h index 527f8cc7256..dc470da47b5 100644 --- a/orchagent/routeorch.h +++ b/orchagent/routeorch.h @@ -134,7 +134,7 @@ struct RouteBulkContext RouteBulkContext(const std::string& key, bool is_set) : key(key), excp_intfs_flag(false), using_temp_nhg(false), is_set(is_set), - fallback_to_default_route(false) + fallback_to_default_route(false) { } @@ -152,7 +152,7 @@ struct RouteBulkContext using_temp_nhg = false; key.clear(); protocol.clear(); - fallback_to_default_route = false; + fallback_to_default_route = false; } }; @@ -298,7 +298,6 @@ class RouteOrch : public Orch, public Subject void removeVipRouteSubnetDecapTerm(const IpPrefix &ipPrefix); bool addDefaultRouteNexthopsInNextHopGroup(NextHopGroupEntry& original_next_hop_group, std::set& default_route_next_hop_set); void updateDefaultRouteSwapSet(const NextHopGroupKey default_nhg_key, std::set& active_default_route_nhops); - }; #endif /* SWSS_ROUTEORCH_H */ diff --git a/tests/conftest.py b/tests/conftest.py index c94224539bf..d25705a7ff1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -749,6 +749,12 @@ def stop_fpmsyncd(self): self.runcmd(['sh', '-c', 'pkill -x fpmsyncd']) time.sleep(1) + def disable_fpmsyncd(self): + self.runcmd(['sh', '-c', 'supervisorctl stop fpmsyncd']) + + # Let's give fpmsyncd a chance to connect to Zebra. + time.sleep(5) + # deps: warm_reboot def SubscribeAppDbObject(self, objpfx): r = redis.Redis(unix_socket_path=self.redis_sock, db=swsscommon.APPL_DB, From 4751f4126a55b358dbeb2e8969a45351419a87f4 Mon Sep 17 00:00:00 2001 From: Abhishek Dosi Date: Mon, 25 Nov 2024 21:12:39 +0000 Subject: [PATCH 05/16] Fix indenation Signed-off-by: Abhishek Dosi --- orchagent/routeorch.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/orchagent/routeorch.cpp b/orchagent/routeorch.cpp index c24d9f06253..475ffca64ca 100644 --- a/orchagent/routeorch.cpp +++ b/orchagent/routeorch.cpp @@ -762,9 +762,9 @@ void RouteOrch::doTask(Consumer& consumer) ctx.protocol = fvValue(i); } if (fvField(i) == "fallback_to_default_route") - { + { fallback_to_default_route = fvValue(i) == "true"; - } + } } /* From 1fef1c284b92e0b288e0d078769b129f9c929a36 Mon Sep 17 00:00:00 2001 From: Abhishek Dosi Date: Mon, 25 Nov 2024 21:23:14 +0000 Subject: [PATCH 06/16] Fix Signed-off-by: Abhishek Dosi --- orchagent/routeorch.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/orchagent/routeorch.h b/orchagent/routeorch.h index dc470da47b5..e43d91169ad 100644 --- a/orchagent/routeorch.h +++ b/orchagent/routeorch.h @@ -134,7 +134,7 @@ struct RouteBulkContext RouteBulkContext(const std::string& key, bool is_set) : key(key), excp_intfs_flag(false), using_temp_nhg(false), is_set(is_set), - fallback_to_default_route(false) + fallback_to_default_route(false) { } @@ -152,7 +152,7 @@ struct RouteBulkContext using_temp_nhg = false; key.clear(); protocol.clear(); - fallback_to_default_route = false; + fallback_to_default_route = false; } }; From de8e979e02daa526c25e6c202fef64ab4fe4fc0d Mon Sep 17 00:00:00 2001 From: Abhishek Dosi Date: Tue, 26 Nov 2024 01:16:35 +0000 Subject: [PATCH 07/16] Fixed flaky test case Signed-off-by: Abhishek Dosi --- tests/test_nhg.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test_nhg.py b/tests/test_nhg.py index 23b730e0602..81f65684856 100644 --- a/tests/test_nhg.py +++ b/tests/test_nhg.py @@ -1744,7 +1744,11 @@ def test_route_fallback_to_default_negative(self, is_ipv6_needed, dvs, dvs_route self.flap_intf(i, 'up') time.sleep(1) keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - assert len(keys) == 3 + assert len(keys) == i + 1 + + rt_nhops.clear() + rt_nhgmids.clear() + rt_nhopsids.clear() for k in keys: fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) From 8d2d008a9a308ca4bc9e9bc51ca962147e85d76b Mon Sep 17 00:00:00 2001 From: abdosi <58047199+abdosi@users.noreply.github.com> Date: Thu, 28 Nov 2024 17:56:23 -0800 Subject: [PATCH 08/16] Update test_nhg.py --- tests/test_nhg.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_nhg.py b/tests/test_nhg.py index 81f65684856..e350db418fd 100644 --- a/tests/test_nhg.py +++ b/tests/test_nhg.py @@ -1418,7 +1418,7 @@ def test_route_fallback_to_default(self, is_ipv6_needed, dvs, dvs_route, testlog assert len(keys) == 0 finally: - dvs.start_fpmsyncd() + dvs.start_swss() @pytest.mark.parametrize('is_ipv6_needed', [False, True]) def test_route_fallback_to_default_update(self, is_ipv6_needed, dvs, dvs_route, testlog): @@ -1666,7 +1666,7 @@ def test_route_fallback_to_default_update(self, is_ipv6_needed, dvs, dvs_route, assert len(keys) == 0 finally: - dvs.start_fpmsyncd() + dvs.start_swss() @pytest.mark.parametrize('is_ipv6_needed', [False, True]) def test_route_fallback_to_default_negative(self, is_ipv6_needed, dvs, dvs_route, testlog): @@ -1778,7 +1778,7 @@ def test_route_fallback_to_default_negative(self, is_ipv6_needed, dvs, dvs_route assert len(keys) == 0 finally: - dvs.start_fpmsyncd() + dvs.start_swss() def test_route_fallback_to_default_bothv4v6(self, dvs, dvs_route, testlog): self.init_test(dvs, 6, True) @@ -2004,7 +2004,7 @@ def test_route_fallback_to_default_bothv4v6(self, dvs, dvs_route, testlog): assert len(keys) == 0 finally: - dvs.start_fpmsyncd() + dvs.start_swss() def test_label_route_nhg(self, dvs, testlog): From a90ba417ea88d05d7bc98c13e22811c75580418f Mon Sep 17 00:00:00 2001 From: Abhishek Dosi Date: Fri, 29 Nov 2024 21:50:48 +0000 Subject: [PATCH 09/16] Revert "Update test_nhg.py" This reverts commit 8d2d008a9a308ca4bc9e9bc51ca962147e85d76b. --- tests/test_nhg.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_nhg.py b/tests/test_nhg.py index e350db418fd..81f65684856 100644 --- a/tests/test_nhg.py +++ b/tests/test_nhg.py @@ -1418,7 +1418,7 @@ def test_route_fallback_to_default(self, is_ipv6_needed, dvs, dvs_route, testlog assert len(keys) == 0 finally: - dvs.start_swss() + dvs.start_fpmsyncd() @pytest.mark.parametrize('is_ipv6_needed', [False, True]) def test_route_fallback_to_default_update(self, is_ipv6_needed, dvs, dvs_route, testlog): @@ -1666,7 +1666,7 @@ def test_route_fallback_to_default_update(self, is_ipv6_needed, dvs, dvs_route, assert len(keys) == 0 finally: - dvs.start_swss() + dvs.start_fpmsyncd() @pytest.mark.parametrize('is_ipv6_needed', [False, True]) def test_route_fallback_to_default_negative(self, is_ipv6_needed, dvs, dvs_route, testlog): @@ -1778,7 +1778,7 @@ def test_route_fallback_to_default_negative(self, is_ipv6_needed, dvs, dvs_route assert len(keys) == 0 finally: - dvs.start_swss() + dvs.start_fpmsyncd() def test_route_fallback_to_default_bothv4v6(self, dvs, dvs_route, testlog): self.init_test(dvs, 6, True) @@ -2004,7 +2004,7 @@ def test_route_fallback_to_default_bothv4v6(self, dvs, dvs_route, testlog): assert len(keys) == 0 finally: - dvs.start_swss() + dvs.start_fpmsyncd() def test_label_route_nhg(self, dvs, testlog): From d9272eec60372cf2580657960777319e4b708b34 Mon Sep 17 00:00:00 2001 From: Abhishek Dosi Date: Fri, 29 Nov 2024 22:07:23 +0000 Subject: [PATCH 10/16] More changes Signed-off-by: Abhishek Dosi --- tests/conftest.py | 2 +- tests/test_nhg.py | 2102 ++++++++++++++++++++++----------------------- 2 files changed, 1052 insertions(+), 1052 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index d25705a7ff1..1c5c6c3eddf 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -742,7 +742,7 @@ def start_fpmsyncd(self): self.runcmd(['sh', '-c', 'supervisorctl start fpmsyncd']) # Let's give fpmsyncd a chance to connect to Zebra. - time.sleep(5) + time.sleep(10) # deps: warm_reboot def stop_fpmsyncd(self): diff --git a/tests/test_nhg.py b/tests/test_nhg.py index 81f65684856..398cedf4a4f 100644 --- a/tests/test_nhg.py +++ b/tests/test_nhg.py @@ -1214,469 +1214,646 @@ def test_route_nhg(self, ordered_ecmp, dvs, dvs_route, testlog): if ordered_ecmp == 'true': self.disble_ordered_ecmp() - @pytest.mark.parametrize('is_ipv6_needed', [False, True]) - def test_route_fallback_to_default(self, is_ipv6_needed, dvs, dvs_route, testlog): - self.init_test(dvs, 6, True) - if is_ipv6_needed: - rtprefix = "2603:10b0::1/120" - defaultprefix = "::/0" - nexthop_str = "fc00::1,fc00::3,fc00::5" - default_nexthop_str = "fc00::7,fc00::9,fc00::b" - else: - rtprefix = "2.2.2.0/24" - defaultprefix = "0.0.0.0/0" - nexthop_str = "10.0.0.1,10.0.0.3,10.0.0.5" - default_nexthop_str = "10.0.0.7,10.0.0.9,10.0.0.11" - - dvs_route.check_asicdb_deleted_route_entries([rtprefix]) + def test_label_route_nhg(self, dvs, testlog): + self.init_test(dvs, 3) - try: - dvs.disable_fpmsyncd() - # Program Regular Rouute with fallback to default - fvs = swsscommon.FieldValuePairs([("nexthop",nexthop_str), - ("ifname", "Ethernet0,Ethernet4,Ethernet8"), - ("fallback_to_default_route", "true")]) - self.rt_ps.set(rtprefix, fvs) + # add label route + fvs = swsscommon.FieldValuePairs([("nexthop","10.0.0.1,10.0.0.3,10.0.0.5"), + ("ifname", "Ethernet0,Ethernet4,Ethernet8")]) + self.lr_ps.set("10", fvs) + self.asic_db.wait_for_n_keys(self.ASIC_INSEG_STR, self.asic_insgs_count + 1) + self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count + 1) + self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 3) + + k = self.get_inseg_id('10') + assert k is not None + + # assert the route points to next hop group + fvs = self.asic_db.get_entry(self.ASIC_INSEG_STR, k) + nhgid = fvs["SAI_INSEG_ENTRY_ATTR_NEXT_HOP_ID"] + fvs = self.asic_db.get_entry(self.ASIC_NHG_STR, nhgid) + assert bool(fvs) + + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + assert len(keys) == 3 + for k in keys: + fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) + assert fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == nhgid + + # bring links down one-by-one + for i in [0, 1, 2]: + self.flap_intf(i, 'down') + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + assert len(keys) == 2 - i + + # bring links up one-by-one + for i in [0, 1, 2]: + self.flap_intf(i, 'up') + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + assert len(keys) == i + 1 + for k in keys: + fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) + assert fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == nhgid + + # Remove label route 10 + self.lr_ps._del("10") + + # Wait for label route 10 to be removed + self.asic_db.wait_for_n_keys(self.ASIC_INSEG_STR, self.asic_insgs_count) + self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count) + + def test_nhgorch_labeled_nhs(self, dvs, testlog): + # Test scenario: + # - create a NHG with all labeled and weighted NHs and assert 2 new NHs are created + # - create a NHG with an existing label and assert no new NHs are created + # - create a NHG with a new label and assert a new NH is created + # - remove the third NHG and assert the NH is deleted + # - delete the second group and assert no NH is deleted because it is still referenced by the first group + # - remove the weights from the first NHG and change the labels, leaving one NH unlabeled; assert one NH is + # deleted + # - delete the first NHG and perform cleanup + def mainline_labeled_nhs_test(): + # Add a group containing labeled weighted NHs + fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.1,10.0.0.3'), + ('mpls_nh', 'push1,push3'), + ('ifname', 'Ethernet0,Ethernet4'), + ('weight', '2,4')]) + self.nhg_ps.set('group1', fvs) + self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count + 1) + self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 2) + + # NhgOrch should create two next hops for the labeled ones + self.asic_db.wait_for_n_keys(self.ASIC_NHS_STR, self.asic_nhs_count + 2) + + # Assert the weights are properly set + nhgm_ids = self.get_nhgm_ids('group1') + weights = [] + for k in nhgm_ids: + fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) + weights.append(fvs['SAI_NEXT_HOP_GROUP_MEMBER_ATTR_WEIGHT']) + assert set(weights) == set(['2', '4']) + + # Create a new single next hop with the same label + fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.1'), + ('mpls_nh', 'push1'), + ('ifname', 'Ethernet0')]) + self.nhg_ps.set('group2', fvs) + + # No new next hop should be added time.sleep(1) + assert len(self.asic_db.get_keys(self.ASIC_NHS_STR)) == self.asic_nhs_count + 2 - # Program default route - fvs = swsscommon.FieldValuePairs([("nexthop", default_nexthop_str), - ("ifname", "Ethernet12,Ethernet16,Ethernet20")]) + # Create a new single next hop with a different label + fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.1'), + ('mpls_nh', 'push2'), + ('ifname', 'Ethernet0')]) + self.nhg_ps.set('group3', fvs) - self.rt_ps.set(defaultprefix, fvs) + # A new next hop should be added + self.asic_db.wait_for_n_keys(self.ASIC_NHS_STR, self.asic_nhs_count + 3) + + # Delete group3 + self.nhg_ps._del('group3') + + # Group3's NH should be deleted + self.asic_db.wait_for_n_keys(self.ASIC_NHS_STR, self.asic_nhs_count + 2) + + # Delete group2 + self.nhg_ps._del('group2') + + # The number of NHs should be the same as they are still referenced by + # group1 time.sleep(1) + self.asic_db.wait_for_n_keys(self.ASIC_NHS_STR, self.asic_nhs_count + 2) - # check if route was propagated to ASIC DB - rtkeys = dvs_route.check_asicdb_route_entries([rtprefix]) - - # check if default route was propagated to ASIC DB - defaultrtkeys = dvs_route.check_asicdb_route_entries([defaultprefix]) + # Update group1 with no weights and both labeled and unlabeled NHs + fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.1,10.0.0.3'), + ('mpls_nh', 'push2,na'), + ('ifname', 'Ethernet0,Ethernet4')]) + self.nhg_ps.set('group1', fvs) - default_nhgid = None - default_nhops = set() - default_nhgmids = set() - default_nhopsids = set() - default_parentnhgid = set() + # Group members should be replaced and one NH should get deleted + self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 2) + self.asic_db.wait_for_n_keys(self.ASIC_NHS_STR, self.asic_nhs_count + 1) - rt_nhgid = None - rt_nhops = set() - rt_nhopsids = set() - rt_nhgmids = set() - rt_parentnhgid = set() - - # assert the route points to next hop group - for idx, rtkey in enumerate([rtkeys, defaultrtkeys]): - fvs = self.asic_db.get_entry(self.ASIC_RT_STR, rtkey[0]) + # Delete group1 + self.nhg_ps._del('group1') - if idx == 0: - rt_nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] - else: - default_nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] + # Wait for the group and it's members to be deleted + self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count) + self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count) - nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] + # The two next hops should also get deleted + self.asic_db.wait_for_n_keys(self.ASIC_NHS_STR, self.asic_nhs_count) - fvs = self.asic_db.get_entry(self.ASIC_NHG_STR, nhgid) + # Test scenario: + # - create a route with labeled and weighted NHs and assert a NHG and 2 NHs are created + # - create a NHG with the same details as the one being used by the route and assert a NHG is created and no + # new NHs are added + # - update the NHG by changing the first NH's label and assert a new NH is created + # - remove the route and assert that only one (now unreferenced) NH is removed + # - remove the NHG and perform cleanup + def routeorch_nhgorch_interop_test(): + # Create a route with labeled NHs + fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.1,10.0.0.3'), + ('mpls_nh', 'push1,push3'), + ('ifname', 'Ethernet0,Ethernet4'), + ('weight', '2,4')]) + self.rt_ps.set('2.2.2.0/24', fvs) + self.asic_db.wait_for_n_keys(self.ASIC_RT_STR, self.asic_rts_count + 1) - assert bool(fvs) + # A NHG should be created + self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count + 1) - keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - - assert len(keys) == 6 + # Two new next hops should be created + self.asic_db.wait_for_n_keys(self.ASIC_NHS_STR, self.asic_nhs_count + 2) - for k in keys: - fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) - nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] - nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) + # Create a NHG with the same details + self.nhg_ps.set('group1', fvs) + self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count + 2) - if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == rt_nhgid: - rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) - rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) - rt_nhgmids.add(k) - rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) - elif fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == default_nhgid: - default_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) - default_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) - default_nhgmids.add(k) - default_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) + # No new next hops should be created + assert len(self.asic_db.get_keys(self.ASIC_NHS_STR)) == self.asic_nhs_count + 2 - assert len(rt_parentnhgid) == 1 - assert len(rt_nhopsids) == 3 - assert len(rt_nhops) == 3 - assert len(rt_nhgmids) == 3 + # Update the group with a different NH + fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.1,10.0.0.3'), + ('mpls_nh', 'push2,push3'), + ('ifname', 'Ethernet0,Ethernet4'), + ('weight', '2,4')]) + self.nhg_ps.set('group1', fvs) - assert len(default_parentnhgid) == 1 - assert len(default_nhopsids) == 3 - assert len(default_nhops) == 3 - assert len(default_nhgmids) == 3 + # A new next hop should be created + self.asic_db.wait_for_n_keys(self.ASIC_NHS_STR, self.asic_nhs_count + 3) - assert rt_nhops != default_nhops - assert rt_nhopsids != default_nhopsids - assert rt_parentnhgid != default_parentnhgid - assert rt_nhgmids != default_nhgmids - - # bring links down one-by-one - for i in [0, 1, 2]: - self.flap_intf(i, 'down') - time.sleep(1) + # group1 should be updated and a new NHG shouldn't be created + time.sleep(1) + assert len(self.asic_db.get_keys(self.ASIC_NHG_STR)) == self.asic_nhgs_count + 2 - keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + # Remove the route + self.rt_ps._del('2.2.2.0/24') + self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count + 1) - if i != 2: - assert len(keys) == 5 - i - else: - # Last Link down so we will fallback to default eoute 3 members - assert len(keys) == 6 - rt_nhops.clear() - rt_nhgmids.clear() - rt_nhopsids.clear() - for k in keys: - fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) - nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] - nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) + # One NH should become unreferenced and should be deleted. The other + # one is still referenced by NhgOrch's owned NHG. + self.asic_db.wait_for_n_keys(self.ASIC_NHS_STR, self.asic_nhs_count + 2) - if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == rt_nhgid: - rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) - rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) - rt_nhgmids.add(k) - rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) - elif fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == default_nhgid: - default_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) - default_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) - default_nhgmids.add(k) - default_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) + # Remove the group + self.nhg_ps._del('group1') + self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count) + + # Both new next hops should be deleted + self.asic_db.wait_for_n_keys(self.ASIC_NHS_STR, self.asic_nhs_count) + + self.init_test(dvs, 2) + + mainline_labeled_nhs_test() + routeorch_nhgorch_interop_test() + + def test_nhgorch_excp_group_cases(self, dvs, testlog): + # Test scenario: + # - remove a NHG that does not exist and assert the number of NHGs in ASIC DB remains the same + def remove_inexistent_nhg_test(): + # Remove a group that does not exist + self.nhg_ps._del("group1") + time.sleep(1) + assert len(self.asic_db.get_keys(self.ASIC_NHG_STR)) == self.asic_nhgs_count + + # Test scenario: + # - create a NHG with a member which does not exist and assert no NHG is created + # - update the NHG to contain all valid members and assert the NHG is created and it has 2 members + def nhg_members_validation_test(): + # Create a next hop group with a member that does not exist - should fail + fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.1,10.0.0.3,10.0.0.63'), + ("ifname", "Ethernet0,Ethernet4,Ethernet124")]) + self.nhg_ps.set("group1", fvs) + time.sleep(1) + assert len(self.asic_db.get_keys(self.ASIC_NHG_STR)) == self.asic_nhgs_count + + # Issue an update for this next hop group that doesn't yet exist, + # which contains only valid NHs. This will overwrite the previous + # operation and create the group. + fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.1,10.0.0.5'), + ("ifname", "Ethernet0,Ethernet8")]) + self.nhg_ps.set("group1", fvs) + self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count + 1) + self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 2) - assert len(rt_parentnhgid) == 1 - assert len(rt_nhopsids) == 3 - assert len(rt_nhops) == 3 - assert len(rt_nhgmids) == 3 + # Check the group has its two members + assert len(self.get_nhgm_ids('group1')) == 2 - assert len(default_parentnhgid) == 1 - assert len(default_nhopsids) == 3 - assert len(default_nhops) == 3 - assert len(default_nhgmids) == 3 + # Test scenario: + # - create a route pointing to the NHG created in `test_nhg_members_validation` and assert it is being created + # - remove the NHG and assert it fails as it is being referenced + # - create a new NHG and assert it and its members are being created + # - update the route to point to the new NHG and assert the first NHG is now deleted as it's not referenced + # anymore + def remove_referenced_nhg_test(): + # Add a route referencing the new group + fvs = swsscommon.FieldValuePairs([('nexthop_group', 'group1')]) + self.rt_ps.set('2.2.2.0/24', fvs) + self.asic_db.wait_for_n_keys(self.ASIC_RT_STR, self.asic_rts_count + 1) - assert rt_nhops == default_nhops - assert rt_nhopsids == default_nhopsids - assert rt_nhgmids != default_nhgmids - - # bring links up one-by-one - # Bring link up in random order to verify sequence id is as per order - for i, val in enumerate([2,1,0]): - self.flap_intf(i, 'up') - time.sleep(1) + # Try removing the group while it still has references - should fail + self.nhg_ps._del('group1') + time.sleep(1) + assert len(self.asic_db.get_keys(self.ASIC_NHG_STR)) == self.asic_nhgs_count + 1 - keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - assert len(keys) == 6 + # Create a new group + fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.1,10.0.0.3'), + ('ifname', 'Ethernet0,Ethernet4')]) + self.nhg_ps.set("group2", fvs) + self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count + 2) + self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 4) - for k in keys: - fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) - nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] - nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) + # Update the route to point to the new group + fvs = swsscommon.FieldValuePairs([('nexthop_group', 'group2')]) + self.rt_ps.set('2.2.2.0/24', fvs) - if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == rt_nhgid: - rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) - rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) - rt_nhgmids.add(k) - rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) - elif fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == default_nhgid: - default_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) - default_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) - default_nhgmids.add(k) - default_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) + # The first group should have got deleted + self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count + 1) + self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 2) - assert len(rt_parentnhgid) == 1 - assert len(rt_nhopsids) == 3 - assert len(rt_nhops) == 3 - assert len(rt_nhgmids) == 3 + # The route's group should have changed to the new one + assert self.asic_db.get_entry(self.ASIC_RT_STR, self.get_route_id('2.2.2.0/24'))['SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID'] == self.get_nhg_id('group2') - assert len(default_parentnhgid) == 1 - assert len(default_nhopsids) == 3 - assert len(default_nhops) == 3 - assert len(default_nhgmids) == 3 + # Test scenario: + # - update the route created in `test_remove_referenced_nhg` to own the NHG with the same details as the + # previous one and assert a new NHG and 2 new NHGMs are added + # - update the route to point back to the original NHG and assert the routeOrch's owned NHG is deleted + def routeorch_nhgorch_interop_test(): + rt_id = self.get_route_id('2.2.2.0/24') + assert rt_id is not None - assert rt_nhops == default_nhops - assert rt_nhopsids == default_nhopsids - assert rt_nhgmids != default_nhgmids + # Update the route with routeOrch's owned next hop group + nhgid = self.asic_db.get_entry(self.ASIC_RT_STR, rt_id)['SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID'] + fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.1,10.0.0.3'), + ('ifname', 'Ethernet0,Ethernet4')]) + self.rt_ps.set('2.2.2.0/24', fvs) - # Remove route 2.2.2.0/24 - self.rt_ps._del(rtprefix) + self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count + 2) + self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 4) + + # Assert the next hop group ID changed time.sleep(1) + assert self.asic_db.get_entry(self.ASIC_RT_STR, rt_id)['SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID'] != nhgid + nhgid = self.asic_db.get_entry(self.ASIC_RT_STR, rt_id)['SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID'] - # Wait for route 2.2.2.0/24 to be removed - dvs_route.check_asicdb_deleted_route_entries([rtprefix]) + # Update the route to point back to group2 + fvs = swsscommon.FieldValuePairs([('nexthop_group', 'group2')]) + self.rt_ps.set('2.2.2.0/24', fvs) - keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + # The routeOrch's owned next hop group should get deleted + self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count + 1) + self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 2) - assert len(keys) == 3 + # Assert the route points back to group2 + assert self.asic_db.get_entry(self.ASIC_RT_STR, rt_id)['SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID'] != nhgid - # Remove route 0.0.0.0/0 - self.rt_ps._del(defaultprefix) - time.sleep(1) + # Test scenario: + # - create a new NHG with the same details as the previous NHG and assert a new NHG and 2 new NHGMs are created + # - update the route to point to the new NHG and assert its SAI NHG ID changes + def identical_nhgs_test(): + rt_id = self.get_route_id('2.2.2.0/24') + assert rt_id is not None - keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + # Create a new group with the same members as group2 + nhgid = self.asic_db.get_entry(self.ASIC_RT_STR, rt_id)['SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID'] + fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.1,10.0.0.3'), + ('ifname', 'Ethernet0,Ethernet4')]) + self.nhg_ps.set("group1", fvs) + self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count + 2) + self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 4) - assert len(keys) == 0 + # Update the route to point to the new group + fvs = swsscommon.FieldValuePairs([('nexthop_group', 'group1')]) + self.rt_ps.set('2.2.2.0/24', fvs) + time.sleep(1) - finally: - dvs.start_fpmsyncd() + # Assert the next hop group ID changed + assert self.asic_db.get_entry(self.ASIC_RT_STR, rt_id)['SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID'] != nhgid - @pytest.mark.parametrize('is_ipv6_needed', [False, True]) - def test_route_fallback_to_default_update(self, is_ipv6_needed, dvs, dvs_route, testlog): - self.init_test(dvs, 6, True) + # Test scenario: + # - create a route referencing a NHG that does not exist and assert it is not created + def create_route_inexistent_nhg_test(): + # Add a route with a NHG that does not exist + fvs = swsscommon.FieldValuePairs([('nexthop_group', 'group3')]) + self.rt_ps.set('2.2.3.0/24', fvs) + time.sleep(1) + assert self.get_route_id('2.2.3.0/24') is None - if is_ipv6_needed: - rtprefix = "2603:10b0::1/120" - defaultprefix = "::/0" - nexthop_str = "fc00::1,fc00::3,fc00::5" - default_nexthop_str = "fc00::7,fc00::9,fc00::b" - updated_default_nexthop_str = "fc00::7,fc00::b" - else: - rtprefix = "2.2.2.0/24" - defaultprefix = "0.0.0.0/0" - nexthop_str = "10.0.0.1,10.0.0.3,10.0.0.5" - default_nexthop_str = "10.0.0.7,10.0.0.9,10.0.0.11" - updated_default_nexthop_str = "10.0.0.7,10.0.0.11" - + # Remove the pending route + self.rt_ps._del('2.2.3.0/24') - dvs_route.check_asicdb_deleted_route_entries([rtprefix]) + self.init_test(dvs, 3) - try: - dvs.disable_fpmsyncd() + remove_inexistent_nhg_test() + nhg_members_validation_test() + remove_referenced_nhg_test() + routeorch_nhgorch_interop_test() + identical_nhgs_test() + create_route_inexistent_nhg_test() - # Program default route - fvs = swsscommon.FieldValuePairs([("nexthop", default_nexthop_str), - ("ifname", "Ethernet12,Ethernet16,Ethernet20")]) + # Cleanup - self.rt_ps.set(defaultprefix, fvs) - time.sleep(1) + # Remove the route + self.rt_ps._del('2.2.2.0/24') + self.asic_db.wait_for_n_keys(self.ASIC_RT_STR, self.asic_rts_count) - # check if default route was propagated to ASIC DB - defaultrtkeys = dvs_route.check_asicdb_route_entries([defaultprefix]) + # Remove the groups + self.nhg_ps._del('group1') + self.nhg_ps._del('group2') + self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count) + self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count) - fvs = self.asic_db.get_entry(self.ASIC_RT_STR, defaultrtkeys[0]) + def test_nhgorch_nh_group(self, dvs, testlog): + # Test scenario: + # - create recursive nhg - rec_grp1 with two members - grp1 and grp2 only one of which exists + # - create singleton nhg grp2 and check if the rec_grp1 is updated with both the members + # - create a recursive nhg - rec_grp2 with another recursive nhg - rec_grp1 as member. Assert that the nhg is not created. + def create_recursive_nhg_test(): + # create next hop group in APPL DB + fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.1'), ('ifname', 'Ethernet0')]) + self.nhg_ps.set("grp1", fvs) - nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] + # create a recursive nexthop group with two members + fvs = swsscommon.FieldValuePairs([('nexthop_group', 'grp1,grp2')]) + self.nhg_ps.set("rec_grp1", fvs) - fvs = self.asic_db.get_entry(self.ASIC_NHG_STR, nhgid) + # check if group was propagated to ASIC DB with the existing member + self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count + 1) + assert self.nhg_exists('rec_grp1') - assert bool(fvs) + # check if the existing member was propagated to ASIC DB + self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 1) + assert len(self.get_nhgm_ids('rec_grp1')) == 1 - keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - - assert len(keys) == 3 + # add another singleton nexthop group - grp2 + fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.3'), ('ifname', 'Ethernet4')]) + self.nhg_ps.set("grp2", fvs) + + # check if both the members were propagated to ASIC DB + self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 2) + assert len(self.get_nhgm_ids('rec_grp1')) == 2 + # update the recursive nexthop group with another member not yet existing + fvs = swsscommon.FieldValuePairs([('nexthop_group', 'grp1,grp2,grp3')]) + self.nhg_ps.set("rec_grp1", fvs) - # Program default route - fvs = swsscommon.FieldValuePairs([("nexthop", updated_default_nexthop_str), - ("ifname", "Ethernet12,Ethernet20")]) + # check if only two members were propagated to ASIC DB + self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 2) + assert len(self.get_nhgm_ids('rec_grp1')) == 2 + # add another singleton nexthop group - grp3 + fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.5'), ('ifname', 'Ethernet8')]) + self.nhg_ps.set("grp3", fvs) - self.rt_ps.set(defaultprefix, fvs) - time.sleep(1) + # check if all members were propagated to ASIC DB + self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 3) + assert len(self.get_nhgm_ids('rec_grp1')) == 3 - # check if default route was propagated to ASIC DB - defaultrtkeys = dvs_route.check_asicdb_route_entries([defaultprefix]) + # create a recursive nhg with another recursive nhg as member + fvs = swsscommon.FieldValuePairs([('nexthop_group', 'rec_grp1')]) + self.nhg_ps.set("rec_grp2", fvs) - fvs = self.asic_db.get_entry(self.ASIC_RT_STR, defaultrtkeys[0]) + # check that the group was not propagated to ASIC DB + self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count + 1) + assert not self.nhg_exists('rec_grp2') - nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] + self.nhg_ps._del("rec_grp2") + self.nhg_ps._del("rec_grp1") + self.nhg_ps._del("grp1") + self.nhg_ps._del("grp2") + self.nhg_ps._del("grp3") + self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count) + self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count) - fvs = self.asic_db.get_entry(self.ASIC_NHG_STR, nhgid) + # Test scenario: + # - create NHG 'group1' and assert it is being added to ASIC DB along with its members + def create_nhg_test(): + # create next hop group in APPL DB + fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.1,10.0.0.3,10.0.0.5'), + ("ifname", "Ethernet0,Ethernet4,Ethernet8")]) + self.nhg_ps.set("group1", fvs) - assert bool(fvs) + # check if group was propagated to ASIC DB + self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count + 1) + assert self.nhg_exists('group1') - keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - - assert len(keys) == 2 + # check if members were propagated to ASIC DB + self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 3) + assert len(self.get_nhgm_ids('group1')) == 3 - # Program Regular Rouute with fallback to default - fvs = swsscommon.FieldValuePairs([("nexthop", nexthop_str), - ("ifname", "Ethernet0,Ethernet4,Ethernet8"), - ("fallback_to_default_route", "true")]) - self.rt_ps.set(rtprefix, fvs) - time.sleep(1) + # Test scenario: + # - create a route pointing to `group1` and assert it is being added to ASIC DB and pointing to its SAI ID + # - delete the route and assert it is being removed + def create_route_nhg_test(): + # create route in APPL DB + fvs = swsscommon.FieldValuePairs([("nexthop_group", "group1")]) + self.rt_ps.set("2.2.2.0/24", fvs) # check if route was propagated to ASIC DB - rtkeys = dvs_route.check_asicdb_route_entries([rtprefix]) - + self.asic_db.wait_for_n_keys(self.ASIC_RT_STR, self.asic_rts_count + 1) - default_nhgid = None - default_nhops = set() - default_nhgmids = set() - default_nhopsids = set() - default_parentnhgid = set() + k = self.get_route_id('2.2.2.0/24') + assert k is not None - rt_nhgid = None - rt_nhops = set() - rt_nhopsids = set() - rt_nhgmids = set() - rt_parentnhgid = set() - # assert the route points to next hop group - for idx, rtkey in enumerate([rtkeys, defaultrtkeys]): - fvs = self.asic_db.get_entry(self.ASIC_RT_STR, rtkey[0]) + fvs = self.asic_db.get_entry(self.ASIC_RT_STR, k) + assert fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] == self.get_nhg_id('group1') - if idx == 0: - rt_nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] - else: - default_nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] + # Remove route 2.2.2.0/24 + self.rt_ps._del("2.2.2.0/24") + self.asic_db.wait_for_n_keys(self.ASIC_RT_STR, self.asic_rts_count) - nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] + # Test scenario: + # - bring the links down one by one and assert the group1's members are subsequently removed and the group + # still exists + # - bring the liks up one by one and assert the group1's members are subsequently added back + def link_flap_test(): + # bring links down one-by-one + for i in [0, 1, 2]: + self.flap_intf(i, 'down') + self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 2 - i) + assert len(self.get_nhgm_ids('group1')) == 2 - i + assert self.nhg_exists('group1') - fvs = self.asic_db.get_entry(self.ASIC_NHG_STR, nhgid) + # bring links up one-by-one + for i in [0, 1, 2]: + self.flap_intf(i, 'up') + self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + i + 1) + assert len(self.get_nhgm_ids('group1')) == i + 1 - assert bool(fvs) + # Test scenario: + # - bring a link down and assert a NHGM of `group1` is removed + # - create NHG `group2` which has a member pointing to the link being down and assert the group gets created + # but the member referencing the link is not added + # - update `group1` by removing a member while having another member referencing the link which is down and + # assert it'll only have a member added in ASIC DB + # - bring the link back up and assert the missing 2 members of `group1` and `group2` are added + # - remove `group2` and assert it and its members are removed + def validate_invalidate_group_member_test(): + # Bring an interface down + self.flap_intf(1, 'down') - keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - - assert len(keys) == 5 + # One group member will get deleted + self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 2) - for k in keys: - fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) - nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] - nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) + # Create a group that contains a NH that uses the down link + fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.1,10.0.0.3'), + ("ifname", "Ethernet0,Ethernet4")]) + self.nhg_ps.set('group2', fvs) - if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == rt_nhgid: - rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) - rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) - rt_nhgmids.add(k) - rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) - elif fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == default_nhgid: - default_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) - default_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) - default_nhgmids.add(k) - default_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) + # The group should get created, but it will not contained the NH that + # has the link down + self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count + 2) + self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 3) + assert len(self.get_nhgm_ids('group2')) == 1 - assert len(rt_parentnhgid) == 1 - assert len(rt_nhopsids) == 3 - assert len(rt_nhops) == 3 - assert len(rt_nhgmids) == 3 + # Update the NHG with one interface down + fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.3,10.0.0.1'), + ("ifname", "Ethernet4,Ethernet0")]) + self.nhg_ps.set("group1", fvs) - assert len(default_parentnhgid) == 1 - assert len(default_nhopsids) == 2 - assert len(default_nhops) == 2 - assert len(default_nhgmids) == 2 + # Wait for group members to update - the group will contain only the + # members that have their links up + self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 2) + assert len(self.get_nhgm_ids('group1')) == 1 - assert rt_nhops != default_nhops - assert rt_nhopsids != default_nhopsids - assert rt_parentnhgid != default_parentnhgid - assert rt_nhgmids != default_nhgmids - - # bring links down one-by-one - for i in [0, 1, 2]: - self.flap_intf(i, 'down') - time.sleep(1) + # Bring the interface up + self.flap_intf(1, 'up') - keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + # Check that the missing member of group1 and group2 is being added + self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 4) - if i != 2: - assert len(keys) == 4 - i - else: - # Last Link down so we will fallback to default eoute 3 members - assert len(keys) == 4 - rt_nhops.clear() - rt_nhgmids.clear() - rt_nhopsids.clear() - for k in keys: - fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) - nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] - nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) + # Remove group2 + self.nhg_ps._del('group2') + self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count + 1) + self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 2) - if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == rt_nhgid: - rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) - rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) - rt_nhgmids.add(k) - rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) - elif fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == default_nhgid: - default_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) - default_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) - default_nhgmids.add(k) - default_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) + # Test scenario: + # - create NHG `group2` with a NH that does not exist and assert it isn't created + # - update `group1` to contain the invalid NH and assert it remains only with the unremoved members + # - configure the invalid NH's interface and assert `group2` gets created and `group1`'s NH is added + # - delete `group` and assert it is being removed + def inexistent_group_member_test(): + # Create group2 with a NH that does not exist + fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.3,10.0.0.63'), + ("ifname", "Ethernet4,Ethernet124")]) + self.nhg_ps.set("group2", fvs) - assert len(rt_parentnhgid) == 1 - assert len(rt_nhopsids) == 2 - assert len(rt_nhops) == 2 - assert len(rt_nhgmids) == 2 + # The groups should not be created + time.sleep(1) + self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count + 1) - assert len(default_parentnhgid) == 1 - assert len(default_nhopsids) == 2 - assert len(default_nhops) == 2 - assert len(default_nhgmids) == 2 + # Update group1 with a NH that does not exist + fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.3,10.0.0.63'), + ("ifname", "Ethernet4,Ethernet124")]) + self.nhg_ps.set("group1", fvs) - assert rt_nhops == default_nhops - assert rt_nhopsids == default_nhopsids - assert rt_nhgmids != default_nhgmids - - # bring links up one-by-one - # Bring link up in random order to verify sequence id is as per order - for i, val in enumerate([2,1,0]): - self.flap_intf(i, 'up') - time.sleep(1) + # The update should fail, leaving group1 with only the unremoved + # members + self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 1) + assert len(self.get_nhgm_ids('group1')) == 1 - keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - assert len(keys) == 4 + # Configure the missing NH's interface + self.config_intf(31) - for k in keys: - fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) - nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] - nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) + # A couple more routes will be added to ASIC DB + self.asic_rts_count += 2 - if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == rt_nhgid: - rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) - rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) - rt_nhgmids.add(k) - rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) - elif fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == default_nhgid: - default_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) - default_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) - default_nhgmids.add(k) - default_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) + # Group2 should get created and group1 should be updated + self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count + 2) + self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 4) + assert len(self.get_nhgm_ids('group1')) == 2 + assert len(self.get_nhgm_ids('group2')) == 2 - assert len(rt_parentnhgid) == 1 - assert len(rt_nhopsids) == 2 - assert len(rt_nhops) == 2 - assert len(rt_nhgmids) == 2 + # Delete group2 + self.nhg_ps._del('group2') + self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count + 1) + + # Test scenario: + # - update `group1` to have 4 members and assert they are all added + # - update `group1` to have only 1 member and assert the other 3 are removed + # - update `group1` to have 2 members and assert a new one is added + def update_nhgm_count_test(): + # Update the NHG, adding two new members + fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.1,10.0.0.3,10.0.0.5,10.0.0.7'), + ("ifname", "Ethernet0,Ethernet4,Ethernet8,Ethernet12")]) + self.nhg_ps.set("group1", fvs) + self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 4) + assert len(self.get_nhgm_ids('group1')) == 4 + + # Update the group to one NH only + fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.1'), ("ifname", "Ethernet0")]) + self.nhg_ps.set("group1", fvs) + self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count) + assert len(self.get_nhgm_ids('group1')) == 0 - assert len(default_parentnhgid) == 1 - assert len(default_nhopsids) == 2 - assert len(default_nhops) == 2 - assert len(default_nhgmids) == 2 + # Update the group to 2 NHs + fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.1,10.0.0.3'), ("ifname", "Ethernet0,Ethernet4")]) + self.nhg_ps.set("group1", fvs) + self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 2) + assert len(self.get_nhgm_ids('group1')) == 2 - assert rt_nhops == default_nhops - assert rt_nhopsids == default_nhopsids - assert rt_nhgmids != default_nhgmids + self.init_test(dvs, 4) - # Remove route 2.2.2.0/24 - self.rt_ps._del(rtprefix) - time.sleep(1) + create_recursive_nhg_test() + create_nhg_test() + create_route_nhg_test() + link_flap_test() + validate_invalidate_group_member_test() + inexistent_group_member_test() + update_nhgm_count_test() - # Wait for route 2.2.2.0/24 to be removed - dvs_route.check_asicdb_deleted_route_entries([rtprefix]) + # Cleanup - keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + # Remove group1 + self.nhg_ps._del("group1") + self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count) - assert len(keys) == 2 + def test_nhgorch_label_route(self, dvs, testlog): + self.init_test(dvs, 4) - # Remove route 0.0.0.0/0 - self.rt_ps._del(defaultprefix) - time.sleep(1) + # create next hop group in APPL DB + fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.1,10.0.0.3,10.0.0.5'), + ("ifname", "Ethernet0,Ethernet4,Ethernet8")]) + self.nhg_ps.set("group1", fvs) + self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count + 1) + self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 3) - keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + # create label route in APPL DB pointing to the NHG + fvs = swsscommon.FieldValuePairs([("nexthop_group", "group1")]) + self.lr_ps.set("20", fvs) + self.asic_db.wait_for_n_keys(self.ASIC_INSEG_STR, self.asic_insgs_count + 1) - assert len(keys) == 0 + k = self.get_inseg_id('20') + assert k is not None - finally: - dvs.start_fpmsyncd() + # assert the route points to next hop group + fvs = self.asic_db.get_entry(self.ASIC_INSEG_STR, k) + assert fvs["SAI_INSEG_ENTRY_ATTR_NEXT_HOP_ID"] == self.get_nhg_id('group1') + + # Remove label route 20 + self.lr_ps._del("20") + self.asic_db.wait_for_n_keys(self.ASIC_INSEG_STR, self.asic_insgs_count) + # Remove group1 + self.nhg_ps._del("group1") + self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count) + @pytest.mark.parametrize('is_ipv6_needed', [False, True]) - def test_route_fallback_to_default_negative(self, is_ipv6_needed, dvs, dvs_route, testlog): + def test_route_fallback_to_default(self, is_ipv6_needed, dvs, dvs_route, testlog): self.init_test(dvs, 6, True) if is_ipv6_needed: rtprefix = "2603:10b0::1/120" + defaultprefix = "::/0" nexthop_str = "fc00::1,fc00::3,fc00::5" + default_nexthop_str = "fc00::7,fc00::9,fc00::b" else: rtprefix = "2.2.2.0/24" + defaultprefix = "0.0.0.0/0" nexthop_str = "10.0.0.1,10.0.0.3,10.0.0.5" + default_nexthop_str = "10.0.0.7,10.0.0.9,10.0.0.11" dvs_route.check_asicdb_deleted_route_entries([rtprefix]) @@ -1689,125 +1866,6 @@ def test_route_fallback_to_default_negative(self, is_ipv6_needed, dvs, dvs_route self.rt_ps.set(rtprefix, fvs) time.sleep(1) - - # check if route was propagated to ASIC DB - rtkeys = dvs_route.check_asicdb_route_entries([rtprefix]) - - rt_nhgid = None - rt_nhops = set() - rt_nhopsids = set() - rt_nhgmids = set() - rt_parentnhgid = set() - - # assert the route points to next hop group - for rtkey in ([rtkeys]): - fvs = self.asic_db.get_entry(self.ASIC_RT_STR, rtkey[0]) - - rt_nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] - nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] - - fvs = self.asic_db.get_entry(self.ASIC_NHG_STR, nhgid) - - assert bool(fvs) - - keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - - assert len(keys) == 3 - - for k in keys: - fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) - nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] - nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) - - if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == rt_nhgid: - rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) - rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) - rt_nhgmids.add(k) - rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) - - assert len(rt_parentnhgid) == 1 - assert len(rt_nhopsids) == 3 - assert len(rt_nhops) == 3 - assert len(rt_nhgmids) == 3 - - # bring links down one-by-one - for i in [0, 1, 2]: - self.flap_intf(i, 'down') - time.sleep(1) - - keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - assert len(keys) == 2 - i - - # bring links up one-by-one - # Bring link up in random order to verify sequence id is as per order - for i, val in enumerate([2,1,0]): - self.flap_intf(i, 'up') - time.sleep(1) - keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - assert len(keys) == i + 1 - - rt_nhops.clear() - rt_nhgmids.clear() - rt_nhopsids.clear() - - for k in keys: - fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) - nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] - nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) - - if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == rt_nhgid: - rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) - rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) - rt_nhgmids.add(k) - rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) - - assert len(rt_parentnhgid) == 1 - assert len(rt_nhopsids) == 3 - assert len(rt_nhops) == 3 - assert len(rt_nhgmids) == 3 - - # Remove route 2.2.2.0/24 - self.rt_ps._del(rtprefix) - time.sleep(1) - - # Wait for route 2.2.2.0/24 to be removed - dvs_route.check_asicdb_deleted_route_entries([rtprefix]) - - keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - - assert len(keys) == 0 - - finally: - dvs.start_fpmsyncd() - - def test_route_fallback_to_default_bothv4v6(self, dvs, dvs_route, testlog): - self.init_test(dvs, 6, True) - rtprefix_v6 = "2603:10b0::1/120" - defaultprefix_v6 = "::/0" - nexthop_str_v6 = "fc00::1,fc00::3,fc00::5" - default_nexthop_str_v6 = "fc00::7,fc00::9,fc00::b" - rtprefix = "2.2.2.0/24" - defaultprefix = "0.0.0.0/0" - nexthop_str = "10.0.0.1,10.0.0.3,10.0.0.5" - default_nexthop_str = "10.0.0.7,10.0.0.9,10.0.0.11" - - dvs_route.check_asicdb_deleted_route_entries([rtprefix, rtprefix_v6]) - - try: - dvs.disable_fpmsyncd() - # Program Regular Rouute with fallback to default - fvs = swsscommon.FieldValuePairs([("nexthop",nexthop_str), - ("ifname", "Ethernet0,Ethernet4,Ethernet8"), - ("fallback_to_default_route", "true")]) - self.rt_ps.set(rtprefix, fvs) - time.sleep(1) - - fvs = swsscommon.FieldValuePairs([("nexthop",nexthop_str_v6), - ("ifname", "Ethernet0,Ethernet4,Ethernet8"), - ("fallback_to_default_route", "true")]) - self.rt_ps.set(rtprefix_v6, fvs) - time.sleep(1) - # Program default route fvs = swsscommon.FieldValuePairs([("nexthop", default_nexthop_str), ("ifname", "Ethernet12,Ethernet16,Ethernet20")]) @@ -1815,42 +1873,32 @@ def test_route_fallback_to_default_bothv4v6(self, dvs, dvs_route, testlog): self.rt_ps.set(defaultprefix, fvs) time.sleep(1) - fvs = swsscommon.FieldValuePairs([("nexthop", default_nexthop_str_v6), - ("ifname", "Ethernet12,Ethernet16,Ethernet20")]) - - self.rt_ps.set(defaultprefix_v6, fvs) - time.sleep(1) - # check if route was propagated to ASIC DB - rtkeys = dvs_route.check_asicdb_route_entries([rtprefix, rtprefix_v6]) + rtkeys = dvs_route.check_asicdb_route_entries([rtprefix]) # check if default route was propagated to ASIC DB - defaultrtkeys = dvs_route.check_asicdb_route_entries([defaultprefix, defaultprefix_v6]) + defaultrtkeys = dvs_route.check_asicdb_route_entries([defaultprefix]) - default_nhgid = [] + default_nhgid = None default_nhops = set() default_nhgmids = set() default_nhopsids = set() default_parentnhgid = set() - rt_nhgid = [] + rt_nhgid = None rt_nhops = set() rt_nhopsids = set() rt_nhgmids = set() rt_parentnhgid = set() - + # assert the route points to next hop group - flat_list = [] - for x in rtkeys: - flat_list.append(x) - for x in defaultrtkeys: - flat_list.append(x) - for idx, rtkey in enumerate(flat_list): - fvs = self.asic_db.get_entry(self.ASIC_RT_STR, rtkey) - if idx in [0,1]: - rt_nhgid.append(fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"]) + for idx, rtkey in enumerate([rtkeys, defaultrtkeys]): + fvs = self.asic_db.get_entry(self.ASIC_RT_STR, rtkey[0]) + + if idx == 0: + rt_nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] else: - default_nhgid.append(fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"]) + default_nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] @@ -1860,33 +1908,33 @@ def test_route_fallback_to_default_bothv4v6(self, dvs, dvs_route, testlog): keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - assert len(keys) == 12 + assert len(keys) == 6 for k in keys: fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) - if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] in rt_nhgid: + if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == rt_nhgid: rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) rt_nhgmids.add(k) rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) - elif fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] in default_nhgid: + elif fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == default_nhgid: default_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) default_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) default_nhgmids.add(k) default_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) - assert len(rt_parentnhgid) == 2 - assert len(rt_nhopsids) == 6 - assert len(rt_nhops) == 6 - assert len(rt_nhgmids) == 6 + assert len(rt_parentnhgid) == 1 + assert len(rt_nhopsids) == 3 + assert len(rt_nhops) == 3 + assert len(rt_nhgmids) == 3 - assert len(default_parentnhgid) == 2 - assert len(default_nhopsids) == 6 - assert len(default_nhops) == 6 - assert len(default_nhgmids) == 6 + assert len(default_parentnhgid) == 1 + assert len(default_nhopsids) == 3 + assert len(default_nhops) == 3 + assert len(default_nhgmids) == 3 assert rt_nhops != default_nhops assert rt_nhopsids != default_nhopsids @@ -1901,10 +1949,10 @@ def test_route_fallback_to_default_bothv4v6(self, dvs, dvs_route, testlog): keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) if i != 2: - assert len(keys) == 10 - (i * 2) + assert len(keys) == 5 - i else: # Last Link down so we will fallback to default eoute 3 members - assert len(keys) == 12 + assert len(keys) == 6 rt_nhops.clear() rt_nhgmids.clear() rt_nhopsids.clear() @@ -1913,26 +1961,26 @@ def test_route_fallback_to_default_bothv4v6(self, dvs, dvs_route, testlog): nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) - if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] in rt_nhgid: + if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == rt_nhgid: rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) rt_nhgmids.add(k) rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) - elif fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] in default_nhgid: + elif fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == default_nhgid: default_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) default_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) default_nhgmids.add(k) - default_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) - - assert len(rt_parentnhgid) == 2 - assert len(rt_nhopsids) == 6 - assert len(rt_nhops) == 6 - assert len(rt_nhgmids) == 6 + default_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) - assert len(default_parentnhgid) == 2 - assert len(default_nhopsids) == 6 - assert len(default_nhops) == 6 - assert len(default_nhgmids) == 6 + assert len(rt_parentnhgid) == 1 + assert len(rt_nhopsids) == 3 + assert len(rt_nhops) == 3 + assert len(rt_nhgmids) == 3 + + assert len(default_parentnhgid) == 1 + assert len(default_nhopsids) == 3 + assert len(default_nhops) == 3 + assert len(default_nhgmids) == 3 assert rt_nhops == default_nhops assert rt_nhopsids == default_nhopsids @@ -1945,34 +1993,33 @@ def test_route_fallback_to_default_bothv4v6(self, dvs, dvs_route, testlog): time.sleep(1) keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - - assert len(keys) == 12 + assert len(keys) == 6 for k in keys: fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) - if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] in rt_nhgid: + if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == rt_nhgid: rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) rt_nhgmids.add(k) rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) - elif fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] in default_nhgid: + elif fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == default_nhgid: default_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) default_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) default_nhgmids.add(k) default_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) - assert len(rt_parentnhgid) == 2 - assert len(rt_nhopsids) == 6 - assert len(rt_nhops) == 6 - assert len(rt_nhgmids) == 6 + assert len(rt_parentnhgid) == 1 + assert len(rt_nhopsids) == 3 + assert len(rt_nhops) == 3 + assert len(rt_nhgmids) == 3 - assert len(default_parentnhgid) == 2 - assert len(default_nhopsids) == 6 - assert len(default_nhops) == 6 - assert len(default_nhgmids) == 6 + assert len(default_parentnhgid) == 1 + assert len(default_nhopsids) == 3 + assert len(default_nhops) == 3 + assert len(default_nhgmids) == 3 assert rt_nhops == default_nhops assert rt_nhopsids == default_nhopsids @@ -1982,23 +2029,17 @@ def test_route_fallback_to_default_bothv4v6(self, dvs, dvs_route, testlog): self.rt_ps._del(rtprefix) time.sleep(1) - self.rt_ps._del(rtprefix_v6) - time.sleep(1) - # Wait for route 2.2.2.0/24 to be removed - dvs_route.check_asicdb_deleted_route_entries([rtprefix, rtprefix_v6]) + dvs_route.check_asicdb_deleted_route_entries([rtprefix]) keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - assert len(keys) == 6 + assert len(keys) == 3 # Remove route 0.0.0.0/0 self.rt_ps._del(defaultprefix) time.sleep(1) - self.rt_ps._del(defaultprefix_v6) - time.sleep(1) - keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) assert len(keys) == 0 @@ -2006,633 +2047,592 @@ def test_route_fallback_to_default_bothv4v6(self, dvs, dvs_route, testlog): finally: dvs.start_fpmsyncd() + @pytest.mark.parametrize('is_ipv6_needed', [False, True]) + def test_route_fallback_to_default_update(self, is_ipv6_needed, dvs, dvs_route, testlog): + self.init_test(dvs, 6, True) - def test_label_route_nhg(self, dvs, testlog): - self.init_test(dvs, 3) - - # add label route - fvs = swsscommon.FieldValuePairs([("nexthop","10.0.0.1,10.0.0.3,10.0.0.5"), - ("ifname", "Ethernet0,Ethernet4,Ethernet8")]) - self.lr_ps.set("10", fvs) - self.asic_db.wait_for_n_keys(self.ASIC_INSEG_STR, self.asic_insgs_count + 1) - self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count + 1) - self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 3) - - k = self.get_inseg_id('10') - assert k is not None - - # assert the route points to next hop group - fvs = self.asic_db.get_entry(self.ASIC_INSEG_STR, k) - nhgid = fvs["SAI_INSEG_ENTRY_ATTR_NEXT_HOP_ID"] - fvs = self.asic_db.get_entry(self.ASIC_NHG_STR, nhgid) - assert bool(fvs) - - keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - assert len(keys) == 3 - for k in keys: - fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) - assert fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == nhgid - - # bring links down one-by-one - for i in [0, 1, 2]: - self.flap_intf(i, 'down') - keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - assert len(keys) == 2 - i + if is_ipv6_needed: + rtprefix = "2603:10b0::1/120" + defaultprefix = "::/0" + nexthop_str = "fc00::1,fc00::3,fc00::5" + default_nexthop_str = "fc00::7,fc00::9,fc00::b" + updated_default_nexthop_str = "fc00::7,fc00::b" + else: + rtprefix = "2.2.2.0/24" + defaultprefix = "0.0.0.0/0" + nexthop_str = "10.0.0.1,10.0.0.3,10.0.0.5" + default_nexthop_str = "10.0.0.7,10.0.0.9,10.0.0.11" + updated_default_nexthop_str = "10.0.0.7,10.0.0.11" + - # bring links up one-by-one - for i in [0, 1, 2]: - self.flap_intf(i, 'up') - keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - assert len(keys) == i + 1 - for k in keys: - fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) - assert fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == nhgid + dvs_route.check_asicdb_deleted_route_entries([rtprefix]) - # Remove label route 10 - self.lr_ps._del("10") + try: + dvs.disable_fpmsyncd() - # Wait for label route 10 to be removed - self.asic_db.wait_for_n_keys(self.ASIC_INSEG_STR, self.asic_insgs_count) - self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count) + # Program default route + fvs = swsscommon.FieldValuePairs([("nexthop", default_nexthop_str), + ("ifname", "Ethernet12,Ethernet16,Ethernet20")]) - def test_nhgorch_labeled_nhs(self, dvs, testlog): - # Test scenario: - # - create a NHG with all labeled and weighted NHs and assert 2 new NHs are created - # - create a NHG with an existing label and assert no new NHs are created - # - create a NHG with a new label and assert a new NH is created - # - remove the third NHG and assert the NH is deleted - # - delete the second group and assert no NH is deleted because it is still referenced by the first group - # - remove the weights from the first NHG and change the labels, leaving one NH unlabeled; assert one NH is - # deleted - # - delete the first NHG and perform cleanup - def mainline_labeled_nhs_test(): - # Add a group containing labeled weighted NHs - fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.1,10.0.0.3'), - ('mpls_nh', 'push1,push3'), - ('ifname', 'Ethernet0,Ethernet4'), - ('weight', '2,4')]) - self.nhg_ps.set('group1', fvs) - self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count + 1) - self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 2) + self.rt_ps.set(defaultprefix, fvs) + time.sleep(1) - # NhgOrch should create two next hops for the labeled ones - self.asic_db.wait_for_n_keys(self.ASIC_NHS_STR, self.asic_nhs_count + 2) + # check if default route was propagated to ASIC DB + defaultrtkeys = dvs_route.check_asicdb_route_entries([defaultprefix]) - # Assert the weights are properly set - nhgm_ids = self.get_nhgm_ids('group1') - weights = [] - for k in nhgm_ids: - fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) - weights.append(fvs['SAI_NEXT_HOP_GROUP_MEMBER_ATTR_WEIGHT']) - assert set(weights) == set(['2', '4']) + fvs = self.asic_db.get_entry(self.ASIC_RT_STR, defaultrtkeys[0]) - # Create a new single next hop with the same label - fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.1'), - ('mpls_nh', 'push1'), - ('ifname', 'Ethernet0')]) - self.nhg_ps.set('group2', fvs) + nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] - # No new next hop should be added - time.sleep(1) - assert len(self.asic_db.get_keys(self.ASIC_NHS_STR)) == self.asic_nhs_count + 2 + fvs = self.asic_db.get_entry(self.ASIC_NHG_STR, nhgid) - # Create a new single next hop with a different label - fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.1'), - ('mpls_nh', 'push2'), - ('ifname', 'Ethernet0')]) - self.nhg_ps.set('group3', fvs) + assert bool(fvs) - # A new next hop should be added - self.asic_db.wait_for_n_keys(self.ASIC_NHS_STR, self.asic_nhs_count + 3) + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + + assert len(keys) == 3 - # Delete group3 - self.nhg_ps._del('group3') - # Group3's NH should be deleted - self.asic_db.wait_for_n_keys(self.ASIC_NHS_STR, self.asic_nhs_count + 2) + # Program default route + fvs = swsscommon.FieldValuePairs([("nexthop", updated_default_nexthop_str), + ("ifname", "Ethernet12,Ethernet20")]) - # Delete group2 - self.nhg_ps._del('group2') - # The number of NHs should be the same as they are still referenced by - # group1 + self.rt_ps.set(defaultprefix, fvs) time.sleep(1) - self.asic_db.wait_for_n_keys(self.ASIC_NHS_STR, self.asic_nhs_count + 2) - - # Update group1 with no weights and both labeled and unlabeled NHs - fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.1,10.0.0.3'), - ('mpls_nh', 'push2,na'), - ('ifname', 'Ethernet0,Ethernet4')]) - self.nhg_ps.set('group1', fvs) - # Group members should be replaced and one NH should get deleted - self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 2) - self.asic_db.wait_for_n_keys(self.ASIC_NHS_STR, self.asic_nhs_count + 1) + # check if default route was propagated to ASIC DB + defaultrtkeys = dvs_route.check_asicdb_route_entries([defaultprefix]) - # Delete group1 - self.nhg_ps._del('group1') + fvs = self.asic_db.get_entry(self.ASIC_RT_STR, defaultrtkeys[0]) - # Wait for the group and it's members to be deleted - self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count) - self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count) + nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] - # The two next hops should also get deleted - self.asic_db.wait_for_n_keys(self.ASIC_NHS_STR, self.asic_nhs_count) + fvs = self.asic_db.get_entry(self.ASIC_NHG_STR, nhgid) - # Test scenario: - # - create a route with labeled and weighted NHs and assert a NHG and 2 NHs are created - # - create a NHG with the same details as the one being used by the route and assert a NHG is created and no - # new NHs are added - # - update the NHG by changing the first NH's label and assert a new NH is created - # - remove the route and assert that only one (now unreferenced) NH is removed - # - remove the NHG and perform cleanup - def routeorch_nhgorch_interop_test(): - # Create a route with labeled NHs - fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.1,10.0.0.3'), - ('mpls_nh', 'push1,push3'), - ('ifname', 'Ethernet0,Ethernet4'), - ('weight', '2,4')]) - self.rt_ps.set('2.2.2.0/24', fvs) - self.asic_db.wait_for_n_keys(self.ASIC_RT_STR, self.asic_rts_count + 1) + assert bool(fvs) - # A NHG should be created - self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count + 1) + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + + assert len(keys) == 2 - # Two new next hops should be created - self.asic_db.wait_for_n_keys(self.ASIC_NHS_STR, self.asic_nhs_count + 2) + # Program Regular Rouute with fallback to default + fvs = swsscommon.FieldValuePairs([("nexthop", nexthop_str), + ("ifname", "Ethernet0,Ethernet4,Ethernet8"), + ("fallback_to_default_route", "true")]) + self.rt_ps.set(rtprefix, fvs) + time.sleep(1) - # Create a NHG with the same details - self.nhg_ps.set('group1', fvs) - self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count + 2) + # check if route was propagated to ASIC DB + rtkeys = dvs_route.check_asicdb_route_entries([rtprefix]) + - # No new next hops should be created - assert len(self.asic_db.get_keys(self.ASIC_NHS_STR)) == self.asic_nhs_count + 2 + default_nhgid = None + default_nhops = set() + default_nhgmids = set() + default_nhopsids = set() + default_parentnhgid = set() - # Update the group with a different NH - fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.1,10.0.0.3'), - ('mpls_nh', 'push2,push3'), - ('ifname', 'Ethernet0,Ethernet4'), - ('weight', '2,4')]) - self.nhg_ps.set('group1', fvs) + rt_nhgid = None + rt_nhops = set() + rt_nhopsids = set() + rt_nhgmids = set() + rt_parentnhgid = set() + + # assert the route points to next hop group + for idx, rtkey in enumerate([rtkeys, defaultrtkeys]): + fvs = self.asic_db.get_entry(self.ASIC_RT_STR, rtkey[0]) - # A new next hop should be created - self.asic_db.wait_for_n_keys(self.ASIC_NHS_STR, self.asic_nhs_count + 3) + if idx == 0: + rt_nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] + else: + default_nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] - # group1 should be updated and a new NHG shouldn't be created - time.sleep(1) - assert len(self.asic_db.get_keys(self.ASIC_NHG_STR)) == self.asic_nhgs_count + 2 + nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] - # Remove the route - self.rt_ps._del('2.2.2.0/24') - self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count + 1) + fvs = self.asic_db.get_entry(self.ASIC_NHG_STR, nhgid) - # One NH should become unreferenced and should be deleted. The other - # one is still referenced by NhgOrch's owned NHG. - self.asic_db.wait_for_n_keys(self.ASIC_NHS_STR, self.asic_nhs_count + 2) + assert bool(fvs) - # Remove the group - self.nhg_ps._del('group1') - self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count) + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + + assert len(keys) == 5 - # Both new next hops should be deleted - self.asic_db.wait_for_n_keys(self.ASIC_NHS_STR, self.asic_nhs_count) + for k in keys: + fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) + nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] + nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) - self.init_test(dvs, 2) + if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == rt_nhgid: + rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) + rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) + rt_nhgmids.add(k) + rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) + elif fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == default_nhgid: + default_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) + default_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) + default_nhgmids.add(k) + default_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) - mainline_labeled_nhs_test() - routeorch_nhgorch_interop_test() + assert len(rt_parentnhgid) == 1 + assert len(rt_nhopsids) == 3 + assert len(rt_nhops) == 3 + assert len(rt_nhgmids) == 3 - def test_nhgorch_excp_group_cases(self, dvs, testlog): - # Test scenario: - # - remove a NHG that does not exist and assert the number of NHGs in ASIC DB remains the same - def remove_inexistent_nhg_test(): - # Remove a group that does not exist - self.nhg_ps._del("group1") - time.sleep(1) - assert len(self.asic_db.get_keys(self.ASIC_NHG_STR)) == self.asic_nhgs_count + assert len(default_parentnhgid) == 1 + assert len(default_nhopsids) == 2 + assert len(default_nhops) == 2 + assert len(default_nhgmids) == 2 - # Test scenario: - # - create a NHG with a member which does not exist and assert no NHG is created - # - update the NHG to contain all valid members and assert the NHG is created and it has 2 members - def nhg_members_validation_test(): - # Create a next hop group with a member that does not exist - should fail - fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.1,10.0.0.3,10.0.0.63'), - ("ifname", "Ethernet0,Ethernet4,Ethernet124")]) - self.nhg_ps.set("group1", fvs) - time.sleep(1) - assert len(self.asic_db.get_keys(self.ASIC_NHG_STR)) == self.asic_nhgs_count + assert rt_nhops != default_nhops + assert rt_nhopsids != default_nhopsids + assert rt_parentnhgid != default_parentnhgid + assert rt_nhgmids != default_nhgmids + + # bring links down one-by-one + for i in [0, 1, 2]: + self.flap_intf(i, 'down') + time.sleep(1) - # Issue an update for this next hop group that doesn't yet exist, - # which contains only valid NHs. This will overwrite the previous - # operation and create the group. - fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.1,10.0.0.5'), - ("ifname", "Ethernet0,Ethernet8")]) - self.nhg_ps.set("group1", fvs) - self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count + 1) - self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 2) + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - # Check the group has its two members - assert len(self.get_nhgm_ids('group1')) == 2 + if i != 2: + assert len(keys) == 4 - i + else: + # Last Link down so we will fallback to default eoute 3 members + assert len(keys) == 4 + rt_nhops.clear() + rt_nhgmids.clear() + rt_nhopsids.clear() + for k in keys: + fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) + nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] + nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) - # Test scenario: - # - create a route pointing to the NHG created in `test_nhg_members_validation` and assert it is being created - # - remove the NHG and assert it fails as it is being referenced - # - create a new NHG and assert it and its members are being created - # - update the route to point to the new NHG and assert the first NHG is now deleted as it's not referenced - # anymore - def remove_referenced_nhg_test(): - # Add a route referencing the new group - fvs = swsscommon.FieldValuePairs([('nexthop_group', 'group1')]) - self.rt_ps.set('2.2.2.0/24', fvs) - self.asic_db.wait_for_n_keys(self.ASIC_RT_STR, self.asic_rts_count + 1) + if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == rt_nhgid: + rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) + rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) + rt_nhgmids.add(k) + rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) + elif fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == default_nhgid: + default_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) + default_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) + default_nhgmids.add(k) + default_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) - # Try removing the group while it still has references - should fail - self.nhg_ps._del('group1') - time.sleep(1) - assert len(self.asic_db.get_keys(self.ASIC_NHG_STR)) == self.asic_nhgs_count + 1 + assert len(rt_parentnhgid) == 1 + assert len(rt_nhopsids) == 2 + assert len(rt_nhops) == 2 + assert len(rt_nhgmids) == 2 - # Create a new group - fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.1,10.0.0.3'), - ('ifname', 'Ethernet0,Ethernet4')]) - self.nhg_ps.set("group2", fvs) - self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count + 2) - self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 4) + assert len(default_parentnhgid) == 1 + assert len(default_nhopsids) == 2 + assert len(default_nhops) == 2 + assert len(default_nhgmids) == 2 - # Update the route to point to the new group - fvs = swsscommon.FieldValuePairs([('nexthop_group', 'group2')]) - self.rt_ps.set('2.2.2.0/24', fvs) + assert rt_nhops == default_nhops + assert rt_nhopsids == default_nhopsids + assert rt_nhgmids != default_nhgmids + + # bring links up one-by-one + # Bring link up in random order to verify sequence id is as per order + for i, val in enumerate([2,1,0]): + self.flap_intf(i, 'up') + time.sleep(1) - # The first group should have got deleted - self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count + 1) - self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 2) + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + assert len(keys) == 4 - # The route's group should have changed to the new one - assert self.asic_db.get_entry(self.ASIC_RT_STR, self.get_route_id('2.2.2.0/24'))['SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID'] == self.get_nhg_id('group2') + for k in keys: + fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) + nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] + nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) - # Test scenario: - # - update the route created in `test_remove_referenced_nhg` to own the NHG with the same details as the - # previous one and assert a new NHG and 2 new NHGMs are added - # - update the route to point back to the original NHG and assert the routeOrch's owned NHG is deleted - def routeorch_nhgorch_interop_test(): - rt_id = self.get_route_id('2.2.2.0/24') - assert rt_id is not None + if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == rt_nhgid: + rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) + rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) + rt_nhgmids.add(k) + rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) + elif fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == default_nhgid: + default_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) + default_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) + default_nhgmids.add(k) + default_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) - # Update the route with routeOrch's owned next hop group - nhgid = self.asic_db.get_entry(self.ASIC_RT_STR, rt_id)['SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID'] - fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.1,10.0.0.3'), - ('ifname', 'Ethernet0,Ethernet4')]) - self.rt_ps.set('2.2.2.0/24', fvs) + assert len(rt_parentnhgid) == 1 + assert len(rt_nhopsids) == 2 + assert len(rt_nhops) == 2 + assert len(rt_nhgmids) == 2 - self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count + 2) - self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 4) + assert len(default_parentnhgid) == 1 + assert len(default_nhopsids) == 2 + assert len(default_nhops) == 2 + assert len(default_nhgmids) == 2 - # Assert the next hop group ID changed + assert rt_nhops == default_nhops + assert rt_nhopsids == default_nhopsids + assert rt_nhgmids != default_nhgmids + + # Remove route 2.2.2.0/24 + self.rt_ps._del(rtprefix) time.sleep(1) - assert self.asic_db.get_entry(self.ASIC_RT_STR, rt_id)['SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID'] != nhgid - nhgid = self.asic_db.get_entry(self.ASIC_RT_STR, rt_id)['SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID'] - # Update the route to point back to group2 - fvs = swsscommon.FieldValuePairs([('nexthop_group', 'group2')]) - self.rt_ps.set('2.2.2.0/24', fvs) + # Wait for route 2.2.2.0/24 to be removed + dvs_route.check_asicdb_deleted_route_entries([rtprefix]) - # The routeOrch's owned next hop group should get deleted - self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count + 1) - self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 2) + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - # Assert the route points back to group2 - assert self.asic_db.get_entry(self.ASIC_RT_STR, rt_id)['SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID'] != nhgid + assert len(keys) == 2 - # Test scenario: - # - create a new NHG with the same details as the previous NHG and assert a new NHG and 2 new NHGMs are created - # - update the route to point to the new NHG and assert its SAI NHG ID changes - def identical_nhgs_test(): - rt_id = self.get_route_id('2.2.2.0/24') - assert rt_id is not None + # Remove route 0.0.0.0/0 + self.rt_ps._del(defaultprefix) + time.sleep(1) - # Create a new group with the same members as group2 - nhgid = self.asic_db.get_entry(self.ASIC_RT_STR, rt_id)['SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID'] - fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.1,10.0.0.3'), - ('ifname', 'Ethernet0,Ethernet4')]) - self.nhg_ps.set("group1", fvs) - self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count + 2) - self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 4) + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - # Update the route to point to the new group - fvs = swsscommon.FieldValuePairs([('nexthop_group', 'group1')]) - self.rt_ps.set('2.2.2.0/24', fvs) - time.sleep(1) + assert len(keys) == 0 - # Assert the next hop group ID changed - assert self.asic_db.get_entry(self.ASIC_RT_STR, rt_id)['SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID'] != nhgid + finally: + dvs.start_fpmsyncd() + + @pytest.mark.parametrize('is_ipv6_needed', [False, True]) + def test_route_fallback_to_default_negative(self, is_ipv6_needed, dvs, dvs_route, testlog): + self.init_test(dvs, 6, True) + if is_ipv6_needed: + rtprefix = "2603:10b0::1/120" + nexthop_str = "fc00::1,fc00::3,fc00::5" + else: + rtprefix = "2.2.2.0/24" + nexthop_str = "10.0.0.1,10.0.0.3,10.0.0.5" + + dvs_route.check_asicdb_deleted_route_entries([rtprefix]) - # Test scenario: - # - create a route referencing a NHG that does not exist and assert it is not created - def create_route_inexistent_nhg_test(): - # Add a route with a NHG that does not exist - fvs = swsscommon.FieldValuePairs([('nexthop_group', 'group3')]) - self.rt_ps.set('2.2.3.0/24', fvs) + try: + dvs.disable_fpmsyncd() + # Program Regular Rouute with fallback to default + fvs = swsscommon.FieldValuePairs([("nexthop",nexthop_str), + ("ifname", "Ethernet0,Ethernet4,Ethernet8"), + ("fallback_to_default_route", "true")]) + self.rt_ps.set(rtprefix, fvs) time.sleep(1) - assert self.get_route_id('2.2.3.0/24') is None - # Remove the pending route - self.rt_ps._del('2.2.3.0/24') + + # check if route was propagated to ASIC DB + rtkeys = dvs_route.check_asicdb_route_entries([rtprefix]) + + rt_nhgid = None + rt_nhops = set() + rt_nhopsids = set() + rt_nhgmids = set() + rt_parentnhgid = set() + + # assert the route points to next hop group + for rtkey in ([rtkeys]): + fvs = self.asic_db.get_entry(self.ASIC_RT_STR, rtkey[0]) - self.init_test(dvs, 3) + rt_nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] + nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] - remove_inexistent_nhg_test() - nhg_members_validation_test() - remove_referenced_nhg_test() - routeorch_nhgorch_interop_test() - identical_nhgs_test() - create_route_inexistent_nhg_test() + fvs = self.asic_db.get_entry(self.ASIC_NHG_STR, nhgid) - # Cleanup + assert bool(fvs) - # Remove the route - self.rt_ps._del('2.2.2.0/24') - self.asic_db.wait_for_n_keys(self.ASIC_RT_STR, self.asic_rts_count) + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + + assert len(keys) == 3 - # Remove the groups - self.nhg_ps._del('group1') - self.nhg_ps._del('group2') - self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count) - self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count) + for k in keys: + fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) + nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] + nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) - def test_nhgorch_nh_group(self, dvs, testlog): - # Test scenario: - # - create recursive nhg - rec_grp1 with two members - grp1 and grp2 only one of which exists - # - create singleton nhg grp2 and check if the rec_grp1 is updated with both the members - # - create a recursive nhg - rec_grp2 with another recursive nhg - rec_grp1 as member. Assert that the nhg is not created. - def create_recursive_nhg_test(): - # create next hop group in APPL DB - fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.1'), ('ifname', 'Ethernet0')]) - self.nhg_ps.set("grp1", fvs) + if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == rt_nhgid: + rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) + rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) + rt_nhgmids.add(k) + rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) + + assert len(rt_parentnhgid) == 1 + assert len(rt_nhopsids) == 3 + assert len(rt_nhops) == 3 + assert len(rt_nhgmids) == 3 - # create a recursive nexthop group with two members - fvs = swsscommon.FieldValuePairs([('nexthop_group', 'grp1,grp2')]) - self.nhg_ps.set("rec_grp1", fvs) + # bring links down one-by-one + for i in [0, 1, 2]: + self.flap_intf(i, 'down') + time.sleep(1) - # check if group was propagated to ASIC DB with the existing member - self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count + 1) - assert self.nhg_exists('rec_grp1') + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + assert len(keys) == 2 - i + + # bring links up one-by-one + # Bring link up in random order to verify sequence id is as per order + for i, val in enumerate([2,1,0]): + self.flap_intf(i, 'up') + time.sleep(1) + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + assert len(keys) == i + 1 + + rt_nhops.clear() + rt_nhgmids.clear() + rt_nhopsids.clear() - # check if the existing member was propagated to ASIC DB - self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 1) - assert len(self.get_nhgm_ids('rec_grp1')) == 1 + for k in keys: + fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) + nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] + nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) - # add another singleton nexthop group - grp2 - fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.3'), ('ifname', 'Ethernet4')]) - self.nhg_ps.set("grp2", fvs) + if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == rt_nhgid: + rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) + rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) + rt_nhgmids.add(k) + rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) - # check if both the members were propagated to ASIC DB - self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 2) - assert len(self.get_nhgm_ids('rec_grp1')) == 2 + assert len(rt_parentnhgid) == 1 + assert len(rt_nhopsids) == 3 + assert len(rt_nhops) == 3 + assert len(rt_nhgmids) == 3 - # update the recursive nexthop group with another member not yet existing - fvs = swsscommon.FieldValuePairs([('nexthop_group', 'grp1,grp2,grp3')]) - self.nhg_ps.set("rec_grp1", fvs) + # Remove route 2.2.2.0/24 + self.rt_ps._del(rtprefix) + time.sleep(1) - # check if only two members were propagated to ASIC DB - self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 2) - assert len(self.get_nhgm_ids('rec_grp1')) == 2 + # Wait for route 2.2.2.0/24 to be removed + dvs_route.check_asicdb_deleted_route_entries([rtprefix]) - # add another singleton nexthop group - grp3 - fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.5'), ('ifname', 'Ethernet8')]) - self.nhg_ps.set("grp3", fvs) + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - # check if all members were propagated to ASIC DB - self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 3) - assert len(self.get_nhgm_ids('rec_grp1')) == 3 + assert len(keys) == 0 - # create a recursive nhg with another recursive nhg as member - fvs = swsscommon.FieldValuePairs([('nexthop_group', 'rec_grp1')]) - self.nhg_ps.set("rec_grp2", fvs) + finally: + dvs.start_fpmsyncd() - # check that the group was not propagated to ASIC DB - self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count + 1) - assert not self.nhg_exists('rec_grp2') + def test_route_fallback_to_default_bothv4v6(self, dvs, dvs_route, testlog): + self.init_test(dvs, 6, True) + rtprefix_v6 = "2603:10b0::1/120" + defaultprefix_v6 = "::/0" + nexthop_str_v6 = "fc00::1,fc00::3,fc00::5" + default_nexthop_str_v6 = "fc00::7,fc00::9,fc00::b" + rtprefix = "2.2.2.0/24" + defaultprefix = "0.0.0.0/0" + nexthop_str = "10.0.0.1,10.0.0.3,10.0.0.5" + default_nexthop_str = "10.0.0.7,10.0.0.9,10.0.0.11" + + dvs_route.check_asicdb_deleted_route_entries([rtprefix, rtprefix_v6]) - self.nhg_ps._del("rec_grp2") - self.nhg_ps._del("rec_grp1") - self.nhg_ps._del("grp1") - self.nhg_ps._del("grp2") - self.nhg_ps._del("grp3") - self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count) - self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count) + try: + dvs.disable_fpmsyncd() + # Program Regular Rouute with fallback to default + fvs = swsscommon.FieldValuePairs([("nexthop",nexthop_str), + ("ifname", "Ethernet0,Ethernet4,Ethernet8"), + ("fallback_to_default_route", "true")]) + self.rt_ps.set(rtprefix, fvs) + time.sleep(1) + + fvs = swsscommon.FieldValuePairs([("nexthop",nexthop_str_v6), + ("ifname", "Ethernet0,Ethernet4,Ethernet8"), + ("fallback_to_default_route", "true")]) + self.rt_ps.set(rtprefix_v6, fvs) + time.sleep(1) - # Test scenario: - # - create NHG 'group1' and assert it is being added to ASIC DB along with its members - def create_nhg_test(): - # create next hop group in APPL DB - fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.1,10.0.0.3,10.0.0.5'), - ("ifname", "Ethernet0,Ethernet4,Ethernet8")]) - self.nhg_ps.set("group1", fvs) + # Program default route + fvs = swsscommon.FieldValuePairs([("nexthop", default_nexthop_str), + ("ifname", "Ethernet12,Ethernet16,Ethernet20")]) - # check if group was propagated to ASIC DB - self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count + 1) - assert self.nhg_exists('group1') + self.rt_ps.set(defaultprefix, fvs) + time.sleep(1) - # check if members were propagated to ASIC DB - self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 3) - assert len(self.get_nhgm_ids('group1')) == 3 + fvs = swsscommon.FieldValuePairs([("nexthop", default_nexthop_str_v6), + ("ifname", "Ethernet12,Ethernet16,Ethernet20")]) - # Test scenario: - # - create a route pointing to `group1` and assert it is being added to ASIC DB and pointing to its SAI ID - # - delete the route and assert it is being removed - def create_route_nhg_test(): - # create route in APPL DB - fvs = swsscommon.FieldValuePairs([("nexthop_group", "group1")]) - self.rt_ps.set("2.2.2.0/24", fvs) + self.rt_ps.set(defaultprefix_v6, fvs) + time.sleep(1) # check if route was propagated to ASIC DB - self.asic_db.wait_for_n_keys(self.ASIC_RT_STR, self.asic_rts_count + 1) - - k = self.get_route_id('2.2.2.0/24') - assert k is not None + rtkeys = dvs_route.check_asicdb_route_entries([rtprefix, rtprefix_v6]) + + # check if default route was propagated to ASIC DB + defaultrtkeys = dvs_route.check_asicdb_route_entries([defaultprefix, defaultprefix_v6]) - # assert the route points to next hop group - fvs = self.asic_db.get_entry(self.ASIC_RT_STR, k) - assert fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] == self.get_nhg_id('group1') + default_nhgid = [] + default_nhops = set() + default_nhgmids = set() + default_nhopsids = set() + default_parentnhgid = set() - # Remove route 2.2.2.0/24 - self.rt_ps._del("2.2.2.0/24") - self.asic_db.wait_for_n_keys(self.ASIC_RT_STR, self.asic_rts_count) + rt_nhgid = [] + rt_nhops = set() + rt_nhopsids = set() + rt_nhgmids = set() + rt_parentnhgid = set() - # Test scenario: - # - bring the links down one by one and assert the group1's members are subsequently removed and the group - # still exists - # - bring the liks up one by one and assert the group1's members are subsequently added back - def link_flap_test(): - # bring links down one-by-one - for i in [0, 1, 2]: - self.flap_intf(i, 'down') - self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 2 - i) - assert len(self.get_nhgm_ids('group1')) == 2 - i - assert self.nhg_exists('group1') + # assert the route points to next hop group + flat_list = [] + for x in rtkeys: + flat_list.append(x) + for x in defaultrtkeys: + flat_list.append(x) + for idx, rtkey in enumerate(flat_list): + fvs = self.asic_db.get_entry(self.ASIC_RT_STR, rtkey) + if idx in [0,1]: + rt_nhgid.append(fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"]) + else: + default_nhgid.append(fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"]) - # bring links up one-by-one - for i in [0, 1, 2]: - self.flap_intf(i, 'up') - self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + i + 1) - assert len(self.get_nhgm_ids('group1')) == i + 1 + nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] - # Test scenario: - # - bring a link down and assert a NHGM of `group1` is removed - # - create NHG `group2` which has a member pointing to the link being down and assert the group gets created - # but the member referencing the link is not added - # - update `group1` by removing a member while having another member referencing the link which is down and - # assert it'll only have a member added in ASIC DB - # - bring the link back up and assert the missing 2 members of `group1` and `group2` are added - # - remove `group2` and assert it and its members are removed - def validate_invalidate_group_member_test(): - # Bring an interface down - self.flap_intf(1, 'down') + fvs = self.asic_db.get_entry(self.ASIC_NHG_STR, nhgid) - # One group member will get deleted - self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 2) + assert bool(fvs) - # Create a group that contains a NH that uses the down link - fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.1,10.0.0.3'), - ("ifname", "Ethernet0,Ethernet4")]) - self.nhg_ps.set('group2', fvs) + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + + assert len(keys) == 12 - # The group should get created, but it will not contained the NH that - # has the link down - self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count + 2) - self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 3) - assert len(self.get_nhgm_ids('group2')) == 1 + for k in keys: + fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) + nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] + nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) - # Update the NHG with one interface down - fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.3,10.0.0.1'), - ("ifname", "Ethernet4,Ethernet0")]) - self.nhg_ps.set("group1", fvs) + if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] in rt_nhgid: + rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) + rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) + rt_nhgmids.add(k) + rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) + elif fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] in default_nhgid: + default_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) + default_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) + default_nhgmids.add(k) + default_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) - # Wait for group members to update - the group will contain only the - # members that have their links up - self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 2) - assert len(self.get_nhgm_ids('group1')) == 1 + assert len(rt_parentnhgid) == 2 + assert len(rt_nhopsids) == 6 + assert len(rt_nhops) == 6 + assert len(rt_nhgmids) == 6 - # Bring the interface up - self.flap_intf(1, 'up') + assert len(default_parentnhgid) == 2 + assert len(default_nhopsids) == 6 + assert len(default_nhops) == 6 + assert len(default_nhgmids) == 6 - # Check that the missing member of group1 and group2 is being added - self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 4) + assert rt_nhops != default_nhops + assert rt_nhopsids != default_nhopsids + assert rt_parentnhgid != default_parentnhgid + assert rt_nhgmids != default_nhgmids + + # bring links down one-by-one + for i in [0, 1, 2]: + self.flap_intf(i, 'down') + time.sleep(1) - # Remove group2 - self.nhg_ps._del('group2') - self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count + 1) - self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 2) + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - # Test scenario: - # - create NHG `group2` with a NH that does not exist and assert it isn't created - # - update `group1` to contain the invalid NH and assert it remains only with the unremoved members - # - configure the invalid NH's interface and assert `group2` gets created and `group1`'s NH is added - # - delete `group` and assert it is being removed - def inexistent_group_member_test(): - # Create group2 with a NH that does not exist - fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.3,10.0.0.63'), - ("ifname", "Ethernet4,Ethernet124")]) - self.nhg_ps.set("group2", fvs) + if i != 2: + assert len(keys) == 10 - (i * 2) + else: + # Last Link down so we will fallback to default eoute 3 members + assert len(keys) == 12 + rt_nhops.clear() + rt_nhgmids.clear() + rt_nhopsids.clear() + for k in keys: + fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) + nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] + nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) - # The groups should not be created - time.sleep(1) - self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count + 1) + if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] in rt_nhgid: + rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) + rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) + rt_nhgmids.add(k) + rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) + elif fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] in default_nhgid: + default_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) + default_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) + default_nhgmids.add(k) + default_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) - # Update group1 with a NH that does not exist - fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.3,10.0.0.63'), - ("ifname", "Ethernet4,Ethernet124")]) - self.nhg_ps.set("group1", fvs) + assert len(rt_parentnhgid) == 2 + assert len(rt_nhopsids) == 6 + assert len(rt_nhops) == 6 + assert len(rt_nhgmids) == 6 - # The update should fail, leaving group1 with only the unremoved - # members - self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 1) - assert len(self.get_nhgm_ids('group1')) == 1 + assert len(default_parentnhgid) == 2 + assert len(default_nhopsids) == 6 + assert len(default_nhops) == 6 + assert len(default_nhgmids) == 6 - # Configure the missing NH's interface - self.config_intf(31) + assert rt_nhops == default_nhops + assert rt_nhopsids == default_nhopsids + assert rt_nhgmids != default_nhgmids + + # bring links up one-by-one + # Bring link up in random order to verify sequence id is as per order + for i, val in enumerate([2,1,0]): + self.flap_intf(i, 'up') + time.sleep(1) - # A couple more routes will be added to ASIC DB - self.asic_rts_count += 2 + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - # Group2 should get created and group1 should be updated - self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count + 2) - self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 4) - assert len(self.get_nhgm_ids('group1')) == 2 - assert len(self.get_nhgm_ids('group2')) == 2 + assert len(keys) == 12 - # Delete group2 - self.nhg_ps._del('group2') - self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count + 1) + for k in keys: + fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) + nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] + nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) - # Test scenario: - # - update `group1` to have 4 members and assert they are all added - # - update `group1` to have only 1 member and assert the other 3 are removed - # - update `group1` to have 2 members and assert a new one is added - def update_nhgm_count_test(): - # Update the NHG, adding two new members - fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.1,10.0.0.3,10.0.0.5,10.0.0.7'), - ("ifname", "Ethernet0,Ethernet4,Ethernet8,Ethernet12")]) - self.nhg_ps.set("group1", fvs) - self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 4) - assert len(self.get_nhgm_ids('group1')) == 4 + if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] in rt_nhgid: + rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) + rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) + rt_nhgmids.add(k) + rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) + elif fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] in default_nhgid: + default_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) + default_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) + default_nhgmids.add(k) + default_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) - # Update the group to one NH only - fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.1'), ("ifname", "Ethernet0")]) - self.nhg_ps.set("group1", fvs) - self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count) - assert len(self.get_nhgm_ids('group1')) == 0 + assert len(rt_parentnhgid) == 2 + assert len(rt_nhopsids) == 6 + assert len(rt_nhops) == 6 + assert len(rt_nhgmids) == 6 - # Update the group to 2 NHs - fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.1,10.0.0.3'), ("ifname", "Ethernet0,Ethernet4")]) - self.nhg_ps.set("group1", fvs) - self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 2) - assert len(self.get_nhgm_ids('group1')) == 2 + assert len(default_parentnhgid) == 2 + assert len(default_nhopsids) == 6 + assert len(default_nhops) == 6 + assert len(default_nhgmids) == 6 - self.init_test(dvs, 4) + assert rt_nhops == default_nhops + assert rt_nhopsids == default_nhopsids + assert rt_nhgmids != default_nhgmids - create_recursive_nhg_test() - create_nhg_test() - create_route_nhg_test() - link_flap_test() - validate_invalidate_group_member_test() - inexistent_group_member_test() - update_nhgm_count_test() + # Remove route 2.2.2.0/24 + self.rt_ps._del(rtprefix) + time.sleep(1) - # Cleanup + self.rt_ps._del(rtprefix_v6) + time.sleep(1) - # Remove group1 - self.nhg_ps._del("group1") - self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count) + # Wait for route 2.2.2.0/24 to be removed + dvs_route.check_asicdb_deleted_route_entries([rtprefix, rtprefix_v6]) - def test_nhgorch_label_route(self, dvs, testlog): - self.init_test(dvs, 4) + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - # create next hop group in APPL DB - fvs = swsscommon.FieldValuePairs([('nexthop', '10.0.0.1,10.0.0.3,10.0.0.5'), - ("ifname", "Ethernet0,Ethernet4,Ethernet8")]) - self.nhg_ps.set("group1", fvs) - self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count + 1) - self.asic_db.wait_for_n_keys(self.ASIC_NHGM_STR, self.asic_nhgms_count + 3) + assert len(keys) == 6 - # create label route in APPL DB pointing to the NHG - fvs = swsscommon.FieldValuePairs([("nexthop_group", "group1")]) - self.lr_ps.set("20", fvs) - self.asic_db.wait_for_n_keys(self.ASIC_INSEG_STR, self.asic_insgs_count + 1) + # Remove route 0.0.0.0/0 + self.rt_ps._del(defaultprefix) + time.sleep(1) - k = self.get_inseg_id('20') - assert k is not None + self.rt_ps._del(defaultprefix_v6) + time.sleep(1) - # assert the route points to next hop group - fvs = self.asic_db.get_entry(self.ASIC_INSEG_STR, k) - assert fvs["SAI_INSEG_ENTRY_ATTR_NEXT_HOP_ID"] == self.get_nhg_id('group1') + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - # Remove label route 20 - self.lr_ps._del("20") - self.asic_db.wait_for_n_keys(self.ASIC_INSEG_STR, self.asic_insgs_count) + assert len(keys) == 0 + + finally: + dvs.start_fpmsyncd() - # Remove group1 - self.nhg_ps._del("group1") - self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count) class TestCbfNextHopGroup(TestNextHopGroupBase): MAX_NHG_MAP_COUNT = 512 From 5985e348bc4a2ed089edb6f8c5d6e6195d17f47e Mon Sep 17 00:00:00 2001 From: Abhishek Dosi Date: Fri, 6 Dec 2024 13:07:25 +0000 Subject: [PATCH 11/16] Fix Signed-off-by: Abhishek Dosi --- tests/test_nhg.py | 793 ---------------------------------------------- 1 file changed, 793 deletions(-) diff --git a/tests/test_nhg.py b/tests/test_nhg.py index 398cedf4a4f..fdb4234ed5f 100644 --- a/tests/test_nhg.py +++ b/tests/test_nhg.py @@ -1841,799 +1841,6 @@ def test_nhgorch_label_route(self, dvs, testlog): self.nhg_ps._del("group1") self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count) - @pytest.mark.parametrize('is_ipv6_needed', [False, True]) - def test_route_fallback_to_default(self, is_ipv6_needed, dvs, dvs_route, testlog): - self.init_test(dvs, 6, True) - if is_ipv6_needed: - rtprefix = "2603:10b0::1/120" - defaultprefix = "::/0" - nexthop_str = "fc00::1,fc00::3,fc00::5" - default_nexthop_str = "fc00::7,fc00::9,fc00::b" - else: - rtprefix = "2.2.2.0/24" - defaultprefix = "0.0.0.0/0" - nexthop_str = "10.0.0.1,10.0.0.3,10.0.0.5" - default_nexthop_str = "10.0.0.7,10.0.0.9,10.0.0.11" - - dvs_route.check_asicdb_deleted_route_entries([rtprefix]) - - try: - dvs.disable_fpmsyncd() - # Program Regular Rouute with fallback to default - fvs = swsscommon.FieldValuePairs([("nexthop",nexthop_str), - ("ifname", "Ethernet0,Ethernet4,Ethernet8"), - ("fallback_to_default_route", "true")]) - self.rt_ps.set(rtprefix, fvs) - time.sleep(1) - - # Program default route - fvs = swsscommon.FieldValuePairs([("nexthop", default_nexthop_str), - ("ifname", "Ethernet12,Ethernet16,Ethernet20")]) - - self.rt_ps.set(defaultprefix, fvs) - time.sleep(1) - - # check if route was propagated to ASIC DB - rtkeys = dvs_route.check_asicdb_route_entries([rtprefix]) - - # check if default route was propagated to ASIC DB - defaultrtkeys = dvs_route.check_asicdb_route_entries([defaultprefix]) - - default_nhgid = None - default_nhops = set() - default_nhgmids = set() - default_nhopsids = set() - default_parentnhgid = set() - - rt_nhgid = None - rt_nhops = set() - rt_nhopsids = set() - rt_nhgmids = set() - rt_parentnhgid = set() - - # assert the route points to next hop group - for idx, rtkey in enumerate([rtkeys, defaultrtkeys]): - fvs = self.asic_db.get_entry(self.ASIC_RT_STR, rtkey[0]) - - if idx == 0: - rt_nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] - else: - default_nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] - - nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] - - fvs = self.asic_db.get_entry(self.ASIC_NHG_STR, nhgid) - - assert bool(fvs) - - keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - - assert len(keys) == 6 - - for k in keys: - fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) - nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] - nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) - - if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == rt_nhgid: - rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) - rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) - rt_nhgmids.add(k) - rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) - elif fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == default_nhgid: - default_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) - default_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) - default_nhgmids.add(k) - default_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) - - assert len(rt_parentnhgid) == 1 - assert len(rt_nhopsids) == 3 - assert len(rt_nhops) == 3 - assert len(rt_nhgmids) == 3 - - assert len(default_parentnhgid) == 1 - assert len(default_nhopsids) == 3 - assert len(default_nhops) == 3 - assert len(default_nhgmids) == 3 - - assert rt_nhops != default_nhops - assert rt_nhopsids != default_nhopsids - assert rt_parentnhgid != default_parentnhgid - assert rt_nhgmids != default_nhgmids - - # bring links down one-by-one - for i in [0, 1, 2]: - self.flap_intf(i, 'down') - time.sleep(1) - - keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - - if i != 2: - assert len(keys) == 5 - i - else: - # Last Link down so we will fallback to default eoute 3 members - assert len(keys) == 6 - rt_nhops.clear() - rt_nhgmids.clear() - rt_nhopsids.clear() - for k in keys: - fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) - nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] - nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) - - if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == rt_nhgid: - rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) - rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) - rt_nhgmids.add(k) - rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) - elif fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == default_nhgid: - default_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) - default_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) - default_nhgmids.add(k) - default_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) - - assert len(rt_parentnhgid) == 1 - assert len(rt_nhopsids) == 3 - assert len(rt_nhops) == 3 - assert len(rt_nhgmids) == 3 - - assert len(default_parentnhgid) == 1 - assert len(default_nhopsids) == 3 - assert len(default_nhops) == 3 - assert len(default_nhgmids) == 3 - - assert rt_nhops == default_nhops - assert rt_nhopsids == default_nhopsids - assert rt_nhgmids != default_nhgmids - - # bring links up one-by-one - # Bring link up in random order to verify sequence id is as per order - for i, val in enumerate([2,1,0]): - self.flap_intf(i, 'up') - time.sleep(1) - - keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - assert len(keys) == 6 - - for k in keys: - fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) - nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] - nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) - - if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == rt_nhgid: - rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) - rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) - rt_nhgmids.add(k) - rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) - elif fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == default_nhgid: - default_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) - default_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) - default_nhgmids.add(k) - default_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) - - assert len(rt_parentnhgid) == 1 - assert len(rt_nhopsids) == 3 - assert len(rt_nhops) == 3 - assert len(rt_nhgmids) == 3 - - assert len(default_parentnhgid) == 1 - assert len(default_nhopsids) == 3 - assert len(default_nhops) == 3 - assert len(default_nhgmids) == 3 - - assert rt_nhops == default_nhops - assert rt_nhopsids == default_nhopsids - assert rt_nhgmids != default_nhgmids - - # Remove route 2.2.2.0/24 - self.rt_ps._del(rtprefix) - time.sleep(1) - - # Wait for route 2.2.2.0/24 to be removed - dvs_route.check_asicdb_deleted_route_entries([rtprefix]) - - keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - - assert len(keys) == 3 - - # Remove route 0.0.0.0/0 - self.rt_ps._del(defaultprefix) - time.sleep(1) - - keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - - assert len(keys) == 0 - - finally: - dvs.start_fpmsyncd() - - @pytest.mark.parametrize('is_ipv6_needed', [False, True]) - def test_route_fallback_to_default_update(self, is_ipv6_needed, dvs, dvs_route, testlog): - self.init_test(dvs, 6, True) - - if is_ipv6_needed: - rtprefix = "2603:10b0::1/120" - defaultprefix = "::/0" - nexthop_str = "fc00::1,fc00::3,fc00::5" - default_nexthop_str = "fc00::7,fc00::9,fc00::b" - updated_default_nexthop_str = "fc00::7,fc00::b" - else: - rtprefix = "2.2.2.0/24" - defaultprefix = "0.0.0.0/0" - nexthop_str = "10.0.0.1,10.0.0.3,10.0.0.5" - default_nexthop_str = "10.0.0.7,10.0.0.9,10.0.0.11" - updated_default_nexthop_str = "10.0.0.7,10.0.0.11" - - - dvs_route.check_asicdb_deleted_route_entries([rtprefix]) - - try: - dvs.disable_fpmsyncd() - - # Program default route - fvs = swsscommon.FieldValuePairs([("nexthop", default_nexthop_str), - ("ifname", "Ethernet12,Ethernet16,Ethernet20")]) - - self.rt_ps.set(defaultprefix, fvs) - time.sleep(1) - - # check if default route was propagated to ASIC DB - defaultrtkeys = dvs_route.check_asicdb_route_entries([defaultprefix]) - - fvs = self.asic_db.get_entry(self.ASIC_RT_STR, defaultrtkeys[0]) - - nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] - - fvs = self.asic_db.get_entry(self.ASIC_NHG_STR, nhgid) - - assert bool(fvs) - - keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - - assert len(keys) == 3 - - - # Program default route - fvs = swsscommon.FieldValuePairs([("nexthop", updated_default_nexthop_str), - ("ifname", "Ethernet12,Ethernet20")]) - - - self.rt_ps.set(defaultprefix, fvs) - time.sleep(1) - - # check if default route was propagated to ASIC DB - defaultrtkeys = dvs_route.check_asicdb_route_entries([defaultprefix]) - - fvs = self.asic_db.get_entry(self.ASIC_RT_STR, defaultrtkeys[0]) - - nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] - - fvs = self.asic_db.get_entry(self.ASIC_NHG_STR, nhgid) - - assert bool(fvs) - - keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - - assert len(keys) == 2 - - # Program Regular Rouute with fallback to default - fvs = swsscommon.FieldValuePairs([("nexthop", nexthop_str), - ("ifname", "Ethernet0,Ethernet4,Ethernet8"), - ("fallback_to_default_route", "true")]) - self.rt_ps.set(rtprefix, fvs) - time.sleep(1) - - # check if route was propagated to ASIC DB - rtkeys = dvs_route.check_asicdb_route_entries([rtprefix]) - - - default_nhgid = None - default_nhops = set() - default_nhgmids = set() - default_nhopsids = set() - default_parentnhgid = set() - - rt_nhgid = None - rt_nhops = set() - rt_nhopsids = set() - rt_nhgmids = set() - rt_parentnhgid = set() - - # assert the route points to next hop group - for idx, rtkey in enumerate([rtkeys, defaultrtkeys]): - fvs = self.asic_db.get_entry(self.ASIC_RT_STR, rtkey[0]) - - if idx == 0: - rt_nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] - else: - default_nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] - - nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] - - fvs = self.asic_db.get_entry(self.ASIC_NHG_STR, nhgid) - - assert bool(fvs) - - keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - - assert len(keys) == 5 - - for k in keys: - fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) - nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] - nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) - - if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == rt_nhgid: - rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) - rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) - rt_nhgmids.add(k) - rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) - elif fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == default_nhgid: - default_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) - default_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) - default_nhgmids.add(k) - default_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) - - assert len(rt_parentnhgid) == 1 - assert len(rt_nhopsids) == 3 - assert len(rt_nhops) == 3 - assert len(rt_nhgmids) == 3 - - assert len(default_parentnhgid) == 1 - assert len(default_nhopsids) == 2 - assert len(default_nhops) == 2 - assert len(default_nhgmids) == 2 - - assert rt_nhops != default_nhops - assert rt_nhopsids != default_nhopsids - assert rt_parentnhgid != default_parentnhgid - assert rt_nhgmids != default_nhgmids - - # bring links down one-by-one - for i in [0, 1, 2]: - self.flap_intf(i, 'down') - time.sleep(1) - - keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - - if i != 2: - assert len(keys) == 4 - i - else: - # Last Link down so we will fallback to default eoute 3 members - assert len(keys) == 4 - rt_nhops.clear() - rt_nhgmids.clear() - rt_nhopsids.clear() - for k in keys: - fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) - nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] - nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) - - if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == rt_nhgid: - rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) - rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) - rt_nhgmids.add(k) - rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) - elif fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == default_nhgid: - default_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) - default_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) - default_nhgmids.add(k) - default_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) - - assert len(rt_parentnhgid) == 1 - assert len(rt_nhopsids) == 2 - assert len(rt_nhops) == 2 - assert len(rt_nhgmids) == 2 - - assert len(default_parentnhgid) == 1 - assert len(default_nhopsids) == 2 - assert len(default_nhops) == 2 - assert len(default_nhgmids) == 2 - - assert rt_nhops == default_nhops - assert rt_nhopsids == default_nhopsids - assert rt_nhgmids != default_nhgmids - - # bring links up one-by-one - # Bring link up in random order to verify sequence id is as per order - for i, val in enumerate([2,1,0]): - self.flap_intf(i, 'up') - time.sleep(1) - - keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - assert len(keys) == 4 - - for k in keys: - fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) - nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] - nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) - - if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == rt_nhgid: - rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) - rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) - rt_nhgmids.add(k) - rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) - elif fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == default_nhgid: - default_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) - default_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) - default_nhgmids.add(k) - default_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) - - assert len(rt_parentnhgid) == 1 - assert len(rt_nhopsids) == 2 - assert len(rt_nhops) == 2 - assert len(rt_nhgmids) == 2 - - assert len(default_parentnhgid) == 1 - assert len(default_nhopsids) == 2 - assert len(default_nhops) == 2 - assert len(default_nhgmids) == 2 - - assert rt_nhops == default_nhops - assert rt_nhopsids == default_nhopsids - assert rt_nhgmids != default_nhgmids - - # Remove route 2.2.2.0/24 - self.rt_ps._del(rtprefix) - time.sleep(1) - - # Wait for route 2.2.2.0/24 to be removed - dvs_route.check_asicdb_deleted_route_entries([rtprefix]) - - keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - - assert len(keys) == 2 - - # Remove route 0.0.0.0/0 - self.rt_ps._del(defaultprefix) - time.sleep(1) - - keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - - assert len(keys) == 0 - - finally: - dvs.start_fpmsyncd() - - @pytest.mark.parametrize('is_ipv6_needed', [False, True]) - def test_route_fallback_to_default_negative(self, is_ipv6_needed, dvs, dvs_route, testlog): - self.init_test(dvs, 6, True) - if is_ipv6_needed: - rtprefix = "2603:10b0::1/120" - nexthop_str = "fc00::1,fc00::3,fc00::5" - else: - rtprefix = "2.2.2.0/24" - nexthop_str = "10.0.0.1,10.0.0.3,10.0.0.5" - - dvs_route.check_asicdb_deleted_route_entries([rtprefix]) - - try: - dvs.disable_fpmsyncd() - # Program Regular Rouute with fallback to default - fvs = swsscommon.FieldValuePairs([("nexthop",nexthop_str), - ("ifname", "Ethernet0,Ethernet4,Ethernet8"), - ("fallback_to_default_route", "true")]) - self.rt_ps.set(rtprefix, fvs) - time.sleep(1) - - - # check if route was propagated to ASIC DB - rtkeys = dvs_route.check_asicdb_route_entries([rtprefix]) - - rt_nhgid = None - rt_nhops = set() - rt_nhopsids = set() - rt_nhgmids = set() - rt_parentnhgid = set() - - # assert the route points to next hop group - for rtkey in ([rtkeys]): - fvs = self.asic_db.get_entry(self.ASIC_RT_STR, rtkey[0]) - - rt_nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] - nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] - - fvs = self.asic_db.get_entry(self.ASIC_NHG_STR, nhgid) - - assert bool(fvs) - - keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - - assert len(keys) == 3 - - for k in keys: - fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) - nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] - nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) - - if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == rt_nhgid: - rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) - rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) - rt_nhgmids.add(k) - rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) - - assert len(rt_parentnhgid) == 1 - assert len(rt_nhopsids) == 3 - assert len(rt_nhops) == 3 - assert len(rt_nhgmids) == 3 - - # bring links down one-by-one - for i in [0, 1, 2]: - self.flap_intf(i, 'down') - time.sleep(1) - - keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - assert len(keys) == 2 - i - - # bring links up one-by-one - # Bring link up in random order to verify sequence id is as per order - for i, val in enumerate([2,1,0]): - self.flap_intf(i, 'up') - time.sleep(1) - keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - assert len(keys) == i + 1 - - rt_nhops.clear() - rt_nhgmids.clear() - rt_nhopsids.clear() - - for k in keys: - fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) - nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] - nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) - - if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] == rt_nhgid: - rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) - rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) - rt_nhgmids.add(k) - rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) - - assert len(rt_parentnhgid) == 1 - assert len(rt_nhopsids) == 3 - assert len(rt_nhops) == 3 - assert len(rt_nhgmids) == 3 - - # Remove route 2.2.2.0/24 - self.rt_ps._del(rtprefix) - time.sleep(1) - - # Wait for route 2.2.2.0/24 to be removed - dvs_route.check_asicdb_deleted_route_entries([rtprefix]) - - keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - - assert len(keys) == 0 - - finally: - dvs.start_fpmsyncd() - - def test_route_fallback_to_default_bothv4v6(self, dvs, dvs_route, testlog): - self.init_test(dvs, 6, True) - rtprefix_v6 = "2603:10b0::1/120" - defaultprefix_v6 = "::/0" - nexthop_str_v6 = "fc00::1,fc00::3,fc00::5" - default_nexthop_str_v6 = "fc00::7,fc00::9,fc00::b" - rtprefix = "2.2.2.0/24" - defaultprefix = "0.0.0.0/0" - nexthop_str = "10.0.0.1,10.0.0.3,10.0.0.5" - default_nexthop_str = "10.0.0.7,10.0.0.9,10.0.0.11" - - dvs_route.check_asicdb_deleted_route_entries([rtprefix, rtprefix_v6]) - - try: - dvs.disable_fpmsyncd() - # Program Regular Rouute with fallback to default - fvs = swsscommon.FieldValuePairs([("nexthop",nexthop_str), - ("ifname", "Ethernet0,Ethernet4,Ethernet8"), - ("fallback_to_default_route", "true")]) - self.rt_ps.set(rtprefix, fvs) - time.sleep(1) - - fvs = swsscommon.FieldValuePairs([("nexthop",nexthop_str_v6), - ("ifname", "Ethernet0,Ethernet4,Ethernet8"), - ("fallback_to_default_route", "true")]) - self.rt_ps.set(rtprefix_v6, fvs) - time.sleep(1) - - # Program default route - fvs = swsscommon.FieldValuePairs([("nexthop", default_nexthop_str), - ("ifname", "Ethernet12,Ethernet16,Ethernet20")]) - - self.rt_ps.set(defaultprefix, fvs) - time.sleep(1) - - fvs = swsscommon.FieldValuePairs([("nexthop", default_nexthop_str_v6), - ("ifname", "Ethernet12,Ethernet16,Ethernet20")]) - - self.rt_ps.set(defaultprefix_v6, fvs) - time.sleep(1) - - # check if route was propagated to ASIC DB - rtkeys = dvs_route.check_asicdb_route_entries([rtprefix, rtprefix_v6]) - - # check if default route was propagated to ASIC DB - defaultrtkeys = dvs_route.check_asicdb_route_entries([defaultprefix, defaultprefix_v6]) - - default_nhgid = [] - default_nhops = set() - default_nhgmids = set() - default_nhopsids = set() - default_parentnhgid = set() - - rt_nhgid = [] - rt_nhops = set() - rt_nhopsids = set() - rt_nhgmids = set() - rt_parentnhgid = set() - - # assert the route points to next hop group - flat_list = [] - for x in rtkeys: - flat_list.append(x) - for x in defaultrtkeys: - flat_list.append(x) - for idx, rtkey in enumerate(flat_list): - fvs = self.asic_db.get_entry(self.ASIC_RT_STR, rtkey) - if idx in [0,1]: - rt_nhgid.append(fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"]) - else: - default_nhgid.append(fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"]) - - nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] - - fvs = self.asic_db.get_entry(self.ASIC_NHG_STR, nhgid) - - assert bool(fvs) - - keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - - assert len(keys) == 12 - - for k in keys: - fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) - nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] - nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) - - if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] in rt_nhgid: - rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) - rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) - rt_nhgmids.add(k) - rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) - elif fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] in default_nhgid: - default_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) - default_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) - default_nhgmids.add(k) - default_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) - - assert len(rt_parentnhgid) == 2 - assert len(rt_nhopsids) == 6 - assert len(rt_nhops) == 6 - assert len(rt_nhgmids) == 6 - - assert len(default_parentnhgid) == 2 - assert len(default_nhopsids) == 6 - assert len(default_nhops) == 6 - assert len(default_nhgmids) == 6 - - assert rt_nhops != default_nhops - assert rt_nhopsids != default_nhopsids - assert rt_parentnhgid != default_parentnhgid - assert rt_nhgmids != default_nhgmids - - # bring links down one-by-one - for i in [0, 1, 2]: - self.flap_intf(i, 'down') - time.sleep(1) - - keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - - if i != 2: - assert len(keys) == 10 - (i * 2) - else: - # Last Link down so we will fallback to default eoute 3 members - assert len(keys) == 12 - rt_nhops.clear() - rt_nhgmids.clear() - rt_nhopsids.clear() - for k in keys: - fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) - nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] - nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) - - if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] in rt_nhgid: - rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) - rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) - rt_nhgmids.add(k) - rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) - elif fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] in default_nhgid: - default_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) - default_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) - default_nhgmids.add(k) - default_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) - - assert len(rt_parentnhgid) == 2 - assert len(rt_nhopsids) == 6 - assert len(rt_nhops) == 6 - assert len(rt_nhgmids) == 6 - - assert len(default_parentnhgid) == 2 - assert len(default_nhopsids) == 6 - assert len(default_nhops) == 6 - assert len(default_nhgmids) == 6 - - assert rt_nhops == default_nhops - assert rt_nhopsids == default_nhopsids - assert rt_nhgmids != default_nhgmids - - # bring links up one-by-one - # Bring link up in random order to verify sequence id is as per order - for i, val in enumerate([2,1,0]): - self.flap_intf(i, 'up') - time.sleep(1) - - keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - - assert len(keys) == 12 - - for k in keys: - fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) - nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] - nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) - - if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] in rt_nhgid: - rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) - rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) - rt_nhgmids.add(k) - rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) - elif fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] in default_nhgid: - default_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) - default_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) - default_nhgmids.add(k) - default_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) - - assert len(rt_parentnhgid) == 2 - assert len(rt_nhopsids) == 6 - assert len(rt_nhops) == 6 - assert len(rt_nhgmids) == 6 - - assert len(default_parentnhgid) == 2 - assert len(default_nhopsids) == 6 - assert len(default_nhops) == 6 - assert len(default_nhgmids) == 6 - - assert rt_nhops == default_nhops - assert rt_nhopsids == default_nhopsids - assert rt_nhgmids != default_nhgmids - - # Remove route 2.2.2.0/24 - self.rt_ps._del(rtprefix) - time.sleep(1) - - self.rt_ps._del(rtprefix_v6) - time.sleep(1) - - # Wait for route 2.2.2.0/24 to be removed - dvs_route.check_asicdb_deleted_route_entries([rtprefix, rtprefix_v6]) - - keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - - assert len(keys) == 6 - - # Remove route 0.0.0.0/0 - self.rt_ps._del(defaultprefix) - time.sleep(1) - - self.rt_ps._del(defaultprefix_v6) - time.sleep(1) - - keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) - - assert len(keys) == 0 - - finally: - dvs.start_fpmsyncd() - - class TestCbfNextHopGroup(TestNextHopGroupBase): MAX_NHG_MAP_COUNT = 512 From 409b268abd84a7cd70fa6618b58fe64fc92c4b17 Mon Sep 17 00:00:00 2001 From: Abhishek Dosi Date: Wed, 11 Dec 2024 09:41:22 +0000 Subject: [PATCH 12/16] Address review comment Signed-off-by: Abhishek Dosi --- orchagent/routeorch.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/orchagent/routeorch.cpp b/orchagent/routeorch.cpp index 23ee1c9d033..85d80e16f58 100644 --- a/orchagent/routeorch.cpp +++ b/orchagent/routeorch.cpp @@ -1180,13 +1180,17 @@ void RouteOrch::doTask(Consumer& consumer) } if (!(v4_default_nhg_key.getSize()) && !(v6_default_nhg_key.getSize())) + { return; - + } if (v4_default_nhg_key.getSize()) + { updateDefaultRouteSwapSet(v4_default_nhg_key, v4_active_default_route_nhops); - + } if (v6_default_nhg_key.getSize()) + { updateDefaultRouteSwapSet(v6_default_nhg_key, v6_active_default_route_nhops); + } } } @@ -2647,9 +2651,13 @@ bool RouteOrch::removeRoutePost(const RouteBulkContext& ctx) SWSS_LOG_INFO("Set route %s next hop ID to NULL", ipPrefix.to_string().c_str()); if (ipPrefix.isV4()) + { v4_active_default_route_nhops.clear(); + } else + { v6_active_default_route_nhops.clear(); + } } else { From 7718dfa8b19dcbe90673f409947799fe6043883b Mon Sep 17 00:00:00 2001 From: Abhishek Dosi Date: Wed, 11 Dec 2024 09:49:32 +0000 Subject: [PATCH 13/16] Add the test case back Signed-off-by: Abhishek Dosi --- tests/test_nhg.py | 228 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 227 insertions(+), 1 deletion(-) diff --git a/tests/test_nhg.py b/tests/test_nhg.py index fdb4234ed5f..2b4b70a050c 100644 --- a/tests/test_nhg.py +++ b/tests/test_nhg.py @@ -1840,7 +1840,233 @@ def test_nhgorch_label_route(self, dvs, testlog): # Remove group1 self.nhg_ps._del("group1") self.asic_db.wait_for_n_keys(self.ASIC_NHG_STR, self.asic_nhgs_count) - + + def test_route_fallback_to_default_bothv4v6(self, dvs, dvs_route, testlog): + self.init_test(dvs, 6, True) + rtprefix_v6 = "2603:10b0::1/120" + defaultprefix_v6 = "::/0" + nexthop_str_v6 = "fc00::1,fc00::3,fc00::5" + default_nexthop_str_v6 = "fc00::7,fc00::9,fc00::b" + rtprefix = "3.3.3.0/24" + defaultprefix = "0.0.0.0/0" + nexthop_str = "10.0.0.1,10.0.0.3,10.0.0.5" + default_nexthop_str = "10.0.0.7,10.0.0.9,10.0.0.11" + + dvs_route.check_asicdb_deleted_route_entries([rtprefix, rtprefix_v6]) + + try: + dvs.disable_fpmsyncd() + # Program Regular Rouute with fallback to default + fvs = swsscommon.FieldValuePairs([("nexthop",nexthop_str), + ("ifname", "Ethernet0,Ethernet4,Ethernet8"), + ("fallback_to_default_route", "true")]) + self.rt_ps.set(rtprefix, fvs) + time.sleep(1) + + fvs = swsscommon.FieldValuePairs([("nexthop",nexthop_str_v6), + ("ifname", "Ethernet0,Ethernet4,Ethernet8"), + ("fallback_to_default_route", "true")]) + self.rt_ps.set(rtprefix_v6, fvs) + time.sleep(1) + + # Program default route + fvs = swsscommon.FieldValuePairs([("nexthop", default_nexthop_str), + ("ifname", "Ethernet12,Ethernet16,Ethernet20")]) + + self.rt_ps.set(defaultprefix, fvs) + time.sleep(1) + + fvs = swsscommon.FieldValuePairs([("nexthop", default_nexthop_str_v6), + ("ifname", "Ethernet12,Ethernet16,Ethernet20")]) + + self.rt_ps.set(defaultprefix_v6, fvs) + time.sleep(1) + + # check if route was propagated to ASIC DB + rtkeys = dvs_route.check_asicdb_route_entries([rtprefix, rtprefix_v6]) + + # check if default route was propagated to ASIC DB + defaultrtkeys = dvs_route.check_asicdb_route_entries([defaultprefix, defaultprefix_v6]) + + default_nhgid = [] + default_nhops = set() + default_nhgmids = set() + default_nhopsids = set() + default_parentnhgid = set() + + rt_nhgid = [] + rt_nhops = set() + rt_nhopsids = set() + rt_nhgmids = set() + rt_parentnhgid = set() + + # assert the route points to next hop group + flat_list = [] + for x in rtkeys: + flat_list.append(x) + for x in defaultrtkeys: + flat_list.append(x) + for idx, rtkey in enumerate(flat_list): + fvs = self.asic_db.get_entry(self.ASIC_RT_STR, rtkey) + if idx in [0,1]: + rt_nhgid.append(fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"]) + else: + default_nhgid.append(fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"]) + + nhgid = fvs["SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"] + + fvs = self.asic_db.get_entry(self.ASIC_NHG_STR, nhgid) + + assert bool(fvs) + + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + + assert len(keys) == 12 + + for k in keys: + fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) + nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] + nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) + + if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] in rt_nhgid: + rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) + rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) + rt_nhgmids.add(k) + rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) + elif fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] in default_nhgid: + default_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) + default_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) + default_nhgmids.add(k) + default_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) + + assert len(rt_parentnhgid) == 2 + assert len(rt_nhopsids) == 6 + assert len(rt_nhops) == 6 + assert len(rt_nhgmids) == 6 + + assert len(default_parentnhgid) == 2 + assert len(default_nhopsids) == 6 + assert len(default_nhops) == 6 + assert len(default_nhgmids) == 6 + + assert rt_nhops != default_nhops + assert rt_nhopsids != default_nhopsids + assert rt_parentnhgid != default_parentnhgid + assert rt_nhgmids != default_nhgmids + + # bring links down one-by-one + for i in [0, 1, 2]: + self.flap_intf(i, 'down') + time.sleep(1) + + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + + if i != 2: + assert len(keys) == 10 - (i * 2) + else: + # Last Link down so we will fallback to default eoute 3 members + assert len(keys) == 12 + rt_nhops.clear() + rt_nhgmids.clear() + rt_nhopsids.clear() + for k in keys: + fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) + nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] + nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) + + if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] in rt_nhgid: + rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) + rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) + rt_nhgmids.add(k) + rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) + elif fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] in default_nhgid: + default_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) + default_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) + default_nhgmids.add(k) + default_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) + + assert len(rt_parentnhgid) == 2 + assert len(rt_nhopsids) == 6 + assert len(rt_nhops) == 6 + assert len(rt_nhgmids) == 6 + + assert len(default_parentnhgid) == 2 + assert len(default_nhopsids) == 6 + assert len(default_nhops) == 6 + assert len(default_nhgmids) == 6 + + assert rt_nhops == default_nhops + assert rt_nhopsids == default_nhopsids + assert rt_nhgmids != default_nhgmids + + # bring links up one-by-one + # Bring link up in random order to verify sequence id is as per order + for i, val in enumerate([2,1,0]): + self.flap_intf(i, 'up') + time.sleep(1) + + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + + assert len(keys) == 12 + + for k in keys: + fvs = self.asic_db.get_entry(self.ASIC_NHGM_STR, k) + nhid = fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"] + nh_fvs = self.asic_db.get_entry("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP", nhid) + + if fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] in rt_nhgid: + rt_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) + rt_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) + rt_nhgmids.add(k) + rt_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) + elif fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"] in default_nhgid: + default_nhopsids.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"]) + default_nhops.add(nh_fvs["SAI_NEXT_HOP_ATTR_IP"]) + default_nhgmids.add(k) + default_parentnhgid.add(fvs["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID"]) + + assert len(rt_parentnhgid) == 2 + assert len(rt_nhopsids) == 6 + assert len(rt_nhops) == 6 + assert len(rt_nhgmids) == 6 + + assert len(default_parentnhgid) == 2 + assert len(default_nhopsids) == 6 + assert len(default_nhops) == 6 + assert len(default_nhgmids) == 6 + + assert rt_nhops == default_nhops + assert rt_nhopsids == default_nhopsids + assert rt_nhgmids != default_nhgmids + + # Remove route 2.2.2.0/24 + self.rt_ps._del(rtprefix) + time.sleep(1) + + self.rt_ps._del(rtprefix_v6) + time.sleep(1) + + # Wait for route 2.2.2.0/24 to be removed + dvs_route.check_asicdb_deleted_route_entries([rtprefix, rtprefix_v6]) + + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + + assert len(keys) == 6 + + # Remove route 0.0.0.0/0 + self.rt_ps._del(defaultprefix) + time.sleep(1) + + self.rt_ps._del(defaultprefix_v6) + time.sleep(1) + + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + + assert len(keys) == 0 + + finally: + dvs.start_fpmsyncd() + class TestCbfNextHopGroup(TestNextHopGroupBase): MAX_NHG_MAP_COUNT = 512 From ca0f0b9f452bcbc19e655d5e857661c413090506 Mon Sep 17 00:00:00 2001 From: Abhishek Dosi Date: Thu, 23 Jan 2025 20:51:38 +0000 Subject: [PATCH 14/16] Fix the nexthop delete SAI error after default swap. Signed-off-by: Abhishek Dosi --- orchagent/routeorch.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/orchagent/routeorch.cpp b/orchagent/routeorch.cpp index ad356b99d00..f3d8bc4af80 100644 --- a/orchagent/routeorch.cpp +++ b/orchagent/routeorch.cpp @@ -543,6 +543,14 @@ bool RouteOrch::invalidnexthopinNextHopGroup(const NextHopKey &nexthop, uint32_t continue; } + // Route NHOP Group is already swapped by default route nh memeber . do not delete actual nexthop again. + + if (nhopgroup->second.is_default_route_nh_swap) + { + continue; + } + + nexthop_id = nhopgroup->second.nhopgroup_members[nexthop].next_hop_id; status = sai_next_hop_group_api->remove_next_hop_group_member(nexthop_id); From 32f19a9a0a5075d40d0cf246b79d5d84d4addebc Mon Sep 17 00:00:00 2001 From: Abhishek Dosi Date: Fri, 24 Jan 2025 22:19:32 +0000 Subject: [PATCH 15/16] Ehnahnce UT to handle port toggle case Signed-off-by: Abhishek Dosi --- tests/test_nhg.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/test_nhg.py b/tests/test_nhg.py index 2b4b70a050c..2b71fb0993a 100644 --- a/tests/test_nhg.py +++ b/tests/test_nhg.py @@ -2039,6 +2039,26 @@ def test_route_fallback_to_default_bothv4v6(self, dvs, dvs_route, testlog): assert rt_nhopsids == default_nhopsids assert rt_nhgmids != default_nhgmids + # bring links up one-by-one + # Bring link up in random order to verify sequence id is as per order + for i, val in enumerate([2,1,0]): + self.flap_intf(i, 'down') + time.sleep(1) + + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + + assert len(keys) == 12 + + # bring links up one-by-one + # Bring link up in random order to verify sequence id is as per order + for i, val in enumerate([2,1,0]): + self.flap_intf(i, 'up') + time.sleep(1) + + keys = self.asic_db.get_keys(self.ASIC_NHGM_STR) + + assert len(keys) == 12 + # Remove route 2.2.2.0/24 self.rt_ps._del(rtprefix) time.sleep(1) From b5fdeda9256f87f5c87fe5f7fa43db2c9e65d7df Mon Sep 17 00:00:00 2001 From: Abhishek Dosi Date: Sat, 22 Mar 2025 02:36:30 +0000 Subject: [PATCH 16/16] Address Review Comments Signed-off-by: Abhishek Dosi --- orchagent/routeorch.cpp | 20 +++++++++++++++++++- tests/conftest.py | 2 +- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/orchagent/routeorch.cpp b/orchagent/routeorch.cpp index c380672388e..27205a9199a 100644 --- a/orchagent/routeorch.cpp +++ b/orchagent/routeorch.cpp @@ -407,6 +407,7 @@ void RouteOrch::updateDefaultRouteSwapSet(const NextHopGroupKey default_nhg_key, bool RouteOrch::addDefaultRouteNexthopsInNextHopGroup(NextHopGroupEntry& original_next_hop_group, std::set& default_route_next_hop_set) { + /* In the function we update the member of existing NexthopGroup to the Default Route Nexthop's */ SWSS_LOG_ENTER(); sai_object_id_t nexthop_group_member_id; sai_status_t status; @@ -517,6 +518,8 @@ bool RouteOrch::validnexthopinNextHopGroup(const NextHopKey &nexthop, uint32_t& ++count; gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_NEXTHOP_GROUP_MEMBER); nhopgroup->second.nhopgroup_members[nexthop].next_hop_id = nexthop_id; + /* Keep the count of number of nexthop members are present in Nexthop Group + * when the links became active again*/ nhopgroup->second.nh_member_install_count++; } @@ -566,11 +569,13 @@ bool RouteOrch::invalidnexthopinNextHopGroup(const NextHopKey &nexthop, uint32_t return parseHandleSaiStatusFailure(handle_status); } } + // Reduce the member install count when links down if (nhopgroup->second.nh_member_install_count) { nhopgroup->second.nh_member_install_count--; } - + // Nexthop Group member count has become zero so swap it's memebers with default route + // nexthop's if this route is eligible for such a swap if (nhopgroup->second.nh_member_install_count == 0 && nhopgroup->second.eligible_for_default_route_nh_swap && !nhopgroup->second.is_default_route_nh_swap) { if(nexthop.ip_address.isV4()) @@ -1171,6 +1176,8 @@ void RouteOrch::doTask(Consumer& consumer) else it_prev++; + // Save the Default Route of Default VRF to be used for + // enabling fallback to it as needed if (ip_prefix.isDefaultRoute() && vrf_id == gVirtualRouterId) { if (ip_prefix.isV4()) @@ -1213,14 +1220,17 @@ void RouteOrch::doTask(Consumer& consumer) { m_srv6Orch->removeSrv6Nexthops(m_bulkSrv6NhgReducedVec); } + /* No Update to Default Route so we can return */ if (!(v4_default_nhg_key.getSize()) && !(v6_default_nhg_key.getSize())) { return; } + /* Update to v4 Default Route so update the data structure */ if (v4_default_nhg_key.getSize()) { updateDefaultRouteSwapSet(v4_default_nhg_key, v4_active_default_route_nhops); } + /* Update to v6 Default Route so update the data structure */ if (v6_default_nhg_key.getSize()) { updateDefaultRouteSwapSet(v6_default_nhg_key, v6_active_default_route_nhops); @@ -1594,6 +1604,7 @@ bool RouteOrch::addNextHopGroup(const NextHopGroupKey &nexthops) { next_hop_group_entry.nhopgroup_members[nhopgroup_members_set.find(nhid)->second].next_hop_id = nhgm_id; next_hop_group_entry.nhopgroup_members[nhopgroup_members_set.find(nhid)->second].seq_id = ((uint32_t)i) + 1; + /* Keep the count of number of nexthop members are present in Nexthop Group*/ next_hop_group_entry.nh_member_install_count++; } } @@ -1633,9 +1644,14 @@ bool RouteOrch::removeNextHopGroup(const NextHopGroupKey &nexthops, const bool i SWSS_LOG_NOTICE("Delete next hop group %s", nexthops.to_string().c_str()); vector next_hop_ids; + /* If the NexthopGroup is the one that has been swapped with default route members + * than when deleting such Nexthop Group we have to remove default route nexthop group members */ auto& nhgm = is_default_route_nh_swap ? next_hop_group_entry->second.default_route_nhopgroup_members : next_hop_group_entry->second.nhopgroup_members; for (auto nhop = nhgm.begin(); nhop != nhgm.end();) { + /* This check we skip for Nexthop Group that has been swapped + * as Nexthop Group Members are not original member which are already removed + * as part of API invalidnexthopinNextHopGroup */ if (m_neighOrch->isNextHopFlagSet(nhop->first, NHFLAGS_IFDOWN) && (!is_default_route_nh_swap)) { SWSS_LOG_WARN("NHFLAGS_IFDOWN set for next hop group member %s with next_hop_id %" PRIx64, @@ -2148,6 +2164,8 @@ bool RouteOrch::addRoute(RouteBulkContext& ctx, const NextHopGroupKey &nextHops) } else { + /* Nexthop Creation Successful. So the save the state if eligible to fallback to default route + * based on APP_DB value for the route. Also initialize the present to False as swap did not happen */ m_syncdNextHopGroups[nextHops].eligible_for_default_route_nh_swap = ctx.fallback_to_default_route; m_syncdNextHopGroups[nextHops].is_default_route_nh_swap = false; } diff --git a/tests/conftest.py b/tests/conftest.py index b584c2ddfde..65d486bf0d8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -757,7 +757,7 @@ def start_fpmsyncd(self): self.runcmd(['sh', '-c', 'supervisorctl start fpmsyncd']) # Let's give fpmsyncd a chance to connect to Zebra. - time.sleep(10) + time.sleep(5) # deps: warm_reboot def stop_fpmsyncd(self):