tools: nfdc face destroy command
refs #3864
Change-Id: I1d070570c14364529c566273eba44b87413942b1
diff --git a/docs/manpages/nfdc-face.rst b/docs/manpages/nfdc-face.rst
index 86935d4..46d0ef5 100644
--- a/docs/manpages/nfdc-face.rst
+++ b/docs/manpages/nfdc-face.rst
@@ -6,7 +6,7 @@
| nfdc face [list]
| nfdc face show <FACEID>
| nfdc face create [remote] <FACEURI> [[persistency] <PERSISTENCY>]
-| nfdc destroy <FACEID|FACEURI>
+| nfdc face destroy <FACEID|FACEURI>
| nfdc channel [list]
DESCRIPTION
@@ -22,8 +22,7 @@
The **nfdc face create** command creates a unicast UDP or TCP face.
-The **nfdc destroy** command destroys an existing face.
-It has no effect if the specified face does not exist.
+The **nfdc face destroy** command destroys an existing face.
The **nfdc channel list** command shows a list of channels.
Channels are listening sockets that can accept incoming connections and create new faces.
@@ -57,9 +56,11 @@
2: Malformed command line
-3: Face not found (**nfdc face show** only)
+3: Face not found (**nfdc face show** and **nfdc face destroy** only)
-4: FaceUri canonization failed (**nfdc face create** only)
+4: FaceUri canonization failed (**nfdc face create** and **nfdc face destroy** only)
+
+5: Ambiguous: multiple matching faces are found (**nfdc face destroy** only)
SEE ALSO
--------
diff --git a/tests/tools/nfdc/face-module.t.cpp b/tests/tools/nfdc/face-module.t.cpp
index 20d5514..8d465e2 100644
--- a/tests/tools/nfdc/face-module.t.cpp
+++ b/tests/tools/nfdc/face-module.t.cpp
@@ -142,6 +142,159 @@
BOOST_AUTO_TEST_SUITE_END() // CreateCommand
+BOOST_FIXTURE_TEST_SUITE(DestroyCommand, ExecuteCommandFixture)
+
+BOOST_AUTO_TEST_CASE(NormalByFaceId)
+{
+ this->processInterest = [this] (const Interest& interest) {
+ if (Name("/localhost/nfd/faces/query").isPrefixOf(interest.getName())) {
+ BOOST_CHECK_EQUAL(interest.getName().size(), 5);
+ FaceQueryFilter filter(interest.getName().at(4).blockFromValue());
+ // BOOST_CHECK_EQUAL(filter, FaceQueryFilter().setFaceId(10156));
+ BOOST_CHECK_EQUAL(filter.getFaceId(), 10156);
+
+ FaceStatus faceStatus;
+ faceStatus.setFaceId(10156)
+ .setLocalUri("tcp4://151.26.163.27:22967")
+ .setRemoteUri("tcp4://198.57.27.40:6363")
+ .setFacePersistency(FacePersistency::FACE_PERSISTENCY_PERSISTENT);
+ this->sendDataset(interest.getName(), faceStatus);
+ return;
+ }
+
+ ControlParameters req = MOCK_NFD_MGMT_REQUIRE_LAST_COMMAND_IS("/localhost/nfd/faces/destroy");
+ BOOST_REQUIRE(req.hasFaceId());
+ BOOST_CHECK_EQUAL(req.getFaceId(), 10156);
+
+ ControlParameters resp;
+ resp.setFaceId(10156);
+ this->succeedCommand(resp);
+ };
+
+ this->execute("face destroy 10156");
+ BOOST_CHECK_EQUAL(exitCode, 0);
+ BOOST_CHECK(out.is_equal("face-destroyed id=10156 local=tcp4://151.26.163.27:22967 "
+ "remote=tcp4://198.57.27.40:6363 persistency=persistent\n"));
+ BOOST_CHECK(err.is_empty());
+}
+
+BOOST_AUTO_TEST_CASE(NormalByFaceUri)
+{
+ this->processInterest = [this] (const Interest& interest) {
+ if (Name("/localhost/nfd/faces/query").isPrefixOf(interest.getName())) {
+ BOOST_CHECK_EQUAL(interest.getName().size(), 5);
+ FaceQueryFilter filter(interest.getName().at(4).blockFromValue());
+ // BOOST_CHECK_EQUAL(filter, FaceQueryFilter().setRemoteUri("tcp4://32.121.182.82:6363"));
+ BOOST_CHECK_EQUAL(filter.getRemoteUri(), "tcp4://32.121.182.82:6363");
+
+ FaceStatus faceStatus;
+ faceStatus.setFaceId(2249)
+ .setLocalUri("tcp4://30.99.87.98:31414")
+ .setRemoteUri("tcp4://32.121.182.82:6363")
+ .setFacePersistency(FacePersistency::FACE_PERSISTENCY_PERSISTENT);
+ this->sendDataset(interest.getName(), faceStatus);
+ return;
+ }
+
+ ControlParameters req = MOCK_NFD_MGMT_REQUIRE_LAST_COMMAND_IS("/localhost/nfd/faces/destroy");
+ BOOST_REQUIRE(req.hasFaceId());
+ BOOST_CHECK_EQUAL(req.getFaceId(), 2249);
+
+ ControlParameters resp;
+ resp.setFaceId(2249);
+ this->succeedCommand(resp);
+ };
+
+ this->execute("face destroy tcp://32.121.182.82");
+ BOOST_CHECK_EQUAL(exitCode, 0);
+ BOOST_CHECK(out.is_equal("face-destroyed id=2249 local=tcp4://30.99.87.98:31414 "
+ "remote=tcp4://32.121.182.82:6363 persistency=persistent\n"));
+ BOOST_CHECK(err.is_empty());
+}
+
+BOOST_AUTO_TEST_CASE(FaceNotExist)
+{
+ this->processInterest = [this] (const Interest& interest) {
+ BOOST_CHECK(Name("/localhost/nfd/faces/query").isPrefixOf(interest.getName()));
+ this->sendEmptyDataset(interest.getName());
+ };
+
+ this->execute("face destroy 23728");
+ BOOST_CHECK_EQUAL(exitCode, 3);
+ BOOST_CHECK(out.is_empty());
+ BOOST_CHECK(err.is_equal("Face not found\n"));
+}
+
+BOOST_AUTO_TEST_CASE(Ambiguous)
+{
+ this->processInterest = [this] (const Interest& interest) {
+ BOOST_CHECK(Name("/localhost/nfd/faces/query").isPrefixOf(interest.getName()));
+
+ FaceStatus faceStatus1, faceStatus2;
+ faceStatus1.setFaceId(6720)
+ .setLocalUri("udp4://202.83.168.28:56363")
+ .setRemoteUri("udp4://225.131.75.231:56363")
+ .setFacePersistency(FacePersistency::FACE_PERSISTENCY_PERMANENT);
+ faceStatus2.setFaceId(31066)
+ .setLocalUri("udp4://25.90.26.32:56363")
+ .setRemoteUri("udp4://225.131.75.231:56363")
+ .setFacePersistency(FacePersistency::FACE_PERSISTENCY_PERMANENT);
+ this->sendDataset(interest.getName(), faceStatus1, faceStatus2);
+ };
+
+ this->execute("face destroy udp4://225.131.75.231:56363");
+ BOOST_CHECK_EQUAL(exitCode, 5);
+ BOOST_CHECK(out.is_empty());
+ BOOST_CHECK(err.is_equal("Multiple faces match specified remote FaceUri. "
+ "Re-run the command with a FaceId: "
+ "6720 (local=udp4://202.83.168.28:56363), "
+ "31066 (local=udp4://25.90.26.32:56363)\n"));
+}
+
+BOOST_AUTO_TEST_CASE(ErrorCanonization)
+{
+ this->execute("face destroy udp6://32.38.164.64:10445");
+ BOOST_CHECK_EQUAL(exitCode, 4);
+ BOOST_CHECK(out.is_empty());
+ BOOST_CHECK(err.is_equal("Error during remote FaceUri canonization: "
+ "No endpoints match the specified address selector\n"));
+}
+
+BOOST_AUTO_TEST_CASE(ErrorDataset)
+{
+ this->processInterest = nullptr; // no response to dataset or command
+
+ this->execute("face destroy udp://159.242.33.78");
+ BOOST_CHECK_EQUAL(exitCode, 1);
+ BOOST_CHECK(out.is_empty());
+ BOOST_CHECK(err.is_equal("Error 10060 when querying face: Timeout\n"));
+}
+
+BOOST_AUTO_TEST_CASE(ErrorCommand)
+{
+ this->processInterest = [this] (const Interest& interest) {
+ if (Name("/localhost/nfd/faces/query").isPrefixOf(interest.getName())) {
+ FaceStatus faceStatus;
+ faceStatus.setFaceId(17757)
+ .setLocalUri("tcp4://27.65.24.30:19187")
+ .setRemoteUri("tcp4://70.47.27.77:6363")
+ .setFacePersistency(FacePersistency::FACE_PERSISTENCY_PERSISTENT);
+ this->sendDataset(interest.getName(), faceStatus);
+ return;
+ }
+
+ MOCK_NFD_MGMT_REQUIRE_LAST_COMMAND_IS("/localhost/nfd/faces/destroy");
+ // no response to command
+ };
+
+ this->execute("face destroy 17757");
+ BOOST_CHECK_EQUAL(exitCode, 1);
+ BOOST_CHECK(out.is_empty());
+ BOOST_CHECK(err.is_equal("Error 10060 when destroying face: request timed out\n"));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // DestroyCommand
+
const std::string STATUS_XML = stripXmlSpaces(R"XML(
<faces>
<face>
diff --git a/tools/nfdc/face-module.cpp b/tools/nfdc/face-module.cpp
index 3c087f4..4dc732e 100644
--- a/tools/nfdc/face-module.cpp
+++ b/tools/nfdc/face-module.cpp
@@ -24,6 +24,7 @@
*/
#include "face-module.hpp"
+#include "find-face.hpp"
#include "format-helpers.hpp"
namespace nfd {
@@ -45,6 +46,12 @@
.addArg("remote", ArgValueType::FACE_URI, Required::YES, Positional::YES)
.addArg("persistency", ArgValueType::FACE_PERSISTENCY, Required::NO, Positional::YES);
parser.addCommand(defFaceCreate, &FaceModule::create);
+
+ CommandDefinition defFaceDestroy("face", "destroy");
+ defFaceDestroy
+ .setTitle("destroy a face")
+ .addArg("face", ArgValueType::FACE_ID_OR_URI, Required::YES, Positional::YES);
+ parser.addCommand(defFaceDestroy, &FaceModule::destroy);
}
void
@@ -101,6 +108,58 @@
}
void
+FaceModule::destroy(ExecuteContext& ctx)
+{
+ const boost::any faceIdOrUri = ctx.args.at("face");
+
+ FindFace findFace(ctx);
+ FindFace::Code res = FindFace::Code::ERROR;
+ const uint64_t* faceId = boost::any_cast<uint64_t>(&faceIdOrUri);
+ if (faceId != nullptr) {
+ res = findFace.execute(*faceId);
+ }
+ else {
+ res = findFace.execute(boost::any_cast<FaceUri>(faceIdOrUri));
+ }
+
+ ctx.exitCode = static_cast<int>(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;
+ case FindFace::Code::AMBIGUOUS:
+ ctx.err << "Multiple faces match specified remote FaceUri. Re-run the command with a FaceId:";
+ findFace.printDisambiguation(ctx.err, FindFace::DisambiguationStyle::LOCAL_URI);
+ ctx.err << '\n';
+ return;
+ default:
+ BOOST_ASSERT_MSG(false, "unexpected FindFace result");
+ return;
+ }
+
+ const FaceStatus& face = findFace.getFaceStatus();
+
+ ctx.controller.start<ndn::nfd::FaceDestroyCommand>(
+ ControlParameters().setFaceId(face.getFaceId()),
+ [&] (const ControlParameters& resp) {
+ ctx.out << "face-destroyed ";
+ text::ItemAttributes ia;
+ ctx.out << ia("id") << face.getFaceId()
+ << ia("local") << face.getLocalUri()
+ << ia("remote") << face.getRemoteUri()
+ << ia("persistency") << face.getFacePersistency() << '\n';
+ },
+ ctx.makeCommandFailureHandler("destroying face"),
+ ctx.makeCommandOptions());
+
+ ctx.face.processEvents();
+}
+
+void
FaceModule::fetchStatus(Controller& controller,
const function<void()>& onSuccess,
const Controller::DatasetFailCallback& onFailure,
diff --git a/tools/nfdc/face-module.hpp b/tools/nfdc/face-module.hpp
index 1bbc31b..3f70d48 100644
--- a/tools/nfdc/face-module.hpp
+++ b/tools/nfdc/face-module.hpp
@@ -56,6 +56,11 @@
static void
create(ExecuteContext& ctx);
+ /** \brief the 'face destroy' command
+ */
+ static void
+ destroy(ExecuteContext& ctx);
+
void
fetchStatus(Controller& controller,
const function<void()>& onSuccess,
diff --git a/tools/nfdc/find-face.cpp b/tools/nfdc/find-face.cpp
new file mode 100644
index 0000000..c03e6dc
--- /dev/null
+++ b/tools/nfdc/find-face.cpp
@@ -0,0 +1,149 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2017, 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 "find-face.hpp"
+#include "format-helpers.hpp"
+#include <ndn-cxx/util/logger.hpp>
+
+namespace nfd {
+namespace tools {
+namespace nfdc {
+
+NDN_LOG_INIT(nfdc.FindFace);
+
+FindFace::FindFace(ExecuteContext& ctx)
+ : m_ctx(ctx)
+{
+}
+
+FindFace::Code
+FindFace::execute(const FaceUri& faceUri, bool allowMulti)
+{
+ FaceQueryFilter filter;
+ filter.setRemoteUri(faceUri.toString());
+ return this->execute(filter);
+}
+
+FindFace::Code
+FindFace::execute(uint64_t faceId)
+{
+ FaceQueryFilter filter;
+ filter.setFaceId(faceId);
+ return this->execute(filter);
+}
+
+FindFace::Code
+FindFace::execute(const FaceQueryFilter& filter, bool allowMulti)
+{
+ BOOST_ASSERT(m_res == Code::NOT_STARTED);
+ m_res = Code::IN_PROGRESS;
+ m_filter = filter;
+
+ if (m_filter.hasRemoteUri()) {
+ auto remoteUri = this->canonize("remote", FaceUri(m_filter.getRemoteUri()));
+ if (!remoteUri) {
+ m_res = Code::CANONIZE_ERROR;
+ return m_res;
+ }
+ m_filter.setRemoteUri(remoteUri->toString());
+ }
+
+ ///\todo #3864 canonize localUri
+
+ this->query();
+ if (m_res == Code::OK) {
+ if (m_results.size() == 0) {
+ m_res = Code::NOT_FOUND;
+ m_errorReason = "Face not found";
+ }
+ else if (m_results.size() > 1 && !allowMulti) {
+ m_res = Code::AMBIGUOUS;
+ m_errorReason = "Multiple faces match the query";
+ }
+ }
+ return m_res;
+}
+
+ndn::optional<FaceUri>
+FindFace::canonize(const std::string& fieldName, const FaceUri& input)
+{
+ if (!FaceUri::canCanonize(input.getScheme())) {
+ NDN_LOG_DEBUG("Using " << fieldName << '=' << input << " without canonization");
+ return input;
+ }
+
+ ndn::optional<FaceUri> result;
+ input.canonize(
+ [&result] (const FaceUri& canonicalUri) { result = canonicalUri; },
+ [this, fieldName] (const std::string& errorReason) {
+ m_errorReason = "Error during " + fieldName + " FaceUri canonization: " + errorReason;
+ },
+ m_ctx.face.getIoService(), m_ctx.getTimeout());
+ m_ctx.face.processEvents();
+
+ return result;
+}
+
+void
+FindFace::query()
+{
+ m_ctx.controller.fetch<ndn::nfd::FaceQueryDataset>(
+ m_filter,
+ [this] (const std::vector<ndn::nfd::FaceStatus>& result) {
+ m_res = Code::OK;
+ m_results = result;
+ },
+ [this] (uint32_t code, const std::string& reason) {
+ m_res = Code::ERROR;
+ m_errorReason = "Error " + std::to_string(code) + " when querying face: " + reason;
+ },
+ m_ctx.makeCommandOptions());
+ m_ctx.face.processEvents();
+}
+
+const FaceStatus&
+FindFace::getFaceStatus() const
+{
+ BOOST_ASSERT(m_results.size() == 1);
+ return m_results.front();
+}
+
+void
+FindFace::printDisambiguation(std::ostream& os, DisambiguationStyle style) const
+{
+ text::Separator sep(" ", ", ");
+ for (const auto& item : m_results) {
+ os << sep;
+ switch (style) {
+ case DisambiguationStyle::LOCAL_URI:
+ os << item.getFaceId() << " (local=" << item.getLocalUri() << ')';
+ break;
+ }
+ }
+}
+
+} // namespace nfdc
+} // namespace tools
+} // namespace nfd
diff --git a/tools/nfdc/find-face.hpp b/tools/nfdc/find-face.hpp
new file mode 100644
index 0000000..57eb0f0
--- /dev/null
+++ b/tools/nfdc/find-face.hpp
@@ -0,0 +1,137 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2017, 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/>.
+ */
+
+#ifndef NFD_TOOLS_NFDC_FIND_FACE_HPP
+#define NFD_TOOLS_NFDC_FIND_FACE_HPP
+
+#include "execute-command.hpp"
+
+namespace nfd {
+namespace tools {
+namespace nfdc {
+
+using ndn::nfd::FaceQueryFilter;
+using ndn::nfd::FaceStatus;
+
+/** \brief procedure to find a face
+ */
+class FindFace : noncopyable
+{
+public:
+ enum class Code {
+ OK = 0, ///< found exactly one face, or found multiple faces when allowMulti is true
+ ERROR = 1, ///< unspecified error
+ NOT_FOUND = 3, ///< found zero face
+ CANONIZE_ERROR = 4, ///< error during FaceUri canonization
+ AMBIGUOUS = 5, ///< found multiple faces and allowMulti is false
+ NOT_STARTED = -1, ///< for internal use
+ IN_PROGRESS = -2, ///< for internal use
+ };
+
+ enum class DisambiguationStyle
+ {
+ LOCAL_URI = 1 ///< print FaceId and LocalUri
+ };
+
+ explicit
+ FindFace(ExecuteContext& ctx);
+
+ /** \brief find face by FaceUri
+ * \pre execute has not been invoked
+ */
+ Code
+ execute(const FaceUri& faceUri, bool allowMulti = false);
+
+ /** \brief find face by FaceId
+ * \pre execute has not been invoked
+ */
+ Code
+ execute(uint64_t faceId);
+
+ /** \brief find face by FaceQueryFilter
+ * \pre execute has not been invoked
+ */
+ Code
+ execute(const FaceQueryFilter& filter, bool allowMulti = false);
+
+ /** \return face status for all results
+ */
+ const std::vector<FaceStatus>&
+ getResults() const
+ {
+ return m_results;
+ }
+
+ /** \return a single face status
+ * \pre getResults().size() == 1
+ */
+ const FaceStatus&
+ getFaceStatus() const;
+
+ uint64_t
+ getFaceId() const
+ {
+ return this->getFaceStatus().getFaceId();
+ }
+
+ const std::string&
+ getErrorReason() const
+ {
+ return m_errorReason;
+ }
+
+ /** \brief print results for disambiguation
+ */
+ void
+ printDisambiguation(std::ostream& os, DisambiguationStyle style) const;
+
+private:
+ /** \brief canonize FaceUri
+ * \return canonical FaceUri if canonization succeeds, input if canonization is unsupported
+ * \retval nullopt canonization fails; m_errorReason describes the failure
+ */
+ ndn::optional<FaceUri>
+ canonize(const std::string& fieldName, const FaceUri& input);
+
+ /** \brief retrieve FaceStatus from filter
+ * \post m_res == Code::OK and m_results is populated if retrieval succeeds
+ * \post m_res == Code::ERROR and m_errorReason is set if retrieval fails
+ */
+ void
+ query();
+
+private:
+ ExecuteContext& m_ctx;
+ FaceQueryFilter m_filter;
+ Code m_res = Code::NOT_STARTED;
+ std::vector<FaceStatus> m_results;
+ std::string m_errorReason;
+};
+
+} // namespace nfdc
+} // namespace tools
+} // namespace nfd
+
+#endif // NFD_TOOLS_NFDC_FIND_FACE_HPP
diff --git a/wscript b/wscript
index fc5d999..9bb3e15 100644
--- a/wscript
+++ b/wscript
@@ -250,8 +250,6 @@
install_path="${MANDIR}/",
VERSION=VERSION)
bld.symlink_as('${MANDIR}/man1/nfdc-channel.1', 'nfdc-face.1')
- bld.symlink_as('${MANDIR}/man1/nfdc-create.1', 'nfdc-face.1')
- bld.symlink_as('${MANDIR}/man1/nfdc-destroy.1', 'nfdc-face.1')
bld.symlink_as('${MANDIR}/man1/nfdc-fib.1', 'nfdc-route.1')
bld.symlink_as('${MANDIR}/man1/nfdc-register.1', 'nfdc-route.1')
bld.symlink_as('${MANDIR}/man1/nfdc-unregister.1', 'nfdc-route.1')