Browse Source

[5350] User contexts implemented for subnets.

Tomek Mrugalski 7 years ago
parent
commit
28558468b2

+ 1 - 0
src/bin/dhcp4/dhcp4_lexer.ll

@@ -525,6 +525,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
 
 
 \"user-context\" {
 \"user-context\" {
     switch(driver.ctx_) {
     switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::SUBNET4:
     case isc::dhcp::Parser4Context::POOLS:
     case isc::dhcp::Parser4Context::POOLS:
         return isc::dhcp::Dhcp4Parser::make_USER_CONTEXT(driver.loc_);
         return isc::dhcp::Dhcp4Parser::make_USER_CONTEXT(driver.loc_);
     default:
     default:

+ 1 - 0
src/bin/dhcp4/dhcp4_parser.yy

@@ -874,6 +874,7 @@ subnet4_param: valid_lifetime
              | subnet_4o6_interface
              | subnet_4o6_interface
              | subnet_4o6_interface_id
              | subnet_4o6_interface_id
              | subnet_4o6_subnet
              | subnet_4o6_subnet
+             | user_context
              | unknown_map_entry
              | unknown_map_entry
              ;
              ;
 
 

+ 47 - 1
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc

@@ -119,7 +119,22 @@ const char* CONFIGS[] = {
     "            \"always-send\": true"
     "            \"always-send\": true"
     "        }"
     "        }"
     "    ]"
     "    ]"
-    "}" };
+    "}",
+
+    // Configuration 3:
+    // - one subnet, with one pool
+    // - user-contexts defined in both subnet and pool
+    "{"
+        "    \"subnet4\": [ { "
+        "    \"pools\": [ { \"pool\": \"10.254.226.0/25\","
+        "                   \"user-context\": { \"value\": 42 } } ],"
+        "    \"subnet\": \"10.254.226.0/24\", "
+        "    \"user-context\": {"
+        "        \"secure\": false"
+        "    }"
+        " } ],"
+    "\"valid-lifetime\": 4000 }",
+};
 
 
 // This test verifies that the destination address of the response
 // This test verifies that the destination address of the response
 // message is set to giaddr, when giaddr is set to non-zero address
 // message is set to giaddr, when giaddr is set to non-zero address
@@ -2795,6 +2810,37 @@ TEST_F(Dhcpv4SrvTest, tooLongClientId) {
     EXPECT_NO_THROW(client.doDORA());
     EXPECT_NO_THROW(client.doDORA());
 }
 }
 
 
+// Checks if user-contexts are parsed properly.
+TEST_F(Dhcpv4SrvTest, userContext) {
+
+    IfaceMgrTestConfig test_config(true);
+
+    NakedDhcpv4Srv srv(0);
+
+    // This config has one subnet with user-context with one
+    // pool (also with context). Make sure the configuration could be accepted.
+    cout << CONFIGS[3] << endl;
+    EXPECT_NO_THROW(configure(CONFIGS[3]));
+
+    // Now make sure the data was not lost.
+    ConstSrvConfigPtr cfg = CfgMgr::instance().getCurrentCfg();
+    const Subnet4Collection* subnets = cfg->getCfgSubnets4()->getAll();
+    ASSERT_TRUE(subnets);
+    ASSERT_EQ(1, subnets->size());
+
+    // Let's get the subnet and check its context.
+    Subnet4Ptr subnet1 = (*subnets)[0];
+    ASSERT_TRUE(subnet1);
+    ASSERT_TRUE(subnet1->getContext());
+    EXPECT_EQ("{ \"secure\": false }", subnet1->getContext()->str());
+
+    // Ok, not get the sole pool in it and check its context, too.
+    PoolCollection pools = subnet1->getPools(Lease::TYPE_V4);
+    ASSERT_EQ(1, pools.size());
+    ASSERT_TRUE(pools[0]);
+    ASSERT_TRUE(pools[0]->getContext());
+    EXPECT_EQ("{ \"value\": 42 }", pools[0]->getContext()->str());
+}
 
 
 /// @todo: Implement proper tests for MySQL lease/host database,
 /// @todo: Implement proper tests for MySQL lease/host database,
 ///        see ticket #4214.
 ///        see ticket #4214.

