Browse Source

[2975] Stub implementation of the DNSClient class.

Marcin Siodelski 12 years ago
parent
commit
a50149705d

+ 2 - 0
src/bin/d2/Makefile.am

@@ -54,6 +54,7 @@ b10_dhcp_ddns_SOURCES += d_controller.cc d_controller.h
 b10_dhcp_ddns_SOURCES += d2_controller.cc d2_controller.h
 b10_dhcp_ddns_SOURCES += d2_update_message.cc d2_update_message.h
 b10_dhcp_ddns_SOURCES += d2_zone.cc d2_zone.h
+b10_dhcp_ddns_SOURCES += dns_client.cc dns_client.h
 
 nodist_b10_dhcp_ddns_SOURCES = d2_messages.h d2_messages.cc
 EXTRA_DIST += d2_messages.mes
@@ -61,6 +62,7 @@ EXTRA_DIST += d2_messages.mes
 b10_dhcp_ddns_LDADD = $(top_builddir)/src/lib/log/libb10-log.la
 b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
 b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
+b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/asiodns/libb10-asiodns.la
 b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
 b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/dns/libb10-dns++.la

+ 4 - 0
src/bin/d2/d2_update_message.h

@@ -63,6 +63,10 @@ public:
         isc::Exception(file, line, what) {}
 };
 
+class D2UpdateMessage;
+
+/// @brief Pointer to the DNS Update Message.
+typedef boost::shared_ptr<D2UpdateMessage> D2UpdateMessagePtr;
 
 /// @brief The @c D2UpdateMessage encapsulates a DNS Update message.
 ///

+ 90 - 0
src/bin/d2/dns_client.cc

@@ -0,0 +1,90 @@
+// Copyright (C) 2013 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 <d2/dns_client.h>
+#include <dns/messagerenderer.h>
+
+namespace isc {
+namespace d2 {
+
+namespace {
+
+// OutputBuffer objects are pre-allocated before data is written to them.
+// This is a default number of bytes for the buffers we create within
+// DNSClient class.
+const size_t DEFAULT_BUFFER_SIZE = 128;
+
+}
+
+using namespace isc::util;
+using namespace isc::asiolink;
+using namespace isc::asiodns;
+
+DNSClient::DNSClient(D2UpdateMessagePtr& response_placeholder,
+                     Callback* callback)
+    : in_buf_(new OutputBuffer(DEFAULT_BUFFER_SIZE)),
+      response_(response_placeholder), callback_(callback) {
+    if (!response_) {
+        isc_throw(BadValue, "a pointer to an object to encapsulate the DNS"
+                  " server must be provided; found NULL value");
+    }
+}
+
+void
+DNSClient::operator()(IOFetch::Result result) {
+    // @todo Do something useful here. One of the useful things will be to parse
+    // incoming message if the result is SUCCESS.
+
+    // Once we are done with internal business, let's call a callback supplied
+    // by a caller.
+    if (callback_ != NULL) {
+        (*callback_)(result);
+    }
+}
+
+void
+DNSClient::doUpdate(IOService& io_service,
+                    const IOAddress& ns_addr,
+                    const uint16_t ns_port,
+                    D2UpdateMessage& update,
+                    const int wait) {
+    // A renderer is used by the toWire function which creates the on-wire data
+    // from the DNS Update message. A renderer has its internal buffer where it
+    // renders data by default. However, this buffer can't be directly accessed.
+    // Fortunately, the renderer's API accepts user-supplied buffers. So, let's
+    // create our own buffer and pass it to the renderer so as the message is
+    // rendered to this buffer. Finally, we pass this buffer to IOFetch.
+    dns::MessageRenderer renderer;
+    OutputBufferPtr msg_buf(new OutputBuffer(DEFAULT_BUFFER_SIZE));
+    renderer.setBuffer(msg_buf.get());
+
+    // Render DNS Update message. This may throw a bunch of exceptions if
+    // invalid message object is given.
+    update.toWire(renderer);
+
+    // IOFetch has all the mechanisms that we need to perform asynchronous
+    // communication with the DNS server. The last but one argument points to
+    // this object as a completion callback for the message exchange. As a
+    // result operator()(IOFetch::Result) will be called.
+    IOFetch io_fetch(IOFetch::UDP, io_service, msg_buf, ns_addr, ns_port,
+                     in_buf_, this, wait);
+    // Post the task to the task queue in the IO service. Caller will actually
+    // run these tasks by executing IOService::run.
+    io_service.post(io_fetch);
+}
+
+
+} // namespace d2
+} // namespace isc
+

