VOM: bond: Add support for LACP

Change-Id: I0245263b212142858d3305b0f365d8342912dbb9
Signed-off-by: Mohsin Kazmi <sykazmi@cisco.com>
This commit is contained in:
Mohsin Kazmi
2018-03-02 12:31:37 +01:00
committed by Neale Ranns
parent de91006803
commit ed76ee24df
18 changed files with 1794 additions and 6 deletions

View File

@ -78,6 +78,11 @@ libvom_la_SOURCES = \
arp_proxy_binding.cpp \
arp_proxy_config_cmds.cpp \
arp_proxy_config.cpp \
bond_group_binding_cmds.cpp \
bond_group_binding.cpp \
bond_interface_cmds.cpp \
bond_interface.cpp \
bond_member.cpp \
bridge_domain_cmds.cpp \
bridge_domain.cpp \
bridge_domain_arp_entry.cpp \
@ -169,6 +174,9 @@ endif
vominclude_HEADERS = \
arp_proxy_binding.hpp \
arp_proxy_config.hpp \
bond_group_binding.hpp \
bond_interface.hpp \
bond_member.hpp \
bridge_domain.hpp \
bridge_domain_arp_entry.hpp \
bridge_domain_entry.hpp \

View File

@ -0,0 +1,178 @@
/*
* Copyright (c) 2018 Cisco and/or its affiliates.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "vom/bond_group_binding.hpp"
#include "vom/bond_group_binding_cmds.hpp"
namespace VOM {
/**
* A DB of all bond interface binding
*/
singular_db<bond_group_binding::key_t, bond_group_binding>
bond_group_binding::m_db;
bond_group_binding::event_handler bond_group_binding::m_evh;
bond_group_binding::bond_group_binding(const bond_interface& itf,
const enslaved_itf_t& itfs)
: m_itf(itf.singular())
, m_mem_itfs(itfs)
, m_binding(false)
{
}
bond_group_binding::bond_group_binding(const bond_group_binding& o)
: m_itf(o.m_itf)
, m_mem_itfs(o.m_mem_itfs)
, m_binding(o.m_binding)
{
}
bond_group_binding::~bond_group_binding()
{
sweep();
// not in the DB anymore.
m_db.release(key(), this);
}
const bond_group_binding::key_t
bond_group_binding::key() const
{
return (m_itf->key() + "-binding");
}
void
bond_group_binding::sweep()
{
auto it = m_mem_itfs.cbegin();
while (it != m_mem_itfs.cend()) {
if (m_binding) {
HW::enqueue(
new bond_group_binding_cmds::unbind_cmd(m_binding, it->hdl()));
}
HW::write();
++it;
}
}
void
bond_group_binding::dump(std::ostream& os)
{
m_db.dump(os);
}
void
bond_group_binding::replay()
{
auto it = m_mem_itfs.cbegin();
while (it != m_mem_itfs.cend()) {
if (m_binding) {
HW::enqueue(
new bond_group_binding_cmds::bind_cmd(m_binding, m_itf->handle(), *it));
}
HW::write();
++it;
}
}
std::string
bond_group_binding::to_string() const
{
auto it = m_mem_itfs.cbegin();
std::ostringstream s;
s << "bond-interface-binding: " << m_itf->to_string() << " slave-itfs: [";
while (it != m_mem_itfs.cend()) {
s << " " << it->to_string();
++it;
}
s << "]";
return (s.str());
}
void
bond_group_binding::update(const bond_group_binding& desired)
{
/*
* the desired state is always that the interface should be created
*/
auto it = m_mem_itfs.cbegin();
while (it != m_mem_itfs.cend()) {
if (!m_binding) {
HW::enqueue(
new bond_group_binding_cmds::bind_cmd(m_binding, m_itf->handle(), *it));
}
++it;
}
}
std::shared_ptr<bond_group_binding>
bond_group_binding::find_or_add(const bond_group_binding& temp)
{
return (m_db.find_or_add(temp.key(), temp));
}
std::shared_ptr<bond_group_binding>
bond_group_binding::singular() const
{
return find_or_add(*this);
}
bond_group_binding::event_handler::event_handler()
{
OM::register_listener(this);
inspect::register_handler({ "bond-intf-binding" }, "Bond interface binding",
this);
}
void
bond_group_binding::event_handler::handle_replay()
{
m_db.replay();
}
void
bond_group_binding::event_handler::handle_populate(const client_db::key_t& key)
{
/*
* handle it in interface class
*/
}
dependency_t
bond_group_binding::event_handler::order() const
{
/*
* We want enslaved interfaces bind to bond after interface
* but before anything else.
*/
return (dependency_t::BOND_BINDING);
}
void
bond_group_binding::event_handler::show(std::ostream& os)
{
m_db.dump(os);
}
}
/*
* fd.io coding-style-patch-verification: ON
*
* Local Variables:
* eval: (c-set-style "mozilla")
* End:
*/

View File

