mgmt: add faces/update command

refs #3731

Change-Id: I79777a10feecb2de83276371100cc86a43d0e76d
This commit is contained in:
Eric Newberry committed 2016-09-05 09:33:17 -07:00
1 parent 64567bb12a
commit b5aa7f58e8
5 files changed
+686 -45

No files matched your search

+89 -4
View File
@@ -59,6 +59,9 @@ FaceManager::FaceManager(FaceTable& faceTable, Dispatcher& dispatcher, CommandAu
registerCommandHandler<ndn::nfd::FaceCreateCommand>("create",
bind(&FaceManager::createFace, this, _2, _3, _4, _5));
registerCommandHandler<ndn::nfd::FaceUpdateCommand>("update",
bind(&FaceManager::updateFace, this, _2, _3, _4, _5));
registerCommandHandler<ndn::nfd::FaceDestroyCommand>("destroy",
bind(&FaceManager::destroyFace, this, _2, _3, _4, _5));
@@ -143,6 +146,8 @@ FaceManager::afterCreateFaceSuccess(const ControlParameters& parameters,
m_faceTable.add(newFace);
// TODO: #3731: Verify and add Flags
// Set ControlResponse parameters
response.setFaceId(newFace->getId());
response.setFacePersistency(newFace->getPersistency());
@@ -160,6 +165,69 @@ FaceManager::afterCreateFaceFailure(uint32_t status,
done(ControlResponse(status, reason));
}
void
FaceManager::updateFace(const Name& topPrefix, const Interest& interest,
const ControlParameters& parameters,
const ndn::mgmt::CommandContinuation& done)
{
FaceId faceId = parameters.getFaceId();
if (faceId == 0) {
// Self-updating
shared_ptr<lp::IncomingFaceIdTag> incomingFaceIdTag = interest.getTag<lp::IncomingFaceIdTag>();
if (incomingFaceIdTag == nullptr) {
NFD_LOG_TRACE("unable to determine face for self-update");
done(ControlResponse(404, "No FaceId specified and IncomingFaceId not available"));
return;
}
faceId = *incomingFaceIdTag;
}
Face* face = m_faceTable.get(faceId);
if (face == nullptr) {
NFD_LOG_TRACE("invalid face specified");
done(ControlResponse(404, "Specified face does not exist"));
return;
}
// Verify validity of requested changes
ControlParameters response;
bool areParamsValid = true;
if (parameters.hasFacePersistency()) {
// TODO #3232: Add FacePersistency updating
NFD_LOG_TRACE("received unsupported face persistency change");
areParamsValid = false;
response.setFacePersistency(parameters.getFacePersistency());
}
if (parameters.hasFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED) &&
parameters.getFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED) &&
face->getScope() != ndn::nfd::FACE_SCOPE_LOCAL) {
NFD_LOG_TRACE("received request to enable local fields on non-local face");
areParamsValid = false;
response.setFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED,
parameters.getFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED));
}
if (!areParamsValid) {
done(ControlResponse(409, "Invalid properties specified").setBody(response.wireEncode()));
return;
}
// All specified properties are valid, so make changes
// TODO #3232: Add FacePersistency updating
setLinkServiceOptions(*face, parameters, response);
// Set remaining ControlResponse fields
response.setFaceId(faceId);
response.setFacePersistency(face->getPersistency());
done(ControlResponse(200, "OK").setBody(response.wireEncode()));
}
void
FaceManager::destroyFace(const Name& topPrefix, const Interest& interest,
const ControlParameters& parameters,
@@ -183,8 +251,7 @@ FaceManager::enableLocalControl(const Name& topPrefix, const Interest& interest,
return;
}
// TODO#3226 redesign enable-local-control
// For now, enable-local-control will enable all local fields in GenericLinkService.
// enable-local-control will enable all local fields in GenericLinkService
auto service = dynamic_cast<face::GenericLinkService*>(face->getLinkService());
if (service == nullptr) {
return done(ControlResponse(503, "LinkService type not supported"));
@@ -208,8 +275,7 @@ FaceManager::disableLocalControl(const Name& topPrefix, const Interest& interest
return;
}
// TODO#3226 redesign disable-local-control
// For now, disable-local-control will disable all local fields in GenericLinkService.
// disable-local-control will disable all local fields in GenericLinkService
auto service = dynamic_cast<face::GenericLinkService*>(face->getLinkService());
if (service == nullptr) {
return done(ControlResponse(503, "LinkService type not supported"));
@@ -250,6 +316,25 @@ FaceManager::findFaceForLocalControl(const Interest& request,
return face;
}
void
FaceManager::setLinkServiceOptions(Face& face,
const ControlParameters& parameters,
ControlParameters& response)
{
auto linkService = dynamic_cast<face::GenericLinkService*>(face.getLinkService());
BOOST_ASSERT(linkService != nullptr);
auto options = linkService->getOptions();
if (parameters.hasFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED) &&
face.getScope() == ndn::nfd::FACE_SCOPE_LOCAL) {
options.allowLocalFields = parameters.getFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED);
}
linkService->setOptions(options);
// Set Flags for ControlResponse
response.setFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED, options.allowLocalFields, false);
}
void
FaceManager::listFaces(const Name& topPrefix, const Interest& interest,
ndn::mgmt::StatusDatasetContext& context)
+17 -1
View File
@@ -60,16 +60,27 @@ PUBLIC_WITH_TESTS_ELSE_PRIVATE: // ControlCommand
const ControlParameters& parameters,
const ndn::mgmt::CommandContinuation& done);
void
updateFace(const Name& topPrefix, const Interest& interest,
const ControlParameters& parameters,
const ndn::mgmt::CommandContinuation& done);
void
destroyFace(const Name& topPrefix, const Interest& interest,
const ControlParameters& parameters,
const ndn::mgmt::CommandContinuation& done);
/**
* \deprecated use Flags+Mask in faces/update instead
*/
void
enableLocalControl(const Name& topPrefix, const Interest& interest,
const ControlParameters& parameters,
const ndn::mgmt::CommandContinuation& done);
/**
* \deprecated use Flags+Mask in faces/update instead
*/
void
disableLocalControl(const Name& topPrefix, const Interest& interest,
const ControlParameters& parameters,
@@ -91,6 +102,11 @@ PUBLIC_WITH_TESTS_ELSE_PRIVATE: // helpers for ControlCommand
const ControlParameters& parameters,
const ndn::mgmt::CommandContinuation& done);
static void
setLinkServiceOptions(Face& face,
const ControlParameters& parameters,
ControlParameters& response);
PUBLIC_WITH_TESTS_ELSE_PRIVATE: // StatusDataset
void
listFaces(const Name& topPrefix, const Interest& interest,
@@ -151,9 +167,9 @@ private: // configuration
PUBLIC_WITH_TESTS_ELSE_PRIVATE:
std::map<std::string /*protocol*/, shared_ptr<ProtocolFactory>> m_factories;
FaceTable& m_faceTable;
private:
FaceTable& m_faceTable;
ndn::mgmt::PostNotification m_postNotification;
signal::ScopedConnection m_faceAddConn;
signal::ScopedConnection m_faceRemoveConn;
@@ -84,19 +84,6 @@ public:
}
};
class UdpFaceConnectToSelf // face that will cause afterCreateFaceFailure to be invoked
// fails because remote endpoint is prohibited
{
public:
ControlParameters
getParameters()
{
return ControlParameters()
.setUri("udp4://0.0.0.0:16363"); // cannot connect to self
}
};
class UdpFacePersistent
{
public:
@@ -121,41 +108,28 @@ public:
}
};
class Success
class UdpFaceConnectToSelf // face that will cause afterCreateFaceFailure to be invoked
// fails because remote endpoint is prohibited
{
public:
ControlResponse
getExpected()
ControlParameters
getParameters()
{
return ControlResponse()
.setCode(200)
.setText("OK");
}
};
template<int CODE>
class Failure
{
public:
ControlResponse
getExpected()
{
return ControlResponse()
.setCode(CODE)
.setText("Error"); // error description should not be checked
return ControlParameters()
.setUri("udp4://0.0.0.0:16363"); // cannot connect to self
}
};
namespace mpl = boost::mpl;
// pairs of CreateCommand and Success/Failure status
typedef mpl::vector<mpl::pair<TcpFaceOnDemand, Failure<406>>,
mpl::pair<TcpFacePersistent, Success>,
mpl::pair<TcpFacePermanent, Failure<406>>,
mpl::pair<UdpFaceOnDemand, Failure<406>>,
mpl::pair<UdpFacePersistent, Success>,
mpl::pair<UdpFacePermanent, Success>,
mpl::pair<UdpFaceConnectToSelf, Failure<406>>> Faces;
typedef mpl::vector<mpl::pair<TcpFaceOnDemand, CommandFailure<406>>,
mpl::pair<TcpFacePersistent, CommandSuccess>,
mpl::pair<TcpFacePermanent, CommandFailure<406>>,
mpl::pair<UdpFaceOnDemand, CommandFailure<406>>,
mpl::pair<UdpFacePersistent, CommandSuccess>,
mpl::pair<UdpFacePermanent, CommandSuccess>,
mpl::pair<UdpFaceConnectToSelf, CommandFailure<406>>> Faces;
BOOST_FIXTURE_TEST_CASE_TEMPLATE(NewFace, T, Faces, FaceManagerCommandFixture)
{
@@ -186,10 +160,28 @@ BOOST_FIXTURE_TEST_CASE_TEMPLATE(NewFace, T, Faces, FaceManagerCommandFixture)
BOOST_CHECK(actualParams.hasFaceId());
BOOST_CHECK_EQUAL(expectedParams.getFacePersistency(), actualParams.getFacePersistency());
if (actual.getCode() != 200) {
if (actual.getCode() == 200) {
if (expectedParams.hasFlags()) {
// TODO: #3731 check if Flags match
}
else {
// TODO: #3731 check if Flags at defaults
}
}
else {
BOOST_CHECK_EQUAL(expectedParams.getUri(), actualParams.getUri());
}
}
if (actual.getCode() != 200) {
// ensure face not created
FaceUri uri(FaceType().getParameters().getUri());
auto& faceTable = this->node1.manager.m_faceTable;
BOOST_CHECK(std::none_of(faceTable.begin(), faceTable.end(), [uri] (Face& face) {
return face.getRemoteUri() == uri;
}));
}
hasCallbackFired = true;
});
@@ -0,0 +1,523 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/**
* Copyright (c) 2014-2016, 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 "face-manager-command-fixture.hpp"
#include "nfd-manager-common-fixture.hpp"
namespace nfd {
namespace tests {
BOOST_AUTO_TEST_SUITE(Mgmt)
BOOST_AUTO_TEST_SUITE(TestFaceManager)
class FaceManagerUpdateFixture : public FaceManagerCommandFixture
{
public:
FaceManagerUpdateFixture()
: faceId(0)
{
}
~FaceManagerUpdateFixture()
{
destroyFace();
}
void
createFace(const std::string& uri = "tcp4://127.0.0.1:26363",
ndn::nfd::FacePersistency persistency = ndn::nfd::FACE_PERSISTENCY_PERSISTENT,
bool enableLocalFields = false)
{
ControlParameters params;
params.setUri(uri);
params.setFacePersistency(persistency);
params.setFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED, enableLocalFields);
Name commandName("/localhost/nfd/faces/create");
commandName.append(params.wireEncode());
auto command = makeInterest(commandName);
m_keyChain.sign(*command);
bool hasCallbackFired = false;
signal::ScopedConnection connection = this->node1.face.onSendData.connect(
[this, command, &hasCallbackFired] (const Data& response) {
if (!command->getName().isPrefixOf(response.getName())) {
return;
}
ControlResponse create(response.getContent().blockFromValue());
BOOST_REQUIRE_EQUAL(create.getCode(), 200);
if (create.getBody().hasWire()) {
ControlParameters faceParams(create.getBody());
BOOST_REQUIRE(faceParams.hasFaceId());
this->faceId = faceParams.getFaceId();
}
else {
BOOST_FAIL("Face creation failed");
}
hasCallbackFired = true;
});
this->node1.face.receive(*command);
this->advanceClocks(time::milliseconds(1), 5);
BOOST_REQUIRE(hasCallbackFired);
}
void
updateFace(const ControlParameters& requestParams,
bool isSelfUpdating,
const function<void(const ControlResponse& resp)>& checkResp)
{
Name commandName("/localhost/nfd/faces/update");
commandName.append(requestParams.wireEncode());
auto command = makeInterest(commandName);
if (isSelfUpdating) {
// Attach IncomingFaceIdTag to interest
command->setTag(make_shared<lp::IncomingFaceIdTag>(faceId));
}
m_keyChain.sign(*command);
bool hasCallbackFired = false;
signal::ScopedConnection connection = this->node1.face.onSendData.connect(
[this, command, &hasCallbackFired, &checkResp] (const Data& response) {
if (!command->getName().isPrefixOf(response.getName())) {
return;
}
ControlResponse actual(response.getContent().blockFromValue());
checkResp(actual);
hasCallbackFired = true;
});
this->node1.face.receive(*command);
this->advanceClocks(time::milliseconds(1), 5);
BOOST_CHECK(hasCallbackFired);
}
private:
void
destroyFace()
{
if (faceId == 0) {
return;
}
ControlParameters params;
params.setFaceId(faceId);
Name commandName("/localhost/nfd/faces/destroy");
commandName.append(params.wireEncode());
auto command = makeInterest(commandName);
m_keyChain.sign(*command);
bool hasCallbackFired = false;
signal::ScopedConnection connection = this->node1.face.onSendData.connect(
[this, command, &hasCallbackFired] (const Data& response) {
if (!command->getName().isPrefixOf(response.getName())) {
return;
}
ControlResponse destroy(response.getContent().blockFromValue());
BOOST_CHECK_EQUAL(destroy.getCode(), 200);
faceId = 0;
hasCallbackFired = true;
});
this->node1.face.receive(*command);
this->advanceClocks(time::milliseconds(1), 5);
BOOST_CHECK(hasCallbackFired);
}
public:
FaceId faceId;
};
BOOST_FIXTURE_TEST_SUITE(UpdateFace, FaceManagerUpdateFixture)
BOOST_AUTO_TEST_CASE(FaceDoesNotExist)
{
ControlParameters requestParams;
requestParams.setFaceId(65535);
updateFace(requestParams, false, [] (const ControlResponse& actual) {
ControlResponse expected(404, "Specified face does not exist");
BOOST_CHECK_EQUAL(actual.getCode(), expected.getCode());
BOOST_TEST_MESSAGE(actual.getText());
});
}
// TODO #3232: Expected failure until FacePersistency updating implemented
BOOST_AUTO_TEST_CASE(UpdatePersistency)
{
createFace();
ControlParameters requestParams;
requestParams.setFaceId(faceId);
requestParams.setFacePersistency(ndn::nfd::FACE_PERSISTENCY_PERMANENT);
updateFace(requestParams, false, [] (const ControlResponse& actual) {
ControlResponse expected(409, "Invalid fields specified");
BOOST_CHECK_EQUAL(actual.getCode(), expected.getCode());
BOOST_TEST_MESSAGE(actual.getText());
if (actual.getBody().hasWire()) {
ControlParameters actualParams(actual.getBody());
BOOST_REQUIRE(actualParams.hasFacePersistency());
BOOST_CHECK_EQUAL(actualParams.getFacePersistency(), ndn::nfd::FACE_PERSISTENCY_PERMANENT);
}
else {
BOOST_ERROR("Response does not contain ControlParameters");
}
});
}
class TcpLocalFieldsEnable
{
public:
std::string
getUri()
{
return "tcp4://127.0.0.1:26363";
}
boost::asio::ip::address_v4
getIpAddress()
{
return boost::asio::ip::address_v4::from_string("127.0.0.1");
}
ndn::nfd::FacePersistency
getPersistency()
{
return ndn::nfd::FACE_PERSISTENCY_PERSISTENT;
}
bool
getInitLocalFieldsEnabled()
{
return false;
}
bool
getLocalFieldsEnabled()
{
return true;
}
bool
getLocalFieldsEnabledMask()
{
return true;
}
bool
shouldHaveWire()
{
return false;
}
};
class TcpLocalFieldsDisable
{
public:
std::string
getUri()
{
return "tcp4://127.0.0.1:26363";
}
ndn::nfd::FacePersistency
getPersistency()
{
return ndn::nfd::FACE_PERSISTENCY_PERSISTENT;
}
bool
getInitLocalFieldsEnabled()
{
return true;
}
bool
getLocalFieldsEnabled()
{
return false;
}
bool
getLocalFieldsEnabledMask()
{
return true;
}
bool
shouldHaveWire()
{
return false;
}
};
// UDP faces are non-local by definition
class UdpLocalFieldsEnable
{
public:
std::string
getUri()
{
return "udp4://127.0.0.1:26363";
}
ndn::nfd::FacePersistency
getPersistency()
{
return ndn::nfd::FACE_PERSISTENCY_PERSISTENT;
}
bool
getInitLocalFieldsEnabled()
{
return false;
}
bool
getLocalFieldsEnabled()
{
return true;
}
bool
getLocalFieldsEnabledMask()
{
return true;
}
bool
shouldHaveWire()
{
return true;
}
};
// UDP faces are non-local by definition
// In this test case, attempt to disable local fields on face with local fields already disabled
class UdpLocalFieldsDisable
{
public:
std::string
getUri()
{
return "udp4://127.0.0.1:26363";
}
ndn::nfd::FacePersistency
getPersistency()
{
return ndn::nfd::FACE_PERSISTENCY_PERSISTENT;
}
bool
getInitLocalFieldsEnabled()
{
return false;
}
bool
getLocalFieldsEnabled()
{
return false;
}
bool
getLocalFieldsEnabledMask()
{
return true;
}
bool
shouldHaveWire()
{
return false;
}
};
// In this test case, set Flags to enable local fields on non-local face, but exclude local fields
// from Mask. This test case will pass as no action is taken due to the missing Mask bit.
class UdpLocalFieldsEnableNoMaskBit
{
public:
std::string
getUri()
{
return "udp4://127.0.0.1:26363";
}
ndn::nfd::FacePersistency
getPersistency()
{
return ndn::nfd::FACE_PERSISTENCY_PERSISTENT;
}
bool
getInitLocalFieldsEnabled()
{
return false;
}
bool
getLocalFieldsEnabled()
{
return true;
}
bool
getLocalFieldsEnabledMask()
{
return false;
}
bool
shouldHaveWire()
{
return false;
}
};
namespace mpl = boost::mpl;
typedef mpl::vector<mpl::pair<TcpLocalFieldsEnable, CommandSuccess>,
mpl::pair<TcpLocalFieldsDisable, CommandSuccess>,
mpl::pair<UdpLocalFieldsEnable, CommandFailure<409>>,
mpl::pair<UdpLocalFieldsDisable, CommandSuccess>,
mpl::pair<UdpLocalFieldsEnableNoMaskBit, CommandSuccess>> LocalFieldFaces;
BOOST_AUTO_TEST_CASE_TEMPLATE(UpdateLocalFields, T, LocalFieldFaces)
{
typedef typename T::first TestType;
typedef typename T::second ResultType;
createFace(TestType().getUri(), TestType().getPersistency(), TestType().getInitLocalFieldsEnabled());
ControlParameters requestParams;
requestParams.setFaceId(faceId);
requestParams.setFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED, TestType().getLocalFieldsEnabled());
if (!TestType().getLocalFieldsEnabledMask()) {
requestParams.unsetFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED);
}
updateFace(requestParams, false, [] (const ControlResponse& actual) {
ControlResponse expected(ResultType().getExpected().getCode(), "");
BOOST_CHECK_EQUAL(actual.getCode(), expected.getCode());
BOOST_TEST_MESSAGE(actual.getText());
if (TestType().shouldHaveWire() && actual.getBody().hasWire()) {
ControlParameters actualParams(actual.getBody());
BOOST_CHECK(!actualParams.hasFacePersistency());
BOOST_CHECK(actualParams.hasFlags());
BOOST_CHECK(actualParams.getFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED));
BOOST_CHECK(actualParams.hasFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED));
}
});
}
BOOST_AUTO_TEST_CASE(UpdateLocalFieldsEnableDisable)
{
createFace();
ControlParameters enableParams;
enableParams.setFaceId(faceId);
enableParams.setFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED, true);
ControlParameters disableParams;
disableParams.setFaceId(faceId);
disableParams.setFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED, false);
updateFace(enableParams, false, [] (const ControlResponse& actual) {
ControlResponse expected(200, "OK");
BOOST_CHECK_EQUAL(actual.getCode(), expected.getCode());
BOOST_TEST_MESSAGE(actual.getText());
if (actual.getBody().hasWire()) {
ControlParameters actualParams(actual.getBody());
BOOST_CHECK(actualParams.hasFaceId());
BOOST_CHECK(actualParams.hasFacePersistency());
BOOST_REQUIRE(actualParams.hasFlags());
// Check if flags indicate local fields enabled
BOOST_CHECK(actualParams.getFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED));
}
else {
BOOST_ERROR("Enable: Response does not contain ControlParameters");
}
});
updateFace(disableParams, false, [] (const ControlResponse& actual) {
ControlResponse expected(200, "OK");
BOOST_CHECK_EQUAL(actual.getCode(), expected.getCode());
BOOST_TEST_MESSAGE(actual.getText());
if (actual.getBody().hasWire()) {
ControlParameters actualParams(actual.getBody());
BOOST_CHECK(actualParams.hasFaceId());
BOOST_CHECK(actualParams.hasFacePersistency());
BOOST_REQUIRE(actualParams.hasFlags());
// Check if flags indicate local fields disabled
BOOST_CHECK(!actualParams.getFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED));
}
else {
BOOST_ERROR("Disable: Response does not contain ControlParameters");
}
});
}
BOOST_AUTO_TEST_CASE(SelfUpdating)
{
createFace();
// Send a command that does nothing (will return 200) and does not contain a FaceId
ControlParameters sentParams;
updateFace(sentParams, true, [] (const ControlResponse& actual) {
ControlResponse expected(200, "OK");
BOOST_REQUIRE_EQUAL(actual.getCode(), expected.getCode());
BOOST_TEST_MESSAGE(actual.getText());
});
}
BOOST_AUTO_TEST_SUITE_END() // UpdateFace
BOOST_AUTO_TEST_SUITE_END() // TestFaceManager
BOOST_AUTO_TEST_SUITE_END() // Mgmt
} // namespace tests
} // namespace nfd
@@ -55,6 +55,31 @@ protected:
shared_ptr<CommandAuthenticator> m_authenticator;
};
class CommandSuccess
{
public:
ControlResponse
getExpected()
{
return ControlResponse()
.setCode(200)
.setText("OK");
}
};
template<int CODE>
class CommandFailure
{
public:
ControlResponse
getExpected()
{
return ControlResponse()
.setCode(CODE);
// error description should not be checked
}
};
} // namespace tests
} // namespace nfd