+ 140 - 0
src/bin/d2/dns_client.h

@@ -0,0 +1,140 @@
+// Copyright (C) 2013 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.
+
+#ifndef DNS_CLIENT_H
+#define DNS_CLIENT_H
+
+#include <d2/d2_update_message.h>
+
+#include <asiolink/io_service.h>
+#include <util/buffer.h>
+
+#include <asiodns/io_fetch.h>
+
+namespace isc {
+namespace d2 {
+
+/// @brief The @c DNSClient class handles communication with the DNS server.
+///
+/// Communication with the DNS server is asynchronous. Caller must provide a
+/// callback, which will be invoked when the response from the DNS server is
+/// received, a timeout has occured or IO service has been stopped for any
+/// reason. The caller-supplied callback is called by the internal callback
+/// operator implemented by @c DNSClient. This callback is responsible for
+/// initializing the @c D2UpdateMessage instance which encapsulates the response
+/// from the DNS. This initialization does not take place if the response from
+/// DNS is not received.
+///
+/// Caller must supply a pointer to the @c D2UpdateMessage object, which will
+/// encapsulate DNS response, through class constructor. An exception will be
+/// thrown if the pointer is not initialized by the caller.
+///
+/// @todo Currently, only the stub implementation is available for this class.
+/// The major missing piece is to create @c D2UpdateMessage object which will
+/// encapsulate the response from the DNS server.
+class DNSClient : public asiodns::IOFetch::Callback {
+public:
+
+    /// @brief Callback for the @c DNSClient class.
+    ///
+    /// This is is abstract class which represents the external callback for the
+    /// @c DNSClient. Caller must implement this class and supply its instance
+    /// in the @c DNSClient constructor to get callbacks when the DNS Update
+    /// exchange is complete (@see @c DNSClient).
+    class Callback {
+    public:
+        /// @brief Virtual destructor.
+        virtual ~Callback() { }
+
+        /// @brief Function operator implementing a callback.
+        ///
+        /// @param result an @c asiodns::IOFetch::Result object representing
+        /// IO status code.
+        virtual void operator()(asiodns::IOFetch::Result result) = 0;
+    };
+
+    /// @brief Constructor.
+    ///
+    /// @param response_placeholder Pointer to an object which will hold a
+    /// DNS server's response. Caller is responsible for allocating this object.
+    /// @param callback Pointer to an object implementing @c DNSClient::Callback
+    /// class. This object will be called when DNS message exchange completes or
+    /// if an error occurs. NULL value disables callback invocation.
+    DNSClient(D2UpdateMessagePtr& response_placeholder, Callback* callback);
+
+    /// @brief Virtual destructor, does nothing.
+    virtual ~DNSClient() { }
+
+    ///
+    /// @name Copy constructor and assignment operator
+    ///
+    /// Copy constructor and assignment operator are private because
+    /// @c DNSClient is a singleton class and its instance should not be copied.
+    //@{
+private:
+    DNSClient(const DNSClient& source);
+    DNSClient& operator=(const DNSClient& source);
+    //@}
+
+public:
+
+    /// @brief Function operator, implementing an internal callback.
+    ///
+    /// This internal callback is called when the DNS update message exchange is
+    /// complete. It further invokes the external callback provided by a caller.
+    /// Before external callback is invoked, an object of the @c D2UpdateMessage
+    /// type, representing a response from the server is set.
+    ///
+    /// @param result An @c asiodns::IOFetch::Result object representing status
+    /// code returned by the IO.
+    virtual void operator()(asiodns::IOFetch::Result result);
+
+    /// @brief Start asynchronous DNS Update.
+    ///
+    /// This function starts asynchronous DNS Update and returns. The DNS Update
+    /// will be executed by the specified IO service. Once the message exchange
+    /// with a DNS server is complete, timeout occurs or IO operation is
+    /// interrupted, the caller-supplied callback function will be invoked.
+    ///
+    /// An address and port of the DNS server is specified through the function
+    /// arguments so as the same instance of the @c DNSClient can be used to
+    /// initiate multiple message exchanges.
+    ///
+    /// @param io_service IO service to be used to run the message exchange.
+    /// @param ns_addr DNS server address.
+    /// @param ns_port DNS server port.
+    /// @param update A DNS Update message to be sent to the server.
+    /// @param wait A timeout (in seconds) for the response. If a response is
+    /// not received within the timeout, exchange is interrupted. A negative
+    /// value disables timeout.
+    void doUpdate(asiolink::IOService& io_service,
+                  const asiolink::IOAddress& ns_addr,
+                  const uint16_t ns_port,
+                  D2UpdateMessage& update,
+                  const int wait = -1);
+
+private:
+    /// A buffer holding server's response in the wire format.
+    util::OutputBufferPtr in_buf_;
+    /// A pointer to the caller-supplied object, encapsuating a response
+    /// from DNS.
+    D2UpdateMessagePtr response_;
+    /// A pointer to the external callback.
+    Callback* callback_;
+};
+
+} // namespace d2
+} // namespace isc
+
+#endif // DNS_CLIENT_H