@ -0,0 +1,186 @@
/*
* Copyright (c) 2018 Cisco and/or its affiliates.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __VOM_BOND_GROUP_BINDING_H__
#define __VOM_BOND_GROUP_BINDING_H__
#include <set>
#include "vom/bond_interface.hpp"
#include "vom/bond_member.hpp"
#include "vom/hw.hpp"
#include "vom/inspect.hpp"
#include "vom/interface.hpp"
#include "vom/object_base.hpp"
#include "vom/om.hpp"
#include "vom/singular_db.hpp"
namespace VOM {
/**
* A representation of bond interface binding
*/
class bond_group_binding : public object_base
{
public:
/**
* The KEY can be used to uniquely identify the Bond Binding.
* (other choices for keys, like the summation of the properties
* of the rules, are rather too cumbersome to use
*/
typedef std::string key_t;
/**
* The container type for enslaved itfs
*/
typedef std::set<bond_member> enslaved_itf_t;
/**
* Construct a new object matching the desried state
*/
bond_group_binding(const bond_interface& itf, const enslaved_itf_t& mem);
/**
* Copy Constructor
*/
bond_group_binding(const bond_group_binding& o);
/**
* Destructor
*/
~bond_group_binding();
/**
* Return the 'singular' of the bond interface binding that matches this
* object
*/
std::shared_ptr<bond_group_binding> singular() const;
/**
* convert to string format for debug purposes
*/
std::string to_string() const;
/**
* get the key to this object
*/
const key_t key() const;
/**
* Dump all bond interface bindings into the stream provided
*/
static void dump(std::ostream& os);
private:
/**
* Class definition for listeners to OM events
*/
class event_handler : public OM::listener, public inspect::command_handler
{
public:
event_handler();
virtual ~event_handler() = default;
/**
* Handle a populate event
*/
void handle_populate(const client_db::key_t& key);
/**
* Handle a replay event
*/
void handle_replay();
/**
* Show the object in the Singular DB
*/
void show(std::ostream& os);
/**
* Get the sortable Id of the listener
*/
dependency_t order() const;
};
/**
* event_handler to register with OM
*/
static event_handler m_evh;
/**
* Enqueue command to the VPP command Q for the update
*/
void update(const bond_group_binding& obj);
/**
* Find or add bond interface binding to the OM
*/
static std::shared_ptr<bond_group_binding> find_or_add(
const bond_group_binding& temp);
/*
* It's the OM class that calls singular()
*/
friend class OM;
/**
* It's the singular_db class that calls replay()
*/
friend class singular_db<key_t, bond_group_binding>;
/**
* Sweep/reap the object if still stale
*/
void sweep(void);
/**
* replay the object to create it in hardware
*/
void replay(void);
/**
* A reference counting pointer to the bond interface.
* By holding the reference here, we can guarantee that
* this object will outlive the interface
*/
std::shared_ptr<bond_interface> m_itf;
/**
* A list of member interfaces.
*/
const enslaved_itf_t m_mem_itfs;
/**
* HW configuration for the binding. The bool representing the
* do/don't bind.
*/
HW::item<bool> m_binding;
/**
* A map of all bond interface bindings keyed against the interface +
* "binding".
*/
static singular_db<key_t, bond_group_binding> m_db;
};
};
/*
* fd.io coding-style-patch-verification: ON
*
* Local Variables:
* eval: (c-set-style "mozilla")
* End:
*/
#endif

View File

@ -0,0 +1,147 @@
/*
* Copyright (c) 2018 Cisco and/or its affiliates.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "vom/bond_group_binding_cmds.hpp"
namespace VOM {
namespace bond_group_binding_cmds {
bind_cmd::bind_cmd(HW::item<bool>& item,
const handle_t& bond_itf,
const bond_member& itf)
: rpc_cmd(item)
, m_bond_itf(bond_itf)
, m_itf(itf)
{
}
bool
bind_cmd::operator==(const bind_cmd& other) const
{
return ((m_bond_itf == other.m_bond_itf) && (m_itf == other.m_itf));
}
rc_t
bind_cmd::issue(connection& con)
{
msg_t req(con.ctx(), std::ref(*this));
auto& payload = req.get_request().get_payload();
m_itf.to_vpp(payload);
payload.bond_sw_if_index = m_bond_itf.value();
VAPI_CALL(req.execute());
m_hw_item.set(wait());
return rc_t::OK;
}
std::string
bind_cmd::to_string() const
{
std::ostringstream s;
s << "bond-itf-bind: " << m_hw_item.to_string()
<< " bond-itf:" << m_bond_itf.to_string()
<< " slave-itf:" << m_itf.hdl().to_string();
return (s.str());
}
unbind_cmd::unbind_cmd(HW::item<bool>& item, const handle_t& itf)
: rpc_cmd(item)
, m_itf(itf)
{
}
bool
unbind_cmd::operator==(const unbind_cmd& other) const
{
return (m_itf == other.m_itf);
}
rc_t
unbind_cmd::issue(connection& con)
{
msg_t req(con.ctx(), std::ref(*this));
auto& payload = req.get_request().get_payload();
payload.sw_if_index = m_itf.value();
VAPI_CALL(req.execute());
wait();
m_hw_item.set(rc_t::NOOP);
return rc_t::OK;
}
std::string
unbind_cmd::to_string() const
{
std::ostringstream s;
s << "bond-itf-unbind: " << m_hw_item.to_string()
<< " slave-itf:" << m_itf.to_string();
return (s.str());
}
dump_cmd::dump_cmd(const handle_t& hdl)
: m_itf(hdl)
{
}
dump_cmd::dump_cmd(const dump_cmd& d)
: m_itf(d.m_itf)
{
}
bool
dump_cmd::operator==(const dump_cmd& other) const
{
return (true);
}
rc_t
dump_cmd::issue(connection& con)
{
m_dump.reset(new msg_t(con.ctx(), std::ref(*this)));
auto& payload = m_dump->get_request().get_payload();
payload.sw_if_index = m_itf.value();
VAPI_CALL(m_dump->execute());
wait();
return rc_t::OK;
}
std::string
dump_cmd::to_string() const
{
return ("bond-slave-itfs-dump");
}
}; // namespace bond_group_binding_cmds
}; // namespace VOM
/*
* fd.io coding-style-patch-verification: ON
*
* Local Variables:
* eval: (c-set-style "mozilla")
* End:
*/

View File

@ -0,0 +1,138 @@
/*
* Copyright (c) 2018 Cisco and/or its affiliates.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __VOM_BOND_GROUP_BINDING_CMDS_H__
#define __VOM_BOND_GROUP_BINDING_CMDS_H__
#include "vom/bond_group_binding.hpp"
#include "vom/dump_cmd.hpp"
#include <vapi/bond.api.vapi.hpp>
namespace VOM {
namespace bond_group_binding_cmds {
/**
* A command class that binds the slave interface to the bond interface
*/
class bind_cmd : public rpc_cmd<HW::item<bool>, rc_t, vapi::Bond_enslave>
{
public:
/**
* Constructor
*/
bind_cmd(HW::item<bool>& item,
const handle_t& bond_itf,
const bond_member& itf);
/**
* Issue the command to VPP/HW
*/
rc_t issue(connection& con);
/**
* convert to string format for debug purposes
*/
std::string to_string() const;
/**
* Comparison operator - only used for UT
*/
bool operator==(const bind_cmd& i) const;
private:
/**
* sw_if_index of bond interface
*/
const handle_t m_bond_itf;
/**
* member interface of bond group
*/
const bond_member m_itf;
};
/**
* A cmd class that detach slave from a bond interface
*/
class unbind_cmd : public rpc_cmd<HW::item<bool>, rc_t, vapi::Bond_detach_slave>
{
public:
/**
* Constructor
*/
unbind_cmd(HW::item<bool>& item, const handle_t& itf);
/**
* Issue the command to VPP/HW
*/
rc_t issue(connection& con);
/**
* convert to string format for debug purposes
*/
std::string to_string() const;
/**
* Comparison operator - only used for UT
*/
bool operator==(const unbind_cmd& i) const;
private:
/**
* slave interface of bond group
*/
const handle_t m_itf;
};
/**
* A cmd class that Dumps slave itfs
*/
class dump_cmd : public VOM::dump_cmd<vapi::Sw_interface_slave_dump>
{
public:
/**
* Default Constructor
*/
dump_cmd(const handle_t& itf);
dump_cmd(const dump_cmd& d);
/**
* Issue the command to VPP/HW
*/
rc_t issue(connection& con);
/**
* convert to string format for debug purposes
*/
std::string to_string() const;
/**
* Comparison operator - only used for UT
*/
bool operator==(const dump_cmd& i) const;
private:
/**
* The interface to get the addresses for
*/
const handle_t m_itf;
};
};
};
/*
* fd.io coding-style-patch-verification: ON
*
* Local Variables:
* eval: (c-set-style "mozilla")
* End:
*/
#endif

View File

@ -0,0 +1,197 @@
/*
* Copyright (c) 2018 Cisco and/or its affiliates.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "vom/bond_interface.hpp"
#include "vom/bond_group_binding.hpp"
#include "vom/bond_group_binding_cmds.hpp"
#include "vom/bond_interface_cmds.hpp"
namespace VOM {
/**
* Construct a new object matching the desried state
*/
bond_interface::bond_interface(const std::string& name,
admin_state_t state,
mode_t mode,
lb_t lb)
: interface(name, type_t::BOND, state)
, m_l2_address(l2_address_t::ZERO)
, m_mode(mode)
, m_lb(lb)
{
}
bond_interface::bond_interface(const std::string& name,
admin_state_t state,
const l2_address_t& l2_address,
mode_t mode,
lb_t lb)
: interface(name, type_t::BOND, state)
, m_l2_address(l2_address)
, m_mode(mode)
, m_lb(lb)
{
}
bond_interface::~bond_interface()
{
sweep();
release();
}
bond_interface::bond_interface(const bond_interface& o)
: interface(o)
, m_l2_address(o.m_l2_address)
, m_mode(o.m_mode)
, m_lb(o.m_lb)
{
}
std::shared_ptr<bond_interface>
bond_interface::find(const handle_t& hdl)
{
return std::dynamic_pointer_cast<bond_interface>(interface::find(hdl));
}
void
bond_interface::set(bond_interface::mode_t mode)
{
m_mode = mode;
}
void
bond_interface::set(bond_interface::lb_t lb)
{
m_lb = lb;
}
std::string
bond_interface::to_string() const
{
std::ostringstream s;
s << this->interface::to_string() << " mode:" << m_mode.to_string()
<< " lb:" << m_lb.to_string();
return (s.str());
}
std::queue<cmd*>&
bond_interface::mk_create_cmd(std::queue<cmd*>& q)
{
q.push(new bond_interface_cmds::create_cmd(m_hdl, name(), m_mode, m_lb,
m_l2_address));
return (q);
}
std::queue<cmd*>&
bond_interface::mk_delete_cmd(std::queue<cmd*>& q)
{
q.push(new bond_interface_cmds::delete_cmd(m_hdl));
return (q);
}
std::shared_ptr<bond_interface>
bond_interface::singular() const
{
return std::dynamic_pointer_cast<bond_interface>(singular_i());
}
std::shared_ptr<interface>
bond_interface::singular_i() const
{
return m_db.find_or_add(name(), *this);
}
void
bond_interface::set(handle_t& handle)
{
this->interface::set(handle);
}
const bond_interface::mode_t bond_interface::mode_t::ROUND_ROBIN(1,
"round-robin");
const bond_interface::mode_t bond_interface::mode_t::ACTIVE_BACKUP(
2,
"active-backup");
const bond_interface::mode_t bond_interface::mode_t::XOR(3, "xor");
const bond_interface::mode_t bond_interface::mode_t::BROADCAST(4, "broadcast");
const bond_interface::mode_t bond_interface::mode_t::LACP(5, "lacp");
const bond_interface::mode_t bond_interface::mode_t::UNSPECIFIED(0,
"unspecified");
const bond_interface::mode_t
bond_interface::mode_t::from_numeric_val(uint8_t numeric)
{
if (1 == numeric) {
return (bond_interface::mode_t::ROUND_ROBIN);
}
if (2 == numeric) {
return (bond_interface::mode_t::ACTIVE_BACKUP);
}
if (3 == numeric) {
return (bond_interface::mode_t::XOR);
}
if (4 == numeric) {
return (bond_interface::mode_t::BROADCAST);
}
if (5 == numeric) {
return (bond_interface::mode_t::LACP);
}
return (bond_interface::mode_t::UNSPECIFIED);
}
bond_interface::mode_t::mode_t(int v, const std::string& s)
: enum_base<bond_interface::mode_t>(v, s)
{
}
const bond_interface::lb_t bond_interface::lb_t::L2(0, "l2");
const bond_interface::lb_t bond_interface::lb_t::L34(1, "l34");
const bond_interface::lb_t bond_interface::lb_t::L23(2, "l23");
const bond_interface::lb_t bond_interface::lb_t::UNSPECIFIED(~0, "unspecified");
const bond_interface::lb_t
bond_interface::lb_t::from_numeric_val(uint8_t numeric)
{
if (0 == numeric) {
return (bond_interface::lb_t::L2);
}
if (1 == numeric) {
return (bond_interface::lb_t::L34);
}
if (2 == numeric) {
return (bond_interface::lb_t::L23);
}
return (bond_interface::lb_t::UNSPECIFIED);
}
bond_interface::lb_t::lb_t(int v, const std::string& s)
: enum_base<bond_interface::lb_t>(v, s)
{
}
}; // namespace VOM
/*
* fd.io coding-style-patch-verification: ON
*
* Local Variables:
* eval: (c-set-style "mozilla")
* End:
*/

