summaryrefslogtreecommitdiffstats
path: root/src/bin/netconf/tests/control_socket_unittests.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/netconf/tests/control_socket_unittests.cc')
-rw-r--r--src/bin/netconf/tests/control_socket_unittests.cc866
1 files changed, 866 insertions, 0 deletions
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 0000000..fc4b35e
--- /dev/null
+++ b/src/bin/netconf/tests/control_socket_unittests.cc
@@ -0,0 +1,866 @@
+// Copyright (C) 2018-2022 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 <config.h>
+
+#include <netconf/netconf_config.h>
+#include <netconf/http_control_socket.h>
+#include <netconf/stdout_control_socket.h>
+#include <netconf/unix_control_socket.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_service.h>
+#include <http/listener.h>
+#include <http/post_request_json.h>
+#include <http/response_creator.h>
+#include <http/response_creator_factory.h>
+#include <http/response_json.h>
+#include <http/tests/response_test.h>
+#include <testutils/threaded_test.h>
+#include <testutils/sandbox.h>
+#include <yang/tests/sysrepo_setup.h>
+
+#include <gtest/gtest.h>
+
+#include <sstream>
+#include <thread>
+
+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::test;
+
+using isc::yang::test::SysrepoSetup;
+
+namespace {
+
+/// @brief Type definition for the pointer to Thread objects.
+typedef boost::shared_ptr<thread> ThreadPtr;
+
+//////////////////////////////// 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<TestStdoutControlSocket> 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 = controlSocketFactory(cfg);
+ ASSERT_TRUE(cs);
+ StdoutControlSocketPtr scs =
+ boost::dynamic_pointer_cast<StdoutControlSocket>(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_LOG(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_LOG(answer = tscs->configSet(json, "foo"));
+
+ // Check answer.
+ ASSERT_TRUE(answer);
+ EXPECT_EQ("{ \"result\": 0 }", answer->str());
+
+ // Check output.
+ string expected = "{\n \"bar\": 1\n}\n";
+ EXPECT_EQ(expected, os.str());
+}
+
+//////////////////////////////// UNIX ////////////////////////////////
+
+/// @brief Test timeout in ms.
+const long TEST_TIMEOUT = 10000;
+
+/// @brief Test fixture class for unix control sockets.
+class UnixControlSocketTest : public ThreadedTest {
+public:
+ isc::test::Sandbox sandbox;
+
+ /// @brief Constructor.
+ UnixControlSocketTest()
+ : ThreadedTest(), io_service_() {
+ }
+
+
+ void SetUp() override {
+ SysrepoSetup::cleanSharedMemory();
+ removeUnixSocketFile();
+ }
+
+ void TearDown() override {
+ if (thread_) {
+ thread_->join();
+ thread_.reset();
+ }
+ // io_service must be stopped after the thread returns,
+ // otherwise the thread may never return if it is
+ // waiting for the completion of some asynchronous tasks.
+ io_service_.stop();
+ 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.
+ string unixSocketFilePath() {
+ std::string socket_path;
+ const char* env = getenv("KEA_SOCKET_TEST_DIR");
+ if (env) {
+ socket_path = std::string(env) + "/test-socket";
+ } else {
+ socket_path = sandbox.join("test-socket");
+ }
+ return (socket_path);
+ }
+
+ /// @brief Removes unix socket descriptor.
+ void removeUnixSocketFile() {
+ static_cast<void>(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 IOService object.
+ IOService io_service_;
+};
+
+/// @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.
+ signalReady();
+
+ // 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 = 0;
+ 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 = 0;
+ 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();
+
+ // Close socket.
+ if (socket.is_open()) {
+ EXPECT_NO_THROW(socket.close());
+ }
+
+ EXPECT_FALSE(timeout);
+ EXPECT_TRUE(accepted);
+ EXPECT_TRUE(received);
+ EXPECT_TRUE(sent);
+ EXPECT_EQ(sent, sbuf.size());
+}
+
+// Verifies that the createControlSocket template can create an unix
+// control socket.
+TEST_F(UnixControlSocketTest, createControlSocket) {
+ CfgControlSocketPtr cfg = createCfgControlSocket();
+ ASSERT_TRUE(cfg);
+ ControlSocketBasePtr cs = controlSocketFactory(cfg);
+ ASSERT_TRUE(cs);
+ UnixControlSocketPtr ucs =
+ boost::dynamic_pointer_cast<UnixControlSocket>(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(); }));
+
+ waitReady();
+
+ // 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(); }));
+
+ waitReady();
+
+ // 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(); }));
+
+ waitReady();
+
+ // 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]() { waitReady(); }));
+
+ // Try configGet: it should get a communication error,
+ EXPECT_THROW(ucs->configGet("foo"), ControlSocketError);
+ signalReady();
+}
+
+//////////////////////////////// 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<HttpResponseJson> Response;
+
+/// @brief Pointer to test HTTP JSON response.
+typedef boost::shared_ptr<Response> ResponsePtr;
+
+/// @brief Generic test HTTP response.
+typedef TestHttpResponseBase<HttpResponse> GenericResponse;
+
+/// @brief Pointer to generic test HTTP response.
+typedef boost::shared_ptr<GenericResponse> 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 HttpRequestPtr& 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.
+ /// It can be told to respond with a partial JSON.
+ ///
+ /// @param request Pointer to the HTTP request.
+ /// @return Pointer to an object representing HTTP response.
+ virtual HttpResponsePtr
+ createDynamicHttpResponse(HttpRequestPtr request) {
+ // Request must always be JSON.
+ PostHttpRequestJsonPtr request_json =
+ boost::dynamic_pointer_cast<PostHttpRequestJson>(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 ThreadedTest {
+public:
+ void SetUp() override {
+ SysrepoSetup::cleanSharedMemory();
+ }
+
+ void TearDown() override {
+ SysrepoSetup::cleanSharedMemory();
+ if (thread_) {
+ thread_->join();
+ thread_.reset();
+ }
+ // io_service must be stopped after the thread returns,
+ // otherwise the thread may never return if it is
+ // waiting for the completion of some asynchronous tasks.
+ io_service_.stop();
+ }
+
+ /// @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]() {
+ // The thread is ready to go. Signal it to the main
+ // thread so it can start the actual test.
+ signalReady();
+ // Until stop() is called run IO service.
+ while (!isStopping()) {
+ io_service_.run_one();
+ }
+ // Main thread signalled that the thread should
+ // terminate. Launch any outstanding IO service
+ // handlers.
+ io_service_.poll();
+ // Nothing more to do. Signal that the thread is
+ // done so as the main thread can close HTTP
+ // listener and clean up after the test.
+ signalStopped();
+ }));
+
+ // Main thread waits here for the thread to start.
+ waitReady();
+
+ // If the thread is ready to go, start the listener.
+ if (listener_) {
+ ASSERT_NO_THROW_LOG(listener_->start());
+ }
+ }
+
+ /// @brief Stop listener.
+ ///
+ /// Post an empty action to finish current run_one.
+ void stop() {
+ // Notify the thread that it should terminate.
+ signalStopping();
+ // If the thread is blocked on running the IO
+ // service, post the empty handler to cause
+ // run_one to return.
+ io_service_.post([]() { return; });
+ // We asked that the thread stops. Let's wait
+ // for it to signal that it has stopped.
+ waitStopped();
+
+ // Thread has terminated. We can stop the HTTP
+ // listener safely.
+ if (listener_) {
+ ASSERT_NO_THROW_LOG(listener_->stop());
+ }
+ }
+
+ /// @brief IOService object.
+ IOService io_service_;
+
+ /// @brief Pointer to listener.
+ HttpListenerPtr listener_;
+};
+
+/// @brief Create the reflecting listener.
+void
+HttpControlSocketTest::createReflectListener() {
+ HttpResponseCreatorFactoryPtr
+ factory(new TestHttpResponseCreatorFactory());
+ listener_.reset(new
+ HttpListener(io_service_,
+ IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ TlsContextPtr(), 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 = controlSocketFactory(cfg);
+ ASSERT_TRUE(cs);
+ HttpControlSocketPtr hcs =
+ boost::dynamic_pointer_cast<HttpControlSocket>(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\", "
+ "\"remote-address\": \"127.0.0.1\", \"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\", \"remote-address\": \"127.0.0.1\" }";
+ 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\", "
+ "\"remote-address\": \"127.0.0.1\", \"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\", \"remote-address\": \"127.0.0.1\" }";
+ 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\", "
+ "\"remote-address\": \"127.0.0.1\", \"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\", \"remote-address\": \"127.0.0.1\" }";
+ 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 }");
+
+ // Warn this makes time.
+ cout << "this test waits for 2 seconds" << endl;
+
+ // 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();
+}
+
+} // namespace