+ 3 - 0
src/bin/d2/tests/Makefile.am

@@ -58,6 +58,7 @@ d2_unittests_SOURCES += ../d2_process.cc ../d2_process.h
 d2_unittests_SOURCES += ../d2_controller.cc ../d2_controller.h
 d2_unittests_SOURCES += ../d2_update_message.cc ../d2_update_message.h
 d2_unittests_SOURCES += ../d2_zone.cc ../d2_zone.h
+d2_unittests_SOURCES += ../dns_client.cc ../dns_client.h
 d2_unittests_SOURCES += d_test_stubs.cc d_test_stubs.h
 d2_unittests_SOURCES += d2_unittests.cc
 d2_unittests_SOURCES += d2_process_unittests.cc
@@ -65,6 +66,7 @@ d2_unittests_SOURCES += d_controller_unittests.cc
 d2_unittests_SOURCES += d2_controller_unittests.cc
 d2_unittests_SOURCES += d2_update_message_unittests.cc
 d2_unittests_SOURCES += d2_zone_unittests.cc
+d2_unittests_SOURCES += dns_client_unittests.cc
 nodist_d2_unittests_SOURCES = ../d2_messages.h ../d2_messages.cc
 
 d2_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
@@ -72,6 +74,7 @@ d2_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 d2_unittests_LDADD = $(GTEST_LDADD)
 d2_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
 d2_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/asiodns/libb10-asiodns.la
 d2_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 d2_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
 d2_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la

+ 141 - 0
src/bin/d2/tests/dns_client_unittests.cc

@@ -0,0 +1,141 @@
+// Copyright (C) 2013 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 <config.h>
+#include <d2/dns_client.h>
+#include <asiodns/io_fetch.h>
+#include <asiodns/logger.h>
+#include <dns/rcode.h>
+#include <dns/rrclass.h>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::asiodns;
+using namespace isc::d2;
+
+using namespace isc;
+using namespace isc::dns;
+
+namespace {
+
+const char* TEST_ADDRESS = "127.0.0.1";
+const uint16_t TEST_PORT = 5301;
+
+// @brief Test Fixture class.
+//
+// This test fixture class implements DNSClient::Callback so as it can be
+// installed as a completion callback for tests it implements. This callback
+// is called when a DDNS transaction (send and receive) completes. This allows
+// for the callback function to direcetly access class members. In particular,
+// the callback function can access IOService on which run() was called and
+// call stop() on it.
+class DNSClientTest : public virtual ::testing::Test, DNSClient::Callback {
+public:
+    IOService service_;
+    D2UpdateMessagePtr response_;
+    IOFetch::Result result_;
+
+    // @brief Constructor.
+    //
+    // This constructor overrides the default logging level of asiodns logger to
+    // prevent it from emitting debug messages from IOFetch class. Such an error
+    // message can be emitted if timeout occurs when DNSClient class is
+    // waiting for a response. Some of the tests are checking DNSClient behavior
+    // in case when response from the server is not received. Tests output would
+    // become messy if such errors were logged.
+    DNSClientTest()
+        : service_(),
+          result_(IOFetch::SUCCESS) {
+        asiodns::logger.setSeverity(log::INFO);
+        response_.reset(new D2UpdateMessage(D2UpdateMessage::INBOUND));
+    }
+
+    // @brief Destructor.
+    //
+    // Sets the asiodns logging level back to DEBUG.
+    virtual ~DNSClientTest() {
+        asiodns::logger.setSeverity(log::DEBUG);
+    };
+
+    // @brief Exchange completion calback.
+    //
+    // This callback is called when the exchange with the DNS server is
+    // complete or an error occured. This includes the occurence of a timeout.
+    //
+    // @param result An error code returned by an IO.
+    virtual void operator()(IOFetch::Result result) {
+        result_ = result;
+        service_.stop();
+    }
+
+    // This test verifies that when invalid response placeholder object is
+    // passed to a constructor, constructor throws the appropriate exception.
+    // It also verifies that the constructor will not throw if the supplied
+    // callback object is NULL.
+    void runConstructorTest() {
+        D2UpdateMessagePtr null_response;
+        EXPECT_THROW(DNSClient(null_response, this), isc::BadValue);
+        EXPECT_NO_THROW(DNSClient(response_, NULL));
+    }
+
+    // This test verifies the DNSClient behavior when a server does not respond
+    // do the DNS Update message. In such case, the callback function is
+    // expected to be called and the TIME_OUT error code should be returned.
+    void runSendNoReceiveTest() {
+        // Create outgoing message. Simply set the required message fields:
+        // error code and Zone section. This is enough to create on-wire format
+        // of this message and send it.
+        D2UpdateMessage message(D2UpdateMessage::OUTBOUND);
+        ASSERT_NO_THROW(message.setRcode(Rcode(Rcode::NOERROR_CODE)));
+        ASSERT_NO_THROW(message.setZone(Name("example.com"), RRClass::IN()));
+
+        // Use scoped pointer so as we can declare dns_client in the function
+        // scope.
+        boost::scoped_ptr<DNSClient> dns_client;
+        // Constructor may throw if the response placehoder is NULL.
+        EXPECT_NO_THROW(dns_client.reset(new DNSClient(response_, this)));
+
+        IOService io_service;
+        // Set the response wait time to 0 so as our test is not hanging. This
+        // should cause instant timeout.
+        const int timeout = 0;
+        // The doUpdate() function starts asynchronous message exchange with DNS
+        // server. When message exchange is done or timeout occurs, the
+        // completion callback will be triggered. The doUpdate function returns
+        // immediately.
+        EXPECT_NO_THROW(dns_client->doUpdate(service_, IOAddress(TEST_ADDRESS),
+                                             TEST_PORT, message, timeout));
+
+        // This starts the execution of tasks posted to IOService. run() blocks
+        // until stop() is called in the completion callback function.
+        service_.run();
+
+        // If callback function was called it should have modified the default
+        // value of result_ with the TIME_OUT error code.
+        EXPECT_EQ(IOFetch::TIME_OUT, result_);
+    }
+};
+
+TEST_F(DNSClientTest, constructor) {
+    runConstructorTest();
+}
+
+TEST_F(DNSClientTest, timeout) {
+    runSendNoReceiveTest();
+}
+
+} // End of anonymous namespace