--- /dev/null
+// 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 <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 <util/threads/thread.h>
+#include <gtest/gtest.h>
+#include <sstream>
+
+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<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 = createControlSocket(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(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<Thread> 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<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 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<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(); }));
+ 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<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 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<const 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 ::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<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\", "
+ "\"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();
+}
+
+}