View File

@ -0,0 +1,197 @@
/*
* Copyright (c) 2018 Cisco and/or its affiliates.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __VOM_BOND_INTERFACE_H__
#define __VOM_BOND_INTERFACE_H__
#include "vom/interface.hpp"
namespace VOM {
/**
* A bond-interface. e.g. a bond interface
*/
class bond_interface : public interface
{
public:
/**
* A bond interface mode
*/
struct mode_t : enum_base<mode_t>
{
/**
* Round-Robin bond interface mode
*/
const static mode_t ROUND_ROBIN;
/**
* Active-backup bond interface mode
*/
const static mode_t ACTIVE_BACKUP;
/**
* XOR bond interface mode
*/
const static mode_t XOR;
/**
* Broadcast bond interface mode
*/
const static mode_t BROADCAST;
/**
* LACP bond interface mode
*/
const static mode_t LACP;
/**
* Unspecificed bond interface mode
*/
const static mode_t UNSPECIFIED;
/**
* Convert VPP's value of the bond to a mode
*/
static const mode_t from_numeric_val(uint8_t v);
private:
/**
* Private constructor taking the value and the string name
*/
mode_t(int v, const std::string& s);
};
/**
* A bond interface load balance
*/
struct lb_t : enum_base<lb_t>
{
/**
* L2 bond interface lb
*/
const static lb_t L2;
/**
* L34 bond interface lb
*/
const static lb_t L34;
/**
* L23 bond interface lb
*/
const static lb_t L23;
/**
* Unspecificed bond interface lb
*/
const static lb_t UNSPECIFIED;
/**
* Convert VPP's value of the bond to a lb
*/
static const lb_t from_numeric_val(uint8_t v);
private:
/**
* Private constructor taking the value and the string name
*/
lb_t(int v, const std::string& s);
};
bond_interface(const std::string& name,
admin_state_t state,
mode_t mode,
lb_t lb = lb_t::UNSPECIFIED);
bond_interface(const std::string& name,
admin_state_t state,
const l2_address_t& l2_address,
mode_t mode,
lb_t lb = lb_t::UNSPECIFIED);
~bond_interface();
bond_interface(const bond_interface& o);
/**
* The the singular instance of the bond interface in the DB by handle
*/
static std::shared_ptr<bond_interface> find(const handle_t& hdl);
/**
* Return the matching 'singular instance' of the BOND interface
*/
std::shared_ptr<bond_interface> singular() const;
/**
* set the mode
*/
void set(mode_t mode);
/**
* set the lb
*/
void set(lb_t lb);
/**
* convert to string
*/
virtual std::string to_string() const;
protected:
/**
* set the handle
*/
void set(handle_t& handle);
friend class interface_factory;
private:
/**
* l2 address on bond interface
*/
l2_address_t m_l2_address;
/**
* mode on bond interface
*/
mode_t m_mode;
/**
* lb mode on bond interface
*/
lb_t m_lb;
/**
* Return the matching 'instance' of the sub-interface
* over-ride from the base class
*/
std::shared_ptr<interface> singular_i() const;
/**
* Virtual functions to construct an interface create commands.
*/
virtual std::queue<cmd*>& mk_create_cmd(std::queue<cmd*>& cmds);
/**
* Virtual functions to construct an interface delete commands.
*/
virtual std::queue<cmd*>& mk_delete_cmd(std::queue<cmd*>& cmds);
/*
* It's the OM class that call singular()
*/
friend class OM;
};
}
/*
* fd.io coding-style-patch-verification: ON
*
* Local Variables:
* eval: (c-set-style "mozilla")
* End:
*/
#endif

View File

@ -0,0 +1,138 @@
/*
* Copyright (c) 2018 Cisco and/or its affiliates.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "vom/bond_interface_cmds.hpp"
DEFINE_VAPI_MSG_IDS_BOND_API_JSON;
namespace VOM {
namespace bond_interface_cmds {
create_cmd::create_cmd(HW::item<handle_t>& item,
const std::string& name,
const bond_interface::mode_t& mode,
const bond_interface::lb_t& lb,
const l2_address_t& l2_address)
: interface::create_cmd<vapi::Bond_create>(item, name)
, m_mode(mode)
, m_lb(lb)
, m_l2_address(l2_address)
{
}
rc_t
create_cmd::issue(connection& con)
{
msg_t req(con.ctx(), std::ref(*this));
auto& payload = req.get_request().get_payload();
if (m_l2_address != l2_address_t::ZERO) {
m_l2_address.to_bytes(payload.mac_address, 6);
payload.use_custom_mac = 1;
}
payload.mode = m_mode.value();
if ((m_mode == bond_interface::mode_t::XOR ||
m_mode == bond_interface::mode_t::LACP) &&
m_lb != bond_interface::lb_t::UNSPECIFIED)
payload.lb = m_lb.value();
VAPI_CALL(req.execute());
m_hw_item = wait();
if (m_hw_item.rc() == rc_t::OK) {
insert_interface();
}
return rc_t::OK;
}
std::string
create_cmd::to_string() const
{
std::ostringstream s;
s << "bond-intf-create: " << m_hw_item.to_string();
return (s.str());
}
delete_cmd::delete_cmd(HW::item<handle_t>& item)
: interface::delete_cmd<vapi::Bond_delete>(item)
{
}
rc_t
delete_cmd::issue(connection& con)
{
msg_t req(con.ctx(), std::ref(*this));
auto& payload = req.get_request().get_payload();
payload.sw_if_index = m_hw_item.data().value();
VAPI_CALL(req.execute());
wait();
m_hw_item.set(rc_t::NOOP);
remove_interface();
return rc_t::OK;
}
std::string
delete_cmd::to_string() const
{
std::ostringstream s;
s << "bond-itf-delete: " << m_hw_item.to_string();
return (s.str());
}
dump_cmd::dump_cmd()
{
}
bool
dump_cmd::operator==(const dump_cmd& other) const
{
return (true);
}
rc_t
dump_cmd::issue(connection& con)
{
m_dump.reset(new msg_t(con.ctx(), std::ref(*this)));
VAPI_CALL(m_dump->execute());
wait();
return rc_t::OK;
}
std::string
dump_cmd::to_string() const
{
return ("bond-itf-dump");
}
} // namespace bond_interface_cmds
} // namespace VOM
/*
* fd.io coding-style-patch-verification: ON
*
* Local Variables:
* eval: (c-set-style "mozilla")
* End:
*/

View File

@ -0,0 +1,111 @@
/*
* Copyright (c) 2018 Cisco and/or its affiliates.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __VOM_BOND_INTERFACE_CMDS_H__
#define __VOM_BOND_INTERFACE_CMDS_H__
#include "vom/bond_interface.hpp"
#include "vom/dump_cmd.hpp"
#include "vom/interface.hpp"
#include "vom/rpc_cmd.hpp"
#include <vapi/bond.api.vapi.hpp>
#include <vapi/interface.api.vapi.hpp>
namespace VOM {
namespace bond_interface_cmds {
/**
* A functor class that creates an interface
*/
class create_cmd : public interface::create_cmd<vapi::Bond_create>
{
public:
create_cmd(HW::item<handle_t>& item,
const std::string& name,
const bond_interface::mode_t& mode,
const bond_interface::lb_t& lb,
const l2_address_t& l2_address);
/**
* Issue the command to VPP/HW
*/
rc_t issue(connection& con);
/**
* convert to string format for debug purposes
*/
std::string to_string() const;
private:
const bond_interface::mode_t m_mode;
const bond_interface::lb_t m_lb;
const l2_address_t m_l2_address;
};
/**
* A functor class that deletes a Tap interface
*/
class delete_cmd : public interface::delete_cmd<vapi::Bond_delete>
{
public:
delete_cmd(HW::item<handle_t>& item);
/**
* Issue the command to VPP/HW
*/
rc_t issue(connection& con);
/**
* convert to string format for debug purposes
*/
std::string to_string() const;
};
/**
* A cmd class that Dumps all the Vpp Interfaces
*/
class dump_cmd : public VOM::dump_cmd<vapi::Sw_interface_bond_dump>
{
public:
/**
* Default Constructor
*/
dump_cmd();
/**
* Issue the command to VPP/HW
*/
rc_t issue(connection& con);
/**
* convert to string format for debug purposes
*/
std::string to_string() const;
/**
* Comparison operator - only used for UT
*/
bool operator==(const dump_cmd& i) const;
};
}; // namespace bond_interface_cmds
}; // namespace VOM
/*
* fd.io coding-style-patch-verification: ON
*
* Local Variables:
* eval: (c-set-style "mozilla")
* End:
*/
#endif

View File

@ -0,0 +1,130 @@
/*
* Copyright (c) 2018 Cisco and/or its affiliates.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "vom/bond_member.hpp"
namespace VOM {
/**
* Construct a new object matching the desried state
*/
bond_member::bond_member(const interface& itf, mode_t mode, rate_t rate)
: m_itf(itf.singular())
, m_mode(mode)
, m_rate(rate)
{
}
bond_member::~bond_member()
{
}
bond_member::bond_member(const bond_member& o)
: m_itf(o.m_itf)
, m_mode(o.m_mode)
, m_rate(o.m_rate)
{
}
void
bond_member::to_vpp(vapi_payload_bond_enslave& bond_enslave) const
{
bond_enslave.sw_if_index = m_itf->handle().value();
bond_enslave.is_passive = (m_mode == mode_t::PASSIVE) ? 1 : 0;
bond_enslave.is_long_timeout = (m_rate == rate_t::SLOW) ? 1 : 0;
}
std::string
bond_member::to_string() const
{
std::ostringstream s;
s << m_itf->to_string() << " mode:" << m_mode.to_string()
<< " rate:" << m_rate.to_string();
return (s.str());
}
bool
bond_member::operator<(const bond_member& itf) const
{
return (m_itf->handle() < itf.m_itf->handle());
}
void
bond_member::set(mode_t mode)
{
m_mode = mode;
}
void
bond_member::set(rate_t rate)
{
m_rate = rate;
}
const handle_t
bond_member::hdl(void) const
{
return m_itf->handle();
}
bool
bond_member::operator==(const bond_member& b) const
{
return ((m_itf == b.m_itf) && (m_mode == b.m_mode) && (m_rate == b.m_rate));
}
const bond_member::mode_t bond_member::mode_t::ACTIVE(0, "active");
const bond_member::mode_t bond_member::mode_t::PASSIVE(1, "passive");
const bond_member::mode_t
bond_member::mode_t::from_numeric_val(uint8_t numeric)
{
if (0 == numeric)
return (bond_member::mode_t::ACTIVE);
return (bond_member::mode_t::PASSIVE);
}
bond_member::mode_t::mode_t(int v, const std::string& s)
: enum_base<bond_member::mode_t>(v, s)
{
}
const bond_member::rate_t bond_member::rate_t::FAST(0, "fast");
const bond_member::rate_t bond_member::rate_t::SLOW(1, "slow");
const bond_member::rate_t
bond_member::rate_t::from_numeric_val(uint8_t numeric)
{
if (0 == numeric)
return (bond_member::rate_t::FAST);
return (bond_member::rate_t::SLOW);
}
bond_member::rate_t::rate_t(int v, const std::string& s)
: enum_base<bond_member::rate_t>(v, s)
{
}
}; // namespace VOM
/*
* fd.io coding-style-patch-verification: ON
*
* Local Variables:
* eval: (c-set-style "mozilla")
* End:
*/

View File

@ -0,0 +1,147 @@
/*
* Copyright (c) 2018 Cisco and/or its affiliates.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __VOM_BOND_MEMBER_H__
#define __VOM_BOND_MEMBER_H__
#include "vom/interface.hpp"
#include <vapi/bond.api.vapi.hpp>
namespace VOM {
/**
* A bond-member. e.g. a bond_member interface
*/
class bond_member
{
public:
/**
* A member interface mode
*/
struct mode_t : enum_base<mode_t>
{
/**
* Active member interface mode
*/
const static mode_t ACTIVE;
/**
* Passive member interface mode
*/
const static mode_t PASSIVE;
/**
* Convert VPP's value of the bond to a mode
*/
static const mode_t from_numeric_val(uint8_t v);
private:
/**
* Private constructor taking the value and the string name
*/
mode_t(int v, const std::string& s);
};
/**
* A member interface rate
*/
struct rate_t : enum_base<rate_t>
{
/**
* Fast member interface rate
*/
const static rate_t FAST;
/**
* SLOW member interface rate
*/
const static rate_t SLOW;
/**
* Convert VPP's value of the bond to a mode
*/
static const rate_t from_numeric_val(uint8_t v);
private:
/**
* Private constructor taking the value and the string name
*/
rate_t(int v, const std::string& s);
};
bond_member(const interface& itf, mode_t mode, rate_t rate);
~bond_member();
bond_member(const bond_member& o);
/**
* convert to VPP
*/
void to_vpp(vapi_payload_bond_enslave& bond_enslave) const;
/**
* set the mode
*/
void set(mode_t mode);
/**
* set the rate
*/
void set(rate_t rate);
/**
* convert to string
*/
std::string to_string(void) const;
/**
* less-than operator
*/
bool operator<(const bond_member& mem_itf) const;
/**
* Get the interface handle
*/
const handle_t hdl(void) const;
/**
* equality operator
*/
bool operator==(const bond_member& i) const;
private:
/**
* Refernece conter lock on the parent
*/
const std::shared_ptr<interface> m_itf;
/**
* passive vs active member
*/
mode_t m_mode;
/**
* slow 90sec. vs. fast 3sec timeout
*/
rate_t m_rate;
};
}
/*
* fd.io coding-style-patch-verification: ON
*
* Local Variables:
* eval: (c-set-style "mozilla")
* End:
*/
#endif

View File

@ -14,6 +14,9 @@
*/
#include "vom/interface.hpp"
#include "vom/bond_group_binding.hpp"
#include "vom/bond_group_binding_cmds.hpp"
#include "vom/bond_interface_cmds.hpp"
#include "vom/interface_cmds.hpp"
#include "vom/interface_factory.hpp"
#include "vom/l3_binding_cmds.hpp"
@ -464,6 +467,9 @@ interface::dump(std::ostream& os)
void
interface::event_handler::handle_populate(const client_db::key_t& key)
{
/*
* dump VPP current states
*/
std::shared_ptr<interface_cmds::vhost_dump_cmd> vcmd =
std::make_shared<interface_cmds::vhost_dump_cmd>();
@ -474,13 +480,10 @@ interface::event_handler::handle_populate(const client_db::key_t& key)
std::shared_ptr<interface> vitf =
interface_factory::new_vhost_user_interface(
vhost_itf_record.get_payload());
VOM_LOG(log_level_t::DEBUG) << "dump: " << vitf->to_string();
VOM_LOG(log_level_t::DEBUG) << " vhost-dump: " << vitf->to_string();
OM::commit(key, *vitf);
}
/*
* dump VPP current states
*/
std::shared_ptr<interface_cmds::dump_cmd> cmd =
std::make_shared<interface_cmds::dump_cmd>();
@ -522,6 +525,60 @@ interface::event_handler::handle_populate(const client_db::key_t& key)
}
}
}
std::shared_ptr<bond_interface_cmds::dump_cmd> bcmd =
std::make_shared<bond_interface_cmds::dump_cmd>();
HW::enqueue(bcmd);
HW::write();
for (auto& bond_itf_record : *bcmd) {
std::shared_ptr<bond_interface> bond_itf =
interface_factory::new_bond_interface(bond_itf_record.get_payload());
VOM_LOG(log_level_t::DEBUG) << " bond-dump:" << bond_itf->to_string();
/*
* Write each of the discovered interfaces into the OM,
* but disable the HW Command q whilst we do, so that no
* commands are sent to VPP
*/
OM::commit(key, *bond_itf);
std::shared_ptr<bond_group_binding_cmds::dump_cmd> scmd =
std::make_shared<bond_group_binding_cmds::dump_cmd>(
bond_group_binding_cmds::dump_cmd(bond_itf->handle()));
HW::enqueue(scmd);
HW::write();
bond_group_binding::enslaved_itf_t enslaved_itfs;
for (auto& slave_itf_record : *scmd) {
bond_member slave_itf = interface_factory::new_bond_member_interface(
slave_itf_record.get_payload());
VOM_LOG(log_level_t::DEBUG) << " slave-dump:" << slave_itf.to_string();
/*
* Write each of the discovered interfaces into the OM,
* but disable the HW Command q whilst we do, so that no
* commands are sent to VPP
*/
// OM::commit(slave_itf->key(), *slave_itf);
enslaved_itfs.insert(slave_itf);
}
if (!enslaved_itfs.empty()) {
bond_group_binding bid(*bond_itf, enslaved_itfs);
/*
* Write each of the discovered interfaces into the OM,
* but disable the HW Command q whilst we do, so that no
* commands are sent to VPP
*/
OM::commit(key, bid);
}
}
}
interface::event_handler::event_handler()

