]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#494, !273] Unit-test written for authoritative flag. 494-dhcp4configparser-sharednetworkssanitychecks-is-buggy
authorTomek Mrugalski <tomasz@isc.org>
Fri, 19 Apr 2019 10:22:40 +0000 (12:22 +0200)
committerTomek Mrugalski <tomek@isc.org>
Fri, 19 Apr 2019 11:26:14 +0000 (07:26 -0400)
src/bin/dhcp4/tests/dhcp4_test_utils.cc
src/bin/dhcp4/tests/dhcp4_test_utils.h
src/bin/dhcp4/tests/shared_network_unittest.cc

index 2083ad9270fb48e8d47670ab5f119ad9208152af..0f952eb98d2d33d129d8c6b1c0b17bea953b2e6b 100644 (file)
@@ -26,6 +26,7 @@
 #include <dhcpsrv/lease_mgr_factory.h>
 #include <log/logger_support.h>
 #include <stats/stats_mgr.h>
+#include <sstream>
 
 using namespace std;
 using namespace isc::asiolink;
@@ -638,6 +639,54 @@ Dhcpv4SrvTest::configure(const std::string& config, NakedDhcpv4Srv& srv,
     }
  }
 
+std::pair<int, std::string>
+Dhcpv4SrvTest::configureWithStatus(const std::string& config, NakedDhcpv4Srv& srv,
+                                   const bool commit, const int exp_rcode) {
+    ConstElementPtr json;
+    try {
+        json = parseJSON(config);
+    } catch (const std::exception& ex){
+        // Fatal falure on parsing error
+
+        std::stringstream tmp;
+        tmp << "parsing failure:"
+            << "config:" << config << std::endl
+            << "error: " << ex.what();
+        ADD_FAILURE() << tmp.str();
+        return (std::make_pair(-1, tmp.str()));
+    }
+
+    ConstElementPtr status;
+
+    // Disable the re-detect flag
+    disableIfacesReDetect(json);
+
+    // Configure the server and make sure the config is accepted
+    EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
+    EXPECT_TRUE(status);
+    if (!status) {
+        return (make_pair(-1, "configureDhcp4Server didn't return anythin"));
+    }
+
+    int rcode;
+    ConstElementPtr comment = config::parseAnswer(rcode, status);
+    EXPECT_EQ(exp_rcode, rcode) << comment->stringValue();
+
+    // Use specified lease database backend.
+    if (rcode == 0) {
+        EXPECT_NO_THROW( {
+            CfgDbAccessPtr cfg_db = CfgMgr::instance().getStagingCfg()->getCfgDbAccess();
+            cfg_db->setAppendedParameters("universe=4");
+            cfg_db->createManagers();
+            } );
+        if (commit) {
+            CfgMgr::instance().commit();
+        }
+    }
+
+    return (std::make_pair(rcode, comment->stringValue()));
+ }
+
 Dhcpv4Exchange
 Dhcpv4SrvTest::createExchange(const Pkt4Ptr& query) {
     bool drop = false;
index 0ec1171f283485bdfc3683ea8ecf72e977ca15e0..c286d97b5689d66b222848a2fa2dc07501a93756 100644 (file)
@@ -446,6 +446,18 @@ public:
     void configure(const std::string& config, NakedDhcpv4Srv& srv,
                    const bool commit = true);
 
+    /// @brief Configure specified DHCP server using JSON string.
+    ///
+    /// @param config String holding server configuration in JSON format.
+    /// @param srv Instance of the server to be configured.
+    /// @param commit A boolean flag indicating if the new configuration
+    /// should be committed (if true), or not (if false).
+    /// @param exp_rcode expected status code (default = 0 (success))
+    /// @return (a pair of status code and a string with result)
+    std::pair<int, std::string>
+    configureWithStatus(const std::string& config, NakedDhcpv4Srv& srv,
+                        const bool commit = true, const int exp_rcode = 0);
+
     /// @brief Pretends a packet of specified type was received.
     ///
     /// Instantiates fake network interfaces, configures passed Dhcpv4Srv,
index 7afcbe2077f33545879334b420fb950bf3fe09d9..019e9e4f707da8fe0564bc0355e993e3eb0a8f19 100644 (file)
@@ -1029,6 +1029,15 @@ public:
         StatsMgr::instance().removeAll();
     }
 
+    /// @brief Specifies authoritative flag value
+    ///
+    /// Used to generate authoritative configs
+    typedef enum AuthoritativeFlag {
+        AUTH_DEFAULT, // explicit value not specified (use default)
+        AUTH_YES,     // defined explicitly as yes
+        AUTH_NO       // defined explciitly as no
+    } AuthoritativeFlag;
+
     /// @brief Returns subnet having specified address in range.
     ///
     /// @param address Address for which subnet is being searched.
@@ -1242,6 +1251,76 @@ public:
         EXPECT_EQ(ns_address, addrs[0].toText());
     }
 
