mgmt refactoring: StrategyChoiceManager

Change-Id: Ib6df3f627070a3b6d4777f8efbe30e601e4c1512
Refs: #2107
diff --git a/daemon/mgmt/strategy-choice-manager.cpp b/daemon/mgmt/strategy-choice-manager.cpp
new file mode 100644
index 0000000..fba7f4c
--- /dev/null
+++ b/daemon/mgmt/strategy-choice-manager.cpp
@@ -0,0 +1,99 @@
+/* -*- 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 "strategy-choice-manager.hpp"
+#include "table/strategy-choice.hpp"
+#include <ndn-cxx/management/nfd-strategy-choice.hpp>
+
+namespace nfd {
+
+NFD_LOG_INIT("StrategyChoiceManager");
+
+StrategyChoiceManager::StrategyChoiceManager(StrategyChoice& strategyChoice,
+                                             Dispatcher& dispatcher,
+                                             CommandValidator& validator)
+  : ManagerBase(dispatcher, validator, "strategy-choice")
+  , m_strategyChoice(strategyChoice)
+{
+  registerCommandHandler<ndn::nfd::StrategyChoiceSetCommand>("set",
+    bind(&StrategyChoiceManager::setStrategy, this, _2, _3, _4, _5));
+  registerCommandHandler<ndn::nfd::StrategyChoiceUnsetCommand>("unset",
+    bind(&StrategyChoiceManager::unsetStrategy, this, _2, _3, _4, _5));
+
+  registerStatusDatasetHandler("list",
+    bind(&StrategyChoiceManager::listChoices, this, _1, _2, _3));
+}
+
+void
+StrategyChoiceManager::setStrategy(const Name& topPrefix, const Interest& interest,
+                                   ControlParameters parameters,
+                                   const ndn::mgmt::CommandContinuation& done)
+{
+  const Name& prefix = parameters.getName();
+  const Name& selectedStrategy = parameters.getStrategy();
+
+  if (!m_strategyChoice.hasStrategy(selectedStrategy)) {
+    NFD_LOG_DEBUG("strategy-choice result: FAIL reason: unknown-strategy: "
+                  << parameters.getStrategy());
+    return done(ControlResponse(504, "Unsupported strategy"));
+  }
+
+  if (m_strategyChoice.insert(prefix, selectedStrategy)) {
+    NFD_LOG_DEBUG("strategy-choice result: SUCCESS");
+    auto currentStrategyChoice = m_strategyChoice.get(prefix);
+    BOOST_ASSERT(currentStrategyChoice.first);
+    parameters.setStrategy(currentStrategyChoice.second);
+    return done(ControlResponse(200, "OK").setBody(parameters.wireEncode()));
+  }
+  else {
+    NFD_LOG_DEBUG("strategy-choice result: FAIL reason: not-installed");
+    return done(ControlResponse(405, "Strategy not installed"));
+  }
+}
+
+void
+StrategyChoiceManager::unsetStrategy(const Name& topPrefix, const Interest& interest,
+                                     ControlParameters parameters,
+                                     const ndn::mgmt::CommandContinuation& done)
+{
+  m_strategyChoice.erase(parameters.getName());
+
+  NFD_LOG_DEBUG("strategy-choice result: SUCCESS");
+  done(ControlResponse(200, "OK").setBody(parameters.wireEncode()));
+}
+
+void
+StrategyChoiceManager::listChoices(const Name& topPrefix, const Interest& interest,
+                                   ndn::mgmt::StatusDatasetContext& context)
+{
+  for (auto&& i : m_strategyChoice) {
+    ndn::nfd::StrategyChoice entry;
+    entry.setName(i.getPrefix()).setStrategy(i.getStrategyName());
+    context.append(entry.wireEncode());
+  }
+  context.end();
+}
+
+} // namespace
diff --git a/daemon/mgmt/strategy-choice-manager.hpp b/daemon/mgmt/strategy-choice-manager.hpp
new file mode 100644
index 0000000..74c54db
--- /dev/null
+++ b/daemon/mgmt/strategy-choice-manager.hpp
@@ -0,0 +1,67 @@
+/* -*- 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/>.
+ */
+
+#ifndef NFD_DAEMON_MGMT_STRATEGY_CHOICE_MANAGER_HPP
+#define NFD_DAEMON_MGMT_STRATEGY_CHOICE_MANAGER_HPP
+
+#include "manager-base.hpp"
+
+namespace nfd {
+
+class StrategyChoice;
+
+/**
+ * @brief implement the Strategy Choice Management of NFD Management Protocol.
+ * @sa http://redmine.named-data.net/projects/nfd/wiki/StrategyChoice
+ */
+class StrategyChoiceManager : public ManagerBase
+{
+public:
+  StrategyChoiceManager(StrategyChoice& strategyChoice,
+                        Dispatcher& dispatcher,
+                        CommandValidator& validator);
+
+private:
+  void
+  setStrategy(const Name& topPrefix, const Interest& interest,
+              ControlParameters parameters,
+              const ndn::mgmt::CommandContinuation& done);
+
+  void
+  unsetStrategy(const Name& topPrefix, const Interest& interest,
+                ControlParameters parameters,
+                const ndn::mgmt::CommandContinuation& done);
+
+  void
+  listChoices(const Name& topPrefix, const Interest& interest,
+              ndn::mgmt::StatusDatasetContext& context);
+
+private:
+  StrategyChoice& m_strategyChoice;
+};
+
+} // namespace nfd
+
+#endif // NFD_DAEMON_MGMT_STRATEGY_CHOICE_MANAGER_HPP
diff --git a/daemon/nfd.cpp b/daemon/nfd.cpp
index d42be83..6bf3831 100644
--- a/daemon/nfd.cpp
+++ b/daemon/nfd.cpp
@@ -35,7 +35,7 @@
 #include "face/internal-client-face.hpp"
 #include "mgmt/fib-manager.hpp"
 #include "mgmt/face-manager.hpp"
