mgmt: added FIB manager support for insert, delete, and remove-nexthop verbs

Refactor option decoding, verb authorization (placeholder),
and response sending into single location.
Added unit tests for ManagerBase.

refs: #1223

Change-Id: I731be586ee8f06defb00fcadc6010409560891a1
diff --git a/tests/mgmt/fib-manager.cpp b/tests/mgmt/fib-manager.cpp
index b07d11b..25c38b3 100644
--- a/tests/mgmt/fib-manager.cpp
+++ b/tests/mgmt/fib-manager.cpp
@@ -5,7 +5,6 @@
  */
 
 #include "mgmt/fib-manager.hpp"
-#include "fw/forwarder.hpp"
 #include "table/fib.hpp"
 #include "table/fib-nexthop.hpp"
 #include "face/face.hpp"
@@ -52,6 +51,7 @@
 
   void
   validateControlResponse(const Data& response,
+                          const Name& expectedName,
                           uint32_t expectedCode,
                           const std::string& expectedText)
   {
@@ -66,8 +66,9 @@
                   << " code: " << control.getCode()
                   << " text: " << control.getText());
 
-    BOOST_REQUIRE(control.getCode() == expectedCode);
-    BOOST_REQUIRE(control.getText() == expectedText);
+    BOOST_CHECK_EQUAL(response.getName(), expectedName);
+    BOOST_CHECK_EQUAL(control.getCode(), expectedCode);
+    BOOST_CHECK_EQUAL(control.getText(), expectedText);
   }
 
   bool
@@ -101,7 +102,7 @@
 bool
 addedNextHopWithCost(const Fib& fib, const Name& prefix, size_t oldSize, uint32_t cost)
 {
-  shared_ptr<fib::Entry> entry = fib.findLongestPrefixMatch(prefix);
+  shared_ptr<fib::Entry> entry = fib.findExactMatch(prefix);
 
   if (static_cast<bool>(entry))
     {
@@ -112,42 +113,44 @@
   return false;
 }
 
-BOOST_AUTO_TEST_CASE(TestFireInterestFilter)
+BOOST_FIXTURE_TEST_CASE(TestFireInterestFilter, FibManagerFixture)
 {
-  FibManagerFixture fixture;
   shared_ptr<InternalFace> face(make_shared<InternalFace>());
   Fib fib;
   FibManager manager(fib,
-                     bind(&FibManagerFixture::getFace,
-                          &fixture, _1),
-                          face);
-
-  face->onReceiveData +=
-    bind(&FibManagerFixture::validateControlResponse, &fixture,  _1, 400, "Malformed command");
+                     bind(&FibManagerFixture::getFace, this, _1),
+                     face);
 
   Interest command("/localhost/nfd/fib");
+
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse, this,  _1,
+         command.getName(), 400, "Malformed command");
+
   face->sendInterest(command);
+
+  BOOST_REQUIRE(didCallbackFire());
 }
 
-BOOST_AUTO_TEST_CASE(MalformedCommmand)
+BOOST_FIXTURE_TEST_CASE(MalformedCommmand, FibManagerFixture)
 {
-  FibManagerFixture fixture;
   shared_ptr<InternalFace> face(make_shared<InternalFace>());
   Fib fib;
   FibManager manager(fib,
-                     bind(&FibManagerFixture::getFace,
-                          &fixture, _1),
+                     bind(&FibManagerFixture::getFace, this, _1),
                           face);
 
-  BOOST_REQUIRE(fixture.didCallbackFire() == false);
-
-  face->onReceiveData +=
-    bind(&FibManagerFixture::validateControlResponse, &fixture, _1, 400, "Malformed command");
+  BOOST_REQUIRE(didCallbackFire() == false);
 
   Interest command("/localhost/nfd/fib");
+
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse, this, _1,
+         command.getName(), 400, "Malformed command");
+
   manager.onFibRequest(command);
 
