]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#4460] support both list and map results
authorRazvan Becheriu <razvan@isc.org>
Fri, 22 May 2026 20:09:10 +0000 (23:09 +0300)
committerAndrei Pavel <andrei@isc.org>
Mon, 25 May 2026 06:22:51 +0000 (09:22 +0300)
ChangeLog
src/bin/netconf/http_control_socket.cc
src/bin/netconf/tests/control_socket_unittests.cc

index 60bd7b58e363853edfe1cd37939311b64d122fa4..1b1b7fcbc4a5ff37a5a6ab08f3e7a7484238aa9f 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,4 +1,4 @@
-2462.  [bug]           razvan
+2463.  [bug]           razvan
        Fixed kea-netconf communication over HTTP sockets with the kea
        dhcp demons. The control socket type is now mandatory for each
        server in the "managed-servers" configuration map.
index 8c6edb036aa2f13717d5c588f41b16ec631d2a18..c614a1f694a3df37945fff0ee1105ffb4b79974a 100644 (file)
@@ -111,11 +111,16 @@ HttpControlSocket::sendCommand(ConstElementPtr command) {
 
     try {
         auto response_list = response->getBodyAsJson();
-        if (response_list->getType() != Element::list) {
-            isc_throw(CtrlChannelError, "invalid answer: expected toplevel entry to be a list, got "
-                      << Element::typeToName(response_list->getType()) << " instead");
+        // If there is no error the expected response is a list.
+        if (response_list->getType() == Element::list) {
+            // There must be at least one response.
+            if (response_list->empty()) {
+                isc_throw(ControlSocketError, "list of responses must not be empty");
+            }
+            return (response_list->get(0));
         }
-        return (response_list->get(0));
+        // If there is an error the expected response is a map.
+        return (response->getBodyAsJson());
     } catch (exception const& ex) {
         isc_throw(ControlSocketError, "unparsable response: " << ex.what());
     }
index 1cb3e7321c1766c13e3cf727d8d1712465501787..ff59c79b96c32cd10c9e5fb2898d176effd642c4 100644 (file)
@@ -432,6 +432,12 @@ public:
         return (HttpRequestPtr(new PostHttpRequestJson()));
     }
 
+    /// @brief Flag which sets the type of reply (either list or map).
+    ///
+    /// @note The kea daemons reply using a list if the command succeeds and
+    /// with a map if there is an error.
+    static bool emulate_agent_response_;
+
 protected:
     /// @brief Creates HTTP response.
     ///
@@ -492,14 +498,20 @@ protected:
                                           HttpStatusCode::OK));
         ElementPtr map = Element::createMap();
         map->set("received", Element::create(body->str()));
-        ElementPtr response_list = Element::createList();
-        response_list->add(boost::const_pointer_cast<Element>(map));
-        response->setBodyAsJson(response_list);
+        if (TestHttpResponseCreator::emulate_agent_response_) {
+            ElementPtr response_list = Element::createList();
+            response_list->add(boost::const_pointer_cast<Element>(map));
+            response->setBodyAsJson(response_list);
+        } else {
+            response->setBodyAsJson(map);
+        }
         response->finalize();
         return (response);
     }
 };  // TestHttpResponseCreator
 
+bool TestHttpResponseCreator::emulate_agent_response_ = true;
+
 /// @brief Implementation of the test HttpResponseCreatorFactory.
 class TestHttpResponseCreatorFactory : public HttpResponseCreatorFactory {
 public:
@@ -519,10 +531,12 @@ public:
     }
 
     void SetUp() override {
+        TestHttpResponseCreator::emulate_agent_response_ = true;
         SysrepoSetup::cleanSharedMemory();
     }
 
     void TearDown() override {
+        TestHttpResponseCreator::emulate_agent_response_ = true;
         SysrepoSetup::cleanSharedMemory();
         if (thread_) {
             thread_->join();
@@ -778,4 +792,96 @@ TEST_F(HttpControlSocketTest, partial) {
     stop();
 }
 
+// Verifies that http control sockets handle configGet() as expected.
+TEST_F(HttpControlSocketTest, configGetReceiveMap) {
+    TestHttpResponseCreator::emulate_agent_response_ = false;
+    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_LOG(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 configTest() as expected.
+TEST_F(HttpControlSocketTest, configTestReceiveMap) {
+    TestHttpResponseCreator::emulate_agent_response_ = false;
+    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.
+    ElementPtr json = Element::fromJSON("{ \"bar\": 1 }");
+
+    // Try configTest.
+    ConstElementPtr reflected;
+    EXPECT_NO_THROW_LOG(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 configSet() as expected.
+TEST_F(HttpControlSocketTest, configSetReceiveMap) {
+    TestHttpResponseCreator::emulate_agent_response_ = false;
+    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.
+    ElementPtr json = Element::fromJSON("{ \"bar\": 1 }");
+
+    // Try configSet.
+    ConstElementPtr reflected;
+    EXPECT_NO_THROW_LOG(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());
+}
+
 }  // namespace