-// #include "mgmt/strategy-choice-manager.hpp"
+#include "mgmt/strategy-choice-manager.hpp"
 #include "mgmt/forwarder-status-manager.hpp"
 #include "mgmt/general-config-section.hpp"
 #include "mgmt/tables-config-section.hpp"
@@ -147,8 +147,9 @@
                                       *m_dispatcher,
                                       *m_validator));
 
-  // m_strategyChoiceManager.reset(new StrategyChoiceManager(m_forwarder->getStrategyChoice(),
-  //                                                         m_internalFace, m_keyChain));
+  m_strategyChoiceManager.reset(new StrategyChoiceManager(m_forwarder->getStrategyChoice(),
+                                                          *m_dispatcher,
+                                                          *m_validator));
 
   m_forwarderStatusManager.reset(new ForwarderStatusManager(*m_forwarder, *m_dispatcher));
 
diff --git a/daemon/nfd.hpp b/daemon/nfd.hpp
index 667ff94..34111b3 100644
--- a/daemon/nfd.hpp
+++ b/daemon/nfd.hpp
@@ -115,7 +115,7 @@
   unique_ptr<ndn::mgmt::Dispatcher>  m_dispatcher;
   unique_ptr<FibManager>             m_fibManager;
   unique_ptr<FaceManager>            m_faceManager;
-  // unique_ptr<StrategyChoiceManager> m_strategyChoiceManager;
+  unique_ptr<StrategyChoiceManager>  m_strategyChoiceManager;
   unique_ptr<ForwarderStatusManager> m_forwarderStatusManager;
 
   ndn::util::NetworkMonitor          m_networkMonitor;