+    /// @brief returns authoritative flag as JSON string
+    /// @param f value to be specified (default, true or false)
+    string auth(AuthoritativeFlag f) {
+        switch (f) {
+        case AUTH_DEFAULT:
+            return ("");
+        case AUTH_YES:
+            return ("            \"authoritative\": true,");
+            break;
+        case AUTH_NO:
+            return ("            \"authoritative\": false,");
+        }
+        return ("");
+    }
+
+    /// @brief generates Config file with specified authoritative flag values
+    ///
+    /// The config file has the same structure:
+    /// - 1 shared network with 2 subnets (global authoritative flag)
+    ///   - first subnet: authoritative (subnet1 flag here)
+    ///   - second subnet: authoritative (subnet2 flag here)
+    ///
+    /// @param global coverns presence/value of global authoritative flag
+    /// @param subnet1 coverns presence/value of authoritative flag in subnet1
+    /// @param subnet2 coverns presence/value of authoritative flag in subnet2
+    string generateAuthConfig(AuthoritativeFlag global, AuthoritativeFlag subnet1,
+                              AuthoritativeFlag subnet2) {
+        string cfg = "{"
+            "    \"interfaces-config\": {"
+            "        \"interfaces\": [ \"*\" ]"
+            "    },"
+            "    \"valid-lifetime\": 600,"
+            "    \"shared-networks\": ["
+            "        {"
+            "            \"name\": \"frog\","
+            "            \"comment\": \"example\",";
+        cfg += auth(global);
+        cfg +=
+            "            \"subnet4\": ["
+            "                {"
+            "                    \"subnet\": \"192.0.2.0/26\","
+            "                    \"id\": 10,";
+        cfg += auth(subnet1);
+
+        cfg +=
+            "                    \"pools\": ["
+            "                        {"
+            "                            \"pool\": \"192.0.2.63 - 192.0.2.63\""
+            "                        }"
+            "                    ]"
+            "                },"
+            "                {"
+            "                    \"subnet\": \"10.0.0.0/24\","
+            "                    \"id\": 100,";
+        cfg += auth(subnet2);
+        cfg +=
+            "                    \"pools\": ["
+            "                        {"
+            "                            \"pool\": \"10.0.0.16 - 10.0.0.16\""
+            "                        }"
+            "                    ]"
+            "                }"
+            "            ]"
+            "        }"
+            "    ]"
+            "}";
+
+        return (cfg);
+    }
+
     /// @brief Destructor.
     virtual ~Dhcpv4SharedNetworkTest() {
         StatsMgr::instance().removeAll();
@@ -2564,4 +2643,66 @@ TEST_F(Dhcpv4SharedNetworkTest, precedenceReservation) {
     testPrecedence(config, "192.0.2.6");
 }
 
+// Verify authoritative sanitization.
+// This test generates many similar configs. They all have similar
+// structure:
+// - 1 shared-network (with possibly authoritative flag in it)
+//   2 subnets (with each possibly having its own authoritative flag)
+//
+// If the flag is specified on subnet-level, the value must match those
+// specified on subnet level.
+TEST_F(Dhcpv4SharedNetworkTest, authoritative) {
+
+    // Each scenario will be defined using those parameters.
+    typedef struct scenario {
+        bool exp_success;
+        AuthoritativeFlag global;
+        AuthoritativeFlag subnet1;
+        AuthoritativeFlag subnet2;
+    } scenario;
+
+    // We have the following scenarios. The default is no.
+    // The only allowed combinations are those that end up with
+    // having all three values (global, subnet1, subnet2) agree.
+    scenario scenarios[] = {
+        //result, global,      subnet1,      subnet2
+        { true, AUTH_DEFAULT,  AUTH_DEFAULT, AUTH_DEFAULT },
+        { true, AUTH_YES,      AUTH_DEFAULT, AUTH_DEFAULT },
+        { true, AUTH_YES,      AUTH_YES,     AUTH_YES },
+        { true, AUTH_NO,       AUTH_DEFAULT, AUTH_DEFAULT },
+        { true, AUTH_NO,       AUTH_NO,      AUTH_NO },
+        { false, AUTH_DEFAULT, AUTH_YES,     AUTH_NO },
+        { false, AUTH_DEFAULT, AUTH_NO,      AUTH_YES },
+        { false, AUTH_DEFAULT, AUTH_YES,     AUTH_YES },
+        { false, AUTH_YES,     AUTH_NO,      AUTH_NO },
+        { false, AUTH_YES,     AUTH_DEFAULT, AUTH_NO },
+        { false, AUTH_YES,     AUTH_NO,      AUTH_DEFAULT },
+        { false, AUTH_YES,     AUTH_NO,      AUTH_NO },
+        { false, AUTH_YES,     AUTH_NO,      AUTH_YES },
+        { false, AUTH_YES,     AUTH_YES,     AUTH_NO }
+    };
+
+    // Let's test them one by one
+    int cnt = 0;
+    for ( auto s : scenarios) {
+        cnt++;
+
+        string cfg = generateAuthConfig(s.global, s.subnet1, s.subnet2);
+
+        // Create client and set MAC address to the one that has a reservation.
+        Dhcp4Client client(Dhcp4Client::SELECTING);
+
+        stringstream tmp;
+        tmp << "Testing scenario " << cnt;
+        SCOPED_TRACE(tmp.str());
+        // Create server configuration.
+        auto result = configureWithStatus(cfg, *client.getServer(), true, s.exp_success? 0 : 1);
+        if (s.exp_success) {
+            EXPECT_EQ(result.first, 0) << result.second;
+        } else {
+            EXPECT_EQ(result.first, 1) << "Configuration expected to fail, but succeeded";
+        }
+    }
+}
+
 } // end of anonymous namespace