]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#1258] Ported basic status-get
authorMarcin Siodelski <marcin@isc.org>
Tue, 23 Jun 2020 12:40:18 +0000 (14:40 +0200)
committerMarcin Siodelski <marcin@isc.org>
Tue, 23 Jun 2020 15:41:03 +0000 (17:41 +0200)
The status-get command support was ported without High Availability status.

26 files changed:
doc/sphinx/api-files.txt
doc/sphinx/api/status-get.json [new file with mode: 0644]
doc/sphinx/arm/ctrl-channel.rst
doc/sphinx/arm/ddns.rst
doc/sphinx/arm/dhcp4-srv.rst
doc/sphinx/arm/dhcp6-srv.rst
doc/sphinx/arm/hooks-ha.rst
src/bin/agent/ca_controller.cc
src/bin/agent/tests/ca_controller_unittests.cc
src/bin/d2/d2_controller.cc
src/bin/d2/tests/d2_command_unittest.cc
src/bin/dhcp4/ctrl_dhcp4_srv.cc
src/bin/dhcp4/ctrl_dhcp4_srv.h
src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc
src/bin/dhcp6/ctrl_dhcp6_srv.cc
src/bin/dhcp6/ctrl_dhcp6_srv.h
src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc
src/bin/shell/tests/shell_process_tests.sh.in
src/lib/dhcpsrv/cfgmgr.cc
src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
src/lib/process/config_base.h
src/lib/process/d_cfg_mgr.cc
src/lib/process/d_controller.cc
src/lib/process/d_controller.h
src/lib/process/d_process.h
src/lib/process/daemon.h

index bb552ae516f3af583d9f0ec1918e01bb7cac028a..53b7aff44753a863acb903586f0d44089e117151 100644 (file)
@@ -134,6 +134,7 @@ api/statistic-sample-age-set-all.json
 api/statistic-sample-age-set.json
 api/statistic-sample-count-set-all.json
 api/statistic-sample-count-set.json
+api/status-get.json
 api/stat-lease4-get.json
 api/stat-lease6-get.json
 api/subnet4-add.json
diff --git a/doc/sphinx/api/status-get.json b/doc/sphinx/api/status-get.json
new file mode 100644 (file)
index 0000000..9fc6286
--- /dev/null
@@ -0,0 +1,48 @@
+{
+    "avail": "1.6.3",
+    "brief": [
+        "This command returns server's runtime information.",
+        "It takes no arguments."
+    ],
+    "cmd-syntax": [
+        "{",
+        "    \"command\": \"status-get\"",
+        "}"
+    ],
+    "description": "See <xref linkend=\"command-status-get\"/>",
+    "name": "status-get",
+    "resp-comment": [
+        "If the libdhcp_ha (High Availability) hooks library is loaded by the DHCP server receiving this command the response also includes the HA specific status information of the server receiving the command and its partner's status."
+    ],
+    "resp-syntax": [
+        "{",
+        "    \"result\": <integer>,",
+        "    \"arguments\": {",
+        "        \"pid\": <integer>,",
+        "        \"uptime\": <uptime in seconds>,",
+        "        \"reload\": <time since reload in seconds>,",
+        "        \"ha-servers\": {",
+        "            \"local\": {",
+        "                \"role\": <role of this server as in the configuration file>,",
+        "                \"scopes\": <list of scope names served by this server>,",
+        "                \"state\": <HA state name of the server receiving the command>,",
+        "            },",
+        "            \"remote\": {",
+        "                \"age\": <the age of the remote status in seconds>,",
+        "                \"in-touch\": <indicates if this server communicated with remote>,",
+        "                \"last-scopes\": <list of scopes served by partner>,",
+        "                \"last-state\": <HA state name of the partner>,",
+        "                \"role\": <partner role>",
+        "            }",
+        "        }",
+        "    }",
+        "}"
+    ],
+
+    "support": [
+        "kea-dhcp4",
+        "kea-dhcp6",
+        "kea-dhcp-ddns",
+        "kea-ctrl-agent"
+    ]
+}
index 3650964bebbb8a43ffeeb553c9bf051129301e4f..7b0a4e21b7319514ca4bbc3e171a27da67d2bb94 100644 (file)
@@ -538,6 +538,38 @@ The ``dhcp-enable`` command globally enables the DHCP service.
        "command": "dhcp-enable"
    }
 
+.. _command-status-get:
+
+The status-get Command
+----------------------
+
+The ``status-get`` command returns server's runtime information:
+
+ - pid: process id.
+
+ - uptime: number of seconds since the start of the server.
+
+ - reload: number of seconds since the last configuration (re)load.
+
+ - ha-servers: HA specific status information about the DHCP servers
+   configured to use HA hooks library:
+
+     * local: for the local server the state, the role (primary,
+       secondary, ...) and scopes (i.e. what the server is actually
+       processing).
+
+     * remote: for the remote server the last known state, served
+       HA scopes and the role of the server in HA relationship.
+
+The ``ha-servers`` information is only returned when the command is
+sent to the DHCP servers being in the HA setup. This parameter is
+never returned when the ``status-get`` command is sent to the
+Control Agent or DDNS deamon.
+
+To learn more about the HA status information returned by the
+``status-get`` command please refer to the the :ref:`command-ha-status-get`
+section.
+
 .. _command-version-get:
 
 The version-get Command