+ 1 - 0
src/bin/dhcp6/dhcp6_lexer.ll

@@ -784,6 +784,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
     switch(driver.ctx_) {
     switch(driver.ctx_) {
     case isc::dhcp::Parser6Context::POOLS:
     case isc::dhcp::Parser6Context::POOLS:
     case isc::dhcp::Parser6Context::PD_POOLS:
     case isc::dhcp::Parser6Context::PD_POOLS:
+    case isc::dhcp::Parser6Context::SUBNET6:
         return isc::dhcp::Dhcp6Parser::make_USER_CONTEXT(driver.loc_);
         return isc::dhcp::Dhcp6Parser::make_USER_CONTEXT(driver.loc_);
     default:
     default:
         return isc::dhcp::Dhcp6Parser::make_STRING("user-context", driver.loc_);
         return isc::dhcp::Dhcp6Parser::make_STRING("user-context", driver.loc_);

+ 1 - 0
src/bin/dhcp6/dhcp6_parser.yy

@@ -885,6 +885,7 @@ subnet6_param: preferred_lifetime
              | reservations
              | reservations
              | reservation_mode
              | reservation_mode
              | relay
              | relay
+             | user_context
              | unknown_map_entry
              | unknown_map_entry
              ;
              ;
 
 

+ 58 - 0
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc

@@ -125,6 +125,26 @@ const char* CONFIGS[] = {
     "         \"always-send\": true"
     "         \"always-send\": true"
     "    }"
     "    }"
     "    ]"
     "    ]"
+    "}",
+
+    // Configuration 3:
+    // - one subnet with one address pool and one prefix pool
+    // - user-contexts defined in subnet and each pool
+    "{"
+    "    \"subnet6\": [ {"
+    "       \"pools\": [ {"
+    "           \"pool\": \"2001:db8:1::/64\","
+    "           \"user-context\": { \"value\": 42 }"
+    "       } ], "
+    "       \"pd-pools\": [ {"
+    "           \"prefix\": \"2001:db8:abcd::\","
+    "           \"prefix-len\": 48,"
+    "           \"delegated-len\": 64,"
+    "           \"user-context\": { \"type\": \"prefixes\" }"
+    "        } ],"
+    "       \"subnet\": \"2001:db8:1::/48\","
+    "       \"user-context\": { \"secure\": false }"
+    "    } ] "
     "}"
     "}"
 };
 };
 
 
@@ -2424,6 +2444,44 @@ TEST_F(Dhcpv6SrvTest, tooLongServerId) {
     EXPECT_NO_THROW(client.doSARR());
     EXPECT_NO_THROW(client.doSARR());
 }
 }
 
 
+// Checks if user-contexts are parsed properly.
+TEST_F(Dhcpv6SrvTest, userContext) {
+
+    IfaceMgrTestConfig test_config(true);
+
+    NakedDhcpv6Srv srv(0);
+
+    // This config has one subnet with user-context with one
+    // pool (also with context). Make sure the configuration could be accepted.
+    EXPECT_NO_THROW(configure(CONFIGS[3]));
+
+    // Now make sure the data was not lost.
+    ConstSrvConfigPtr cfg = CfgMgr::instance().getCurrentCfg();
+    const Subnet6Collection* subnets = cfg->getCfgSubnets6()->getAll();
+    ASSERT_TRUE(subnets);
+    ASSERT_EQ(1, subnets->size());
+
+    // Let's get the subnet and check its context.
+    Subnet6Ptr subnet1 = (*subnets)[0];
+    ASSERT_TRUE(subnet1);
+    ASSERT_TRUE(subnet1->getContext());
+    EXPECT_EQ("{ \"secure\": false }", subnet1->getContext()->str());
+
+    // Ok, not get the address pool in it and check its context, too.
+    PoolCollection pools = subnet1->getPools(Lease::TYPE_NA);
+    ASSERT_EQ(1, pools.size());
+    ASSERT_TRUE(pools[0]);
+    ASSERT_TRUE(pools[0]->getContext());
+    EXPECT_EQ("{ \"value\": 42 }", pools[0]->getContext()->str());
+
+    // Ok, not get the prefix pool in it and check its context, too.
+    pools = subnet1->getPools(Lease::TYPE_PD);
+    ASSERT_EQ(1, pools.size());
+    ASSERT_TRUE(pools[0]);
+    ASSERT_TRUE(pools[0]->getContext());
+    EXPECT_EQ("{ \"type\": \"prefixes\" }", pools[0]->getContext()->str());
+}
+
 
 
 /// @todo: Add more negative tests for processX(), e.g. extend sanityCheck() test
 /// @todo: Add more negative tests for processX(), e.g. extend sanityCheck() test
 /// to call processX() methods.
 /// to call processX() methods.