-  BOOST_REQUIRE(fixture.didCallbackFire());
+  BOOST_REQUIRE(didCallbackFire());
 }
 
 BOOST_FIXTURE_TEST_CASE(UnsupportedVerb, FibManagerFixture)
@@ -155,13 +158,9 @@
   shared_ptr<InternalFace> face(make_shared<InternalFace>());
   Fib fib;
   FibManager manager(fib,
-                     bind(&FibManagerFixture::getFace,
-                          this, _1),
+                     bind(&FibManagerFixture::getFace, this, _1),
                           face);
 
-  face->onReceiveData +=
-    bind(&FibManagerFixture::validateControlResponse, this, _1, 501, "Unsupported command");
-
   ndn::FibManagementOptions options;
   options.setName("/hello");
   options.setFaceId(1);
@@ -173,6 +172,10 @@
   commandName.append("unsupported");
   commandName.append(encodedOptions);
 
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse, this, _1,
+         commandName, 501, "Unsupported command");
+
   Interest command(commandName);
   manager.onFibRequest(command);
 
@@ -186,13 +189,8 @@
   shared_ptr<InternalFace> face(make_shared<InternalFace>());
   Fib fib;
   FibManager manager(fib,
-                     bind(&FibManagerFixture::getFace,
-                          this, _1),
+                     bind(&FibManagerFixture::getFace, this, _1),
                      face);
-  face->onReceiveData +=
-    bind(&FibManagerFixture::validateControlResponse, this, _1, 200, "OK");
-  /// \todo enable once sig checking implement
-    // bind(&FibManagerFixture::validateControlResponse, this, _1, 401, "SIGREG");
 
   ndn::FibManagementOptions options;
   options.setName("/hello");
@@ -205,11 +203,17 @@
   commandName.append("add-nexthop");
   commandName.append(encodedOptions);
 
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse, this, _1,
+         commandName, 404, "Prefix not found");
+  /// \todo enable once sig checking implemented
+    // bind(&FibManagerFixture::validateControlResponse, this, _1, 401, "Signature required");
+
   Interest command(commandName);
   manager.onFibRequest(command);
 
   BOOST_REQUIRE(didCallbackFire());
-  BOOST_REQUIRE(addedNextHopWithCost(fib, "/hello", 0, 101));
+  BOOST_REQUIRE(!addedNextHopWithCost(fib, "/hello", 0, 101));
 }
 
 BOOST_FIXTURE_TEST_CASE(UnauthorizedCommand, FibManagerFixture)
@@ -219,13 +223,8 @@
   shared_ptr<InternalFace> face(make_shared<InternalFace>());
   Fib fib;
   FibManager manager(fib,
-                     bind(&FibManagerFixture::getFace,
-                          this, _1),
+                     bind(&FibManagerFixture::getFace, this, _1),
                      face);
-  face->onReceiveData +=
-    bind(&FibManagerFixture::validateControlResponse, this, _1, 200, "OK");
-  /// \todo enable once sig checking implement
-    // bind(&FibManagerFixture::validateControlResponse, this, _1, 403, "Unauthorized command");
 
   ndn::FibManagementOptions options;
   options.setName("/hello");
@@ -238,11 +237,17 @@
   commandName.append("add-nexthop");
   commandName.append(encodedOptions);
 
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse, this, _1,
+         commandName, 404, "Prefix not found");
+  /// \todo enable once sig checking implemented
+    // bind(&FibManagerFixture::validateControlResponse, this, _1, 403, "Unauthorized command");
+
   Interest command(commandName);
   manager.onFibRequest(command);
 
   BOOST_REQUIRE(didCallbackFire());
-  BOOST_REQUIRE(addedNextHopWithCost(fib, "/hello", 0, 101));
+  BOOST_REQUIRE(!addedNextHopWithCost(fib, "/hello", 0, 101));
 }
 
 BOOST_FIXTURE_TEST_CASE(BadOptionParse, FibManagerFixture)
@@ -252,17 +257,17 @@
   shared_ptr<InternalFace> face(make_shared<InternalFace>());
   Fib fib;
   FibManager manager(fib,
-                     bind(&FibManagerFixture::getFace,
-                          this, _1),
+                     bind(&FibManagerFixture::getFace, this, _1),
                      face);
 
-  face->onReceiveData +=
-    bind(&FibManagerFixture::validateControlResponse, this, _1, 400, "Malformed command");
-
   Name commandName("/localhost/nfd/fib");
   commandName.append("add-nexthop");
   commandName.append("NotReallyOptions");
 
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse, this, _1,
+         commandName, 400, "Malformed command");
+
   Interest command(commandName);
   manager.onFibRequest(command);
 
@@ -276,13 +281,9 @@
   shared_ptr<InternalFace> face(make_shared<InternalFace>());
   Fib fib;
   FibManager manager(fib,
-                     bind(&FibManagerFixture::getFace,
-                          this, _1),
+                     bind(&FibManagerFixture::getFace, this, _1),
                      face);
 
-  face->onReceiveData +=
-    bind(&FibManagerFixture::validateControlResponse, this, _1, 400, "Malformed command");
-
   ndn::FibManagementOptions options;
   options.setName("/hello");
   options.setFaceId(1000);
@@ -294,6 +295,10 @@
   commandName.append("add-nexthop");
   commandName.append(encodedOptions);
 
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse, this, _1,
+         commandName, 404, "Face not found");
+
   Interest command(commandName);
   manager.onFibRequest(command);
 
@@ -308,11 +313,8 @@
   shared_ptr<InternalFace> face(make_shared<InternalFace>());
   Fib fib;
   FibManager manager(fib,
-                     bind(&FibManagerFixture::getFace,
-                          this, _1),
+                     bind(&FibManagerFixture::getFace, this, _1),
                           face);
-  face->onReceiveData +=
-    bind(&FibManagerFixture::validateControlResponse, this, _1, 200, "OK");
 
   ndn::FibManagementOptions options;
   options.setName("/hello");
@@ -325,6 +327,12 @@
   commandName.append("add-nexthop");
   commandName.append(encodedOptions);
 
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse, this, _1,
+         commandName, 200, "OK");
+
+  fib.insert("/hello");
+
   Interest command(commandName);
   manager.onFibRequest(command);
 
@@ -335,26 +343,21 @@
 BOOST_FIXTURE_TEST_CASE(AddNextHopVerbAddToExisting, FibManagerFixture)
 {
   addFace(make_shared<DummyFace>());
-  addFace(make_shared<DummyFace>());
 
   shared_ptr<InternalFace> face(make_shared<InternalFace>());
   Fib fib;
   FibManager manager(fib,
-                     bind(&FibManagerFixture::getFace,
-                          this, _1),
+                     bind(&FibManagerFixture::getFace, this, _1),
                           face);
-  face->onReceiveData +=
-    bind(&FibManagerFixture::validateControlResponse, this, _1, 200, "OK");
 
-  // Add faces with cost == FaceID for the name /hello
-  // This test assumes:
-  //   FaceIDs are -1 because we don't add them to a forwarder
+  fib.insert("/hello");
 
   for (int i = 1; i <= 2; i++)
     {
+
       ndn::FibManagementOptions options;
       options.setName("/hello");
-      options.setFaceId(i);
+      options.setFaceId(1);
       options.setCost(100 + i);
 
       Block encodedOptions(options.wireEncode());
@@ -363,25 +366,31 @@
       commandName.append("add-nexthop");
       commandName.append(encodedOptions);
 
+      face->onReceiveData +=
+        bind(&FibManagerFixture::validateControlResponse, this, _1,
+             commandName, 200, "OK");
+
       Interest command(commandName);
       manager.onFibRequest(command);
       BOOST_REQUIRE(didCallbackFire());
       resetCallbackFired();
 
-      shared_ptr<fib::Entry> entry = fib.findLongestPrefixMatch("/hello");
+      shared_ptr<fib::Entry> entry = fib.findExactMatch("/hello");
 
       if (static_cast<bool>(entry))
         {
           const fib::NextHopList& hops = entry->getNextHops();
-          for (int j = 1; j <= i; j++)
-            {
-              BOOST_REQUIRE(addedNextHopWithCost(fib, "/hello", i - 1, 100 + j));
-            }
+          BOOST_REQUIRE(hops.size() == 1);
+          BOOST_REQUIRE(std::find_if(hops.begin(), hops.end(),
+                                     bind(&foundNextHop, -1, 100 + i, _1)) != hops.end());
+
         }
       else
         {
           BOOST_FAIL("Failed to find expected fib entry");
         }
+
+      face->onReceiveData.clear();
     }
 }
 
@@ -396,8 +405,8 @@
                      bind(&FibManagerFixture::getFace,
                           this, _1),
                           face);
-  face->onReceiveData +=
-    bind(&FibManagerFixture::validateControlResponse, this, _1, 200, "OK");
+
+  fib.insert("/hello");
 
   ndn::FibManagementOptions options;
   options.setName("/hello");
@@ -412,12 +421,19 @@
     commandName.append("add-nexthop");
     commandName.append(encodedOptions);
 
+    face->onReceiveData +=
+      bind(&FibManagerFixture::validateControlResponse, this, _1,
+           commandName, 200, "OK");
+
     Interest command(commandName);
     manager.onFibRequest(command);
+
     BOOST_REQUIRE(didCallbackFire());
-    resetCallbackFired();
   }
 
+  resetCallbackFired();
+  face->onReceiveData.clear();
+
   {
     options.setCost(102);
 
@@ -427,12 +443,17 @@
     commandName.append("add-nexthop");
     commandName.append(encodedOptions);
 
+    face->onReceiveData +=
+      bind(&FibManagerFixture::validateControlResponse, this, _1,
+           commandName, 200, "OK");
+
     Interest command(commandName);
     manager.onFibRequest(command);
+
     BOOST_REQUIRE(didCallbackFire());
   }
 
-  shared_ptr<fib::Entry> entry = fib.findLongestPrefixMatch("/hello");
+  shared_ptr<fib::Entry> entry = fib.findExactMatch("/hello");
 
   // Add faces with cost == FaceID for the name /hello
   // This test assumes:
@@ -451,6 +472,286 @@
     }
 }
 
