| /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ |
| /* |
| * Copyright (c) 2014-2019, 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 "fw/ncc-strategy.hpp" |
| |
| #include "tests/daemon/face/dummy-face.hpp" |
| #include "strategy-tester.hpp" |
| #include "topology-tester.hpp" |
| |
| namespace nfd { |
| namespace fw { |
| namespace tests { |
| |
| using NccStrategyTester = StrategyTester<NccStrategy>; |
| NFD_REGISTER_STRATEGY(NccStrategyTester); |
| |
| BOOST_AUTO_TEST_SUITE(Fw) |
| BOOST_FIXTURE_TEST_SUITE(TestNccStrategy, GlobalIoTimeFixture) |
| |
| BOOST_AUTO_TEST_CASE(FavorRespondingUpstream) |
| { |
| // NccStrategy is fairly complex. |
| // The most important property is: it remembers which upstream is the fastest to return Data, |
| // and favors this upstream in subsequent Interests. |
| |
| LimitedIo limitedIo(this); |
| FaceTable faceTable; |
| Forwarder forwarder(faceTable); |
| NccStrategyTester& strategy = choose<NccStrategyTester>(forwarder); |
| strategy.afterAction.connect(bind(&LimitedIo::afterOp, &limitedIo)); |
| |
| shared_ptr<DummyFace> face1 = make_shared<DummyFace>(); |
| shared_ptr<DummyFace> face2 = make_shared<DummyFace>(); |
| shared_ptr<DummyFace> face3 = make_shared<DummyFace>(); |
| faceTable.add(face1); |
| faceTable.add(face2); |
| faceTable.add(face3); |
| |
| Fib& fib = forwarder.getFib(); |
| fib::Entry& fibEntry = *fib.insert(Name()).first; |
| fib.addOrUpdateNextHop(fibEntry, *face1, 10); |
| fib.addOrUpdateNextHop(fibEntry, *face2, 20); |
| |
| Pit& pit = forwarder.getPit(); |
| |
| // first Interest: strategy knows nothing and follows routing |
| shared_ptr<Interest> interest1p = makeInterest("ndn:/0Jm1ajrW/%00"); |
| Interest& interest1 = *interest1p; |
| interest1.setInterestLifetime(8_s); |
| shared_ptr<pit::Entry> pitEntry1 = pit.insert(interest1).first; |
| |
| pitEntry1->insertOrUpdateInRecord(*face3, interest1); |
| strategy.afterReceiveInterest(FaceEndpoint(*face3, 0), interest1, pitEntry1); |
| |
| // forwards to face1 because routing says it's best |
| // (no io run here: afterReceiveInterest has already sent the Interest) |
| BOOST_REQUIRE_EQUAL(strategy.sendInterestHistory.size(), 1); |
| BOOST_CHECK_EQUAL(strategy.sendInterestHistory[0].outFaceId, face1->getId()); |
| |
| // forwards to face2 because face1 doesn't respond |
| limitedIo.run(1, 500_ms, 10_ms); |
| BOOST_REQUIRE_EQUAL(strategy.sendInterestHistory.size(), 2); |
| BOOST_CHECK_EQUAL(strategy.sendInterestHistory[1].outFaceId, face2->getId()); |
| |
| // face2 responds |
| shared_ptr<Data> data1p = makeData("ndn:/0Jm1ajrW/%00"); |
| Data& data1 = *data1p; |
| strategy.beforeSatisfyInterest(pitEntry1, FaceEndpoint(*face2, 0), data1); |
| this->advanceClocks(10_ms, 500_ms); |
| |
| // second Interest: strategy knows face2 is best |
| shared_ptr<Interest> interest2p = makeInterest("ndn:/0Jm1ajrW/%00%01"); |
| Interest& interest2 = *interest2p; |
| interest2.setInterestLifetime(8_s); |
| shared_ptr<pit::Entry> pitEntry2 = pit.insert(interest2).first; |
| |
| pitEntry2->insertOrUpdateInRecord(*face3, interest2); |
| strategy.afterReceiveInterest(FaceEndpoint(*face3, 0), interest2, pitEntry2); |
| |
| // forwards to face2 because it responds previously |
| this->advanceClocks(1_ms); |
| BOOST_REQUIRE_GE(strategy.sendInterestHistory.size(), 3); |
| BOOST_CHECK_EQUAL(strategy.sendInterestHistory[2].outFaceId, face2->getId()); |
| } |
| |
| BOOST_AUTO_TEST_CASE(Bug1853) |
| { |
| FaceTable faceTable; |
| Forwarder forwarder(faceTable); |
| NccStrategyTester& strategy = choose<NccStrategyTester>(forwarder); |
| |
| shared_ptr<DummyFace> face1 = make_shared<DummyFace>(); |
| shared_ptr<DummyFace> face2 = make_shared<DummyFace>(); |
| shared_ptr<DummyFace> face3 = make_shared<DummyFace>(); |
| faceTable.add(face1); |
| faceTable.add(face2); |
| faceTable.add(face3); |
| |
| Fib& fib = forwarder.getFib(); |
| fib::Entry& fibEntry = *fib.insert(Name()).first; |
| fib.addOrUpdateNextHop(fibEntry, *face1, 10); |
| |
| Pit& pit = forwarder.getPit(); |
| |
| // first Interest: strategy follows routing and forwards to face1 |
| shared_ptr<Interest> interest1 = makeInterest("ndn:/nztwIvHX/%00"); |
| interest1->setInterestLifetime(8_s); |
| shared_ptr<pit::Entry> pitEntry1 = pit.insert(*interest1).first; |
| |
| pitEntry1->insertOrUpdateInRecord(*face3, *interest1); |
| strategy.afterReceiveInterest(FaceEndpoint(*face3, 0), *interest1, pitEntry1); |
| |
| this->advanceClocks(1_ms); |
| BOOST_REQUIRE_EQUAL(strategy.sendInterestHistory.size(), 1); |
| BOOST_CHECK_EQUAL(strategy.sendInterestHistory[0].outFaceId, face1->getId()); |
| |
| // face1 responds |
| shared_ptr<Data> data1 = makeData("ndn:/nztwIvHX/%00"); |
| strategy.beforeSatisfyInterest(pitEntry1, FaceEndpoint(*face1, 0), *data1); |
| this->advanceClocks(10_ms, 500_ms); |
| |
| // second Interest: bestFace is face1, nUpstreams becomes 0, |
| // therefore pitEntryInfo->maxInterval cannot be calculated from deferRange and nUpstreams |
| shared_ptr<Interest> interest2 = makeInterest("ndn:/nztwIvHX/%01"); |
| interest2->setInterestLifetime(8_s); |
| shared_ptr<pit::Entry> pitEntry2 = pit.insert(*interest2).first; |
| |
| pitEntry2->insertOrUpdateInRecord(*face3, *interest2); |
| strategy.afterReceiveInterest(FaceEndpoint(*face3, 0), *interest2, pitEntry2); |
| |
| // FIB entry is changed before doPropagate executes |
| fib.addOrUpdateNextHop(fibEntry, *face2, 20); |
| this->advanceClocks(10_ms, 1_s);// should not crash |
| } |
| |
| BOOST_AUTO_TEST_CASE(Bug1961) |
| { |
| LimitedIo limitedIo(this); |
| FaceTable faceTable; |
| Forwarder forwarder(faceTable); |
| NccStrategyTester& strategy = choose<NccStrategyTester>(forwarder); |
| strategy.afterAction.connect(bind(&LimitedIo::afterOp, &limitedIo)); |
| |
| shared_ptr<DummyFace> face1 = make_shared<DummyFace>(); |
| shared_ptr<DummyFace> face2 = make_shared<DummyFace>(); |
| shared_ptr<DummyFace> face3 = make_shared<DummyFace>(); |
| faceTable.add(face1); |
| faceTable.add(face2); |
| faceTable.add(face3); |
| |
| Fib& fib = forwarder.getFib(); |
| fib::Entry& fibEntry = *fib.insert(Name()).first; |
| fib.addOrUpdateNextHop(fibEntry, *face1, 10); |
| fib.addOrUpdateNextHop(fibEntry, *face2, 20); |
| |
| Pit& pit = forwarder.getPit(); |
| |
| // first Interest: strategy forwards to face1 and face2 |
| shared_ptr<Interest> interest1 = makeInterest("ndn:/seRMz5a6/%00"); |
| interest1->setInterestLifetime(2_s); |
| shared_ptr<pit::Entry> pitEntry1 = pit.insert(*interest1).first; |
| |
| pitEntry1->insertOrUpdateInRecord(*face3, *interest1); |
| strategy.afterReceiveInterest(FaceEndpoint(*face3, 0), *interest1, pitEntry1); |
| limitedIo.run(2 - strategy.sendInterestHistory.size(), 2_s, 10_ms); |
| BOOST_REQUIRE_EQUAL(strategy.sendInterestHistory.size(), 2); |
| BOOST_CHECK_EQUAL(strategy.sendInterestHistory[0].outFaceId, face1->getId()); |
| BOOST_CHECK_EQUAL(strategy.sendInterestHistory[1].outFaceId, face2->getId()); |
| |
| // face1 responds |
| shared_ptr<Data> data1 = makeData("ndn:/seRMz5a6/%00"); |
| strategy.beforeSatisfyInterest(pitEntry1, FaceEndpoint(*face1, 0), *data1); |
| pitEntry1->clearInRecords(); |
| this->advanceClocks(10_ms); |
| // face2 also responds |
| strategy.beforeSatisfyInterest(pitEntry1, FaceEndpoint(*face2, 0), *data1); |
| this->advanceClocks(10_ms); |
| |
| // second Interest: bestFace should be face 1 |
| shared_ptr<Interest> interest2 = makeInterest("ndn:/seRMz5a6/%01"); |
| interest2->setInterestLifetime(2_s); |
| shared_ptr<pit::Entry> pitEntry2 = pit.insert(*interest2).first; |
| |
| pitEntry2->insertOrUpdateInRecord(*face3, *interest2); |
| strategy.afterReceiveInterest(FaceEndpoint(*face3, 0), *interest2, pitEntry2); |
| limitedIo.run(3 - strategy.sendInterestHistory.size(), 2_s, 10_ms); |
| |
| BOOST_REQUIRE_GE(strategy.sendInterestHistory.size(), 3); |
| BOOST_CHECK_EQUAL(strategy.sendInterestHistory[2].outFaceId, face1->getId()); |
| } |
| |
| BOOST_AUTO_TEST_CASE(Bug1971) |
| { |
| LimitedIo limitedIo(this); |
| FaceTable faceTable; |
| Forwarder forwarder(faceTable); |
| NccStrategyTester& strategy = choose<NccStrategyTester>(forwarder); |
| strategy.afterAction.connect(bind(&LimitedIo::afterOp, &limitedIo)); |
| |
| shared_ptr<DummyFace> face1 = make_shared<DummyFace>(); |
| shared_ptr<DummyFace> face2 = make_shared<DummyFace>(); |
| faceTable.add(face1); |
| faceTable.add(face2); |
| |
| Fib& fib = forwarder.getFib(); |
| fib::Entry& fibEntry = *fib.insert(Name()).first; |
| fib.addOrUpdateNextHop(fibEntry, *face2, 10); |
| |
| Pit& pit = forwarder.getPit(); |
| |
| // first Interest: strategy forwards to face2 |
| shared_ptr<Interest> interest1 = makeInterest("ndn:/M4mBXCsd"); |
| interest1->setInterestLifetime(2_s); |
| shared_ptr<pit::Entry> pitEntry1 = pit.insert(*interest1).first; |
| |
| pitEntry1->insertOrUpdateInRecord(*face1, *interest1); |
| strategy.afterReceiveInterest(FaceEndpoint(*face1, 0), *interest1, pitEntry1); |
| limitedIo.run(1 - strategy.sendInterestHistory.size(), 2_s, 10_ms); |
| BOOST_REQUIRE_EQUAL(strategy.sendInterestHistory.size(), 1); |
| BOOST_CHECK_EQUAL(strategy.sendInterestHistory[0].outFaceId, face2->getId()); |
| |
| // face2 responds |
| shared_ptr<Data> data1 = makeData("ndn:/M4mBXCsd"); |
| data1->setFreshnessPeriod(5_ms); |
| strategy.beforeSatisfyInterest(pitEntry1, FaceEndpoint(*face2, 0), *data1); |
| pitEntry1->deleteOutRecord(*face2); |
| pitEntry1->clearInRecords(); |
| this->advanceClocks(10_ms); |
| |
| // similar Interest: strategy should still forward it |
| pitEntry1->insertOrUpdateInRecord(*face1, *interest1); |
| strategy.afterReceiveInterest(FaceEndpoint(*face1, 0), *interest1, pitEntry1); |
| limitedIo.run(2 - strategy.sendInterestHistory.size(), 2_s, 10_ms); |
| BOOST_REQUIRE_EQUAL(strategy.sendInterestHistory.size(), 2); |
| BOOST_CHECK_EQUAL(strategy.sendInterestHistory[1].outFaceId, face2->getId()); |
| } |
| |
| BOOST_AUTO_TEST_CASE(Bug1998) |
| { |
| FaceTable faceTable; |
| Forwarder forwarder(faceTable); |
| NccStrategyTester& strategy = choose<NccStrategyTester>(forwarder); |
| |
| shared_ptr<DummyFace> face1 = make_shared<DummyFace>(); |
| shared_ptr<DummyFace> face2 = make_shared<DummyFace>(); |
| faceTable.add(face1); |
| faceTable.add(face2); |
| |
| Fib& fib = forwarder.getFib(); |
| fib::Entry& fibEntry = *fib.insert(Name()).first; |
| fib.addOrUpdateNextHop(fibEntry, *face1, 10); // face1 is top-ranked nexthop |
| fib.addOrUpdateNextHop(fibEntry, *face2, 20); |
| |
| Pit& pit = forwarder.getPit(); |
| |
| // Interest comes from face1, which is sole downstream |
| shared_ptr<Interest> interest1 = makeInterest("ndn:/tFy5HzUzD4"); |
| shared_ptr<pit::Entry> pitEntry1 = pit.insert(*interest1).first; |
| pitEntry1->insertOrUpdateInRecord(*face1, *interest1); |
| |
| strategy.afterReceiveInterest(FaceEndpoint(*face1, 0), *interest1, pitEntry1); |
| |
| // Interest shall go to face2, not loop back to face1 |
| BOOST_REQUIRE_EQUAL(strategy.sendInterestHistory.size(), 1); |
| BOOST_CHECK_EQUAL(strategy.sendInterestHistory[0].outFaceId, face2->getId()); |
| } |
| |
| BOOST_AUTO_TEST_CASE_EXPECTED_FAILURES(PredictionAdjustment, 1) |
| BOOST_AUTO_TEST_CASE(PredictionAdjustment) // Bug 3411 |
| { |
| /* |
| * /----------\ |
| * | consumer | |
| * \----------/ |
| * | |
| * | |
| * +---+ 5ms +---+ |
| * | A |-------| B | |
| * +---+ +---+ |
| * | | |
| * | 5ms | 15ms/5ms |
| * | | |
| * +---+ +---+ |
| * | C |-------| D | |
| * +---+ 15ms +---+ |
| * /5ms | |
| * | |
| * /----------\ |
| * | producer | |
| * \----------/ |
| */ |
| |
| TopologyTester topo; |
| TopologyNode nodeA = topo.addForwarder("A"), |
| nodeB = topo.addForwarder("B"), |
| nodeC = topo.addForwarder("C"), |
| nodeD = topo.addForwarder("D"); |
| |
| for (TopologyNode node : {nodeA, nodeB, nodeC, nodeD}) { |
| topo.setStrategy<NccStrategy>(node); |
| } |
| |
| shared_ptr<TopologyLink> linkAB = topo.addLink("AB", 5_ms, {nodeA, nodeB}), |
| linkAC = topo.addLink("AC", 5_ms, {nodeA, nodeC}), |
| linkBD = topo.addLink("BD", 15_ms, {nodeB, nodeD}), |
| linkCD = topo.addLink("CD", 15_ms, {nodeC, nodeD}); |
| |
| topo.registerPrefix(nodeA, linkAB->getFace(nodeA), "ndn:/P"); |
| topo.registerPrefix(nodeA, linkAC->getFace(nodeA), "ndn:/P"); |
| topo.registerPrefix(nodeB, linkBD->getFace(nodeB), "ndn:/P"); |
| topo.registerPrefix(nodeC, linkCD->getFace(nodeC), "ndn:/P"); |
| |
| shared_ptr<TopologyAppLink> producer = topo.addAppFace("producer", nodeD, "ndn:/P"); |
| topo.addEchoProducer(producer->getClientFace()); |
| |
| shared_ptr<TopologyAppLink> consumer = topo.addAppFace("consumer", nodeA); |
| topo.addIntervalConsumer(consumer->getClientFace(), "ndn:/P", |
| 100_ms, 300); |
| |
| auto getMeInfo = [&] () -> NccStrategy::MeasurementsEntryInfo* { |
| Measurements& measurements = topo.getForwarder(nodeA).getMeasurements(); |
| measurements::Entry* me = measurements.findExactMatch("ndn:/P"); |
| return me == nullptr ? nullptr : me->getStrategyInfo<NccStrategy::MeasurementsEntryInfo>(); |
| }; |
| |
| // NccStrategy starts with an exploration period when the algorithm adjusts |
| // its prediction to converge near the path RTT. |
| bool isExplorationFinished = false; |
| const time::milliseconds TRUE_RTT1(40); |
| bool isLastPredictionUnder = true; |
| int nPredictionCrossings = 0; |
| for (int i = 0; i < 10000; ++i) { |
| this->advanceClocks(5_ms); |
| auto meInfo = getMeInfo(); |
| if (meInfo == nullptr) { |
| continue; |
| } |
| |
| if ((isLastPredictionUnder && meInfo->prediction > TRUE_RTT1) || |
| (!isLastPredictionUnder && meInfo->prediction < TRUE_RTT1)) { |
| isLastPredictionUnder = !isLastPredictionUnder; |
| if (++nPredictionCrossings > 6) { |
| isExplorationFinished = true; |
| BOOST_TEST_MESSAGE("exploration finishes after " << (i * 5) << "ms"); |
| break; |
| } |
| } |
| } |
| BOOST_REQUIRE_MESSAGE(isExplorationFinished, "exploration did not finish within 50s"); |
| |
| // NccStrategy has selected one path as the best. |
| // When we reduce the RTT of the other path, ideally it should be selected as the best face. |
| // However, this won't happen due to a weakness in NccStrategy. |
| // See https://redmine.named-data.net/issues/3411#note-4 |
| shared_ptr<Face> bestFace1 = getMeInfo()->bestFace.lock(); |
| if (bestFace1.get() == &linkAB->getFace(nodeA)) { |
| linkCD->setDelay(5_ms); |
| } |
| else if (bestFace1.get() == &linkAC->getFace(nodeA)) { |
| linkBD->setDelay(5_ms); |
| } |
| else { |
| BOOST_FAIL("unexpected best face"); |
| } |
| |
| bool isNewBestChosen = false; |
| for (int i = 0; i < 10000; ++i) { |
| this->advanceClocks(5_ms); |
| auto meInfo = getMeInfo(); |
| if (meInfo == nullptr) { |
| continue; |
| } |
| |
| if (meInfo->bestFace.lock() != bestFace1) { |
| isNewBestChosen = true; |
| BOOST_TEST_MESSAGE("new best face is found after " << (i * 5) << "ms"); |
| break; |
| } |
| } |
| BOOST_CHECK_MESSAGE(isNewBestChosen, "new best face is not found in 50000ms"); // expected failure |
| } |
| |
| BOOST_AUTO_TEST_SUITE_END() // TestNccStrategy |
| BOOST_AUTO_TEST_SUITE_END() // Fw |
| |
| } // namespace tests |
| } // namespace fw |
| } // namespace nfd |