@@ -574,6 +606,8 @@ The D2 server supports only a subset of DHCPv4 / DHCPv6 server commands:
 
 -  shutdown
 
+-  status-get
+
 -  version-get
 
 .. _agent-commands:
@@ -601,4 +635,6 @@ commands are handled by the CA and they relate to the CA process itself:
 
 -  shutdown
 
+-  status-get
+
 -  version-get
index f1c37558a5e96d98c174420b336acd7b084e2e65..1858090d55b64b3e95991654b537cfac0124e73f 100644 (file)
@@ -295,6 +295,7 @@ The D2 server supports the following operational commands:
 -  config-write
 -  list-commands
 -  shutdown
+-  status-get
 -  version-get
 
 .. _d2-tsig-key-list-config:
index b5cb846c2438e4d9fa855c52e6f10fcdc01b0af0..00b12540ce18b1e02b09a6b4e4caf421fd6c31c9 100644 (file)
@@ -5689,6 +5689,7 @@ The DHCPv4 server supports the following operational commands:
 -  leases-reclaim
 -  list-commands
 -  shutdown
+-  status-get
 -  version-get
 
 as described in :ref:`commands-common`. In addition, it supports the
index edf3497ce06909ac82d66d25ec7e7b3d10cfed6d..23fca8583aceb60a62c991f566c03bdf54704da2 100644 (file)
@@ -5668,6 +5668,7 @@ The DHCPv6 server supports the following operational commands:
 -  leases-reclaim
 -  list-commands
 -  shutdown
+-  status-get
 -  version-get
 
 as described in :ref:`commands-common`. In addition, it supports the
index 7570278ab5c4520e77ac9405696660fba41e9250..7ff36646f0b6b4596c1adf1ac7c58d8f8118e72f 100644 (file)
@@ -1218,3 +1218,67 @@ command structure is as simple as:
    {
        "command": "ha-continue"
    }
+
+.. _command-ha-status-get:
+
+The status-get Command
+------------------------
+
+The ``status-get`` is the general purpose command supported by several Kea deamons,
+not only DHCP servers. However, when sent to the DHCP server with HA enabled, it
+can be used to get insight into the details of the HA specific status information
+of the servers being in the HA configuration. Not only does the response contain
+the status information of the server receiving this command but also the
+information about its partner, if this information is available.
+
+The following is the example response to the ``status-get`` command including
+the HA status of two load balancing servers:
+
+::
+
+   {
+       "result": 0,
+       "text": "",
+       "arguments": {
+           "pid": 1234,
+           "uptime": 3024,
+           "reload": 1111,
+           "ha-servers": {
+               "local": {
+                   "role": "primary",
+                   "scopes": [ "server1" ],
+                   "state": "load-balancing"
+               },
+                "remote": {
+                   "age": 10,
+                   "in-touch": true,
+                   "role": "secondary",
+                   "last-scopes": [ "server2" ],
+                   "last-state": "load-balancing"
+               }
+           }
+       }
+   }
+
+
+The ``ha-servers`` map contains two structures: ``local`` and ``remote``. The former
+contains the status information of the server which received the command. The
+latter contains the status information known to the local server about the
+partner. The ``role`` of the partner server is gathered from the local
+configuration file, therefore it should always be available. The remaining
+status information such as ``last-scopes`` and ``last-state`` is not available
+until the local server communicates with the remote by successfully sending
+the ``ha-heartbeat`` command. If at least one such communication took place,
+the returned value of ``in-touch`` parameter is set to ``true``. By examining
+this value, the command sender can determine whether the information about
+the remote server is reliable.
+
+The ``last-scopes`` and ``last-state`` contain the information about the
+HA scopes served by the partner and its state. Note that this information
+is gathered during the heartbeat command exchange, so it may not be
+accurate if the communication problem occur between the partners and this
+status information is not refreshed. In such case, it may be useful to
+send the ``status-get`` command to the partner server directly to check
+its current state. The ``age`` parameter specifies the number of seconds
+since the information from the partner was gathered (the age of this
+information).
index 38d5733e9760eaf268eead42124b4eeaed5d054c..4bd9691a3a49564ca2c3d4d391b02b63a4a4bfb4 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2016-2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2020 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
@@ -72,6 +72,9 @@ CtrlAgentController::registerCommands() {
     CtrlAgentCommandMgr::instance().registerCommand(SHUT_DOWN_COMMAND,
         boost::bind(&DControllerBase::shutdownHandler, this, _1, _2));
 
+    CtrlAgentCommandMgr::instance().registerCommand(STATUS_GET_COMMAND,
+        boost::bind(&DControllerBase::statusGetHandler, this, _1, _2));
+
     CtrlAgentCommandMgr::instance().registerCommand(VERSION_GET_COMMAND,
         boost::bind(&DControllerBase::versionGetHandler, this, _1, _2));
 }
@@ -85,6 +88,7 @@ CtrlAgentController::deregisterCommands() {
     CtrlAgentCommandMgr::instance().deregisterCommand(CONFIG_TEST_COMMAND);
     CtrlAgentCommandMgr::instance().deregisterCommand(CONFIG_WRITE_COMMAND);
     CtrlAgentCommandMgr::instance().deregisterCommand(SHUT_DOWN_COMMAND);
+    CtrlAgentCommandMgr::instance().deregisterCommand(STATUS_GET_COMMAND);
     CtrlAgentCommandMgr::instance().deregisterCommand(VERSION_GET_COMMAND);
 }
 
index b7223eff5eb9ed1d2f9c90c553d3057c798ebdb7..a2fd3c3a6caba32c35104c9ffa2eff31874b97c5 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2016-2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2020 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
@@ -13,6 +13,7 @@
 #include <process/testutils/d_test_stubs.h>
 #include <boost/pointer_cast.hpp>
 #include <sstream>
+#include <unistd.h>
 
 using namespace std;
 using namespace isc::agent;
@@ -454,6 +455,7 @@ TEST_F(CtrlAgentControllerTest, registeredCommands) {
     checkCommandRegistered("config-write");
     checkCommandRegistered("list-commands");
     checkCommandRegistered("shutdown");
+    checkCommandRegistered("status-get");
     checkCommandRegistered("version-get");
 
     ctrl->deregisterCommands();
@@ -657,4 +659,42 @@ TEST_F(CtrlAgentControllerTest, configReloadFileValid) {
     ctrl->deregisterCommands();
 }
 
+// Tests that status-get returns expected info (pid, uptime and reload).
+TEST_F(CtrlAgentControllerTest, statusGet) {
+    // Start the server.
+    time_duration elapsed_time;
+    runWithConfig(valid_agent_config, 500, elapsed_time);
+
+    const DControllerBasePtr& ctrl = getController();
+    ConstElementPtr response;
+    ASSERT_NO_THROW(response = ctrl->statusGetHandler("status-get", ConstElementPtr()));
+    ASSERT_TRUE(response);
+    ASSERT_EQ(Element::map, response->getType());
+    EXPECT_EQ(2, response->size());
+    ConstElementPtr result = response->get("result");
+    ASSERT_TRUE(result);
+    ASSERT_EQ(Element::integer, result->getType());
+    EXPECT_EQ(0, result->intValue());
+    ConstElementPtr arguments = response->get("arguments");
+    ASSERT_EQ(Element::map, arguments->getType());
+
+    // The returned pid should be the pid of our process.
+    auto found_pid = arguments->get("pid");
+    ASSERT_TRUE(found_pid);
+    EXPECT_EQ(static_cast<int64_t>(getpid()), found_pid->intValue());
+
+    // It is hard to check the actual uptime (and reload) as it is based
+    // on current time. Let's just make sure it is within a reasonable
+    // range.
+    auto found_uptime = arguments->get("uptime");
+    ASSERT_TRUE(found_uptime);
+    EXPECT_LE(found_uptime->intValue(), 5);
+    EXPECT_GE(found_uptime->intValue(), 0);
+
+    auto found_reload = arguments->get("reload");
+    ASSERT_TRUE(found_reload);
+    EXPECT_LE(found_reload->intValue(), 5);
+    EXPECT_GE(found_reload->intValue(), 0);
+}
+
 }
index aceca355d3f2ff3e7baa235bec20468a4e9691b2..67a06fa4fd15d43809f3fe2316b6a688679bbe0d 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2020 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
@@ -73,6 +73,9 @@ D2Controller::registerCommands() {
     CommandMgr::instance().registerCommand(SHUT_DOWN_COMMAND,
         boost::bind(&D2Controller::shutdownHandler, this, _1, _2));
 
+    CommandMgr::instance().registerCommand(STATUS_GET_COMMAND,
+        boost::bind(&DControllerBase::statusGetHandler, this, _1, _2));
+
     CommandMgr::instance().registerCommand(VERSION_GET_COMMAND,
         boost::bind(&D2Controller::versionGetHandler, this, _1, _2));
 }
