From 1d62e62bc057c8b5e5c82b99fea31d63ad4df700 Mon Sep 17 00:00:00 2001 From: Junxiao Shi Date: Wed, 8 Mar 2017 22:39:28 +0000 Subject: [PATCH] tools: nfdc route list and nfd route show commands refs #3866 Change-Id: Ic8feab0ce9e5707c1cf382cdea7264e28e3edb30 --- docs/manpages/nfdc-route.rst | 17 ++-- tests/tools/nfdc/rib-module.t.cpp | 161 +++++++++++++++++++++++++++++- tools/nfdc/find-face.cpp | 10 ++ tools/nfdc/find-face.hpp | 5 + tools/nfdc/rib-module.cpp | 141 ++++++++++++++++++++++---- tools/nfdc/rib-module.hpp | 40 ++++++-- tools/nfdc/status.cpp | 5 - 7 files changed, 336 insertions(+), 43 deletions(-) diff --git a/docs/manpages/nfdc-route.rst b/docs/manpages/nfdc-route.rst index 0c2f0e0b..8ae5bc6b 100644 --- a/docs/manpages/nfdc-route.rst +++ b/docs/manpages/nfdc-route.rst @@ -3,11 +3,12 @@ nfdc-route SYNOPSIS -------- -| nfdc route [list] -| nfdc fib [list] +| nfdc route [list [[nexthop] ] [origin ]] +| nfdc route show [prefix] | nfdc route add [prefix] [nexthop] [origin ] [cost ] | [no-inherit] [capture] [expires ] -| nfdc unregister [-o ] +| nfdc route remove [prefix] [nexthop] [origin ] +| nfdc fib [list] DESCRIPTION ----------- @@ -17,10 +18,9 @@ Each *route* in the RIB indicates that contents under a name prefix may be avail A route contains a name prefix, a nexthop face, the origin, a cost, and a set of route inheritance flags; refer to NFD Management protocol for more information. -The **nfdc route list** command shows a list of routes in the RIB. +The **nfdc route list** command lists RIB routes, optionally filtered by nexthop and origin. -The **nfdc fib list** command shows the forwarding information base (FIB), -which is calculated from RIB routes and used directly by NFD forwarding. +The **nfdc route show** command shows RIB routes at a specified name prefix. The **nfdc route add** command requests to add a route. If a route with the same prefix, nexthop, and origin already exists, @@ -29,6 +29,9 @@ This command returns when the request has been accepted, but does not wait for R The **nfdc route remove** command removes a route with matching prefix, nexthop, and origin. +The **nfdc fib list** command shows the forwarding information base (FIB), +which is calculated from RIB routes and used directly by NFD forwarding. + OPTIONS ------- @@ -77,6 +80,8 @@ EXIT CODES 5: Ambiguous: multiple matching faces are found (**nfdc route add** only) +6: Route not found (**nfdc route list** and **nfdc route show** only) + SEE ALSO -------- nfd(1), nfdc(1) diff --git a/tests/tools/nfdc/rib-module.t.cpp b/tests/tools/nfdc/rib-module.t.cpp index 2cdc21dd..0c5bc314 100644 --- a/tests/tools/nfdc/rib-module.t.cpp +++ b/tests/tools/nfdc/rib-module.t.cpp @@ -36,6 +36,158 @@ namespace tests { BOOST_AUTO_TEST_SUITE(Nfdc) BOOST_FIXTURE_TEST_SUITE(TestRibModule, StatusFixture) +class RouteListFixture : public ExecuteCommandFixture +{ +protected: + bool + respondRibDataset(const Interest& interest) + { + if (!Name("/localhost/nfd/rib/list").isPrefixOf(interest.getName())) { + return false; + } + + RibEntry entry1; + entry1.setName("/5BBmTevRJ"); + entry1.addRoute(Route() + .setFaceId(6720) + .setOrigin(ndn::nfd::ROUTE_ORIGIN_CLIENT) + .setCost(2956) + .setFlags(ndn::nfd::ROUTE_FLAG_CHILD_INHERIT | ndn::nfd::ROUTE_FLAG_CAPTURE) + .setExpirationPeriod(time::milliseconds(29950035))); + entry1.addRoute(Route() + .setFaceId(6720) + .setOrigin(ndn::nfd::ROUTE_ORIGIN_STATIC) + .setCost(425) + .setFlags(ndn::nfd::ROUTE_FLAGS_NONE)); + entry1.addRoute(Route() + .setFaceId(8599) + .setOrigin(ndn::nfd::ROUTE_ORIGIN_STATIC) + .setCost(9140) + .setFlags(ndn::nfd::ROUTE_FLAG_CHILD_INHERIT)); + + RibEntry entry2; + entry2.setName("/aDPTKCio"); + entry2.addRoute(Route() + .setFaceId(31066) + .setOrigin(ndn::nfd::ROUTE_ORIGIN_CLIENT) + .setCost(4617) + .setFlags(ndn::nfd::ROUTE_FLAG_CAPTURE)); + + this->sendDataset(interest.getName(), entry1, entry2); + return true; + } +}; + +BOOST_FIXTURE_TEST_SUITE(ListShowCommand, RouteListFixture) + +const std::string NOFILTER_OUTPUT = std::string(R"TEXT( +prefix=/5BBmTevRJ nexthop=6720 origin=65 cost=2956 flags=child-inherit|capture expires=29950s +prefix=/5BBmTevRJ nexthop=6720 origin=255 cost=425 flags=none expires=never +prefix=/5BBmTevRJ nexthop=8599 origin=255 cost=9140 flags=child-inherit expires=never +prefix=/aDPTKCio nexthop=31066 origin=65 cost=4617 flags=capture expires=never +)TEXT").substr(1); + +BOOST_AUTO_TEST_CASE(ListNoFilter) +{ + this->processInterest = [this] (const Interest& interest) { + BOOST_CHECK(this->respondRibDataset(interest)); + }; + + this->execute("route"); + BOOST_CHECK_EQUAL(exitCode, 0); + BOOST_CHECK(out.is_equal(NOFILTER_OUTPUT)); + BOOST_CHECK(err.is_empty()); +} + +const std::string NEXTHOP_OUTPUT = std::string(R"TEXT( +prefix=/5BBmTevRJ nexthop=6720 origin=65 cost=2956 flags=child-inherit|capture expires=29950s +prefix=/5BBmTevRJ nexthop=6720 origin=255 cost=425 flags=none expires=never +prefix=/aDPTKCio nexthop=31066 origin=65 cost=4617 flags=capture expires=never +)TEXT").substr(1); + +BOOST_AUTO_TEST_CASE(ListByNexthop) +{ + this->processInterest = [this] (const Interest& interest) { + BOOST_CHECK(this->respondFaceQuery(interest) || this->respondRibDataset(interest)); + }; + + this->execute("route list udp4://225.131.75.231:56363"); + BOOST_CHECK_EQUAL(exitCode, 0); + BOOST_CHECK(out.is_equal(NEXTHOP_OUTPUT)); + BOOST_CHECK(err.is_empty()); +} + +const std::string ORIGIN_OUTPUT = std::string(R"TEXT( +prefix=/5BBmTevRJ nexthop=6720 origin=255 cost=425 flags=none expires=never +prefix=/5BBmTevRJ nexthop=8599 origin=255 cost=9140 flags=child-inherit expires=never +)TEXT").substr(1); + +BOOST_AUTO_TEST_CASE(ListByOrigin) +{ + this->processInterest = [this] (const Interest& interest) { + BOOST_CHECK(this->respondRibDataset(interest)); + }; + + this->execute("route list origin 255"); + BOOST_CHECK_EQUAL(exitCode, 0); + BOOST_CHECK(out.is_equal(ORIGIN_OUTPUT)); + BOOST_CHECK(err.is_empty()); +} + +const std::string PREFIX_OUTPUT = std::string(R"TEXT( +prefix=/5BBmTevRJ nexthop=6720 origin=65 cost=2956 flags=child-inherit|capture expires=29950s +prefix=/5BBmTevRJ nexthop=6720 origin=255 cost=425 flags=none expires=never +prefix=/5BBmTevRJ nexthop=8599 origin=255 cost=9140 flags=child-inherit expires=never +)TEXT").substr(1); + +BOOST_AUTO_TEST_CASE(ShowByPrefix) +{ + this->processInterest = [this] (const Interest& interest) { + BOOST_CHECK(this->respondRibDataset(interest)); + }; + + this->execute("route show 5BBmTevRJ"); + BOOST_CHECK_EQUAL(exitCode, 0); + BOOST_CHECK(out.is_equal(PREFIX_OUTPUT)); + BOOST_CHECK(err.is_empty()); +} + +BOOST_AUTO_TEST_CASE(FaceNotExist) +{ + this->processInterest = [this] (const Interest& interest) { + BOOST_CHECK(this->respondFaceQuery(interest)); + }; + + this->execute("route list 23728"); + BOOST_CHECK_EQUAL(exitCode, 3); + BOOST_CHECK(out.is_empty()); + BOOST_CHECK(err.is_equal("Face not found\n")); +} + +BOOST_AUTO_TEST_CASE(RouteNotExist) +{ + this->processInterest = [this] (const Interest& interest) { + BOOST_CHECK(this->respondFaceQuery(interest) || this->respondRibDataset(interest)); + }; + + this->execute("route list 10156"); + BOOST_CHECK_EQUAL(exitCode, 6); + BOOST_CHECK(out.is_empty()); + BOOST_CHECK(err.is_equal("Route not found\n")); +} + +BOOST_AUTO_TEST_CASE(ErrorDataset) +{ + this->processInterest = nullptr; // no response to dataset + + this->execute("route list"); + BOOST_CHECK_EQUAL(exitCode, 1); + BOOST_CHECK(out.is_empty()); + BOOST_CHECK(err.is_equal("Error 10060 when fetching RIB dataset: Timeout\n")); +} + +BOOST_AUTO_TEST_SUITE_END() // ListShowCommand + BOOST_FIXTURE_TEST_SUITE(AddCommand, ExecuteCommandFixture) BOOST_AUTO_TEST_CASE(NormalByFaceId) @@ -339,10 +491,11 @@ const std::string STATUS_XML = stripXmlSpaces(R"XML( const std::string STATUS_TEXT = "RIB:\n" - " / route={faceid=262 (origin=255 cost=9 RibCapture), faceid=272 (origin=255 cost=50), " - "faceid=274 (origin=255 cost=78 ChildInherit RibCapture), " - "faceid=276 (origin=255 cost=79 expires=47s ChildInherit)}\n" - " /localhost/nfd route={faceid=258 (origin=0 cost=0 ChildInherit)}\n"; + " / routes={nexthop=262 origin=255 cost=9 flags=capture expires=never, " + "nexthop=272 origin=255 cost=50 flags=none expires=never, " + "nexthop=274 origin=255 cost=78 flags=child-inherit|capture expires=never, " + "nexthop=276 origin=255 cost=79 flags=child-inherit expires=47s}\n" + " /localhost/nfd routes={nexthop=258 origin=0 cost=0 flags=child-inherit expires=never}\n"; BOOST_AUTO_TEST_CASE(Status) { diff --git a/tools/nfdc/find-face.cpp b/tools/nfdc/find-face.cpp index 5645e26e..5c23c7ac 100644 --- a/tools/nfdc/find-face.cpp +++ b/tools/nfdc/find-face.cpp @@ -148,6 +148,16 @@ FindFace::query() m_ctx.face.processEvents(); } +std::set +FindFace::getFaceIds() const +{ + std::set faceIds; + for (const FaceStatus& faceStatus : m_results) { + faceIds.insert(faceStatus.getFaceId()); + } + return faceIds; +} + const FaceStatus& FindFace::getFaceStatus() const { diff --git a/tools/nfdc/find-face.hpp b/tools/nfdc/find-face.hpp index 2661aa6f..120718bd 100644 --- a/tools/nfdc/find-face.hpp +++ b/tools/nfdc/find-face.hpp @@ -92,6 +92,11 @@ public: return m_results; } + /** \return FaceId for all results + */ + std::set + getFaceIds() const; + /** \return a single face status * \pre getResults().size() == 1 */ diff --git a/tools/nfdc/rib-module.cpp b/tools/nfdc/rib-module.cpp index 4342894a..d7f04cd5 100644 --- a/tools/nfdc/rib-module.cpp +++ b/tools/nfdc/rib-module.cpp @@ -34,6 +34,19 @@ namespace nfdc { void RibModule::registerCommands(CommandParser& parser) { + CommandDefinition defRouteList("route", "list"); + defRouteList + .setTitle("print RIB routes") + .addArg("nexthop", ArgValueType::FACE_ID_OR_URI, Required::NO, Positional::YES) + .addArg("origin", ArgValueType::UNSIGNED, Required::NO, Positional::NO); + parser.addCommand(defRouteList, &RibModule::list); + + CommandDefinition defRouteShow("route", "show"); + defRouteShow + .setTitle("show routes toward a prefix") + .addArg("prefix", ArgValueType::NAME, Required::YES, Positional::YES); + parser.addCommand(defRouteShow, &RibModule::show); + CommandDefinition defRouteAdd("route", "add"); defRouteAdd .setTitle("add a route") @@ -55,6 +68,77 @@ RibModule::registerCommands(CommandParser& parser) parser.addCommand(defRouteRemove, &RibModule::remove); } +void +RibModule::list(ExecuteContext& ctx) +{ + auto nexthopIt = ctx.args.find("nexthop"); + std::set nexthops; + auto origin = ctx.args.getOptional("origin"); + + if (nexthopIt != ctx.args.end()) { + FindFace findFace(ctx); + FindFace::Code res = findFace.execute(nexthopIt->second, true); + + ctx.exitCode = static_cast(res); + switch (res) { + case FindFace::Code::OK: + break; + case FindFace::Code::ERROR: + case FindFace::Code::CANONIZE_ERROR: + case FindFace::Code::NOT_FOUND: + ctx.err << findFace.getErrorReason() << '\n'; + return; + default: + BOOST_ASSERT_MSG(false, "unexpected FindFace result"); + return; + } + + nexthops = findFace.getFaceIds(); + } + + listRoutesImpl(ctx, [&] (const RibEntry& entry, const Route& route) { + return (nexthops.empty() || nexthops.count(route.getFaceId()) > 0) && + (!origin || route.getOrigin() == *origin); + }); +} + +void +RibModule::show(ExecuteContext& ctx) +{ + auto prefix = ctx.args.get("prefix"); + + listRoutesImpl(ctx, [&] (const RibEntry& entry, const Route& route) { + return entry.getName() == prefix; + }); +} + +void +RibModule::listRoutesImpl(ExecuteContext& ctx, const RoutePredicate& filter) +{ + ctx.controller.fetch( + [&] (const std::vector& dataset) { + bool hasRoute = false; + for (const RibEntry& entry : dataset) { + for (const Route& route : entry.getRoutes()) { + if (filter(entry, route)) { + hasRoute = true; + formatRouteText(ctx.out, entry, route, true); + ctx.out << '\n'; + } + } + } + + if (!hasRoute) { + ctx.exitCode = 6; + ctx.err << "Route not found\n"; + } + }, + ctx.makeDatasetFailureHandler("RIB dataset"), + ctx.makeCommandOptions()); + + ctx.face.processEvents(); +} + void RibModule::add(ExecuteContext& ctx) { @@ -147,11 +231,11 @@ RibModule::remove(ExecuteContext& ctx) return; } - for (const FaceStatus& faceStatus : findFace.getResults()) { + for (uint64_t faceId : findFace.getFaceIds()) { ControlParameters unregisterParams; unregisterParams .setName(prefix) - .setFaceId(faceStatus.getFaceId()) + .setFaceId(faceId) .setOrigin(origin); ctx.controller.start( @@ -238,35 +322,50 @@ RibModule::formatStatusText(std::ostream& os) const { os << "RIB:\n"; for (const RibEntry& item : m_status) { - this->formatItemText(os, item); + os << " "; + formatEntryText(os, item); + os << '\n'; } } void -RibModule::formatItemText(std::ostream& os, const RibEntry& item) const +RibModule::formatEntryText(std::ostream& os, const RibEntry& entry) { - os << " " << item.getName() << " route={"; + os << entry.getName() << " routes={"; text::Separator sep(", "); - for (const Route& route : item.getRoutes()) { - os << sep - << "faceid=" << route.getFaceId() - << " (origin=" << route.getOrigin() - << " cost=" << route.getCost(); - if (route.hasExpirationPeriod()) { - os << " expires=" << text::formatDuration(route.getExpirationPeriod()); - } - if (route.isChildInherit()) { - os << " ChildInherit"; - } - if (route.isRibCapture()) { - os << " RibCapture"; - } - os << ")"; + for (const Route& route : entry.getRoutes()) { + os << sep; + formatRouteText(os, entry, route, false); } os << "}"; - os << "\n"; +} + +void +RibModule::formatRouteText(std::ostream& os, const RibEntry& entry, const Route& route, + bool includePrefix) +{ + text::ItemAttributes ia; + + if (includePrefix) { + os << ia("prefix") << entry.getName(); + } + os << ia("nexthop") << route.getFaceId(); + os << ia("origin") << static_cast(route.getOrigin()); + os << ia("cost") << route.getCost(); + os << ia("flags") << static_cast(route.getFlags()); + + // 'origin' field is printed as a number, because printing 'origin' as string may mislead user + // into passing strings to 'origin' command line argument which currently only accepts numbers. + ///\todo #3987 print 'origin' with RouteOrigin stream insertion operator + + if (route.hasExpirationPeriod()) { + os << ia("expires") << text::formatDuration(route.getExpirationPeriod()); + } + else { + os << ia("expires") << "never"; + } } } // namespace nfdc diff --git a/tools/nfdc/rib-module.hpp b/tools/nfdc/rib-module.hpp index aee2c8b0..ae53928a 100644 --- a/tools/nfdc/rib-module.hpp +++ b/tools/nfdc/rib-module.hpp @@ -47,6 +47,16 @@ public: static void registerCommands(CommandParser& parser); + /** \brief the 'route list' command + */ + static void + list(ExecuteContext& ctx); + + /** \brief the 'route show' command + */ + static void + show(ExecuteContext& ctx); + /** \brief the 'route add' command */ static void @@ -66,6 +76,15 @@ public: void formatStatusXml(std::ostream& os) const override; + void + formatStatusText(std::ostream& os) const override; + +private: + using RoutePredicate = function; + + static void + listRoutesImpl(ExecuteContext& ctx, const RoutePredicate& filter); + /** \brief format a single status item as XML * \param os output stream * \param item status item @@ -73,15 +92,22 @@ public: void formatItemXml(std::ostream& os, const RibEntry& item) const; - void - formatStatusText(std::ostream& os) const override; - - /** \brief format a single status item as text + /** \brief format a RibEntry as text * \param os output stream - * \param item status item + * \param entry RIB entry */ - void - formatItemText(std::ostream& os, const RibEntry& item) const; + static void + formatEntryText(std::ostream& os, const RibEntry& entry); + + /** \brief format a Route as text + * \param os output stream + * \param entry RIB entry + * \param route RIB route within \p entry + * \param includePrefix whether to print the name prefix + */ + static void + formatRouteText(std::ostream& os, const RibEntry& entry, const Route& route, + bool includePrefix); private: std::vector m_status; diff --git a/tools/nfdc/status.cpp b/tools/nfdc/status.cpp index 61655655..a09fd7e1 100644 --- a/tools/nfdc/status.cpp +++ b/tools/nfdc/status.cpp @@ -145,11 +145,6 @@ registerStatusCommands(CommandParser& parser) defFibList .setTitle("print FIB entries"); parser.addCommand(defFibList, bind(&reportStatusSingleSection, _1, &StatusReportOptions::wantFib)); - - CommandDefinition defRouteList("route", "list"); - defRouteList - .setTitle("print RIB entries"); - parser.addCommand(defRouteList, bind(&reportStatusSingleSection, _1, &StatusReportOptions::wantRib)); } } // namespace nfdc