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