Commit 35c022c9 authored by Thomas Markwalder's avatar Thomas Markwalder

[3329] Added D2ClientMgr control into b10-dhcp4

Server now starts D2ClientMgr send mode after configuration if updates are
enabled, and provides an error handler to intervene if D2 communications fail.
Added new unitests in d2_unittest.h and .cc.
parent e448f9b0
......@@ -114,6 +114,16 @@ ControlledDhcpv4Srv::dhcp4ConfigHandler(ConstElementPtr new_config) {
return (answer);
}
// Server will start DDNS communications if its enabled.
try {
server_->startD2();
} catch (const std::exception& ex) {
std::ostringstream err;
err << "error starting DHCP_DDNS client "
" after server reconfiguration: " << ex.what();
return (isc::config::createAnswer(1, err.str()));
}
// Configuration may change active interfaces. Therefore, we have to reopen
// sockets according to new configuration. This operation is not exception
// safe and we really don't want to emit exceptions to the callback caller.
......
......@@ -370,3 +370,8 @@ steps in the processing of incoming client message.
This warning message is output when a packet was received from a subnet
for which the DHCPv4 server has not been configured. The most probable
cause is a misconfiguration of the server.
% DHCP4_DDNS_REQUEST_SEND_FAILED failed sending a request to b10-dhcp-ddns, error : %1, ncr: %2
This error message indicates that IPv4 DHCP server attempted to send a DDNS
update reqeust to the DHCP-DDNS server. This is most likely a configuration or
networking error.
......@@ -1869,5 +1869,29 @@ bool Dhcpv4Srv::classSpecificProcessing(const Pkt4Ptr& query, const Pkt4Ptr& rsp
return (true);
}
void
Dhcpv4Srv::startD2() {
D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
if (d2_mgr.ddnsEnabled()) {
// Updates are enabled, so lets start the sender, passing in
// our error handler.
// This may throw so wherever this is called needs to ready.
d2_mgr.startSender(boost::bind(&Dhcpv4Srv::d2ClientErrorHandler,
this, _1, _2));
}
}
void
Dhcpv4Srv::d2ClientErrorHandler(const
dhcp_ddns::NameChangeSender::Result result,
dhcp_ddns::NameChangeRequestPtr& ncr) {
LOG_ERROR(dhcp4_logger, DHCP4_DDNS_REQUEST_SEND_FAILED).
arg(result).arg((ncr ? ncr->toText() : " NULL "));
// We cannot communicate with b10-dhcp-ddns, suspend futher updates.
/// @todo We may wish to revisit this, but for now we will simpy turn
/// them off.
CfgMgr::instance().getD2ClientMgr().suspendUpdates();
}
} // namespace dhcp
} // namespace isc
......@@ -21,6 +21,7 @@
#include <dhcp/option4_client_fqdn.h>
#include <dhcp/option_custom.h>
#include <dhcp_ddns/ncr_msg.h>
#include <dhcpsrv/d2_client_mgr.h>
#include <dhcpsrv/subnet.h>
#include <dhcpsrv/alloc_engine.h>
#include <hooks/callout_handle.h>
......@@ -165,6 +166,30 @@ public:
/// @param use_bcast should broadcast flags be set on the sockets.
static void openActiveSockets(const uint16_t port, const bool use_bcast);
/// @brief Starts DHCP_DDNS client IO if DDNS updates are enabled.
///
/// If updates are enabled, it Instructs the D2ClientMgr singleton to
/// enter send mode. If D2ClientMgr encounters errors it may throw
/// D2ClientErrors. This method does not catch exceptions.
void startD2();
/// @brief Implements the error handler for DHCP_DDNS IO errors
///
/// Invoked when a NameChangeRequest send to b10-dhcp-ddns completes with
/// a failed status. These are communications errors, not data related
/// failures.
///
/// This method logs the failure and then suspends all further updates.
/// Updating can only be restored by reconfiguration or restarting the
/// server. There is currently no retry logic so the first IO error that
/// occurs will suspend updates.
/// @todo We may wish to make this more robust or sophisticated.
///
/// @param result Result code of the send operation.
/// @param ncr NameChangeRequest which failed to send.
virtual void d2ClientErrorHandler(const dhcp_ddns::
NameChangeSender::Result result,
dhcp_ddns::NameChangeRequestPtr& ncr);
protected:
/// @name Functions filtering and sanity-checking received messages.
......
......@@ -76,6 +76,7 @@ TESTS += dhcp4_unittests
dhcp4_unittests_SOURCES = ../dhcp4_srv.h ../dhcp4_srv.cc ../ctrl_dhcp4_srv.cc
dhcp4_unittests_SOURCES += ../dhcp4_log.h ../dhcp4_log.cc
dhcp4_unittests_SOURCES += ../config_parser.cc ../config_parser.h
dhcp4_unittests_SOURCES += d2_unittest.h d2_unittest.cc
dhcp4_unittests_SOURCES += dhcp4_test_utils.h
dhcp4_unittests_SOURCES += dhcp4_unittests.cc
dhcp4_unittests_SOURCES += dhcp4_srv_unittest.cc
......
// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <dhcp/iface_mgr.h>
#include <dhcp4/config_parser.h>
#include <dhcp4/tests/d2_unittest.h>
#include <dhcpsrv/cfgmgr.h>
#include <gtest/gtest.h>
#include <string>
using namespace isc;
using namespace isc::asiolink;
using namespace isc::data;
namespace isc {
namespace dhcp {
namespace test {
/// @todo
void
D2Dhcpv4Srv::d2ClientErrorHandler(const
dhcp_ddns::NameChangeSender::Result result,
dhcp_ddns::NameChangeRequestPtr& ncr) {
++error_count_;
// call base class error handler
Dhcpv4Srv::d2ClientErrorHandler(result, ncr);
}
const bool Dhcp4SrvD2Test::SHOULD_PASS;
const bool Dhcp4SrvD2Test::SHOULD_FAIL;
Dhcp4SrvD2Test::Dhcp4SrvD2Test() {
}
Dhcp4SrvD2Test::~Dhcp4SrvD2Test() {
reset();
}
dhcp_ddns::NameChangeRequestPtr
Dhcp4SrvD2Test::buildTestNcr(uint32_t dhcid_id_num) {
// Build an NCR from json string.
std::ostringstream stream;
stream <<
"{"
" \"change_type\" : 0 , "
" \"forward_change\" : true , "
" \"reverse_change\" : false , "
" \"fqdn\" : \"myhost.example.com.\" , "
" \"ip_address\" : \"192.168.2.1\" , "
" \"dhcid\" : \""
<< std::hex << std::setfill('0') << std::setw(16)
<< dhcid_id_num << "\" , "
" \"lease_expires_on\" : \"20140121132405\" , "
" \"lease_length\" : 1300 "
"}";
return (dhcp_ddns::NameChangeRequest::fromJSON(stream.str()));
}
void
Dhcp4SrvD2Test::reset() {
std::string config = "{ \"interfaces\": [ \"*\" ],"
"\"hooks-libraries\": [ ], "
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"valid-lifetime\": 4000, "
"\"subnet4\": [ ], "
"\"dhcp-ddns\": { \"enable-updates\" : false }, "
"\"option-def\": [ ], "
"\"option-data\": [ ] }";
configure(config, SHOULD_PASS);
}
void
Dhcp4SrvD2Test::configureD2(bool enable_d2, const bool exp_result,
const std::string& ip_address,
const uint32_t port) {
std::ostringstream config;
config <<
"{ \"interfaces\": [ \"*\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ { "
" \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
" \"subnet\": \"192.0.2.0/24\" } ],"
" \"dhcp-ddns\" : {"
" \"enable-updates\" : " << (enable_d2 ? "true" : "false") << ", "
" \"server-ip\" : \"" << ip_address << "\", "
" \"server-port\" : " << port << ", "
" \"ncr-protocol\" : \"UDP\", "
" \"ncr-format\" : \"JSON\", "
" \"always-include-fqdn\" : true, "
" \"allow-client-update\" : true, "
" \"override-no-update\" : true, "
" \"override-client-update\" : true, "
" \"replace-client-name\" : true, "
" \"generated-prefix\" : \"test.prefix\", "
" \"qualifying-suffix\" : \"test.suffix.\" },"
"\"valid-lifetime\": 4000 }";
configure(config.str(), exp_result);
}
void
Dhcp4SrvD2Test::configure(const std::string& config, bool exp_result) {
ElementPtr json = Element::fromJSON(config);
ConstElementPtr status;
// Configure the server and make sure the config is accepted
EXPECT_NO_THROW(status = configureDhcp4Server(srv_, json));
ASSERT_TRUE(status);
int rcode;
ConstElementPtr comment = config::parseAnswer(rcode, status);
if (exp_result == SHOULD_PASS) {
ASSERT_EQ(0, rcode);
} else {
ASSERT_EQ(1, rcode);
}
}
TEST_F(Dhcp4SrvD2Test, enableDisable) {
// Grab the manager and verify that be default ddns is off
// and a sender was not started.
dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
ASSERT_FALSE(mgr.ddnsEnabled());
ASSERT_FALSE(mgr.amSending());
// Verify a valid config with ddns enabled configures ddns properly,
// but does not start the sender.
ASSERT_NO_FATAL_FAILURE(configureD2(true));
ASSERT_TRUE(mgr.ddnsEnabled());
ASSERT_FALSE(mgr.amSending());
// Verify that calling start does not throw and starts the sender.
ASSERT_NO_THROW(srv_.startD2());
ASSERT_TRUE(mgr.amSending());
// Verify a valid config with ddns disabled configures ddns properly.
// Sender should not have been started.
ASSERT_NO_FATAL_FAILURE(configureD2(false));
ASSERT_FALSE(mgr.ddnsEnabled());
ASSERT_FALSE(mgr.amSending());
// Verify that the sender does NOT get started when ddns is disabled.
srv_.startD2();
ASSERT_FALSE(mgr.amSending());
}
TEST_F(Dhcp4SrvD2Test, badConfig) {
// Grab the manager and verify that be default ddns is off
// and a sender was not started.
dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
ASSERT_FALSE(mgr.ddnsEnabled());
// Configure it enabled and start it.
ASSERT_NO_FATAL_FAILURE(configureD2(true));
ASSERT_TRUE(mgr.ddnsEnabled());
ASSERT_NO_THROW(srv_.startD2());
ASSERT_TRUE(mgr.amSending());
// Now attempt to give it an invalid configuration.
// Result should indicate failure.
ASSERT_NO_FATAL_FAILURE(configureD2(false, SHOULD_FAIL, "bogus_ip"));
// Configure was not altered, so ddns should be enabled and still sending.
ASSERT_TRUE(mgr.ddnsEnabled());
ASSERT_TRUE(mgr.amSending());
// Verify that calling start does not throw or stop the sender.
ASSERT_NO_THROW(srv_.startD2());
ASSERT_TRUE(mgr.amSending());
}
TEST_F(Dhcp4SrvD2Test, sameConfig) {
// Grab the manager and verify that be default ddns is off
// and a sender was not started.
dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
ASSERT_FALSE(mgr.ddnsEnabled());
// Configure it enabled and start it.
ASSERT_NO_FATAL_FAILURE(configureD2(true));
ASSERT_TRUE(mgr.ddnsEnabled());
ASSERT_NO_THROW(srv_.startD2());
ASSERT_TRUE(mgr.amSending());
// Now submit an identical configuration.
ASSERT_NO_FATAL_FAILURE(configureD2(true));
// Configuration was not altered, so ddns should still enabled and sending.
ASSERT_TRUE(mgr.ddnsEnabled());
ASSERT_TRUE(mgr.amSending());
// Verify that calling start does not throw or stop the sender.
ASSERT_NO_THROW(srv_.startD2());
ASSERT_TRUE(mgr.amSending());
}
TEST_F(Dhcp4SrvD2Test, differentConfig) {
// Grab the manager and verify that be default ddns is off
// and a sender was not started.
dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
ASSERT_FALSE(mgr.ddnsEnabled());
// Configure it enabled and start it.
ASSERT_NO_FATAL_FAILURE(configureD2(true));
ASSERT_TRUE(mgr.ddnsEnabled());
ASSERT_NO_THROW(srv_.startD2());
ASSERT_TRUE(mgr.amSending());
// Now enable it on a different port.
ASSERT_NO_FATAL_FAILURE(configureD2(true, SHOULD_PASS, "127.0.0.1", 54001));
// Configuration was altered, so ddns should still enabled but not sending.
ASSERT_TRUE(mgr.ddnsEnabled());
ASSERT_FALSE(mgr.amSending());
// Verify that calling start starts the sender.
ASSERT_NO_THROW(srv_.startD2());
ASSERT_TRUE(mgr.amSending());
}
TEST_F(Dhcp4SrvD2Test, simpleUDPSend) {
// Grab the manager and verify that be default ddns is off
// and a sender was not started.
dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
ASSERT_FALSE(mgr.ddnsEnabled());
// Configure it enabled and start it.
ASSERT_NO_FATAL_FAILURE(configureD2(true));
ASSERT_TRUE(mgr.ddnsEnabled());
ASSERT_NO_THROW(srv_.startD2());
ASSERT_TRUE(mgr.amSending());
// Verify that we can queue up a message.
dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr();
ASSERT_NO_THROW(mgr.sendRequest(ncr));
EXPECT_EQ(1, mgr.getQueueSize());
// Calling receive should detect the ready IO on the sender's select-fd,
// and invoke callback, which should complete the send.
ASSERT_NO_THROW(IfaceMgr::instance().receive4(0,0));
// Verify the queue is now empty.
EXPECT_EQ(0, mgr.getQueueSize());
}
TEST_F(Dhcp4SrvD2Test, forceUDPSendFailure) {
// Grab the manager and verify that be default ddns is off
// and a sender was not started.
dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
ASSERT_FALSE(mgr.ddnsEnabled());
// Configure it enabled and start it.
// Using server address of 0.0.0.0/0 should induce failure on send.
ASSERT_NO_FATAL_FAILURE(configureD2(true, SHOULD_PASS, "0.0.0.0", 0));
ASSERT_TRUE(mgr.ddnsEnabled());
ASSERT_NO_THROW(srv_.startD2());
ASSERT_TRUE(mgr.amSending());
// Queue up 3 messages.
for (int i = 0; i < 3; i++) {
dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr(i + 1);
ASSERT_NO_THROW(mgr.sendRequest(ncr));
}
EXPECT_EQ(3, mgr.getQueueSize());
// Calling receive should detect the ready IO on the sender's select-fd,
// and invoke callback, which should complete the send, which should
// fail.
ASSERT_NO_THROW(IfaceMgr::instance().receive4(0,0));
// Verify the error handler was invoked.
EXPECT_EQ(1, srv_.error_count_);
// Verify that updates are disabled and we are no longer sending.
ASSERT_FALSE(mgr.ddnsEnabled());
ASSERT_FALSE(mgr.amSending());
// Verify message is still in the queue.
EXPECT_EQ(3, mgr.getQueueSize());
// Verify that we can't just restart it.
/// @todo This may change if we add ability to resume.
ASSERT_NO_THROW(srv_.startD2());
ASSERT_FALSE(mgr.amSending());
// Configure it enabled and start it.
ASSERT_NO_FATAL_FAILURE(configureD2(true));
ASSERT_TRUE(mgr.ddnsEnabled());
ASSERT_NO_THROW(srv_.startD2());
ASSERT_TRUE(mgr.amSending());
// Verify message is still in the queue.
EXPECT_EQ(3, mgr.getQueueSize());
// This will finish sending the 1st message in queue
// and initiate send of 2nd message.
ASSERT_NO_THROW(IfaceMgr::instance().receive4(0,0));
EXPECT_EQ(1, srv_.error_count_);
// First message is off the queue.
EXPECT_EQ(2, mgr.getQueueSize());
}
} // namespace test
} // namespace dhcp
} // namespace isc
// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
/// @file d2_unittest.h Defines classes for testing Dhcpv4srv with D2ClientMgr
#ifndef D2_UNITTEST_H
#define D2_UNITTEST_H
#include <dhcp4/dhcp4_srv.h>
#include <config/ccsession.h>
#include <gtest/gtest.h>
namespace isc {
namespace dhcp {
namespace test {
/// @brief Test derivation of Dhcpv4Srv class used in D2 testing.
/// Use of this class allows the intervention at strategic points in testing
/// by permitting overridden methods and access to scope protected members.
class D2Dhcpv4Srv : public Dhcpv4Srv {
public:
/// @brief Counts the number of times the client error handler is called.
int error_count_;
/// @brief Constructor
D2Dhcpv4Srv()
: Dhcpv4Srv(0, "type=memfile", false, false), error_count_(0) {
}
/// @brief virtual Destructor.
virtual ~D2Dhcpv4Srv() {
}
/// @brief Override the error handler.
virtual void d2ClientErrorHandler(const dhcp_ddns::NameChangeSender::
Result result,
dhcp_ddns::NameChangeRequestPtr& ncr);
};
/// @brief Test fixture which permits testing the interaction between the
/// D2ClientMgr and Dhcpv4Srv.
class Dhcp4SrvD2Test : public ::testing::Test {
public:
/// @brief Mnemonic constants for calls to configuration methods.
static const bool SHOULD_PASS = true;
static const bool SHOULD_FAIL = false;
/// @brief Constructor
Dhcp4SrvD2Test();
/// @brief virtual Destructor
virtual ~Dhcp4SrvD2Test();
/// @brief Resets the CfgMgr singleton to defaults.
/// Primarily used in the test destructor as gtest doesn't exit between
/// tests.
/// @todo CfgMgr should provide a method to reset everything or maybe
/// reconstruct the singleton.
void reset();
/// @brief Configures the server with D2 enabled or disabled
///
/// Constructs a configuration string including dhcp-ddns with the
/// parameters given and passes it into the server's configuration handler.
///
/// @param enable_updates value to assign to the enable-updates parameter
/// @param exp_result indicates if configuration should pass or fail
/// @param ip_address IP address for the D2 server
/// @param port port for the D2 server
void configureD2(bool enable_updates, bool exp_result = SHOULD_PASS,
const std::string& ip_address = "127.0.0.1",
const uint32_t port = 53001);
/// @brief Configures the server with the given configuration
///
/// Passes the given configuration string into the server's configuration
/// handler. It accepts a flag indicating whether or not the configuration
/// is expected to succeed or fail. This permits testing the server's
/// response to both valid and invalid configurations.
///
/// @param config JSON string containing the configuration
/// @param exp_result indicates if configuration should pass or fail
void configure(const std::string& config, bool exp_result = SHOULD_PASS);
/// @brief Contructs a NameChangeRequest message from a fixed JSON string.
///
/// @param dhcid_id_num Integer value to use as the DHCID.
dhcp_ddns::NameChangeRequestPtr buildTestNcr(uint32_t
dhcid_id_num = 0xdeadbeef);
/// @brief Stores the return code of the last configuration attempt.
int rcode_;
/// @brief Stores the message component of the last configuration tattempt.
isc::data::ConstElementPtr comment_;
/// @brief Server object under test.
D2Dhcpv4Srv srv_;
};
}; // end of isc::dhcp::test namespace
}; // end of isc::dhcp namespace
}; // end of isc namespace
#endif // D2_UNITTEST_H
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment