From: Francis Dupont Date: Sun, 7 Oct 2018 21:10:09 +0000 (+0200) Subject: [153-netconf-control-socket] Added control socket code from kea-yang X-Git-Tag: 171-keactrl-tests-not-posix_base~4 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8366e9916789955a612f148fb27a6ad35426753d;p=thirdparty%2Fkea.git [153-netconf-control-socket] Added control socket code from kea-yang --- diff --git a/src/bin/netconf/Makefile.am b/src/bin/netconf/Makefile.am index 70c29377d2..73b3aa4019 100644 --- a/src/bin/netconf/Makefile.am +++ b/src/bin/netconf/Makefile.am @@ -44,7 +44,11 @@ BUILT_SOURCES = netconf_messages.h netconf_messages.cc noinst_LTLIBRARIES = libnetconf.la -libnetconf_la_SOURCES = netconf_cfg_mgr.cc netconf_cfg_mgr.h +libnetconf_la_SOURCES = control_socket.cc control_socket.h +libnetconf_la_SOURCES += http_control_socket.cc http_control_socket.h +libnetconf_la_SOURCES += stdout_control_socket.cc stdout_control_socket.h +libnetconf_la_SOURCES += unix_control_socket.cc unix_control_socket.h +libnetconf_la_SOURCES += netconf_cfg_mgr.cc netconf_cfg_mgr.h libnetconf_la_SOURCES += netconf_config.cc netconf_config.h libnetconf_la_SOURCES += netconf_controller.cc netconf_controller.h libnetconf_la_SOURCES += netconf_log.cc netconf_log.h diff --git a/src/bin/netconf/control_socket.cc b/src/bin/netconf/control_socket.cc new file mode 100644 index 0000000000..5b18cd23e4 --- /dev/null +++ b/src/bin/netconf/control_socket.cc @@ -0,0 +1,41 @@ +// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +/// @file stdout_control_socket.cc +/// Contains the stdout derived class for control socket communication. + +#include + +#include +#include +#include +#include + +using namespace std; + +namespace isc { +namespace netconf { + +ControlSocketBasePtr +createControlSocket(CfgControlSocketPtr ctrl_sock) { + if (!ctrl_sock) { + isc_throw(BadValue, "null control socket configuration"); + } + CfgControlSocket::Type sock_type = ctrl_sock->getType(); + switch (sock_type) { + case CfgControlSocket::Type::UNIX: + return (createControlSocket(ctrl_sock)); + case CfgControlSocket::Type::HTTP: + return (createControlSocket(ctrl_sock)); + case CfgControlSocket::Type::STDOUT: + return (createControlSocket(ctrl_sock)); + default: + isc_throw(BadValue, "Unknown control socket type: " << sock_type); + } +} + +} // namespace netconf +} // namespace isc diff --git a/src/bin/netconf/control_socket.h b/src/bin/netconf/control_socket.h new file mode 100644 index 0000000000..c9ad087655 --- /dev/null +++ b/src/bin/netconf/control_socket.h @@ -0,0 +1,130 @@ +// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +/// @file control_socket.h +/// Contains declarations for control socket communication. + +#ifndef CONTROL_SOCKET_H +#define CONTROL_SOCKET_H + +#include +#include + +namespace isc { +namespace netconf { + +/// @brief Exception thrown when the error during communication. +class ControlSocketError : public isc::Exception { +public: + ControlSocketError(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// @brief Base class for control socket communication. +/// +/// This class is the base class for control socket communication. +/// Derived classes implement config-get, config-test and config-set +/// using control sockets of different types. +class ControlSocketBase { +public: + + /// @brief Constructor. + /// + /// @param ctrl_sock The control socket configuration. + /// @throw ControlSocketError if ctrl_sock is null. + ControlSocketBase(CfgControlSocketPtr ctrl_sock) : socket_cfg_(ctrl_sock) { + if (!ctrl_sock) { + isc_throw(ControlSocketError, "ControlSocket constructor called " + "with a null configuration"); + } + } + + /// @brief Destructor (does nothing). + virtual ~ControlSocketBase() { + } + + /// @brief Getter which returns the socket type. + /// + /// @return returns the socket type as a CfgControlSocket::Type. + CfgControlSocket::Type getType() const { + return (socket_cfg_->getType()); + } + + /// @brief Getter which returns the Unix socket name. + /// + /// @return returns the Unix socket name as a std::string. + const std::string getName() const { + return (socket_cfg_->getName()); + } + + /// @brief Getter which returns the HTTP server URL. + /// + /// @return returns the HTTP server URL as an isc::http::Url. + const isc::http::Url getUrl() const { + return (socket_cfg_->getUrl()); + } + + /// @brief Get configuration. + /// + /// Call config-get over the control socket. + /// + /// @param service The target service (used by http). + /// @return The JSON element answer of config-get. + /// @throw ControlSocketError when a communication error occurs. + virtual data::ConstElementPtr configGet(const std::string& service) = 0; + + /// @brief Test configuration. + /// + /// Call config-test over the control socket. + /// + /// @param config The configuration to test. + /// @param service The target service (used by http). + /// @return The JSON element answer of config-test. + /// @throw ControlSocketError when a communication error occurs. + virtual data::ConstElementPtr configTest(data::ConstElementPtr config, + const std::string& service) = 0; + + /// @brief Set configuration. + /// + /// Call config-set over the control socket. + /// + /// @param config The configuration to set. + /// @param service The target service (used by http). + /// @return The JSON element answer of config-set. + /// @throw ControlSocketError when a communication error occurs. + virtual data::ConstElementPtr configSet(data::ConstElementPtr config, + const std::string& service) = 0; + + /// @brief The control socket configuration. + CfgControlSocketPtr socket_cfg_; +}; + +/// @brief Type definition for the pointer to the @c ControlSocketBase. +typedef boost::shared_ptr ControlSocketBasePtr; + +/// @brief Factory template for control sockets. +/// +/// @tparam TYPE The control socket type. +/// @param ctrl_sock The control socket configuration. +/// @return A pointer to a control socket communication object. +/// @throw NotImplemented if no specialization was called. +template ControlSocketBasePtr +createControlSocket(CfgControlSocketPtr ctrl_sock) { + isc_throw(NotImplemented, "not specialized createControlSocket"); +} + +/// @brief Factory function for control sockets. +/// +/// @param ctrl_sock The control socket configuration. +/// @return A pointer to a control socket communication object. +/// @throw BadValue if called with null or an unknown type. +ControlSocketBasePtr +createControlSocket(CfgControlSocketPtr ctrl_sock); + +} // namespace netconf +} // namespace isc + +#endif // CONTROL_SOCKET_H diff --git a/src/bin/netconf/http_control_socket.cc b/src/bin/netconf/http_control_socket.cc new file mode 100644 index 0000000000..b3d91796d9 --- /dev/null +++ b/src/bin/netconf/http_control_socket.cc @@ -0,0 +1,130 @@ +// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +/// @file http_control_socket.cc +/// Contains the HTTP socket derived class for control socket communication. + +#include + +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace isc::asiolink; +using namespace isc::config; +using namespace isc::data; +using namespace isc::http; + +namespace isc { +namespace netconf { + +template <> ControlSocketBasePtr +createControlSocket(CfgControlSocketPtr ctrl_sock) { + return (HttpControlSocketPtr(new HttpControlSocket(ctrl_sock))); +} + +HttpControlSocket::HttpControlSocket(CfgControlSocketPtr ctrl_sock) + : ControlSocketBase(ctrl_sock) { +} + +HttpControlSocket::~HttpControlSocket() { +} + +ConstElementPtr +HttpControlSocket::configGet(const string& service) { + if (service == "ca") { + return (sendCommand(createCommand("config-get"))); + } else { + return (sendCommand(createCommand("config-get", service))); + } +} + +ConstElementPtr +HttpControlSocket::configTest(ConstElementPtr config, const string& service) { + if (service == "ca") { + return (sendCommand(createCommand("config-test", config))); + } else { + return (sendCommand(createCommand("config-test", config, service))); + } +} + +ConstElementPtr +HttpControlSocket::configSet(ConstElementPtr config, const string& service) { + if (service == "ca") { + return (sendCommand(createCommand("config-set", config))); + } else { + return (sendCommand(createCommand("config-set", config, service))); + } +} + +ConstElementPtr +HttpControlSocket::sendCommand(ConstElementPtr command) { + PostHttpRequestJsonPtr request; + request.reset(new PostHttpRequestJson(HttpRequest::Method::HTTP_POST, + "/", + HttpVersion(1, 1))); + request->setBodyAsJson(command); + try { + request->finalize(); + } catch (const std::exception& ex) { + isc_throw(ControlSocketError, "failed to create request: " + << ex.what()); + } + + IOServicePtr io_service(new IOService()); + HttpClient client(*io_service); + boost::system::error_code received_ec; + string receive_errmsg; + HttpResponseJsonPtr response(new HttpResponseJson()); + + client.asyncSendRequest(getUrl(), request, response, + [&io_service, &received_ec, &receive_errmsg] + (const boost::system::error_code& ec, + const HttpResponsePtr&, const string& errmsg) { + // Capture error code and message. + received_ec = ec; + receive_errmsg = errmsg; + // Got the IO service so stop IO service. + // This causes to stop IO service when + // all handlers have been invoked. + io_service->stopWork(); + }, + HttpClient::RequestTimeout(TIMEOUT_AGENT_FORWARD_COMMAND)); + + // Perform this synchronously. + io_service->run(); + + if (received_ec) { + // Got an error code. + isc_throw(ControlSocketError, "communication error (code): " + << received_ec.message()); + } + + if (!receive_errmsg.empty()) { + // Got an error message. + isc_throw(ControlSocketError, "communication error (message): " + << receive_errmsg); + } + + if (!response) { + // Failed to get the answer. + isc_throw(ControlSocketError, "empty response"); + } + + try { + return (response->getBodyAsJson()); + } catch (const std::exception& ex) { + isc_throw(ControlSocketError, "unparsable response: " << ex.what()); + } +} + +} // namespace netconf +} // namespace isc + diff --git a/src/bin/netconf/http_control_socket.h b/src/bin/netconf/http_control_socket.h new file mode 100644 index 0000000000..752f13b0ec --- /dev/null +++ b/src/bin/netconf/http_control_socket.h @@ -0,0 +1,86 @@ +// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +/// @file http_control_socket.h +/// Contains declarations for HTTP control socket communication. + +#ifndef HTTP_CONTROL_SOCKET_H +#define HTTP_CONTROL_SOCKET_H + +#include + +namespace isc { +namespace netconf { + +/// @brief Class for control socket communication over HTTP socket. +/// +/// This class is the derived class for control socket communication +/// over HTTP sockets. +/// This class implements config-get, config-test and config-set. +class HttpControlSocket : public ControlSocketBase { +public: + + /// @brief Constructor. + /// + /// @param ctrl_sock The control socket configuration. + HttpControlSocket(CfgControlSocketPtr ctrl_sock); + + /// @brief Destructor (does nothing). + virtual ~HttpControlSocket(); + + /// @brief Get configuration. + /// + /// Call config-get over the control socket. + /// + /// @param service The target service. + /// @return The JSON element answer of config-get. + /// @throw ControlSocketError when a communication error occurs. + virtual data::ConstElementPtr configGet(const std::string& service); + + /// @brief Test configuration. + /// + /// Call config-test over the control socket. + /// + /// @param config The configuration to test. + /// @param service The target service. + /// @return The JSON element answer of config-test. + /// @throw ControlSocketError when a communication error occurs. + virtual data::ConstElementPtr configTest(data::ConstElementPtr config, + const std::string& service); + + /// @brief Set configuration. + /// + /// Call config-set over the control socket. + /// + /// @param config The configuration to set. + /// @param service The target service. + /// @return The JSON element answer of config-set. + /// @throw ControlSocketError when a communication error occurs. + virtual data::ConstElementPtr configSet(data::ConstElementPtr config, + const std::string& service); + +private: + /// @brief Preform the actual communication. + /// + /// @param command The command to send. + /// @return The answer. + data::ConstElementPtr sendCommand(data::ConstElementPtr command); +}; + +/// @brief Type definition for the pointer to the @c HttpControlSocket. +typedef boost::shared_ptr HttpControlSocketPtr; + +/// @brief Factory template specialization for http control sockets. +/// +/// @param ctrl_sock The control socket configuration. +/// @return A pointer to a http control socket communication object. +template <> ControlSocketBasePtr +createControlSocket(CfgControlSocketPtr ctrl_sock); + +} // namespace netconf +} // namespace isc + +#endif // HTTP_CONTROL_SOCKET_H diff --git a/src/bin/netconf/stdout_control_socket.cc b/src/bin/netconf/stdout_control_socket.cc new file mode 100644 index 0000000000..d0219016e0 --- /dev/null +++ b/src/bin/netconf/stdout_control_socket.cc @@ -0,0 +1,62 @@ +// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +/// @file stdout_control_socket.cc +/// Contains the stdout derived class for control socket communication. + +#include + +#include +#include + +using namespace std; +using namespace isc::config; +using namespace isc::data; + +namespace isc { +namespace netconf { + +template <> ControlSocketBasePtr +createControlSocket(CfgControlSocketPtr ctrl_sock) { + return (StdoutControlSocketPtr(new StdoutControlSocket(ctrl_sock))); +} + +StdoutControlSocket::StdoutControlSocket(CfgControlSocketPtr ctrl_sock) + : ControlSocketBase(ctrl_sock), output_(cout) { +} + +StdoutControlSocket::StdoutControlSocket(CfgControlSocketPtr ctrl_sock, + ostream& output) + : ControlSocketBase(ctrl_sock), output_(output) { +} + +StdoutControlSocket::~StdoutControlSocket() { +} + +ConstElementPtr +StdoutControlSocket::configGet(const string& /*service*/) { + isc_throw(NotImplemented, "No config-get for stdout control socket"); +} + +ConstElementPtr +StdoutControlSocket::configTest(ConstElementPtr /*config*/, + const string& /*service*/) { + return (createAnswer()); +} + +ConstElementPtr +StdoutControlSocket::configSet(ConstElementPtr config, + const string& service) { + output_ << "//////////////// " + << service << " configuration " + << "////////////////" << endl; + prettyPrint(config, output_); + output_ << endl; + return (createAnswer()); +} + +} // namespace netconf +} // namespace isc diff --git a/src/bin/netconf/stdout_control_socket.h b/src/bin/netconf/stdout_control_socket.h new file mode 100644 index 0000000000..9b0d1f343d --- /dev/null +++ b/src/bin/netconf/stdout_control_socket.h @@ -0,0 +1,90 @@ +// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +/// @file stdout_control_socket.h +/// Contains declarations for stdout control socket communication. + +#ifndef STDOUT_CONTROL_SOCKET_H +#define STDOUT_CONTROL_SOCKET_H + +#include +#include + +namespace isc { +namespace netconf { + +/// @brief Class for control socket communication over stdout. +/// +/// This class is the derived class for control socket communication +/// over stdout. +/// This class implements config-test (always OK) and config-set. +class StdoutControlSocket : public ControlSocketBase { +public: + + /// @brief Constructor. + /// + /// Use std::cout. + /// + /// @param ctrl_sock The control socket configuration. + StdoutControlSocket(CfgControlSocketPtr ctrl_sock); + + /// @brief Destructor (does nothing). + virtual ~StdoutControlSocket(); + + /// @brief Get configuration. + /// + /// Call config-get over the control socket. + /// + /// @param service The target service (ignored). + /// @return The JSON element answer of config-get. + /// @throw NotImplemented + virtual data::ConstElementPtr configGet(const std::string& service); + + /// @brief Test configuration. + /// + /// Call config-test over the control socket. + /// + /// @param config The configuration to test (ignored). + /// @param service The target service (ignored). + /// @return The JSON element answer of config-test (fixed answer). + virtual data::ConstElementPtr configTest(data::ConstElementPtr config, + const std::string& service); + + /// @brief Set configuration. + /// + /// Call config-set over the control socket. + /// + /// @param config The configuration to set. + /// @param service The target service. + /// @return The JSON element answer of config-set (fixed answer). + virtual data::ConstElementPtr configSet(data::ConstElementPtr config, + const std::string& service); + +protected: + /// @brief Alternative constructor for tests. + /// + /// @param ctrl_sock The control socket configuration. + /// @param output The output stream. + StdoutControlSocket(CfgControlSocketPtr ctrl_sock, std::ostream& output); + + /// @brief The output stream (std::cout outside tests). + std::ostream& output_; +}; + +/// @brief Type definition for the pointer to the @c StdoutControlSocket. +typedef boost::shared_ptr StdoutControlSocketPtr; + +/// @brief Factory template specialization for stdout control sockets. +/// +/// @param ctrl_sock The control socket configuration. +/// @return A pointer to a stdout control socket communication object. +template <> ControlSocketBasePtr +createControlSocket(CfgControlSocketPtr ctrl_sock); + +} // namespace netconf +} // namespace isc + +#endif // STDOUT_CONTROL_SOCKET_H diff --git a/src/bin/netconf/tests/Makefile.am b/src/bin/netconf/tests/Makefile.am index 0c14ae51e8..1da8e6af9d 100644 --- a/src/bin/netconf/tests/Makefile.am +++ b/src/bin/netconf/tests/Makefile.am @@ -44,7 +44,8 @@ noinst_LTLIBRARIES = libbasic.la TESTS += netconf_unittests -netconf_unittests_SOURCES = get_config_unittest.cc +netconf_unittests_SOURCES = control_socket_unittests.cc +netconf_unittests_SOURCES += get_config_unittest.cc netconf_unittests_SOURCES += netconf_cfg_mgr_unittests.cc netconf_unittests_SOURCES += netconf_controller_unittests.cc netconf_unittests_SOURCES += netconf_process_unittests.cc diff --git a/src/bin/netconf/tests/control_socket_unittests.cc b/src/bin/netconf/tests/control_socket_unittests.cc new file mode 100644 index 0000000000..a6941340ef --- /dev/null +++ b/src/bin/netconf/tests/control_socket_unittests.cc @@ -0,0 +1,864 @@ +// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace isc; +using namespace isc::netconf; +using namespace isc::asiolink; +using namespace isc::data; +using namespace isc::http; +using namespace isc::http::test; +using namespace isc::util::thread; + +namespace { + +//////////////////////////////// STDOUT //////////////////////////////// + +/// @brief Test derived StdoutControlSocket class. +/// +/// This class exposes the constructor taking the output stream. +class TestStdoutControlSocket : public StdoutControlSocket { +public: + /// @brief Constructor. + /// + /// @param ctrl_sock The control socket configuration. + /// @param output The output stream. + TestStdoutControlSocket(CfgControlSocketPtr ctrl_sock, ostream& output) + : StdoutControlSocket(ctrl_sock, output) { + } + + /// @brief Destructor. + virtual ~TestStdoutControlSocket() { + } +}; + +/// @brief Type definition for the pointer to the @c TestStdoutControlSocket. +typedef boost::shared_ptr TestStdoutControlSocketPtr; + +// Verifies that the createControlSocket template can create a stdout +// control socket. +TEST(StdoutControlSocketTest, createControlSocket) { + CfgControlSocketPtr cfg(new + CfgControlSocket(CfgControlSocket::Type::STDOUT, + "", + Url("http://127.0.0.1:8000/"))); + ASSERT_TRUE(cfg); + ControlSocketBasePtr cs = createControlSocket(cfg); + ASSERT_TRUE(cs); + StdoutControlSocketPtr scs = + boost::dynamic_pointer_cast(cs); + EXPECT_TRUE(scs); +} + +// Verifies that a stdout control socket does not implement configGet. +TEST(StdoutControlSocketTest, configGet) { + CfgControlSocketPtr cfg(new + CfgControlSocket(CfgControlSocket::Type::STDOUT, + "", + Url("http://127.0.0.1:8000/"))); + ASSERT_TRUE(cfg); + StdoutControlSocketPtr scs(new StdoutControlSocket(cfg)); + ASSERT_TRUE(scs); + EXPECT_THROW(scs->configGet("foo"), NotImplemented); +} + +// Verifies that a stdout control socket does not nothing for configTest. +TEST(StdoutControlSocketTest, configTest) { + CfgControlSocketPtr cfg(new + CfgControlSocket(CfgControlSocket::Type::STDOUT, + "", + Url("http://127.0.0.1:8000/"))); + ASSERT_TRUE(cfg); + StdoutControlSocketPtr scs(new StdoutControlSocket(cfg)); + ASSERT_TRUE(scs); + ConstElementPtr answer; + ASSERT_NO_THROW(answer = scs->configTest(ConstElementPtr(), "foo")); + + // Check answer. + ASSERT_TRUE(answer); + EXPECT_EQ("{ \"result\": 0 }", answer->str()); +} + +// Verifies that a stdout control socket outputs configSet argument. +TEST(StdoutControlSocketTest, configSet) { + CfgControlSocketPtr cfg(new + CfgControlSocket(CfgControlSocket::Type::STDOUT, + "", + Url("http://127.0.0.1:8000/"))); + ASSERT_TRUE(cfg); + ostringstream os; + TestStdoutControlSocketPtr tscs(new TestStdoutControlSocket(cfg, os)); + ASSERT_TRUE(tscs); + ConstElementPtr json = Element::fromJSON("{ \"bar\": 1 }"); + ConstElementPtr answer; + ASSERT_NO_THROW(answer = tscs->configSet(json, "foo")); + + // Check answer. + ASSERT_TRUE(answer); + EXPECT_EQ("{ \"result\": 0 }", answer->str()); + + // Check output. + string expected = "//////////////// foo configuration ////////////////\n" + "{\n \"bar\": 1\n}\n"; + EXPECT_EQ(expected, os.str()); +} + +//////////////////////////////// UNIX //////////////////////////////// + +/// @brief Test unix socket file name. +const string TEST_SOCKET = "test-socket"; + +/// @brief Test timeout in ms. +const long TEST_TIMEOUT = 10000; + +/// @brief Type definition for the pointer to Thread objects.. +typedef boost::shared_ptr ThreadPtr; + +/// @brief Test fixture class for unix control sockets. +class UnixControlSocketTest : public ::testing::Test { +public: + /// @brief Constructor. + UnixControlSocketTest() : io_service_(), thread_(), ready_(false) { + removeUnixSocketFile(); + } + + /// @brief Destructor. + virtual ~UnixControlSocketTest() { + io_service_.stop(); + if (thread_) { + thread_->wait(); + thread_.reset(); + } + removeUnixSocketFile(); + } + + /// @brief Returns socket file path. + /// + /// If the KEA_SOCKET_TEST_DIR environment variable is specified, the + /// socket file is created in the location pointed to by this variable. + /// Otherwise, it is created in the build directory. + static string unixSocketFilePath() { + ostringstream s; + const char* env = getenv("KEA_SOCKET_TEST_DIR"); + if (env) { + s << string(env); + } else { + s << TEST_DATA_BUILDDIR; + } + + s << "/" << TEST_SOCKET; + return (s.str()); + } + + /// @brief Removes unix socket descriptor. + void removeUnixSocketFile() { + static_cast(remove(unixSocketFilePath().c_str())); + } + + /// @brief Create configuration of the control socket. + /// + /// @return a pointer to a control socket configuration. + CfgControlSocketPtr createCfgControlSocket() { + CfgControlSocketPtr cfg; + cfg.reset(new CfgControlSocket(CfgControlSocket::Type::UNIX, + unixSocketFilePath(), + Url("http://127.0.0.1:8000/"))); + return (cfg); + } + + /// @brief Thread reflecting server function. + void reflectServer(); + + /// @brief Thread timeout server function. + void timeoutServer(); + + /// @brief IOService object. + IOService io_service_; + + /// @brief Pointer to server thread. + ThreadPtr thread_; + + /// @brief Ready flag. + bool ready_; +}; + +/// @brief Server method running in a thread reflecting the command. +/// +/// It creates the server socket, accepts client connection, read +/// the command and send it back in a received JSON map. +void +UnixControlSocketTest::reflectServer() { + // Acceptor. + boost::asio::local::stream_protocol::acceptor + acceptor(io_service_.get_io_service()); + EXPECT_NO_THROW(acceptor.open()); + boost::asio::local::stream_protocol::endpoint + endpoint(unixSocketFilePath()); + EXPECT_NO_THROW(acceptor.bind(endpoint)); + EXPECT_NO_THROW(acceptor.listen()); + boost::asio::local::stream_protocol::socket + socket(io_service_.get_io_service()); + + // Ready. + ready_ = true; + + // Timeout. + IntervalTimer timer(io_service_); + bool timeout = false; + timer.setup([&timeout]() { + timeout = true; + FAIL() << "timeout"; + }, 1500, IntervalTimer::ONE_SHOT); + + // Accept. + bool accepted = false; + boost::system::error_code ec; + acceptor.async_accept(socket, + [&ec, &accepted] + (const boost::system::error_code& error) { + ec = error; + accepted = true; + }); + while (!accepted && !timeout) { + io_service_.run_one(); + } + ASSERT_FALSE(ec); + + // Receive command. + string rbuf(1024, ' '); + size_t received; + socket.async_receive(boost::asio::buffer(&rbuf[0], rbuf.size()), + [&ec, &received] + (const boost::system::error_code& error, size_t cnt) { + ec = error; + received = cnt; + }); + while (!received && !timeout) { + io_service_.run_one(); + } + ASSERT_FALSE(ec); + rbuf.resize(received); + + // Reflect. + ElementPtr map = Element::createMap(); + map->set("received", Element::create(rbuf)); + string sbuf = map->str(); + + // Send back. + size_t sent; + socket.async_send(boost::asio::buffer(&sbuf[0], sbuf.size()), + [&ec, &sent] + (const boost::system::error_code& error, size_t cnt) { + ec = error; + sent = cnt; + }); + while (!sent && !timeout) { + io_service_.run_one(); + } + ASSERT_FALSE(ec); + + // Stop timer. + timer.cancel(); + + EXPECT_FALSE(timeout); + EXPECT_TRUE(accepted); + EXPECT_TRUE(received); + EXPECT_TRUE(sent); + EXPECT_EQ(sent, sbuf.size()); + + if (socket.is_open()) { + EXPECT_NO_THROW(socket.close()); + } +} + +/// @brief Server method running in a thread waiting forever. +/// +/// It waits that ready becomes true. +void +UnixControlSocketTest::timeoutServer() { + while (!ready_) { + usleep(1000); + } +} + +// Verifies that the createControlSocket template can create an unix +// control socket. +TEST_F(UnixControlSocketTest, createControlSocket) { + CfgControlSocketPtr cfg = createCfgControlSocket(); + ASSERT_TRUE(cfg); + ControlSocketBasePtr cs = createControlSocket(cfg); + ASSERT_TRUE(cs); + UnixControlSocketPtr ucs = + boost::dynamic_pointer_cast(cs); + EXPECT_TRUE(ucs); +} + +// Verifies that unix control sockets handle configGet() as expected. +TEST_F(UnixControlSocketTest, configGet) { + CfgControlSocketPtr cfg = createCfgControlSocket(); + ASSERT_TRUE(cfg); + UnixControlSocketPtr ucs(new UnixControlSocket(cfg)); + ASSERT_TRUE(ucs); + + // Run a reflecting server in a thread. + thread_.reset(new Thread([this]() { reflectServer(); })); + while (!ready_) { + usleep(1000); + } + + // Try configGet. + ConstElementPtr reflected; + EXPECT_NO_THROW(reflected = ucs->configGet("foo")); + ASSERT_TRUE(reflected); + ASSERT_EQ(Element::map, reflected->getType()); + ConstElementPtr command = reflected->get("received"); + ASSERT_TRUE(command); + ASSERT_EQ(Element::string, command->getType()); + string expected = "{ \"command\": \"config-get\" }"; + EXPECT_EQ(expected, command->stringValue()); +} + +// Verifies that unix control sockets handle configTest() as expected. +TEST_F(UnixControlSocketTest, configTest) { + CfgControlSocketPtr cfg = createCfgControlSocket(); + ASSERT_TRUE(cfg); + UnixControlSocketPtr ucs(new UnixControlSocket(cfg)); + ASSERT_TRUE(ucs); + + // Run a reflecting server in a thread. + thread_.reset(new Thread([this]() { reflectServer(); })); + while (!ready_) { + usleep(1000); + } + + // Prepare a config to test. + ConstElementPtr json = Element::fromJSON("{ \"bar\": 1 }"); + + ConstElementPtr reflected; + EXPECT_NO_THROW(reflected = ucs->configTest(json, "foo")); + ASSERT_TRUE(reflected); + ASSERT_EQ(Element::map, reflected->getType()); + ConstElementPtr command = reflected->get("received"); + ASSERT_TRUE(command); + ASSERT_EQ(Element::string, command->getType()); + string expected = "{ \"arguments\": { \"bar\": 1 }, " + "\"command\": \"config-test\" }"; + EXPECT_EQ(expected, command->stringValue()); +} + +// Verifies that unix control sockets handle configSet() as expected. +TEST_F(UnixControlSocketTest, configSet) { + CfgControlSocketPtr cfg = createCfgControlSocket(); + ASSERT_TRUE(cfg); + UnixControlSocketPtr ucs(new UnixControlSocket(cfg)); + ASSERT_TRUE(ucs); + + // Run a reflecting server in a thread. + thread_.reset(new Thread([this]() { reflectServer(); })); + while (!ready_) { + usleep(1000); + } + + // Prepare a config to set. + ConstElementPtr json = Element::fromJSON("{ \"bar\": 1 }"); + + ConstElementPtr reflected; + EXPECT_NO_THROW(reflected = ucs->configSet(json, "foo")); + ASSERT_TRUE(reflected); + ASSERT_EQ(Element::map, reflected->getType()); + ConstElementPtr command = reflected->get("received"); + ASSERT_TRUE(command); + ASSERT_EQ(Element::string, command->getType()); + string expected = "{ \"arguments\": { \"bar\": 1 }, " + "\"command\": \"config-set\" }"; + EXPECT_EQ(expected, command->stringValue()); +} + +// Verifies that unix control sockets handle timeouts. +TEST_F(UnixControlSocketTest, timeout) { + CfgControlSocketPtr cfg = createCfgControlSocket(); + ASSERT_TRUE(cfg); + UnixControlSocketPtr ucs(new UnixControlSocket(cfg)); + ASSERT_TRUE(ucs); + + // Run a timeout server in a thread. + thread_.reset(new Thread([this]() { timeoutServer(); })); + + // Try configGet: it should get a communication error, + EXPECT_THROW(ucs->configGet("foo"), ControlSocketError); + ready_ = true; +} + +//////////////////////////////// HTTP //////////////////////////////// + +/// @brief IP address to which HTTP service is bound. +const string SERVER_ADDRESS = "127.0.0.1"; + +/// @brief Port number to which HTTP service is bound. +const uint16_t SERVER_PORT = 18123; + +/// @brief Test HTTP JSON response. +typedef TestHttpResponseBase Response; + +/// @brief Pointer to test HTTP JSON response. +typedef boost::shared_ptr ResponsePtr; + +/// @brief Generic test HTTP response. +typedef TestHttpResponseBase GenericResponse; + +/// @brief Pointer to generic test HTTP response. +typedef boost::shared_ptr GenericResponsePtr; + +/// @brief Implementation of the HttpResponseCreator. +/// +/// Send back the request in a received JSON map. +class TestHttpResponseCreator : public HttpResponseCreator { +public: + /// @brief Create a new request. + /// + /// @return Pointer to the new instance of the HttpRequest. + virtual HttpRequestPtr + createNewHttpRequest() const { + return (HttpRequestPtr(new PostHttpRequestJson())); + } + +protected: + /// @brief Creates HTTP response. + /// + /// @param request Pointer to the HTTP request. + /// @return Pointer to the generated HTTP response. + virtual HttpResponsePtr + createStockHttpResponse(const ConstHttpRequestPtr& request, + const HttpStatusCode& status_code) const { + // Data is in the request context. + HttpVersion http_version(request->context()->http_version_major_, + request->context()->http_version_minor_); + ResponsePtr response(new Response(http_version, status_code)); + response->finalize(); + return (response); + } + + /// @brief Creates HTTP response. + /// + /// Build a response with reflected request in a received JSON map. + /// When wanted reply with partial JSON. + /// + /// @param request Pointer to the HTTP request. + /// @return Pointer to an object representing HTTP response. + virtual HttpResponsePtr + createDynamicHttpResponse(const ConstHttpRequestPtr& request) { + // Request must always be JSON. + ConstPostHttpRequestJsonPtr request_json = + boost::dynamic_pointer_cast(request); + if (!request_json) { + isc_throw(Unexpected, "request is not JSON"); + } + ConstElementPtr body = request_json->getBodyAsJson(); + if (!body) { + isc_throw(Unexpected, "can't get JSON from request"); + } + + // Check for the special partial JSON. + ConstElementPtr arguments = body->get("arguments"); + if (arguments && (arguments->contains("want-partial"))) { + // Use a generic response. + GenericResponsePtr + response(new GenericResponse(request->getHttpVersion(), + HttpStatusCode::OK)); + HttpResponseContextPtr ctx = response->context(); + // Generate JSON response. + ctx->headers_.push_back(HttpHeaderContext("Content-Type", + "application/json")); + // Not closed JSON map so it will fail. + ctx->body_ = "{"; + response->finalize(); + // Take into account the missing '}'. + response->setContentLength(2); + return (response); + } + + // Reflect. + ResponsePtr response(new Response(request->getHttpVersion(), + HttpStatusCode::OK)); + ElementPtr map = Element::createMap(); + map->set("received", Element::create(body->str())); + response->setBodyAsJson(map); + response->finalize(); + return (response); + } +}; + +/// @brief Implementation of the test HttpResponseCreatorFactory. +class TestHttpResponseCreatorFactory : public HttpResponseCreatorFactory { +public: + + /// @brief Creates @ref TestHttpResponseCreator instance. + virtual HttpResponseCreatorPtr create() const { + HttpResponseCreatorPtr response_creator(new TestHttpResponseCreator()); + return (response_creator); + } +}; + +/// @brief Test fixture class for http control sockets. +class HttpControlSocketTest : public ::testing::Test { +public: + HttpControlSocketTest() + : io_service_(), thread_(), + ready_(false), done_(false), finished_(false) { + } + + /// @brief Destructor. + virtual ~HttpControlSocketTest() { + io_service_.stop(); + if (thread_) { + thread_->wait(); + thread_.reset(); + } + } + + /// @brief Returns socket URL. + static Url httpSocketUrl() { + ostringstream s; + s << "http://" << SERVER_ADDRESS << ":" << SERVER_PORT << "/"; + return (Url(s.str())); + } + + /// @brief Create configuration of the control socket. + /// + /// @return a pointer to a control socket configuration. + CfgControlSocketPtr createCfgControlSocket() { + CfgControlSocketPtr cfg; + cfg.reset(new CfgControlSocket(CfgControlSocket::Type::HTTP, + "", httpSocketUrl())); + return (cfg); + } + + /// @brief Create the reflecting listener. + void createReflectListener(); + + /// @brief Start listener. + /// + /// Run IO in a thread. + void start() { + thread_.reset(new Thread([this]() { + ready_ = true; + while (!done_) { + io_service_.run_one(); + } + io_service_.poll(); + finished_ = true; + })); + while (!ready_) { + usleep(1000); + } + if (listener_) { + ASSERT_NO_THROW(listener_->start()); + } + } + + /// @brief Stop listener. + /// + /// Post an empty action to finish current run_one. + void stop() { + if (listener_) { + ASSERT_NO_THROW(listener_->stop()); + } + done_ = true; + io_service_.post([]() { return; }); + while (!finished_) { + usleep(1000); + } + } + + /// @brief IOService object. + IOService io_service_; + + /// @brief Pointer to listener. + HttpListenerPtr listener_; + + /// @brief Pointer to server thread. + ThreadPtr thread_; + + /// @brief Ready flag. + bool ready_; + + /// @brief Done flag (stopping thread). + bool done_; + + /// @brief Finished flag (stopped thread). + bool finished_; +}; + +/// @brief Create the reflecting listener. +void +HttpControlSocketTest::createReflectListener() { + HttpResponseCreatorFactoryPtr + factory(new TestHttpResponseCreatorFactory()); + listener_.reset(new + HttpListener(io_service_, + IOAddress(SERVER_ADDRESS), SERVER_PORT, + factory, + HttpListener::RequestTimeout(2000), + HttpListener::IdleTimeout(2000))); +} + +// Verifies that the createControlSocket template can create a http +// control socket. +TEST_F(HttpControlSocketTest, createControlSocket) { + CfgControlSocketPtr cfg = createCfgControlSocket(); + ASSERT_TRUE(cfg); + ControlSocketBasePtr cs = createControlSocket(cfg); + ASSERT_TRUE(cs); + HttpControlSocketPtr hcs = + boost::dynamic_pointer_cast(cs); + EXPECT_TRUE(hcs); +} + +// Verifies that http control sockets handle configGet() as expected. +TEST_F(HttpControlSocketTest, configGet) { + CfgControlSocketPtr cfg = createCfgControlSocket(); + ASSERT_TRUE(cfg); + HttpControlSocketPtr hcs(new HttpControlSocket(cfg)); + ASSERT_TRUE(hcs); + + // Run a reflecting server in a thread. + createReflectListener(); + start(); + + // Try configGet. + ConstElementPtr reflected; + EXPECT_NO_THROW(reflected = hcs->configGet("foo")); + stop(); + + // Check result. + ASSERT_TRUE(reflected); + ASSERT_EQ(Element::map, reflected->getType()); + ConstElementPtr command = reflected->get("received"); + ASSERT_TRUE(command); + ASSERT_EQ(Element::string, command->getType()); + string expected = "{ \"command\": \"config-get\", " + "\"service\": [ \"foo\" ] }"; + EXPECT_EQ(expected, command->stringValue()); +} + +// Verifies that http control sockets handle configGet() for a control agent +// as expected. +TEST_F(HttpControlSocketTest, configGetCA) { + CfgControlSocketPtr cfg = createCfgControlSocket(); + ASSERT_TRUE(cfg); + HttpControlSocketPtr hcs(new HttpControlSocket(cfg)); + ASSERT_TRUE(hcs); + + // Run a reflecting server in a thread. + createReflectListener(); + start(); + + // Try configGet. + ConstElementPtr reflected; + EXPECT_NO_THROW(reflected = hcs->configGet("ca")); + stop(); + + // Check result. + ASSERT_TRUE(reflected); + ASSERT_EQ(Element::map, reflected->getType()); + ConstElementPtr command = reflected->get("received"); + ASSERT_TRUE(command); + ASSERT_EQ(Element::string, command->getType()); + string expected = "{ \"command\": \"config-get\" }"; + EXPECT_EQ(expected, command->stringValue()); +} + +// Verifies that http control sockets handle configTest() as expected. +TEST_F(HttpControlSocketTest, configTest) { + CfgControlSocketPtr cfg = createCfgControlSocket(); + ASSERT_TRUE(cfg); + HttpControlSocketPtr hcs(new HttpControlSocket(cfg)); + ASSERT_TRUE(hcs); + + // Run a reflecting server in a thread. + createReflectListener(); + start(); + + // Prepare a config to test. + ConstElementPtr json = Element::fromJSON("{ \"bar\": 1 }"); + + // Try configTest. + ConstElementPtr reflected; + EXPECT_NO_THROW(reflected = hcs->configTest(json, "foo")); + stop(); + + // Check result. + ASSERT_TRUE(reflected); + ASSERT_EQ(Element::map, reflected->getType()); + ConstElementPtr command = reflected->get("received"); + ASSERT_TRUE(command); + ASSERT_EQ(Element::string, command->getType()); + string expected = "{ \"arguments\": { \"bar\": 1 }, " + "\"command\": \"config-test\", " + "\"service\": [ \"foo\" ] }"; + EXPECT_EQ(expected, command->stringValue()); +} + +// Verifies that http control sockets handle configTest() for a control agent +// as expected. +TEST_F(HttpControlSocketTest, configTestCA) { + CfgControlSocketPtr cfg = createCfgControlSocket(); + ASSERT_TRUE(cfg); + HttpControlSocketPtr hcs(new HttpControlSocket(cfg)); + ASSERT_TRUE(hcs); + + // Run a reflecting server in a thread. + createReflectListener(); + start(); + + // Prepare a config to test. + ConstElementPtr json = Element::fromJSON("{ \"bar\": 1 }"); + + // Try configTest. + ConstElementPtr reflected; + EXPECT_NO_THROW(reflected = hcs->configTest(json, "ca")); + stop(); + + // Check result. + ASSERT_TRUE(reflected); + ASSERT_EQ(Element::map, reflected->getType()); + ConstElementPtr command = reflected->get("received"); + ASSERT_TRUE(command); + ASSERT_EQ(Element::string, command->getType()); + string expected = "{ \"arguments\": { \"bar\": 1 }, " + "\"command\": \"config-test\" }"; + EXPECT_EQ(expected, command->stringValue()); +} + +// Verifies that http control sockets handle configSet() as expected. +TEST_F(HttpControlSocketTest, configSet) { + CfgControlSocketPtr cfg = createCfgControlSocket(); + ASSERT_TRUE(cfg); + HttpControlSocketPtr hcs(new HttpControlSocket(cfg)); + ASSERT_TRUE(hcs); + + // Run a reflecting server in a thread. + createReflectListener(); + start(); + + // Prepare a config to set. + ConstElementPtr json = Element::fromJSON("{ \"bar\": 1 }"); + + // Try configSet. + ConstElementPtr reflected; + EXPECT_NO_THROW(reflected = hcs->configSet(json, "foo")); + stop(); + + // Check result. + ASSERT_TRUE(reflected); + ASSERT_EQ(Element::map, reflected->getType()); + ConstElementPtr command = reflected->get("received"); + ASSERT_TRUE(command); + ASSERT_EQ(Element::string, command->getType()); + string expected = "{ \"arguments\": { \"bar\": 1 }, " + "\"command\": \"config-set\", " + "\"service\": [ \"foo\" ] }"; + EXPECT_EQ(expected, command->stringValue()); +} + +// Verifies that http control sockets handle configSet() for a control agent +// as expected. +TEST_F(HttpControlSocketTest, configSetCA) { + CfgControlSocketPtr cfg = createCfgControlSocket(); + ASSERT_TRUE(cfg); + HttpControlSocketPtr hcs(new HttpControlSocket(cfg)); + ASSERT_TRUE(hcs); + + // Run a reflecting server in a thread. + createReflectListener(); + start(); + + // Prepare a config to set. + ConstElementPtr json = Element::fromJSON("{ \"bar\": 1 }"); + + // Try configSet. + ConstElementPtr reflected; + EXPECT_NO_THROW(reflected = hcs->configSet(json, "ca")); + stop(); + + // Check result. + ASSERT_TRUE(reflected); + ASSERT_EQ(Element::map, reflected->getType()); + ConstElementPtr command = reflected->get("received"); + ASSERT_TRUE(command); + ASSERT_EQ(Element::string, command->getType()); + string expected = "{ \"arguments\": { \"bar\": 1 }, " + "\"command\": \"config-set\" }"; + EXPECT_EQ(expected, command->stringValue()); +} + +// Verifies that http control sockets handle can't connect errors. +TEST_F(HttpControlSocketTest, connectionRefused) { + CfgControlSocketPtr cfg = createCfgControlSocket(); + ASSERT_TRUE(cfg); + HttpControlSocketPtr hcs(new HttpControlSocket(cfg)); + ASSERT_TRUE(hcs); + + // Try configGet: it should get a communication error, + try { + hcs->configGet("foo"); + } catch (const ControlSocketError& ex) { + EXPECT_EQ("communication error (code): Connection refused", + string(ex.what())); + } catch (const std::exception& ex) { + FAIL() << "unexpected exception: " << ex.what(); + } catch (...) { + FAIL() << "unexpected exception"; + } +} + +// Verifies that http control sockets handle timeout errors. +TEST_F(HttpControlSocketTest, partial) { + CfgControlSocketPtr cfg = createCfgControlSocket(); + ASSERT_TRUE(cfg); + HttpControlSocketPtr hcs(new HttpControlSocket(cfg)); + ASSERT_TRUE(hcs); + + // Create the server but do not start it. + createReflectListener(); + start(); + + // Prepare a special config to set. + ConstElementPtr json = Element::fromJSON("{ \"want-partial\": true }"); + + // Try configSet: it should get a communication error, + try { + hcs->configSet(json, "foo"); + } catch (const ControlSocketError& ex) { + EXPECT_EQ("communication error (code): End of file", + string(ex.what())); + } catch (const std::exception& ex) { + FAIL() << "unexpected exception: " << ex.what(); + } catch (...) { + FAIL() << "unexpected exception"; + } + stop(); +} + +} diff --git a/src/bin/netconf/unix_control_socket.cc b/src/bin/netconf/unix_control_socket.cc new file mode 100644 index 0000000000..2553371e8a --- /dev/null +++ b/src/bin/netconf/unix_control_socket.cc @@ -0,0 +1,99 @@ +// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +/// @file unix_control_socket.cc +/// Contains the UNIX socket derived class for control socket communication. + +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace isc::asiolink; +using namespace isc::config; +using namespace isc::data; + +namespace isc { +namespace netconf { + +template <> ControlSocketBasePtr +createControlSocket(CfgControlSocketPtr ctrl_sock) { + return (UnixControlSocketPtr(new UnixControlSocket(ctrl_sock))); +} + +UnixControlSocket::UnixControlSocket(CfgControlSocketPtr ctrl_sock) + : ControlSocketBase(ctrl_sock) { +} + +UnixControlSocket::~UnixControlSocket() { +} + +ConstElementPtr +UnixControlSocket::configGet(const string& /*service*/) { + return (sendCommand(createCommand("config-get"))); +} + +ConstElementPtr +UnixControlSocket::configTest(ConstElementPtr config, + const string& /*service*/) { + return (sendCommand(createCommand("config-test", config))); +} + +ConstElementPtr +UnixControlSocket::configSet(ConstElementPtr config, + const string& /*service*/) { + return (sendCommand(createCommand("config-set", config))); +} + +ConstElementPtr +UnixControlSocket::sendCommand(ConstElementPtr command) { + IOServicePtr io_service(new IOService()); + ClientConnection conn(*io_service); + boost::system::error_code received_ec; + ConstJSONFeedPtr received_feed; + + conn.start(ClientConnection::SocketPath(getName()), + ClientConnection::ControlCommand(command->toWire()), + [&io_service, &received_ec, &received_feed] + (const boost::system::error_code& ec, ConstJSONFeedPtr feed) { + // Capture error code and parsed data. + received_ec = ec; + received_feed = feed; + // Got the IO service so stop IO service. This causes to + // stop IO service when all handlers have been invoked. + io_service->stopWork(); + }, + ClientConnection::Timeout(TIMEOUT_AGENT_FORWARD_COMMAND)); + + // Perform this synchronously. + io_service->run(); + + if (received_ec) { + // Got an error. + isc_throw(ControlSocketError, "communication error: " + << received_ec.message()); + } + + if (!received_feed) { + // Failed to get the answer. + isc_throw(ControlSocketError, "empty response"); + } + + try { + return (received_feed->toElement()); + } catch (const std::exception& ex) { + isc_throw(ControlSocketError, "unparsable response: " << ex.what()); + } +} + +} // namespace netconf +} // namespace isc diff --git a/src/bin/netconf/unix_control_socket.h b/src/bin/netconf/unix_control_socket.h new file mode 100644 index 0000000000..08c9589765 --- /dev/null +++ b/src/bin/netconf/unix_control_socket.h @@ -0,0 +1,86 @@ +// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +/// @file unix_control_socket.h +/// Contains declarations for UNIX control socket communication. + +#ifndef UNIX_CONTROL_SOCKET_H +#define UNIX_CONTROL_SOCKET_H + +#include + +namespace isc { +namespace netconf { + +/// @brief Class for control socket communication over UNIX socket. +/// +/// This class is the derived class for control socket communication +/// over UNIX sockets. +/// This class implements config-get, config-test and config-set. +class UnixControlSocket : public ControlSocketBase { +public: + + /// @brief Constructor. + /// + /// @param ctrl_sock The control socket configuration. + UnixControlSocket(CfgControlSocketPtr ctrl_sock); + + /// @brief Destructor (does nothing). + virtual ~UnixControlSocket(); + + /// @brief Get configuration. + /// + /// Call config-get over the control socket. + /// + /// @param service The target service (ignored). + /// @return The JSON element answer of config-get. + /// @throw ControlSocketError when a communication error occurs. + virtual data::ConstElementPtr configGet(const std::string& service); + + /// @brief Test configuration. + /// + /// Call config-test over the control socket. + /// + /// @param service The target service (ignored). + /// @param config The configuration to test. + /// @return The JSON element answer of config-test. + /// @throw ControlSocketError when a communication error occurs. + virtual data::ConstElementPtr configTest(data::ConstElementPtr config, + const std::string& service); + + /// @brief Set configuration. + /// + /// Call config-set over the control socket. + /// + /// @param config The configuration to set. + /// @param service The target service (ignored). + /// @return The JSON element answer of config-set. + /// @throw ControlSocketError when a communication error occurs. + virtual data::ConstElementPtr configSet(data::ConstElementPtr config, + const std::string& service); + +private: + /// @brief Preform the actual communication. + /// + /// @param command The command to send. + /// @return The answer. + data::ConstElementPtr sendCommand(data::ConstElementPtr command); +}; + +/// @brief Type definition for the pointer to the @c UnixControlSocket. +typedef boost::shared_ptr UnixControlSocketPtr; + +/// @brief Factory template specialization for unix control sockets. +/// +/// @param ctrl_sock The control socket configuration. +/// @return A pointer to a unix control socket communication object. +template <> ControlSocketBasePtr +createControlSocket(CfgControlSocketPtr ctrl_sock); + +} // namespace netconf +} // namespace isc + +#endif // UNIX_CONTROL_SOCKET_H