+ 12 - 2
src/lib/dhcpsrv/parsers/dhcp_parsers.cc

@@ -445,7 +445,7 @@ PoolParser::parse(PoolStoragePtr pools,
             isc_throw(isc::dhcp::DhcpConfigError, "User context has to be a map ("
             isc_throw(isc::dhcp::DhcpConfigError, "User context has to be a map ("
                       << user_context->getPosition() << ")");
                       << user_context->getPosition() << ")");
         }
         }
-        pool->setUserContext(user_context);
+        pool->setContext(user_context);
     }
     }
 
 
     // Parser pool specific options.
     // Parser pool specific options.
@@ -620,6 +620,16 @@ SubnetConfigParser::createSubnet(ConstElementPtr params) {
         subnet_->allowClientClass(client_class);
         subnet_->allowClientClass(client_class);
     }
     }
 
 
+    // If there's user-context specified, store it.
+    ConstElementPtr user_context = params->get("user-context");
+    if (user_context) {
+        if (user_context->getType() != Element::map) {
+            isc_throw(isc::dhcp::DhcpConfigError, "User context has to be a map ("
+                      << user_context->getPosition() << ")");
+        }
+        subnet_->setContext(user_context);
+    }
+
     // Here globally defined options were merged to the subnet specific
     // Here globally defined options were merged to the subnet specific
     // options but this is no longer the case (they have a different
     // options but this is no longer the case (they have a different
     // and not consecutive priority).
     // and not consecutive priority).
@@ -890,7 +900,7 @@ PdPoolParser::parse(PoolStoragePtr pools, ConstElementPtr pd_pool_) {
     }
     }
 
 
     if (user_context_) {
     if (user_context_) {
-        pool_->setUserContext(user_context_);
+        pool_->setContext(user_context_);
     }
     }
 
 
     // Add the local pool to the external storage ptr.
     // Add the local pool to the external storage ptr.

+ 1 - 1
src/lib/dhcpsrv/pool.h

@@ -102,7 +102,7 @@ public:
 
 
     /// @brief Sets user context.
     /// @brief Sets user context.
     /// @param ctx user context to be stored.
     /// @param ctx user context to be stored.
-    void setUserContext(const data::ConstElementPtr& ctx) {
+    void setContext(const data::ConstElementPtr& ctx) {
         user_context_ = ctx;
         user_context_ = ctx;
     }
     }
 
 

+ 6 - 0
src/lib/dhcpsrv/subnet.cc

@@ -542,6 +542,12 @@ Subnet::toElement() const {
     ConstCfgOptionPtr opts = getCfgOption();
     ConstCfgOptionPtr opts = getCfgOption();
     map->set("option-data", opts->toElement());
     map->set("option-data", opts->toElement());
 
 
+    // Add user-context, but only if  defined. Omit if it was not.
+    ConstElementPtr ctx = getContext();
+    if (ctx) {
+        map->set("user-context", ctx);
+    }
+
     return (map);
     return (map);
 }
 }
 
 

+ 14 - 0
src/lib/dhcpsrv/subnet.h

@@ -349,6 +349,17 @@ public:
         host_reservation_mode_ = mode;
         host_reservation_mode_ = mode;
     }
     }
 
 
