From 85ff17da8ec7b750b668fb8d03d3494dcb51b555 Mon Sep 17 00:00:00 2001 From: Tyler Li Date: Thu, 7 Nov 2019 05:18:52 +0800 Subject: [PATCH] [VRF]: submit vrf feature (#943) Dataplane vrf feature --- cfgmgr/intfmgr.cpp | 161 ++- cfgmgr/intfmgr.h | 7 +- cfgmgr/nbrmgr.cpp | 23 +- cfgmgr/vrfmgr.cpp | 68 +- cfgmgr/vrfmgr.h | 5 +- doc/swss-schema.md | 28 +- fpmsyncd/routesync.cpp | 27 +- fpmsyncd/routesync.h | 2 +- neighsyncd/neighsync.cpp | 5 + orchagent/intfsorch.cpp | 234 ++-- orchagent/intfsorch.h | 6 +- orchagent/orchdaemon.cpp | 4 +- orchagent/request_parser.cpp | 4 +- orchagent/routeorch.cpp | 280 +++-- orchagent/routeorch.h | 27 +- orchagent/vnetorch.cpp | 2 +- orchagent/vrforch.cpp | 15 +- orchagent/vrforch.h | 66 +- tests/conftest.py | 6 + tests/mock_tests/aclorch_ut.cpp | 4 +- tests/test_acl.py | 88 ++ tests/test_interface.py | 1799 ++++++++++++++++++++++++++++--- tests/test_neighbor.py | 390 +++++++ tests/test_route.py | 627 ++++++++++- tests/test_vnet.py | 15 +- tests/test_vrf.py | 347 +++--- tests/test_warm_reboot.py | 123 +++ 27 files changed, 3695 insertions(+), 668 deletions(-) create mode 100644 tests/test_neighbor.py diff --git a/cfgmgr/intfmgr.cpp b/cfgmgr/intfmgr.cpp index 18f1d8e2f1ae..a84fe92a9e31 100644 --- a/cfgmgr/intfmgr.cpp +++ b/cfgmgr/intfmgr.cpp @@ -15,6 +15,9 @@ using namespace swss; #define LAG_PREFIX "PortChannel" #define LOOPBACK_PREFIX "Loopback" #define VNET_PREFIX "Vnet" +#define VRF_PREFIX "Vrf" + +#define LOOPBACK_DEFAULT_MTU_STR "65536" IntfMgr::IntfMgr(DBConnector *cfgDb, DBConnector *appDb, DBConnector *stateDb, const vector &tableNames) : Orch(cfgDb, tableNames), @@ -58,7 +61,7 @@ void IntfMgr::setIntfIp(const string &alias, const string &opCmd, } } -void IntfMgr::setIntfVrf(const string &alias, const string vrfName) +void IntfMgr::setIntfVrf(const string &alias, const string &vrfName) { stringstream cmd; string res; @@ -71,7 +74,97 @@ void IntfMgr::setIntfVrf(const string &alias, const string vrfName) { cmd << IP_CMD << " link set " << shellquote(alias) << " nomaster"; } - EXEC_WITH_ERROR_THROW(cmd.str(), res); + int ret = swss::exec(cmd.str(), res); + if (ret) + { + SWSS_LOG_ERROR("Command '%s' failed with rc %d", cmd.str().c_str(), ret); + } +} + +void IntfMgr::addLoopbackIntf(const string &alias) +{ + stringstream cmd; + string res; + + cmd << IP_CMD << " link add " << alias << " mtu " << LOOPBACK_DEFAULT_MTU_STR << " type dummy && "; + cmd << IP_CMD << " link set " << alias << " up"; + int ret = swss::exec(cmd.str(), res); + if (ret) + { + SWSS_LOG_ERROR("Command '%s' failed with rc %d", cmd.str().c_str(), ret); + } +} + +void IntfMgr::delLoopbackIntf(const string &alias) +{ + stringstream cmd; + string res; + + cmd << IP_CMD << " link del " << alias; + int ret = swss::exec(cmd.str(), res); + if (ret) + { + SWSS_LOG_ERROR("Command '%s' failed with rc %d", cmd.str().c_str(), ret); + } +} + +int IntfMgr::getIntfIpCount(const string &alias) +{ + stringstream cmd; + string res; + + /* query ip address of the device with master name, it is much faster */ + // ip address show {{intf_name}} + // $(ip link show {{intf_name}} | grep -o 'master [^\\s]*') ==> [master {{vrf_name}}] + // | grep inet | grep -v 'inet6 fe80:' | wc -l + cmd << IP_CMD << " address show " << alias + << " $(" << IP_CMD << " link show " << alias << " | grep -o 'master [^\\s]*')" + << " | grep inet | grep -v 'inet6 fe80:' | wc -l"; + + int ret = swss::exec(cmd.str(), res); + if (ret) + { + SWSS_LOG_ERROR("Command '%s' failed with rc %d", cmd.str().c_str(), ret); + return 0; + } + + return std::stoi(res); +} + +bool IntfMgr::isIntfCreated(const string &alias) +{ + vector temp; + + if (m_stateIntfTable.get(alias, temp)) + { + SWSS_LOG_DEBUG("Intf %s is ready", alias.c_str()); + return true; + } + + return false; +} + +bool IntfMgr::isIntfChangeVrf(const string &alias, const string &vrfName) +{ + vector temp; + + if (m_stateIntfTable.get(alias, temp)) + { + for (auto idx : temp) + { + const auto &field = fvField(idx); + const auto &value = fvValue(idx); + if (field == "vrf") + { + if (value == vrfName) + return false; + else + return true; + } + } + } + + return false; } bool IntfMgr::isIntfStateOk(const string &alias) @@ -102,6 +195,14 @@ bool IntfMgr::isIntfStateOk(const string &alias) return true; } } + else if (!alias.compare(0, strlen(VRF_PREFIX), VRF_PREFIX)) + { + if (m_stateVrfTable.get(alias, temp)) + { + SWSS_LOG_DEBUG("Vrf %s is ready", alias.c_str()); + return true; + } + } else if (m_statePortTable.get(alias, temp)) { SWSS_LOG_DEBUG("Port %s is ready", alias.c_str()); @@ -149,32 +250,38 @@ bool IntfMgr::doIntfGeneralTask(const vector& keys, return false; } - // Set Interface VRF except for lo - if (!is_lo) + /* if to change vrf then skip */ + if (isIntfChangeVrf(alias, vrf_name)) { - if (!vrf_name.empty()) - { - setIntfVrf(alias, vrf_name); - } - m_appIntfTableProducer.set(alias, data); + SWSS_LOG_ERROR("%s can not change to %s directly, skipping", alias.c_str(), vrf_name.c_str()); + return true; } - else + if (is_lo) { - m_appIntfTableProducer.set("lo", data); + addLoopbackIntf(alias); } + if (!vrf_name.empty()) + { + setIntfVrf(alias, vrf_name); + } + m_appIntfTableProducer.set(alias, data); + m_stateIntfTable.hset(alias, "vrf", vrf_name); } else if (op == DEL_COMMAND) { - // Set Interface VRF except for lo - if (!is_lo) + /* make sure all ip addresses associated with interface are removed, otherwise these ip address would + be set with global vrf and it may cause ip address confliction. */ + if (getIntfIpCount(alias)) { - setIntfVrf(alias, ""); - m_appIntfTableProducer.del(alias); + return false; } - else + setIntfVrf(alias, ""); + if (is_lo) { - m_appIntfTableProducer.del("lo"); + delLoopbackIntf(alias); } + m_appIntfTableProducer.del(alias); + m_stateIntfTable.del(alias); } else { @@ -192,26 +299,21 @@ bool IntfMgr::doIntfAddrTask(const vector& keys, string alias(keys[0]); IpPrefix ip_prefix(keys[1]); - bool is_lo = !alias.compare(0, strlen(LOOPBACK_PREFIX), LOOPBACK_PREFIX); - string appKey = (is_lo ? "lo" : keys[0]) + ":" + keys[1]; + string appKey = keys[0] + ":" + keys[1]; if (op == SET_COMMAND) { /* - * Don't proceed if port/LAG/VLAN is not ready yet. + * Don't proceed if port/LAG/VLAN and intfGeneral are not ready yet. * The pending task will be checked periodically and retried. */ - if (!isIntfStateOk(alias)) + if (!isIntfStateOk(alias) || !isIntfCreated(alias)) { SWSS_LOG_DEBUG("Interface is not ready, skipping %s", alias.c_str()); return false; } - // Set Interface IP except for lo - if (!is_lo) - { - setIntfIp(alias, "add", ip_prefix); - } + setIntfIp(alias, "add", ip_prefix); std::vector fvVector; FieldValueTuple f("family", ip_prefix.isV4() ? IPV4_NAME : IPV6_NAME); @@ -224,11 +326,8 @@ bool IntfMgr::doIntfAddrTask(const vector& keys, } else if (op == DEL_COMMAND) { - // Set Interface IP except for lo - if (!is_lo) - { - setIntfIp(alias, "del", ip_prefix); - } + setIntfIp(alias, "del", ip_prefix); + m_appIntfTableProducer.del(appKey); m_stateIntfTable.del(keys[0] + state_db_key_delimiter + keys[1]); } diff --git a/cfgmgr/intfmgr.h b/cfgmgr/intfmgr.h index a0746e723e94..4e82baef5e24 100644 --- a/cfgmgr/intfmgr.h +++ b/cfgmgr/intfmgr.h @@ -22,11 +22,16 @@ class IntfMgr : public Orch Table m_statePortTable, m_stateLagTable, m_stateVlanTable, m_stateVrfTable, m_stateIntfTable; void setIntfIp(const std::string &alias, const std::string &opCmd, const IpPrefix &ipPrefix); - void setIntfVrf(const std::string &alias, const std::string vrfName); + void setIntfVrf(const std::string &alias, const std::string &vrfName); bool doIntfGeneralTask(const std::vector& keys, const std::vector& data, const std::string& op); bool doIntfAddrTask(const std::vector& keys, const std::vector& data, const std::string& op); void doTask(Consumer &consumer); bool isIntfStateOk(const std::string &alias); + bool isIntfCreated(const std::string &alias); + bool isIntfChangeVrf(const std::string &alias, const std::string &vrfName); + int getIntfIpCount(const std::string &alias); + void addLoopbackIntf(const std::string &alias); + void delLoopbackIntf(const std::string &alias); }; } diff --git a/cfgmgr/nbrmgr.cpp b/cfgmgr/nbrmgr.cpp index e3080b7c01a7..e525a7f0af52 100644 --- a/cfgmgr/nbrmgr.cpp +++ b/cfgmgr/nbrmgr.cpp @@ -13,9 +13,6 @@ using namespace swss; -#define VLAN_PREFIX "Vlan" -#define LAG_PREFIX "PortChannel" - static bool send_message(struct nl_sock *sk, struct nl_msg *msg) { bool rc = false; @@ -67,25 +64,9 @@ bool NbrMgr::isIntfStateOk(const string &alias) { vector temp; - if (!alias.compare(0, strlen(VLAN_PREFIX), VLAN_PREFIX)) - { - if (m_stateVlanTable.get(alias, temp)) - { - SWSS_LOG_DEBUG("Vlan %s is ready", alias.c_str()); - return true; - } - } - else if (!alias.compare(0, strlen(LAG_PREFIX), LAG_PREFIX)) - { - if (m_stateLagTable.get(alias, temp)) - { - SWSS_LOG_DEBUG("Lag %s is ready", alias.c_str()); - return true; - } - } - else if (m_statePortTable.get(alias, temp)) + if (m_stateIntfTable.get(alias, temp)) { - SWSS_LOG_DEBUG("Port %s is ready", alias.c_str()); + SWSS_LOG_DEBUG("Intf %s is ready", alias.c_str()); return true; } diff --git a/cfgmgr/vrfmgr.cpp b/cfgmgr/vrfmgr.cpp index d8048a6b3513..d8718813ef1e 100644 --- a/cfgmgr/vrfmgr.cpp +++ b/cfgmgr/vrfmgr.cpp @@ -10,6 +10,7 @@ #define VRF_TABLE_START 1001 #define VRF_TABLE_END 2000 +#define TABLE_LOCAL_PREF 1001 // after l3mdev-table using namespace swss; @@ -17,7 +18,8 @@ VrfMgr::VrfMgr(DBConnector *cfgDb, DBConnector *appDb, DBConnector *stateDb, con Orch(cfgDb, tableNames), m_appVrfTableProducer(appDb, APP_VRF_TABLE_NAME), m_appVnetTableProducer(appDb, APP_VNET_TABLE_NAME), - m_stateVrfTable(stateDb, STATE_VRF_TABLE_NAME) + m_stateVrfTable(stateDb, STATE_VRF_TABLE_NAME), + m_stateVrfObjectTable(stateDb, STATE_VRF_OBJECT_TABLE_NAME) { for (uint32_t i = VRF_TABLE_START; i < VRF_TABLE_END; i++) { @@ -63,6 +65,18 @@ VrfMgr::VrfMgr(DBConnector *cfgDb, DBConnector *appDb, DBConnector *stateDb, con break; } } + + cmd.str(""); + cmd.clear(); + cmd << IP_CMD << " rule | grep '^0:'"; + if (swss::exec(cmd.str(), res) == 0) + { + cmd.str(""); + cmd.clear(); + cmd << IP_CMD << " rule add pref " << TABLE_LOCAL_PREF << " table local && " << IP_CMD << " rule del pref 0 && " + << IP_CMD << " -6 rule add pref " << TABLE_LOCAL_PREF << " table local && " << IP_CMD << " -6 rule del pref 0"; + EXEC_WITH_ERROR_THROW(cmd.str(), res); + } } uint32_t VrfMgr::getFreeTable(void) @@ -139,6 +153,19 @@ bool VrfMgr::setLink(const string& vrfName) return true; } +bool VrfMgr::isVrfObjExist(const string& vrfName) +{ + vector temp; + + if (m_stateVrfObjectTable.get(vrfName, temp)) + { + SWSS_LOG_DEBUG("Vrf %s object exist", vrfName.c_str()); + return true; + } + + return false; +} + void VrfMgr::doTask(Consumer &consumer) { SWSS_LOG_ENTER(); @@ -173,20 +200,43 @@ void VrfMgr::doTask(Consumer &consumer) } else if (op == DEL_COMMAND) { - if (!delLink(vrfName)) - { - SWSS_LOG_ERROR("Failed to remove vrf netdev %s", vrfName.c_str()); - } - - m_stateVrfTable.del(vrfName); - + /* + * Delay delLink until vrf object deleted in orchagent to ensure fpmsyncd can get vrf ifname. + * Now state VRF_TABLE|Vrf represent vrf exist in appDB, if it exist vrf device is always effective. + * VRFOrch add/del state VRF_OBJECT_TABLE|Vrf to represent object existence. VNETOrch is not do so now. + */ if (consumer.getTableName() == CFG_VRF_TABLE_NAME) { - m_appVrfTableProducer.del(vrfName); + vector temp; + + if (m_stateVrfTable.get(vrfName, temp)) + { + /* VRFOrch add delay so wait */ + if (!isVrfObjExist(vrfName)) + { + it++; + continue; + } + + m_appVrfTableProducer.del(vrfName); + m_stateVrfTable.del(vrfName); + } + + if (isVrfObjExist(vrfName)) + { + it++; + continue; + } } else { m_appVnetTableProducer.del(vrfName); + m_stateVrfTable.del(vrfName); + } + + if (!delLink(vrfName)) + { + SWSS_LOG_ERROR("Failed to remove vrf netdev %s", vrfName.c_str()); } SWSS_LOG_NOTICE("Removed vrf netdev %s", vrfName.c_str()); diff --git a/cfgmgr/vrfmgr.h b/cfgmgr/vrfmgr.h index aae82a906c14..f8da87d3186a 100644 --- a/cfgmgr/vrfmgr.h +++ b/cfgmgr/vrfmgr.h @@ -21,15 +21,16 @@ class VrfMgr : public Orch private: bool delLink(const std::string& vrfName); bool setLink(const std::string& vrfName); + bool isVrfObjExist(const std::string& vrfName); void recycleTable(uint32_t table); uint32_t getFreeTable(void); void handleVnetConfigSet(KeyOpFieldsValuesTuple &t); void doTask(Consumer &consumer); std::map m_vrfTableMap; - set m_freeTables; + std::set m_freeTables; - Table m_stateVrfTable; + Table m_stateVrfTable, m_stateVrfObjectTable; ProducerStateTable m_appVrfTableProducer, m_appVnetTableProducer; }; diff --git a/doc/swss-schema.md b/doc/swss-schema.md index b58d7cc81f44..8cb191a2311a 100644 --- a/doc/swss-schema.md +++ b/doc/swss-schema.md @@ -466,8 +466,10 @@ Stores rules associated with a specific ACL table on the switch. ; it could be: : name of physical port. Example: "Ethernet10" : name of LAG port Example: "PortChannel5" - : next-hop ip address Example: "10.0.0.1" - : next-hop group set of addresses Example: "10.0.0.1,10.0.0.3" + : next-hop ip address (in global) Example: "10.0.0.1" + : next-hop ip address and vrf Example: "10.0.0.2@Vrf2" + : next-hop ip address and ifname Example: "10.0.0.3@Ethernet1" + : next-hop group set of next-hop Example: "10.0.0.1,10.0.0.3@Ethernet1" mirror_action = 1*255VCHAR ; refer to the mirror session (by default it will be ingress mirror action) mirror_ingress_action = 1*255VCHAR ; refer to the mirror session @@ -878,6 +880,28 @@ Stores information for physical switch ports managed by the switch chip. Ports t full-date = date-fullyear "-" date-month "-" date-mday time-stamp = full-date %x20 partial-time +### INTERFACE_TABLE + ;State for interface status, including two types of key + + key = INTERFACE_TABLE|ifname ; ifname should be Ethernet,Portchannel,Vlan,Loopback + vrf = "" / vrf_name ; interface has been created, global or vrf + + key = INTERFACE_TABLE|ifname|IPprefix + state = "ok" ; IP address has been set to interface + +### VRF_TABLE + ;State for vrf status, vrfmgrd has written it to app_db + + key = VRF_TABLE|vrf_name ; vrf_name start with 'Vrf' or 'Vnet' prefix + state = "ok" ; vrf entry exist in app_db, if yes vrf device must exist + +### VRF_OBJECT_TABLE + ;State for vrf object status, vrf exist in vrforch + + key = VRF_OBJECT_TABLE|vrf_name ; vrf_name start with 'Vrf' prefix + state = "ok" ; vrf entry exist in orchagent + + ## Configuration files What configuration files should we have? Do apps, orch agent each need separate files? diff --git a/fpmsyncd/routesync.cpp b/fpmsyncd/routesync.cpp index 9e7a9adf4ef2..2250d26d1a54 100644 --- a/fpmsyncd/routesync.cpp +++ b/fpmsyncd/routesync.cpp @@ -16,6 +16,7 @@ using namespace swss; #define VXLAN_IF_NAME_PREFIX "Brvxlan" #define VNET_PREFIX "Vnet" +#define VRF_PREFIX "Vrf" RouteSync::RouteSync(RedisPipeline *pipeline) : m_routeTable(pipeline, APP_ROUTE_TABLE_NAME, true), @@ -60,13 +61,13 @@ void RouteSync::onMsg(int nlmsg_type, struct nl_object *obj) /* Otherwise, it is a regular route (include VRF route). */ else { - onRouteMsg(nlmsg_type, obj); + onRouteMsg(nlmsg_type, obj, master_name); } } else { - onRouteMsg(nlmsg_type, obj); + onRouteMsg(nlmsg_type, obj, NULL); } } @@ -74,15 +75,31 @@ void RouteSync::onMsg(int nlmsg_type, struct nl_object *obj) * Handle regular route (include VRF route) * @arg nlmsg_type Netlink message type * @arg obj Netlink object + * @arg vrf Vrf name */ -void RouteSync::onRouteMsg(int nlmsg_type, struct nl_object *obj) +void RouteSync::onRouteMsg(int nlmsg_type, struct nl_object *obj, char *vrf) { struct rtnl_route *route_obj = (struct rtnl_route *)obj; struct nl_addr *dip; - char destipprefix[MAX_ADDR_SIZE + 1] = {0}; + char destipprefix[IFNAMSIZ + MAX_ADDR_SIZE + 2] = {0}; + + if (vrf) + { + /* + * Now vrf device name is required to start with VRF_PREFIX, + * it is difficult to split vrf_name:ipv6_addr. + */ + if (memcmp(vrf, VRF_PREFIX, strlen(VRF_PREFIX))) + { + SWSS_LOG_ERROR("Invalid VRF name %s (ifindex %u)", vrf, rtnl_route_get_table(route_obj)); + return; + } + memcpy(destipprefix, vrf, strlen(vrf)); + destipprefix[strlen(vrf)] = ':'; + } dip = rtnl_route_get_dst(route_obj); - nl_addr2str(dip, destipprefix, MAX_ADDR_SIZE); + nl_addr2str(dip, destipprefix + strlen(destipprefix), MAX_ADDR_SIZE); SWSS_LOG_DEBUG("Receive new route message dest ip prefix: %s", destipprefix); /* diff --git a/fpmsyncd/routesync.h b/fpmsyncd/routesync.h index 8ab8fc9cb4df..d636ca134ab0 100644 --- a/fpmsyncd/routesync.h +++ b/fpmsyncd/routesync.h @@ -33,7 +33,7 @@ class RouteSync : public NetMsg struct nl_sock *m_nl_sock; /* Handle regular route (include VRF route) */ - void onRouteMsg(int nlmsg_type, struct nl_object *obj); + void onRouteMsg(int nlmsg_type, struct nl_object *obj, char *vrf); /* Handle vnet route */ void onVnetRouteMsg(int nlmsg_type, struct nl_object *obj, string vnet); diff --git a/neighsyncd/neighsync.cpp b/neighsyncd/neighsync.cpp index c1f83c3aac4a..69ca563f2ea9 100644 --- a/neighsyncd/neighsync.cpp +++ b/neighsyncd/neighsync.cpp @@ -70,6 +70,11 @@ void NeighSync::onMsg(int nlmsg_type, struct nl_object *obj) key+= ipStr; int state = rtnl_neigh_get_state(neigh); + if (state == NUD_NOARP) + { + return; + } + bool delete_key = false; if ((nlmsg_type == RTM_DELNEIGH) || (state == NUD_INCOMPLETE) || (state == NUD_FAILED)) diff --git a/orchagent/intfsorch.cpp b/orchagent/intfsorch.cpp index 44978658928e..fc84a397521a 100644 --- a/orchagent/intfsorch.cpp +++ b/orchagent/intfsorch.cpp @@ -35,6 +35,8 @@ const int intfsorch_pri = 35; #define RIF_FLEX_STAT_COUNTER_POLL_MSECS "1000" #define UPDATE_MAPS_SEC 1 +#define LOOPBACK_PREFIX "Loopback" + static const vector rifStatIds = { SAI_ROUTER_INTERFACE_STAT_IN_PACKETS, @@ -82,6 +84,22 @@ sai_object_id_t IntfsOrch::getRouterIntfsId(const string &alias) return port.m_rif_id; } +bool IntfsOrch::isPrefixSubnet(const IpPrefix &ip_prefix, const string &alias) +{ + if (m_syncdIntfses.find(alias) == m_syncdIntfses.end()) + { + return false; + } + for (auto &prefixIt: m_syncdIntfses[alias].ip_addresses) + { + if (prefixIt.getSubnet() == ip_prefix) + { + return true; + } + } + return false; +} + string IntfsOrch::getRouterIntfsAlias(const IpAddress &ip, const string &vrf_name) { sai_object_id_t vrf_id = gVirtualRouterId; @@ -174,13 +192,14 @@ bool IntfsOrch::setIntf(const string& alias, sai_object_id_t vrf_id, const IpPre auto it_intfs = m_syncdIntfses.find(alias); if (it_intfs == m_syncdIntfses.end()) { - if (addRouterIntfs(vrf_id, port)) + if (!ip_prefix && addRouterIntfs(vrf_id, port)) { gPortsOrch->increasePortRefCount(alias); IntfsEntry intfs_entry; intfs_entry.ref_count = 0; intfs_entry.vrf_id = vrf_id; m_syncdIntfses[alias] = intfs_entry; + m_vrfOrch->increaseVrfRefCount(vrf_id); } else { @@ -202,28 +221,36 @@ bool IntfsOrch::setIntf(const string& alias, sai_object_id_t vrf_id, const IpPre * delete IP with netmask /8. To handle this we in case of overlap * we should wait until entry with /8 netmask will be removed. * Time frame between those event is quite small.*/ + /* NOTE: Overlap checking in this interface is not enough. + * So extend to check in all interfaces of this VRF */ bool overlaps = false; - for (const auto &prefixIt: m_syncdIntfses[alias].ip_addresses) + for (const auto &intfsIt: m_syncdIntfses) { - if (prefixIt.isAddressInSubnet(ip_prefix->getIp()) || - ip_prefix->isAddressInSubnet(prefixIt.getIp())) + if (port.m_vr_id != intfsIt.second.vrf_id) { - overlaps = true; - SWSS_LOG_NOTICE("Router interface %s IP %s overlaps with %s.", port.m_alias.c_str(), - prefixIt.to_string().c_str(), ip_prefix->to_string().c_str()); - break; + continue; } - } - if (overlaps) - { - /* Overlap of IP address network */ - return false; + for (const auto &prefixIt: intfsIt.second.ip_addresses) + { + if (prefixIt.isAddressInSubnet(ip_prefix->getIp()) || + ip_prefix->isAddressInSubnet(prefixIt.getIp())) + { + overlaps = true; + SWSS_LOG_NOTICE("Router interface %s IP %s overlaps with %s.", port.m_alias.c_str(), + prefixIt.to_string().c_str(), ip_prefix->to_string().c_str()); + break; + } + } + + if (overlaps) + { + /* Overlap of IP address network */ + return false; + } } - vrf_id = port.m_vr_id; - addSubnetRoute(port, *ip_prefix); - addIp2MeRoute(vrf_id, *ip_prefix); + addIp2MeRoute(port.m_vr_id, *ip_prefix); if (port.m_type == Port::VLAN) { @@ -246,8 +273,7 @@ bool IntfsOrch::removeIntf(const string& alias, sai_object_id_t vrf_id, const Ip if (ip_prefix && m_syncdIntfses[alias].ip_addresses.count(*ip_prefix)) { - removeSubnetRoute(port, *ip_prefix); - removeIp2MeRoute(vrf_id, *ip_prefix); + removeIp2MeRoute(port.m_vr_id, *ip_prefix); if(port.m_type == Port::VLAN) { @@ -257,13 +283,13 @@ bool IntfsOrch::removeIntf(const string& alias, sai_object_id_t vrf_id, const Ip m_syncdIntfses[alias].ip_addresses.erase(*ip_prefix); } - /* Remove router interface that no IP addresses are associated with */ - if (m_syncdIntfses[alias].ip_addresses.size() == 0) + if (!ip_prefix) { - if (removeRouterIntfs(port)) + if (m_syncdIntfses[alias].ip_addresses.size() == 0 && removeRouterIntfs(port)) { gPortsOrch->decreasePortRefCount(alias); m_syncdIntfses.erase(alias); + m_vrfOrch->decreaseVrfRefCount(vrf_id); return true; } else @@ -293,6 +319,7 @@ void IntfsOrch::doTask(Consumer &consumer) string alias(keys[0]); IpPrefix ip_prefix; bool ip_prefix_in_key = false; + bool is_lo = !alias.compare(0, strlen(LOOPBACK_PREFIX), LOOPBACK_PREFIX); if (keys.size() > 1) { @@ -337,53 +364,37 @@ void IntfsOrch::doTask(Consumer &consumer) string op = kfvOp(t); if (op == SET_COMMAND) { - if (alias == "lo") + if (is_lo) { if (!ip_prefix_in_key) { - it = consumer.m_toSync.erase(it); - continue; - } - - bool addIp2Me = false; - // set request for lo may come after warm start restore. - // It is also to prevent dupicate set requests in normal running case. - auto it_intfs = m_syncdIntfses.find(alias); - if (it_intfs == m_syncdIntfses.end()) - { - IntfsEntry intfs_entry; - - intfs_entry.ref_count = 0; - intfs_entry.vrf_id = vrf_id; - intfs_entry.ip_addresses.insert(ip_prefix); - m_syncdIntfses[alias] = intfs_entry; - addIp2Me = true; + if (m_syncdIntfses.find(alias) == m_syncdIntfses.end()) + { + IntfsEntry intfs_entry; + intfs_entry.ref_count = 0; + intfs_entry.vrf_id = vrf_id; + m_syncdIntfses[alias] = intfs_entry; + m_vrfOrch->increaseVrfRefCount(vrf_id); + } } else { - if (m_syncdIntfses[alias].ip_addresses.count(ip_prefix) == 0) - { + if (m_syncdIntfses.find(alias) == m_syncdIntfses.end()) + { + it++; + continue; + } + if (m_syncdIntfses[alias].ip_addresses.count(ip_prefix) == 0) + { m_syncdIntfses[alias].ip_addresses.insert(ip_prefix); - addIp2Me = true; - } - } - if (addIp2Me) - { - addIp2MeRoute(vrf_id, ip_prefix); + addIp2MeRoute(m_syncdIntfses[alias].vrf_id, ip_prefix); + } } it = consumer.m_toSync.erase(it); continue; } - /* Wait for the Interface entry first */ - auto it_intfs = m_syncdIntfses.find(alias); - if (ip_prefix_in_key && it_intfs == m_syncdIntfses.end()) - { - it++; - continue; - } - Port port; if (!gPortsOrch->getPort(alias, port)) { @@ -429,19 +440,33 @@ void IntfsOrch::doTask(Consumer &consumer) } else if (op == DEL_COMMAND) { - if (alias == "lo") + if (is_lo) { - // TODO: handle case for which lo is not in default vrf gVirtualRouterId - if (m_syncdIntfses.find(alias) != m_syncdIntfses.end()) + if (!ip_prefix_in_key) { - if (m_syncdIntfses[alias].ip_addresses.count(ip_prefix)) + if (m_syncdIntfses.find(alias) != m_syncdIntfses.end()) { - m_syncdIntfses[alias].ip_addresses.erase(ip_prefix); - removeIp2MeRoute(vrf_id, ip_prefix); + if (m_syncdIntfses[alias].ip_addresses.size() == 0) + { + m_vrfOrch->decreaseVrfRefCount(m_syncdIntfses[alias].vrf_id); + m_syncdIntfses.erase(alias); + } + else + { + it++; + continue; + } } - if (m_syncdIntfses[alias].ip_addresses.size() == 0) + } + else + { + if (m_syncdIntfses.find(alias) != m_syncdIntfses.end()) { - m_syncdIntfses.erase(alias); + if (m_syncdIntfses[alias].ip_addresses.count(ip_prefix)) + { + m_syncdIntfses[alias].ip_addresses.erase(ip_prefix); + removeIp2MeRoute(m_syncdIntfses[alias].vrf_id, ip_prefix); + } } } @@ -616,89 +641,6 @@ bool IntfsOrch::removeRouterIntfs(Port &port) return true; } -void IntfsOrch::addSubnetRoute(const Port &port, const IpPrefix &ip_prefix) -{ - sai_route_entry_t unicast_route_entry; - unicast_route_entry.switch_id = gSwitchId; - unicast_route_entry.vr_id = port.m_vr_id; - copy(unicast_route_entry.destination, ip_prefix); - subnet(unicast_route_entry.destination, unicast_route_entry.destination); - - sai_attribute_t attr; - vector attrs; - - attr.id = SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION; - attr.value.s32 = SAI_PACKET_ACTION_FORWARD; - attrs.push_back(attr); - - attr.id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; - attr.value.oid = port.m_rif_id; - attrs.push_back(attr); - - sai_status_t status = sai_route_api->create_route_entry(&unicast_route_entry, (uint32_t)attrs.size(), attrs.data()); - if (status != SAI_STATUS_SUCCESS) - { - SWSS_LOG_ERROR("Failed to create subnet route to %s from %s, rv:%d", - ip_prefix.to_string().c_str(), port.m_alias.c_str(), status); - throw runtime_error("Failed to create subnet route."); - } - - SWSS_LOG_NOTICE("Create subnet route to %s from %s", - ip_prefix.to_string().c_str(), port.m_alias.c_str()); - increaseRouterIntfsRefCount(port.m_alias); - - if (unicast_route_entry.destination.addr_family == SAI_IP_ADDR_FAMILY_IPV4) - { - gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_IPV4_ROUTE); - } - else - { - gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_IPV6_ROUTE); - } - - gRouteOrch->notifyNextHopChangeObservers(ip_prefix, NextHopGroupKey(), true); -} - -void IntfsOrch::removeSubnetRoute(const Port &port, const IpPrefix &ip_prefix) -{ - sai_route_entry_t unicast_route_entry; - unicast_route_entry.switch_id = gSwitchId; - unicast_route_entry.vr_id = port.m_vr_id; - copy(unicast_route_entry.destination, ip_prefix); - subnet(unicast_route_entry.destination, unicast_route_entry.destination); - - sai_status_t status = sai_route_api->remove_route_entry(&unicast_route_entry); - if (status != SAI_STATUS_SUCCESS) - { - if (status == SAI_STATUS_ITEM_NOT_FOUND) - { - SWSS_LOG_ERROR("No subnet route found for %s", ip_prefix.to_string().c_str()); - return; - } - else - { - SWSS_LOG_ERROR("Failed to remove subnet route to %s from %s, rv:%d", - ip_prefix.to_string().c_str(), port.m_alias.c_str(), status); - throw runtime_error("Failed to remove subnet route."); - } - } - - SWSS_LOG_NOTICE("Remove subnet route to %s from %s", - ip_prefix.to_string().c_str(), port.m_alias.c_str()); - decreaseRouterIntfsRefCount(port.m_alias); - - if (unicast_route_entry.destination.addr_family == SAI_IP_ADDR_FAMILY_IPV4) - { - gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_IPV4_ROUTE); - } - else - { - gCrmOrch->decCrmResUsedCounter(CrmResourceType::CRM_IPV6_ROUTE); - } - - gRouteOrch->notifyNextHopChangeObservers(ip_prefix, NextHopGroupKey(), false); -} - void IntfsOrch::addIp2MeRoute(sai_object_id_t vrf_id, const IpPrefix &ip_prefix) { sai_route_entry_t unicast_route_entry; diff --git a/orchagent/intfsorch.h b/orchagent/intfsorch.h index acf1276a6272..5ac0d047ed3e 100644 --- a/orchagent/intfsorch.h +++ b/orchagent/intfsorch.h @@ -33,6 +33,7 @@ class IntfsOrch : public Orch IntfsOrch(DBConnector *db, string tableName, VRFOrch *vrf_orch); sai_object_id_t getRouterIntfsId(const string&); + bool isPrefixSubnet(const IpPrefix&, const string&); string getRouterIntfsAlias(const IpAddress &ip, const string &vrf_name = ""); void increaseRouterIntfsRefCount(const string&); @@ -78,14 +79,9 @@ class IntfsOrch : public Orch std::string getRifFlexCounterTableKey(std::string s); - int getRouterIntfsRefCount(const string&); - bool addRouterIntfs(sai_object_id_t vrf_id, Port &port); bool removeRouterIntfs(Port &port); - void addSubnetRoute(const Port &port, const IpPrefix &ip_prefix); - void removeSubnetRoute(const Port &port, const IpPrefix &ip_prefix); - void addDirectedBroadcast(const Port &port, const IpPrefix &ip_prefix); void removeDirectedBroadcast(const Port &port, const IpPrefix &ip_prefix); }; diff --git a/orchagent/orchdaemon.cpp b/orchagent/orchdaemon.cpp index df151f996630..ada19d1494db 100644 --- a/orchagent/orchdaemon.cpp +++ b/orchagent/orchdaemon.cpp @@ -110,7 +110,7 @@ bool OrchDaemon::init() gDirectory.set(cfg_vnet_rt_orch); VNetRouteOrch *vnet_rt_orch = new VNetRouteOrch(m_applDb, vnet_tables, vnet_orch); gDirectory.set(vnet_rt_orch); - VRFOrch *vrf_orch = new VRFOrch(m_applDb, APP_VRF_TABLE_NAME); + VRFOrch *vrf_orch = new VRFOrch(m_applDb, APP_VRF_TABLE_NAME, m_stateDb, STATE_VRF_OBJECT_TABLE_NAME); gDirectory.set(vrf_orch); const vector chassis_frontend_tables = { @@ -121,7 +121,7 @@ bool OrchDaemon::init() gIntfsOrch = new IntfsOrch(m_applDb, APP_INTF_TABLE_NAME, vrf_orch); gNeighOrch = new NeighOrch(m_applDb, APP_NEIGH_TABLE_NAME, gIntfsOrch); - gRouteOrch = new RouteOrch(m_applDb, APP_ROUTE_TABLE_NAME, gNeighOrch); + gRouteOrch = new RouteOrch(m_applDb, APP_ROUTE_TABLE_NAME, gNeighOrch, gIntfsOrch, vrf_orch); TableConnector confDbSflowTable(m_configDb, CFG_SFLOW_TABLE_NAME); TableConnector appCoppTable(m_applDb, APP_COPP_TABLE_NAME); diff --git a/orchagent/request_parser.cpp b/orchagent/request_parser.cpp index cdd74c3d4033..59676a01f34d 100644 --- a/orchagent/request_parser.cpp +++ b/orchagent/request_parser.cpp @@ -115,9 +115,9 @@ void Request::parseAttrs(const KeyOpFieldsValuesTuple& request) for (auto i = kfvFieldsValues(request).begin(); i != kfvFieldsValues(request).end(); i++) { - if (fvField(*i) == "empty") + if (fvField(*i) == "empty" || fvField(*i) == "NULL") { - // if name of the attribute is 'empty', just skip it. + // if name of the attribute is 'empty' or 'NULL', just skip it. // it's used when we don't have any attributes, but we have to provide one for redis continue; } diff --git a/orchagent/routeorch.cpp b/orchagent/routeorch.cpp index 1b611ca8902f..8aabef4a9e55 100644 --- a/orchagent/routeorch.cpp +++ b/orchagent/routeorch.cpp @@ -13,7 +13,6 @@ extern sai_route_api_t* sai_route_api; extern sai_switch_api_t* sai_switch_api; extern PortsOrch *gPortsOrch; -extern IntfsOrch *gIntfsOrch; extern CrmOrch *gCrmOrch; /* Default maximum number of next hop groups */ @@ -22,9 +21,11 @@ extern CrmOrch *gCrmOrch; const int routeorch_pri = 5; -RouteOrch::RouteOrch(DBConnector *db, string tableName, NeighOrch *neighOrch) : +RouteOrch::RouteOrch(DBConnector *db, string tableName, NeighOrch *neighOrch, IntfsOrch *intfsOrch, VRFOrch *vrfOrch) : Orch(db, tableName, routeorch_pri), m_neighOrch(neighOrch), + m_intfsOrch(intfsOrch), + m_vrfOrch(vrfOrch), m_nextHopGroupCount(0), m_resync(false) { @@ -82,7 +83,7 @@ RouteOrch::RouteOrch(DBConnector *db, string tableName, NeighOrch *neighOrch) : gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_IPV4_ROUTE); /* Add default IPv4 route into the m_syncdRoutes */ - m_syncdRoutes[default_ip_prefix] = NextHopGroupKey(); + m_syncdRoutes[gVirtualRouterId][default_ip_prefix] = NextHopGroupKey(); SWSS_LOG_NOTICE("Create IPv4 default route with packet action drop"); @@ -101,7 +102,7 @@ RouteOrch::RouteOrch(DBConnector *db, string tableName, NeighOrch *neighOrch) : gCrmOrch->incCrmResUsedCounter(CrmResourceType::CRM_IPV6_ROUTE); /* Add default IPv6 route into the m_syncdRoutes */ - m_syncdRoutes[v6_default_ip_prefix] = NextHopGroupKey(); + m_syncdRoutes[gVirtualRouterId][v6_default_ip_prefix] = NextHopGroupKey(); SWSS_LOG_NOTICE("Create IPv6 default route with packet action drop"); @@ -202,39 +203,32 @@ sai_object_id_t RouteOrch::getNextHopGroupId(const NextHopGroupKey& nexthops) return m_syncdNextHopGroups[nexthops].next_hop_group_id; } -void RouteOrch::attach(Observer *observer, const IpAddress& dstAddr) +void RouteOrch::attach(Observer *observer, const IpAddress& dstAddr, sai_object_id_t vrf_id) { SWSS_LOG_ENTER(); - auto observerEntry = m_nextHopObservers.find(dstAddr); + Host host = std::make_pair(vrf_id, dstAddr); + auto observerEntry = m_nextHopObservers.find(host); /* Create a new observer entry if no current observer is observing this * IP address */ if (observerEntry == m_nextHopObservers.end()) { - m_nextHopObservers.emplace(dstAddr, NextHopObserverEntry()); - observerEntry = m_nextHopObservers.find(dstAddr); + m_nextHopObservers.emplace(host, NextHopObserverEntry()); + observerEntry = m_nextHopObservers.find(host); /* Find the prefixes that cover the destination IP */ - for (auto route : m_syncdRoutes) + if (m_syncdRoutes.find(vrf_id) != m_syncdRoutes.end()) { - if (route.first.isAddressInSubnet(dstAddr)) + for (auto route : m_syncdRoutes.at(vrf_id)) { - SWSS_LOG_INFO("Prefix %s covers destination address", - route.first.to_string().c_str()); - observerEntry->second.routeTable.emplace( - route.first, route.second); - } - } - - /* Find the subnets that cover the destination IP - * The next hop of the subnet routes is left empty */ - for (auto prefix : gIntfsOrch->getSubnetRoutes()) - { - if (prefix.isAddressInSubnet(dstAddr)) - { - observerEntry->second.routeTable.emplace( - prefix, NextHopGroupKey()); + if (route.first.isAddressInSubnet(dstAddr)) + { + SWSS_LOG_INFO("Prefix %s covers destination address", + route.first.to_string().c_str()); + observerEntry->second.routeTable.emplace( + route.first, route.second); + } } } } @@ -249,16 +243,16 @@ void RouteOrch::attach(Observer *observer, const IpAddress& dstAddr) SWSS_LOG_NOTICE("Attached next hop observer of route %s for destination IP %s", observerEntry->second.routeTable.rbegin()->first.to_string().c_str(), dstAddr.to_string().c_str()); - NextHopUpdate update = { dstAddr, route->first, route->second }; + NextHopUpdate update = { vrf_id, dstAddr, route->first, route->second }; observer->update(SUBJECT_TYPE_NEXTHOP_CHANGE, static_cast(&update)); } } -void RouteOrch::detach(Observer *observer, const IpAddress& dstAddr) +void RouteOrch::detach(Observer *observer, const IpAddress& dstAddr, sai_object_id_t vrf_id) { SWSS_LOG_ENTER(); - auto observerEntry = m_nextHopObservers.find(dstAddr); + auto observerEntry = m_nextHopObservers.find(std::make_pair(vrf_id, dstAddr)); if (observerEntry == m_nextHopObservers.end()) { @@ -398,11 +392,22 @@ void RouteOrch::doTask(Consumer& consumer) { /* Mark all current routes as dirty (DEL) in consumer.m_toSync map */ SWSS_LOG_NOTICE("Start resync routes\n"); - for (auto i : m_syncdRoutes) + for (auto j : m_syncdRoutes) { - vector v; - auto x = KeyOpFieldsValuesTuple(i.first.to_string(), DEL_COMMAND, v); - consumer.m_toSync[i.first.to_string()] = x; + string vrf; + + if (j.first != gVirtualRouterId) + { + vrf = m_vrfOrch->getVRFname(j.first) + ":"; + } + + for (auto i : j.second) + { + vector v; + key = vrf + i.first.to_string(); + auto x = KeyOpFieldsValuesTuple(key, DEL_COMMAND, v); + consumer.m_toSync[key] = x; + } } m_resync = true; } @@ -422,7 +427,27 @@ void RouteOrch::doTask(Consumer& consumer) continue; } - IpPrefix ip_prefix = IpPrefix(key); + sai_object_id_t vrf_id; + IpPrefix ip_prefix; + + if (!key.compare(0, strlen(VRF_PREFIX), VRF_PREFIX)) + { + size_t found = key.find(':'); + string vrf_name = key.substr(0, found); + + if (!m_vrfOrch->isVRFexists(vrf_name)) + { + it++; + continue; + } + vrf_id = m_vrfOrch->getVRFid(vrf_name); + ip_prefix = IpPrefix(key.substr(found+1)); + } + else + { + vrf_id = gVirtualRouterId; + ip_prefix = IpPrefix(key); + } if (op == SET_COMMAND) { @@ -455,15 +480,10 @@ void RouteOrch::doTask(Consumer& consumer) { /* If any existing routes are updated to point to the * above interfaces, remove them from the ASIC. */ - if (m_syncdRoutes.find(ip_prefix) != m_syncdRoutes.end()) - { - if (removeRoute(ip_prefix)) - it = consumer.m_toSync.erase(it); - else - it++; - } - else + if (removeRoute(vrf_id, ip_prefix)) it = consumer.m_toSync.erase(it); + else + it++; continue; } @@ -477,13 +497,43 @@ void RouteOrch::doTask(Consumer& consumer) if (ipv.size() == 1 && IpAddress(ipv[0]).isZero()) { - it = consumer.m_toSync.erase(it); + /* blackhole to be done */ + if (alsv[0] == "unknown") + { + /* add addBlackholeRoute or addRoute support empty nhg */ + it = consumer.m_toSync.erase(it); + } + /* directly connected route to VRF interface which come from kernel */ + else if (!alsv[0].compare(0, strlen(VRF_PREFIX), VRF_PREFIX)) + { + it = consumer.m_toSync.erase(it); + } + /* skip prefix which is linklocal or multicast */ + else if (ip_prefix.getIp().getAddrScope() != IpAddress::GLOBAL_SCOPE) + { + it = consumer.m_toSync.erase(it); + } + /* fullmask subnet route is same as ip2me route */ + else if (ip_prefix.isFullMask() && m_intfsOrch->isPrefixSubnet(ip_prefix, alsv[0])) + { + it = consumer.m_toSync.erase(it); + } + /* subnet route, vrf leaked route, etc */ + else + { + if (addRoute(vrf_id, ip_prefix, nhg)) + it = consumer.m_toSync.erase(it); + else + it++; + } continue; } - if (m_syncdRoutes.find(ip_prefix) == m_syncdRoutes.end() || m_syncdRoutes[ip_prefix] != nhg) + if (m_syncdRoutes.find(vrf_id) == m_syncdRoutes.end() || + m_syncdRoutes.at(vrf_id).find(ip_prefix) == m_syncdRoutes.at(vrf_id).end() || + m_syncdRoutes.at(vrf_id).at(ip_prefix) != nhg) { - if (addRoute(ip_prefix, nhg)) + if (addRoute(vrf_id, ip_prefix, nhg)) it = consumer.m_toSync.erase(it); else it++; @@ -494,16 +544,11 @@ void RouteOrch::doTask(Consumer& consumer) } else if (op == DEL_COMMAND) { - if (m_syncdRoutes.find(ip_prefix) != m_syncdRoutes.end()) - { - if (removeRoute(ip_prefix)) - it = consumer.m_toSync.erase(it); - else - it++; - } - else - /* Cannot locate the route */ + /* Cannot locate the route or remove succeed */ + if (removeRoute(vrf_id, ip_prefix)) it = consumer.m_toSync.erase(it); + else + it++; } else { @@ -513,13 +558,13 @@ void RouteOrch::doTask(Consumer& consumer) } } -void RouteOrch::notifyNextHopChangeObservers(const IpPrefix &prefix, const NextHopGroupKey &nexthops, bool add) +void RouteOrch::notifyNextHopChangeObservers(sai_object_id_t vrf_id, const IpPrefix &prefix, const NextHopGroupKey &nexthops, bool add) { SWSS_LOG_ENTER(); for (auto& entry : m_nextHopObservers) { - if (!prefix.isAddressInSubnet(entry.first)) + if (vrf_id != entry.first.first || !prefix.isAddressInSubnet(entry.first.second)) { continue; } @@ -527,7 +572,7 @@ void RouteOrch::notifyNextHopChangeObservers(const IpPrefix &prefix, const NextH if (add) { bool update_required = false; - NextHopUpdate update = { entry.first, prefix, nexthops }; + NextHopUpdate update = { vrf_id, entry.first.second, prefix, nexthops }; /* Table should not be empty. Default route should always exists. */ assert(!entry.second.routeTable.empty()); @@ -578,7 +623,7 @@ void RouteOrch::notifyNextHopChangeObservers(const IpPrefix &prefix, const NextH assert(!entry.second.routeTable.empty()); auto route = entry.second.routeTable.rbegin(); - NextHopUpdate update = { entry.first, route->first, route->second }; + NextHopUpdate update = { vrf_id, entry.first.second, route->first, route->second }; for (auto observer : entry.second.observers) { @@ -604,7 +649,10 @@ void RouteOrch::increaseNextHopRefCount(const NextHopGroupKey &nexthops) else if (nexthops.getSize() == 1) { NextHopKey nexthop(nexthops.to_string()); - m_neighOrch->increaseNextHopRefCount(nexthop); + if (nexthop.ip_address.isZero()) + m_intfsOrch->increaseRouterIntfsRefCount(nexthop.alias); + else + m_neighOrch->increaseNextHopRefCount(nexthop); } else { @@ -621,7 +669,10 @@ void RouteOrch::decreaseNextHopRefCount(const NextHopGroupKey &nexthops) else if (nexthops.getSize() == 1) { NextHopKey nexthop(nexthops.to_string()); - m_neighOrch->decreaseNextHopRefCount(nexthop); + if (nexthop.ip_address.isZero()) + m_intfsOrch->decreaseRouterIntfsRefCount(nexthop.alias); + else + m_neighOrch->decreaseNextHopRefCount(nexthop); } else { @@ -816,7 +867,7 @@ bool RouteOrch::removeNextHopGroup(const NextHopGroupKey &nexthops) return true; } -void RouteOrch::addTempRoute(const IpPrefix &ipPrefix, const NextHopGroupKey &nextHops) +void RouteOrch::addTempRoute(sai_object_id_t vrf_id, const IpPrefix &ipPrefix, const NextHopGroupKey &nextHops) { SWSS_LOG_ENTER(); @@ -845,30 +896,51 @@ void RouteOrch::addTempRoute(const IpPrefix &ipPrefix, const NextHopGroupKey &ne /* Set the route's temporary next hop to be the randomly picked one */ NextHopGroupKey tmp_next_hop((*it).to_string()); - addRoute(ipPrefix, tmp_next_hop); + addRoute(vrf_id, ipPrefix, tmp_next_hop); } -bool RouteOrch::addRoute(const IpPrefix &ipPrefix, const NextHopGroupKey &nextHops) +bool RouteOrch::addRoute(sai_object_id_t vrf_id, const IpPrefix &ipPrefix, const NextHopGroupKey &nextHops) { SWSS_LOG_ENTER(); /* next_hop_id indicates the next hop id or next hop group id of this route */ sai_object_id_t next_hop_id; - auto it_route = m_syncdRoutes.find(ipPrefix); + + if (m_syncdRoutes.find(vrf_id) == m_syncdRoutes.end()) + { + m_syncdRoutes.emplace(vrf_id, RouteTable()); + m_vrfOrch->increaseVrfRefCount(vrf_id); + } + + auto it_route = m_syncdRoutes.at(vrf_id).find(ipPrefix); /* The route is pointing to a next hop */ if (nextHops.getSize() == 1) { NextHopKey nexthop(nextHops.to_string()); - if (m_neighOrch->hasNextHop(nexthop)) + if (nexthop.ip_address.isZero()) { - next_hop_id = m_neighOrch->getNextHopId(nexthop); + next_hop_id = m_intfsOrch->getRouterIntfsId(nexthop.alias); + /* rif is not created yet */ + if (next_hop_id == SAI_NULL_OBJECT_ID) + { + SWSS_LOG_INFO("Failed to get next hop %s for %s", + nextHops.to_string().c_str(), ipPrefix.to_string().c_str()); + return false; + } } else { - SWSS_LOG_INFO("Failed to get next hop %s for %s", - nextHops.to_string().c_str(), ipPrefix.to_string().c_str()); - return false; + if (m_neighOrch->hasNextHop(nexthop)) + { + next_hop_id = m_neighOrch->getNextHopId(nexthop); + } + else + { + SWSS_LOG_INFO("Failed to get next hop %s for %s", + nextHops.to_string().c_str(), ipPrefix.to_string().c_str()); + return false; + } } } /* The route is pointing to a next hop group */ @@ -884,7 +956,7 @@ bool RouteOrch::addRoute(const IpPrefix &ipPrefix, const NextHopGroupKey &nextHo /* If the current next hop is part of the next hop group to sync, * then return false and no need to add another temporary route. */ - if (it_route != m_syncdRoutes.end() && it_route->second.getSize() == 1) + if (it_route != m_syncdRoutes.at(vrf_id).end() && it_route->second.getSize() == 1) { NextHopKey nexthop(it_route->second.to_string()); if (nextHops.contains(nexthop)) @@ -896,7 +968,7 @@ bool RouteOrch::addRoute(const IpPrefix &ipPrefix, const NextHopGroupKey &nextHo /* Add a temporary route when a next hop group cannot be added, * and there is no temporary route right now or the current temporary * route is not pointing to a member of the next hop group to sync. */ - addTempRoute(ipPrefix, nextHops); + addTempRoute(vrf_id, ipPrefix, nextHops); /* Return false since the original route is not successfully added */ return false; } @@ -907,7 +979,7 @@ bool RouteOrch::addRoute(const IpPrefix &ipPrefix, const NextHopGroupKey &nextHo /* Sync the route entry */ sai_route_entry_t route_entry; - route_entry.vr_id = gVirtualRouterId; + route_entry.vr_id = vrf_id; route_entry.switch_id = gSwitchId; copy(route_entry.destination, ipPrefix); @@ -919,7 +991,7 @@ bool RouteOrch::addRoute(const IpPrefix &ipPrefix, const NextHopGroupKey &nextHo * (group) id. The old next hop (group) is then not used and the reference * count will decrease by 1. */ - if (it_route == m_syncdRoutes.end()) + if (it_route == m_syncdRoutes.at(vrf_id).end()) { route_attr.id = SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID; route_attr.value.oid = next_hop_id; @@ -996,18 +1068,33 @@ bool RouteOrch::addRoute(const IpPrefix &ipPrefix, const NextHopGroupKey &nextHo ipPrefix.to_string().c_str(), nextHops.to_string().c_str()); } - m_syncdRoutes[ipPrefix] = nextHops; + m_syncdRoutes[vrf_id][ipPrefix] = nextHops; - notifyNextHopChangeObservers(ipPrefix, nextHops, true); + notifyNextHopChangeObservers(vrf_id, ipPrefix, nextHops, true); return true; } -bool RouteOrch::removeRoute(const IpPrefix &ipPrefix) +bool RouteOrch::removeRoute(sai_object_id_t vrf_id, const IpPrefix &ipPrefix) { SWSS_LOG_ENTER(); + auto it_route_table = m_syncdRoutes.find(vrf_id); + if (it_route_table == m_syncdRoutes.end()) + { + SWSS_LOG_INFO("Failed to find route table, vrf_id 0x%lx\n", vrf_id); + return true; + } + + auto it_route = it_route_table->second.find(ipPrefix); + if (it_route == it_route_table->second.end()) + { + SWSS_LOG_INFO("Failed to find route entry, vrf_id 0x%lx, prefix %s\n", vrf_id, + ipPrefix.to_string().c_str()); + return true; + } + sai_route_entry_t route_entry; - route_entry.vr_id = gVirtualRouterId; + route_entry.vr_id = vrf_id; route_entry.switch_id = gSwitchId; copy(route_entry.destination, ipPrefix); @@ -1060,39 +1147,42 @@ bool RouteOrch::removeRoute(const IpPrefix &ipPrefix) } } - /* Remove next hop group entry if ref_count is zero */ - auto it_route = m_syncdRoutes.find(ipPrefix); - if (it_route != m_syncdRoutes.end()) + + /* + * Decrease the reference count only when the route is pointing to a next hop. + * Decrease the reference count when the route is pointing to a next hop group, + * and check whether the reference count decreases to zero. If yes, then we need + * to remove the next hop group. + */ + decreaseNextHopRefCount(it_route->second); + if (it_route->second.getSize() > 1 + && m_syncdNextHopGroups[it_route->second].ref_count == 0) { - /* - * Decrease the reference count only when the route is pointing to a next hop. - * Decrease the reference count when the route is pointing to a next hop group, - * and check whether the reference count decreases to zero. If yes, then we need - * to remove the next hop group. - */ - decreaseNextHopRefCount(it_route->second); - if (it_route->second.getSize() > 1 - && m_syncdNextHopGroups[it_route->second].ref_count == 0) - { - removeNextHopGroup(it_route->second); - } + removeNextHopGroup(it_route->second); } + SWSS_LOG_INFO("Remove route %s with next hop(s) %s", ipPrefix.to_string().c_str(), it_route->second.to_string().c_str()); if (ipPrefix.isDefaultRoute()) { - m_syncdRoutes[ipPrefix] = NextHopGroupKey(); + it_route_table->second[ipPrefix] = NextHopGroupKey(); /* Notify about default route next hop change */ - notifyNextHopChangeObservers(ipPrefix, m_syncdRoutes[ipPrefix], true); + notifyNextHopChangeObservers(vrf_id, ipPrefix, it_route_table->second[ipPrefix], true); } else { - m_syncdRoutes.erase(ipPrefix); + it_route_table->second.erase(ipPrefix); /* Notify about the route next hop removal */ - notifyNextHopChangeObservers(ipPrefix, NextHopGroupKey(), false); + notifyNextHopChangeObservers(vrf_id, ipPrefix, NextHopGroupKey(), false); + + if (it_route_table->second.size() == 0) + { + m_syncdRoutes.erase(vrf_id); + m_vrfOrch->decreaseVrfRefCount(vrf_id); + } } return true; diff --git a/orchagent/routeorch.h b/orchagent/routeorch.h index e00223245c23..84550d758962 100644 --- a/orchagent/routeorch.h +++ b/orchagent/routeorch.h @@ -29,6 +29,7 @@ struct NextHopGroupEntry struct NextHopUpdate { + sai_object_id_t vrf_id; IpAddress destination; IpPrefix prefix; NextHopGroupKey nexthopGroup; @@ -40,8 +41,12 @@ struct NextHopObserverEntry; typedef std::map NextHopGroupTable; /* RouteTable: destination network, NextHopGroupKey */ typedef std::map RouteTable; -/* NextHopObserverTable: Destination IP address, next hop observer entry */ -typedef std::map NextHopObserverTable; +/* RouteTables: vrf_id, RouteTable */ +typedef std::map RouteTables; +/* Host: vrf_id, IpAddress */ +typedef std::pair Host; +/* NextHopObserverTable: Host, next hop observer entry */ +typedef std::map NextHopObserverTable; struct NextHopObserverEntry { @@ -52,13 +57,13 @@ struct NextHopObserverEntry class RouteOrch : public Orch, public Subject { public: - RouteOrch(DBConnector *db, string tableName, NeighOrch *neighOrch); + RouteOrch(DBConnector *db, string tableName, NeighOrch *neighOrch, IntfsOrch *intfsOrch, VRFOrch *vrfOrch); bool hasNextHopGroup(const NextHopGroupKey&) const; sai_object_id_t getNextHopGroupId(const NextHopGroupKey&); - void attach(Observer *, const IpAddress&); - void detach(Observer *, const IpAddress&); + void attach(Observer *, const IpAddress&, sai_object_id_t vrf_id = gVirtualRouterId); + void detach(Observer *, const IpAddress&, sai_object_id_t vrf_id = gVirtualRouterId); void increaseNextHopRefCount(const NextHopGroupKey&); void decreaseNextHopRefCount(const NextHopGroupKey&); @@ -70,22 +75,24 @@ class RouteOrch : public Orch, public Subject bool validnexthopinNextHopGroup(const NextHopKey&); bool invalidnexthopinNextHopGroup(const NextHopKey&); - void notifyNextHopChangeObservers(const IpPrefix&, const NextHopGroupKey&, bool); + void notifyNextHopChangeObservers(sai_object_id_t, const IpPrefix&, const NextHopGroupKey&, bool); private: NeighOrch *m_neighOrch; + IntfsOrch *m_intfsOrch; + VRFOrch *m_vrfOrch; int m_nextHopGroupCount; int m_maxNextHopGroupCount; bool m_resync; - RouteTable m_syncdRoutes; + RouteTables m_syncdRoutes; NextHopGroupTable m_syncdNextHopGroups; NextHopObserverTable m_nextHopObservers; - void addTempRoute(const IpPrefix&, const NextHopGroupKey&); - bool addRoute(const IpPrefix&, const NextHopGroupKey&); - bool removeRoute(const IpPrefix&); + void addTempRoute(sai_object_id_t, const IpPrefix&, const NextHopGroupKey&); + bool addRoute(sai_object_id_t, const IpPrefix&, const NextHopGroupKey&); + bool removeRoute(sai_object_id_t, const IpPrefix&); std::string getLinkLocalEui64Addr(void); void addLinkLocalRouteToMe(sai_object_id_t vrf_id, IpPrefix linklocal_prefix); diff --git a/orchagent/vnetorch.cpp b/orchagent/vnetorch.cpp index 684d77343332..6552f79cc5b0 100644 --- a/orchagent/vnetorch.cpp +++ b/orchagent/vnetorch.cpp @@ -1748,7 +1748,7 @@ bool VNetRouteOrch::doRouteTask(const string& vnet, IpPrefix& ipP } else if (vr_id == port.m_vr_id) { - vr_set.insert(vrf_obj->getVRidEgress()); + vr_set = vrf_obj->getVRids(); } else { diff --git a/orchagent/vrforch.cpp b/orchagent/vrforch.cpp index 8258aacc9082..62b56e1e0e27 100644 --- a/orchagent/vrforch.cpp +++ b/orchagent/vrforch.cpp @@ -14,7 +14,6 @@ using namespace std; using namespace swss; - extern sai_virtual_router_api_t* sai_virtual_router_api; extern sai_object_id_t gSwitchId; @@ -82,14 +81,17 @@ bool VRFOrch::addOperation(const Request& request) return false; } - vrf_table_[vrf_name] = router_id; + vrf_table_[vrf_name].vrf_id = router_id; + vrf_table_[vrf_name].ref_count = 0; + vrf_id_table_[router_id] = vrf_name; + m_stateVrfObjectTable.hset(vrf_name, "state", "ok"); SWSS_LOG_NOTICE("VRF '%s' was added", vrf_name.c_str()); } else { // Update an existing vrf - sai_object_id_t router_id = it->second; + sai_object_id_t router_id = it->second.vrf_id; for (const auto& attr: attrs) { @@ -118,7 +120,10 @@ bool VRFOrch::delOperation(const Request& request) return true; } - sai_object_id_t router_id = vrf_table_[vrf_name]; + if (vrf_table_[vrf_name].ref_count) + return false; + + sai_object_id_t router_id = vrf_table_[vrf_name].vrf_id; sai_status_t status = sai_virtual_router_api->remove_virtual_router(router_id); if (status != SAI_STATUS_SUCCESS) { @@ -127,6 +132,8 @@ bool VRFOrch::delOperation(const Request& request) } vrf_table_.erase(vrf_name); + vrf_id_table_.erase(router_id); + m_stateVrfObjectTable.del(vrf_name); SWSS_LOG_NOTICE("VRF '%s' was removed", vrf_name.c_str()); diff --git a/orchagent/vrforch.h b/orchagent/vrforch.h index 7512c51a411a..41ad55a003a1 100644 --- a/orchagent/vrforch.h +++ b/orchagent/vrforch.h @@ -4,7 +4,15 @@ #include "request_parser.h" extern sai_object_id_t gVirtualRouterId; -typedef std::unordered_map VRFTable; + +struct VrfEntry +{ + sai_object_id_t vrf_id; + int ref_count; +}; + +typedef std::unordered_map VRFTable; +typedef std::unordered_map VRFIdNameTable; const request_description_t request_description = { { REQ_T_STRING }, @@ -30,7 +38,9 @@ class VRFRequest : public Request class VRFOrch : public Orch2 { public: - VRFOrch(swss::DBConnector *db, const std::string& tableName) : Orch2(db, tableName, request_) + VRFOrch(swss::DBConnector *appDb, const std::string& appTableName, swss::DBConnector *stateDb, const std::string& stateTableName) : + Orch2(appDb, appTableName, request_), + m_stateVrfObjectTable(stateDb, stateTableName) { } @@ -43,7 +53,7 @@ class VRFOrch : public Orch2 { if (vrf_table_.find(name) != std::end(vrf_table_)) { - return vrf_table_.at(name); + return vrf_table_.at(name).vrf_id; } else { @@ -51,12 +61,62 @@ class VRFOrch : public Orch2 } } + std::string getVRFname(sai_object_id_t vrf_id) const + { + if (vrf_id == gVirtualRouterId) + { + return std::string(""); + } + if (vrf_id_table_.find(vrf_id) != std::end(vrf_id_table_)) + { + return vrf_id_table_.at(vrf_id); + } + else + { + return std::string(""); + } + } + + void increaseVrfRefCount(const std::string& name) + { + if (vrf_table_.find(name) != std::end(vrf_table_)) + { + vrf_table_.at(name).ref_count++; + } + } + + void increaseVrfRefCount(sai_object_id_t vrf_id) + { + if (vrf_id != gVirtualRouterId) + { + increaseVrfRefCount(getVRFname(vrf_id)); + } + } + + void decreaseVrfRefCount(const std::string& name) + { + if (vrf_table_.find(name) != std::end(vrf_table_)) + { + vrf_table_.at(name).ref_count--; + } + } + + void decreaseVrfRefCount(sai_object_id_t vrf_id) + { + if (vrf_id != gVirtualRouterId) + { + decreaseVrfRefCount(getVRFname(vrf_id)); + } + } + private: virtual bool addOperation(const Request& request); virtual bool delOperation(const Request& request); VRFTable vrf_table_; + VRFIdNameTable vrf_id_table_; VRFRequest request_; + swss::Table m_stateVrfObjectTable; }; #endif // __VRFORCH_H diff --git a/tests/conftest.py b/tests/conftest.py index 7ae579222971..8fba25d8d8d5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -754,6 +754,7 @@ def remove_ip_address(self, interface, ip): tbl_name = "INTERFACE" tbl = swsscommon.Table(self.cdb, tbl_name) tbl._del(interface + "|" + ip); + tbl._del(interface); time.sleep(1) def set_mtu(self, interface, mtu): @@ -775,6 +776,11 @@ def add_neighbor(self, interface, ip, mac): tbl.set(interface + ":" + ip, fvs) time.sleep(1) + def remove_neighbor(self, interface, ip): + tbl = swsscommon.ProducerStateTable(self.pdb, "NEIGH_TABLE") + tbl._del(interface + ":" + ip) + time.sleep(1) + def setup_db(self): self.pdb = swsscommon.DBConnector(0, self.redis_sock, 0) self.adb = swsscommon.DBConnector(1, self.redis_sock, 0) diff --git a/tests/mock_tests/aclorch_ut.cpp b/tests/mock_tests/aclorch_ut.cpp index ae4267ddb5db..c6541dbded51 100644 --- a/tests/mock_tests/aclorch_ut.cpp +++ b/tests/mock_tests/aclorch_ut.cpp @@ -352,7 +352,7 @@ namespace aclorch_test gCrmOrch = new CrmOrch(m_config_db.get(), CFG_CRM_TABLE_NAME); ASSERT_EQ(gVrfOrch, nullptr); - gVrfOrch = new VRFOrch(m_app_db.get(), APP_VRF_TABLE_NAME); + gVrfOrch = new VRFOrch(m_app_db.get(), APP_VRF_TABLE_NAME, m_state_db.get(), STATE_VRF_OBJECT_TABLE_NAME); ASSERT_EQ(gIntfsOrch, nullptr); gIntfsOrch = new IntfsOrch(m_app_db.get(), APP_INTF_TABLE_NAME, gVrfOrch); @@ -361,7 +361,7 @@ namespace aclorch_test gNeighOrch = new NeighOrch(m_app_db.get(), APP_NEIGH_TABLE_NAME, gIntfsOrch); ASSERT_EQ(gRouteOrch, nullptr); - gRouteOrch = new RouteOrch(m_app_db.get(), APP_ROUTE_TABLE_NAME, gNeighOrch); + gRouteOrch = new RouteOrch(m_app_db.get(), APP_ROUTE_TABLE_NAME, gNeighOrch, gIntfsOrch, gVrfOrch); TableConnector applDbFdb(m_app_db.get(), APP_FDB_TABLE_NAME); TableConnector stateDbFdb(m_state_db.get(), STATE_FDB_TABLE_NAME); diff --git a/tests/test_acl.py b/tests/test_acl.py index 5cfeb49b4e2d..83909b7c816d 100644 --- a/tests/test_acl.py +++ b/tests/test_acl.py @@ -1215,6 +1215,94 @@ def test_AclRuleIcmpV6(self, dvs, testlog): self.remove_acl_table(acl_table) + def test_AclRuleRedirectToNexthop(self, dvs, testlog): + dvs.setup_db() + self.setup_db(dvs) + + # bring up interface + dvs.set_interface_status("Ethernet4", "up") + + # assign IP to interface + dvs.add_ip_address("Ethernet4", "10.0.0.1/24") + + # add neighbor + dvs.add_neighbor("Ethernet4", "10.0.0.2", "00:01:02:03:04:05") + + atbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP") + keys = atbl.getKeys() + assert len(keys) == 1 + nhi_oid = keys[0] + + # create ACL_TABLE in config db + tbl = swsscommon.Table(self.cdb, "ACL_TABLE") + fvs = swsscommon.FieldValuePairs([("policy_desc", "test"), ("type", "L3"), ("ports", "Ethernet0")]) + tbl.set("test_redirect", fvs) + + time.sleep(2) + + # create acl rule + tbl = swsscommon.Table(self.cdb, "ACL_RULE") + fvs = swsscommon.FieldValuePairs([ + ("priority", "100"), + ("L4_SRC_PORT", "65000"), + ("PACKET_ACTION", "REDIRECT:10.0.0.2@Ethernet4")]) + tbl.set("test_redirect|test_rule1", fvs) + + time.sleep(1) + + test_acl_table_id = self.get_acl_table_id(dvs) + + # check acl table in asic db + atbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ACL_ENTRY") + keys = atbl.getKeys() + + acl_entry = [k for k in keys if k not in dvs.asicdb.default_acl_entries] + assert len(acl_entry) == 1 + + (status, fvs) = atbl.get(acl_entry[0]) + assert status == True + assert len(fvs) == 6 + for fv in fvs: + if fv[0] == "SAI_ACL_ENTRY_ATTR_TABLE_ID": + assert fv[1] == test_acl_table_id + elif fv[0] == "SAI_ACL_ENTRY_ATTR_ADMIN_STATE": + assert fv[1] == "true" + elif fv[0] == "SAI_ACL_ENTRY_ATTR_PRIORITY": + assert fv[1] == "100" + elif fv[0] == "SAI_ACL_ENTRY_ATTR_ACTION_COUNTER": + assert True + elif fv[0] == "SAI_ACL_ENTRY_ATTR_FIELD_L4_SRC_PORT": + assert fv[1] == "65000&mask:0xffff" + elif fv[0] == "SAI_ACL_ENTRY_ATTR_ACTION_REDIRECT": + assert fv[1] == nhi_oid + else: + assert False + + # remove acl rule + tbl._del("test_redirect|test_rule1") + + time.sleep(1) + + (status, fvs) = atbl.get(acl_entry[0]) + assert status == False + + tbl = swsscommon.Table(self.cdb, "ACL_TABLE") + tbl._del("test_redirect") + + time.sleep(1) + + atbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ACL_TABLE") + keys = atbl.getKeys() + assert len(keys) >= 1 + + # remove neighbor + dvs.remove_neighbor("Ethernet4", "10.0.0.2") + + # remove interface ip + dvs.remove_ip_address("Ethernet4", "10.0.0.1/24") + + # bring down interface + dvs.set_interface_status("Ethernet4", "down") class TestAclRuleValidation(BaseTestAcl): """ Test class for cases that check if orchagent corectly validates diff --git a/tests/test_interface.py b/tests/test_interface.py index 0ff67dcc00a5..5abe7eb9d062 100644 --- a/tests/test_interface.py +++ b/tests/test_interface.py @@ -9,41 +9,134 @@ def setup_db(self, dvs): self.adb = swsscommon.DBConnector(1, dvs.redis_sock, 0) self.cdb = swsscommon.DBConnector(4, dvs.redis_sock, 0) - def set_admin_status(self, interface, status): - tbl = swsscommon.Table(self.cdb, "PORT") + def set_admin_status(self, dvs, interface, status): + if interface.startswith("PortChannel"): + tbl_name = "PORTCHANNEL" + elif interface.startswith("Vlan"): + tbl_name = "VLAN" + else: + tbl_name = "PORT" + tbl = swsscommon.Table(self.cdb, tbl_name) fvs = swsscommon.FieldValuePairs([("admin_status", status)]) tbl.set(interface, fvs) time.sleep(1) + # when using FRR, route cannot be inserted if the neighbor is not + # connected. thus it is mandatory to force the interface up manually + if interface.startswith("PortChannel"): + dvs.runcmd("bash -c 'echo " + ("1" if status == "up" else "0") +\ + " > /sys/class/net/" + interface + "/carrier'") + time.sleep(1) + + def create_vrf(self, vrf_name): + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER") + initial_entries = set(tbl.getKeys()) + + tbl = swsscommon.Table(self.cdb, "VRF") + fvs = swsscommon.FieldValuePairs([('empty', 'empty')]) + tbl.set(vrf_name, fvs) + time.sleep(1) + + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER") + current_entries = set(tbl.getKeys()) + assert len(current_entries - initial_entries) == 1 + return list(current_entries - initial_entries)[0] + + def remove_vrf(self, vrf_name): + tbl = swsscommon.Table(self.cdb, "VRF") + tbl._del(vrf_name) + time.sleep(1) + + def create_l3_intf(self, interface, vrf_name): + if interface.startswith("PortChannel"): + tbl_name = "PORTCHANNEL_INTERFACE" + elif interface.startswith("Vlan"): + tbl_name = "VLAN_INTERFACE" + elif interface.startswith("Loopback"): + tbl_name = "LOOPBACK_INTERFACE" + else: + tbl_name = "INTERFACE" + if len(vrf_name) == 0: + fvs = swsscommon.FieldValuePairs([("NULL", "NULL")]) + else: + fvs = swsscommon.FieldValuePairs([("vrf_name", vrf_name)]) + tbl = swsscommon.Table(self.cdb, tbl_name) + tbl.set(interface, fvs) + time.sleep(1) + + def remove_l3_intf(self, interface): + if interface.startswith("PortChannel"): + tbl_name = "PORTCHANNEL_INTERFACE" + elif interface.startswith("Vlan"): + tbl_name = "VLAN_INTERFACE" + elif interface.startswith("Loopback"): + tbl_name = "LOOPBACK_INTERFACE" + else: + tbl_name = "INTERFACE" + tbl = swsscommon.Table(self.cdb, tbl_name) + tbl._del(interface) + time.sleep(1) + def add_ip_address(self, interface, ip): - tbl = swsscommon.Table(self.cdb, "INTERFACE") + if interface.startswith("PortChannel"): + tbl_name = "PORTCHANNEL_INTERFACE" + elif interface.startswith("Vlan"): + tbl_name = "VLAN_INTERFACE" + elif interface.startswith("Loopback"): + tbl_name = "LOOPBACK_INTERFACE" + else: + tbl_name = "INTERFACE" + tbl = swsscommon.Table(self.cdb, tbl_name) fvs = swsscommon.FieldValuePairs([("NULL", "NULL")]) tbl.set(interface + "|" + ip, fvs) - tbl.set(interface, fvs) - time.sleep(2) # IPv6 netlink message needs longer time + time.sleep(1) def remove_ip_address(self, interface, ip): - tbl = swsscommon.Table(self.cdb, "INTERFACE") - tbl._del(interface + "|" + ip); - tbl._del(interface); + if interface.startswith("PortChannel"): + tbl_name = "PORTCHANNEL_INTERFACE" + elif interface.startswith("Vlan"): + tbl_name = "VLAN_INTERFACE" + elif interface.startswith("Loopback"): + tbl_name = "LOOPBACK_INTERFACE" + else: + tbl_name = "INTERFACE" + tbl = swsscommon.Table(self.cdb, tbl_name) + tbl._del(interface + "|" + ip) time.sleep(1) def set_mtu(self, interface, mtu): - tbl = swsscommon.Table(self.cdb, "PORT") + if interface.startswith("PortChannel"): + tbl_name = "PORTCHANNEL" + elif interface.startswith("Vlan"): + tbl_name = "VLAN" + else: + tbl_name = "PORT" + tbl = swsscommon.Table(self.cdb, tbl_name) fvs = swsscommon.FieldValuePairs([("mtu", mtu)]) tbl.set(interface, fvs) time.sleep(1) - def test_InterfaceAddRemoveIpv6Address(self, dvs, testlog): + def test_PortInterfaceAddRemoveIpv6Address(self, dvs, testlog): self.setup_db(dvs) + # create interface + self.create_l3_intf("Ethernet8", "") + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + (status, fvs) = tbl.get("Ethernet8") + assert status == True + for fv in fvs: + assert fv[0] != "vrf_name" + # bring up interface # NOTE: For IPv6, only when the interface is up will the netlink message # get generated. - self.set_admin_status("Ethernet8", "up") + self.set_admin_status(dvs, "Ethernet8", "up") # assign IP to interface self.add_ip_address("Ethernet8", "fc00::1/126") + time.sleep(2) # IPv6 netlink message needs longer time # check application database tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Ethernet8") @@ -94,14 +187,22 @@ def test_InterfaceAddRemoveIpv6Address(self, dvs, testlog): # remove IP from interface self.remove_ip_address("Ethernet8", "fc00::1/126") + # remove interface + self.remove_l3_intf("Ethernet8") + # bring down interface - self.set_admin_status("Ethernet8", "down") + self.set_admin_status(dvs, "Ethernet8", "down") # check application database tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Ethernet8") intf_entries = tbl.getKeys() assert len(intf_entries) == 0 + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + intf_entries = tbl.getKeys() + for entry in intf_entries: + assert entry[0] != "Ethernet8" + # check ASIC database tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") for key in tbl.getKeys(): @@ -111,9 +212,22 @@ def test_InterfaceAddRemoveIpv6Address(self, dvs, testlog): if route["dest"] == "fc00::1/128": assert False - def test_InterfaceAddRemoveIpv4Address(self, dvs, testlog): + def test_PortInterfaceAddRemoveIpv4Address(self, dvs, testlog): self.setup_db(dvs) + # bring up interface + self.set_admin_status(dvs, "Ethernet8", "up") + + # create interface + self.create_l3_intf("Ethernet8", "") + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + (status, fvs) = tbl.get("Ethernet8") + assert status == True + for fv in fvs: + assert fv[0] != "vrf_name" + # assign IP to interface self.add_ip_address("Ethernet8", "10.0.0.4/31") @@ -166,11 +280,22 @@ def test_InterfaceAddRemoveIpv4Address(self, dvs, testlog): # remove IP from interface self.remove_ip_address("Ethernet8", "10.0.0.4/31") + # remove interface + self.remove_l3_intf("Ethernet8") + + # bring down interface + self.set_admin_status(dvs, "Ethernet8", "down") + # check application database tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Ethernet8") intf_entries = tbl.getKeys() assert len(intf_entries) == 0 + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + intf_entries = tbl.getKeys() + for entry in intf_entries: + assert entry[0] != "Ethernet8" + # check ASIC database tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") for key in tbl.getKeys(): @@ -180,9 +305,19 @@ def test_InterfaceAddRemoveIpv4Address(self, dvs, testlog): if route["dest"] == "10.0.0.4/32": assert False - def test_InterfaceSetMtu(self, dvs, testlog): + def test_PortInterfaceSetMtu(self, dvs, testlog): self.setup_db(dvs) + # create interface + self.create_l3_intf("Ethernet16", "") + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + (status, fvs) = tbl.get("Ethernet16") + assert status == True + for fv in fvs: + assert fv[0] != "vrf_name" + # assign IP to interface self.add_ip_address("Ethernet16", "20.0.0.8/29") @@ -210,69 +345,65 @@ def test_InterfaceSetMtu(self, dvs, testlog): # remove IP from interface self.remove_ip_address("Ethernet16", "20.0.0.8/29") -class TestLagRouterInterfaceIpv4(object): - def setup_db(self, dvs): - self.pdb = swsscommon.DBConnector(0, dvs.redis_sock, 0) - self.adb = swsscommon.DBConnector(1, dvs.redis_sock, 0) - self.cdb = swsscommon.DBConnector(4, dvs.redis_sock, 0) + # remove interface + self.remove_l3_intf("Ethernet16") - def create_port_channel(self, dvs, alias): - tbl = swsscommon.Table(self.cdb, "PORTCHANNEL") - fvs = swsscommon.FieldValuePairs([("admin_status", "up"), - ("mtu", "9100")]) - tbl.set(alias, fvs) - time.sleep(1) + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Ethernet16") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 - def remove_port_channel(self, dvs, alias): - tbl = swsscommon.Table(self.cdb, "PORTCHANNEL") - tbl._del(alias) - time.sleep(1) + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + intf_entries = tbl.getKeys() + for entry in intf_entries: + assert entry[0] != "Ethernet16" - def add_port_channel_members(self, dvs, lag, members): - tbl = swsscommon.Table(self.cdb, "PORTCHANNEL_MEMBER") - fvs = swsscommon.FieldValuePairs([("NULL", "NULL")]) - for member in members: - tbl.set(lag + "|" + member, fvs) - time.sleep(1) + # check ASIC database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "20.0.0.8/29": + assert False + if route["dest"] == "20.0.0.8/32": + assert False - def remove_port_channel_members(self, dvs, lag, members): - tbl = swsscommon.Table(self.cdb, "PORTCHANNEL_MEMBER") - for member in members: - tbl._del(lag + "|" + member) - time.sleep(1) + def test_PortInterfaceAddRemoveIpv6AddressWithVrf(self, dvs, testlog): + self.setup_db(dvs) - def add_ip_address(self, interface, ip): - tbl = swsscommon.Table(self.cdb, "PORTCHANNEL_INTERFACE") - fvs = swsscommon.FieldValuePairs([("NULL", "NULL")]) - tbl.set(interface + "|" + ip, fvs) - tbl.set(interface, fvs) - time.sleep(1) + # bring up interface + self.set_admin_status(dvs, "Ethernet8", "up") - def remove_ip_address(self, interface, ip): - tbl = swsscommon.Table(self.cdb, "PORTCHANNEL_INTERFACE") - tbl._del(interface + "|" + ip); - time.sleep(1) + # create vrf + vrf_oid = self.create_vrf("Vrf_0") - def set_mtu(self, interface, mtu): - tbl = swsscommon.Table(self.cdb, "PORTCHANNEL") - fvs = swsscommon.FieldValuePairs([("mtu", mtu)]) - tbl.set(interface, fvs) - time.sleep(1) + # create interface with vrf + self.create_l3_intf("Ethernet8", "Vrf_0") - def test_InterfaceAddRemoveIpv4Address(self, dvs, testlog): - self.setup_db(dvs) + # check interface's vrf + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + (status, fvs) = tbl.get("Ethernet8") + assert status == True + for fv in fvs: + if fv[0] == "vrf_name": + assert fv[1] == "Vrf_0" + vrf_found = True + break + assert vrf_found == True - # create port channel - self.create_port_channel(dvs, "PortChannel001") + # check linux kernel + (exitcode, result) = dvs.runcmd(['sh', '-c', "ip link show Ethernet8 | grep Vrf"]) + assert "Vrf_0" in result # assign IP to interface - self.add_ip_address("PortChannel001", "30.0.0.4/31") + self.add_ip_address("Ethernet8", "fc00::1/126") + time.sleep(2) # IPv6 netlink message needs longer time # check application database - tbl = swsscommon.Table(self.pdb, "INTF_TABLE:PortChannel001") + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Ethernet8") intf_entries = tbl.getKeys() + assert len(intf_entries) == 1 - assert intf_entries[0] == "30.0.0.4/31" + assert intf_entries[0] == "fc00::1/126" (status, fvs) = tbl.get(tbl.getKeys()[0]) assert status == True @@ -281,7 +412,7 @@ def test_InterfaceAddRemoveIpv4Address(self, dvs, testlog): if fv[0] == "scope": assert fv[1] == "global" elif fv[0] == "family": - assert fv[1] == "IPv4" + assert fv[1] == "IPv6" else: assert False @@ -302,23 +433,49 @@ def test_InterfaceAddRemoveIpv4Address(self, dvs, testlog): # the default MTU without any configuration is 9100 if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_MTU": assert fv[1] == "9100" + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_VIRTUAL_ROUTER_ID": + assert fv[1] == vrf_oid # check ASIC route database tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") for key in tbl.getKeys(): route = json.loads(key) - if route["dest"] == "30.0.0.4/31": + if route["dest"] == "fc00::/126": subnet_found = True - if route["dest"] == "30.0.0.4/32": + assert route["vr"] == vrf_oid + if route["dest"] == "fc00::1/128": ip2me_found = True + assert route["vr"] == vrf_oid assert subnet_found and ip2me_found # remove IP from interface - self.remove_ip_address("PortChannel001", "30.0.0.4/31") + self.remove_ip_address("Ethernet8", "fc00::1/126") + + # remove vrf from interface + self.remove_l3_intf("Ethernet8") + + # remove vrf + self.remove_vrf("Vrf_0") + + # bring down interface + self.set_admin_status(dvs, "Ethernet8", "down") # check application database - tbl = swsscommon.Table(self.pdb, "INTF_TABLE:PortChannel001") + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Ethernet8") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + # check linux kernel + (exitcode, result) = dvs.runcmd(['sh', '-c', "ip link show Ethernet8 | grep Vrf"]) + assert "Vrf_0" not in result + + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + intf_entries = tbl.getKeys() + for entry in intf_entries: + assert entry[0] != "Ethernet8" + + tbl = swsscommon.Table(self.pdb, "VRF") intf_entries = tbl.getKeys() assert len(intf_entries) == 0 @@ -326,29 +483,54 @@ def test_InterfaceAddRemoveIpv4Address(self, dvs, testlog): tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") for key in tbl.getKeys(): route = json.loads(key) - if route["dest"] == "30.0.0.4/31": + if route["dest"] == "fc00::/126": assert False - if route["dest"] == "30.0.0.4/32": + if route["dest"] == "fc00::1/128": assert False - # remove port channel - self.remove_port_channel(dvs, "PortChannel001") - - def test_InterfaceSetMtu(self, dvs, testlog): + def test_PortInterfaceAddRemoveIpv4AddressWithVrf(self, dvs, testlog): self.setup_db(dvs) - # create port channel - self.create_port_channel(dvs, "PortChannel002") + # bring up interface + self.set_admin_status(dvs, "Ethernet8", "up") - # add port channel members - self.add_port_channel_members(dvs, "PortChannel002", ["Ethernet0", "Ethernet4"]) + # create vrf + vrf_oid = self.create_vrf("Vrf_0") + + # create interface with vrf + self.create_l3_intf("Ethernet8", "Vrf_0") + + # check interface's vrf + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + (status, fvs) = tbl.get("Ethernet8") + assert status == True + for fv in fvs: + if fv[0] == "vrf_name": + assert fv[1] == "Vrf_0" + vrf_found = True + break + assert vrf_found == True # assign IP to interface - self.add_ip_address("PortChannel002", "40.0.0.8/29") + self.add_ip_address("Ethernet8", "10.0.0.4/31") - # configure MTU to interface - self.set_mtu("PortChannel002", "8888") + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Ethernet8") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 1 + assert intf_entries[0] == "10.0.0.4/31" + + (status, fvs) = tbl.get(tbl.getKeys()[0]) + assert status == True + assert len(fvs) == 2 + for fv in fvs: + if fv[0] == "scope": + assert fv[1] == "global" + elif fv[0] == "family": + assert fv[1] == "IPv4" + else: + assert False # check ASIC router interface database tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE") @@ -364,103 +546,1420 @@ def test_InterfaceSetMtu(self, dvs, testlog): for fv in fvs: if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_TYPE": assert fv[1] == "SAI_ROUTER_INTERFACE_TYPE_PORT" - # assert the new value set to the router interface + # the default MTU without any configuration is 9100 if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_MTU": - assert fv[1] == "8888" + assert fv[1] == "9100" + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_VIRTUAL_ROUTER_ID": + assert fv[1] == vrf_oid - # check ASIC port database - tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_PORT") - port_entries = tbl.getKeys() + # check ASIC route database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "10.0.0.4/31": + subnet_found = True + assert route["vr"] == vrf_oid + if route["dest"] == "10.0.0.4/32": + ip2me_found = True + assert route["vr"] == vrf_oid - for key in port_entries: - (status, fvs) = tbl.get(key) - assert status == True - # a member port configured with MTU will have six field/value tuples - if len(fvs) == 6: - for fv in fvs: - # asser the new value 8888 + 22 = 8910 set to the port - if fv[0] == "SAI_PORT_ATTR_MTU": - assert fv[1] == "8910" + assert subnet_found and ip2me_found # remove IP from interface - self.remove_ip_address("PortChannel002", "40.0.0.8/29") - - # remove port channel members - self.remove_port_channel_members(dvs, "PortChannel002", ["Ethernet0", "Ethernet4"]) - - # remove port channel - self.remove_port_channel(dvs, "PortChannel002") - -class TestLoopbackRouterInterface(object): - def setup_db(self, dvs): - self.pdb = swsscommon.DBConnector(0, dvs.redis_sock, 0) - self.adb = swsscommon.DBConnector(1, dvs.redis_sock, 0) - self.cdb = swsscommon.DBConnector(4, dvs.redis_sock, 0) - - def createLoIntf(self, interface, ip): - tbl = swsscommon.Table(self.cdb, "LOOPBACK_INTERFACE") - fvs = swsscommon.FieldValuePairs([("NULL", "NULL")]) - tbl.set(interface + "|" + ip, fvs) - time.sleep(1) + self.remove_ip_address("Ethernet8", "10.0.0.4/31") - def removeLoIntf(self, interface, ip): - tbl = swsscommon.Table(self.cdb, "LOOPBACK_INTERFACE") - tbl._del(interface + "|" + ip); - time.sleep(1) + # remove interface + self.remove_l3_intf("Ethernet8") - def test_InterfacesCreateRemove(self, dvs, testlog): - self.setup_db(dvs) + # remove vrf + self.remove_vrf("Vrf_0") - # Create loopback interfaces - self.createLoIntf("Loopback0", "10.1.0.1/32") - self.createLoIntf("Loopback1", "10.1.0.2/32") + # bring down interface + self.set_admin_status(dvs, "Ethernet8", "down") - # Check configuration database - tbl = swsscommon.Table(self.cdb, "LOOPBACK_INTERFACE") + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Ethernet8") intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 - assert len(intf_entries) == 2 - assert "Loopback0|10.1.0.1/32" in intf_entries - assert "Loopback1|10.1.0.2/32" in intf_entries - - # Check application database - tbl = swsscommon.Table(self.pdb, "INTF_TABLE:lo") + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") intf_entries = tbl.getKeys() + for entry in intf_entries: + assert entry[0] != "Ethernet8" - assert len(intf_entries) == 2 - assert "10.1.0.1/32" in intf_entries - assert "10.1.0.2/32" in intf_entries + tbl = swsscommon.Table(self.pdb, "VRF") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 - # Check ASIC database + # check ASIC database tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") for key in tbl.getKeys(): route = json.loads(key) - if route["dest"] == "10.1.0.1/32": - lo0_ip2me_found = True - if route["dest"] == "10.1.0.2/32": - lo1_ip2me_found = True + if route["dest"] == "10.0.0.4/31": + assert False + if route["dest"] == "10.0.0.4/32": + assert False - assert lo0_ip2me_found and lo1_ip2me_found + def test_PortInterfaceAddSameIpv4AddressWithDiffVrf(self, dvs, testlog): + self.setup_db(dvs) + + for i in [0, 4]: + # record ASIC router interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE") + old_intf_entries = set(tbl.getKeys()) + + # record ASIC router entry database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + old_route_entries = set(tbl.getKeys()) - # Remove lopback interfaces - self.removeLoIntf("Loopback0", "10.1.0.1/32") - self.removeLoIntf("Loopback1", "10.1.0.2/32") + intf_name = "Ethernet" + str(i) + vrf_name = "Vrf_" + str(i) - # Check configuration database - tbl = swsscommon.Table(self.cdb, "LOOPBACK_INTERFACE") + # bring up interface + self.set_admin_status(dvs, intf_name, "up") + + # create vrf + vrf_oid = self.create_vrf(vrf_name) + + # create interface with vrf + self.create_l3_intf(intf_name, vrf_name) + + # check interface's vrf + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + (status, fvs) = tbl.get(intf_name) + assert status == True + for fv in fvs: + if fv[0] == "vrf_name": + assert fv[1] == vrf_name + vrf_found = True + break + assert vrf_found == True + + # assign IP to interface + self.add_ip_address(intf_name, "10.0.0.4/31") + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:" + intf_name) + intf_entries = tbl.getKeys() + assert len(intf_entries) == 1 + assert intf_entries[0] == "10.0.0.4/31" + + (status, fvs) = tbl.get(tbl.getKeys()[0]) + assert status == True + assert len(fvs) == 2 + for fv in fvs: + if fv[0] == "scope": + assert fv[1] == "global" + elif fv[0] == "family": + assert fv[1] == "IPv4" + else: + assert False + + # check ASIC router interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE") + current_intf_entries = set(tbl.getKeys()) + intf_entries = list(current_intf_entries - old_intf_entries) + for key in intf_entries: + (status, fvs) = tbl.get(key) + assert status == True + # a port based router interface has five field/value tuples + if len(fvs) == 5: + for fv in fvs: + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_TYPE": + assert fv[1] == "SAI_ROUTER_INTERFACE_TYPE_PORT" + # the default MTU without any configuration is 9100 + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_MTU": + assert fv[1] == "9100" + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_VIRTUAL_ROUTER_ID": + assert fv[1] == vrf_oid + + # check ASIC route database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + current_route_entries = set(tbl.getKeys()) + route_entries = list(current_route_entries - old_route_entries) + + for key in route_entries: + route = json.loads(key) + if route["dest"] == "10.0.0.4/31": + subnet_found = True + assert route["vr"] == vrf_oid + if route["dest"] == "10.0.0.4/32": + ip2me_found = True + assert route["vr"] == vrf_oid + + assert subnet_found and ip2me_found + + + for i in [0, 4]: + # check ASIC router interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE") + old_intf_entries = set(tbl.getKeys()) + + intf_name = "Ethernet" + str(i) + vrf_name = "Vrf_" + str(i) + + # remove IP from interface + self.remove_ip_address(intf_name, "10.0.0.4/31") + + # remove interface + self.remove_l3_intf(intf_name) + + # remove vrf + self.remove_vrf(vrf_name) + + # bring down interface + self.set_admin_status(dvs, intf_name, "down") + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:" + intf_name) + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") intf_entries = tbl.getKeys() - assert len(intf_entries) == 0 + for entry in intf_entries: + assert entry[0] != "Ethernet0" and entry[0] != "Ethernet4" - # Check application database - tbl = swsscommon.Table(self.pdb, "INTF_TABLE:lo") + tbl = swsscommon.Table(self.pdb, "VRF") intf_entries = tbl.getKeys() assert len(intf_entries) == 0 - # Check ASIC database + # check ASIC database tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") for key in tbl.getKeys(): route = json.loads(key) - if route["dest"] == "10.1.0.1/32": + if route["dest"] == "10.0.0.4/31": assert False - if route["dest"] == "10.1.0.2/32": + if route["dest"] == "10.0.0.4/32": + assert False + + def create_port_channel(self, alias): + tbl = swsscommon.Table(self.cdb, "PORTCHANNEL") + fvs = swsscommon.FieldValuePairs([("admin_status", "up"), + ("mtu", "9100")]) + tbl.set(alias, fvs) + time.sleep(1) + + def remove_port_channel(self, alias): + tbl = swsscommon.Table(self.cdb, "PORTCHANNEL") + tbl._del(alias) + time.sleep(1) + + def add_port_channel_members(self, lag, members): + tbl = swsscommon.Table(self.cdb, "PORTCHANNEL_MEMBER") + fvs = swsscommon.FieldValuePairs([("NULL", "NULL")]) + for member in members: + tbl.set(lag + "|" + member, fvs) + time.sleep(1) + + def remove_port_channel_members(self, lag, members): + tbl = swsscommon.Table(self.cdb, "PORTCHANNEL_MEMBER") + for member in members: + tbl._del(lag + "|" + member) + time.sleep(1) + + def test_LagInterfaceAddRemoveIpv6Address(self, dvs, testlog): + self.setup_db(dvs) + + # create port channel + self.create_port_channel("PortChannel001") + + # bring up interface + self.set_admin_status(dvs, "PortChannel001", "up") + + # create l3 interface + self.create_l3_intf("PortChannel001", "") + + # assign IP to interface + self.add_ip_address("PortChannel001", "fc00::1/126") + time.sleep(2) # IPv6 netlink message needs longer time + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:PortChannel001") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 1 + assert intf_entries[0] == "fc00::1/126" + + (status, fvs) = tbl.get(tbl.getKeys()[0]) + assert status == True + assert len(fvs) == 2 + for fv in fvs: + if fv[0] == "scope": + assert fv[1] == "global" + elif fv[0] == "family": + assert fv[1] == "IPv6" + else: + assert False + + # check ASIC router interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE") + intf_entries = tbl.getKeys() + # one loopback router interface one port based router interface + assert len(intf_entries) == 2 + + for key in intf_entries: + (status, fvs) = tbl.get(key) + assert status == True + # a port based router interface has five field/value tuples + if len(fvs) == 5: + for fv in fvs: + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_TYPE": + assert fv[1] == "SAI_ROUTER_INTERFACE_TYPE_PORT" + # the default MTU without any configuration is 9100 + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_MTU": + assert fv[1] == "9100" + + # check ASIC route database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "fc00::/126": + subnet_found = True + if route["dest"] == "fc00::1/128": + ip2me_found = True + + assert subnet_found and ip2me_found + + # remove IP from interface + self.remove_ip_address("PortChannel001", "fc00::1/126") + + # remove interface + self.remove_l3_intf("PortChannel001") + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:PortChannel001") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + intf_entries = tbl.getKeys() + for entry in intf_entries: + assert entry[0] != "PortChannel001" + + # check ASIC database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "fc00::/126": + assert False + if route["dest"] == "fc00::1/128": + assert False + + # remove port channel + self.remove_port_channel("PortChannel001") + + def test_LagInterfaceAddRemoveIpv4Address(self, dvs, testlog): + self.setup_db(dvs) + + # create port channel + self.create_port_channel("PortChannel001") + + # bring up interface + self.set_admin_status(dvs, "PortChannel001", "up") + + # create l3 interface + self.create_l3_intf("PortChannel001", "") + + # assign IP to interface + self.add_ip_address("PortChannel001", "30.0.0.4/31") + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:PortChannel001") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 1 + assert intf_entries[0] == "30.0.0.4/31" + + (status, fvs) = tbl.get(tbl.getKeys()[0]) + assert status == True + assert len(fvs) == 2 + for fv in fvs: + if fv[0] == "scope": + assert fv[1] == "global" + elif fv[0] == "family": + assert fv[1] == "IPv4" + else: + assert False + + # check ASIC router interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE") + intf_entries = tbl.getKeys() + # one loopback router interface one port based router interface + assert len(intf_entries) == 2 + + for key in intf_entries: + (status, fvs) = tbl.get(key) + assert status == True + # a port based router interface has five field/value tuples + if len(fvs) == 5: + for fv in fvs: + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_TYPE": + assert fv[1] == "SAI_ROUTER_INTERFACE_TYPE_PORT" + # the default MTU without any configuration is 9100 + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_MTU": + assert fv[1] == "9100" + + # check ASIC route database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "30.0.0.4/31": + subnet_found = True + if route["dest"] == "30.0.0.4/32": + ip2me_found = True + + assert subnet_found and ip2me_found + + # remove IP from interface + self.remove_ip_address("PortChannel001", "30.0.0.4/31") + + # remove l3 interface + self.remove_l3_intf("PortChannel001") + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:PortChannel001") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + intf_entries = tbl.getKeys() + for entry in intf_entries: + assert entry[0] != "PortChannel001" + + # check ASIC database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "30.0.0.4/31": + assert False + if route["dest"] == "30.0.0.4/32": + assert False + + # remove port channel + self.remove_port_channel("PortChannel001") + + + def test_LagInterfaceSetMtu(self, dvs, testlog): + self.setup_db(dvs) + + # create port channel + self.create_port_channel("PortChannel002") + + # add port channel members + self.add_port_channel_members("PortChannel002", ["Ethernet0", "Ethernet4"]) + + # create l3 interface + self.create_l3_intf("PortChannel002", "") + + # assign IP to interface + self.add_ip_address("PortChannel002", "40.0.0.8/29") + + # configure MTU to interface + self.set_mtu("PortChannel002", "8888") + + # check ASIC router interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE") + intf_entries = tbl.getKeys() + # one loopback router interface one port based router interface + assert len(intf_entries) == 2 + + for key in intf_entries: + (status, fvs) = tbl.get(key) + assert status == True + # a port based router interface has five field/value tuples + if len(fvs) == 5: + for fv in fvs: + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_TYPE": + assert fv[1] == "SAI_ROUTER_INTERFACE_TYPE_PORT" + # assert the new value set to the router interface + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_MTU": + assert fv[1] == "8888" + + # check ASIC port database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_PORT") + port_entries = tbl.getKeys() + + for key in port_entries: + (status, fvs) = tbl.get(key) + assert status == True + # a member port configured with MTU will have six field/value tuples + if len(fvs) == 6: + for fv in fvs: + # asser the new value 8888 + 22 = 8910 set to the port + if fv[0] == "SAI_PORT_ATTR_MTU": + assert fv[1] == "8910" + + # remove IP from interface + self.remove_ip_address("PortChannel002", "40.0.0.8/29") + + # remove l3 interface + self.remove_l3_intf("PortChannel002") + + # remove port channel members + self.remove_port_channel_members("PortChannel002", ["Ethernet0", "Ethernet4"]) + + # remove port channel + self.remove_port_channel("PortChannel002") + + def test_LagInterfaceAddRemoveIpv6AddressWithVrf(self, dvs, testlog): + self.setup_db(dvs) + + # create vrf + vrf_oid = self.create_vrf("Vrf_0") + + # create port channel + self.create_port_channel("PortChannel001") + + # bring up interface + self.set_admin_status(dvs, "PortChannel001", "up") + + # create l3 interface + self.create_l3_intf("PortChannel001", "Vrf_0") + + # check interface's vrf + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + (status, fvs) = tbl.get("PortChannel001") + assert status == True + for fv in fvs: + if fv[0] == "vrf_name": + assert fv[1] == "Vrf_0" + vrf_found = True + break + assert vrf_found == True + + # assign IP to interface + self.add_ip_address("PortChannel001", "fc00::1/126") + time.sleep(2) # IPv6 netlink message needs longer time + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:PortChannel001") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 1 + assert intf_entries[0] == "fc00::1/126" + + (status, fvs) = tbl.get(tbl.getKeys()[0]) + assert status == True + assert len(fvs) == 2 + for fv in fvs: + if fv[0] == "scope": + assert fv[1] == "global" + elif fv[0] == "family": + assert fv[1] == "IPv6" + else: + assert False + + # check ASIC router interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE") + intf_entries = tbl.getKeys() + # one loopback router interface one port based router interface + assert len(intf_entries) == 2 + + for key in intf_entries: + (status, fvs) = tbl.get(key) + assert status == True + # a port based router interface has five field/value tuples + if len(fvs) == 5: + for fv in fvs: + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_TYPE": + assert fv[1] == "SAI_ROUTER_INTERFACE_TYPE_PORT" + # the default MTU without any configuration is 9100 + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_MTU": + assert fv[1] == "9100" + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_VIRTUAL_ROUTER_ID": + assert fv[1] == vrf_oid + # check ASIC route database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "fc00::/126": + subnet_found = True + assert route["vr"] == vrf_oid + if route["dest"] == "fc00::1/128": + ip2me_found = True + assert route["vr"] == vrf_oid + + assert subnet_found and ip2me_found + + # remove IP from interface + self.remove_ip_address("PortChannel001", "fc00::1/126") + + # remove interface + self.remove_l3_intf("PortChannel001") + + # remove vrf + self.remove_vrf("Vrf_0") + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:PortChannel001") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + intf_entries = tbl.getKeys() + for entry in intf_entries: + assert entry[0] != "PortChannel001" + + tbl = swsscommon.Table(self.pdb, "VRF") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + # check ASIC database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "fc00::1/126": + assert False + if route["dest"] == "fc00::1/128": + assert False + + # remove port channel + self.remove_port_channel("PortChannel001") + + + def test_LagInterfaceAddRemoveIpv4AddressWithVrf(self, dvs, testlog): + self.setup_db(dvs) + + # create port channel + self.create_port_channel("PortChannel001") + + # create vrf + vrf_oid = self.create_vrf("Vrf_0") + + # create interface with vrf + self.create_l3_intf("PortChannel001", "Vrf_0") + + # bring up interface + self.set_admin_status(dvs, "PortChannel001", "up") + + # check interface's vrf + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + (status, fvs) = tbl.get("PortChannel001") + assert status == True + for fv in fvs: + if fv[0] == "vrf_name": + assert fv[1] == "Vrf_0" + vrf_found = True + break + assert vrf_found == True + + # check linux kernel + (exitcode, result) = dvs.runcmd(['sh', '-c', "ip link show PortChannel001 | grep Vrf"]) + assert "Vrf_0" in result + + # assign IP to interface + self.add_ip_address("PortChannel001", "30.0.0.4/31") + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:PortChannel001") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 1 + assert intf_entries[0] == "30.0.0.4/31" + + (status, fvs) = tbl.get(tbl.getKeys()[0]) + assert status == True + assert len(fvs) == 2 + for fv in fvs: + if fv[0] == "scope": + assert fv[1] == "global" + elif fv[0] == "family": + assert fv[1] == "IPv4" + else: + assert False + + # check ASIC router interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE") + intf_entries = tbl.getKeys() + # one loopback router interface one port based router interface + assert len(intf_entries) == 2 + + for key in intf_entries: + (status, fvs) = tbl.get(key) + assert status == True + # a port based router interface has five field/value tuples + if len(fvs) == 5: + for fv in fvs: + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_TYPE": + assert fv[1] == "SAI_ROUTER_INTERFACE_TYPE_PORT" + # the default MTU without any configuration is 9100 + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_MTU": + assert fv[1] == "9100" + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_VIRTUAL_ROUTER_ID": + assert fv[1] == vrf_oid + + # check ASIC route database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "30.0.0.4/31": + subnet_found = True + assert route["vr"] == vrf_oid + if route["dest"] == "30.0.0.4/32": + ip2me_found = True + assert route["vr"] == vrf_oid + + assert subnet_found and ip2me_found + + # remove IP from interface + self.remove_ip_address("PortChannel001", "30.0.0.4/31") + + # remove l3 interface + self.remove_l3_intf("PortChannel001") + + # check linux kernel + (exitcode, result) = dvs.runcmd(['sh', '-c', "ip link show PortChannel001 | grep Vrf"]) + assert "Vrf_0" not in result + + # remove vrf + self.remove_vrf("Vrf_0") + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:PortChannel001") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + intf_entries = tbl.getKeys() + for entry in intf_entries: + assert entry[0] != "PortChannel001" + + tbl = swsscommon.Table(self.pdb, "VRF") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + # check ASIC database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "30.0.0.4/31": + assert False + if route["dest"] == "30.0.0.4/32": + assert False + + # remove port channel + self.remove_port_channel("PortChannel001") + + def create_vlan(self, vlan_id): + tbl = swsscommon.Table(self.cdb, "VLAN") + fvs = swsscommon.FieldValuePairs([("vlanid", vlan_id)]) + tbl.set("Vlan" + vlan_id, fvs) + time.sleep(1) + + def remove_vlan(self, vlan_id): + tbl = swsscommon.Table(self.cdb, "VLAN") + tbl._del("Vlan" + vlan_id) + time.sleep(1) + + def create_vlan_member(self, vlan_id, interface): + tbl = swsscommon.Table(self.cdb, "VLAN_MEMBER") + fvs = swsscommon.FieldValuePairs([("tagging_mode", "untagged")]) + tbl.set("Vlan" + vlan_id + "|" + interface, fvs) + time.sleep(1) + + def remove_vlan_member(self, vlan_id, interface): + tbl = swsscommon.Table(self.cdb, "VLAN_MEMBER") + tbl._del("Vlan" + vlan_id + "|" + interface) + time.sleep(1) + + def test_VLanInterfaceAddRemoveIpv6Address(self, dvs, testlog): + self.setup_db(dvs) + + # create vlan + self.create_vlan("10") + + # add vlan member + self.create_vlan_member("10", "Ethernet0") + + # bring up interface + self.set_admin_status(dvs, "Ethernet0", "up") + self.set_admin_status(dvs, "Vlan10", "up") + + # create vlan interface + self.create_l3_intf("Vlan10", "") + + # assign IP to interface + self.add_ip_address("Vlan10", "fc00::1/126") + time.sleep(2) # IPv6 netlink message needs longer time + + # check asic database and get vlan_oid + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VLAN") + vlan_entries = [k for k in tbl.getKeys() if k != dvs.asicdb.default_vlan_id] + assert len(vlan_entries) == 1 + vlan_oid = vlan_entries[0] + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Vlan10") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 1 + assert intf_entries[0] == "fc00::1/126" + + (status, fvs) = tbl.get(tbl.getKeys()[0]) + assert status == True + assert len(fvs) == 2 + for fv in fvs: + if fv[0] == "scope": + assert fv[1] == "global" + elif fv[0] == "family": + assert fv[1] == "IPv6" + else: + assert False + + # check ASIC router interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE") + intf_entries = tbl.getKeys() + # one loopback router interface one vlan router interface + assert len(intf_entries) == 2 + + for key in intf_entries: + (status, fvs) = tbl.get(key) + assert status == True + if len(fvs) == 5: + for fv in fvs: + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_TYPE": + assert fv[1] == "SAI_ROUTER_INTERFACE_TYPE_VLAN" + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_VLAN_ID": + assert fv[1] == vlan_oid + + # check ASIC route database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "fc00::/126": + subnet_found = True + if route["dest"] == "fc00::1/128": + ip2me_found = True + + assert subnet_found and ip2me_found + + # remove IP from interface + self.remove_ip_address("Vlan10", "fc00::1/126") + + # remove interface + self.remove_l3_intf("Vlan10") + + # remove vlan member + self.remove_vlan_member("10", "Ethernet0") + + # remove vlan + self.remove_vlan("10") + + # bring down interface + self.set_admin_status(dvs, "Ethernet0", "down") + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Vlan10") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + intf_entries = tbl.getKeys() + for entry in intf_entries: + assert entry[0] != "Vlan10" + + # check ASIC database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "fc00::/126": + assert False + if route["dest"] == "fc00::1/128": + assert False + + def test_VLanInterfaceAddRemoveIpv4Address(self, dvs, testlog): + self.setup_db(dvs) + + # create vlan + self.create_vlan("10") + + # add vlan member + self.create_vlan_member("10", "Ethernet0") + + # bring up interface + self.set_admin_status(dvs, "Ethernet0", "up") + self.set_admin_status(dvs, "Vlan10", "up") + + #create vlan interface + self.create_l3_intf("Vlan10", "") + + # assign IP to interface + self.add_ip_address("Vlan10", "10.0.0.4/31") + + # check asic database and get vlan_oid + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VLAN") + vlan_entries = [k for k in tbl.getKeys() if k != dvs.asicdb.default_vlan_id] + assert len(vlan_entries) == 1 + vlan_oid = vlan_entries[0] + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Vlan10") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 1 + assert intf_entries[0] == "10.0.0.4/31" + + (status, fvs) = tbl.get(tbl.getKeys()[0]) + assert status == True + assert len(fvs) == 2 + for fv in fvs: + if fv[0] == "scope": + assert fv[1] == "global" + elif fv[0] == "family": + assert fv[1] == "IPv4" + else: + assert False + + # check ASIC router interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE") + intf_entries = tbl.getKeys() + # one loopback router interface one vlan router interface + assert len(intf_entries) == 2 + + for key in intf_entries: + (status, fvs) = tbl.get(key) + assert status == True + if len(fvs) == 5: + for fv in fvs: + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_TYPE": + assert fv[1] == "SAI_ROUTER_INTERFACE_TYPE_VLAN" + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_VLAN_ID": + assert fv[1] == vlan_oid + + # check ASIC route database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "10.0.0.4/31": + subnet_found = True + if route["dest"] == "10.0.0.4/32": + ip2me_found = True + + assert subnet_found and ip2me_found + + # remove IP from interface + self.remove_ip_address("Vlan10", "10.0.0.4/31") + + # remove vlan interface + self.remove_l3_intf("Vlan10") + + # remove vlan member + self.remove_vlan_member("10", "Ethernet0") + + # remove vlan + self.remove_vlan("10") + + # bring down interface + self.set_admin_status(dvs, "Ethernet0", "down") + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Vlan10") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + tbl = swsscommon.Table(self.pdb, "VLAN_TABLE") + intf_entries = tbl.getKeys() + for entry in intf_entries: + assert entry[0] != "Vlan10" + + # check ASIC database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "10.0.0.4/31": + assert False + if route["dest"] == "10.0.0.4/32": + assert False + + def test_VLanInterfaceAddRemoveIpv6AddressWithVrf(self, dvs, testlog): + self.setup_db(dvs) + + # create vlan + self.create_vlan("10") + + # add vlan member + self.create_vlan_member("10", "Ethernet0") + + # bring up interface + self.set_admin_status(dvs, "Ethernet0", "up") + self.set_admin_status(dvs, "Vlan10", "up") + + # create vrf + vrf_oid = self.create_vrf("Vrf_0") + + # create vlan interface + self.create_l3_intf("Vlan10", "Vrf_0") + + # check linux kernel + (exitcode, result) = dvs.runcmd(['sh', '-c', "ip link show Vlan10 | grep Vrf"]) + assert "Vrf_0" in result + + # assign IP to interface + self.add_ip_address("Vlan10", "fc00::1/126") + time.sleep(2) # IPv6 netlink message needs longer time + + # check interface's vrf + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + (status, fvs) = tbl.get("Vlan10") + assert status == True + for fv in fvs: + if fv[0] == "vrf_name": + assert fv[1] == "Vrf_0" + vrf_found = True + break + assert vrf_found == True + + # check asic database and get vlan_oid + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VLAN") + vlan_entries = [k for k in tbl.getKeys() if k != dvs.asicdb.default_vlan_id] + assert len(vlan_entries) == 1 + vlan_oid = vlan_entries[0] + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Vlan10") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 1 + assert intf_entries[0] == "fc00::1/126" + + (status, fvs) = tbl.get(tbl.getKeys()[0]) + assert status == True + assert len(fvs) == 2 + for fv in fvs: + if fv[0] == "scope": + assert fv[1] == "global" + elif fv[0] == "family": + assert fv[1] == "IPv6" + else: + assert False + + # check ASIC router interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE") + intf_entries = tbl.getKeys() + # one loopback router interface one vlan router interface + assert len(intf_entries) == 2 + + for key in intf_entries: + (status, fvs) = tbl.get(key) + assert status == True + if len(fvs) == 5: + for fv in fvs: + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_TYPE": + assert fv[1] == "SAI_ROUTER_INTERFACE_TYPE_VLAN" + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_VLAN_ID": + assert fv[1] == vlan_oid + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_VIRTUAL_ROUTER_ID": + assert fv[1] == vrf_oid + + # check ASIC route database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "fc00::/126": + subnet_found = True + assert route["vr"] == vrf_oid + if route["dest"] == "fc00::1/128": + ip2me_found = True + assert route["vr"] == vrf_oid + + assert subnet_found and ip2me_found + + # remove IP from interface + self.remove_ip_address("Vlan10", "fc00::1/126") + + # remove vlan interface + self.remove_l3_intf("Vlan10") + + # check linux kernel + (exitcode, result) = dvs.runcmd(['sh', '-c', "ip link show Vlan10 | grep Vrf"]) + assert "Vrf_0" not in result + + # remove vlan member + self.remove_vlan_member("10", "Ethernet0") + + # remove vlan + self.remove_vlan("10") + + # bring down interface + self.set_admin_status(dvs, "Ethernet0", "down") + + # remove vrf + self.remove_vrf("Vrf_0") + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Vlan10") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + intf_entries = tbl.getKeys() + for entry in intf_entries: + assert entry[0] != "Vlan10" + + # check ASIC database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "fc00::/126": + assert False + if route["dest"] == "fc00::1/128": + assert False + + def test_VLanInterfaceAddRemoveIpv4AddressWithVrf(self, dvs, testlog): + self.setup_db(dvs) + + # create vlan + self.create_vlan("10") + + # add vlan member + self.create_vlan_member("10", "Ethernet0") + + # bring up interface + self.set_admin_status(dvs, "Ethernet0", "up") + self.set_admin_status(dvs, "Vlan10", "up") + + # create vrf + vrf_oid = self.create_vrf("Vrf_0") + + # create vlan interface + self.create_l3_intf("Vlan10", "Vrf_0") + + # assign IP to interface + self.add_ip_address("Vlan10", "10.0.0.4/31") + + # check interface's vrf + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + (status, fvs) = tbl.get("Vlan10") + assert status == True + for fv in fvs: + if fv[0] == "vrf_name": + assert fv[1] == "Vrf_0" + vrf_found = True + break + assert vrf_found == True + + # check asic database and get vlan_oid + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VLAN") + vlan_entries = [k for k in tbl.getKeys() if k != dvs.asicdb.default_vlan_id] + assert len(vlan_entries) == 1 + vlan_oid = vlan_entries[0] + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Vlan10") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 1 + assert intf_entries[0] == "10.0.0.4/31" + + (status, fvs) = tbl.get(tbl.getKeys()[0]) + assert status == True + assert len(fvs) == 2 + for fv in fvs: + if fv[0] == "scope": + assert fv[1] == "global" + elif fv[0] == "family": + assert fv[1] == "IPv4" + else: + assert False + + # check ASIC router interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE") + intf_entries = tbl.getKeys() + # one loopback router interface one vlan router interface + assert len(intf_entries) == 2 + + for key in intf_entries: + (status, fvs) = tbl.get(key) + assert status == True + if len(fvs) == 5: + for fv in fvs: + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_TYPE": + assert fv[1] == "SAI_ROUTER_INTERFACE_TYPE_VLAN" + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_VLAN_ID": + assert fv[1] == vlan_oid + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_VIRTUAL_ROUTER_ID": + assert fv[1] == vrf_oid + + # check ASIC route database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "10.0.0.4/31": + subnet_found = True + assert route["vr"] == vrf_oid + if route["dest"] == "10.0.0.4/32": + ip2me_found = True + assert route["vr"] == vrf_oid + + assert subnet_found and ip2me_found + + # remove IP from interface + self.remove_ip_address("Vlan10", "10.0.0.4/31") + + # remove vlan interface + self.remove_l3_intf("Vlan10") + + # remove vlan member + self.remove_vlan_member("10", "Ethernet0") + + # remove vlan + self.remove_vlan("10") + + # bring down interface + self.set_admin_status(dvs, "Ethernet0", "down") + + # remove vrf + self.remove_vrf("Vrf_0") + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Vlan10") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + tbl = swsscommon.Table(self.pdb, "VLAN_TABLE") + intf_entries = tbl.getKeys() + for entry in intf_entries: + assert entry[0] != "Vlan10" + + # check ASIC database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "10.0.0.4/31": + assert False + if route["dest"] == "10.0.0.4/32": + assert False + + def test_LoopbackInterfacesAddRemoveIpv4Address(self, dvs, testlog): + self.setup_db(dvs) + + # Create loopback interfaces + self.create_l3_intf("Loopback0", "") + self.create_l3_intf("Loopback1", "") + + # add ip address + self.add_ip_address("Loopback0", "10.1.0.1/32") + self.add_ip_address("Loopback1", "10.1.0.2/32") + + # Check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Loopback0") + intf_entries = tbl.getKeys() + assert intf_entries[0] == "10.1.0.1/32" + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Loopback1") + intf_entries = tbl.getKeys() + assert intf_entries[0] == "10.1.0.2/32" + + # Check ASIC database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "10.1.0.1/32": + lo0_ip2me_found = True + if route["dest"] == "10.1.0.2/32": + lo1_ip2me_found = True + + assert lo0_ip2me_found and lo1_ip2me_found + + # Remove ip address + self.remove_ip_address("Loopback0", "10.1.0.1/32") + self.remove_ip_address("Loopback1", "10.1.0.2/32") + + # Remove interface + self.remove_l3_intf("Loopback0") + self.remove_l3_intf("Loopback1") + + # Check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Loopback0") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Loopback1") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + # Check ASIC database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "10.1.0.1/32": + assert False + if route["dest"] == "10.1.0.2/32": + assert False + + def test_LoopbackInterfacesAddRemoveIpv6Address(self, dvs, testlog): + self.setup_db(dvs) + + # Create loopback interfaces + self.create_l3_intf("Loopback0", "") + self.create_l3_intf("Loopback1", "") + + # add ip address + self.add_ip_address("Loopback0", "fc00::1/128") + self.add_ip_address("Loopback1", "fd00::1/128") + time.sleep(2) # IPv6 netlink message needs longer time + + # Check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Loopback0") + intf_entries = tbl.getKeys() + assert intf_entries[0] == "fc00::1/128" + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Loopback1") + intf_entries = tbl.getKeys() + assert intf_entries[0] == "fd00::1/128" + + # Check ASIC database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "fc00::1/128": + lo0_ip2me_found = True + if route["dest"] == "fd00::1/128": + lo1_ip2me_found = True + + assert lo0_ip2me_found and lo1_ip2me_found + + # Remove ip address + self.remove_ip_address("Loopback0", "fc00::1/128") + self.remove_ip_address("Loopback1", "fd00::1/128") + + # Remove interface + self.remove_l3_intf("Loopback0") + self.remove_l3_intf("Loopback1") + + # Check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Loopback0") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:Loopback1") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + # Check ASIC database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "fc00::1/128": + assert False + if route["dest"] == "fd00::1/128": + assert False + + def test_LoopbackInterfaceIpv4AddressWithVrf(self, dvs, testlog): + self.setup_db(dvs) + + for i in [0, 1]: + # record ASIC router interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE") + old_intf_entries = set(tbl.getKeys()) + + # record ASIC router entry database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + old_route_entries = set(tbl.getKeys()) + + intf_name = "Loopback" + str(i) + vrf_name = "Vrf_" + str(i) + + # create vrf + vrf_oid = self.create_vrf(vrf_name) + + # create interface with vrf + self.create_l3_intf(intf_name, vrf_name) + + # check linux kernel + (exitcode, result) = dvs.runcmd(['sh', '-c', "ip link show %s | grep Vrf" % intf_name]) + assert "%s" % vrf_name in result + + # check interface's vrf + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + (status, fvs) = tbl.get(intf_name) + assert status == True + for fv in fvs: + if fv[0] == "vrf_name": + assert fv[1] == vrf_name + vrf_found = True + break + assert vrf_found == True + + # assign IP to interface + self.add_ip_address(intf_name, "10.0.0.4/32") + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:" + intf_name) + intf_entries = tbl.getKeys() + assert len(intf_entries) == 1 + assert intf_entries[0] == "10.0.0.4/32" + + (status, fvs) = tbl.get(tbl.getKeys()[0]) + assert status == True + assert len(fvs) == 2 + for fv in fvs: + if fv[0] == "scope": + assert fv[1] == "global" + elif fv[0] == "family": + assert fv[1] == "IPv4" + else: + assert False + + # check ASIC router interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE") + current_intf_entries = set(tbl.getKeys()) + intf_entries = list(current_intf_entries - old_intf_entries) + for key in intf_entries: + (status, fvs) = tbl.get(key) + assert status == True + # a port based router interface has five field/value tuples + if len(fvs) == 5: + for fv in fvs: + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_TYPE": + assert fv[1] == "SAI_ROUTER_INTERFACE_TYPE_PORT" + # the default MTU without any configuration is 9100 + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_MTU": + assert fv[1] == "9100" + if fv[0] == "SAI_ROUTER_INTERFACE_ATTR_VIRTUAL_ROUTER_ID": + assert fv[1] == vrf_oid + + # check ASIC route database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + current_route_entries = set(tbl.getKeys()) + route_entries = list(current_route_entries - old_route_entries) + + for key in route_entries: + route = json.loads(key) + if route["dest"] == "10.0.0.4/32": + ip2me_found = True + assert route["vr"] == vrf_oid + + assert ip2me_found + + + for i in [0, 1]: + # check ASIC router interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE") + old_intf_entries = set(tbl.getKeys()) + + intf_name = "Loopback" + str(i) + vrf_name = "Vrf_" + str(i) + + # remove IP from interface + self.remove_ip_address(intf_name, "10.0.0.4/32") + + # remove interface + self.remove_l3_intf(intf_name) + + # remove vrf + self.remove_vrf(vrf_name) + + # check linux kernel + (exitcode, result) = dvs.runcmd(['sh', '-c', "ip link show %s | grep Vrf" % intf_name]) + assert "%s" % vrf_name not in result + + # check application database + tbl = swsscommon.Table(self.pdb, "INTF_TABLE:" + intf_name) + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + tbl = swsscommon.Table(self.pdb, "INTF_TABLE") + intf_entries = tbl.getKeys() + for entry in intf_entries: + assert entry[0] != "Loopback0" and entry[0] != "Loopback1" + + tbl = swsscommon.Table(self.pdb, "VRF") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + # check ASIC database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "10.0.0.4/32": assert False diff --git a/tests/test_neighbor.py b/tests/test_neighbor.py new file mode 100644 index 000000000000..4cf21963346a --- /dev/null +++ b/tests/test_neighbor.py @@ -0,0 +1,390 @@ +from swsscommon import swsscommon + +import time +import json + +class TestNeighbor(object): + def setup_db(self, dvs): + self.pdb = swsscommon.DBConnector(0, dvs.redis_sock, 0) + self.adb = swsscommon.DBConnector(1, dvs.redis_sock, 0) + self.cdb = swsscommon.DBConnector(4, dvs.redis_sock, 0) + + def set_admin_status(self, interface, status): + tbl = swsscommon.Table(self.cdb, "PORT") + fvs = swsscommon.FieldValuePairs([("admin_status", status)]) + tbl.set(interface, fvs) + time.sleep(1) + + def create_vrf(self, vrf_name): + tbl = swsscommon.Table(self.cdb, "VRF") + fvs = swsscommon.FieldValuePairs([('empty', 'empty')]) + tbl.set(vrf_name, fvs) + time.sleep(1) + + def remove_vrf(self, vrf_name): + tbl = swsscommon.Table(self.cdb, "VRF") + tbl._del(vrf_name) + time.sleep(1) + + def create_l3_intf(self, interface, vrf_name): + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE") + initial_entries = set(tbl.getKeys()) + + tbl = swsscommon.Table(self.cdb, "INTERFACE") + if len(vrf_name) == 0: + fvs = swsscommon.FieldValuePairs([("NULL", "NULL")]) + else: + fvs = swsscommon.FieldValuePairs([("vrf_name", vrf_name)]) + tbl.set(interface, fvs) + time.sleep(1) + + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE") + current_entries = set(tbl.getKeys()) + assert len(current_entries - initial_entries) == 1 + return list(current_entries - initial_entries)[0] + + def remove_l3_intf(self, interface): + tbl = swsscommon.Table(self.cdb, "INTERFACE") + tbl._del(interface) + time.sleep(1) + + def add_ip_address(self, interface, ip): + tbl = swsscommon.Table(self.cdb, "INTERFACE") + fvs = swsscommon.FieldValuePairs([("NULL", "NULL")]) + tbl.set(interface + "|" + ip, fvs) + time.sleep(2) # IPv6 netlink message needs longer time + + def remove_ip_address(self, interface, ip): + tbl = swsscommon.Table(self.cdb, "INTERFACE") + tbl._del(interface + "|" + ip) + time.sleep(1) + + def add_neighbor(self, interface, ip, mac): + tbl = swsscommon.Table(self.cdb, "NEIGH") + fvs = swsscommon.FieldValuePairs([("neigh", mac)]) + tbl.set(interface + "|" + ip, fvs) + time.sleep(1) + + def remove_neighbor(self, interface, ip): + tbl = swsscommon.Table(self.cdb, "NEIGH") + tbl._del(interface + "|" + ip) + time.sleep(1) + + def test_NeighborAddRemoveIpv6(self, dvs, testlog): + self.setup_db(dvs) + + # bring up interface + # NOTE: For IPv6, only when the interface is up will the netlink message + # get generated. + self.set_admin_status("Ethernet8", "up") + + # create interface and get rif_oid + rif_oid = self.create_l3_intf("Ethernet8", "") + + # assign IP to interface + self.add_ip_address("Ethernet8", "2000::1/64") + + # add neighbor + self.add_neighbor("Ethernet8", "2000::2", "00:01:02:03:04:05") + + # check application database + tbl = swsscommon.Table(self.pdb, "NEIGH_TABLE:Ethernet8") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 1 + assert intf_entries[0] == "2000::2" + (status, fvs) = tbl.get(intf_entries[0]) + assert status == True + assert len(fvs) == 2 + for fv in fvs: + if fv[0] == "neigh": + assert fv[1] == "00:01:02:03:04:05" + elif fv[0] == "family": + assert fv[1] == "IPv6" + else: + assert False + + # check ASIC neighbor database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_NEIGHBOR_ENTRY") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 1 + route = json.loads(intf_entries[0]) + assert route["ip"] == "2000::2" + assert route["rif"] == rif_oid + + (status, fvs) = tbl.get(intf_entries[0]) + assert status == True + for fv in fvs: + if fv[0] == "SAI_NEIGHBOR_ENTRY_ATTR_DST_MAC_ADDRESS": + assert fv[1] == "00:01:02:03:04:05" + + # remove neighbor + self.remove_neighbor("Ethernet8", "2000::2") + + # remove IP from interface + self.remove_ip_address("Ethernet8", "2000::1/64") + + # remove interface + self.remove_l3_intf("Ethernet8") + + # bring down interface + self.set_admin_status("Ethernet8", "down") + + # check application database + tbl = swsscommon.Table(self.pdb, "NEIGH_TABLE:Ethernet8") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + # check ASIC neighbor database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_NEIGHBOR_ENTRY") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + + def test_NeighborAddRemoveIpv4(self, dvs, testlog): + self.setup_db(dvs) + + # bring up interface + self.set_admin_status("Ethernet8", "up") + + # create interface and get rif_oid + rif_oid = self.create_l3_intf("Ethernet8", "") + + # assign IP to interface + self.add_ip_address("Ethernet8", "10.0.0.1/24") + + # add neighbor + self.add_neighbor("Ethernet8", "10.0.0.2", "00:01:02:03:04:05") + + # check application database + tbl = swsscommon.Table(self.pdb, "NEIGH_TABLE:Ethernet8") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 1 + assert intf_entries[0] == "10.0.0.2" + (status, fvs) = tbl.get(intf_entries[0]) + assert status == True + assert len(fvs) == 2 + for fv in fvs: + if fv[0] == "neigh": + assert fv[1] == "00:01:02:03:04:05" + elif fv[0] == "family": + assert fv[1] == "IPv4" + else: + assert False + + # check ASIC neighbor database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_NEIGHBOR_ENTRY") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 1 + route = json.loads(intf_entries[0]) + assert route["ip"] == "10.0.0.2" + assert route["rif"] == rif_oid + + (status, fvs) = tbl.get(intf_entries[0]) + assert status == True + for fv in fvs: + if fv[0] == "SAI_NEIGHBOR_ENTRY_ATTR_DST_MAC_ADDRESS": + assert fv[1] == "00:01:02:03:04:05" + + # remove neighbor + self.remove_neighbor("Ethernet8", "10.0.0.2") + + # remove IP from interface + self.remove_ip_address("Ethernet8", "10.0.0.1/24") + + # remove interface + self.remove_l3_intf("Ethernet8") + + # bring down interface + self.set_admin_status("Ethernet8", "down") + + # check application database + tbl = swsscommon.Table(self.pdb, "NEIGH_TABLE:Ethernet8") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + # check ASIC neighbor database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_NEIGHBOR_ENTRY") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + def test_NeighborAddRemoveIpv6WithVrf(self, dvs, testlog): + self.setup_db(dvs) + + for i in [0, 4]: + # record ASIC neighbor database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_NEIGHBOR_ENTRY") + old_neigh_entries = set(tbl.getKeys()) + + intf_name = "Ethernet" + str(i) + vrf_name = "Vrf_" + str(i) + + # bring up interface + self.set_admin_status(intf_name, "up") + + # create vrf + self.create_vrf(vrf_name) + + # create interface and get rif_oid + rif_oid = self.create_l3_intf(intf_name, vrf_name) + + # assign IP to interface + self.add_ip_address(intf_name, "2000::1/64") + + # add neighbor + self.add_neighbor(intf_name, "2000::2", "00:01:02:03:04:05") + + # check application database + tbl = swsscommon.Table(self.pdb, "NEIGH_TABLE:" + intf_name) + neigh_entries = tbl.getKeys() + assert len(neigh_entries) == 1 + assert neigh_entries[0] == "2000::2" + (status, fvs) = tbl.get(neigh_entries[0]) + assert status == True + assert len(fvs) == 2 + for fv in fvs: + if fv[0] == "neigh": + assert fv[1] == "00:01:02:03:04:05" + elif fv[0] == "family": + assert fv[1] == "IPv6" + else: + assert False + + # check ASIC neighbor interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_NEIGHBOR_ENTRY") + current_neigh_entries = set(tbl.getKeys()) + neigh_entries = list(current_neigh_entries - old_neigh_entries) + assert len(neigh_entries) == 1 + route = json.loads(neigh_entries[0]) + assert route["ip"] == "2000::2" + assert route["rif"] == rif_oid + + (status, fvs) = tbl.get(neigh_entries[0]) + assert status == True + for fv in fvs: + if fv[0] == "SAI_NEIGHBOR_ENTRY_ATTR_DST_MAC_ADDRESS": + assert fv[1] == "00:01:02:03:04:05" + + for i in [0, 4]: + # record ASIC neighbor database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_NEIGHBOR_ENTRY") + old_neigh_entries_cnt = len(tbl.getKeys()) + + intf_name = "Ethernet" + str(i) + vrf_name = "Vrf_" + str(i) + + # remove neighbor + self.remove_neighbor(intf_name, "2000::2") + + # remove IP from interface + self.remove_ip_address(intf_name, "2000::1/64") + + # remove interface + self.remove_l3_intf(intf_name) + + # remove vrf + self.remove_vrf(vrf_name) + + # bring down interface + self.set_admin_status(intf_name, "down") + + # check application database + tbl = swsscommon.Table(self.pdb, "NEIGH_TABLE:" + intf_name) + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + # check ASIC neighbor interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_NEIGHBOR_ENTRY") + current_neigh_entries_cnt = len(tbl.getKeys()) + dec_neigh_entries_cnt = (old_neigh_entries_cnt - current_neigh_entries_cnt) + assert dec_neigh_entries_cnt == 1 + + def test_NeighborAddRemoveIpv4WithVrf(self, dvs, testlog): + self.setup_db(dvs) + + for i in [0, 4]: + # record ASIC neighbor database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_NEIGHBOR_ENTRY") + old_neigh_entries = set(tbl.getKeys()) + + intf_name = "Ethernet" + str(i) + vrf_name = "Vrf_" + str(i) + + # bring up interface + self.set_admin_status(intf_name, "up") + + # create vrf + self.create_vrf(vrf_name) + + # create interface and get rif_oid + rif_oid = self.create_l3_intf(intf_name, vrf_name) + + # assign IP to interface + self.add_ip_address(intf_name, "10.0.0.1/24") + + # add neighbor + self.add_neighbor(intf_name, "10.0.0.2", "00:01:02:03:04:05") + + # check application database + tbl = swsscommon.Table(self.pdb, "NEIGH_TABLE:" + intf_name) + neigh_entries = tbl.getKeys() + assert len(neigh_entries) == 1 + assert neigh_entries[0] == "10.0.0.2" + (status, fvs) = tbl.get(neigh_entries[0]) + assert status == True + assert len(fvs) == 2 + for fv in fvs: + if fv[0] == "neigh": + assert fv[1] == "00:01:02:03:04:05" + elif fv[0] == "family": + assert fv[1] == "IPv4" + else: + assert False + + # check ASIC neighbor interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_NEIGHBOR_ENTRY") + current_neigh_entries = set(tbl.getKeys()) + neigh_entries = list(current_neigh_entries - old_neigh_entries) + assert len(neigh_entries) == 1 + route = json.loads(neigh_entries[0]) + assert route["ip"] == "10.0.0.2" + assert route["rif"] == rif_oid + + (status, fvs) = tbl.get(neigh_entries[0]) + assert status == True + for fv in fvs: + if fv[0] == "SAI_NEIGHBOR_ENTRY_ATTR_DST_MAC_ADDRESS": + assert fv[1] == "00:01:02:03:04:05" + + for i in [0, 4]: + # record ASIC neighbor database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_NEIGHBOR_ENTRY") + old_neigh_entries_cnt = len(tbl.getKeys()) + + intf_name = "Ethernet" + str(i) + vrf_name = "Vrf_" + str(i) + + # remove neighbor + self.remove_neighbor(intf_name, "10.0.0.2") + + # remove IP from interface + self.remove_ip_address(intf_name, "10.0.0.1/24") + + # remove interface + self.remove_l3_intf(intf_name) + + # remove vrf + self.remove_vrf(vrf_name) + + # bring down interface + self.set_admin_status(intf_name, "down") + + # check application database + tbl = swsscommon.Table(self.pdb, "NEIGH_TABLE:" + intf_name) + intf_entries = tbl.getKeys() + assert len(intf_entries) == 0 + + # check ASIC neighbor interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_NEIGHBOR_ENTRY") + current_neigh_entries_cnt = len(tbl.getKeys()) + dec_neigh_entries_cnt = (old_neigh_entries_cnt - current_neigh_entries_cnt) + assert dec_neigh_entries_cnt == 1 \ No newline at end of file diff --git a/tests/test_route.py b/tests/test_route.py index 12c30d99d731..e1c12857e3e2 100644 --- a/tests/test_route.py +++ b/tests/test_route.py @@ -5,49 +5,616 @@ import json class TestRoute(object): - def test_RouteAdd(self, dvs, testlog): - - config_db = swsscommon.DBConnector(swsscommon.CONFIG_DB, dvs.redis_sock, 0) - intf_tbl = swsscommon.Table(config_db, "INTERFACE") - fvs = swsscommon.FieldValuePairs([("NULL","NULL")]) - intf_tbl.set("Ethernet0|10.0.0.0/31", fvs) - intf_tbl.set("Ethernet4|10.0.0.2/31", fvs) - intf_tbl.set("Ethernet0", fvs) - intf_tbl.set("Ethernet4", fvs) - dvs.runcmd("ifconfig Ethernet0 up") - dvs.runcmd("ifconfig Ethernet4 up") - - dvs.servers[0].runcmd("ifconfig eth0 10.0.0.1/31") + def setup_db(self, dvs): + self.pdb = swsscommon.DBConnector(0, dvs.redis_sock, 0) + self.adb = swsscommon.DBConnector(1, dvs.redis_sock, 0) + self.cdb = swsscommon.DBConnector(4, dvs.redis_sock, 0) + + def set_admin_status(self, interface, status): + tbl = swsscommon.Table(self.cdb, "PORT") + fvs = swsscommon.FieldValuePairs([("admin_status", status)]) + tbl.set(interface, fvs) + time.sleep(1) + + def create_vrf(self, vrf_name): + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER") + initial_entries = set(tbl.getKeys()) + + tbl = swsscommon.Table(self.cdb, "VRF") + fvs = swsscommon.FieldValuePairs([('empty', 'empty')]) + tbl.set(vrf_name, fvs) + time.sleep(1) + + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER") + current_entries = set(tbl.getKeys()) + assert len(current_entries - initial_entries) == 1 + return list(current_entries - initial_entries)[0] + + def remove_vrf(self, vrf_name): + tbl = swsscommon.Table(self.cdb, "VRF") + tbl._del(vrf_name) + time.sleep(1) + + def create_l3_intf(self, interface, vrf_name): + tbl = swsscommon.Table(self.cdb, "INTERFACE") + if len(vrf_name) == 0: + fvs = swsscommon.FieldValuePairs([("NULL", "NULL")]) + else: + fvs = swsscommon.FieldValuePairs([("vrf_name", vrf_name)]) + tbl.set(interface, fvs) + time.sleep(1) + + def remove_l3_intf(self, interface): + tbl = swsscommon.Table(self.cdb, "INTERFACE") + tbl._del(interface) + time.sleep(1) + + def add_ip_address(self, interface, ip): + tbl = swsscommon.Table(self.cdb, "INTERFACE") + fvs = swsscommon.FieldValuePairs([("NULL", "NULL")]) + tbl.set(interface + "|" + ip, fvs) + time.sleep(1) + + def remove_ip_address(self, interface, ip): + tbl = swsscommon.Table(self.cdb, "INTERFACE") + tbl._del(interface + "|" + ip) + time.sleep(1) + + def create_route_entry(self, key, pairs): + tbl = swsscommon.ProducerStateTable(self.pdb, "ROUTE_TABLE") + fvs = swsscommon.FieldValuePairs(pairs) + tbl.set(key, fvs) + time.sleep(1) + + def remove_route_entry(self, key): + tbl = swsscommon.ProducerStateTable(self.pdb, "ROUTE_TABLE") + tbl._del(key) + time.sleep(1) + + def clear_srv_config(self, dvs): + dvs.servers[0].runcmd("ip address flush dev eth0") + dvs.servers[1].runcmd("ip address flush dev eth0") + dvs.servers[2].runcmd("ip address flush dev eth0") + dvs.servers[3].runcmd("ip address flush dev eth0") + + def test_RouteAddRemoveIpv4Route(self, dvs, testlog): + self.setup_db(dvs) + + self.clear_srv_config(dvs) + + # create l3 interface + self.create_l3_intf("Ethernet0", "") + self.create_l3_intf("Ethernet4", "") + + # set ip address + self.add_ip_address("Ethernet0", "10.0.0.0/31") + self.add_ip_address("Ethernet4", "10.0.0.2/31") + + # bring up interface + self.set_admin_status("Ethernet0", "up") + self.set_admin_status("Ethernet4", "up") + + # set ip address and default route + dvs.servers[0].runcmd("ip address add 10.0.0.1/31 dev eth0") dvs.servers[0].runcmd("ip route add default via 10.0.0.0") - dvs.servers[1].runcmd("ifconfig eth0 10.0.0.3/31") + dvs.servers[1].runcmd("ip address add 10.0.0.3/31 dev eth0") dvs.servers[1].runcmd("ip route add default via 10.0.0.2") # get neighbor and arp entry dvs.servers[0].runcmd("ping -c 1 10.0.0.3") - db = swsscommon.DBConnector(0, dvs.redis_sock, 0) - ps = swsscommon.ProducerStateTable(db, "ROUTE_TABLE") - fvs = swsscommon.FieldValuePairs([("nexthop","10.0.0.1"), ("ifname", "Ethernet0")]) + # add route entry + dvs.runcmd("vtysh -c \"configure terminal\" -c \"ip route 2.2.2.0/24 10.0.0.1\"") + time.sleep(1) + + # check application database + tbl = swsscommon.Table(self.pdb, "ROUTE_TABLE") + route_entries = tbl.getKeys() + assert "2.2.2.0/24" in route_entries + + # check ASIC route database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "2.2.2.0/24": + route_found = True + assert route_found == True + + # remove route entry + dvs.runcmd("vtysh -c \"configure terminal\" -c \"no ip route 2.2.2.0/24 10.0.0.1\"") + time.sleep(1) + + # check application database + tbl = swsscommon.Table(self.pdb, "ROUTE_TABLE") + route_entries = tbl.getKeys() + assert "2.2.2.0/24" not in route_entries + + # check ASIC route database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + assert route["dest"] != "2.2.2.0/24" + + # remove ip address + self.remove_ip_address("Ethernet0", "10.0.0.0/31") + self.remove_ip_address("Ethernet4", "10.0.0.2/31") + + # remove l3 interface + self.remove_l3_intf("Ethernet0") + self.remove_l3_intf("Ethernet4") + + self.set_admin_status("Ethernet0", "down") + self.set_admin_status("Ethernet4", "down") + + # remove ip address and default route + dvs.servers[0].runcmd("ip route del default dev eth0") + dvs.servers[0].runcmd("ip address del 10.0.0.1/31 dev eth0") + + dvs.servers[1].runcmd("ip route del default dev eth0") + dvs.servers[1].runcmd("ip address del 10.0.0.3/31 dev eth0") + + def test_RouteAddRemoveIpv6Route(self, dvs, testlog): + self.setup_db(dvs) - pubsub = dvs.SubscribeAsicDbObject("SAI_OBJECT_TYPE_ROUTE_ENTRY") + # create l3 interface + self.create_l3_intf("Ethernet0", "") + self.create_l3_intf("Ethernet4", "") - ps.set("2.2.2.0/24", fvs) + # bring up interface + self.set_admin_status("Ethernet0", "up") + self.set_admin_status("Ethernet4", "up") - # check if route was propagated to ASIC DB + # set ip address + self.add_ip_address("Ethernet0", "2000::1/64") + self.add_ip_address("Ethernet4", "2001::1/64") + dvs.runcmd("sysctl -w net.ipv6.conf.all.forwarding=1") - (addobjs, delobjs) = dvs.GetSubscribedAsicDbObjects(pubsub) + # set ip address and default route + dvs.servers[0].runcmd("ip -6 address add 2000::2/64 dev eth0") + dvs.servers[0].runcmd("ip -6 route add default via 2000::1") - assert len(addobjs) >= 1 + dvs.servers[1].runcmd("ip -6 address add 2001::2/64 dev eth0") + dvs.servers[1].runcmd("ip -6 route add default via 2001::1") + time.sleep(2) - adb = swsscommon.DBConnector(swsscommon.ASIC_DB, dvs.redis_sock, 0) - atbl = swsscommon.Table(adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") - keys = atbl.getKeys() - found = False + # get neighbor entry + dvs.servers[0].runcmd("ping -6 -c 1 2001::2") - for key in keys: + # add route entry + dvs.runcmd("vtysh -c \"configure terminal\" -c \"ipv6 route 3000::0/64 2000::2\"") + time.sleep(2) + + # check application database + tbl = swsscommon.Table(self.pdb, "ROUTE_TABLE") + route_entries = tbl.getKeys() + assert "3000::/64" in route_entries + + # check ASIC route database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "3000::/64": + route_found = True + assert route_found == True + + # remove route entry + dvs.runcmd("vtysh -c \"configure terminal\" -c \"no ipv6 route 3000::0/64 2000::2\"") + time.sleep(1) + + # check application database + tbl = swsscommon.Table(self.pdb, "ROUTE_TABLE") + route_entries = tbl.getKeys() + assert "3000::/64" not in route_entries + + # check ASIC route database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): route = json.loads(key) - if route['dest'] == "2.2.2.0/24": - found = True + assert route["dest"] != "3000::/64" + + # remove ip address + self.remove_ip_address("Ethernet0", "2000::1/64") + self.remove_ip_address("Ethernet4", "2001::1/64") + + # remove l3 interface + self.remove_l3_intf("Ethernet0") + self.remove_l3_intf("Ethernet4") + + self.set_admin_status("Ethernet0", "down") + self.set_admin_status("Ethernet4", "down") + + # remove ip address and default route + dvs.servers[0].runcmd("ip -6 route del default dev eth0") + dvs.servers[0].runcmd("ip -6 address del 2000::2/64 dev eth0") + + dvs.servers[1].runcmd("ip -6 route del default dev eth0") + dvs.servers[1].runcmd("ip -6 address del 2001::2/64 dev eth0") + + def test_RouteAddRemoveIpv4RouteWithVrf(self, dvs, testlog): + self.setup_db(dvs) + + # create vrf + vrf_1_oid = self.create_vrf("Vrf_1") + vrf_2_oid = self.create_vrf("Vrf_2") + + # create l3 interface + self.create_l3_intf("Ethernet0", "Vrf_1") + self.create_l3_intf("Ethernet4", "Vrf_1") + self.create_l3_intf("Ethernet8", "Vrf_2") + self.create_l3_intf("Ethernet12", "Vrf_2") + + # set ip address + self.add_ip_address("Ethernet0", "10.0.0.0/31") + self.add_ip_address("Ethernet4", "10.0.0.2/31") + self.add_ip_address("Ethernet8", "10.0.0.0/31") + self.add_ip_address("Ethernet12", "10.0.0.2/31") + + # bring up interface + self.set_admin_status("Ethernet0", "up") + self.set_admin_status("Ethernet4", "up") + self.set_admin_status("Ethernet8", "up") + self.set_admin_status("Ethernet12", "up") + + # set ip address and default route + dvs.servers[0].runcmd("ip address add 10.0.0.1/31 dev eth0") + dvs.servers[0].runcmd("ip route add default via 10.0.0.0") + + dvs.servers[1].runcmd("ip address add 10.0.0.3/31 dev eth0") + dvs.servers[1].runcmd("ip route add default via 10.0.0.2") + + dvs.servers[2].runcmd("ip address add 10.0.0.1/31 dev eth0") + dvs.servers[2].runcmd("ip route add default via 10.0.0.0") + + dvs.servers[3].runcmd("ip address add 10.0.0.3/31 dev eth0") + dvs.servers[3].runcmd("ip route add default via 10.0.0.2") + + time.sleep(1) + + # get neighbor entry + dvs.servers[0].runcmd("ping -c 1 10.0.0.3") + dvs.servers[2].runcmd("ping -c 1 10.0.0.3") + + # add route + dvs.runcmd("vtysh -c \"configure terminal\" -c \"ip route 2.2.2.0/24 10.0.0.1 vrf Vrf_1\"") + dvs.runcmd("vtysh -c \"configure terminal\" -c \"ip route 3.3.3.0/24 10.0.0.1 vrf Vrf_2\"") + time.sleep(1) + + # check application database + tbl = swsscommon.Table(self.pdb, "ROUTE_TABLE:Vrf_1") + route_entries = tbl.getKeys() + assert "2.2.2.0/24" in route_entries + + tbl = swsscommon.Table(self.pdb, "ROUTE_TABLE:Vrf_2") + route_entries = tbl.getKeys() + assert "3.3.3.0/24" in route_entries + + # check ASIC route database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "2.2.2.0/24" and route["vr"] == vrf_1_oid: + route_Vrf_1_found = True + if route["dest"] == "3.3.3.0/24" and route["vr"] == vrf_2_oid: + route_Vrf_2_found = True + assert route_Vrf_1_found == True and route_Vrf_2_found == True + + # remove route + dvs.runcmd("vtysh -c \"configure terminal\" -c \"no ip route 2.2.2.0/24 10.0.0.1 vrf Vrf_1\"") + dvs.runcmd("vtysh -c \"configure terminal\" -c \"no ip route 3.3.3.0/24 10.0.0.1 vrf Vrf_2\"") + time.sleep(1) + + # check application database + tbl = swsscommon.Table(self.pdb, "ROUTE_TABLE:Vrf_1") + route_entries = tbl.getKeys() + assert "2.2.2.0/24" not in route_entries + + tbl = swsscommon.Table(self.pdb, "ROUTE_TABLE:Vrf_2") + route_entries = tbl.getKeys() + assert "3.3.3.0/24" not in route_entries + + # check ASIC route database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + assert route["dest"] != "2.2.2.0/24" and route["dest"] != "3.3.3.0/24" + + # remove ip address + self.remove_ip_address("Ethernet0", "10.0.0.0/31") + self.remove_ip_address("Ethernet4", "10.0.0.2/31") + self.remove_ip_address("Ethernet8", "10.0.0.0/31") + self.remove_ip_address("Ethernet12", "10.0.0.2/31") + + # remove l3 interface + self.remove_l3_intf("Ethernet0") + self.remove_l3_intf("Ethernet4") + self.remove_l3_intf("Ethernet8") + self.remove_l3_intf("Ethernet12") + + self.set_admin_status("Ethernet0", "down") + self.set_admin_status("Ethernet4", "down") + self.set_admin_status("Ethernet8", "down") + self.set_admin_status("Ethernet12", "down") + + # remove vrf + self.remove_vrf("Vrf_1") + self.remove_vrf("Vrf_2") + + # remove ip address and default route + dvs.servers[0].runcmd("ip route del default dev eth0") + dvs.servers[0].runcmd("ip address del 10.0.0.1/31 dev eth0") + dvs.servers[1].runcmd("ip route del default dev eth0") + dvs.servers[1].runcmd("ip address del 10.0.0.3/31 dev eth0") + dvs.servers[2].runcmd("ip route del default dev eth0") + dvs.servers[2].runcmd("ip address del 10.0.0.1/31 dev eth0") + dvs.servers[3].runcmd("ip route del default dev eth0") + dvs.servers[3].runcmd("ip address del 10.0.0.3/31 dev eth0") + + def test_RouteAddRemoveIpv6RouteWithVrf(self, dvs, testlog): + self.setup_db(dvs) + + # create vrf + vrf_1_oid = self.create_vrf("Vrf_1") + vrf_2_oid = self.create_vrf("Vrf_2") + + # create l3 interface + self.create_l3_intf("Ethernet0", "Vrf_1") + self.create_l3_intf("Ethernet4", "Vrf_1") + self.create_l3_intf("Ethernet8", "Vrf_2") + self.create_l3_intf("Ethernet12", "Vrf_2") + + # bring up interface + self.set_admin_status("Ethernet0", "up") + self.set_admin_status("Ethernet4", "up") + self.set_admin_status("Ethernet8", "up") + self.set_admin_status("Ethernet12", "up") + + # set ip address + self.add_ip_address("Ethernet0", "2000::1/64") + self.add_ip_address("Ethernet4", "2001::1/64") + self.add_ip_address("Ethernet8", "2000::1/64") + self.add_ip_address("Ethernet12", "2001::1/64") + + dvs.runcmd("sysctl -w net.ipv6.conf.all.forwarding=1") + + # set ip address and default route + dvs.servers[0].runcmd("ip -6 address add 2000::2/64 dev eth0") + dvs.servers[0].runcmd("ip -6 route add default via 2000::1") + dvs.servers[1].runcmd("ip -6 address add 2001::2/64 dev eth0") + dvs.servers[1].runcmd("ip -6 route add default via 2001::1") + dvs.servers[2].runcmd("ip -6 address add 2000::2/64 dev eth0") + dvs.servers[2].runcmd("ip -6 route add default via 2000::1") + dvs.servers[3].runcmd("ip -6 address add 2001::2/64 dev eth0") + dvs.servers[3].runcmd("ip -6 route add default via 2001::1") + time.sleep(2) + + # get neighbor entry + dvs.servers[0].runcmd("ping -6 -c 1 2001::2") + dvs.servers[2].runcmd("ping -6 -c 1 2001::2") + time.sleep(2) + + # add route + dvs.runcmd("vtysh -c \"configure terminal\" -c \"ipv6 route 3000::0/64 2000::2 vrf Vrf_1\"") + dvs.runcmd("vtysh -c \"configure terminal\" -c \"ipv6 route 4000::0/64 2000::2 vrf Vrf_2\"") + time.sleep(2) + + # check application database + tbl = swsscommon.Table(self.pdb, "ROUTE_TABLE:Vrf_1") + route_entries = tbl.getKeys() + assert "3000::/64" in route_entries + + tbl = swsscommon.Table(self.pdb, "ROUTE_TABLE:Vrf_2") + route_entries = tbl.getKeys() + assert "4000::/64" in route_entries + + # check ASIC route database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + if route["dest"] == "3000::/64" and route["vr"] == vrf_1_oid: + route_Vrf_1_found = True + if route["dest"] == "4000::/64" and route["vr"] == vrf_2_oid: + route_Vrf_2_found = True + assert route_Vrf_1_found == True and route_Vrf_2_found == True + + + # remove route + dvs.runcmd("vtysh -c \"configure terminal\" -c \"no ipv6 route 3000::0/64 2000::2 vrf Vrf_1\"") + dvs.runcmd("vtysh -c \"configure terminal\" -c \"no ipv6 route 4000::0/64 2000::2 vrf Vrf_2\"") + + # check application database + tbl = swsscommon.Table(self.pdb, "ROUTE_TABLE:Vrf_1") + route_entries = tbl.getKeys() + assert "3000::/64" not in route_entries + + tbl = swsscommon.Table(self.pdb, "ROUTE_TABLE:Vrf_2") + route_entries = tbl.getKeys() + assert "4000::/64" not in route_entries + + # check ASIC route database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + assert route["dest"] != "3000::/64" and route["dest"] != "4000::/64" + + # remove ip address + self.remove_ip_address("Ethernet0", "2000::1/64") + self.remove_ip_address("Ethernet4", "2001::1/64") + self.remove_ip_address("Ethernet8", "2000::1/64") + self.remove_ip_address("Ethernet12", "2001::1/64") + + # remove l3 interface + self.remove_l3_intf("Ethernet0") + self.remove_l3_intf("Ethernet4") + self.remove_l3_intf("Ethernet8") + self.remove_l3_intf("Ethernet12") + + # bring down interface + self.set_admin_status("Ethernet0", "down") + self.set_admin_status("Ethernet4", "down") + self.set_admin_status("Ethernet8", "down") + self.set_admin_status("Ethernet12", "down") + + # remove vrf + self.remove_vrf("Vrf_1") + self.remove_vrf("Vrf_2") + + # remove ip address and default route + dvs.servers[0].runcmd("ip -6 route del default dev eth0") + dvs.servers[0].runcmd("ip -6 address del 2000::2/64 dev eth0") + dvs.servers[1].runcmd("ip -6 route del default dev eth0") + dvs.servers[1].runcmd("ip -6 address del 2001::2/64 dev eth0") + dvs.servers[2].runcmd("ip -6 route del default dev eth0") + dvs.servers[2].runcmd("ip -6 address del 2000::2/64 dev eth0") + dvs.servers[3].runcmd("ip -6 route del default dev eth0") + dvs.servers[3].runcmd("ip -6 address del 2001::2/64 dev eth0") + + def test_RouteAndNexthopInDifferentVrf(self, dvs, testlog): + self.setup_db(dvs) + + # create vrf + vrf_1_oid = self.create_vrf("Vrf_1") + vrf_2_oid = self.create_vrf("Vrf_2") + + # create l3 interface + self.create_l3_intf("Ethernet0", "Vrf_1") + self.create_l3_intf("Ethernet4", "Vrf_1") + self.create_l3_intf("Ethernet8", "Vrf_2") + self.create_l3_intf("Ethernet12", "Vrf_2") + + # set ip address + self.add_ip_address("Ethernet0", "10.0.0.1/24") + self.add_ip_address("Ethernet4", "10.0.1.1/24") + self.add_ip_address("Ethernet8", "20.0.0.1/24") + self.add_ip_address("Ethernet12", "20.0.1.1/24") + + # bring up interface + self.set_admin_status("Ethernet0", "up") + self.set_admin_status("Ethernet4", "up") + self.set_admin_status("Ethernet8", "up") + self.set_admin_status("Ethernet12", "up") + + # set ip address and default route + dvs.servers[0].runcmd("ip address add 10.0.0.2/24 dev eth0") + dvs.servers[0].runcmd("ip route add default via 10.0.0.1") + + dvs.servers[1].runcmd("ip address add 10.0.1.2/24 dev eth0") + dvs.servers[1].runcmd("ip route add default via 10.0.1.1") + + dvs.servers[2].runcmd("ip address add 20.0.0.2/24 dev eth0") + dvs.servers[2].runcmd("ip route add default via 20.0.0.1") + + dvs.servers[3].runcmd("ip address add 20.0.1.2/24 dev eth0") + dvs.servers[3].runcmd("ip route add default via 20.0.1.1") + + time.sleep(1) + + # get neighbor entry + dvs.servers[0].runcmd("ping -c 1 10.0.1.2") + dvs.servers[2].runcmd("ping -c 1 20.0.1.2") + + # add route + dvs.runcmd("vtysh -c \"configure terminal\" -c \"ip route 20.0.1.2/32 20.0.1.2 vrf Vrf_1 nexthop-vrf Vrf_2\"") + dvs.runcmd("vtysh -c \"configure terminal\" -c \"ip route 10.0.0.2/32 10.0.0.2 vrf Vrf_2 nexthop-vrf Vrf_1\"") + time.sleep(1) + + # check application database + tbl = swsscommon.Table(self.pdb, "ROUTE_TABLE:Vrf_1") + route_entries = tbl.getKeys() + assert "20.0.1.2" in route_entries + + tbl = swsscommon.Table(self.pdb, "ROUTE_TABLE:Vrf_2") + route_entries = tbl.getKeys() + assert "10.0.0.2" in route_entries + + # check ASIC neighbor interface database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP") + nexthop_entries = tbl.getKeys() + for key in nexthop_entries: + (status, fvs) = tbl.get(key) + assert status == True + for fv in fvs: + if fv[0] == "SAI_NEXT_HOP_ATTR_IP" and fv[1] == "20.0.1.2": + nexthop2_found = True + nexthop2_oid = key + if fv[0] == "SAI_NEXT_HOP_ATTR_IP" and fv[1] == "10.0.0.2": + nexthop1_found = True + nexthop1_oid = key + + assert nexthop1_found == True and nexthop2_found == True + + # check ASIC route database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + route_entries = tbl.getKeys() + for key in route_entries: + route = json.loads(key) + if route["dest"] == "10.0.0.2/32" and route["vr"] == vrf_2_oid: + (status, fvs) = tbl.get(key) + assert status == True + for fv in fvs: + if fv[0] == "SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID": + assert fv[1] == nexthop1_oid + route1_found = True + if route["dest"] == "20.0.1.2/32" and route["vr"] == vrf_1_oid: + (status, fvs) = tbl.get(key) + assert status == True + for fv in fvs: + if fv[0] == "SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID": + assert fv[1] == nexthop2_oid + route2_found = True + assert route1_found == True and route2_found == True + + # Ping should work + ping_stats = dvs.servers[0].runcmd("ping -c 1 20.0.1.2") + assert ping_stats == 0 + + # remove route + dvs.runcmd("vtysh -c \"configure terminal\" -c \"no ip route 20.0.1.2/32 20.0.1.2 vrf Vrf_1 nexthop-vrf Vrf_2\"") + dvs.runcmd("vtysh -c \"configure terminal\" -c \"no ip route 10.0.0.2/32 10.0.0.2 vrf Vrf_2 nexthop-vrf Vrf_1\"") + time.sleep(1) + + # check application database + tbl = swsscommon.Table(self.pdb, "ROUTE_TABLE:Vrf_1") + route_entries = tbl.getKeys() + assert "20.0.1.2" not in route_entries + + tbl = swsscommon.Table(self.pdb, "ROUTE_TABLE:Vrf_2") + route_entries = tbl.getKeys() + assert "10.0.0.2" not in route_entries + + # check ASIC route database + tbl = swsscommon.Table(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY") + for key in tbl.getKeys(): + route = json.loads(key) + assert route["dest"] != "10.0.0.2/32" and route["dest"] != "20.0.1.2/32" + + # remove ip address + self.remove_ip_address("Ethernet0", "10.0.0.1/24") + self.remove_ip_address("Ethernet4", "10.0.1.1/24") + self.remove_ip_address("Ethernet8", "20.0.0.1/24") + self.remove_ip_address("Ethernet12", "20.0.1.1/24") + + # remove l3 interface + self.remove_l3_intf("Ethernet0") + self.remove_l3_intf("Ethernet4") + self.remove_l3_intf("Ethernet8") + self.remove_l3_intf("Ethernet12") + + self.set_admin_status("Ethernet0", "down") + self.set_admin_status("Ethernet4", "down") + self.set_admin_status("Ethernet8", "down") + self.set_admin_status("Ethernet12", "down") + + # remove vrf + self.remove_vrf("Vrf_1") + self.remove_vrf("Vrf_2") - assert found + # remove ip address and default route + dvs.servers[0].runcmd("ip route del default dev eth0") + dvs.servers[0].runcmd("ip address del 10.0.0.2/24 dev eth0") + dvs.servers[1].runcmd("ip route del default dev eth0") + dvs.servers[1].runcmd("ip address del 10.0.1.2/24 dev eth0") + dvs.servers[2].runcmd("ip route del default dev eth0") + dvs.servers[2].runcmd("ip address del 20.0.0.2/24 dev eth0") + dvs.servers[3].runcmd("ip route del default dev eth0") + dvs.servers[3].runcmd("ip address del 20.0.1.2/24 dev eth0") diff --git a/tests/test_vnet.py b/tests/test_vnet.py index 2870f5bd8edd..76c8c2726777 100644 --- a/tests/test_vnet.py +++ b/tests/test_vnet.py @@ -254,6 +254,10 @@ def delete_vlan_interface(dvs, ifname, ipaddr): time.sleep(2) + delete_entry_tbl(conf_db, "VLAN_INTERFACE", ifname) + + time.sleep(2) + def create_phy_interface(dvs, ifname, vnet_name, ipaddr): conf_db = swsscommon.DBConnector(swsscommon.CONFIG_DB, dvs.redis_sock, 0) @@ -296,6 +300,10 @@ def delete_phy_interface(dvs, ifname, ipaddr): time.sleep(2) + delete_entry_tbl(conf_db, "INTERFACE", ifname) + + time.sleep(2) + def create_vnet_entry(dvs, name, tunnel, vni, peer_list, scope=""): conf_db = swsscommon.DBConnector(swsscommon.CONFIG_DB, dvs.redis_sock, 0) @@ -544,8 +552,7 @@ def check_del_vnet_entry(self, dvs, name): def vnet_route_ids(self, dvs, name, local=False): vr_set = set() - if not local: - vr_set.add(self.vr_map[name].get('ing')) + vr_set.add(self.vr_map[name].get('ing')) try: for peer in self.vr_map[name].get('peer'): @@ -575,8 +582,8 @@ def check_router_interface(self, dvs, name, vlan_oid=0): new_rif = get_created_entry(asic_db, self.ASIC_RIF_TABLE, self.rifs) check_object(asic_db, self.ASIC_RIF_TABLE, new_rif, expected_attr) - #IP2ME and subnet routes will be created with every router interface - new_route = get_created_entries(asic_db, self.ASIC_ROUTE_ENTRY, self.routes, 2) + #IP2ME route will be created with every router interface + new_route = get_created_entries(asic_db, self.ASIC_ROUTE_ENTRY, self.routes, 1) self.rifs.add(new_rif) self.routes.update(new_route) diff --git a/tests/test_vrf.py b/tests/test_vrf.py index a53d498b98c6..d8d6c9595b25 100644 --- a/tests/test_vrf.py +++ b/tests/test_vrf.py @@ -2,166 +2,174 @@ import time import json import random -import time from pprint import pprint +class TestVrf(object): + def setup_db(self, dvs): + self.pdb = swsscommon.DBConnector(0, dvs.redis_sock, 0) + self.adb = swsscommon.DBConnector(1, dvs.redis_sock, 0) + self.cdb = swsscommon.DBConnector(4, dvs.redis_sock, 0) -def create_entry(tbl, key, pairs): - fvs = swsscommon.FieldValuePairs(pairs) - tbl.set(key, fvs) - - # FIXME: better to wait until DB create them - time.sleep(1) - - -def create_entry_tbl(db, table, key, pairs): - tbl = swsscommon.Table(db, table) - create_entry(tbl, key, pairs) - - -def create_entry_pst(db, table, key, pairs): - tbl = swsscommon.ProducerStateTable(db, table) - create_entry(tbl, key, pairs) - - -def delete_entry_tbl(db, table, key): - tbl = swsscommon.Table(db, table) - tbl._del(key) - time.sleep(1) - -def delete_entry_pst(db, table, key): - tbl = swsscommon.ProducerStateTable(db, table) - tbl._del(key) - time.sleep(1) + def create_entry(self, tbl, key, pairs): + fvs = swsscommon.FieldValuePairs(pairs) + tbl.set(key, fvs) + time.sleep(1) -def how_many_entries_exist(db, table): - tbl = swsscommon.Table(db, table) - return len(tbl.getKeys()) + def create_entry_tbl(self, db, table, key, pairs): + tbl = swsscommon.Table(db, table) + self.create_entry(tbl, key, pairs) + def delete_entry_tbl(self, db, table, key): + tbl = swsscommon.Table(db, table) + tbl._del(key) + time.sleep(1) -def entries(db, table): - tbl = swsscommon.Table(db, table) - return set(tbl.getKeys()) + def how_many_entries_exist(self, db, table): + tbl = swsscommon.Table(db, table) + return len(tbl.getKeys()) + def entries(self, db, table): + tbl = swsscommon.Table(db, table) + return set(tbl.getKeys()) -def is_vrf_attributes_correct(db, table, key, expected_attributes): - tbl = swsscommon.Table(db, table) - keys = set(tbl.getKeys()) - assert key in keys, "The created key wasn't found" + def is_vrf_attributes_correct(self, db, table, key, expected_attributes): + tbl = swsscommon.Table(db, table) + keys = set(tbl.getKeys()) + assert key in keys, "The created key wasn't found" - status, fvs = tbl.get(key) - assert status, "Got an error when get a key" + status, fvs = tbl.get(key) + assert status, "Got an error when get a key" - # filter the fake 'NULL' attribute out - fvs = filter(lambda x : x != ('NULL', 'NULL'), fvs) + # filter the fake 'NULL' attribute out + fvs = filter(lambda x : x != ('NULL', 'NULL'), fvs) - attr_keys = {entry[0] for entry in fvs} - assert attr_keys == set(expected_attributes.keys()) + attr_keys = {entry[0] for entry in fvs} + assert attr_keys == set(expected_attributes.keys()) - for name, value in fvs: - assert expected_attributes[name] == value, "Wrong value %s for the attribute %s = %s" % \ + for name, value in fvs: + assert expected_attributes[name] == value, "Wrong value %s for the attribute %s = %s" % \ (value, name, expected_attributes[name]) -def vrf_create(asic_db, appl_db, vrf_name, attributes, expected_attributes): - # check that the vrf wasn't exist before - assert how_many_entries_exist(asic_db, "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER") == 1, "The initial state is incorrect" + def vrf_create(self, dvs, vrf_name, attributes, expected_attributes): + # check that the vrf wasn't exist before + assert self.how_many_entries_exist(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER") == 1, "The initial state is incorrect" - # read existing entries in the DB - initial_entries = entries(asic_db, "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER") + # read existing entries in the DB + initial_entries = self.entries(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER") - # create a fake attribute if we don't have attributes in the request - if len(attributes) == 0: - attributes = [('empty', 'empty')] + # create a fake attribute if we don't have attributes in the request + if len(attributes) == 0: + attributes = [('empty', 'empty')] - # create the VRF entry in Config DB - create_entry_pst(appl_db, "VRF_TABLE", vrf_name, attributes) + # create the VRF entry in Config DB + self.create_entry_tbl(self.cdb, "VRF", vrf_name, attributes) - # check that the vrf entry was created - assert how_many_entries_exist(asic_db, "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER") == 2, "The vrf wasn't created" + # check vrf created in kernel + (status, rslt) = dvs.runcmd("ip link show " + vrf_name) + assert status == 0 - # find the id of the entry which was added - added_entry_id = list(entries(asic_db, "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER") - initial_entries)[0] + # check application database + tbl = swsscommon.Table(self.pdb, "VRF_TABLE") + intf_entries = tbl.getKeys() + assert len(intf_entries) == 1 + assert intf_entries[0] == vrf_name + exp_attr = {} + for an in xrange(len(attributes)): + exp_attr[attributes[an][0]] = attributes[an][1] + self.is_vrf_attributes_correct(self.pdb, "VRF_TABLE", vrf_name, exp_attr) + + # check that the vrf entry was created + assert self.how_many_entries_exist(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER") == 2, "The vrf wasn't created" + + # find the id of the entry which was added + added_entry_id = list(self.entries(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER") - initial_entries)[0] + + # check correctness of the created attributes + self.is_vrf_attributes_correct( + self.adb, + "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER", + added_entry_id, + expected_attributes, + ) - # check correctness of the created attributes - is_vrf_attributes_correct( - asic_db, - "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER", - added_entry_id, - expected_attributes, - ) + state = { + 'initial_entries': initial_entries, + 'entry_id': added_entry_id, + } - state = { - 'initial_entries': initial_entries, - 'entry_id': added_entry_id, - } + return state - return state + def vrf_remove(self, dvs, vrf_name, state): + # delete the created vrf entry + self.delete_entry_tbl(self.cdb, "VRF", vrf_name) -def vrf_remove(asic_db, appl_db, vrf_name, state): - # delete the created vrf entry - delete_entry_pst(appl_db, "VRF_TABLE", vrf_name) + # check application database + tbl = swsscommon.Table(self.pdb, "VRF_TABLE") + intf_entries = tbl.getKeys() + assert vrf_name not in intf_entries - # check that the vrf entry was removed - assert how_many_entries_exist(asic_db, "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER") == 1, "The vrf wasn't removed" + # check that the vrf entry was removed + assert self.how_many_entries_exist(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER") == 1, "The vrf wasn't removed" - # check that the correct vrf entry was removed - assert state['initial_entries'] == entries(asic_db, "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER"), "The incorrect entry was removed" + # check that the correct vrf entry was removed + assert state['initial_entries'] == self.entries(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER"), "The incorrect entry was removed" + # check vrf was removed from kernel + (status, rslt) = dvs.runcmd("ip link show " + vrf_name) + assert status != 0 -def vrf_update(asic_db, appl_db, vrf_name, attributes, expected_attributes, state): - # update the VRF entry in Config DB - create_entry_pst(appl_db, "VRF_TABLE", vrf_name, attributes) + def vrf_update(self, vrf_name, attributes, expected_attributes, state): + # update the VRF entry in Config DB + self.create_entry_tbl(self.cdb, "VRF", vrf_name, attributes) - # check correctness of the created attributes - is_vrf_attributes_correct( - asic_db, - "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER", - state['entry_id'], - expected_attributes, - ) + # check correctness of the created attributes + self.is_vrf_attributes_correct( + self.adb, + "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER", + state['entry_id'], + expected_attributes, + ) -def boolean_gen(): - result = random.choice(['false', 'true']) - return result, result + def boolean_gen(self): + result = random.choice(['false', 'true']) + return result, result -def mac_addr_gen(): - ns = [random.randint(0, 255) for _ in xrange(6)] - ns[0] &= 0xfe - mac = ':'.join("%02x" % n for n in ns) - return mac, mac.upper() + def mac_addr_gen(self): + ns = [random.randint(0, 255) for _ in xrange(6)] + ns[0] &= 0xfe + mac = ':'.join("%02x" % n for n in ns) + return mac, mac.upper() -def packet_action_gen(): - values = [ - ("drop", "SAI_PACKET_ACTION_DROP"), - ("forward", "SAI_PACKET_ACTION_FORWARD"), - ("copy", "SAI_PACKET_ACTION_COPY"), - ("copy_cancel", "SAI_PACKET_ACTION_COPY_CANCEL"), - ("trap", "SAI_PACKET_ACTION_TRAP"), - ("log", "SAI_PACKET_ACTION_LOG"), - ("deny", "SAI_PACKET_ACTION_DENY"), - ("transit", "SAI_PACKET_ACTION_TRANSIT"), - ] + def packet_action_gen(self): + values = [ + ("drop", "SAI_PACKET_ACTION_DROP"), + ("forward", "SAI_PACKET_ACTION_FORWARD"), + ("copy", "SAI_PACKET_ACTION_COPY"), + ("copy_cancel", "SAI_PACKET_ACTION_COPY_CANCEL"), + ("trap", "SAI_PACKET_ACTION_TRAP"), + ("log", "SAI_PACKET_ACTION_LOG"), + ("deny", "SAI_PACKET_ACTION_DENY"), + ("transit", "SAI_PACKET_ACTION_TRANSIT"), + ] - r = random.choice(values) - return r[0], r[1] + r = random.choice(values) + return r[0], r[1] -class TestVrf(object): - def test_VRFOrch_Comprehensive(self, dvs, testlog): - asic_db = swsscommon.DBConnector(swsscommon.ASIC_DB, dvs.redis_sock, 0) - appl_db = swsscommon.DBConnector(swsscommon.APPL_DB, dvs.redis_sock, 0) + def test_VRFMgr_Comprehensive(self, dvs, testlog): + self.setup_db(dvs) attributes = [ - ('v4', 'SAI_VIRTUAL_ROUTER_ATTR_ADMIN_V4_STATE', boolean_gen), - ('v6', 'SAI_VIRTUAL_ROUTER_ATTR_ADMIN_V6_STATE', boolean_gen), - ('src_mac', 'SAI_VIRTUAL_ROUTER_ATTR_SRC_MAC_ADDRESS', mac_addr_gen), - ('ttl_action', 'SAI_VIRTUAL_ROUTER_ATTR_VIOLATION_TTL1_PACKET_ACTION', packet_action_gen), - ('ip_opt_action', 'SAI_VIRTUAL_ROUTER_ATTR_VIOLATION_IP_OPTIONS_PACKET_ACTION', packet_action_gen), - ('l3_mc_action', 'SAI_VIRTUAL_ROUTER_ATTR_UNKNOWN_L3_MULTICAST_PACKET_ACTION', packet_action_gen), + ('v4', 'SAI_VIRTUAL_ROUTER_ATTR_ADMIN_V4_STATE', self.boolean_gen), + ('v6', 'SAI_VIRTUAL_ROUTER_ATTR_ADMIN_V6_STATE', self.boolean_gen), + ('src_mac', 'SAI_VIRTUAL_ROUTER_ATTR_SRC_MAC_ADDRESS', self.mac_addr_gen), + ('ttl_action', 'SAI_VIRTUAL_ROUTER_ATTR_VIOLATION_TTL1_PACKET_ACTION', self.packet_action_gen), + ('ip_opt_action', 'SAI_VIRTUAL_ROUTER_ATTR_VIOLATION_IP_OPTIONS_PACKET_ACTION', self.packet_action_gen), + ('l3_mc_action', 'SAI_VIRTUAL_ROUTER_ATTR_UNKNOWN_L3_MULTICAST_PACKET_ACTION', self.packet_action_gen), ] random.seed(int(time.clock())) @@ -170,7 +178,7 @@ def test_VRFOrch_Comprehensive(self, dvs, testlog): # generate testcases for all combinations of attributes req_attr = [] exp_attr = {} - vrf_name = "vrf_%d" % n + vrf_name = "Vrf_%d" % n bmask = 0x1 for an in xrange(len(attributes)): if (bmask & n) > 0: @@ -178,22 +186,22 @@ def test_VRFOrch_Comprehensive(self, dvs, testlog): req_attr.append((attributes[an][0], req_res)) exp_attr[attributes[an][1]] = exp_res bmask <<= 1 - state = vrf_create(asic_db, appl_db, vrf_name, req_attr, exp_attr) - vrf_remove(asic_db, appl_db, vrf_name, state) + state = self.vrf_create(dvs, vrf_name, req_attr, exp_attr) + self.vrf_remove(dvs, vrf_name, state) + + def test_VRFMgr(self, dvs, testlog): + self.setup_db(dvs) - def test_VRFOrch(self, dvs, testlog): - asic_db = swsscommon.DBConnector(swsscommon.ASIC_DB, dvs.redis_sock, 0) - appl_db = swsscommon.DBConnector(swsscommon.APPL_DB, dvs.redis_sock, 0) - state = vrf_create(asic_db, appl_db, "vrf0", + state = self.vrf_create(dvs, "Vrf0", [ ], { } ) - vrf_remove(asic_db, appl_db, "vrf0", state) + self.vrf_remove(dvs, "Vrf0", state) - state = vrf_create(asic_db, appl_db, "vrf1", + state = self.vrf_create(dvs, "Vrf1", [ ('v4', 'true'), ('src_mac', '02:04:06:07:08:09'), @@ -203,24 +211,23 @@ def test_VRFOrch(self, dvs, testlog): 'SAI_VIRTUAL_ROUTER_ATTR_SRC_MAC_ADDRESS': '02:04:06:07:08:09', } ) - vrf_remove(asic_db, appl_db, "vrf1", state) + self.vrf_remove(dvs, "Vrf1", state) - def test_VRFOrch_Update(self, dvs, testlog): - asic_db = swsscommon.DBConnector(swsscommon.ASIC_DB, dvs.redis_sock, 0) - appl_db = swsscommon.DBConnector(swsscommon.APPL_DB, dvs.redis_sock, 0) + def test_VRFMgr_Update(self, dvs, testlog): + self.setup_db(dvs) attributes = [ - ('v4', 'SAI_VIRTUAL_ROUTER_ATTR_ADMIN_V4_STATE', boolean_gen), - ('v6', 'SAI_VIRTUAL_ROUTER_ATTR_ADMIN_V6_STATE', boolean_gen), - ('src_mac', 'SAI_VIRTUAL_ROUTER_ATTR_SRC_MAC_ADDRESS', mac_addr_gen), - ('ttl_action', 'SAI_VIRTUAL_ROUTER_ATTR_VIOLATION_TTL1_PACKET_ACTION', packet_action_gen), - ('ip_opt_action', 'SAI_VIRTUAL_ROUTER_ATTR_VIOLATION_IP_OPTIONS_PACKET_ACTION', packet_action_gen), - ('l3_mc_action', 'SAI_VIRTUAL_ROUTER_ATTR_UNKNOWN_L3_MULTICAST_PACKET_ACTION', packet_action_gen), + ('v4', 'SAI_VIRTUAL_ROUTER_ATTR_ADMIN_V4_STATE', self.boolean_gen), + ('v6', 'SAI_VIRTUAL_ROUTER_ATTR_ADMIN_V6_STATE', self.boolean_gen), + ('src_mac', 'SAI_VIRTUAL_ROUTER_ATTR_SRC_MAC_ADDRESS', self.mac_addr_gen), + ('ttl_action', 'SAI_VIRTUAL_ROUTER_ATTR_VIOLATION_TTL1_PACKET_ACTION', self.packet_action_gen), + ('ip_opt_action', 'SAI_VIRTUAL_ROUTER_ATTR_VIOLATION_IP_OPTIONS_PACKET_ACTION', self.packet_action_gen), + ('l3_mc_action', 'SAI_VIRTUAL_ROUTER_ATTR_UNKNOWN_L3_MULTICAST_PACKET_ACTION', self.packet_action_gen), ] random.seed(int(time.clock())) - state = vrf_create(asic_db, appl_db, "vrf_a", + state = self.vrf_create(dvs, "Vrf_a", [ ], { @@ -234,6 +241,62 @@ def test_VRFOrch_Update(self, dvs, testlog): req_res, exp_res = attr[2]() req_attr.append((attr[0], req_res)) exp_attr[attr[1]] = exp_res - vrf_update(asic_db, appl_db, "vrf_a", req_attr, exp_attr, state) + self.vrf_update("Vrf_a", req_attr, exp_attr, state) + + self.vrf_remove(dvs, "Vrf_a", state) + + def test_VRFMgr_Capacity(self, dvs, testlog): + self.setup_db(dvs) + + initial_entries_cnt = self.how_many_entries_exist(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER") + + maximum_vrf_cnt = 999 + + def create_entry(self, tbl, key, pairs): + fvs = swsscommon.FieldValuePairs(pairs) + tbl.set(key, fvs) + time.sleep(1) + + def create_entry_tbl(self, db, table, key, pairs): + tbl = swsscommon.Table(db, table) + self.create_entry(tbl, key, pairs) + + # create the VRF entry in Config DB + tbl = swsscommon.Table(self.cdb, "VRF") + fvs = swsscommon.FieldValuePairs([('empty', 'empty')]) + for i in range(maximum_vrf_cnt): + tbl.set("Vrf_%d" % i, fvs) + + # wait for all VRFs pushed to database and linux + time.sleep(30) + + # check app_db + intf_entries_cnt = self.how_many_entries_exist(self.pdb, "VRF_TABLE") + assert intf_entries_cnt == maximum_vrf_cnt + + # check asic_db + current_entries_cnt = self.how_many_entries_exist(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER") + assert (current_entries_cnt - initial_entries_cnt) == maximum_vrf_cnt + + # check linux kernel + (exitcode, num) = dvs.runcmd(['sh', '-c', "ip link show | grep Vrf | wc -l"]) + assert num.strip() == str(maximum_vrf_cnt) + + # remove VRF from Config DB + for i in range(maximum_vrf_cnt): + tbl._del("Vrf_%d" % i) + + # wait for all VRFs deleted + time.sleep(120) + + # check app_db + intf_entries_cnt = self.how_many_entries_exist(self.pdb, "VRF_TABLE") + assert intf_entries_cnt == 0 + + # check asic_db + current_entries_cnt = self.how_many_entries_exist(self.adb, "ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER") + assert (current_entries_cnt - initial_entries_cnt) == 0 - vrf_remove(asic_db, appl_db, "vrf_a", state) + # check linux kernel + (exitcode, num) = dvs.runcmd(['sh', '-c', "ip link show | grep Vrf | wc -l"]) + assert num.strip() == '0' diff --git a/tests/test_warm_reboot.py b/tests/test_warm_reboot.py index 1b958a1d8451..fc8ca3debd21 100644 --- a/tests/test_warm_reboot.py +++ b/tests/test_warm_reboot.py @@ -2067,3 +2067,126 @@ def test_system_warmreboot_neighbor_syncup(self, dvs, testlog): intf_tbl._del("Ethernet{}|{}00::1/64".format(i*4, i*4)) intf_tbl._del("Ethernet{}".format(i*4, i*4)) intf_tbl._del("Ethernet{}".format(i*4, i*4)) + + def test_VrfMgrdWarmRestart(self, dvs, testlog): + + conf_db = swsscommon.DBConnector(swsscommon.CONFIG_DB, dvs.redis_sock, 0) + appl_db = swsscommon.DBConnector(swsscommon.APPL_DB, dvs.redis_sock, 0) + state_db = swsscommon.DBConnector(swsscommon.STATE_DB, dvs.redis_sock, 0) + + dvs.runcmd("config warm_restart enable swss") + + # bring up interface + dvs.runcmd("ifconfig Ethernet0 up") + dvs.runcmd("ifconfig Ethernet4 up") + + # create vrf + create_entry_tbl(conf_db, "VRF", "Vrf_1", [('empty', 'empty')]) + create_entry_tbl(conf_db, "VRF", "Vrf_2", [('empty', 'empty')]) + + intf_tbl = swsscommon.Table(conf_db, "INTERFACE") + fvs = swsscommon.FieldValuePairs([("vrf_name", "Vrf_1")]) + intf_tbl.set("Ethernet0", fvs) + intf_tbl.set("Ethernet4", fvs) + fvs = swsscommon.FieldValuePairs([("NULL", "NULL")]) + intf_tbl.set("Ethernet0|12.0.0.1/24", fvs) + intf_tbl.set("Ethernet4|13.0.0.1/24", fvs) + + time.sleep(1) + + dvs.servers[0].runcmd("ifconfig eth0 12.0.0.2/24") + dvs.servers[0].runcmd("ip route add default via 12.0.0.1") + + dvs.servers[1].runcmd("ifconfig eth0 13.0.0.2/24") + dvs.servers[1].runcmd("ip route add default via 13.0.0.1") + + time.sleep(1) + + # Ping should work between servers via vs port interfaces + ping_stats = dvs.servers[0].runcmd("ping -c 1 13.0.0.2") + assert ping_stats == 0 + time.sleep(1) + + tbl = swsscommon.Table(appl_db, "NEIGH_TABLE") + (status, fvs) = tbl.get("Ethernet0:12.0.0.2") + assert status == True + + (status, fvs) = tbl.get("Ethernet4:13.0.0.2") + assert status == True + + (exitcode, vrf_before) = dvs.runcmd(['sh', '-c', "ip link show | grep Vrf"]) + + dvs.runcmd(['sh', '-c', 'pkill -x vrfmgrd']) + + pubsub = dvs.SubscribeAsicDbObject("SAI_OBJECT_TYPE") + + dvs.runcmd(['sh', '-c', 'supervisorctl start vrfmgrd']) + time.sleep(2) + + # kernel vrf config should be kept the same + (exitcode, vrf_after) = dvs.runcmd(['sh', '-c', "ip link show | grep Vrf"]) + assert vrf_after == vrf_before + + # VIRTUAL_ROUTER/ROUTE_ENTRY/NEIGH_ENTRY should be kept the same + (nadd, ndel) = dvs.CountSubscribedObjects(pubsub, ignore=["SAI_OBJECT_TYPE_FDB_ENTRY"]) + assert nadd == 0 + assert ndel == 0 + + # new ip on server 1 + dvs.servers[1].runcmd("ifconfig eth0 13.0.0.3/24") + dvs.servers[1].runcmd("ip route add default via 13.0.0.1") + + # Ping should work between servers via vs port interfaces + ping_stats = dvs.servers[0].runcmd("ping -c 1 13.0.0.3") + assert ping_stats == 0 + + # new neighbor learn on vs + (status, fvs) = tbl.get("Ethernet4:13.0.0.3") + assert status == True + + # flush all neigh entries + dvs.runcmd("ip link set group default arp off") + dvs.runcmd("ip link set group default arp on") + + # remove interface Ethernet4 from vrf_1, add it to vrf_2 + intf_tbl._del("Ethernet4|13.0.0.1/24") + intf_tbl._del("Ethernet4") + time.sleep(1) + + intf_tbl = swsscommon.Table(conf_db, "INTERFACE") + fvs = swsscommon.FieldValuePairs([("vrf_name", "Vrf_2")]) + intf_tbl.set("Ethernet4", fvs) + fvs = swsscommon.FieldValuePairs([("NULL", "NULL")]) + intf_tbl.set("Ethernet4|13.0.0.1/24", fvs) + time.sleep(1) + + # Ping should not work + ping_stats = dvs.servers[0].runcmd("ping -c 1 13.0.0.3") + assert ping_stats != 0 + + # remove interface Ethernet0 from vrf_1, add it to vrf_2 + intf_tbl._del("Ethernet0|12.0.0.1/24") + intf_tbl._del("Ethernet0") + time.sleep(1) + fvs = swsscommon.FieldValuePairs([("vrf_name", "Vrf_2")]) + intf_tbl.set("Ethernet0", fvs) + fvs = swsscommon.FieldValuePairs([("NULL", "NULL")]) + intf_tbl.set("Ethernet0|12.0.0.1/24", fvs) + time.sleep(1) + + # Ping should work between servers via vs port interfaces + ping_stats = dvs.servers[0].runcmd("ping -c 1 13.0.0.3") + assert ping_stats == 0 + + (status, fvs) = tbl.get("Ethernet4:13.0.0.3") + assert status == True + + intf_tbl._del("Ethernet0|12.0.0.1/24") + intf_tbl._del("Ethernet4|13.0.0.1/24") + intf_tbl._del("Ethernet0") + intf_tbl._del("Ethernet4") + del_entry_tbl(conf_db, "VRF", "Vrf_1") + del_entry_tbl(conf_db, "VRF", "Vrf_2") + dvs.servers[0].runcmd("ifconfig eth0 0") + dvs.servers[1].runcmd("ifconfig eth0 0") + time.sleep(2)