View File

@ -95,6 +95,11 @@ public:
*/
const static type_t VHOST;
/**
* bond interface type
*/
const static type_t BOND;
/**
* Convert VPP's name of the interface to a type
*/

View File

@ -15,6 +15,8 @@
#include <boost/algorithm/string.hpp>
#include "vom/bond_interface.hpp"
#include "vom/bond_member.hpp"
#include "vom/interface_factory.hpp"
#include "vom/sub_interface.hpp"
#include "vom/tap_interface.hpp"
@ -83,7 +85,7 @@ interface_factory::new_interface(const vapi_payload_sw_interface_details& vd)
*/
} else if (interface::type_t::VHOST == type) {
/*
* vhost interfaces already exist in db, look for it using
* vhost interface already exist in db, look for it using
* sw_if_index
*/
sp = interface::find(hdl);
@ -93,6 +95,10 @@ interface_factory::new_interface(const vapi_payload_sw_interface_details& vd)
if (!tag.empty())
sp->set(tag);
}
} else if (interface::type_t::BOND == type) {
sp = bond_interface(name, state, l2_address,
bond_interface::mode_t::UNSPECIFIED)
.singular();
} else {
sp = interface(name, type, state, tag).singular();
sp->set(l2_address);
@ -121,6 +127,40 @@ interface_factory::new_vhost_user_interface(
sp->set(hdl);
return (sp);
}
std::shared_ptr<bond_interface>
interface_factory::new_bond_interface(
const vapi_payload_sw_interface_bond_details& vd)
{
std::shared_ptr<bond_interface> sp;
std::string name = reinterpret_cast<const char*>(vd.interface_name);
handle_t hdl(vd.sw_if_index);
bond_interface::mode_t mode =
bond_interface::mode_t::from_numeric_val(vd.mode);
bond_interface::lb_t lb = bond_interface::lb_t::from_numeric_val(vd.lb);
sp = bond_interface::find(hdl);
if (sp) {
sp->set(mode);
sp->set(lb);
}
return (sp);
}
bond_member
interface_factory::new_bond_member_interface(
const vapi_payload_sw_interface_slave_details& vd)
{
std::shared_ptr<bond_member> sp;
std::string name = reinterpret_cast<const char*>(vd.interface_name);
handle_t hdl(vd.sw_if_index);
bond_member::mode_t mode =
bond_member::mode_t::from_numeric_val(vd.is_passive);
bond_member::rate_t rate =
bond_member::rate_t::from_numeric_val(vd.is_long_timeout);
std::shared_ptr<interface> itf = interface::find(hdl);
bond_member bm(*itf, mode, rate);
return (bm);
}
}; // namespace VOM
/*

View File

@ -18,8 +18,10 @@
#include <vapi/vapi.hpp>
#include "vom/bond_member.hpp"
#include "vom/interface.hpp"
#include <vapi/bond.api.vapi.hpp>
#include <vapi/interface.api.vapi.hpp>
#include <vapi/vhost_user.api.vapi.hpp>
@ -36,6 +38,12 @@ public:
static std::shared_ptr<interface> new_vhost_user_interface(
const vapi_payload_sw_interface_vhost_user_details& vd);
static std::shared_ptr<bond_interface> new_bond_interface(
const vapi_payload_sw_interface_bond_details& vd);
static bond_member new_bond_member_interface(
const vapi_payload_sw_interface_slave_details& vd);
};
};

View File

@ -14,7 +14,6 @@
*/
#include "vom/interface.hpp"
namespace VOM {
/*
* constants and enums
@ -28,6 +27,7 @@ const interface::type_t interface::type_t::LOOPBACK(5, "LOOPBACK");
const interface::type_t interface::type_t::LOCAL(6, "LOCAL");
const interface::type_t interface::type_t::TAP(7, "TAP");
const interface::type_t interface::type_t::VHOST(8, "VHOST");
const interface::type_t interface::type_t::BOND(9, "Bond");
const interface::oper_state_t interface::oper_state_t::DOWN(0, "down");
const interface::oper_state_t interface::oper_state_t::UP(1, "up");
@ -41,6 +41,8 @@ interface::type_t::from_string(const std::string& str)
if ((str.find("Virtual") != std::string::npos) ||
(str.find("vhost") != std::string::npos)) {
return interface::type_t::VHOST;
} else if (str.find("Bond") != std::string::npos) {
return interface::type_t::BOND;
} else if (str.find("Ethernet") != std::string::npos) {
return interface::type_t::ETHERNET;
} else if (str.find("vxlan") != std::string::npos) {

View File

@ -52,6 +52,12 @@ enum class dependency_t
*/
INTERFACE,
/**
* bond group binding is after interfaces but before
* anything else
*/
BOND_BINDING,
/**
* Tunnel or virtual interfaces next
*/

View File

@ -20,6 +20,9 @@
#include "vom/om.hpp"
#include "vom/interface.hpp"
#include "vom/interface_cmds.hpp"
#include "vom/bond_interface_cmds.hpp"
#include "vom/bond_group_binding.hpp"
#include "vom/bond_group_binding_cmds.hpp"
#include "vom/l2_binding.hpp"
#include "vom/l2_binding_cmds.hpp"
#include "vom/l3_binding.hpp"
@ -178,6 +181,10 @@ public:
{
rc = handle_derived<interface_cmds::vhost_create_cmd>(f_exp, f_act);
}
else if (typeid(*f_exp) == typeid(bond_interface_cmds::create_cmd))
{
rc = handle_derived<bond_interface_cmds::create_cmd>(f_exp, f_act);
}
else if (typeid(*f_exp) == typeid(interface_cmds::loopback_delete_cmd))
{
rc = handle_derived<interface_cmds::loopback_delete_cmd>(f_exp, f_act);
@ -190,6 +197,10 @@ public:
{
rc = handle_derived<interface_cmds::vhost_delete_cmd>(f_exp, f_act);
}
else if (typeid(*f_exp) == typeid(bond_interface_cmds::delete_cmd))
{
rc = handle_derived<bond_interface_cmds::delete_cmd>(f_exp, f_act);
}
else if (typeid(*f_exp) == typeid(interface_cmds::state_change_cmd))
{
rc = handle_derived<interface_cmds::state_change_cmd>(f_exp, f_act);
@ -206,6 +217,14 @@ public:
{
rc = handle_derived<interface_cmds::set_tag>(f_exp, f_act);
}
else if (typeid(*f_exp) == typeid(bond_group_binding_cmds::bind_cmd))
{
rc = handle_derived<bond_group_binding_cmds::bind_cmd>(f_exp, f_act);
}
else if (typeid(*f_exp) == typeid(bond_group_binding_cmds::unbind_cmd))
{
rc = handle_derived<bond_group_binding_cmds::unbind_cmd>(f_exp, f_act);
}
else if (typeid(*f_exp) == typeid(route_domain_cmds::create_cmd))
{
rc = handle_derived<route_domain_cmds::create_cmd>(f_exp, f_act);
@ -770,6 +789,80 @@ BOOST_AUTO_TEST_CASE(test_bvi) {
TRY_CHECK(OM::remove(graham));
}
BOOST_AUTO_TEST_CASE(test_bond) {
VppInit vi;
const std::string cb = "CarolBerg";
rc_t rc = rc_t::OK;
/*
* creates the interfaces
*/
std::string itf1_name = "afpacket1";
interface itf1(itf1_name,
interface::type_t::AFPACKET,
interface::admin_state_t::UP);
HW::item<handle_t> hw_ifh(2, rc_t::OK);
ADD_EXPECT(interface_cmds::af_packet_create_cmd(hw_ifh, itf1_name));
HW::item<interface::admin_state_t> hw_as_up(interface::admin_state_t::UP, rc_t::OK);
ADD_EXPECT(interface_cmds::state_change_cmd(hw_as_up, hw_ifh));
TRY_CHECK_RC(OM::write(cb, itf1));
std::string itf2_name = "afpacket2";
interface itf2(itf2_name,
interface::type_t::AFPACKET,
interface::admin_state_t::UP);
HW::item<handle_t> hw_ifh2(4, rc_t::OK);
ADD_EXPECT(interface_cmds::af_packet_create_cmd(hw_ifh2, itf2_name));
ADD_EXPECT(interface_cmds::state_change_cmd(hw_as_up, hw_ifh2));
TRY_CHECK_RC(OM::write(cb, itf2));
std::string bond_name = "bond";
bond_interface bond_itf(bond_name, interface::admin_state_t::UP,
bond_interface::mode_t::LACP);
HW::item<handle_t> hw_ifh3(6, rc_t::OK);
ADD_EXPECT(bond_interface_cmds::create_cmd(hw_ifh3, bond_name,
bond_interface::mode_t::LACP, bond_interface::lb_t::L2, l2_address_t::ZERO));
ADD_EXPECT(interface_cmds::state_change_cmd(hw_as_up, hw_ifh3));
TRY_CHECK_RC(OM::write(cb, bond_itf));
bond_member *bm1 = new bond_member(itf1, bond_member::mode_t::ACTIVE,
bond_member::rate_t::SLOW);
bond_member *bm2 = new bond_member(itf2, bond_member::mode_t::ACTIVE,
bond_member::rate_t::SLOW);
bond_group_binding *bgb = new bond_group_binding(bond_itf, {*bm1, *bm2});
HW::item<bool> bond_hw_bind(true, rc_t::OK);
ADD_EXPECT(bond_group_binding_cmds::bind_cmd(bond_hw_bind, hw_ifh3.data(), *bm1));
ADD_EXPECT(bond_group_binding_cmds::bind_cmd(bond_hw_bind, hw_ifh3.data(), *bm2));
TRY_CHECK_RC(OM::write(cb, *bgb));
delete bgb;
delete bm2;
delete bm1;
STRICT_ORDER_OFF();
HW::item<interface::admin_state_t> hw_as_down(interface::admin_state_t::DOWN, rc_t::OK);
ADD_EXPECT(bond_group_binding_cmds::unbind_cmd(bond_hw_bind, hw_ifh.data()));
ADD_EXPECT(bond_group_binding_cmds::unbind_cmd(bond_hw_bind, hw_ifh2.data()));
ADD_EXPECT(interface_cmds::state_change_cmd(hw_as_down, hw_ifh2));
ADD_EXPECT(interface_cmds::af_packet_delete_cmd(hw_ifh2, itf2_name));
ADD_EXPECT(interface_cmds::state_change_cmd(hw_as_down, hw_ifh3));
ADD_EXPECT(bond_interface_cmds::delete_cmd(hw_ifh3));
ADD_EXPECT(interface_cmds::state_change_cmd(hw_as_down, hw_ifh));
ADD_EXPECT(interface_cmds::af_packet_delete_cmd(hw_ifh, itf1_name));
TRY_CHECK(OM::remove(cb));
}
BOOST_AUTO_TEST_CASE(test_bridge) {
VppInit vi;
const std::string franz = "FranzKafka";