@@ -91,6 +94,7 @@ D2Controller::deregisterCommands() {
         CommandMgr::instance().deregisterCommand(CONFIG_TEST_COMMAND);
         CommandMgr::instance().deregisterCommand(CONFIG_WRITE_COMMAND);
         CommandMgr::instance().deregisterCommand(SHUT_DOWN_COMMAND);
+        CommandMgr::instance().deregisterCommand(STATUS_GET_COMMAND);
         CommandMgr::instance().deregisterCommand(VERSION_GET_COMMAND);
 
     } catch (...) {
index 29d4b17457ce6e30af2de3801a10df055c572488..863fbf0e70bea5e18d80185729f6355d5ae5af52 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2018-2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2018-2020 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
@@ -23,6 +23,7 @@
 #include <iostream>
 #include <sstream>
 #include <thread>
+#include <unistd.h>
 
 using namespace std;
 using namespace isc;
@@ -537,6 +538,7 @@ TEST_F(CtrlChannelD2Test, commandsRegistration) {
     EXPECT_TRUE(command_list.find("\"config-test\"") != string::npos);
     EXPECT_TRUE(command_list.find("\"config-write\"") != string::npos);
     EXPECT_TRUE(command_list.find("\"shutdown\"") != string::npos);
+    EXPECT_TRUE(command_list.find("\"status-get\"") != string::npos);
     EXPECT_TRUE(command_list.find("\"version-get\"") != string::npos);
 
     // Ok, and now delete the server. It should deregister its commands.
@@ -612,6 +614,45 @@ TEST_F(CtrlChannelD2Test, listCommands) {
     checkListCommands(rsp, "version-get");
 }
 
+// This test verifies that the D2 server handles status-get commands
+TEST_F(CtrlChannelD2Test, statusGet) {
+    EXPECT_NO_THROW(createUnixChannelServer());
+
+    std::string response_txt;
+
+    // Send the version-get command
+    sendUnixCommand("{ \"command\": \"status-get\" }", response_txt);
+    ConstElementPtr response;
+    ASSERT_NO_THROW(response = Element::fromJSON(response_txt));
+    ASSERT_TRUE(response);
+    ASSERT_EQ(Element::map, response->getType());
+    EXPECT_EQ(2, response->size());
+    ConstElementPtr result = response->get("result");
+    ASSERT_TRUE(result);
+    ASSERT_EQ(Element::integer, result->getType());
+    EXPECT_EQ(0, result->intValue());
+    ConstElementPtr arguments = response->get("arguments");
+    ASSERT_EQ(Element::map, arguments->getType());
+
+    // The returned pid should be the pid of our process.
+    auto found_pid = arguments->get("pid");
+    ASSERT_TRUE(found_pid);
+    EXPECT_EQ(static_cast<int64_t>(getpid()), found_pid->intValue());
+
+    // It is hard to check the actual reload time as it is based
+    // on current time. Let's just make sure it is within a reasonable
+    // range.
+    auto found_reload = arguments->get("reload");
+    ASSERT_TRUE(found_reload);
+    EXPECT_LE(found_reload->intValue(), 5);
+    EXPECT_GE(found_reload->intValue(), 0);
+
+    /// @todo uptime is not available in this test, because the launch()
+    /// function is not called. This is not critical to test here,
+    /// because the same logic is tested for CA and in that case the
+    /// uptime is tested.
+}
+
 // Tests if the server returns its configuration using config-get.
 // Note there are separate tests that verify if toElement() called by the
 // config-get handler are actually converting the configuration correctly.
index 621d155706395de5c9922cc1647979f20c06fcc8..9dfe0c00081dec8336b3747e12ff67ece9eb5b97 100644 (file)
@@ -78,6 +78,9 @@ ControlledDhcpv4Srv* ControlledDhcpv4Srv::server_ = NULL;
 
 void
 ControlledDhcpv4Srv::init(const std::string& file_name) {
+    // Keep the call timestamp.
+    start_ = boost::posix_time::second_clock::universal_time();
+
     // Configure the server using JSON file.
     ConstElementPtr result = loadConfigFile(file_name);
     int rcode;
@@ -549,6 +552,30 @@ ControlledDhcpv4Srv::commandServerTagGetHandler(const std::string&,
     return (createAnswer(CONTROL_RESULT_SUCCESS, response));
 }
 
+ConstElementPtr
+ControlledDhcpv4Srv::commandStatusGetHandler(const string&,
+                                             ConstElementPtr /*args*/) {
+    ElementPtr status = Element::createMap();
+    status->set("pid", Element::create(static_cast<int>(getpid())));
+
+    auto now = boost::posix_time::second_clock::universal_time();
+    // Sanity check: start_ is always initialized.
+    if (!start_.is_not_a_date_time()) {
+        auto uptime = now - start_;
+        status->set("uptime", Element::create(uptime.total_seconds()));
+    }
+
+    auto last_commit = CfgMgr::instance().getCurrentCfg()->getLastCommitTime();
+    if (!last_commit.is_not_a_date_time()) {
+        auto reload = now - last_commit;
+        status->set("reload", Element::create(reload.total_seconds()));
+    }
+
+    // todo: number of service threads.
+
+    return (createAnswer(0, status));
+}
+
 ConstElementPtr
 ControlledDhcpv4Srv::processCommand(const string& command,
                                     ConstElementPtr args) {
@@ -606,6 +633,8 @@ ControlledDhcpv4Srv::processCommand(const string& command,
         } else if (command == "server-tag-get") {
             return (srv->commandServerTagGetHandler(command, args));
 
+        } else if (command == "status-get") {
+            return (srv->commandStatusGetHandler(command, args));
         }
         ConstElementPtr answer = isc::config::createAnswer(1,
                                  "Unrecognized command:" + command);
@@ -842,6 +871,9 @@ ControlledDhcpv4Srv::ControlledDhcpv4Srv(uint16_t server_port /*= DHCP4_SERVER_P
     CommandMgr::instance().registerCommand("shutdown",
         boost::bind(&ControlledDhcpv4Srv::commandShutdownHandler, this, _1, _2));
 
+    CommandMgr::instance().registerCommand("status-get",
+        boost::bind(&ControlledDhcpv4Srv::commandStatusGetHandler, this, _1, _2));
+
     CommandMgr::instance().registerCommand("version-get",
         boost::bind(&ControlledDhcpv4Srv::commandVersionGetHandler, this, _1, _2));
 
@@ -918,6 +950,7 @@ ControlledDhcpv4Srv::~ControlledDhcpv4Srv() {
         CommandMgr::instance().deregisterCommand("statistic-sample-age-set-all");
         CommandMgr::instance().deregisterCommand("statistic-sample-count-set");
         CommandMgr::instance().deregisterCommand("statistic-sample-count-set-all");
+        CommandMgr::instance().deregisterCommand("status-get");
         CommandMgr::instance().deregisterCommand("version-get");
 
     } catch (...) {
index 44271a76e3608b88457fb61b6daf852863031fb5..7cb4ca18390ae1aa9975f0a872d8205953db59f5 100644 (file)
@@ -304,6 +304,18 @@ private:
     commandServerTagGetHandler(const std::string& command,
                                isc::data::ConstElementPtr args);
 
+    /// @brief handler for processing 'status-get' command
+    ///
+    /// This handler processes status-get command, which retrieves
+    /// the server process information i.e. the pid and returns it in response.
+    ///
+    /// @param command (ignored)
+    /// @param args (ignored)
+    /// @return process information wrapped in a response
+    isc::data::ConstElementPtr
+    commandStatusGetHandler(const std::string& command,
+                            isc::data::ConstElementPtr args);
+
     /// @brief Reclaims expired IPv4 leases and reschedules timer.
     ///
     /// This is a wrapper method for @c AllocEngine::reclaimExpiredLeases4.
index 14439e52a21dcad48e407374d090a70d9a4e035b..591a7554557b11cc4c72949f2f6ecb8284d410c6 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2020 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
@@ -495,6 +495,7 @@ TEST_F(CtrlChannelDhcpv4SrvTest, commandsRegistration) {
     EXPECT_TRUE(command_list.find("\"statistic-sample-age-set-all\"") != string::npos);
     EXPECT_TRUE(command_list.find("\"statistic-sample-count-set\"") != string::npos);
     EXPECT_TRUE(command_list.find("\"statistic-sample-count-set-all\"") != string::npos);
+    EXPECT_TRUE(command_list.find("\"status-get\"") != string::npos);
     EXPECT_TRUE(command_list.find("\"version-get\"") != string::npos);
 
     // Ok, and now delete the server. It should deregister its commands.
@@ -629,6 +630,48 @@ TEST_F(CtrlChannelDhcpv4SrvTest, serverTagGet) {
     expected = "{ \"arguments\": { \"server-tag\": \"foobar\" }, \"result\": 0 }";
 }
 
+// This test verifies that the DHCP server handles status-get commands
+TEST_F(CtrlChannelDhcpv4SrvTest, statusGet) {
+    createUnixChannelServer();
+
+    // start_ is initialized by init.
+    ASSERT_THROW(server_->init("/no/such/file"), BadValue);
+
+    std::string response_txt;
+
+    // Send the version-get command
+    sendUnixCommand("{ \"command\": \"status-get\" }", response_txt);
+    ConstElementPtr response;
+    ASSERT_NO_THROW(response = Element::fromJSON(response_txt));
+    ASSERT_TRUE(response);
+    ASSERT_EQ(Element::map, response->getType());
+    EXPECT_EQ(2, response->size());
+    ConstElementPtr result = response->get("result");
+    ASSERT_TRUE(result);
+    ASSERT_EQ(Element::integer, result->getType());
+    EXPECT_EQ(0, result->intValue());
+    ConstElementPtr arguments = response->get("arguments");
+    ASSERT_EQ(Element::map, arguments->getType());
+
+    // The returned pid should be the pid of our process.
+    auto found_pid = arguments->get("pid");
+    ASSERT_TRUE(found_pid);
+    EXPECT_EQ(static_cast<int64_t>(getpid()), found_pid->intValue());
+
+    // It is hard to check the actual uptime (and reload) as it is based
+    // on current time. Let's just make sure it is within a reasonable
+    // range.
+    auto found_uptime = arguments->get("uptime");
+    ASSERT_TRUE(found_uptime);
+    EXPECT_LE(found_uptime->intValue(), 5);
+    EXPECT_GE(found_uptime->intValue(), 0);
+
+    auto found_reload = arguments->get("reload");
+    ASSERT_TRUE(found_reload);
+    EXPECT_LE(found_reload->intValue(), 5);
+    EXPECT_GE(found_reload->intValue(), 0);
+}
+
 // This test verifies that the DHCP server immediately removed expired
 // This test verifies that the DHCP server immediately removed expired
 // leases on leases-reclaim command with remove = true
index b94741c2f755dd81ff1846f1d70943de53b5098a..e82d75acf713bdfc1d12f71b5949d83036e4c733 100644 (file)
@@ -162,6 +162,9 @@ ControlledDhcpv6Srv::loadConfigFile(const std::string& file_name) {
 
 void
 ControlledDhcpv6Srv::init(const std::string& file_name) {
+    // Keep the call timestamp.
+    start_ = boost::posix_time::second_clock::universal_time();
+
     // Configure the server using JSON file.
     ConstElementPtr result = loadConfigFile(file_name);
     int rcode;
@@ -550,6 +553,30 @@ ControlledDhcpv6Srv::commandServerTagGetHandler(const std::string&,
     return (createAnswer(CONTROL_RESULT_SUCCESS, response));
 }
 
+ConstElementPtr
+ControlledDhcpv6Srv::commandStatusGetHandler(const string&,
+                                             ConstElementPtr /*args*/) {
+    ElementPtr status = Element::createMap();
+    status->set("pid", Element::create(static_cast<int>(getpid())));
+
+    auto now = boost::posix_time::second_clock::universal_time();
+    // Sanity check: start_ is always initialized.
+    if (!start_.is_not_a_date_time()) {
+        auto uptime = now - start_;
+        status->set("uptime", Element::create(uptime.total_seconds()));
+    }
+
+    auto last_commit = CfgMgr::instance().getCurrentCfg()->getLastCommitTime();
+    if (!last_commit.is_not_a_date_time()) {
+        auto reload = now - last_commit;
+        status->set("reload", Element::create(reload.total_seconds()));
+    }
+
+    // todo: number of service threads.
+
+    return (createAnswer(0, status));
+}
+
 isc::data::ConstElementPtr
 ControlledDhcpv6Srv::processCommand(const std::string& command,
                                     isc::data::ConstElementPtr args) {
@@ -607,8 +634,9 @@ ControlledDhcpv6Srv::processCommand(const std::string& command,
         } else if (command == "server-tag-get") {
             return (srv->commandServerTagGetHandler(command, args));
 
+        } else if (command == "status-get") {
+            return (srv->commandStatusGetHandler(command, args));
         }
-
         return (isc::config::createAnswer(1, "Unrecognized command:"
                                           + command));
 
@@ -866,6 +894,9 @@ ControlledDhcpv6Srv::ControlledDhcpv6Srv(uint16_t server_port,
     CommandMgr::instance().registerCommand("shutdown",
         boost::bind(&ControlledDhcpv6Srv::commandShutdownHandler, this, _1, _2));
 
+    CommandMgr::instance().registerCommand("status-get",
+        boost::bind(&ControlledDhcpv6Srv::commandStatusGetHandler, this, _1, _2));
+
     CommandMgr::instance().registerCommand("version-get",
         boost::bind(&ControlledDhcpv6Srv::commandVersionGetHandler, this, _1, _2));
 
@@ -942,6 +973,7 @@ ControlledDhcpv6Srv::~ControlledDhcpv6Srv() {
         CommandMgr::instance().deregisterCommand("statistic-sample-age-set-all");
         CommandMgr::instance().deregisterCommand("statistic-sample-count-set");
         CommandMgr::instance().deregisterCommand("statistic-sample-count-set-all");
+        CommandMgr::instance().deregisterCommand("status-get");
         CommandMgr::instance().deregisterCommand("version-get");
 
     } catch (...) {
index b4cd2d173518767b44928d9682554c44f29863c7..75583129866a25f6a2da329a156c249dae65811c 100644 (file)
@@ -303,6 +303,18 @@ private:
     commandServerTagGetHandler(const std::string& command,
                                isc::data::ConstElementPtr args);
 
+    /// @brief handler for processing 'status-get' command
+    ///
+    /// This handler processes status-get command, which retrieves
+    /// the server process information i.e. the pid and returns it in response.
+    ///
+    /// @param command (ignored)
+    /// @param args (ignored)
+    /// @return process information wrapped in a response
+    isc::data::ConstElementPtr
+    commandStatusGetHandler(const std::string& command,
+                            isc::data::ConstElementPtr args);
+
     /// @brief Reclaims expired IPv6 leases and reschedules timer.
     ///
     /// This is a wrapper method for @c AllocEngine::reclaimExpiredLeases6.
index ee821e37a4e9f2399f8168b5ccb50bf91ed7eb16..e14956bbd812cc836be426eec38f76fe82dc4ca6 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2020 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
 #include <sys/stat.h>
 #include <sys/ioctl.h>
 #include <cstdlib>
+#include <unistd.h>
 
 #include <thread>
 
 using namespace std;
+using namespace isc;
 using namespace isc::asiolink;
 using namespace isc::config;
 using namespace isc::data;
@@ -802,6 +804,7 @@ TEST_F(CtrlDhcpv6SrvTest, commandsRegistration) {
     EXPECT_TRUE(command_list.find("\"statistic-sample-age-set-all\"") != string::npos);
     EXPECT_TRUE(command_list.find("\"statistic-sample-count-set\"") != string::npos);
     EXPECT_TRUE(command_list.find("\"statistic-sample-count-set-all\"") != string::npos);
+    EXPECT_TRUE(command_list.find("\"status-get\"") != string::npos);
     EXPECT_TRUE(command_list.find("\"version-get\"") != string::npos);
 
     // Ok, and now delete the server. It should deregister its commands.
@@ -840,6 +843,48 @@ TEST_F(CtrlChannelDhcpv6SrvTest, controlChannelShutdown) {
     EXPECT_EQ("{ \"result\": 0, \"text\": \"Shutting down.\" }",response);
 }
 
+// This test verifies that the DHCP server handles status-get commands
+TEST_F(CtrlChannelDhcpv6SrvTest, statusGet) {
+    createUnixChannelServer();
+
+    // start_ is initialized by init.
+    ASSERT_THROW(server_->init("/no/such/file"), BadValue);
+
+    std::string response_txt;
+
+    // Send the version-get command
+    sendUnixCommand("{ \"command\": \"status-get\" }", response_txt);
+    ConstElementPtr response;
+    ASSERT_NO_THROW(response = Element::fromJSON(response_txt));
+    ASSERT_TRUE(response);
+    ASSERT_EQ(Element::map, response->getType());
+    EXPECT_EQ(2, response->size());
+    ConstElementPtr result = response->get("result");
+    ASSERT_TRUE(result);
+    ASSERT_EQ(Element::integer, result->getType());
+    EXPECT_EQ(0, result->intValue());
+    ConstElementPtr arguments = response->get("arguments");
+    ASSERT_EQ(Element::map, arguments->getType());
+
+    // The returned pid should be the pid of our process.
+    auto found_pid = arguments->get("pid");
+    ASSERT_TRUE(found_pid);
+    EXPECT_EQ(static_cast<int64_t>(getpid()), found_pid->intValue());
+
+    // It is hard to check the actual uptime (and reload) as it is based
+    // on current time. Let's just make sure it is within a reasonable
+    // range.
+    auto found_uptime = arguments->get("uptime");
+    ASSERT_TRUE(found_uptime);
+    EXPECT_LE(found_uptime->intValue(), 5);
+    EXPECT_GE(found_uptime->intValue(), 0);
+
+    auto found_reload = arguments->get("reload");
+    ASSERT_TRUE(found_reload);
+    EXPECT_LE(found_reload->intValue(), 5);
+    EXPECT_GE(found_reload->intValue(), 0);
+}
+
 // This test verifies that the DHCP server handles version-get commands
 TEST_F(CtrlChannelDhcpv6SrvTest, getversion) {
     createUnixChannelServer();
index 3a9545c48974a60e5b6dcff39e244643fa3841e9..3aed4e648f8f3aba4cef136bdfb2cbcfb6d53e7e 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (C) 2017-2019 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2017-2020 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
@@ -178,7 +178,7 @@ version_test() {
 
 version_test "shell.version"
 shell_command_test "shell.list-commands" "list-commands" \
-    "[ { \"arguments\": [ \"build-report\", \"config-get\", \"config-reload\", \"config-set\", \"config-test\", \"config-write\", \"list-commands\", \"shutdown\", \"version-get\" ], \"result\": 0 } ]" ""
+    "[ { \"arguments\": [ \"build-report\", \"config-get\", \"config-reload\", \"config-set\", \"config-test\", \"config-write\", \"list-commands\", \"shutdown\", \"status-get\", \"version-get\" ], \"result\": 0 } ]" ""
 shell_command_test "shell.bogus" "give-me-a-beer" \
 "[ { \"result\": 2, \"text\": \"'give-me-a-beer' command not supported. You did not include \\\"service\\\" parameter in the command, which indicates that Kea Control Agent should process this command rather than forward it to one or more Kea servers. If you aimed to send this command to one of the Kea servers you should include the \\\"service\\\" parameter in your request, e.g. \\\"service\\\": [ \\\"dhcp4\\\" ] to forward the command to the DHCPv4 server, or \\\"service\\\": [ \\\"dhcp4\\\", \\\"dhcp6\\\", \\\"d2\\\" ] to forward it to DHCPv4, DHCPv6 and D2 servers etc.\" } ]" ""
 shell_command_test "shell.empty-config-test" "config-test" \
index 24f207156d40049ef5c7f4db609649f0404df8e1..6c737b2a3b5f82426aaae0fba30f6bc0a6133800 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2020 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
@@ -107,6 +107,10 @@ CfgMgr::commit() {
         }
     }
 
+    // Set the last commit timestamp.
+    auto now = boost::posix_time::second_clock::universal_time();
+    configuration_->setLastCommitTime(now);
+
     // Now we need to set the statistics back.
     configuration_->updateStatistics();
 }
index 1069de2365dce034cec61cfc8d99debe5068f566..44a009017616fd39e8c432b675e41e80f3ae8a6f 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2020 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
@@ -463,12 +463,19 @@ TEST_F(CfgMgrTest, staging) {
 
     // This should change the staging configuration so as it becomes a current
     // one.
+    auto before = boost::posix_time::second_clock::universal_time();
     cfg_mgr.commit();
+    auto after = boost::posix_time::second_clock::universal_time();
     const_config = cfg_mgr.getCurrentCfg();
     ASSERT_TRUE(const_config);
     // Sequence id equal to 1 indicates that the current configuration points
     // to the configuration that used to be a staging configuration previously.
     EXPECT_EQ(1, const_config->getSequence());
+    // Last commit timestamp should be between before and after.
+    auto reload = const_config->getLastCommitTime();
+    ASSERT_FALSE(reload.is_not_a_date_time());
+    EXPECT_LE(before, reload);
+    EXPECT_GE(after, reload);
 
     // Create a new staging configuration. It should be assigned a new
     // sequence id.
index a2380f0e5c17bf0e1b348d8172d8d0ae762bbfe6..7eb9659cff9cae81b313d371dc51c7f3beddbc6f 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2018-2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2018-2020 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
@@ -12,6 +12,7 @@
 #include <process/config_ctl_info.h>
 #include <process/logging_info.h>
 #include <util/optional.h>
+#include <boost/date_time/posix_time/posix_time.hpp>
 
 namespace isc {
 namespace process {
@@ -134,6 +135,18 @@ public:
         return (server_tag_);
     }
 
+    /// @brief Returns the last commit timestamp.
+    /// @return the last commit timestamp.
+    boost::posix_time::ptime getLastCommitTime() const {
+        return (last_commit_time_);
+    }
+
+    /// @brief Sets the last commit timestamp.
+    /// @param last_commit_time last commit timestamp.
+    void setLastCommitTime(const boost::posix_time::ptime& last_commit_time) {
+        last_commit_time_ = last_commit_time;
+    }
+
 protected:
     /// @brief Copies the current configuration to a new configuration.
     ///
@@ -154,6 +167,9 @@ private:
 
     /// @brief Logical name of the server
     util::Optional<std::string> server_tag_;
+
+    /// @brief Stores the last commit timestamp.
+    boost::posix_time::ptime last_commit_time_;
 };
 
 /// @brief Non-const pointer to the @c ConfigBase.
index 43596f99743ae9b0ca18ce362b4586dd15ac2c1f..d9c75afedd6440e1f6cbf89d41d74f88746e3d70 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2020 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
@@ -99,6 +99,9 @@ DCfgMgrBase::simpleParseConfig(isc::data::ConstElementPtr config_set,
                     post_config_cb();
                 }
                 LOG_INFO(dctl_logger, DCTL_CONFIG_COMPLETE).arg(getConfigSummary(0));
+                // Set the last commit timestamp.
+                auto now = boost::posix_time::second_clock::universal_time();
+                context_->setLastCommitTime(now);
             } else {
                 rollback = true;
             }
index 18e8de242c48c66df2d7cb5c82e076e37f5e84c4..fb2d440a09696dcb6ed38329b0812b3bc8719048 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2020 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
@@ -135,6 +135,9 @@ DControllerBase::launch(int argc, char* argv[], const bool test_mode) {
                    << comment->stringValue());
     }
 
+    // Note that the controller was started.
+    start_ = boost::posix_time::second_clock::universal_time();
+
     // Everything is clear for launch, so start the application's
     // event loop.
     try {
@@ -636,6 +639,26 @@ DControllerBase::serverTagGetHandler(const std::string&, ConstElementPtr) {
     return (createAnswer(COMMAND_SUCCESS, response));
 }
 
+ConstElementPtr
+DControllerBase::statusGetHandler(const std::string&, ConstElementPtr) {
+    ElementPtr status = Element::createMap();
+    status->set("pid", Element::create(static_cast<int>(getpid())));
+
+    auto now = boost::posix_time::second_clock::universal_time();
+    if (!start_.is_not_a_date_time()) {
+        auto uptime = now - start_;
+        status->set("uptime", Element::create(uptime.total_seconds()));
+    }
+
+    auto last_commit = process_->getCfgMgr()->getContext()->getLastCommitTime();
+    if (!last_commit.is_not_a_date_time()) {
+        auto reload = now - last_commit;
+        status->set("reload", Element::create(reload.total_seconds()));
+    }
+
+    return (createAnswer(COMMAND_SUCCESS, status));
+}
+
 ConstElementPtr
 DControllerBase::versionGetHandler(const std::string&, ConstElementPtr) {
     ConstElementPtr answer;
index 6fbbb7c6ace2290f60608a41b42a873f4a4deeb4..73669ea7f55a968386132dbae26d66aae390e819 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2020 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
@@ -123,9 +123,10 @@ public:
     /// 1. parse command line arguments
     /// 2. instantiate and initialize the application process
     /// 3. load the configuration file
-    /// 4. initialize signal handling
-    /// 5. start and wait on the application process event loop
-    /// 6. exit to the caller
+    /// 4. record the start timestamp
+    /// 5. initialize signal handling
+    /// 6. start and wait on the application process event loop
+    /// 7. exit to the caller
     ///
     /// It is intended to be called from main() and be given the command line
     /// arguments.
@@ -340,6 +341,19 @@ public:
     serverTagGetHandler(const std::string& command,
                         isc::data::ConstElementPtr args);
 
+    /// @brief handler for status-get command
+    ///
+    /// This method handles the status-get command, which retrieves
+    /// the server process information i.e. the pid and returns it in
+    /// response.
+    ///
+    /// @param command (ignored)
+    /// @param args (ignored)
+    /// @return process information wrapped in a response
+    isc::data::ConstElementPtr
+    statusGetHandler(const std::string& command,
+                     isc::data::ConstElementPtr args);
+
 protected:
     /// @brief Virtual method that provides derivations the opportunity to
     /// support additional command line options.  It is invoked during command
index f5de80c46a308d2ebe2d7aa10d2b77b3d0f948c8..45f2046e0bd8eca4180a0c0f97f8177768eca0e9 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2020 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
@@ -52,6 +52,9 @@ static const std::string SERVER_TAG_GET_COMMAND("server-tag-get");
 /// @brief String value for the shutdown command.
 static const std::string SHUT_DOWN_COMMAND("shutdown");
 
+/// @brief String value for the status-get command.
+static const std::string STATUS_GET_COMMAND("status-get");
+
 /// @brief Returned by the process to indicate a command was successful.
 static const int COMMAND_SUCCESS = 0;
 
@@ -134,7 +137,7 @@ public:
     /// Certainly once during process startup, and possibly later if the user
     /// alters configuration. This method must not throw, it should catch any
     /// processing errors and return a success or failure answer as described
-    /// below.
+    /// below. On success the last commit timestamp must be updated.
     ///
     /// @param config_set a new configuration (JSON) for the process
     /// @param check_only true if configuration is to be verified only, not applied
index 91a2412dba37558cb2077e3864078c4a7d89122e..d92edc49ce93963ba96ff128029d390c0f9e06ee 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2020 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
@@ -12,6 +12,7 @@
 #include <util/pid_file.h>
 #include <util/signal_set.h>
 #include <boost/noncopyable.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
 #include <string>
 
 namespace isc {
@@ -260,6 +261,9 @@ protected:
     /// @brief Manufacture the pid file name
     std::string makePIDFileName() const;
 
+    /// @brief Timestamp of the start of the daemon.
+    boost::posix_time::ptime start_;
+
 private:
     /// @brief Config file name or empty if config file not used.
     std::string config_file_;