diff --git a/tests/daemon/mgmt/strategy-choice-manager.t.cpp b/tests/daemon/mgmt/strategy-choice-manager.t.cpp
new file mode 100644
index 0000000..9e7a8c2
--- /dev/null
+++ b/tests/daemon/mgmt/strategy-choice-manager.t.cpp
@@ -0,0 +1,248 @@
+/* -*- 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/strategy-choice-manager.hpp"
+#include "manager-common-fixture.hpp"
+
+#include "face/face.hpp"
+#include "face/internal-face.hpp"
+#include "table/name-tree.hpp"
+#include "table/strategy-choice.hpp"
+#include "fw/strategy.hpp"
+#include "tests/daemon/face/dummy-face.hpp"
+#include "tests/daemon/fw/dummy-strategy.hpp"
+
+#include <ndn-cxx/util/random.hpp>
+#include <ndn-cxx/management/nfd-strategy-choice.hpp>
+
+namespace nfd {
+namespace tests {
+
+class StrategyChoiceManagerFixture : public ManagerCommonFixture
+{
+public:
+  StrategyChoiceManagerFixture()
+    : m_strategyChoice(m_forwarder.getStrategyChoice())
+    , m_manager(m_strategyChoice, m_dispatcher, m_validator)
+  {
+    setTopPrefixAndPrivilege("/localhost/nfd", "strategy-choice");
+  }
+
+public:
+  void
+  installStrategy(const Name& strategy)
+  {
+    m_strategyChoice.install(make_shared<DummyStrategy>(ref(m_forwarder), strategy));
+  }
+
+  const Name&
+  findStrategy(const Name& name)
+  {
+    return m_strategyChoice.findEffectiveStrategy(name).getName();
+  }
+
+  ControlParameters
+  makeParameters(const Name& name, const Name& strategy)
+  {
+    return ControlParameters().setName(name).setStrategy(strategy);
+  }
+
+protected:
+  StrategyChoice& m_strategyChoice;
+  StrategyChoiceManager m_manager;
+};
+
+BOOST_FIXTURE_TEST_SUITE(Mgmt, StrategyChoiceManagerFixture)
+BOOST_AUTO_TEST_SUITE(TestStrategyChoiceManager)
+
+BOOST_AUTO_TEST_CASE(SetStrategy)
+{
+  auto testSetStrategy = [this] (const ControlParameters& parameters) -> Name {
+    m_responses.clear();
+    auto command = makeControlCommandRequest("/localhost/nfd/strategy-choice/set", parameters);
+    receiveInterest(command);
+    return command->getName();
+  };
+
+  installStrategy("/localhost/nfd/strategy/test-strategy-a");
+  installStrategy("/localhost/nfd/strategy/test-strategy-c/%FD%01"); // version 1
+  installStrategy("/localhost/nfd/strategy/test-strategy-c/%FD%02"); // version 2
+
+  auto parametersA  = makeParameters("test", "/localhost/nfd/strategy/test-strategy-a");
+  auto parametersB  = makeParameters("test", "/localhost/nfd/strategy/test-strategy-b");
+  auto parametersC1 = makeParameters("test", "/localhost/nfd/strategy/test-strategy-c/%FD%01");
+  auto parametersC  = makeParameters("test", "/localhost/nfd/strategy/test-strategy-c");
+
+  auto commandNameA = testSetStrategy(parametersA); // succeed
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 1);
+  BOOST_CHECK_EQUAL(checkResponse(0, commandNameA, makeResponse(200, "OK", parametersA)),
+                    CheckResponseResult::OK);
+  BOOST_CHECK_EQUAL(findStrategy("/test"), "/localhost/nfd/strategy/test-strategy-a");
+
+  auto commandNameB = testSetStrategy(parametersB); // not installed
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 1);
+  BOOST_CHECK_EQUAL(checkResponse(0, commandNameB, ControlResponse(504, "Unsupported strategy")),
+                    CheckResponseResult::OK);
+
+  auto commandNameC1 = testSetStrategy(parametersC1); // specified version
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 1);
+  BOOST_CHECK_EQUAL(checkResponse(0, commandNameC1, makeResponse(200, "OK", parametersC1)),
+                    CheckResponseResult::OK);
+  BOOST_CHECK_EQUAL(findStrategy("/test"), "/localhost/nfd/strategy/test-strategy-c/%FD%01");
+
+  auto commandNameC = testSetStrategy(parametersC); // latest version
+  parametersC.setStrategy("/localhost/nfd/strategy/test-strategy-c/%FD%02"); // change to latest
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 1);
+  BOOST_CHECK_EQUAL(checkResponse(0, commandNameC, makeResponse(200, "OK", parametersC)),
+                    CheckResponseResult::OK);
+  BOOST_CHECK_EQUAL(findStrategy("/test"), "/localhost/nfd/strategy/test-strategy-c/%FD%02");
+}
+
+BOOST_AUTO_TEST_CASE(UnsetStrategy)
+{
+  auto testUnsetStrategy = [this] (const ControlParameters& parameters) -> Name {
+    m_responses.clear();
+    auto command = makeControlCommandRequest("/localhost/nfd/strategy-choice/unset", parameters);
+    receiveInterest(command);
+    return command->getName();
+  };
+
+  installStrategy("/localhost/nfd/strategy/test-strategy-a");
+  installStrategy("/localhost/nfd/strategy/test-strategy-b");
+  installStrategy("/localhost/nfd/strategy/test-strategy-c");
+
+  BOOST_CHECK(m_strategyChoice.insert("ndn:/", "/localhost/nfd/strategy/test-strategy-a")); // root
+  BOOST_CHECK(m_strategyChoice.insert("/test", "/localhost/nfd/strategy/test-strategy-b")); // test
+  BOOST_CHECK_EQUAL(findStrategy("/"), "/localhost/nfd/strategy/test-strategy-a");
+
+  auto parametersRoot = ControlParameters().setName("ndn:/"); // root prefix
+  auto parametersNone = ControlParameters().setName("/none"); // no such entry
+  auto parametersTest = ControlParameters().setName("/test"); // has such entry
+
+  BOOST_CHECK_EQUAL(findStrategy("/"), "/localhost/nfd/strategy/test-strategy-a"); // root
+  auto commandRootName = testUnsetStrategy(parametersRoot);
+  auto expectedResponse = ControlResponse(400, "failed in validating parameters");
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 1);
+  BOOST_CHECK_EQUAL(checkResponse(0, commandRootName, expectedResponse), CheckResponseResult::OK);
+  BOOST_CHECK_EQUAL(findStrategy("/"), "/localhost/nfd/strategy/test-strategy-a"); // keep as root
+
+  BOOST_CHECK_EQUAL(findStrategy("/none"), "/localhost/nfd/strategy/test-strategy-a"); // root
+  auto commandNoneName = testUnsetStrategy(parametersNone);
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 1);
+  BOOST_CHECK_EQUAL(checkResponse(0, commandNoneName, makeResponse(200, "OK", parametersNone)),
+                    CheckResponseResult::OK);
+  BOOST_CHECK_EQUAL(findStrategy("/none"), "/localhost/nfd/strategy/test-strategy-a"); // root
+
+  BOOST_CHECK_EQUAL(findStrategy("/test"), "/localhost/nfd/strategy/test-strategy-b"); // self
+  auto commandTestName = testUnsetStrategy(parametersTest);
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 1);
+  BOOST_CHECK_EQUAL(checkResponse(0, commandTestName, makeResponse(200, "OK", parametersTest)),
+                    CheckResponseResult::OK);
+  BOOST_CHECK_EQUAL(findStrategy("/test"), "/localhost/nfd/strategy/test-strategy-a"); // parent
+}
+
+// @todo Remove when ndn::nfd::StrategyChoice implements operator!= and operator<<
+class StrategyChoice : public ndn::nfd::StrategyChoice
+{
+public:
+  StrategyChoice() = default;
+
+  StrategyChoice(const ndn::nfd::StrategyChoice& entry)
+    : ndn::nfd::StrategyChoice(entry)
+  {
+  }
+};
+
+bool
+operator!=(const StrategyChoice& left, const StrategyChoice& right)
+{
+  return left.getName() != right.getName() || left.getStrategy() != right.getStrategy();
+}
+
+std::ostream&
+operator<<(std::ostream &os, const StrategyChoice& entry)
+{
+  os << "[ " << entry.getName() << ", " << entry.getStrategy() << " ]";
+  return os;
+}
+
+BOOST_AUTO_TEST_CASE(ListChoices)
+{
+  size_t nPreInsertedStrategies = m_strategyChoice.size(); // the best-route strategy
+  std::set<Name> actualNames, actualStrategies;
+  for (auto&& entry : m_strategyChoice) {
+    actualNames.insert(entry.getPrefix());
+    actualStrategies.insert(entry.getStrategyName());
+  }
+
+  size_t nEntries = 1024;
+  for (size_t i = 0 ; i < nEntries ; i++) {
+    auto name = Name("test-name").appendSegment(i);
+    auto strategy = Name("test-strategy").appendSegment(ndn::random::generateWord64());
+    auto entry = ndn::nfd::StrategyChoice().setName(name).setStrategy(strategy);
+    actualNames.insert(name);
+    actualStrategies.insert(strategy);
+    installStrategy(strategy);
+    m_strategyChoice.insert(name, strategy);
+  }
+  nEntries += nPreInsertedStrategies;
+
+  receiveInterest(makeInterest("/localhost/nfd/strategy-choice/list"));
+
+  Block content;
+  BOOST_CHECK_NO_THROW(content = concatenateResponses());
+  BOOST_CHECK_NO_THROW(content.parse());
+  BOOST_CHECK_EQUAL(content.elements().size(), nEntries);
+
+  std::vector<StrategyChoice> receivedRecords, expectedRecords;
+  for (size_t idx = 0; idx < nEntries; ++idx) {
+    BOOST_TEST_MESSAGE("processing element: " << idx);
+
+    StrategyChoice decodedEntry;
+    BOOST_REQUIRE_NO_THROW(decodedEntry.wireDecode(content.elements()[idx]));
+    receivedRecords.push_back(decodedEntry);
+
+    actualNames.erase(decodedEntry.getName());
+    actualStrategies.erase(decodedEntry.getStrategy());
+
+    auto result = m_strategyChoice.get(decodedEntry.getName());
+    BOOST_REQUIRE(result.first);
+
+    auto record = StrategyChoice().setName(decodedEntry.getName()).setStrategy(result.second);
+    expectedRecords.push_back(record);
+  }
+
+  BOOST_CHECK_EQUAL(actualNames.size(), 0);
+  BOOST_CHECK_EQUAL(actualStrategies.size(), 0);
+
+  BOOST_CHECK_EQUAL_COLLECTIONS(receivedRecords.begin(), receivedRecords.end(),
+                                expectedRecords.begin(), expectedRecords.end());
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestStrategyChoiceManager
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd