diff --git a/.azure-pipelines/build-template.yml b/.azure-pipelines/build-template.yml index 598e2d9afeb..ed17ebc893d 100644 --- a/.azure-pipelines/build-template.yml +++ b/.azure-pipelines/build-template.yml @@ -98,7 +98,7 @@ jobs: clean: true submodules: true - script: | - sudo apt-get install -y libhiredis0.14 libhiredis-dev + sudo apt-get install -y libhiredis0.14 libhiredis-dev libgmock-dev sudo apt-get install -y libzmq5 libzmq3-dev sudo apt-get install -qq -y \ libhiredis-dev \ diff --git a/orchagent/Makefile.am b/orchagent/Makefile.am index bb7bec57ff9..ff024ca020e 100644 --- a/orchagent/Makefile.am +++ b/orchagent/Makefile.am @@ -101,7 +101,8 @@ orchagent_SOURCES = \ nvgreorch.cpp \ dash/dashorch.cpp \ dash/dashrouteorch.cpp \ - dash/dashvnetorch.cpp + dash/dashvnetorch.cpp \ + dash/dashaclorch.cpp orchagent_SOURCES += flex_counter/flex_counter_manager.cpp flex_counter/flex_counter_stat_manager.cpp flex_counter/flow_counter_handler.cpp flex_counter/flowcounterrouteorch.cpp orchagent_SOURCES += debug_counter/debug_counter.cpp debug_counter/drop_counter.cpp diff --git a/orchagent/dash/dashaclorch.cpp b/orchagent/dash/dashaclorch.cpp new file mode 100644 index 00000000000..b90b1ab50ec --- /dev/null +++ b/orchagent/dash/dashaclorch.cpp @@ -0,0 +1,844 @@ +#include +#include +#include +#include + +#include + +#include "dashaclorch.h" + +using namespace std; +using namespace swss; + + +extern sai_dash_acl_api_t* sai_dash_acl_api; +extern sai_dash_eni_api_t* sai_dash_eni_api; +extern sai_object_id_t gSwitchId; + +template +static bool extractVariables(const string &input, char delimiter, T &output, Args &... args) +{ + const auto tokens = swss::tokenize(input, delimiter); + try + { + swss::lexical_convert(tokens, output, args...); + return true; + } + catch(const exception& e) + { + return false; + } +} + +template +static bool getValue( + const DashAclOrch::TaskArgs &ta, + const string &field, + T &value) +{ + SWSS_LOG_ENTER(); + + auto value_opt = swss::fvsGetValue(ta, field, true); + if (!value_opt) + { + SWSS_LOG_DEBUG("Cannot find field : %s", field.c_str()); + return false; + } + + try + { + lexical_convert(*value_opt, value); + return true; + } + catch(const exception &err) + { + SWSS_LOG_WARN("Cannot convert field %s to type %s (%s)", field.c_str(), typeid(T).name(), err.what()); + return false; + } +} + +namespace swss { + +template<> +inline void lexical_convert(const string &buffer, DashAclStage &stage) +{ + SWSS_LOG_ENTER(); + + if (buffer == "1") + { + stage = DashAclStage::STAGE1; + } + else if (buffer == "2") + { + stage = DashAclStage::STAGE2; + } + else if (buffer == "3") + { + stage = DashAclStage::STAGE3; + } + else if (buffer == "4") + { + stage = DashAclStage::STAGE4; + } + else if (buffer == "5") + { + stage = DashAclStage::STAGE5; + } + else + { + SWSS_LOG_ERROR("Invalid stage : %s", buffer.c_str()); + throw invalid_argument("Invalid stage"); + } + +} + +template<> +inline void lexical_convert(const string &buffer, sai_ip_addr_family_t &ip_version) +{ + SWSS_LOG_ENTER(); + + if (buffer == "ipv4") + { + ip_version = SAI_IP_ADDR_FAMILY_IPV4; + } + else if (buffer == "ipv6") + { + ip_version = SAI_IP_ADDR_FAMILY_IPV6; + } + else + { + SWSS_LOG_ERROR("Invalid ip version : %s", buffer.c_str()); + throw invalid_argument("Invalid ip version"); + } +} + +template<> +inline void lexical_convert(const string &buffer, DashAclRuleEntry::Action &action) +{ + SWSS_LOG_ENTER(); + + if (buffer == "allow") + { + action = DashAclRuleEntry::Action::ALLOW; + } + else if (buffer == "deny") + { + action = DashAclRuleEntry::Action::DENY; + } + else + { + SWSS_LOG_ERROR("Invalid action : %s", buffer.c_str()); + throw invalid_argument("Invalid action"); + } +} + +template<> +inline void lexical_convert(const string &buffer, bool &terminating) +{ + SWSS_LOG_ENTER(); + + if (buffer == "true" || buffer == "True" || buffer == "1") + { + terminating = true; + } + else if (buffer == "false" || buffer == "False" || buffer == "0") + { + terminating = false; + } + else + { + SWSS_LOG_ERROR("Invalid terminating : %s", buffer.c_str()); + throw invalid_argument("Invalid terminating"); + } +} + +template<> +inline void lexical_convert(const string &buffer, vector &protocols) +{ + SWSS_LOG_ENTER(); + + auto tokens = tokenize(buffer, ','); + protocols.clear(); + protocols.reserve(tokens.size()); + for (auto &token : tokens) + { + uint32_t protocol; + lexical_convert(token, protocol); + protocols.push_back(static_cast(protocol)); + } +} + +template<> +inline void lexical_convert(const string &buffer, vector &prefixes) +{ + SWSS_LOG_ENTER(); + + auto tokens = tokenize(buffer, ','); + prefixes.clear(); + prefixes.reserve(tokens.size()); + for (auto &token : tokens) + { + sai_ip_prefix_t prefix; + IpPrefix ipPrefix(token); + swss::copy(prefix, ipPrefix); + prefixes.push_back(prefix); + } +} + +template<> +inline void lexical_convert(const string &buffer, vector &ports) +{ + SWSS_LOG_ENTER(); + + auto tokens = tokenize(buffer, ','); + ports.clear(); + ports.reserve(tokens.size()); + for (auto &token : tokens) + { + sai_u16_range_t port; + if (token.find('-') == string::npos) + { + // Only one port + lexical_convert(token, port.min); + port.max = port.min; + } + else if (!extractVariables(token, '-', port.min, port.max)) + { + // Range + SWSS_LOG_ERROR("Invalid port range : %s", token.c_str()); + throw invalid_argument("Invalid port range"); + } + ports.push_back(port); + } +} + +} + +static bool operator==(const sai_u16_range_t& a, const sai_u16_range_t& b) +{ + SWSS_LOG_ENTER(); + + return a.min == b.min && a.max == b.max; +} + +template +static bool updateValue( + const DashAclOrch::TaskArgs &ta, + const string &field, + boost::optional &opt) +{ + SWSS_LOG_ENTER(); + + T value; + + if (!getValue(ta, field, value)) + { + return false; + } + + if (opt && opt.value() == value) + { + return false; + } + + opt = value; + + return true; +} + +sai_attr_id_t getSaiStage(DashAclDirection d, sai_ip_addr_family_t f, DashAclStage s) +{ + const static map, sai_attr_id_t> StageMaps = + { + {{IN, SAI_IP_ADDR_FAMILY_IPV4, DashAclStage::STAGE1}, SAI_ENI_ATTR_INBOUND_V4_STAGE1_DASH_ACL_GROUP_ID}, + {{IN, SAI_IP_ADDR_FAMILY_IPV4, DashAclStage::STAGE2}, SAI_ENI_ATTR_INBOUND_V4_STAGE2_DASH_ACL_GROUP_ID}, + {{IN, SAI_IP_ADDR_FAMILY_IPV4, DashAclStage::STAGE3}, SAI_ENI_ATTR_INBOUND_V4_STAGE3_DASH_ACL_GROUP_ID}, + {{IN, SAI_IP_ADDR_FAMILY_IPV4, DashAclStage::STAGE4}, SAI_ENI_ATTR_INBOUND_V4_STAGE4_DASH_ACL_GROUP_ID}, + {{IN, SAI_IP_ADDR_FAMILY_IPV4, DashAclStage::STAGE5}, SAI_ENI_ATTR_INBOUND_V4_STAGE5_DASH_ACL_GROUP_ID}, + {{IN, SAI_IP_ADDR_FAMILY_IPV6, DashAclStage::STAGE1}, SAI_ENI_ATTR_INBOUND_V6_STAGE1_DASH_ACL_GROUP_ID}, + {{IN, SAI_IP_ADDR_FAMILY_IPV6, DashAclStage::STAGE2}, SAI_ENI_ATTR_INBOUND_V6_STAGE2_DASH_ACL_GROUP_ID}, + {{IN, SAI_IP_ADDR_FAMILY_IPV6, DashAclStage::STAGE3}, SAI_ENI_ATTR_INBOUND_V6_STAGE3_DASH_ACL_GROUP_ID}, + {{IN, SAI_IP_ADDR_FAMILY_IPV6, DashAclStage::STAGE4}, SAI_ENI_ATTR_INBOUND_V6_STAGE4_DASH_ACL_GROUP_ID}, + {{IN, SAI_IP_ADDR_FAMILY_IPV6, DashAclStage::STAGE5}, SAI_ENI_ATTR_INBOUND_V6_STAGE5_DASH_ACL_GROUP_ID}, + {{OUT, SAI_IP_ADDR_FAMILY_IPV4, DashAclStage::STAGE1}, SAI_ENI_ATTR_OUTBOUND_V4_STAGE1_DASH_ACL_GROUP_ID}, + {{OUT, SAI_IP_ADDR_FAMILY_IPV4, DashAclStage::STAGE2}, SAI_ENI_ATTR_OUTBOUND_V4_STAGE2_DASH_ACL_GROUP_ID}, + {{OUT, SAI_IP_ADDR_FAMILY_IPV4, DashAclStage::STAGE3}, SAI_ENI_ATTR_OUTBOUND_V4_STAGE3_DASH_ACL_GROUP_ID}, + {{OUT, SAI_IP_ADDR_FAMILY_IPV4, DashAclStage::STAGE4}, SAI_ENI_ATTR_OUTBOUND_V4_STAGE4_DASH_ACL_GROUP_ID}, + {{OUT, SAI_IP_ADDR_FAMILY_IPV4, DashAclStage::STAGE5}, SAI_ENI_ATTR_OUTBOUND_V4_STAGE5_DASH_ACL_GROUP_ID}, + {{OUT, SAI_IP_ADDR_FAMILY_IPV6, DashAclStage::STAGE1}, SAI_ENI_ATTR_OUTBOUND_V6_STAGE1_DASH_ACL_GROUP_ID}, + {{OUT, SAI_IP_ADDR_FAMILY_IPV6, DashAclStage::STAGE2}, SAI_ENI_ATTR_OUTBOUND_V6_STAGE2_DASH_ACL_GROUP_ID}, + {{OUT, SAI_IP_ADDR_FAMILY_IPV6, DashAclStage::STAGE3}, SAI_ENI_ATTR_OUTBOUND_V6_STAGE3_DASH_ACL_GROUP_ID}, + {{OUT, SAI_IP_ADDR_FAMILY_IPV6, DashAclStage::STAGE4}, SAI_ENI_ATTR_OUTBOUND_V6_STAGE4_DASH_ACL_GROUP_ID}, + {{OUT, SAI_IP_ADDR_FAMILY_IPV6, DashAclStage::STAGE5}, SAI_ENI_ATTR_OUTBOUND_V6_STAGE5_DASH_ACL_GROUP_ID}, + }; + + auto stage = StageMaps.find({d, f, s}); + if (stage == StageMaps.end()) + { + SWSS_LOG_ERROR("Invalid stage %d %d %d", d, f, s); + throw runtime_error("Invalid stage"); + } + + return stage->second; +} + +DashAclOrch::DashAclOrch(DBConnector *db, const vector &tables, DashOrch *dash_orch) : + Orch(db, tables), + m_dash_orch(dash_orch) +{ + SWSS_LOG_ENTER(); + + assert(m_dash_orch); +} + +void DashAclOrch::doTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + using TaskType = tuple; + using TaskFunc = task_process_status (DashAclOrch::*)( + const string &, + const TaskArgs &); + const static map TaskMap = { + {{APP_DASH_ACL_IN_TABLE_NAME, SET_COMMAND}, &DashAclOrch::taskUpdateDashAclIn}, + {{APP_DASH_ACL_IN_TABLE_NAME, DEL_COMMAND}, &DashAclOrch::taskRemoveDashAclIn}, + {{APP_DASH_ACL_OUT_TABLE_NAME, SET_COMMAND}, &DashAclOrch::taskUpdateDashAclOut}, + {{APP_DASH_ACL_OUT_TABLE_NAME, DEL_COMMAND}, &DashAclOrch::taskRemoveDashAclOut}, + {{APP_DASH_ACL_GROUP_TABLE_NAME, SET_COMMAND}, &DashAclOrch::taskUpdateDashAclGroup}, + {{APP_DASH_ACL_GROUP_TABLE_NAME, DEL_COMMAND}, &DashAclOrch::taskRemoveDashAclGroup}, + {{APP_DASH_ACL_RULE_TABLE_NAME, SET_COMMAND}, &DashAclOrch::taskUpdateDashAclRule}, + {{APP_DASH_ACL_RULE_TABLE_NAME, DEL_COMMAND}, &DashAclOrch::taskRemoveDashAclRule}, + }; + + const string &table_name = consumer.getTableName(); + auto itr = consumer.m_toSync.begin(); + while (itr != consumer.m_toSync.end()) + { + task_process_status task_status = task_failed; + auto &message = itr->second; + const string &op = kfvOp(message); + + auto task = TaskMap.find(make_tuple(table_name, op)); + if (task != TaskMap.end()) + { + task_status = (this->*task->second)( + kfvKey(message), + kfvFieldsValues(message)); + } + else + { + SWSS_LOG_ERROR( + "Unknown task : %s - %s", + table_name.c_str(), + op.c_str()); + } + + if (task_status == task_need_retry) + { + SWSS_LOG_DEBUG( + "Task %s - %s need retry", + table_name.c_str(), + op.c_str()); + ++itr; + } + else + { + if (task_status != task_success) + { + SWSS_LOG_WARN("Task %s - %s fail", + table_name.c_str(), + op.c_str()); + } + else + { + SWSS_LOG_DEBUG( + "Task %s - %s success", + table_name.c_str(), + op.c_str()); + } + + itr = consumer.m_toSync.erase(itr); + } + } +} + +task_process_status DashAclOrch::taskUpdateDashAclIn( + const string &key, + const TaskArgs &data) +{ + SWSS_LOG_ENTER(); + + return bindAclToEni(m_dash_acl_in_table, key, data); +} + +task_process_status DashAclOrch::taskRemoveDashAclIn( + const string &key, + const TaskArgs &data) +{ + SWSS_LOG_ENTER(); + + return unbindAclFromEni(m_dash_acl_in_table, key, data); +} + +task_process_status DashAclOrch::taskUpdateDashAclOut( + const string &key, + const TaskArgs &data) +{ + SWSS_LOG_ENTER(); + + return bindAclToEni(m_dash_acl_out_table, key, data); +} + +task_process_status DashAclOrch::taskRemoveDashAclOut( + const string &key, + const TaskArgs &data) +{ + SWSS_LOG_ENTER(); + + return unbindAclFromEni(m_dash_acl_out_table, key, data); +} + +task_process_status DashAclOrch::taskUpdateDashAclGroup( + const string &key, + const TaskArgs &data) +{ + SWSS_LOG_ENTER(); + + auto &acl_group = m_dash_acl_group_table[key]; + bool is_existing = acl_group.m_dash_acl_group_id != SAI_NULL_OBJECT_ID; + + vector attrs; + + if (updateValue(data, "ip_version", acl_group.m_ip_version) || (acl_group.m_ip_version && !is_existing)) + { + attrs.emplace_back(); + attrs.back().id = SAI_DASH_ACL_GROUP_ATTR_IP_ADDR_FAMILY; + attrs.back().value.s32 = *(acl_group.m_ip_version); + } + + // Guid wasn't mapping to any SAI attributes + updateValue(data, "guid", acl_group.m_guid); + + if (!is_existing && attrs.empty()) + { + // Assign default values + if (!acl_group.m_ip_version) + { + acl_group.m_ip_version = SAI_IP_ADDR_FAMILY_IPV4; + attrs.emplace_back(); + attrs.back().id = SAI_DASH_ACL_GROUP_ATTR_IP_ADDR_FAMILY; + attrs.back().value.s32 = *(acl_group.m_ip_version); + } + } + + if (!attrs.empty()) + { + if (!is_existing) + { + // Create a new ACL group + sai_status_t status = sai_dash_acl_api->create_dash_acl_group(&acl_group.m_dash_acl_group_id, gSwitchId, static_cast(attrs.size()), attrs.data()); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to create ACL group %s, rv: %s", key.c_str(), sai_serialize_status(status).c_str()); + return task_failed; + } + acl_group.m_rule_count = 0; + SWSS_LOG_NOTICE("Created ACL group %s", key.c_str()); + } + else + { + // Update the ACL group's attributes + SWSS_LOG_WARN("Cannot update attributes of ACL group %s", key.c_str()); + return task_failed; + } + } + + return task_success; +} + +task_process_status DashAclOrch::taskRemoveDashAclGroup( + const string &key, + const TaskArgs &data) +{ + SWSS_LOG_ENTER(); + + auto acl_group = getAclGroup(key); + + if (acl_group == nullptr) + { + SWSS_LOG_WARN("ACL group %s doesn't exist", key.c_str()); + return task_success; + } + + // The member rules of group should be removed first + if (acl_group->m_rule_count != 0) + { + SWSS_LOG_INFO("ACL group %s still has %d rules", key.c_str(), acl_group->m_rule_count); + return task_need_retry; + } + + // The refer count of group should be cleaned first + if (acl_group->m_ref_count != 0) + { + SWSS_LOG_INFO("ACL group %s still has %d references", key.c_str(), acl_group->m_ref_count); + return task_need_retry; + } + + // Remove the ACL group + sai_status_t status = sai_dash_acl_api->remove_dash_acl_group(acl_group->m_dash_acl_group_id); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to remove ACL group %s, rv: %s", key.c_str(), sai_serialize_status(status).c_str()); + return task_failed; + } + m_dash_acl_group_table.erase(key); + SWSS_LOG_NOTICE("Removed ACL group %s", key.c_str()); + + return task_success; +} + +task_process_status DashAclOrch::taskUpdateDashAclRule( + const string &key, + const TaskArgs &data) +{ + SWSS_LOG_ENTER(); + + string group_id, rule_id; + if (!extractVariables(key, ':', group_id, rule_id)) + { + SWSS_LOG_ERROR("Failed to parse key %s", key.c_str()); + return task_failed; + } + + auto acl_group = getAclGroup(group_id); + if (acl_group == nullptr) + { + SWSS_LOG_INFO("ACL group %s doesn't exist, waiting for group creating before creating rule %s", group_id.c_str(), rule_id.c_str()); + return task_need_retry; + } + + auto &acl_rule = m_dash_acl_rule_table[key]; + bool is_existing = acl_rule.m_dash_acl_rule_id != SAI_NULL_OBJECT_ID; + vector attrs; + + if (updateValue(data, "priority", acl_rule.m_priority) || (acl_rule.m_priority && !is_existing)) + { + attrs.emplace_back(); + attrs.back().id = SAI_DASH_ACL_RULE_ATTR_PRIORITY; + attrs.back().value.u32 = *(acl_rule.m_priority); + } + + bool update_action = false; + update_action |= updateValue(data, "action", acl_rule.m_action); + update_action |= updateValue(data, "terminating", acl_rule.m_terminating); + if (update_action || (acl_rule.m_action && acl_rule.m_terminating && !is_existing)) + { + attrs.emplace_back(); + attrs.back().id = SAI_DASH_ACL_RULE_ATTR_ACTION; + if (*(acl_rule.m_action) == DashAclRuleEntry::Action::ALLOW) + { + if (*(acl_rule.m_terminating)) + { + attrs.back().value.s32 = SAI_DASH_ACL_RULE_ACTION_PERMIT; + } + else + { + attrs.back().value.s32 = SAI_DASH_ACL_RULE_ACTION_PERMIT_AND_CONTINUE; + } + } + else + { + if (*(acl_rule.m_terminating)) + { + attrs.back().value.s32 = SAI_DASH_ACL_RULE_ACTION_DENY; + } + else + { + attrs.back().value.s32 = SAI_DASH_ACL_RULE_ACTION_DENY_AND_CONTINUE; + } + } + } + + if (updateValue(data, "protocol", acl_rule.m_protocols) || (acl_rule.m_protocols && !is_existing)) + { + attrs.emplace_back(); + attrs.back().id = SAI_DASH_ACL_RULE_ATTR_PROTOCOL; + attrs.back().value.u8list.count = static_cast(acl_rule.m_protocols.value().size()); + attrs.back().value.u8list.list = acl_rule.m_protocols.value().data(); + } + + if (updateValue(data, "src_addr", acl_rule.m_src_prefixes) || (acl_rule.m_src_prefixes && !is_existing)) + { + attrs.emplace_back(); + attrs.back().id = SAI_DASH_ACL_RULE_ATTR_SIP; + attrs.back().value.ipprefixlist.count = static_cast(acl_rule.m_src_prefixes.value().size()); + attrs.back().value.ipprefixlist.list = acl_rule.m_src_prefixes.value().data(); + } + + if (updateValue(data, "dst_addr", acl_rule.m_dst_prefixes) || (acl_rule.m_dst_prefixes && !is_existing)) + { + attrs.emplace_back(); + attrs.back().id = SAI_DASH_ACL_RULE_ATTR_DIP; + attrs.back().value.ipprefixlist.count = static_cast(acl_rule.m_dst_prefixes.value().size()); + attrs.back().value.ipprefixlist.list = acl_rule.m_dst_prefixes.value().data(); + } + + if (updateValue(data, "src_port", acl_rule.m_src_ports) || (acl_rule.m_src_ports && !is_existing)) + { + attrs.emplace_back(); + attrs.back().id = SAI_DASH_ACL_RULE_ATTR_SRC_PORT; + attrs.back().value.u16rangelist.count = static_cast(acl_rule.m_src_ports.value().size()); + attrs.back().value.u16rangelist.list = acl_rule.m_src_ports.value().data(); + } + + if (updateValue(data, "dst_port", acl_rule.m_dst_ports) || (acl_rule.m_dst_ports && !is_existing)) + { + attrs.emplace_back(); + attrs.back().id = SAI_DASH_ACL_RULE_ATTR_DST_PORT; + attrs.back().value.u16rangelist.count = static_cast(acl_rule.m_dst_ports.value().size()); + attrs.back().value.u16rangelist.list = acl_rule.m_dst_ports.value().data(); + } + + if (!is_existing) + { + // Mandatory on create attributes should be setted, otherwise assign the default value. + // If the attributes don't have default value, just skip and wait for the user to set the value at the next message + if (!acl_rule.m_protocols) + { + const static vector all_protocols = [](){ + vector protocols; + for (uint16_t i = 0; i <= 255; i++) + { + protocols.push_back(static_cast(i)); + } + return protocols; + }(); + acl_rule.m_protocols = all_protocols; + attrs.emplace_back(); + attrs.back().id = SAI_DASH_ACL_RULE_ATTR_PROTOCOL; + attrs.back().value.u8list.count = static_cast(acl_rule.m_protocols.value().size()); + attrs.back().value.u8list.list = acl_rule.m_protocols.value().data(); + } + + if (!acl_rule.m_priority || !acl_rule.m_dst_prefixes || !acl_rule.m_src_prefixes || !acl_rule.m_dst_ports || !acl_rule.m_src_ports) + { + SWSS_LOG_WARN("ACL rule %s doesn't have all mandatory attributes, waiting for user to set the value", key.c_str()); + return task_success; + } + } + + if (!attrs.empty()) + { + if (!is_existing) + { + // Create a new ACL rule + + attrs.emplace_back(); + attrs.back().id = SAI_DASH_ACL_RULE_ATTR_DASH_ACL_GROUP_ID; + attrs.back().value.oid = acl_group->m_dash_acl_group_id; + + sai_status_t status = sai_dash_acl_api->create_dash_acl_rule(&acl_rule.m_dash_acl_rule_id, gSwitchId, static_cast(attrs.size()), attrs.data()); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to create dash ACL rule %s, rv: %s", key.c_str(), sai_serialize_status(status).c_str()); + return task_failed; + } + acl_group->m_rule_count++; + SWSS_LOG_NOTICE("Created ACL rule %s", key.c_str()); + } + else + { + // Update the ACL rule's attributes + for (const auto &attr : attrs) + { + sai_status_t status = sai_dash_acl_api->set_dash_acl_rule_attribute(acl_rule.m_dash_acl_rule_id, &attr); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to update attribute %d to dash ACL rule %s, rv:%s", attr.id, key.c_str(), sai_serialize_status(status).c_str()); + return task_failed; + } + } + } + } + + return task_success; +} + +task_process_status DashAclOrch::taskRemoveDashAclRule( + const string &key, + const TaskArgs &data) +{ + SWSS_LOG_ENTER(); + + string group_id, rule_id; + if (!extractVariables(key, ':', group_id, rule_id)) + { + SWSS_LOG_ERROR("Failed to parse key %s", key.c_str()); + return task_failed; + } + + auto itr = m_dash_acl_rule_table.find(key); + + if (itr == m_dash_acl_rule_table.end()) + { + SWSS_LOG_WARN("ACL rule %s doesn't exist", key.c_str()); + return task_success; + } + + auto &acl_rule = itr->second; + + bool is_existing = acl_rule.m_dash_acl_rule_id != SAI_NULL_OBJECT_ID; + + if (!is_existing) + { + SWSS_LOG_WARN("ACL rule %s doesn't exist", key.c_str()); + return task_success; + } + + // Remove the ACL group + sai_status_t status = sai_dash_acl_api->remove_dash_acl_rule(acl_rule.m_dash_acl_rule_id); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to remove dash ACL rule %s, rv: %s", key.c_str(), sai_serialize_status(status).c_str()); + return task_failed; + } + m_dash_acl_rule_table.erase(itr); + m_dash_acl_group_table[group_id].m_rule_count--; + SWSS_LOG_NOTICE("Removed ACL rule %s", key.c_str()); + + return task_success; +} + +DashAclGroupEntry* DashAclOrch::getAclGroup(const string &group_id) +{ + SWSS_LOG_ENTER(); + + auto itr = m_dash_acl_group_table.find(group_id); + + if (itr != m_dash_acl_group_table.end() && itr->second.m_dash_acl_group_id != SAI_NULL_OBJECT_ID) + { + return &itr->second; + } + else + { + return nullptr; + } +} + +task_process_status DashAclOrch::bindAclToEni(DashAclTable &acl_table, const string &key, const TaskArgs &data) +{ + SWSS_LOG_ENTER(); + + assert(&acl_table == &m_dash_acl_in_table || &acl_table == &m_dash_acl_out_table); + DashAclDirection direction = ((&acl_table == &m_dash_acl_in_table) ? DashAclDirection::IN : DashAclDirection::OUT); + + string eni; + DashAclStage stage; + if (!extractVariables(key, ':', eni, stage)) + { + SWSS_LOG_ERROR("Invalid key : %s", key.c_str()); + return task_failed; + } + + auto eni_entry = m_dash_orch->getEni(eni); + if (eni_entry == nullptr) + { + SWSS_LOG_INFO("eni %s cannot be found", eni.c_str()); + // The ENI may not be created yet, so we will wait for the ENI to be created + return task_need_retry; + } + + auto &acl = acl_table[key]; + sai_attribute_t attr; + + if (updateValue(data, "acl_group_id", acl.m_acl_group_id)) + { + auto acl_group = getAclGroup(*(acl.m_acl_group_id)); + if (acl_group == nullptr) + { + SWSS_LOG_INFO("acl group %s cannot be found, wait for create", acl.m_acl_group_id->c_str()); + acl.m_acl_group_id.reset(); + return task_need_retry; + } + + attr.id = getSaiStage(direction, *(acl_group->m_ip_version), stage); + attr.value.oid = acl_group->m_dash_acl_group_id; + } + else + { + if (!acl.m_acl_group_id) + { + SWSS_LOG_WARN("acl_group_id is not specified in %s", key.c_str()); + return task_failed; + } + return task_success; + } + + sai_status_t status = sai_dash_eni_api->set_eni_attribute(eni_entry->eni_id, &attr); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to bind ACL %s to eni %s attribute, status : %s", key.c_str(), acl.m_acl_group_id->c_str(), sai_serialize_status(status).c_str()); + return task_failed; + } + getAclGroup(*(acl.m_acl_group_id))->m_ref_count++; + + SWSS_LOG_NOTICE("Bind ACL group %s to %s", acl.m_acl_group_id->c_str(), key.c_str()); + + return task_success; +} + +task_process_status DashAclOrch::unbindAclFromEni(DashAclTable &acl_table, const string &key, const TaskArgs &data) +{ + SWSS_LOG_ENTER(); + + assert(&acl_table == &m_dash_acl_in_table || &acl_table == &m_dash_acl_out_table); + DashAclDirection direction = ((&acl_table == &m_dash_acl_in_table) ? DashAclDirection::IN : DashAclDirection::OUT); + + string eni; + DashAclStage stage; + if (!extractVariables(key, ':', eni, stage)) + { + SWSS_LOG_ERROR("Invalid key : %s", key.c_str()); + return task_failed; + } + + auto eni_entry = m_dash_orch->getEni(eni); + if (eni_entry == nullptr) + { + SWSS_LOG_WARN("eni %s cannot be found", eni.c_str()); + return task_failed; + } + + auto itr = acl_table.find(key); + if (itr == acl_table.end()) + { + SWSS_LOG_WARN("ACL %s doesn't exist", key.c_str()); + return task_success; + } + auto acl = itr->second; + acl_table.erase(itr); + + auto acl_group = getAclGroup(*(acl.m_acl_group_id)); + if (acl_group == nullptr) + { + SWSS_LOG_WARN("Invalid acl group id : %s", acl.m_acl_group_id->c_str()); + return task_failed; + } + + sai_attribute_t attr; + attr.id = getSaiStage(direction, *(acl_group->m_ip_version), stage); + attr.value.oid = SAI_NULL_OBJECT_ID; + + sai_status_t status = sai_dash_eni_api->set_eni_attribute(eni_entry->eni_id, &attr); + if (status != SAI_STATUS_SUCCESS) + { + SWSS_LOG_ERROR("Failed to unbind ACL %s to eni %s attribute, status : %s", key.c_str(), acl.m_acl_group_id->c_str(), sai_serialize_status(status).c_str()); + return task_failed; + } + + acl_group->m_ref_count--; + + SWSS_LOG_NOTICE("Unbind ACL group %s from %s", acl.m_acl_group_id->c_str(), key.c_str()); + + return task_success; +} diff --git a/orchagent/dash/dashaclorch.h b/orchagent/dash/dashaclorch.h new file mode 100644 index 00000000000..27cbe21e7e7 --- /dev/null +++ b/orchagent/dash/dashaclorch.h @@ -0,0 +1,123 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "dashorch.h" + +typedef enum _DashAclStage +{ + STAGE1, + STAGE2, + STAGE3, + STAGE4, + STAGE5, +} DashAclStage; + +typedef enum _DashAclDirection +{ + IN, + OUT, +} DashAclDirection; + +struct DashAclEntry { + boost::optional m_acl_group_id; +}; + +struct DashAclGroupEntry { + sai_object_id_t m_dash_acl_group_id; + size_t m_ref_count; + size_t m_rule_count; + boost::optional m_guid; + boost::optional m_ip_version; +}; + +struct DashAclRuleEntry { + sai_object_id_t m_dash_acl_rule_id; + boost::optional m_priority; + typedef enum + { + ALLOW, + DENY, + } Action; + boost::optional m_action; + boost::optional m_terminating; + boost::optional > m_protocols; + boost::optional > m_src_prefixes; + boost::optional > m_dst_prefixes; + boost::optional > m_src_ports; + boost::optional > m_dst_ports; +}; + +using DashAclTable = std::unordered_map; +using DashAclGroupTable = std::unordered_map; +using DashAclRuleTable = std::unordered_map; + +class DashAclOrch : public Orch +{ +public: + using TaskArgs = std::vector; + + DashAclOrch(swss::DBConnector *db, const std::vector &tables, DashOrch *dash_orch); + +private: + void doTask(Consumer &consumer); + + task_process_status taskUpdateDashAclIn( + const std::string &key, + const TaskArgs &data); + task_process_status taskRemoveDashAclIn( + const std::string &key, + const TaskArgs &data); + + task_process_status taskUpdateDashAclOut( + const std::string &key, + const TaskArgs &data); + task_process_status taskRemoveDashAclOut( + const std::string &key, + const TaskArgs &data); + + task_process_status taskUpdateDashAclGroup( + const std::string &key, + const TaskArgs &data); + task_process_status taskRemoveDashAclGroup( + const std::string &key, + const TaskArgs &data); + + task_process_status taskUpdateDashAclRule( + const std::string &key, + const TaskArgs &data); + + task_process_status taskRemoveDashAclRule( + const std::string &key, + const TaskArgs &data); + + DashAclGroupEntry* getAclGroup(const std::string &group_id); + + task_process_status bindAclToEni( + DashAclTable &acl_table, + const std::string &key, + const TaskArgs &data); + task_process_status unbindAclFromEni( + DashAclTable &acl_table, + const std::string &key, + const TaskArgs &data); + + DashAclTable m_dash_acl_in_table; + DashAclTable m_dash_acl_out_table; + DashAclGroupTable m_dash_acl_group_table; + DashAclRuleTable m_dash_acl_rule_table; + + DashOrch *m_dash_orch; +}; diff --git a/orchagent/orchdaemon.cpp b/orchagent/orchdaemon.cpp index dd103d26fe1..8a772de77e1 100644 --- a/orchagent/orchdaemon.cpp +++ b/orchagent/orchdaemon.cpp @@ -247,6 +247,15 @@ bool OrchDaemon::init() DashRouteOrch *dash_route_orch = new DashRouteOrch(m_applDb, dash_route_tables, dash_orch); gDirectory.set(dash_route_orch); + vector dash_acl_tables = { + APP_DASH_ACL_IN_TABLE_NAME, + APP_DASH_ACL_OUT_TABLE_NAME, + APP_DASH_ACL_GROUP_TABLE_NAME, + APP_DASH_ACL_RULE_TABLE_NAME + }; + DashAclOrch *dash_acl_orch = new DashAclOrch(m_applDb, dash_acl_tables, dash_orch); + gDirectory.set(dash_acl_orch); + vector qos_tables = { CFG_TC_TO_QUEUE_MAP_TABLE_NAME, CFG_SCHEDULER_TABLE_NAME, @@ -479,6 +488,7 @@ bool OrchDaemon::init() m_orchList.push_back(mux_st_orch); m_orchList.push_back(nvgre_tunnel_orch); m_orchList.push_back(nvgre_tunnel_map_orch); + m_orchList.push_back(dash_acl_orch); m_orchList.push_back(dash_vnet_orch); m_orchList.push_back(dash_route_orch); m_orchList.push_back(dash_orch); diff --git a/orchagent/orchdaemon.h b/orchagent/orchdaemon.h index 21e204f10ea..e5294cef32f 100644 --- a/orchagent/orchdaemon.h +++ b/orchagent/orchdaemon.h @@ -45,6 +45,7 @@ #include "bfdorch.h" #include "srv6orch.h" #include "nvgreorch.h" +#include "dash/dashaclorch.h" #include "dash/dashorch.h" #include "dash/dashrouteorch.h" #include "dash/dashvnetorch.h" diff --git a/orchagent/saihelper.cpp b/orchagent/saihelper.cpp index 0d8c66ba24f..6184c7fb36c 100644 --- a/orchagent/saihelper.cpp +++ b/orchagent/saihelper.cpp @@ -73,6 +73,7 @@ sai_counter_api_t* sai_counter_api; sai_bfd_api_t* sai_bfd_api; sai_my_mac_api_t* sai_my_mac_api; sai_generic_programmable_api_t* sai_generic_programmable_api; +sai_dash_acl_api_t* sai_dash_acl_api; sai_dash_vnet_api_t sai_dash_vnet_api; sai_dash_outbound_ca_to_pa_api_t* sai_dash_outbound_ca_to_pa_api; sai_dash_pa_validation_api_t * sai_dash_pa_validation_api; @@ -211,6 +212,7 @@ void initSaiApi() sai_api_query(SAI_API_BFD, (void **)&sai_bfd_api); sai_api_query(SAI_API_MY_MAC, (void **)&sai_my_mac_api); sai_api_query(SAI_API_GENERIC_PROGRAMMABLE, (void **)&sai_generic_programmable_api); + sai_api_query((sai_api_t)SAI_API_DASH_ACL, (void**)&sai_dash_acl_api); sai_api_query((sai_api_t)SAI_API_DASH_VNET, (void**)&sai_dash_vnet_api); sai_api_query((sai_api_t)SAI_API_DASH_OUTBOUND_CA_TO_PA, (void**)&sai_dash_outbound_ca_to_pa_api); sai_api_query((sai_api_t)SAI_API_DASH_PA_VALIDATION, (void**)&sai_dash_pa_validation_api); diff --git a/tests/mock_tests/Makefile.am b/tests/mock_tests/Makefile.am index 19b841b4f30..2d9e642fee6 100644 --- a/tests/mock_tests/Makefile.am +++ b/tests/mock_tests/Makefile.am @@ -104,6 +104,7 @@ tests_SOURCES = aclorch_ut.cpp \ $(top_srcdir)/orchagent/srv6orch.cpp \ $(top_srcdir)/orchagent/nvgreorch.cpp \ $(top_srcdir)/cfgmgr/portmgr.cpp \ + $(top_srcdir)/orchagent/dash/dashaclorch.cpp \ $(top_srcdir)/orchagent/dash/dashorch.cpp \ $(top_srcdir)/orchagent/dash/dashrouteorch.cpp \ $(top_srcdir)/orchagent/dash/dashvnetorch.cpp \ diff --git a/tests/test_dash_acl.py b/tests/test_dash_acl.py new file mode 100644 index 00000000000..a94082f5fff --- /dev/null +++ b/tests/test_dash_acl.py @@ -0,0 +1,253 @@ +from swsscommon import swsscommon +import typing +import time + +DVS_ENV = ["HWSKU=Nvidia-MBF2H536C"] + +def to_string(value): + if isinstance(value, bool): + return "true" if value else "false" + return str(value) + + +class ProduceStateTable(object): + def __init__(self, database, table_name: str): + self.table = swsscommon.ProducerStateTable( + database.db_connection, + table_name) + + def __setitem__(self, key: str, pairs: typing.Union[dict, list, tuple]): + pairs_str = [] + if isinstance(pairs, dict): + pairs = pairs.items() + for k, v in pairs: + pairs_str.append((to_string(k), to_string(v))) + self.table.set(key, pairs_str) + + def __delitem__(self, key: str): + self.table.delete(str(key)) + + +class Table(object): + def __init__(self, database, table_name: str): + self.table_name = table_name + self.table = swsscommon.Table(database.db_connection, self.table_name) + + def __getitem__(self, key: str): + exists, result = self.table.get(str(key)) + if not exists: + return None + else: + return dict(result) + + def get_keys(self): + return self.table.getKeys() + + +class DashAcl(object): + def __init__(self, dvs): + self.dvs = dvs + self.app_dash_acl_in_table = ProduceStateTable( + self.dvs.get_app_db(), swsscommon.APP_DASH_ACL_IN_TABLE_NAME) + self.app_dash_acl_out_table = ProduceStateTable( + self.dvs.get_app_db(), swsscommon.APP_DASH_ACL_OUT_TABLE_NAME) + self.app_dash_acl_group_table = ProduceStateTable( + self.dvs.get_app_db(), swsscommon.APP_DASH_ACL_GROUP_TABLE_NAME) + self.app_dash_acl_rule_table = ProduceStateTable( + self.dvs.get_app_db(), swsscommon.APP_DASH_ACL_RULE_TABLE_NAME) + self.app_dash_eni_table = ProduceStateTable( + self.dvs.get_app_db(), swsscommon.APP_DASH_ENI_TABLE_NAME) + self.app_dash_vnet_table = ProduceStateTable( + self.dvs.get_app_db(), swsscommon.APP_DASH_VNET_TABLE_NAME) + self.asic_dash_acl_rule_table = Table( + self.dvs.get_asic_db(), "ASIC_STATE:SAI_OBJECT_TYPE_DASH_ACL_RULE") + self.asic_dash_acl_group_table = Table( + self.dvs.get_asic_db(), "ASIC_STATE:SAI_OBJECT_TYPE_DASH_ACL_GROUP") + self.asic_eni_table = Table( + self.dvs.get_asic_db(), "ASIC_STATE:SAI_OBJECT_TYPE_ENI") + + def create_acl_rule(self, group_id, rule_id, attr_maps: dict): + self.app_dash_acl_rule_table[str( + group_id) + ":" + str(rule_id)] = attr_maps + + def remove_acl_rule(self, group_id, rule_id): + del self.app_dash_acl_rule_table[str(group_id) + ":" + str(rule_id)] + + def create_acl_group(self, group_id, attr_maps: dict): + self.app_dash_acl_group_table[str(group_id)] = attr_maps + + def remove_acl_group(self, group_id): + del self.app_dash_acl_group_table[str(group_id)] + + def create_eni(self, eni, attr_maps: dict): + self.app_dash_eni_table[str(eni)] = attr_maps + + def remove_eni(self, eni): + del self.app_dash_eni_table[str(eni)] + + def create_vnet(self, vnet, attr_maps: dict): + self.app_dash_vnet_table[str(vnet)] = attr_maps + + def remove_vnet(self, vnet): + del self.app_dash_vnet_table[str(vnet)] + + def bind_acl_in(self, eni, stage, group_id): + self.app_dash_acl_in_table[str( + eni) + ":" + str(stage)] = {"acl_group_id": str(group_id)} + + def unbind_acl_in(self, eni, stage): + del self.app_dash_acl_in_table[str(eni) + ":" + str(stage)] + + def bind_acl_out(self, eni, stage, group_id): + self.app_dash_acl_out_table[str( + eni) + ":" + str(stage)] = {"acl_group_id": str(group_id)} + + def unbind_acl_out(self, eni, stage): + del self.app_dash_acl_out_table[str(eni) + ":" + str(stage)] + + +class TestAcl(object): + def create_ctx(self, dvs): + self.vnet_name = "vnet1" + self.eni_name = "eth0" + self.vni = "1" + self.mac_address = "00:00:00:00:00:00" + ctx = DashAcl(dvs) + ctx.create_vnet(self.vnet_name, {"vni": self.vni}) + ctx.create_eni(self.eni_name, {"vnet": self.vnet_name, + "mac_address": self.mac_address}) + return ctx + + def destroy_ctx(self, ctx): + time.sleep(1) + # Because the Acl group, ENI and VNET has been referred by each others, + # And their implementation can only support synchronous deletion, So we need to wait for a while. + ctx.remove_eni(self.eni_name) + time.sleep(1) + ctx.remove_vnet(self.vnet_name) + time.sleep(1) + + def test_acl_flow(self, dvs): + ctx = self.create_ctx(dvs) + acl_group1 = "1" + acl_rule1 = "1" + acl_rule2 = "2" + acl_rule3 = "3" + stage1 = "1" + + ctx.create_acl_group(acl_group1, {"ip_version": "ipv4"}) + ctx.create_acl_rule(acl_group1, acl_rule1, {"priority": "1", "action": "allow", "terminating": "false", + "src_addr": "192.168.0.1/32,192.168.1.2/30", "dst_addr": "192.168.0.1/32,192.168.1.2/30", "src_port": "0-1", "dst_port": "0-1"}) + ctx.bind_acl_in(self.eni_name, stage1, acl_group1) + time.sleep(3) + + rule_ids = ctx.asic_dash_acl_rule_table.get_keys() + assert rule_ids + rule1_id = rule_ids[0] + group_ids = ctx.asic_dash_acl_group_table.get_keys() + assert group_ids + group1_id = group_ids[0] + rule1_attr = ctx.asic_dash_acl_rule_table[rule1_id] + assert rule1_attr["SAI_DASH_ACL_RULE_ATTR_PRIORITY"] == "1" + assert rule1_attr["SAI_DASH_ACL_RULE_ATTR_ACTION"] == "SAI_DASH_ACL_RULE_ACTION_PERMIT_AND_CONTINUE" + assert rule1_attr["SAI_DASH_ACL_RULE_ATTR_DASH_ACL_GROUP_ID"] == group1_id + assert rule1_attr["SAI_DASH_ACL_RULE_ATTR_DIP"] == "2:192.168.0.1/32,192.168.1.2/30" + assert rule1_attr["SAI_DASH_ACL_RULE_ATTR_SIP"] == "2:192.168.0.1/32,192.168.1.2/30" + assert rule1_attr["SAI_DASH_ACL_RULE_ATTR_DST_PORT"] == "1:0,1" + assert rule1_attr["SAI_DASH_ACL_RULE_ATTR_SRC_PORT"] == "1:0,1" + assert rule1_attr["SAI_DASH_ACL_RULE_ATTR_PROTOCOL"].split(":")[0] == "256" + group1_attr = ctx.asic_dash_acl_group_table[group1_id] + assert group1_attr["SAI_DASH_ACL_GROUP_ATTR_IP_ADDR_FAMILY"] == "SAI_IP_ADDR_FAMILY_IPV4" + + # Create multiple rules + ctx.create_acl_rule(acl_group1, acl_rule2, {"priority": "2", "action": "allow", "terminating": "false", + "src_addr": "192.168.0.1/32,192.168.1.2/30", "dst_addr": "192.168.0.1/32,192.168.1.2/30", "src_port": "0-1", "dst_port": "0-1"}) + ctx.create_acl_rule(acl_group1, acl_rule3, {"priority": "2", "action": "allow", "terminating": "false", + "src_addr": "192.168.0.1/32,192.168.1.2/30", "dst_addr": "192.168.0.1/32,192.168.1.2/30", "src_port": "0-1", "dst_port": "0-1"}) + time.sleep(3) + rule_ids = ctx.asic_dash_acl_rule_table.get_keys() + assert len(rule_ids) == 3 + ctx.unbind_acl_in(self.eni_name, stage1) + ctx.remove_acl_rule(acl_group1, acl_rule1) + ctx.remove_acl_rule(acl_group1, acl_rule2) + ctx.remove_acl_rule(acl_group1, acl_rule3) + ctx.remove_acl_group(acl_group1) + time.sleep(3) + assert len(ctx.asic_dash_acl_rule_table.get_keys()) == 0 + assert len(ctx.asic_dash_acl_group_table.get_keys()) == 0 + self.destroy_ctx(ctx) + + def test_acl_group(self, dvs): + ctx = self.create_ctx(dvs) + acl_group1 = "1" + acl_rule1 = "1" + + ctx.create_acl_group(acl_group1, {"ip_version": "ipv6"}) + ctx.create_acl_rule(acl_group1, acl_rule1, {"priority": "1", "action": "allow", "terminating": "false", + "src_addr": "192.168.0.1/32,192.168.1.2/30", "dst_addr": "192.168.0.1/32,192.168.1.2/30", "src_port": "0-1", "dst_port": "0-1"}) + time.sleep(3) + + # Remove group before removing its rule + ctx.remove_acl_group(acl_group1) + time.sleep(3) + assert len(ctx.asic_dash_acl_group_table.get_keys()) == 1 + ctx.remove_acl_rule(acl_group1, acl_rule1) + time.sleep(3) + assert len(ctx.asic_dash_acl_group_table.get_keys()) == 0 + + self.destroy_ctx(ctx) + + def test_acl_rule(self, dvs): + ctx = self.create_ctx(dvs) + acl_group1 = "1" + acl_rule1 = "1" + acl_rule2 = "2" + + # Create acl rule before acl group + ctx.create_acl_rule(acl_group1, acl_rule1, {"priority": "1", "action": "allow", "terminating": "false", + "src_addr": "192.168.0.1/32,192.168.1.2/30", "dst_addr": "192.168.0.1/32,192.168.1.2/30", "src_port": "0-1", "dst_port": "0-1"}) + time.sleep(3) + rule_ids = ctx.asic_dash_acl_rule_table.get_keys() + assert len(rule_ids) == 0 + ctx.create_acl_group(acl_group1, {"ip_version": "ipv4"}) + time.sleep(3) + rule_ids = ctx.asic_dash_acl_rule_table.get_keys() + assert len(rule_ids) == 1 + + # Create acl without a invalid acl group + ctx.create_acl_rule("0", "0", {"priority": "1", "action": "allow", "terminating": "false", + "src_addr": "192.168.0.1/32,192.168.1.2/30", "dst_addr": "192.168.0.1/32,192.168.1.2/30", "src_port": "0-1", "dst_port": "0-1"}) + time.sleep(3) + rule_ids = ctx.asic_dash_acl_rule_table.get_keys() + assert len(rule_ids) == 1 + + # Create acl with invalid attribute + ctx.create_acl_rule(acl_group1, acl_rule2, {"priority": "abc"}) + time.sleep(3) + rule_ids = ctx.asic_dash_acl_rule_table.get_keys() + assert len(rule_ids) == 1 + + # Create acl with multiple step + ctx.create_acl_rule(acl_group1, acl_rule2, {"priority": "1", "action": "allow", "terminating": "false"}) + time.sleep(3) + rule_ids = ctx.asic_dash_acl_rule_table.get_keys() + assert len(rule_ids) == 1 + ctx.create_acl_rule(acl_group1, acl_rule2, {"src_addr": "", "dst_addr": "192.168.0.1/32,192.168.1.2/30", "src_port": "", "dst_port": "0-1"}) + time.sleep(3) + rule_ids = ctx.asic_dash_acl_rule_table.get_keys() + assert len(rule_ids) == 2 + + ctx.remove_acl_rule(acl_group1, acl_rule1) + ctx.remove_acl_rule(acl_group1, acl_rule2) + ctx.remove_acl_group(acl_group1) + time.sleep(3) + assert len(ctx.asic_dash_acl_rule_table.get_keys()) == 0 + assert len(ctx.asic_dash_acl_group_table.get_keys()) == 0 + self.destroy_ctx(ctx) + + +# Add Dummy always-pass test at end as workaroud +# for issue when Flaky fail on final test it invokes module tear-down +# before retrying +def test_nonflaky_dummy(): + pass