From 812ed39f9da336310e815c361ab5a9f118657d94 Mon Sep 17 00:00:00 2001 From: Neale Ranns Date: Mon, 16 Oct 2017 04:20:13 -0700 Subject: VPP Object Model (VOM) The VOM is a C++ library for use by clients/agents of VPP for programming state. It uses the binary APIs to do so. Various other common client side functions are also provided. Please see om.hpp for a more detailed description. Change-Id: Ib756bfe99817093815a9e26ccf464aa5583fc523 Signed-off-by: Neale Ranns Co-authored-by: Mohsin Kazmi --- test/ext/Makefile | 59 +- test/ext/vom_test.cpp | 1447 +++++++++++++++++++++++++++++++++++++++++++++++++ test/framework.py | 30 + test/test_vapi.py | 33 +- test/test_vom.py | 48 ++ 5 files changed, 1565 insertions(+), 52 deletions(-) create mode 100644 test/ext/vom_test.cpp create mode 100644 test/test_vom.py (limited to 'test') diff --git a/test/ext/Makefile b/test/ext/Makefile index 1bd035daa13..58015539d92 100644 --- a/test/ext/Makefile +++ b/test/ext/Makefile @@ -1,38 +1,57 @@ -BINDIR = $(BR)/vapi_test/ -CBIN = $(addprefix $(BINDIR), vapi_c_test) -CPPBIN = $(addprefix $(BINDIR), vapi_cpp_test) +VAPI_BINDIR = $(BR)/vapi_test/ +VAPI_CBIN = $(addprefix $(VAPI_BINDIR), vapi_c_test) +VAPI_CPPBIN = $(addprefix $(VAPI_BINDIR), vapi_cpp_test) +VOM_BINDIR = $(BR)/vom_test/ +VOM_BIN = $(addprefix $(VOM_BINDIR), vom_test) ifeq ($(filter centos,$(OS_ID)),$(OS_ID)) -CPPBIN= +VAPI_CPPBIN= endif -LIBS = -L$(VPP_TEST_BUILD_DIR)/vpp/.libs/ -L$(VPP_TEST_BUILD_DIR)/vpp/vpp-api/vapi/.libs/ -lvppinfra -lvlibmemoryclient -lsvm -lpthread -lcheck -lrt -lm -lvapiclient +VAPI_LIBS = -L$(VPP_TEST_BUILD_DIR)/vpp/.libs/ -L$(VPP_TEST_BUILD_DIR)/vpp/vpp-api/vapi/.libs/ -lvppinfra -lvlibmemoryclient -lsvm -lpthread -lcheck -lrt -lm -lvapiclient ifneq ($(filter opensuse,$(OS_ID)),$(OS_ID)) -LIBS += -lsubunit +VAPI_LIBS += -lsubunit endif -CFLAGS = -std=gnu99 -g -Wall -pthread -I$(WS_ROOT)/src -I$(VPP_TEST_INSTALL_PATH)/vpp/include -I$(BINDIR) -CPPFLAGS = -std=c++11 -g -Wall -pthread -I$(WS_ROOT)/src -I$(VPP_TEST_INSTALL_PATH)/vpp/include -I$(BINDIR) +CFLAGS = -std=gnu99 -g -Wall -pthread -I$(WS_ROOT)/src -I$(VPP_TEST_INSTALL_PATH)/vpp/include -I$(VAPI_BINDIR) +CPPFLAGS = -std=c++11 -g -Wall -pthread -I$(WS_ROOT)/src -I$(VPP_TEST_INSTALL_PATH)/vpp/include -I$(VAPI_BINDIR) -all: $(CBIN) $(CPPBIN) +all: $(VAPI_CBIN) $(VAPI_CPPBIN) $(VOM_BINDIR) $(VOM_BIN) -$(BINDIR): - mkdir -p $(BINDIR) +$(VAPI_BINDIR): + mkdir -p $(VAPI_BINDIR) CSRC = vapi_c_test.c -$(BINDIR)/fake.api.vapi.h: fake.api.json $(WS_ROOT)/src/vpp-api/vapi/vapi_c_gen.py | $(BINDIR) - $(WS_ROOT)/src/vpp-api/vapi/vapi_c_gen.py --prefix $(BINDIR) $< +$(VAPI_BINDIR)/fake.api.vapi.h: fake.api.json $(WS_ROOT)/src/vpp-api/vapi/vapi_c_gen.py | $(VAPI_BINDIR) + $(WS_ROOT)/src/vpp-api/vapi/vapi_c_gen.py --prefix $(VAPI_BINDIR) $< -$(BINDIR)/fake.api.vapi.hpp: fake.api.json $(WS_ROOT)/src/vpp-api/vapi/vapi_cpp_gen.py | $(BINDIR) - $(WS_ROOT)/src/vpp-api/vapi/vapi_cpp_gen.py --prefix $(BINDIR) $< +$(VAPI_BINDIR)/fake.api.vapi.hpp: fake.api.json $(WS_ROOT)/src/vpp-api/vapi/vapi_cpp_gen.py | $(VAPI_BINDIR) + $(WS_ROOT)/src/vpp-api/vapi/vapi_cpp_gen.py --prefix $(VAPI_BINDIR) $< -$(CBIN): $(CSRC) $(VPP_TEST_BUILD_DIR)/vpp/vpp-api/vapi/.libs/libvapiclient.so $(VPP_TEST_BUILD_DIR)/vpp/.libs/libvppinfra.so $(VPP_TEST_BUILD_DIR)/vpp/.libs/libvlibmemoryclient.so $(VPP_TEST_BUILD_DIR)/vpp/.libs/libsvm.so $(BINDIR)/fake.api.vapi.h - $(CC) -o $@ $(CFLAGS) $(CSRC) $(LIBS) +$(VAPI_CBIN): $(CSRC) $(VPP_TEST_BUILD_DIR)/vpp/vpp-api/vapi/.libs/libvapiclient.so $(VPP_TEST_BUILD_DIR)/vpp/.libs/libvppinfra.so $(VPP_TEST_BUILD_DIR)/vpp/.libs/libvlibmemoryclient.so $(VPP_TEST_BUILD_DIR)/vpp/.libs/libsvm.so $(VAPI_BINDIR)/fake.api.vapi.h + $(CC) -o $@ $(CFLAGS) $(CSRC) $(VAPI_LIBS) CPPSRC = vapi_cpp_test.cpp -$(CPPBIN): $(CPPSRC) $(VPP_TEST_BUILD_DIR)/vpp/vpp-api/vapi/.libs/libvapiclient.so $(VPP_TEST_BUILD_DIR)/vpp/.libs/libvppinfra.so $(VPP_TEST_BUILD_DIR)/vpp/.libs/libvlibmemoryclient.so $(VPP_TEST_BUILD_DIR)/vpp/.libs/libsvm.so $(BINDIR)/fake.api.vapi.hpp - $(CXX) -o $@ $(CPPFLAGS) $(CPPSRC) $(LIBS) +$(VAPI_CPPBIN): $(CPPSRC) $(VPP_TEST_BUILD_DIR)/vpp/vpp-api/vapi/.libs/libvapiclient.so $(VPP_TEST_BUILD_DIR)/vpp/.libs/libvppinfra.so $(VPP_TEST_BUILD_DIR)/vpp/.libs/libvlibmemoryclient.so $(VPP_TEST_BUILD_DIR)/vpp/.libs/libsvm.so $(VAPI_BINDIR)/fake.api.vapi.hpp + $(CXX) -o $@ $(CPPFLAGS) $(CPPSRC) $(VAPI_LIBS) + +VOM_CPPSRC = vom_test.cpp + +$(VOM_BINDIR): + mkdir -p $(VOM_BINDIR) + +LIB_VOM = $(VPP_TEST_BUILD_DIR)/vpp/vpp-api/vom/.libs/libvom.so +VOM_LIBS = $(LIB_VOM) \ + -lboost_log \ + -lboost_thread \ + -lboost_system \ + -lboost_filesystem \ + -lboost_unit_test_framework \ + $(VAPI_LIBS) + +$(VOM_BIN): $(VOM_CPPSRC) $(VOM_BINDIR) $(LIB_VOM) $(VPP_TEST_BUILD_DIR)/vpp/vpp-api/vapi/.libs/libvapiclient.so + $(CXX) -o $@ $(CPPFLAGS) -DBOOST_LOG_DYN_LINK -O0 -g $(VOM_CPPSRC) $(VOM_LIBS) clean: - rm -rf $(BINDIR) + rm -rf $(VAPI_BINDIR) $(VOM_BINDIR) diff --git a/test/ext/vom_test.cpp b/test/ext/vom_test.cpp new file mode 100644 index 00000000000..4c72fbc1aaa --- /dev/null +++ b/test/ext/vom_test.cpp @@ -0,0 +1,1447 @@ +/* + * Test suite for class VppOM + * + * Copyright (c) 2017 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +#define BOOST_TEST_MODULE "VPP OBJECT MODEL" +#define BOOST_TEST_DYN_LINK + +#include +#include + + +#include +#include + +#include "vom/om.hpp" +#include "vom/interface.hpp" +#include "vom/l2_binding.hpp" +#include "vom/l3_binding.hpp" +#include "vom/bridge_domain.hpp" +#include "vom/bridge_domain_entry.hpp" +#include "vom/bridge_domain_arp_entry.hpp" +#include "vom/prefix.hpp" +#include "vom/route.hpp" +#include "vom/route_domain.hpp" +#include "vom/vxlan_tunnel.hpp" +#include "vom/sub_interface.hpp" +#include "vom/acl_list.hpp" +#include "vom/acl_binding.hpp" +#include "vom/acl_l3_rule.hpp" +#include "vom/acl_l2_rule.hpp" +#include "vom/arp_proxy_config.hpp" +#include "vom/arp_proxy_binding.hpp" +#include "vom/ip_unnumbered.hpp" +#include "vom/interface_ip6_nd.hpp" +#include "vom/interface_span.hpp" +#include "vom/neighbour.hpp" +#include "vom/nat_static.hpp" +#include "vom/nat_binding.hpp" + +using namespace boost; +using namespace VOM; + +/** + * An expectation exception + */ +class ExpException +{ +public: + ExpException(unsigned int number) + { + // a neat place to add a break point + std::cout << " ExpException here: " << number << std::endl; + } +}; + +class MockListener : public interface::event_listener, + public interface::stat_listener +{ + void handle_interface_stat(interface::stats_cmd *cmd) + { + } + void handle_interface_event(interface::events_cmd *cmd) + { + } +}; + +class MockCmdQ : public HW::cmd_q +{ +public: + MockCmdQ(): + m_strict_order(true) + { + } + virtual ~MockCmdQ() + { + } + void expect(cmd *f) + { + m_exp_queue.push_back(f); + } + void enqueue(cmd *f) + { + m_act_queue.push_back(f); + } + void enqueue(std::queue &cmds) + { + while (cmds.size()) + { + m_act_queue.push_back(cmds.front()); + cmds.pop(); + } + } + void enqueue(std::shared_ptr f) + { + m_act_queue.push_back(f.get()); + } + + void dequeue(cmd *f) + { + } + + void dequeue(std::shared_ptr cmd) + { + } + + void strict_order(bool on) + { + m_strict_order = on; + } + + bool is_empty() + { + return ((0 == m_exp_queue.size()) && + (0 == m_act_queue.size())); + } + + rc_t write() + { + cmd *f_exp, *f_act; + rc_t rc = rc_t::OK; + + while (m_act_queue.size()) + { + bool matched = false; + auto it_exp = m_exp_queue.begin(); + auto it_act = m_act_queue.begin(); + + f_act = *it_act; + + std::cout << " Act: " << f_act->to_string() << std::endl; + while (it_exp != m_exp_queue.end()) + { + f_exp = *it_exp; + try + { + std::cout << " Exp: " << f_exp->to_string() << std::endl; + + if (typeid(*f_exp) != typeid(*f_act)) + { + throw ExpException(1); + } + + if (typeid(*f_exp) == typeid(interface::af_packet_create_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(interface::loopback_create_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(interface::loopback_delete_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(interface::af_packet_delete_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(interface::state_change_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(interface::set_table_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(interface::set_mac_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(interface::set_tag)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(route_domain::create_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(route_domain::delete_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(route::ip_route::update_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(route::ip_route::delete_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(neighbour::create_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(neighbour::delete_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(l3_binding::bind_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(l3_binding::unbind_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(bridge_domain::create_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(bridge_domain::delete_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(bridge_domain_entry::create_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(bridge_domain_entry::delete_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(bridge_domain_arp_entry::create_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(bridge_domain_arp_entry::delete_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(l2_binding::bind_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(l2_binding::unbind_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(l2_binding::set_vtr_op_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(vxlan_tunnel::create_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(vxlan_tunnel::delete_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(sub_interface::create_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(sub_interface::delete_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(ACL::l3_list::update_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(ACL::l3_list::delete_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(ACL::l3_binding::bind_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(ACL::l3_binding::unbind_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(ACL::l2_list::update_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(ACL::l2_list::delete_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(ACL::l2_binding::bind_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(ACL::l2_binding::unbind_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(arp_proxy_binding::bind_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(arp_proxy_binding::unbind_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(arp_proxy_config::config_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(arp_proxy_config::unconfig_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(ip_unnumbered::config_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(ip_unnumbered::unconfig_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(ip6nd_ra_config::config_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(ip6nd_ra_config::unconfig_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(ip6nd_ra_prefix::config_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(ip6nd_ra_prefix::unconfig_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(interface_span::config_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(interface_span::unconfig_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(nat_static::create_44_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(nat_static::delete_44_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(nat_binding::bind_44_input_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(nat_binding::unbind_44_input_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else if (typeid(*f_exp) == typeid(interface::events_cmd)) + { + rc = handle_derived(f_exp, f_act); + } + else + { + throw ExpException(2); + } + + // if we get here then we found the match. + m_exp_queue.erase(it_exp); + m_act_queue.erase(it_act); + delete f_exp; + delete f_act; + + // return any injected failures to the agent + if (rc_t::OK != rc && rc_t::NOOP != rc) + { + return (rc); + } + + matched = true; + break; + } + catch (ExpException &e) + { + // The expected and actual do not match + if (m_strict_order) + { + // in strict ordering mode this is fatal, so rethrow + throw e; + } + else + { + // move the iterator onto the next in the expected list and + // check for a match + ++it_exp; + } + } + } + + if (!matched) + throw ExpException(3); + } + + return (rc); + } +private: + + template + rc_t handle_derived(const cmd *f_exp, cmd *f_act) + { + const T *i_exp; + T *i_act; + + i_exp = dynamic_cast(f_exp); + i_act = dynamic_cast(f_act); + if (!(*i_exp == *i_act)) + { + throw ExpException(4); + } + // pass the data and return code to the agent + i_act->item() = i_exp->item(); + + return (i_act->item().rc()); + } + + // The Q to push the expectations on + std::deque m_exp_queue; + + // the queue to push the actual events on + std::deque m_act_queue; + + // control whether the expected queue is strictly ordered. + bool m_strict_order; +}; + +class VppInit { +public: + std::string name; + MockCmdQ *f; + + VppInit() + : name("vpp-ut"), + f(new MockCmdQ()) + { + HW::init(f); + OM::init(); + logger().set(log_level_t::DEBUG); + } + + ~VppInit() { + delete f; + } +}; + +BOOST_AUTO_TEST_SUITE(VppOM_test) + +#define TRY_CHECK_RC(stmt) \ +{ \ + try { \ + BOOST_CHECK(rc_t::OK == stmt); \ + } \ + catch (ExpException &e) \ + { \ + BOOST_CHECK(false); \ + } \ + BOOST_CHECK(vi.f->is_empty()); \ +} + +#define TRY_CHECK(stmt) \ +{ \ + try { \ + stmt; \ + } \ + catch (ExpException &e) \ + { \ + BOOST_CHECK(false); \ + } \ + BOOST_CHECK(vi.f->is_empty()); \ +} + +#define ADD_EXPECT(stmt) \ + vi.f->expect(new stmt) + +#define STRICT_ORDER_OFF() \ + vi.f->strict_order(false) + +BOOST_AUTO_TEST_CASE(test_interface) { + VppInit vi; + const std::string go = "GeorgeOrwell"; + const std::string js = "JohnSteinbeck"; + rc_t rc = rc_t::OK; + + /* + * George creates and deletes the interface + */ + std::string itf1_name = "afpacket1"; + interface itf1(itf1_name, + interface::type_t::AFPACKET, + interface::admin_state_t::UP); + + /* + * set the expectation for a afpacket interface create. + * 2 is the interface handle VPP [mock] assigns + */ + HW::item hw_ifh(2, rc_t::OK); + ADD_EXPECT(interface::af_packet_create_cmd(hw_ifh, itf1_name)); + + HW::item hw_as_up(interface::admin_state_t::UP, rc_t::OK); + ADD_EXPECT(interface::state_change_cmd(hw_as_up, hw_ifh)); + + TRY_CHECK_RC(OM::write(go, itf1)); + + HW::item hw_as_down(interface::admin_state_t::DOWN, rc_t::OK); + ADD_EXPECT(interface::state_change_cmd(hw_as_down, hw_ifh)); + ADD_EXPECT(interface::af_packet_delete_cmd(hw_ifh, itf1_name)); + + TRY_CHECK(OM::remove(go)); + + /* + * George creates the interface, then John brings it down. + * George's remove is a no-op, sice John also owns the interface + */ + interface itf1b(itf1_name, + interface::type_t::AFPACKET, + interface::admin_state_t::DOWN); + + ADD_EXPECT(interface::af_packet_create_cmd(hw_ifh, itf1_name)); + ADD_EXPECT(interface::state_change_cmd(hw_as_up, hw_ifh)); + TRY_CHECK_RC(OM::write(go, itf1)); + + ADD_EXPECT(interface::state_change_cmd(hw_as_down, hw_ifh)); + TRY_CHECK_RC(OM::write(js, itf1b)); + + TRY_CHECK(OM::remove(go)); + + ADD_EXPECT(interface::af_packet_delete_cmd(hw_ifh, itf1_name)); + TRY_CHECK(OM::remove(js)); + + /* + * George adds an interface, then we flush all of Geroge's state + */ + ADD_EXPECT(interface::af_packet_create_cmd(hw_ifh, itf1_name)); + ADD_EXPECT(interface::state_change_cmd(hw_as_up, hw_ifh)); + TRY_CHECK_RC(OM::write(go, itf1)); + + TRY_CHECK(OM::mark(go)); + + ADD_EXPECT(interface::state_change_cmd(hw_as_down, hw_ifh)); + ADD_EXPECT(interface::af_packet_delete_cmd(hw_ifh, itf1_name)); + TRY_CHECK(OM::sweep(go)); + + /* + * George adds an interface. mark stale. update the same interface. sweep + * and expect no delete + */ + ADD_EXPECT(interface::af_packet_create_cmd(hw_ifh, itf1_name)); + ADD_EXPECT(interface::state_change_cmd(hw_as_down, hw_ifh)); + TRY_CHECK_RC(OM::write(go, itf1b)); + + TRY_CHECK(OM::mark(go)); + + ADD_EXPECT(interface::state_change_cmd(hw_as_up, hw_ifh)); + TRY_CHECK_RC(OM::write(go, itf1)); + + TRY_CHECK(OM::sweep(go)); + + ADD_EXPECT(interface::state_change_cmd(hw_as_down, hw_ifh)); + ADD_EXPECT(interface::af_packet_delete_cmd(hw_ifh, itf1_name)); + TRY_CHECK(OM::remove(go)); + + /* + * George adds an insterface, then we mark that state. Add a second interface + * an flush the first that is now stale. + */ + ADD_EXPECT(interface::af_packet_create_cmd(hw_ifh, itf1_name)); + ADD_EXPECT(interface::state_change_cmd(hw_as_up, hw_ifh)); + TRY_CHECK_RC(OM::write(go, itf1)); + + TRY_CHECK(OM::mark(go)); + + std::string itf2_name = "afpacket2"; + interface itf2(itf2_name, + interface::type_t::AFPACKET, + interface::admin_state_t::UP); + HW::item hw_ifh2(3, rc_t::OK); + + ADD_EXPECT(interface::af_packet_create_cmd(hw_ifh2, itf2_name)); + ADD_EXPECT(interface::state_change_cmd(hw_as_up, hw_ifh2)); + TRY_CHECK_RC(OM::write(go, itf2)); + + ADD_EXPECT(interface::state_change_cmd(hw_as_down, hw_ifh)); + ADD_EXPECT(interface::af_packet_delete_cmd(hw_ifh, itf1_name)); + TRY_CHECK(OM::sweep(go)); + + TRY_CHECK(OM::mark(go)); + + ADD_EXPECT(interface::state_change_cmd(hw_as_down, hw_ifh2)); + ADD_EXPECT(interface::af_packet_delete_cmd(hw_ifh2, itf2_name)); + TRY_CHECK(OM::sweep(go)); +} + +BOOST_AUTO_TEST_CASE(test_bvi) { + VppInit vi; + const std::string ernest = "ErnestHemmingway"; + const std::string graham = "GrahamGreene"; + rc_t rc = rc_t::OK; + l3_binding *l3; + + HW::item hw_as_up(interface::admin_state_t::UP, + rc_t::OK); + HW::item hw_as_down(interface::admin_state_t::DOWN, + rc_t::OK); + + /* + * Enrest creates a BVI with address 10.10.10.10/24 + */ + route::prefix_t pfx_10("10.10.10.10", 24); + + const std::string bvi_name = "bvi1"; + interface itf(bvi_name, + interface::type_t::BVI, + interface::admin_state_t::UP); + HW::item hw_ifh(4, rc_t::OK); + HW::item hw_pfx_10(pfx_10, rc_t::OK); + + ADD_EXPECT(interface::loopback_create_cmd(hw_ifh, bvi_name)); + ADD_EXPECT(interface::set_tag(hw_ifh, bvi_name)); + ADD_EXPECT(interface::state_change_cmd(hw_as_up, hw_ifh)); + TRY_CHECK_RC(OM::write(ernest, itf)); + + l3 = new l3_binding(itf, pfx_10); + HW::item hw_l3_bind(true, rc_t::OK); + HW::item hw_l3_unbind(false, rc_t::OK); + ADD_EXPECT(l3_binding::bind_cmd(hw_l3_bind, hw_ifh.data(), pfx_10)); + TRY_CHECK_RC(OM::write(ernest, *l3)); + + // change the MAC address on the BVI + interface itf_new_mac(bvi_name, + interface::type_t::BVI, + interface::admin_state_t::UP); + l2_address_t l2_addr({0,1,2,3,4,5}); + HW::item hw_mac(l2_addr, rc_t::OK); + itf_new_mac.set(l2_addr); + ADD_EXPECT(interface::set_mac_cmd(hw_mac, hw_ifh)); + TRY_CHECK_RC(OM::write(ernest, itf_new_mac)); + + // create/write the interface to the OM again but with an unset MAC + // this should not generate a MAC address update + TRY_CHECK_RC(OM::write(ernest, itf)); + + // change the MAC address on the BVI - again + interface itf_new_mac2(bvi_name, + interface::type_t::BVI, + interface::admin_state_t::UP); + l2_address_t l2_addr2({0,1,2,3,4,6}); + HW::item hw_mac2(l2_addr2, rc_t::OK); + itf_new_mac2.set(l2_addr2); + ADD_EXPECT(interface::set_mac_cmd(hw_mac2, hw_ifh)); + TRY_CHECK_RC(OM::write(ernest, itf_new_mac2)); + + delete l3; + ADD_EXPECT(l3_binding::unbind_cmd(hw_l3_unbind, hw_ifh.data(), pfx_10)); + ADD_EXPECT(interface::state_change_cmd(hw_as_down, hw_ifh)); + ADD_EXPECT(interface::loopback_delete_cmd(hw_ifh)); + TRY_CHECK(OM::remove(ernest)); + + /* + * Graham creates a BVI with address 10.10.10.10/24 in Routing Domain + */ + route_domain rd(1); + HW::item hw_rd4_create(true, rc_t::OK); + HW::item hw_rd4_delete(false, rc_t::OK); + HW::item hw_rd6_create(true, rc_t::OK); + HW::item hw_rd6_delete(false, rc_t::OK); + HW::item hw_rd4_bind(1, rc_t::OK); + HW::item hw_rd4_unbind(route::DEFAULT_TABLE, rc_t::OK); + HW::item hw_rd6_bind(1, rc_t::OK); + HW::item hw_rd6_unbind(route::DEFAULT_TABLE, rc_t::OK); + ADD_EXPECT(route_domain::create_cmd(hw_rd4_create, l3_proto_t::IPV4, 1)); + ADD_EXPECT(route_domain::create_cmd(hw_rd6_create, l3_proto_t::IPV6, 1)); + TRY_CHECK_RC(OM::write(graham, rd)); + + const std::string bvi2_name = "bvi2"; + interface *itf2 = new interface(bvi2_name, + interface::type_t::BVI, + interface::admin_state_t::UP, + rd); + HW::item hw_ifh2(5, rc_t::OK); + + ADD_EXPECT(interface::loopback_create_cmd(hw_ifh2, bvi2_name)); + ADD_EXPECT(interface::set_tag(hw_ifh2, bvi2_name)); + ADD_EXPECT(interface::state_change_cmd(hw_as_up, hw_ifh2)); + ADD_EXPECT(interface::set_table_cmd(hw_rd4_bind, l3_proto_t::IPV4, hw_ifh2)); + ADD_EXPECT(interface::set_table_cmd(hw_rd6_bind, l3_proto_t::IPV6, hw_ifh2)); + + TRY_CHECK_RC(OM::write(graham, *itf2)); + + l3 = new l3_binding(*itf2, pfx_10); + ADD_EXPECT(l3_binding::bind_cmd(hw_l3_bind, hw_ifh2.data(), pfx_10)); + TRY_CHECK_RC(OM::write(graham, *l3)); + + delete l3; + delete itf2; + + ADD_EXPECT(l3_binding::unbind_cmd(hw_l3_unbind, hw_ifh2.data(), pfx_10)); + ADD_EXPECT(interface::set_table_cmd(hw_rd4_unbind, l3_proto_t::IPV4, hw_ifh2)); + ADD_EXPECT(interface::set_table_cmd(hw_rd6_unbind, l3_proto_t::IPV6, hw_ifh2)); + ADD_EXPECT(interface::state_change_cmd(hw_as_down, hw_ifh2)); + ADD_EXPECT(interface::loopback_delete_cmd(hw_ifh2)); + ADD_EXPECT(route_domain::delete_cmd(hw_rd4_delete, l3_proto_t::IPV4, 1)); + ADD_EXPECT(route_domain::delete_cmd(hw_rd6_delete, l3_proto_t::IPV6, 1)); + TRY_CHECK(OM::remove(graham)); +} + +BOOST_AUTO_TEST_CASE(test_bridge) { + VppInit vi; + const std::string franz = "FranzKafka"; + const std::string dante = "Dante"; + rc_t rc = rc_t::OK; + + /* + * Franz creates an interface, Bridge-domain, then binds the two + */ + + // interface create + std::string itf1_name = "afpacket1"; + interface itf1(itf1_name, + interface::type_t::AFPACKET, + interface::admin_state_t::UP); + + HW::item hw_ifh(3, rc_t::OK); + HW::item hw_as_up(interface::admin_state_t::UP, + rc_t::OK); + ADD_EXPECT(interface::af_packet_create_cmd(hw_ifh, itf1_name)); + ADD_EXPECT(interface::state_change_cmd(hw_as_up, hw_ifh)); + + TRY_CHECK_RC(OM::write(franz, itf1)); + + // bridge-domain create + bridge_domain bd1(33); + + HW::item hw_bd(33, rc_t::OK); + ADD_EXPECT(bridge_domain::create_cmd(hw_bd)); + + TRY_CHECK_RC(OM::write(franz, bd1)); + + // L2-interface create and bind + // this needs to be delete'd before the flush below, since it too maintains + // references to the BD and Interface + l2_binding *l2itf = new l2_binding(itf1, bd1); + HW::item hw_l2_bind(true, rc_t::OK); + + ADD_EXPECT(l2_binding::bind_cmd(hw_l2_bind, hw_ifh.data(), hw_bd.data(), false)); + TRY_CHECK_RC(OM::write(franz, *l2itf)); + + /* + * Dante adds an interface to the same BD + */ + std::string itf2_name = "afpacket2"; + interface itf2(itf2_name, + interface::type_t::AFPACKET, + interface::admin_state_t::UP); + + HW::item hw_ifh2(4, rc_t::OK); + ADD_EXPECT(interface::af_packet_create_cmd(hw_ifh2, itf2_name)); + ADD_EXPECT(interface::state_change_cmd(hw_as_up, hw_ifh2)); + TRY_CHECK_RC(OM::write(dante, itf2)); + + // BD add is a no-op since it exists + TRY_CHECK_RC(OM::write(dante, bd1)); + + l2_binding *l2itf2 = new l2_binding(itf2, bd1); + HW::item hw_set_vtr(l2_binding::l2_vtr_op_t::L2_VTR_POP_1, rc_t::OK); + l2itf2->set(l2_binding::l2_vtr_op_t::L2_VTR_POP_1, 68); + + ADD_EXPECT(l2_binding::bind_cmd(hw_l2_bind, hw_ifh2.data(), hw_bd.data(), false)); + ADD_EXPECT(l2_binding::set_vtr_op_cmd(hw_set_vtr, hw_ifh2.data(), 68)); + TRY_CHECK_RC(OM::write(dante, *l2itf2)); + + // Add some static entries to the bridge-domain + HW::item hw_be1(true, rc_t::OK); + mac_address_t mac1({0,1,2,3,4,5}); + bridge_domain_entry *be1 = new bridge_domain_entry(bd1, mac1, itf2); + ADD_EXPECT(bridge_domain_entry::create_cmd(hw_be1, mac1, bd1.id(), hw_ifh2.data())); + TRY_CHECK_RC(OM::write(dante, *be1)); + + // Add some entries to the bridge-domain ARP termination table + HW::item hw_bea1(true, rc_t::OK); + boost::asio::ip::address ip1 = boost::asio::ip::address::from_string("10.10.10.10"); + + bridge_domain_arp_entry *bea1 = new bridge_domain_arp_entry(bd1, mac1, ip1); + ADD_EXPECT(bridge_domain_arp_entry::create_cmd(hw_be1, bd1.id(), mac1, ip1)); + TRY_CHECK_RC(OM::write(dante, *bea1)); + + // flush Franz's state + delete l2itf; + HW::item hw_as_down(interface::admin_state_t::DOWN, + rc_t::OK); + ADD_EXPECT(l2_binding::unbind_cmd(hw_l2_bind, hw_ifh.data(), hw_bd.data(), false)); + ADD_EXPECT(interface::state_change_cmd(hw_as_down, hw_ifh)); + ADD_EXPECT(interface::af_packet_delete_cmd(hw_ifh, itf1_name)); + TRY_CHECK(OM::remove(franz)); + + // flush Dante's state - the order the interface and BD are deleted + // is an uncontrollable artifact of the C++ object destruction. + delete l2itf2; + delete be1; + delete bea1; + STRICT_ORDER_OFF(); + ADD_EXPECT(l2_binding::unbind_cmd(hw_l2_bind, hw_ifh2.data(), hw_bd.data(), false)); + ADD_EXPECT(interface::state_change_cmd(hw_as_down, hw_ifh2)); + ADD_EXPECT(interface::af_packet_delete_cmd(hw_ifh2, itf2_name)); + ADD_EXPECT(bridge_domain_entry::delete_cmd(hw_be1, mac1, bd1.id())); + ADD_EXPECT(bridge_domain_arp_entry::delete_cmd(hw_be1, bd1.id(), mac1, ip1)); + ADD_EXPECT(bridge_domain::delete_cmd(hw_bd)); + TRY_CHECK(OM::remove(dante)); +} + +BOOST_AUTO_TEST_CASE(test_vxlan) { + VppInit vi; + const std::string franz = "FranzKafka"; + rc_t rc = rc_t::OK; + + /* + * Franz creates an interface, Bridge-domain, then binds the two + */ + + // VXLAN create + vxlan_tunnel::endpoint_t ep(boost::asio::ip::address::from_string("10.10.10.10"), + boost::asio::ip::address::from_string("10.10.10.11"), + 322); + + vxlan_tunnel vxt(ep.src, ep.dst, ep.vni); + + HW::item hw_vxt(3, rc_t::OK); + ADD_EXPECT(vxlan_tunnel::create_cmd(hw_vxt, "don't-care", ep)); + + TRY_CHECK_RC(OM::write(franz, vxt)); + + // bridge-domain create + bridge_domain bd1(33); + + HW::item hw_bd(33, rc_t::OK); + ADD_EXPECT(bridge_domain::create_cmd(hw_bd)); + + TRY_CHECK_RC(OM::write(franz, bd1)); + + // L2-interface create and bind + // this needs to be delete'd before the flush below, since it too maintains + // references to the BD and Interface + l2_binding *l2itf = new l2_binding(vxt, bd1); + HW::item hw_l2_bind(true, rc_t::OK); + + ADD_EXPECT(l2_binding::bind_cmd(hw_l2_bind, hw_vxt.data(), hw_bd.data(), false)); + TRY_CHECK_RC(OM::write(franz, *l2itf)); + + // flush Franz's state + delete l2itf; + HW::item hw_vxtdel(3, rc_t::NOOP); + STRICT_ORDER_OFF(); + ADD_EXPECT(l2_binding::unbind_cmd(hw_l2_bind, hw_vxt.data(), hw_bd.data(), false)); + ADD_EXPECT(bridge_domain::delete_cmd(hw_bd)); + ADD_EXPECT(vxlan_tunnel::delete_cmd(hw_vxtdel, ep)); + TRY_CHECK(OM::remove(franz)); +} + +BOOST_AUTO_TEST_CASE(test_vlan) { + VppInit vi; + const std::string noam = "NoamChomsky"; + rc_t rc = rc_t::OK; + + std::string itf1_name = "host1"; + interface itf1(itf1_name, + interface::type_t::AFPACKET, + interface::admin_state_t::UP); + + HW::item hw_ifh(2, rc_t::OK); + ADD_EXPECT(interface::af_packet_create_cmd(hw_ifh, itf1_name)); + + HW::item hw_as_up(interface::admin_state_t::UP, rc_t::OK); + ADD_EXPECT(interface::state_change_cmd(hw_as_up, hw_ifh)); + + TRY_CHECK_RC(OM::write(noam, itf1)); + + sub_interface *vl33 = new sub_interface(itf1, + interface::admin_state_t::UP, + 33); + + HW::item hw_vl33(3, rc_t::OK); + ADD_EXPECT(sub_interface::create_cmd(hw_vl33, itf1_name+".33", hw_ifh.data(), 33)); + ADD_EXPECT(interface::state_change_cmd(hw_as_up, hw_vl33)); + + TRY_CHECK_RC(OM::write(noam, *vl33)); + + delete vl33; + HW::item hw_as_down(interface::admin_state_t::DOWN, rc_t::OK); + HW::item hw_vl33_down(3, rc_t::NOOP); + ADD_EXPECT(interface::state_change_cmd(hw_as_down, hw_vl33)); + ADD_EXPECT(sub_interface::delete_cmd(hw_vl33_down)); + ADD_EXPECT(interface::state_change_cmd(hw_as_down, hw_ifh)); + ADD_EXPECT(interface::af_packet_delete_cmd(hw_ifh, itf1_name)); + + TRY_CHECK(OM::remove(noam)); +} + +BOOST_AUTO_TEST_CASE(test_acl) { + VppInit vi; + const std::string fyodor = "FyodorDostoyevsky"; + const std::string leo = "LeoTolstoy"; + rc_t rc = rc_t::OK; + + /* + * Fyodor adds an ACL in the input direction + */ + std::string itf1_name = "host1"; + interface itf1(itf1_name, + interface::type_t::AFPACKET, + interface::admin_state_t::UP); + HW::item hw_ifh(2, rc_t::OK); + HW::item hw_as_up(interface::admin_state_t::UP, rc_t::OK); + ADD_EXPECT(interface::af_packet_create_cmd(hw_ifh, itf1_name)); + ADD_EXPECT(interface::state_change_cmd(hw_as_up, hw_ifh)); + TRY_CHECK_RC(OM::write(fyodor, itf1)); + + route::prefix_t src("10.10.10.10", 32); + ACL::l3_rule r1(10, ACL::action_t::PERMIT, src, route::prefix_t::ZERO); + ACL::l3_rule r2(20, ACL::action_t::DENY, route::prefix_t::ZERO, route::prefix_t::ZERO); + + std::string acl_name = "acl1"; + ACL::l3_list acl1(acl_name); + acl1.insert(r2); + acl1.insert(r1); + ACL::l3_list::rules_t rules = {r1, r2}; + + HW::item hw_acl(2, rc_t::OK); + ADD_EXPECT(ACL::l3_list::update_cmd(hw_acl, acl_name, rules)); + TRY_CHECK_RC(OM::write(fyodor, acl1)); + + ACL::l3_binding *l3b = new ACL::l3_binding(direction_t::INPUT, itf1, acl1); + HW::item hw_binding(true, rc_t::OK); + ADD_EXPECT(ACL::l3_binding::bind_cmd(hw_binding, direction_t::INPUT, + hw_ifh.data(), hw_acl.data())); + TRY_CHECK_RC(OM::write(fyodor, *l3b)); + + /** + * Leo adds an L2 ACL in the output direction + */ + TRY_CHECK_RC(OM::write(leo, itf1)); + + std::string l2_acl_name = "l2_acl1"; + mac_address_t mac({0x0, 0x0, 0x1, 0x2, 0x3, 0x4}); + mac_address_t mac_mask({0xff, 0xff, 0xff, 0x0, 0x0, 0x0}); + ACL::l2_rule l2_r1(10, ACL::action_t::PERMIT, src, mac, mac_mask); + ACL::l2_rule l2_r2(20, ACL::action_t::DENY, src, {}, {}); + + ACL::l2_list l2_acl(l2_acl_name); + l2_acl.insert(l2_r2); + l2_acl.insert(l2_r1); + + ACL::l2_list::rules_t l2_rules = {l2_r1, l2_r2}; + + HW::item l2_hw_acl(3, rc_t::OK); + ADD_EXPECT(ACL::l2_list::update_cmd(l2_hw_acl, l2_acl_name, l2_rules)); + TRY_CHECK_RC(OM::write(leo, l2_acl)); + + ACL::l2_binding *l2b = new ACL::l2_binding(direction_t::OUTPUT, itf1, l2_acl); + HW::item l2_hw_binding(true, rc_t::OK); + ADD_EXPECT(ACL::l2_binding::bind_cmd(l2_hw_binding, direction_t::OUTPUT, + hw_ifh.data(), l2_hw_acl.data())); + TRY_CHECK_RC(OM::write(leo, *l2b)); + + delete l2b; + ADD_EXPECT(ACL::l2_binding::unbind_cmd(l2_hw_binding, direction_t::OUTPUT, + hw_ifh.data(), l2_hw_acl.data())); + ADD_EXPECT(ACL::l2_list::delete_cmd(l2_hw_acl)); + TRY_CHECK(OM::remove(leo)); + + delete l3b; + HW::item hw_as_down(interface::admin_state_t::DOWN, + rc_t::OK); + STRICT_ORDER_OFF(); + ADD_EXPECT(ACL::l3_binding::unbind_cmd(hw_binding, direction_t::INPUT, + hw_ifh.data(), hw_acl.data())); + ADD_EXPECT(ACL::l3_list::delete_cmd(hw_acl)); + ADD_EXPECT(interface::state_change_cmd(hw_as_down, hw_ifh)); + ADD_EXPECT(interface::af_packet_delete_cmd(hw_ifh, itf1_name)); + + TRY_CHECK(OM::remove(fyodor)); +} + +BOOST_AUTO_TEST_CASE(test_arp_proxy) { + VppInit vi; + const std::string kurt = "KurtVonnegut"; + rc_t rc = rc_t::OK; + + asio::ip::address_v4 low = asio::ip::address_v4::from_string("10.0.0.0"); + asio::ip::address_v4 high = asio::ip::address_v4::from_string("10.0.0.255"); + + arp_proxy_config ap(low, high); + HW::item hw_ap_cfg(true, rc_t::OK); + ADD_EXPECT(arp_proxy_config::config_cmd(hw_ap_cfg, low, high)); + TRY_CHECK_RC(OM::write(kurt, ap)); + + std::string itf3_name = "host3"; + interface itf3(itf3_name, + interface::type_t::AFPACKET, + interface::admin_state_t::UP); + HW::item hw_ifh(2, rc_t::OK); + HW::item hw_as_up(interface::admin_state_t::UP, rc_t::OK); + ADD_EXPECT(interface::af_packet_create_cmd(hw_ifh, itf3_name)); + ADD_EXPECT(interface::state_change_cmd(hw_as_up, hw_ifh)); + TRY_CHECK_RC(OM::write(kurt, itf3)); + + arp_proxy_binding *apb = new arp_proxy_binding(itf3, ap); + HW::item hw_binding(true, rc_t::OK); + ADD_EXPECT(arp_proxy_binding::bind_cmd(hw_binding, hw_ifh.data())); + TRY_CHECK_RC(OM::write(kurt, *apb)); + + delete apb; + + HW::item hw_as_down(interface::admin_state_t::DOWN, + rc_t::OK); + STRICT_ORDER_OFF(); + ADD_EXPECT(arp_proxy_binding::unbind_cmd(hw_binding, hw_ifh.data())); + ADD_EXPECT(interface::state_change_cmd(hw_as_down, hw_ifh)); + ADD_EXPECT(interface::af_packet_delete_cmd(hw_ifh, itf3_name)); + ADD_EXPECT(arp_proxy_config::unconfig_cmd(hw_ap_cfg, low, high)); + + TRY_CHECK(OM::remove(kurt)); +} + +BOOST_AUTO_TEST_CASE(test_ip_unnumbered) { + VppInit vi; + const std::string eric = "EricAmbler"; + rc_t rc = rc_t::OK; + + /* + * Interface 1 has the L3 address + */ + std::string itf1_name = "host1"; + interface itf1(itf1_name, + interface::type_t::AFPACKET, + interface::admin_state_t::UP); + HW::item hw_ifh(2, rc_t::OK); + HW::item hw_as_up(interface::admin_state_t::UP, rc_t::OK); + ADD_EXPECT(interface::af_packet_create_cmd(hw_ifh, itf1_name)); + ADD_EXPECT(interface::state_change_cmd(hw_as_up, hw_ifh)); + TRY_CHECK_RC(OM::write(eric, itf1)); + + route::prefix_t pfx_10("10.10.10.10", 24); + l3_binding *l3 = new l3_binding(itf1, pfx_10); + HW::item hw_l3_bind(true, rc_t::OK); + HW::item hw_l3_unbind(false, rc_t::OK); + ADD_EXPECT(l3_binding::bind_cmd(hw_l3_bind, hw_ifh.data(), pfx_10)); + TRY_CHECK_RC(OM::write(eric, *l3)); + + /* + * Interface 2 is unnumbered + */ + std::string itf2_name = "host2"; + interface itf2(itf2_name, + interface::type_t::AFPACKET, + interface::admin_state_t::UP); + + HW::item hw_ifh2(4, rc_t::OK); + ADD_EXPECT(interface::af_packet_create_cmd(hw_ifh2, itf2_name)); + ADD_EXPECT(interface::state_change_cmd(hw_as_up, hw_ifh2)); + TRY_CHECK_RC(OM::write(eric, itf2)); + + ip_unnumbered *ipun = new ip_unnumbered(itf2, itf1); + HW::item hw_ip_cfg(true, rc_t::OK); + HW::item hw_ip_uncfg(false, rc_t::OK); + ADD_EXPECT(ip_unnumbered::config_cmd(hw_ip_cfg, hw_ifh2.data(), hw_ifh.data())); + TRY_CHECK_RC(OM::write(eric, *ipun)); + + delete l3; + delete ipun; + + HW::item hw_as_down(interface::admin_state_t::DOWN, rc_t::OK); + STRICT_ORDER_OFF(); + ADD_EXPECT(ip_unnumbered::unconfig_cmd(hw_ip_uncfg, hw_ifh2.data(), hw_ifh.data())); + ADD_EXPECT(l3_binding::unbind_cmd(hw_l3_unbind, hw_ifh.data(), pfx_10)); + ADD_EXPECT(interface::state_change_cmd(hw_as_down, hw_ifh2)); + ADD_EXPECT(interface::af_packet_delete_cmd(hw_ifh2, itf2_name)); + ADD_EXPECT(interface::state_change_cmd(hw_as_down, hw_ifh)); + ADD_EXPECT(interface::af_packet_delete_cmd(hw_ifh, itf1_name)); + + TRY_CHECK(OM::remove(eric)); +} + +BOOST_AUTO_TEST_CASE(test_ip6nd) { + VppInit vi; + const std::string paulo = "PauloCoelho"; + rc_t rc = rc_t::OK; + + /* + * ra config + */ + std::string itf_name = "host_ip6nd"; + interface itf(itf_name, + interface::type_t::AFPACKET, + interface::admin_state_t::UP); + HW::item hw_ifh(3, rc_t::OK); + HW::item hw_as_up(interface::admin_state_t::UP, rc_t::OK); + ADD_EXPECT(interface::af_packet_create_cmd(hw_ifh, itf_name)); + ADD_EXPECT(interface::state_change_cmd(hw_as_up, hw_ifh)); + TRY_CHECK_RC(OM::write(paulo, itf)); + + route::prefix_t pfx_10("fd8f:69d8:c12c:ca62::3", 128); + l3_binding *l3 = new l3_binding(itf, pfx_10); + HW::item hw_l3_bind(true, rc_t::OK); + HW::item hw_l3_unbind(false, rc_t::OK); + ADD_EXPECT(l3_binding::bind_cmd(hw_l3_bind, hw_ifh.data(), pfx_10)); + TRY_CHECK_RC(OM::write(paulo, *l3)); + + ra_config ra(0, 1, 0, 4); + ip6nd_ra_config *ip6ra = new ip6nd_ra_config(itf, ra); + HW::item hw_ip6nd_ra_config_config(true, rc_t::OK); + HW::item hw_ip6nd_ra_config_unconfig(false, rc_t::OK); + ADD_EXPECT(ip6nd_ra_config::config_cmd(hw_ip6nd_ra_config_config, hw_ifh.data(), ra)); + TRY_CHECK_RC(OM::write(paulo, *ip6ra)); + + /* + * ra prefix + */ + ra_prefix ra_pfx(pfx_10, 0, 0, 2592000, 604800); + ip6nd_ra_prefix *ip6pfx = new ip6nd_ra_prefix(itf, ra_pfx); + HW::item hw_ip6nd_ra_prefix_config(true, rc_t::OK); + HW::item hw_ip6nd_ra_prefix_unconfig(false, rc_t::OK); + ADD_EXPECT(ip6nd_ra_prefix::config_cmd(hw_ip6nd_ra_prefix_config, hw_ifh.data(), ra_pfx)); + TRY_CHECK_RC(OM::write(paulo, *ip6pfx)); + + delete ip6pfx; + + ADD_EXPECT(ip6nd_ra_prefix::unconfig_cmd(hw_ip6nd_ra_prefix_unconfig, hw_ifh.data(), ra_pfx)); + + delete ip6ra; + delete l3; + + HW::item hw_as_down(interface::admin_state_t::DOWN, rc_t::OK); + + STRICT_ORDER_OFF(); + ADD_EXPECT(ip6nd_ra_config::unconfig_cmd(hw_ip6nd_ra_config_unconfig, hw_ifh.data(), ra)); + ADD_EXPECT(l3_binding::unbind_cmd(hw_l3_unbind, hw_ifh.data(), pfx_10)); + ADD_EXPECT(interface::state_change_cmd(hw_as_down, hw_ifh)); + ADD_EXPECT(interface::af_packet_delete_cmd(hw_ifh, itf_name)); + + TRY_CHECK(OM::remove(paulo)); +} + +BOOST_AUTO_TEST_CASE(test_interface_span) { + VppInit vi; + const std::string elif = "ElifShafak"; + rc_t rc = rc_t::OK; + + /* + * Interface 1 to be mirrored + */ + std::string itf1_name = "port-from"; + interface itf1(itf1_name, + interface::type_t::AFPACKET, + interface::admin_state_t::UP); + HW::item hw_ifh(2, rc_t::OK); + HW::item hw_as_up(interface::admin_state_t::UP, rc_t::OK); + ADD_EXPECT(interface::af_packet_create_cmd(hw_ifh, itf1_name)); + ADD_EXPECT(interface::state_change_cmd(hw_as_up, hw_ifh)); + TRY_CHECK_RC(OM::write(elif, itf1)); + + /* + * Interface 2 where traffic is mirrored + */ + std::string itf2_name = "port-to"; + interface itf2(itf2_name, + interface::type_t::AFPACKET, + interface::admin_state_t::UP); + + HW::item hw_ifh2(4, rc_t::OK); + HW::item hw_as_up2(interface::admin_state_t::UP, rc_t::OK); + + ADD_EXPECT(interface::af_packet_create_cmd(hw_ifh2, itf2_name)); + ADD_EXPECT(interface::state_change_cmd(hw_as_up2, hw_ifh2)); + TRY_CHECK_RC(OM::write(elif, itf2)); + + interface_span *itf_span = new interface_span(itf1, itf2, interface_span::state_t::TX_RX_ENABLED); + HW::item hw_is_cfg(true, rc_t::OK); + HW::item hw_is_uncfg(true, rc_t::OK); + ADD_EXPECT(interface_span::config_cmd(hw_is_cfg, hw_ifh.data(), hw_ifh2.data(), interface_span::state_t::TX_RX_ENABLED)); + TRY_CHECK_RC(OM::write(elif, *itf_span)); + + HW::item hw_as_down(interface::admin_state_t::DOWN, rc_t::OK); + HW::item hw_as_down2(interface::admin_state_t::DOWN, rc_t::OK); + + delete itf_span; + STRICT_ORDER_OFF(); + ADD_EXPECT(interface_span::unconfig_cmd(hw_is_uncfg, hw_ifh.data(), hw_ifh2.data())); + ADD_EXPECT(interface::state_change_cmd(hw_as_down, hw_ifh)); + ADD_EXPECT(interface::af_packet_delete_cmd(hw_ifh, itf1_name)); + ADD_EXPECT(interface::state_change_cmd(hw_as_down2, hw_ifh2)); + ADD_EXPECT(interface::af_packet_delete_cmd(hw_ifh2, itf2_name)); + + TRY_CHECK(OM::remove(elif)); +} + +BOOST_AUTO_TEST_CASE(test_routing) { + VppInit vi; + const std::string ian = "IanFleming"; + rc_t rc = rc_t::OK; + + /* + * non-default route domain + */ + route_domain rd4(1); + HW::item hw_rd4_create(true, rc_t::OK); + HW::item hw_rd4_delete(false, rc_t::OK); + HW::item hw_rd6_create(true, rc_t::OK); + HW::item hw_rd6_delete(false, rc_t::OK); + HW::item hw_rd4_bind(1, rc_t::OK); + HW::item hw_rd4_unbind(route::DEFAULT_TABLE, rc_t::OK); + HW::item hw_rd6_bind(1, rc_t::OK); + HW::item hw_rd7_unbind(route::DEFAULT_TABLE, rc_t::OK); + ADD_EXPECT(route_domain::create_cmd(hw_rd4_create, l3_proto_t::IPV4, 1)); + ADD_EXPECT(route_domain::create_cmd(hw_rd6_create, l3_proto_t::IPV6, 1)); + TRY_CHECK_RC(OM::write(ian, rd4)); + + /* + * a couple of interfaces + */ + std::string itf1_name = "af1"; + interface itf1(itf1_name, + interface::type_t::AFPACKET, + interface::admin_state_t::UP); + HW::item hw_ifh(2, rc_t::OK); + HW::item hw_as_up(interface::admin_state_t::UP, rc_t::OK); + HW::item hw_as_down(interface::admin_state_t::DOWN, rc_t::OK); + ADD_EXPECT(interface::af_packet_create_cmd(hw_ifh, itf1_name)); + ADD_EXPECT(interface::state_change_cmd(hw_as_up, hw_ifh)); + TRY_CHECK_RC(OM::write(ian, itf1)); + + std::string itf2_name = "af2"; + interface *itf2 = new interface(itf2_name, + interface::type_t::AFPACKET, + interface::admin_state_t::UP, + rd4); + + HW::item hw_ifh2(4, rc_t::OK); + HW::item hw_as_up2(interface::admin_state_t::UP, rc_t::OK); + HW::item hw_as_down2(interface::admin_state_t::DOWN, rc_t::OK); + ADD_EXPECT(interface::af_packet_create_cmd(hw_ifh2, itf2_name)); + ADD_EXPECT(interface::state_change_cmd(hw_as_up2, hw_ifh2)); + ADD_EXPECT(interface::set_table_cmd(hw_rd4_bind, l3_proto_t::IPV4, hw_ifh2)); + ADD_EXPECT(interface::set_table_cmd(hw_rd6_bind, l3_proto_t::IPV6, hw_ifh2)); + TRY_CHECK_RC(OM::write(ian, *itf2)); + + /* + * prefix on each interface + */ + route::prefix_t pfx_10("10.10.10.10", 24); + l3_binding *l3_10 = new l3_binding(itf1, pfx_10); + HW::item hw_l3_10_bind(true, rc_t::OK); + HW::item hw_l3_10_unbind(false, rc_t::OK); + ADD_EXPECT(l3_binding::bind_cmd(hw_l3_10_bind, hw_ifh.data(), pfx_10)); + TRY_CHECK_RC(OM::write(ian, *l3_10)); + route::prefix_t pfx_11("11.11.11.11", 24); + l3_binding *l3_11 = new l3_binding(*itf2, pfx_11); + HW::item hw_l3_11_bind(true, rc_t::OK); + HW::item hw_l3_11_unbind(false, rc_t::OK); + ADD_EXPECT(l3_binding::bind_cmd(hw_l3_11_bind, hw_ifh2.data(), pfx_11)); + TRY_CHECK_RC(OM::write(ian, *l3_11)); + + /* + * A route via interface 1 in the default table + */ + route::prefix_t pfx_5("5.5.5.5", 32); + boost::asio::ip::address nh_10 = boost::asio::ip::address::from_string("10.10.10.11"); + route::path *path_10 = new route::path(nh_10, itf1); + route::ip_route *route_5 = new route::ip_route(pfx_5); + route_5->add(*path_10); + HW::item hw_route_5(true, rc_t::OK); + ADD_EXPECT(route::ip_route::update_cmd(hw_route_5, 0, pfx_5, {*path_10})); + TRY_CHECK_RC(OM::write(ian, *route_5)); + + /* + * A route via interface 2 in the non-default table + */ + boost::asio::ip::address nh_11 = boost::asio::ip::address::from_string("11.11.11.10"); + route::path *path_11 = new route::path(nh_11, *itf2); + route::ip_route *route_5_2 = new route::ip_route(rd4, pfx_5); + route_5_2->add(*path_11); + HW::item hw_route_5_2(true, rc_t::OK); + ADD_EXPECT(route::ip_route::update_cmd(hw_route_5_2, 1, pfx_5, {*path_11})); + TRY_CHECK_RC(OM::write(ian, *route_5_2)); + + /* + * An ARP entry for the neighbour on itf1 + */ + HW::item hw_neighbour(true, rc_t::OK); + mac_address_t mac_n({0,1,2,4,5,6}); + neighbour *ne = new neighbour(itf1, mac_n, nh_10); + ADD_EXPECT(neighbour::create_cmd(hw_neighbour, hw_ifh.data(), mac_n, nh_10)); + TRY_CHECK_RC(OM::write(ian, *ne)); + + /* + * A DVR route + */ + route::prefix_t pfx_6("6.6.6.6", 32); + route::path *path_l2 = new route::path(*itf2, nh_proto_t::ETHERNET); + route::ip_route *route_dvr = new route::ip_route(pfx_6); + route_dvr->add(*path_l2); + HW::item hw_route_dvr(true, rc_t::OK); + ADD_EXPECT(route::ip_route::update_cmd(hw_route_dvr, 0, pfx_6, {*path_l2})); + TRY_CHECK_RC(OM::write(ian, *route_dvr)); + + STRICT_ORDER_OFF(); + // delete the stack objects that hold references to others + // so the OM::remove is the call that removes the last reference + delete l3_11; + delete l3_10; + delete itf2; + delete route_5; + delete path_10; + delete route_5_2; + delete path_11; + delete route_dvr; + delete path_l2; + delete ne; + ADD_EXPECT(neighbour::delete_cmd(hw_neighbour, hw_ifh.data(), mac_n, nh_10)); + ADD_EXPECT(route::ip_route::delete_cmd(hw_route_dvr, 0, pfx_6)); + ADD_EXPECT(route::ip_route::delete_cmd(hw_route_5_2, 1, pfx_5)); + ADD_EXPECT(route::ip_route::delete_cmd(hw_route_5, 0, pfx_5)); + ADD_EXPECT(l3_binding::unbind_cmd(hw_l3_10_unbind, hw_ifh.data(), pfx_10)); + ADD_EXPECT(l3_binding::unbind_cmd(hw_l3_11_unbind, hw_ifh2.data(), pfx_11)); + ADD_EXPECT(interface::state_change_cmd(hw_as_down, hw_ifh)); + ADD_EXPECT(interface::af_packet_delete_cmd(hw_ifh, itf1_name)); + ADD_EXPECT(interface::set_table_cmd(hw_rd4_unbind, l3_proto_t::IPV4, hw_ifh2)); + ADD_EXPECT(interface::set_table_cmd(hw_rd4_unbind, l3_proto_t::IPV6, hw_ifh2)); + ADD_EXPECT(interface::state_change_cmd(hw_as_down2, hw_ifh2)); + ADD_EXPECT(interface::af_packet_delete_cmd(hw_ifh2, itf2_name)); + ADD_EXPECT(route_domain::delete_cmd(hw_rd4_delete, l3_proto_t::IPV4, 1)); + ADD_EXPECT(route_domain::delete_cmd(hw_rd6_delete, l3_proto_t::IPV6, 1)); + + TRY_CHECK(OM::remove(ian)); +} + +BOOST_AUTO_TEST_CASE(test_nat) { + VppInit vi; + const std::string gs = "GeorgeSimenon"; + rc_t rc = rc_t::OK; + + /* + * Inside Interface + */ + std::string itf_in_name = "inside"; + interface itf_in(itf_in_name, + interface::type_t::AFPACKET, + interface::admin_state_t::UP); + HW::item hw_ifh(2, rc_t::OK); + HW::item hw_as_up(interface::admin_state_t::UP, rc_t::OK); + HW::item hw_as_down(interface::admin_state_t::DOWN, rc_t::OK); + ADD_EXPECT(interface::af_packet_create_cmd(hw_ifh, itf_in_name)); + ADD_EXPECT(interface::state_change_cmd(hw_as_up, hw_ifh)); + TRY_CHECK_RC(OM::write(gs, itf_in)); + + /* + * outside + */ + std::string itf_out_name = "port-to"; + interface itf_out(itf_out_name, + interface::type_t::AFPACKET, + interface::admin_state_t::UP); + + HW::item hw_ifh2(4, rc_t::OK); + HW::item hw_as_up2(interface::admin_state_t::UP, rc_t::OK); + HW::item hw_as_down2(interface::admin_state_t::DOWN, rc_t::OK); + + ADD_EXPECT(interface::af_packet_create_cmd(hw_ifh2, itf_out_name)); + ADD_EXPECT(interface::state_change_cmd(hw_as_up2, hw_ifh2)); + TRY_CHECK_RC(OM::write(gs, itf_out)); + + /* + * A NAT static mapping + */ + boost::asio::ip::address in_addr = boost::asio::ip::address::from_string("10.0.0.1"); + boost::asio::ip::address_v4 out_addr = boost::asio::ip::address_v4::from_string("1.1.1.1"); + + nat_static ns(in_addr, out_addr); + HW::item hw_ns(true, rc_t::OK); + + ADD_EXPECT(nat_static::create_44_cmd(hw_ns, 0, in_addr.to_v4(), out_addr)); + TRY_CHECK_RC(OM::write(gs, ns)); + + /* + * bind nat inside and out + */ + nat_binding *nb_in = new nat_binding(itf_in, + direction_t::INPUT, + l3_proto_t::IPV4, + nat_binding::zone_t::INSIDE); + HW::item hw_nb_in(true, rc_t::OK); + + ADD_EXPECT(nat_binding::bind_44_input_cmd(hw_nb_in, hw_ifh.data().value(), + nat_binding::zone_t::INSIDE)); + TRY_CHECK_RC(OM::write(gs, *nb_in)); + + nat_binding *nb_out = new nat_binding(itf_out, + direction_t::INPUT, + l3_proto_t::IPV4, + nat_binding::zone_t::OUTSIDE); + HW::item hw_nb_out(true, rc_t::OK); + + ADD_EXPECT(nat_binding::bind_44_input_cmd(hw_nb_out, hw_ifh2.data().value(), + nat_binding::zone_t::OUTSIDE)); + TRY_CHECK_RC(OM::write(gs, *nb_out)); + + + STRICT_ORDER_OFF(); + delete nb_in; + delete nb_out; + ADD_EXPECT(nat_binding::unbind_44_input_cmd(hw_nb_in, hw_ifh.data().value(), + nat_binding::zone_t::INSIDE)); + ADD_EXPECT(nat_binding::unbind_44_input_cmd(hw_nb_out, hw_ifh2.data().value(), + nat_binding::zone_t::OUTSIDE)); + ADD_EXPECT(nat_static::delete_44_cmd(hw_ns, 0, in_addr.to_v4(), out_addr)); + ADD_EXPECT(interface::state_change_cmd(hw_as_down, hw_ifh)); + ADD_EXPECT(interface::af_packet_delete_cmd(hw_ifh, itf_in_name)); + ADD_EXPECT(interface::state_change_cmd(hw_as_down2, hw_ifh2)); + ADD_EXPECT(interface::af_packet_delete_cmd(hw_ifh2, itf_out_name)); + + TRY_CHECK(OM::remove(gs)); +} + +BOOST_AUTO_TEST_CASE(test_interface_events) { + VppInit vi; + MockListener ml; + + HW::item hw_want(true, rc_t::OK); + + ADD_EXPECT(interface::events_cmd(ml)); + cmd* itf = new interface::events_cmd(ml); + + HW::enqueue(itf); + HW::write(); + + HW::dequeue(itf); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/test/framework.py b/test/framework.py index 6446265773d..0c85bfbfcf5 100644 --- a/test/framework.py +++ b/test/framework.py @@ -1051,3 +1051,33 @@ class VppTestRunner(unittest.TextTestRunner): if not running_extended_tests(): print("Not running extended tests (some tests will be skipped)") return super(VppTestRunner, self).run(filtered) + + +class Worker(Thread): + def __init__(self, args, logger): + self.logger = logger + self.args = args + self.result = None + super(Worker, self).__init__() + + def run(self): + executable = self.args[0] + self.logger.debug("Running executable w/args `%s'" % self.args) + env = os.environ.copy() + env["CK_LOG_FILE_NAME"] = "-" + self.process = subprocess.Popen( + self.args, shell=False, env=env, preexec_fn=os.setpgrp, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = self.process.communicate() + self.logger.debug("Finished running `%s'" % executable) + self.logger.info("Return code is `%s'" % self.process.returncode) + self.logger.info(single_line_delim) + self.logger.info("Executable `%s' wrote to stdout:" % executable) + self.logger.info(single_line_delim) + self.logger.info(out) + self.logger.info(single_line_delim) + self.logger.info("Executable `%s' wrote to stderr:" % executable) + self.logger.info(single_line_delim) + self.logger.error(err) + self.logger.info(single_line_delim) + self.result = self.process.returncode diff --git a/test/test_vapi.py b/test/test_vapi.py index 5f972323c61..b5820fa181b 100644 --- a/test/test_vapi.py +++ b/test/test_vapi.py @@ -1,7 +1,6 @@ #!/usr/bin/env python """ VAPI test """ -from __future__ import division import unittest import os import signal @@ -9,37 +8,7 @@ import subprocess from threading import Thread from log import single_line_delim from framework import VppTestCase, running_extended_tests, \ - running_on_centos, VppTestRunner - - -class Worker(Thread): - def __init__(self, args, logger): - self.logger = logger - self.args = args - self.result = None - super(Worker, self).__init__() - - def run(self): - executable = self.args[0] - self.logger.debug("Running executable w/args `%s'" % self.args) - env = os.environ.copy() - env["CK_LOG_FILE_NAME"] = "-" - self.process = subprocess.Popen( - self.args, shell=False, env=env, preexec_fn=os.setpgrp, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - out, err = self.process.communicate() - self.logger.debug("Finished running `%s'" % executable) - self.logger.info("Return code is `%s'" % self.process.returncode) - self.logger.info(single_line_delim) - self.logger.info("Executable `%s' wrote to stdout:" % executable) - self.logger.info(single_line_delim) - self.logger.info(out) - self.logger.info(single_line_delim) - self.logger.info("Executable `%s' wrote to stderr:" % executable) - self.logger.info(single_line_delim) - self.logger.error(err) - self.logger.info(single_line_delim) - self.result = self.process.returncode + running_on_centos, VppTestRunner, Worker @unittest.skipUnless(running_extended_tests(), "part of extended tests") diff --git a/test/test_vom.py b/test/test_vom.py new file mode 100644 index 00000000000..bfd7007fbec --- /dev/null +++ b/test/test_vom.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python +""" VAPI test """ + +import unittest +import os +import signal +import subprocess +from threading import Thread +from log import single_line_delim +from framework import VppTestCase, running_extended_tests, \ + running_on_centos, VppTestRunner, Worker + + +@unittest.skipUnless(running_extended_tests(), "part of extended tests") +class VOMTestCase(VppTestCase): + """ VPP Object Model Test """ + + def test_vom_cpp(self): + """ run C++ VOM tests """ + var = "BR" + built_root = os.getenv(var, None) + self.assertIsNotNone(built_root, + "Environment variable `%s' not set" % var) + executable = "%s/vom_test/vom_test" % built_root + worker = Worker( + [executable, "vpp object model", self.shm_prefix], self.logger) + worker.start() + timeout = 120 + worker.join(timeout) + self.logger.info("Worker result is `%s'" % worker.result) + error = False + if worker.result is None: + try: + error = True + self.logger.error( + "Timeout! Worker did not finish in %ss" % timeout) + os.killpg(os.getpgid(worker.process.pid), signal.SIGTERM) + worker.join() + except: + raise Exception("Couldn't kill worker-spawned process") + if error: + raise Exception( + "Timeout! Worker did not finish in %ss" % timeout) + self.assert_equal(worker.result, 0, "Binary test return code") + + +if __name__ == '__main__': + unittest.main(testRunner=VppTestRunner) -- cgit 1.2.3-korg