VOM: interface RD update reconfigures L3 bindings

Change-Id: I273e1ea28c3c146e4a88d031c790c1cc56dccf00
Signed-off-by: Neale Ranns <neale.ranns@cisco.com>
diff --git a/src/vpp-api/vom/interface.cpp b/src/vpp-api/vom/interface.cpp
index 8e27013..1c9f20d 100644
--- a/src/vpp-api/vom/interface.cpp
+++ b/src/vpp-api/vom/interface.cpp
@@ -149,7 +149,7 @@
 void
 interface::sweep()
 {
-  if (m_table_id) {
+  if (m_table_id && (m_table_id.data() != route::DEFAULT_TABLE)) {
     m_table_id.data() = route::DEFAULT_TABLE;
     HW::enqueue(
       new interface_cmds::set_table_cmd(m_table_id, l3_proto_t::IPV4, m_hdl));
@@ -181,7 +181,7 @@
     HW::enqueue(new interface_cmds::state_change_cmd(m_state, m_hdl));
   }
 
-  if (m_table_id) {
+  if (m_table_id && (m_table_id.data() != route::DEFAULT_TABLE)) {
     HW::enqueue(
       new interface_cmds::set_table_cmd(m_table_id, l3_proto_t::IPV4, m_hdl));
     HW::enqueue(
@@ -267,32 +267,58 @@
 interface::update(const interface& desired)
 {
   /*
- * the desired state is always that the interface should be created
- */
+   * the desired state is always that the interface should be created
+   */
   if (rc_t::OK != m_hdl.rc()) {
     std::queue<cmd*> cmds;
     HW::enqueue(mk_create_cmd(cmds));
   }
 
   /*
- * change the interface state to that which is deisred
- */
+   * change the interface state to that which is deisred
+   */
   if (m_state.update(desired.m_state)) {
     HW::enqueue(new interface_cmds::state_change_cmd(m_state, m_hdl));
   }
 
   /*
- * change the interface state to that which is deisred
- */
+   * change the interface state to that which is deisred
+   */
   if (m_l2_address.update(desired.m_l2_address)) {
     HW::enqueue(new interface_cmds::set_mac_cmd(m_l2_address, m_hdl));
   }
 
   /*
- * If the interface is mapped into a route domain, set VPP's
- * table ID
- */
-  if (!m_table_id && m_rd) {
+   * If the interface is mapped into a route domain, set VPP's
+   * table ID
+   */
+  if (m_rd != desired.m_rd) {
+    /*
+     * changing route domains. need to remove all L3 bindings, swap the table
+     * then reapply the bindings.
+     */
+    auto it = l3_binding::cbegin();
+
+    while (it != l3_binding::cend()) {
+      if (it->second.lock()->itf().key() == key())
+        it->second.lock()->sweep();
+      ++it;
+    }
+    m_rd = desired.m_rd;
+    m_table_id.update(m_rd ? m_rd->table_id() : route::DEFAULT_TABLE);
+    HW::enqueue(
+      new interface_cmds::set_table_cmd(m_table_id, l3_proto_t::IPV4, m_hdl));
+    HW::enqueue(
+      new interface_cmds::set_table_cmd(m_table_id, l3_proto_t::IPV6, m_hdl));
+    HW::write();
+
+    it = l3_binding::cbegin();
+    while (it != l3_binding::cend()) {
+      if (it->second.lock()->itf().key() == key())
+        it->second.lock()->replay(); //(*it->second.lock());
+      ++it;
+    }
+  } else if (!m_table_id && m_rd) {
     HW::enqueue(
       new interface_cmds::set_table_cmd(m_table_id, l3_proto_t::IPV4, m_hdl));
     HW::enqueue(
diff --git a/src/vpp-api/vom/interface.hpp b/src/vpp-api/vom/interface.hpp
index 6fdd1e1..f11e359 100644
--- a/src/vpp-api/vom/interface.hpp
+++ b/src/vpp-api/vom/interface.hpp
@@ -540,9 +540,9 @@
 
   /**
    * shared pointer to the routeDoamin the interface is in.
-   * NULL is not mapped  - i.e. in eht default table
+   * NULL is not mapped  - i.e. in the default table
    */
-  const std::shared_ptr<route_domain> m_rd;
+  std::shared_ptr<route_domain> m_rd;
 
   /**
    * The state of the interface
diff --git a/src/vpp-api/vom/l3_binding.cpp b/src/vpp-api/vom/l3_binding.cpp
index 8bc7c7c..8f90d45 100644
--- a/src/vpp-api/vom/l3_binding.cpp
+++ b/src/vpp-api/vom/l3_binding.cpp
@@ -71,6 +71,24 @@
   return (m_pfx);
 }
 
+const interface&
+l3_binding::itf() const
+{
+  return (*m_itf);
+}
+
+l3_binding::const_iterator_t
+l3_binding::cbegin()
+{
+  return m_db.cbegin();
+}
+
+l3_binding::const_iterator_t
+l3_binding::cend()
+{
+  return m_db.cend();
+}
+
 std::string
 l3_binding::to_string() const
 {
@@ -85,8 +103,10 @@
 l3_binding::update(const l3_binding& desired)
 {
   /*
- * the desired state is always that the interface should be created
- */
+   * no updates for the binding. chaning the interface or the prefix is a change
+   * to the
+   * key, hence a new object
+   */
   if (!m_binding) {
     HW::enqueue(
       new l3_binding_cmds::bind_cmd(m_binding, m_itf->handle(), m_pfx));
diff --git a/src/vpp-api/vom/l3_binding.hpp b/src/vpp-api/vom/l3_binding.hpp
index d94ff69..4403760 100644
--- a/src/vpp-api/vom/l3_binding.hpp
+++ b/src/vpp-api/vom/l3_binding.hpp
@@ -46,6 +46,19 @@
   ~l3_binding();
 
   /**
+   * The key type for l3_bindings
+   */
+  typedef std::pair<interface::key_type, route::prefix_t> key_type_t;
+
+  /**
+   * The iterator type
+   */
+  typedef singular_db<key_type_t, l3_binding>::const_iterator const_iterator_t;
+
+  static const_iterator_t cbegin();
+  static const_iterator_t cend();
+
+  /**
    * Return the 'singular instance' of the L3-Config that matches this
    * object
    */
@@ -57,21 +70,21 @@
   std::string to_string() const;
 
   /**
-   * Return the prefix associated with this L3config
+   * Return the prefix associated with this L3 binding
    */
   const route::prefix_t& prefix() const;
 
   /**
+   * Return the interface associated with this L3 binding
+   */
+  const interface& itf() const;
+
+  /**
    * Dump all l3_bindings into the stream provided
    */
   static void dump(std::ostream& os);
 
   /**
-   * The key type for l3_bindings
-   */
-  typedef std::pair<interface::key_type, route::prefix_t> key_type_t;
-
-  /**
    * Find an singular instance in the DB for the interface passed
    */
   static std::deque<std::shared_ptr<l3_binding>> find(const interface& i);
@@ -142,6 +155,8 @@
    */
   void replay(void);
 
+  friend class interface;
+
   /**
    * A reference counting pointer the interface that this L3 layer
    * represents. By holding the reference here, we can guarantee that
diff --git a/test/ext/vom_test.cpp b/test/ext/vom_test.cpp
index 7882083..612a2d3 100644
--- a/test/ext/vom_test.cpp
+++ b/test/ext/vom_test.cpp
@@ -1467,4 +1467,91 @@
     HW::dequeue(itf);
 }
 
+BOOST_AUTO_TEST_CASE(test_interface_route_domain_change) {
+    VppInit vi;
+    const std::string rene = "ReneGoscinny";
+    rc_t rc = rc_t::OK;
+
+    /*
+     * Create an interface with two IP addresses
+     */
+    std::string itf1_name = "host1";
+    interface itf1(itf1_name,
+                   interface::type_t::AFPACKET,
+                   interface::admin_state_t::UP);
+    HW::item<handle_t> hw_ifh1(2, rc_t::OK);
+    HW::item<interface::admin_state_t> hw_as_up(interface::admin_state_t::UP, rc_t::OK);
+    HW::item<interface::admin_state_t> hw_as_down(interface::admin_state_t::DOWN, rc_t::OK);
+    ADD_EXPECT(interface_cmds::af_packet_create_cmd(hw_ifh1, itf1_name));
+    ADD_EXPECT(interface_cmds::state_change_cmd(hw_as_up, hw_ifh1));
+    TRY_CHECK_RC(OM::write(rene, itf1));
+
+    route::prefix_t pfx_10("10.10.10.10", 24);
+    l3_binding *l3_1 = new l3_binding(itf1, pfx_10);
+    HW::item<bool> hw_l3_bind1(true, rc_t::OK);
+    HW::item<bool> hw_l3_unbind1(false, rc_t::OK);
+    ADD_EXPECT(l3_binding_cmds::bind_cmd(hw_l3_bind1, hw_ifh1.data(), pfx_10));
+    TRY_CHECK_RC(OM::write(rene, *l3_1));
+
+    route::prefix_t pfx_11("10.10.11.11", 24);
+    l3_binding *l3_2 = new l3_binding(itf1, pfx_11);
+    HW::item<bool> hw_l3_bind2(true, rc_t::OK);
+    HW::item<bool> hw_l3_unbind2(false, rc_t::OK);
+    ADD_EXPECT(l3_binding_cmds::bind_cmd(hw_l3_bind2, hw_ifh1.data(), pfx_11));
+    TRY_CHECK_RC(OM::write(rene, *l3_2));
+
+    route_domain rd(1);
+    HW::item<bool> hw_rd_create(true, rc_t::OK);
+    HW::item<bool> hw_rd_delete(false, rc_t::OK);
+    HW::item<route::table_id_t> hw_rd_bind(1, rc_t::OK);
+    HW::item<route::table_id_t> hw_rd_unbind(route::DEFAULT_TABLE, rc_t::OK);
+    ADD_EXPECT(route_domain_cmds::create_cmd(hw_rd_create, l3_proto_t::IPV4, 1));
+    ADD_EXPECT(route_domain_cmds::create_cmd(hw_rd_create, l3_proto_t::IPV6, 1));
+    TRY_CHECK_RC(OM::write(rene, rd));
+
+    /*
+     * update the interface to change to a new route-domain
+     * expect that the l3-bindings are removed and readded.
+     */
+    interface *itf2 = new interface(itf1_name,
+                                    interface::type_t::AFPACKET,
+                                    interface::admin_state_t::UP,
+                                    rd);
+    ADD_EXPECT(l3_binding_cmds::unbind_cmd(hw_l3_unbind1, hw_ifh1.data(), pfx_10));
+    ADD_EXPECT(l3_binding_cmds::unbind_cmd(hw_l3_unbind2, hw_ifh1.data(), pfx_11));
+    ADD_EXPECT(interface_cmds::set_table_cmd(hw_rd_bind, l3_proto_t::IPV4, hw_ifh1));
+    ADD_EXPECT(interface_cmds::set_table_cmd(hw_rd_bind, l3_proto_t::IPV6, hw_ifh1));
+    ADD_EXPECT(l3_binding_cmds::bind_cmd(hw_l3_bind1, hw_ifh1.data(), pfx_10));
+    ADD_EXPECT(l3_binding_cmds::bind_cmd(hw_l3_bind2, hw_ifh1.data(), pfx_11));
+    TRY_CHECK_RC(OM::write(rene, *itf2));
+
+    /*
+     * mve the interface back to the default route-domain
+     */
+    interface itf3(itf1_name,
+                   interface::type_t::AFPACKET,
+                   interface::admin_state_t::UP);
+    ADD_EXPECT(l3_binding_cmds::unbind_cmd(hw_l3_unbind1, hw_ifh1.data(), pfx_10));
+    ADD_EXPECT(l3_binding_cmds::unbind_cmd(hw_l3_unbind2, hw_ifh1.data(), pfx_11));
+    ADD_EXPECT(interface_cmds::set_table_cmd(hw_rd_unbind, l3_proto_t::IPV4, hw_ifh1));
+    ADD_EXPECT(interface_cmds::set_table_cmd(hw_rd_unbind, l3_proto_t::IPV6, hw_ifh1));
+    ADD_EXPECT(l3_binding_cmds::bind_cmd(hw_l3_bind1, hw_ifh1.data(), pfx_10));
+    ADD_EXPECT(l3_binding_cmds::bind_cmd(hw_l3_bind2, hw_ifh1.data(), pfx_11));
+    TRY_CHECK_RC(OM::write(rene, itf3));
+
+    delete l3_1;
+    delete l3_2;
+    delete itf2;
+
+    STRICT_ORDER_OFF();
+    ADD_EXPECT(l3_binding_cmds::unbind_cmd(hw_l3_unbind1, hw_ifh1.data(), pfx_10));
+    ADD_EXPECT(l3_binding_cmds::unbind_cmd(hw_l3_unbind2, hw_ifh1.data(), pfx_11));
+    ADD_EXPECT(interface_cmds::state_change_cmd(hw_as_down, hw_ifh1));
+    ADD_EXPECT(interface_cmds::af_packet_delete_cmd(hw_ifh1, itf1_name));
+    ADD_EXPECT(route_domain_cmds::delete_cmd(hw_rd_delete, l3_proto_t::IPV4, 1));
+    ADD_EXPECT(route_domain_cmds::delete_cmd(hw_rd_delete, l3_proto_t::IPV6, 1));
+
+    TRY_CHECK(OM::remove(rene));
+}
+
 BOOST_AUTO_TEST_SUITE_END()