+    /// @brief Sets user context.
+    /// @param ctx user context to be stored.
+    void setContext(const data::ConstElementPtr& ctx) {
+        user_context_ = ctx;
+    }
+
+    /// @brief Returns const pointer to the user context.
+    data::ConstElementPtr getContext() const {
+        return (user_context_);
+    }
+
 protected:
 protected:
     /// @brief Returns all pools (non-const variant)
     /// @brief Returns all pools (non-const variant)
     ///
     ///
@@ -518,6 +529,9 @@ protected:
     ///
     ///
     /// See @ref HRMode type for details.
     /// See @ref HRMode type for details.
     HRMode host_reservation_mode_;
     HRMode host_reservation_mode_;
+
+    /// @brief Pointer to the user context (may be NULL)
+    data::ConstElementPtr user_context_;
 private:
 private:
 
 
     /// @brief Pointer to the option data configuration for this subnet.
     /// @brief Pointer to the option data configuration for this subnet.

+ 34 - 0
src/lib/dhcpsrv/tests/pool_unittest.cc

@@ -7,6 +7,7 @@
 #include <config.h>
 #include <config.h>
 
 
 #include <asiolink/io_address.h>
 #include <asiolink/io_address.h>
+#include <cc/data.h>
 #include <dhcp/option6_pdexclude.h>
 #include <dhcp/option6_pdexclude.h>
 #include <dhcpsrv/pool.h>
 #include <dhcpsrv/pool.h>
 
 
@@ -21,6 +22,7 @@
 using boost::scoped_ptr;
 using boost::scoped_ptr;
 using namespace isc;
 using namespace isc;
 using namespace isc::dhcp;
 using namespace isc::dhcp;
+using namespace isc::data;
 using namespace isc::asiolink;
 using namespace isc::asiolink;
 
 
 namespace {
 namespace {
@@ -172,6 +174,22 @@ TEST(Pool4Test, addOptions) {
     EXPECT_TRUE(options->empty());
     EXPECT_TRUE(options->empty());
 }
 }
 
 
+// This test checks that handling for user-context is valid.
+TEST(Pool4Test, userContext) {
+    // Create a pool to add options to it.
+    Pool4Ptr pool(new Pool4(IOAddress("192.0.2.0"),
+                            IOAddress("192.0.2.255")));
+
+    // Context should be empty until explicitly set.
+    EXPECT_FALSE(pool->getContext());
+
+    // When set, should be returned properly.
+    ElementPtr ctx = Element::create("{ \"comment\": \"foo\" }");
+    EXPECT_NO_THROW(pool->setContext(ctx));
+    ASSERT_TRUE(pool->getContext());
+    EXPECT_EQ(ctx->str(), pool->getContext()->str());
+}
+
 TEST(Pool6Test, constructor_first_last) {
 TEST(Pool6Test, constructor_first_last) {
 
 
     // let's construct 2001:db8:1:: - 2001:db8:1::ffff:ffff:ffff:ffff pool
     // let's construct 2001:db8:1:: - 2001:db8:1::ffff:ffff:ffff:ffff pool
@@ -460,4 +478,20 @@ TEST(Pool6Test, addOptions) {
     EXPECT_TRUE(options->empty());
     EXPECT_TRUE(options->empty());
 }
 }
 
 
+// This test checks that handling for user-context is valid.
+TEST(Pool6Test, userContext) {
+    // Create a pool to add options to it.
+    Pool6 pool(Lease::TYPE_NA, IOAddress("2001:db8::1"),
+                IOAddress("2001:db8::2"));
+
+    // Context should be empty until explicitly set.
+    EXPECT_FALSE(pool.getContext());
+
+    // When set, should be returned properly.
+    ElementPtr ctx = Element::create("{ \"comment\": \"foo\" }");
+    EXPECT_NO_THROW(pool.setContext(ctx));
+    ASSERT_TRUE(pool.getContext());
+    EXPECT_EQ(ctx->str(), pool.getContext()->str());
+}
+
 }; // end of anonymous namespace
 }; // end of anonymous namespace