+BOOST_FIXTURE_TEST_CASE(Insert, FibManagerFixture)
+{
+  shared_ptr<InternalFace> face(make_shared<InternalFace>());
+  Fib fib;
+  FibManager manager(fib,
+                     bind(&FibManagerFixture::getFace, this, _1),
+                     face);
+
+  {
+    ndn::FibManagementOptions options;
+    options.setName("/hello");
+
+    Block encodedOptions(options.wireEncode());
+
+    Name commandName("/localhost/nfd/fib");
+    commandName.append("insert");
+    commandName.append(encodedOptions);
+
+    face->onReceiveData +=
+      bind(&FibManagerFixture::validateControlResponse, this, _1,
+           commandName, 200, "OK");
+
+    Interest command(commandName);
+    manager.onFibRequest(command);
+  }
+
+  BOOST_REQUIRE(didCallbackFire());
+
+  shared_ptr<fib::Entry> entry = fib.findExactMatch("/hello");
+  if (static_cast<bool>(entry))
+    {
+      const fib::NextHopList& hops = entry->getNextHops();
+      BOOST_CHECK_EQUAL(hops.size(), 0);
+    }
+
+  resetCallbackFired();
+
+  {
+    ndn::FibManagementOptions options;
+    options.setName("/hello");
+
+    Block encodedOptions(options.wireEncode());
+
+    Name commandName("/localhost/nfd/fib");
+    commandName.append("insert");
+    commandName.append(encodedOptions);
+
+    face->onReceiveData +=
+      bind(&FibManagerFixture::validateControlResponse, this, _1,
+           commandName, 200, "OK");
+
+    Interest command(commandName);
+    manager.onFibRequest(command);
+  }
+
+  BOOST_REQUIRE(didCallbackFire());
+
+  entry = fib.findExactMatch("/hello");
+  if (static_cast<bool>(entry))
+    {
+      const fib::NextHopList& hops = entry->getNextHops();
+      BOOST_CHECK_EQUAL(hops.size(), 0);
+    }
+
+}
+
+void
+testRemove(FibManagerFixture* fixture,
+           FibManager& manager,
+           Fib& fib,
+           shared_ptr<Face> face,
+           const Name& target)
+{
+  ndn::FibManagementOptions options;
+  options.setName(target);
+
+  Block encodedOptions(options.wireEncode());
+
+  Name commandName("/localhost/nfd/fib");
+  commandName.append("delete");
+  commandName.append(encodedOptions);
+
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse, fixture, _1,
+         commandName, 200, "OK");
+
+  Interest command(commandName);
+  manager.onFibRequest(command);
+
+  BOOST_REQUIRE(fixture->didCallbackFire());
+
+  if (static_cast<bool>(fib.findExactMatch(target)))
+    {
+      BOOST_FAIL("Found \"removed\" prefix");
+    }
+  face->onReceiveData.clear();
+}
+
+BOOST_FIXTURE_TEST_CASE(Delete, FibManagerFixture)
+{
+  shared_ptr<InternalFace> face(make_shared<InternalFace>());
+  Fib fib;
+  FibManager manager(fib,
+                     bind(&FibManagerFixture::getFace, this, _1),
+                     face);
+
+  fib.insert("/a");
+  fib.insert("/a/b");
+  fib.insert("/a/b/c");
+
+  testRemove(this, manager, fib, face, "/");
+
+  if (!static_cast<bool>(fib.findExactMatch("/a")) ||
+      !static_cast<bool>(fib.findExactMatch("/a/b")) ||
+      !static_cast<bool>(fib.findExactMatch("/a/b/c")))
+    {
+      BOOST_FAIL("Removed incorrect entry");
+    }
+
+  testRemove(this, manager, fib, face, "/a/b");
+
+  if (!static_cast<bool>(fib.findExactMatch("/a")) ||
+      !static_cast<bool>(fib.findExactMatch("/a/b/c")))
+    {
+      BOOST_FAIL("Removed incorrect entry");
+    }
+
+  testRemove(this, manager, fib, face, "/a/b/c");
+
+  if (!static_cast<bool>(fib.findExactMatch("/a")))
+    {
+      BOOST_FAIL("Removed incorrect entry");
+    }
+
+  testRemove(this, manager, fib, face, "/a");
+
+  testRemove(this, manager, fib, face, "/does/not/exist");
+}
+
+bool
+removedNextHopWithCost(const Fib& fib, const Name& prefix, size_t oldSize, uint32_t cost)
+{
+  shared_ptr<fib::Entry> entry = fib.findExactMatch(prefix);
+
+  if (static_cast<bool>(entry))
+    {
+      const fib::NextHopList& hops = entry->getNextHops();
+      return hops.size() == oldSize - 1 &&
+        std::find_if(hops.begin(), hops.end(), bind(&foundNextHop, -1, cost, _1)) == hops.end();
+    }
+  return false;
+}
+
+void
+testRemoveNextHop(FibManagerFixture* fixture,
+                  FibManager& manager,
+                  Fib& fib,
+                  shared_ptr<Face> face,
+                  const Name& targetName,
+                  FaceId targetFace)
+{
+  ndn::FibManagementOptions options;
+  options.setName(targetName);
+  options.setFaceId(targetFace);
+
+  Block encodedOptions(options.wireEncode());
+
+  Name commandName("/localhost/nfd/fib");
+  commandName.append("remove-nexthop");
+  commandName.append(encodedOptions);
+
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse, fixture, _1,
+         commandName, 200, "OK");
+
+  Interest command(commandName);
+  manager.onFibRequest(command);
+
+  BOOST_REQUIRE(fixture->didCallbackFire());
+
+  fixture->resetCallbackFired();
+  face->onReceiveData.clear();
+}
+
+BOOST_FIXTURE_TEST_CASE(RemoveNextHop, FibManagerFixture)
+{
+  shared_ptr<Face> face1 = make_shared<DummyFace>();
+  shared_ptr<Face> face2 = make_shared<DummyFace>();
+  shared_ptr<Face> face3 = make_shared<DummyFace>();
+
+  addFace(face1);
+  addFace(face2);
+  addFace(face3);
+
+  shared_ptr<InternalFace> face(make_shared<InternalFace>());
+  Fib fib;
+  FibManager manager(fib,
+                     bind(&FibManagerFixture::getFace, this, _1),
+                          face);
+
+  shared_ptr<fib::Entry> entry = fib.insert("/hello").first;
+
+  entry->addNextHop(face1, 101);
+  entry->addNextHop(face2, 202);
+  entry->addNextHop(face3, 303);
+
+  testRemoveNextHop(this, manager, fib, face, "/hello", 2);
+  BOOST_REQUIRE(removedNextHopWithCost(fib, "/hello", 3, 202));
+
+  testRemoveNextHop(this, manager, fib, face, "/hello", 3);
+  BOOST_REQUIRE(removedNextHopWithCost(fib, "/hello", 2, 303));
+
+  testRemoveNextHop(this, manager, fib, face, "/hello", 1);
+  BOOST_REQUIRE(removedNextHopWithCost(fib, "/hello", 1, 101));
+
+  if (!static_cast<bool>(fib.findExactMatch("/hello")))
+    {
+      BOOST_FAIL("removed entry after removing all next hops");
+    }
+
+}
+
+BOOST_FIXTURE_TEST_CASE(RemoveNoFace, FibManagerFixture)
+{
+  shared_ptr<InternalFace> face(make_shared<InternalFace>());
+  Fib fib;
+  FibManager manager(fib,
+                     bind(&FibManagerFixture::getFace, this, _1),
+                          face);
+
+  ndn::FibManagementOptions options;
+  options.setName("/hello");
+  options.setFaceId(1);
+
+  Block encodedOptions(options.wireEncode());
+
+  Name commandName("/localhost/nfd/fib");
+  commandName.append("remove-nexthop");
+  commandName.append(encodedOptions);
+
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse, this, _1,
+         commandName, 404, "Face not found");
+
+  Interest command(commandName);
+  manager.onFibRequest(command);
+
+  BOOST_REQUIRE(didCallbackFire());
+}
+
+BOOST_FIXTURE_TEST_CASE(RemoveNoPrefix, FibManagerFixture)
+{
+  addFace(make_shared<DummyFace>());
+
+  shared_ptr<InternalFace> face(make_shared<InternalFace>());
+  Fib fib;
+  FibManager manager(fib,
+                     bind(&FibManagerFixture::getFace, this, _1),
+                     face);
+
+  ndn::FibManagementOptions options;
+  options.setName("/hello");
+  options.setFaceId(1);
+
+  Block encodedOptions(options.wireEncode());
+
+  Name commandName("/localhost/nfd/fib");
+  commandName.append("remove-nexthop");
+  commandName.append(encodedOptions);
+
+  face->onReceiveData +=
+    bind(&FibManagerFixture::validateControlResponse, this, _1,
+         commandName, 404, "Prefix not found");
+
+  Interest command(commandName);
+  manager.onFibRequest(command);
+
+  BOOST_REQUIRE(didCallbackFire());
+}
+
 BOOST_AUTO_TEST_SUITE_END()
 
 } // namespace nfd