mgmt refactoring: FibManager

Change-Id: Ied235d025cbe5059545c6482bfed0f38f987fdeb
Refs: #2107
diff --git a/tests/daemon/mgmt/fib-manager.t.cpp b/tests/daemon/mgmt/fib-manager.t.cpp
new file mode 100644
index 0000000..513536c
--- /dev/null
+++ b/tests/daemon/mgmt/fib-manager.t.cpp
@@ -0,0 +1,491 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#include "mgmt/fib-manager.hpp"
+
+#include "manager-common-fixture.hpp"
+#include "table/fib-nexthop.hpp"
+#include "../face/dummy-face.hpp"
+#include <ndn-cxx/management/nfd-fib-entry.hpp>
+
+namespace nfd {
+namespace tests {
+
+class FibManagerFixture : public ManagerCommonFixture
+{
+public:
+  FibManagerFixture()
+    : m_fib(m_forwarder.getFib())
+    , m_faceTable(m_forwarder.getFaceTable())
+    , m_manager(m_fib, bind(&Forwarder::getFace, &m_forwarder, _1), m_dispatcher, m_validator)
+  {
+    setTopPrefixAndPrivilege("/localhost/nfd", "fib");
+  }
+
+public: // for test
+  ControlParameters
+  makeParameters(const Name& name, const FaceId& id)
+  {
+    return ControlParameters().setName(name).setFaceId(id);
+  }
+
+  ControlParameters
+  makeParameters(const Name& name, const FaceId& id, const uint32_t& cost)
+  {
+    return ControlParameters().setName(name).setFaceId(id).setCost(cost);
+  }
+
+  FaceId
+  addFace()
+  {
+    auto face = make_shared<DummyFace>();
+    m_faceTable.add(face);
+    advanceClocks(time::milliseconds(1), 10);
+    m_responses.clear(); // clear all event notifications, if any
+    return face->getId();
+  }
+
+public: // for check
+  enum class CheckNextHopResult
+  {
+    OK,
+    NO_FIB_ENTRY,
+    WRONG_N_NEXTHOPS,
+    NO_NEXTHOP,
+    WRONG_COST
+   };
+
+  /**
+   * @brief check whether the nexthop record is added / removed properly
+   *
+   * @param expectedNNextHops use -1 to skip this check
+   * @param faceId use FACEID_NULL to skip NextHopRecord checks
+   * @param expectedCost use -1 to skip this check
+   *
+   * @retval OK FIB entry is found by exact match and has the expected number of nexthops;
+   *            NextHopRe record for faceId is found and has the expected cost
+   * @retval NO_FIB_ENTRY FIB entry is not found
+   * @retval WRONG_N_NEXTHOPS FIB entry is found but has wrong number of nexthops
+   * @retval NO_NEXTHOP NextHopRecord for faceId is not found
+   * @retval WRONG_COST NextHopRecord for faceId has wrong cost
+   */
+  CheckNextHopResult
+  checkNextHop(const Name& prefix, ssize_t expectedNNextHops = -1,
+               FaceId faceId = FACEID_NULL, int32_t expectedCost = -1)
+  {
+    auto entry = m_fib.findExactMatch(prefix);
+    if (!static_cast<bool>(entry)) {
+      return CheckNextHopResult::NO_FIB_ENTRY;
+    }
+
+    auto nextHops = entry->getNextHops();
+    if (expectedNNextHops != -1 && nextHops.size() != static_cast<size_t>(expectedNNextHops)) {
+      return CheckNextHopResult::WRONG_N_NEXTHOPS;
+    }
+
+    if (faceId != FACEID_NULL) {
+      for (auto&& record : nextHops) {
+        if (record.getFace()->getId() == faceId) {
+          return expectedCost != -1 && record.getCost() != static_cast<uint32_t>(expectedCost) ?
+            CheckNextHopResult::WRONG_COST : CheckNextHopResult::OK;
+        }
+      }
+
+      return CheckNextHopResult::NO_NEXTHOP;
+    }
+
+    return CheckNextHopResult::OK;
+  }
+
+protected:
+  Fib&       m_fib;
+  FaceTable& m_faceTable;
+  FibManager m_manager;
+};
+
+std::ostream&
+operator<<(std::ostream &os, const FibManagerFixture::CheckNextHopResult& result)
+{
+  switch (result) {
+  case FibManagerFixture::CheckNextHopResult::OK:
+    os << "OK";
+    break;
+  case FibManagerFixture::CheckNextHopResult::NO_FIB_ENTRY:
+    os << "NO_FIB_ENTRY";
+    break;
+  case FibManagerFixture::CheckNextHopResult::WRONG_N_NEXTHOPS:
+    os << "WRONG_N_NEXTHOPS";
+    break;
+  case FibManagerFixture::CheckNextHopResult::NO_NEXTHOP:
+    os << "NO_NEXTHOP";
+    break;
+  case FibManagerFixture::CheckNextHopResult::WRONG_COST:
+    os << "WRONG_COST";
+    break;
+  default:
+    break;
+  };
+
+  return os;
+}
+
+BOOST_FIXTURE_TEST_SUITE(Mgmt, FibManagerFixture)
+BOOST_AUTO_TEST_SUITE(TestFibManager)
+
+BOOST_AUTO_TEST_SUITE(AddNextHop)
+
+BOOST_AUTO_TEST_CASE(UnknownFaceId)
+{
+  auto command = makeControlCommandRequest("/localhost/nfd/fib/add-nexthop",
+                                           makeParameters("hello", FACEID_NULL, 101));
+  receiveInterest(command);
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 1);
+
+  // check response
+  BOOST_CHECK_EQUAL(checkResponse(0, command->getName(), ControlResponse(410, "Face not found")),
+                    CheckResponseResult::OK);
+
+  // double check that the next hop was not added
+  BOOST_CHECK_EQUAL(checkNextHop("/hello", -1, FACEID_NULL, 101), CheckNextHopResult::NO_FIB_ENTRY);
+}
+
+BOOST_AUTO_TEST_CASE(ImplicitFaceId)
+{
+  auto face1 = addFace();
+  auto face2 = addFace();
+  BOOST_REQUIRE(face1 != INVALID_FACEID && face2 != INVALID_FACEID);
+
+  Name expectedName;
+  ControlResponse expectedResponse;
+  auto testAddNextHop = [&] (ControlParameters parameters, const FaceId& faceId) {
+    auto command = makeControlCommandRequest("/localhost/nfd/fib/add-nexthop", parameters,
+                                             [&faceId] (shared_ptr<Interest> interest) {
+                                               interest->setIncomingFaceId(faceId);
+                                             });
+    m_responses.clear();
+    expectedName = command->getName();
+    expectedResponse = makeResponse(200, "Success", parameters.setFaceId(faceId));
+    receiveInterest(command);
+  };
+
+  testAddNextHop(ControlParameters().setName("/hello").setCost(100).setFaceId(0), face1);
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 1);
+  BOOST_CHECK_EQUAL(checkResponse(0, expectedName, expectedResponse), CheckResponseResult::OK);
+  BOOST_CHECK_EQUAL(checkNextHop("/hello", 1, face1, 100), CheckNextHopResult::OK);
+
+  testAddNextHop(ControlParameters().setName("/hello").setCost(100), face2);
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 1);
+  BOOST_CHECK_EQUAL(checkResponse(0, expectedName, expectedResponse), CheckResponseResult::OK);
+  BOOST_CHECK_EQUAL(checkNextHop("/hello", 2, face2, 100), CheckNextHopResult::OK);
+}
+
+BOOST_AUTO_TEST_CASE(InitialAdd)
+{
+  FaceId addedFaceId = addFace();
+  BOOST_REQUIRE(addedFaceId != INVALID_FACEID);
+
+  auto parameters = makeParameters("hello", addedFaceId, 101);
+  auto command = makeControlCommandRequest("/localhost/nfd/fib/add-nexthop", parameters);
+
+  receiveInterest(command);
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 1);
+  BOOST_CHECK_EQUAL(checkResponse(0, command->getName(), makeResponse(200, "Success", parameters)),
+                    CheckResponseResult::OK);
+  BOOST_CHECK_EQUAL(checkNextHop("/hello", 1, addedFaceId, 101), CheckNextHopResult::OK);
+}
+
+BOOST_AUTO_TEST_CASE(ImplicitCost)
+{
+  FaceId addedFaceId = addFace();
+  BOOST_REQUIRE(addedFaceId != INVALID_FACEID);
+
+  auto originalParameters = ControlParameters().setName("/hello").setFaceId(addedFaceId);
+  auto parameters = makeParameters("/hello", addedFaceId, 0);
+  auto command = makeControlCommandRequest("/localhost/nfd/fib/add-nexthop", originalParameters);
+
+  receiveInterest(command);
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 1);
+  BOOST_CHECK_EQUAL(checkResponse(0, command->getName(), makeResponse(200, "Success", parameters)),
+                    CheckResponseResult::OK);
+  BOOST_CHECK_EQUAL(checkNextHop("/hello", 1, addedFaceId, 0), CheckNextHopResult::OK);
+}
+
+BOOST_AUTO_TEST_CASE(AddToExisting)
+{
+  FaceId face = addFace();
+  BOOST_CHECK(face != INVALID_FACEID);
+
+  Name expectedName;
+  ControlResponse expectedResponse;
+  auto testAddNextHop = [&] (const ControlParameters& parameters) {
+    m_responses.clear();
+    auto command = makeControlCommandRequest("/localhost/nfd/fib/add-nexthop", parameters);
+    expectedName = command->getName();
+    expectedResponse = makeResponse(200, "Success", parameters);
+    receiveInterest(command);
+  };
+
+  // add initial, succeeds
+  testAddNextHop(makeParameters("/hello", face, 101));
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 1);
+  BOOST_CHECK_EQUAL(checkResponse(0, expectedName, expectedResponse), CheckResponseResult::OK);
+
+  // add to existing --> update cost, succeeds
+  testAddNextHop(makeParameters("/hello", face, 102));
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 1);
+  BOOST_CHECK_EQUAL(checkResponse(0, expectedName, expectedResponse), CheckResponseResult::OK);
+
+  BOOST_CHECK_EQUAL(checkNextHop("/hello", 2, face, 102), CheckNextHopResult::WRONG_N_NEXTHOPS);
+  BOOST_CHECK_EQUAL(checkNextHop("/hello", 1, face, 101), CheckNextHopResult::WRONG_COST);
+  BOOST_CHECK_EQUAL(checkNextHop("/hello", 1, face, 102), CheckNextHopResult::OK);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // AddNextHop
+
+BOOST_AUTO_TEST_SUITE(RemoveNextHop)
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  Name expectedName;
+  ControlResponse expectedResponse;
+  auto testRemoveNextHop = [&] (const ControlParameters& parameters) {
+    m_responses.clear();
+    auto command = makeControlCommandRequest("/localhost/nfd/fib/remove-nexthop", parameters);
+    expectedName = command->getName();
+    expectedResponse = makeResponse(200, "Success", parameters);
+    receiveInterest(command);
+  };
+
+  FaceId face1 = addFace();
+  FaceId face2 = addFace();
+  FaceId face3 = addFace();
+  BOOST_REQUIRE(face1 != INVALID_FACEID && face2 != INVALID_FACEID && face3 != INVALID_FACEID);
+
+  shared_ptr<fib::Entry> entry = m_fib.insert("/hello").first;
+  entry->addNextHop(m_faceTable.get(face1), 101);
+  entry->addNextHop(m_faceTable.get(face2), 202);
+  entry->addNextHop(m_faceTable.get(face3), 303);
+
+  testRemoveNextHop(makeParameters("/hello", face1));
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 1);
+  BOOST_CHECK_EQUAL(checkResponse(0, expectedName, expectedResponse), CheckResponseResult::OK);
+  BOOST_CHECK_EQUAL(checkNextHop("/hello", 2, face1, 101), CheckNextHopResult::NO_NEXTHOP);
+
+  testRemoveNextHop(makeParameters("/hello", face2));
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 1);
+  BOOST_CHECK_EQUAL(checkResponse(0, expectedName, expectedResponse), CheckResponseResult::OK);
+  BOOST_CHECK_EQUAL(checkNextHop("/hello", 1, face2, 202), CheckNextHopResult::NO_NEXTHOP);
+
+  testRemoveNextHop(makeParameters("/hello", face3));
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 1);
+  BOOST_CHECK_EQUAL(checkResponse(0, expectedName, expectedResponse), CheckResponseResult::OK);
+  BOOST_CHECK_EQUAL(checkNextHop("/hello", 0, face3, 303), CheckNextHopResult::NO_FIB_ENTRY);
+}
+
+BOOST_AUTO_TEST_CASE(PrefixNotFound)
+{
+  FaceId addedFaceId = addFace();
+  BOOST_CHECK(addedFaceId != INVALID_FACEID);
+
+  auto parameters = makeParameters("hello", addedFaceId);
+  auto command = makeControlCommandRequest("/localhost/nfd/fib/remove-nexthop", parameters);
+  auto response = makeResponse(200, "Success", parameters);
+
+  receiveInterest(command);
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 1);
+  BOOST_CHECK_EQUAL(checkResponse(0, command->getName(), response), CheckResponseResult::OK);
+}
+
+BOOST_AUTO_TEST_CASE(ImplicitFaceId)
+{
+  auto face1 = addFace();
+  auto face2 = addFace();
+  BOOST_REQUIRE(face1 != INVALID_FACEID && face2 != INVALID_FACEID);
+
+  Name expectedName;
+  ControlResponse expectedResponse;
+  auto testWithImplicitFaceId = [&] (ControlParameters parameters, FaceId face) {
+    m_responses.clear();
+    auto command = makeControlCommandRequest("/localhost/nfd/fib/remove-nexthop", parameters,
+                                             [face] (shared_ptr<Interest> interest) {
+                                               interest->setIncomingFaceId(face);
+                                             });
+    expectedName = command->getName();
+    expectedResponse = makeResponse(200, "Success", parameters.setFaceId(face));
+    receiveInterest(command);
+  };
+
+  shared_ptr<fib::Entry> entry = m_fib.insert("/hello").first;
+  entry->addNextHop(m_faceTable.get(face1), 101);
+  entry->addNextHop(m_faceTable.get(face2), 202);
+
+  testWithImplicitFaceId(ControlParameters().setName("/hello").setFaceId(0), face1);
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 1);
+  BOOST_CHECK_EQUAL(checkResponse(0, expectedName, expectedResponse), CheckResponseResult::OK);
+  BOOST_CHECK_EQUAL(checkNextHop("/hello", 1, face1, 101), CheckNextHopResult::NO_NEXTHOP);
+
+  testWithImplicitFaceId(ControlParameters().setName("/hello"), face2);
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 1);
+  BOOST_CHECK_EQUAL(checkResponse(0, expectedName, expectedResponse), CheckResponseResult::OK);
+  BOOST_CHECK_EQUAL(checkNextHop("/hello", 0, face2, 202), CheckNextHopResult::NO_FIB_ENTRY);
+}
+
+BOOST_AUTO_TEST_CASE(RecordNotExist)
+{
+  auto face1 = addFace();
+  auto face2 = addFace();
+  BOOST_REQUIRE(face1 != INVALID_FACEID && face2 != INVALID_FACEID);
+
+  Name expectedName;
+  ControlResponse expectedResponse;
+  auto testRemoveNextHop = [&] (ControlParameters parameters) {
+    m_responses.clear();
+    auto command = makeControlCommandRequest("/localhost/nfd/fib/remove-nexthop", parameters);
+    expectedName = command->getName();
+    expectedResponse = makeResponse(200, "Success", parameters);
+    receiveInterest(command);
+  };
+
+  m_fib.insert("/hello").first->addNextHop(m_faceTable.get(face1), 101);
+
+  testRemoveNextHop(makeParameters("/hello", face2 + 100));
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 1); // face does not exist
+  BOOST_CHECK_EQUAL(checkResponse(0, expectedName, expectedResponse), CheckResponseResult::OK);
+  BOOST_CHECK_EQUAL(checkNextHop("/hello", -1, face2 + 100), CheckNextHopResult::NO_NEXTHOP);
+
+  testRemoveNextHop(makeParameters("/hello", face2));
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 1); // record does not exist
+  BOOST_CHECK_EQUAL(checkResponse(0, expectedName, expectedResponse), CheckResponseResult::OK);
+  BOOST_CHECK_EQUAL(checkNextHop("/hello", -1, face2), CheckNextHopResult::NO_NEXTHOP);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // RemoveNextHop
+
+// @todo Remove when ndn::nfd::FibEntry implements operator!= and operator<<
+class FibEntry : public ndn::nfd::FibEntry
+{
+public:
+  FibEntry() = default;
+
+  FibEntry(const ndn::nfd::FibEntry& entry)
+    : ndn::nfd::FibEntry(entry)
+  {
+  }
+};
+
+bool
+operator!=(const FibEntry& left, const FibEntry& right)
+{
+  if (left.getPrefix() != right.getPrefix()) {
+    return true;
+  }
+
+  auto leftNextHops = left.getNextHopRecords();
+  auto rightNextHops = right.getNextHopRecords();
+  if (leftNextHops.size() != rightNextHops.size()) {
+    return true;
+  }
+
+  for (auto&& nexthop : leftNextHops) {
+    auto hitEntry =
+      std::find_if(rightNextHops.begin(), rightNextHops.end(), [&] (const ndn::nfd::NextHopRecord& record) {
+          return nexthop.getCost() == record.getCost() && nexthop.getFaceId() == record.getFaceId();
+        });
+
+    if (hitEntry == rightNextHops.end()) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+std::ostream&
+operator<<(std::ostream &os, const FibEntry& entry)
+{
+  const auto& nexthops = entry.getNextHopRecords();
+  os << "[" << entry.getPrefix() << ", " << nexthops.size() << ": ";
+  for (auto record : nexthops) {
+    os << "{" << record.getFaceId() << ", " << record.getCost() << "} ";
+  }
+  os << "]";
+
+  return os;
+}
+
+BOOST_AUTO_TEST_CASE(FibDataset)
+{
+  const size_t nEntries = 108;
+  std::set<Name> actualPrefixes;
+  for (size_t i = 0 ; i < nEntries ; i ++) {
+    Name prefix = Name("test").appendSegment(i);
+    actualPrefixes.insert(prefix);
+    auto fibEntry = m_fib.insert(prefix).first;
+    fibEntry->addNextHop(m_faceTable.get(addFace()), std::numeric_limits<uint8_t>::max() - 1);
+    fibEntry->addNextHop(m_faceTable.get(addFace()), std::numeric_limits<uint8_t>::max() - 2);
+  }
+
+  receiveInterest(makeInterest("/localhost/nfd/fib/list"));
+
+  Block content;
+  BOOST_CHECK_NO_THROW(content = concatenateResponses());
+  BOOST_CHECK_NO_THROW(content.parse());
+  BOOST_REQUIRE_EQUAL(content.elements().size(), nEntries);
+
+  std::vector<FibEntry> receivedRecords, expectedRecords;
+  for (size_t idx = 0; idx < nEntries; ++idx) {
+    BOOST_TEST_MESSAGE("processing element: " << idx);
+
+    FibEntry decodedEntry;
+    BOOST_REQUIRE_NO_THROW(decodedEntry.wireDecode(content.elements()[idx]));
+    receivedRecords.push_back(decodedEntry);
+
+    actualPrefixes.erase(decodedEntry.getPrefix());
+
+    auto matchedEntry = m_fib.findExactMatch(decodedEntry.getPrefix());
+    BOOST_REQUIRE(matchedEntry != nullptr);
+
+    FibEntry record;
+    record.setPrefix(matchedEntry->getPrefix());
+    const auto& nextHops = matchedEntry->getNextHops();
+    for (auto&& next : nextHops) {
+      ndn::nfd::NextHopRecord nextHopRecord;
+      nextHopRecord.setFaceId(next.getFace()->getId());
+      nextHopRecord.setCost(next.getCost());
+      record.addNextHopRecord(nextHopRecord);
+    }
+    expectedRecords.push_back(record);
+  }
+
+  BOOST_CHECK_EQUAL(actualPrefixes.size(), 0);
+
+  BOOST_CHECK_EQUAL_COLLECTIONS(receivedRecords.begin(), receivedRecords.end(),
+                                expectedRecords.begin(), expectedRecords.end());
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestFibManager
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd