Browse Source

introduced an abstract base class for XfroutClient, introduced a mock derived class of it, and added detailed tests for xfrout using it.

git-svn-id: svn://bind10.isc.org/svn/bind10/branches/trac221@2317 e5f2f494-b856-4b98-b285-d166d9295462
JINMEI Tatuya 15 years ago
parent
commit
798c5262f8

+ 22 - 15
src/bin/auth/auth_srv.cc

@@ -52,7 +52,6 @@
 #include <auth/common.h>
 #include <auth/auth_srv.h>
 #include <auth/asio_link.h>
-#include <auth/spec_config.h>
 
 #include <boost/lexical_cast.hpp>
 
@@ -75,7 +74,7 @@ private:
     AuthSrvImpl(const AuthSrvImpl& source);
     AuthSrvImpl& operator=(const AuthSrvImpl& source);
 public:
-    AuthSrvImpl();
+    AuthSrvImpl(AbstractXfroutClient& xfrout_client);
     ~AuthSrvImpl();
     isc::data::ElementPtr setDbFile(const isc::data::ElementPtr config);
 
@@ -99,16 +98,17 @@ public:
     isc::cc::Session session_with_xfrin_;
 
     bool is_axfr_connection_established_;
-    XfroutClient axfr_client_;
+    AbstractXfroutClient& xfrout_client_;
 
     /// Currently non-configurable, but will be.
     static const uint16_t DEFAULT_LOCAL_UDPSIZE = 4096;
 };
 
-AuthSrvImpl::AuthSrvImpl() : cs_(NULL), verbose_mode_(false),
-                             is_notify_session_established_(false),
-                             is_axfr_connection_established_(false),
-                             axfr_client_(UNIX_SOCKET_FILE)
+AuthSrvImpl::AuthSrvImpl(AbstractXfroutClient& xfrout_client) :
+    cs_(NULL), verbose_mode_(false),
+    is_notify_session_established_(false),
+    is_axfr_connection_established_(false),
+    xfrout_client_(xfrout_client)
 {
     // cur_datasrc_ is automatically initialized by the default constructor,
     // effectively being an empty (sqlite) data source.  once ccsession is up
@@ -125,13 +125,14 @@ AuthSrvImpl::~AuthSrvImpl() {
     }
 
     if (is_axfr_connection_established_) {
-        axfr_client_.disconnect();
+        xfrout_client_.disconnect();
         is_axfr_connection_established_ = false;
     }
 }
 
-AuthSrv::AuthSrv() : impl_(new AuthSrvImpl) {
-}
+AuthSrv::AuthSrv(AbstractXfroutClient& xfrout_client) :
+    impl_(new AuthSrvImpl(xfrout_client))
+{}
 
 AuthSrv::~AuthSrv() {
     delete impl_;
@@ -342,15 +343,20 @@ AuthSrvImpl::processAxfrQuery(const IOMessage& io_message, Message& message,
 
     try {
         if (!is_axfr_connection_established_) {
-            axfr_client_.connect();
+            xfrout_client_.connect();
             is_axfr_connection_established_ = true;
         }
-        axfr_client_.sendXfroutRequestInfo(io_message.getSocket().getNative(),
-                                           io_message.getData(),
-                                           io_message.getDataSize());
+        xfrout_client_.sendXfroutRequestInfo(
+            io_message.getSocket().getNative(),
+            io_message.getData(),
+            io_message.getDataSize());
     } catch (const XfroutError& err) { 
         if (is_axfr_connection_established_) {
-            axfr_client_.disconnect();
+            // discoonect() may trigger an exception, but since we try it
+            // only if we've successfully opened it, it shouldn't happen in
+            // normal condition.  Should this occur, we'll propagate it to the
+            // upper layer.
+            xfrout_client_.disconnect();
             is_axfr_connection_established_ = false;
         }
         
@@ -360,6 +366,7 @@ AuthSrvImpl::processAxfrQuery(const IOMessage& io_message, Message& message,
         }
         makeErrorMessage(message, response_renderer, Rcode::SERVFAIL(),
                          verbose_mode_);
+        return (true);
     }
     return (false);
 }

+ 5 - 1
src/bin/auth/auth_srv.h

@@ -28,6 +28,10 @@ class InputBuffer;
 class Message;
 class MessageRenderer;
 }
+
+namespace xfr {
+class AbstractXfroutClient;
+};
 }
 
 namespace asio_link {
@@ -47,7 +51,7 @@ private:
     AuthSrv(const AuthSrv& source);
     AuthSrv& operator=(const AuthSrv& source);
 public:
-    explicit AuthSrv();
+    explicit AuthSrv(isc::xfr::AbstractXfroutClient& xfrout_client);
     ~AuthSrv();
     //@}
     /// \return \c true if the \message contains a response to be returned;

+ 10 - 5
src/bin/auth/main.cc

@@ -39,16 +39,19 @@
 #include <cc/data.h>
 #include <config/ccsession.h>
 
-#include "spec_config.h"
-#include "common.h"
-#include "auth_srv.h"
-#include "asio_link.h"
+#include <xfr/xfrout_client.h>
+
+#include <auth/spec_config.h>
+#include <auth/common.h>
+#include <auth/auth_srv.h>
+#include <auth/asio_link.h>
 
 using namespace std;
 using namespace isc::data;
 using namespace isc::cc;
 using namespace isc::config;
 using namespace isc::dns;
+using namespace isc::xfr;
 
 namespace {
 
@@ -133,6 +136,8 @@ main(int argc, char* argv[]) {
 
     // initialize command channel
     int ret = 0;
+
+    XfroutClient xfrout_client(UNIX_SOCKET_FILE);
     try {
         string specfile;
         if (getenv("B10_FROM_BUILD")) {
@@ -142,7 +147,7 @@ main(int argc, char* argv[]) {
             specfile = string(AUTH_SPECFILE_LOCATION);
         }
 
-        auth_server = new AuthSrv;
+        auth_server = new AuthSrv(xfrout_client);
         auth_server->setVerbose(verbose_mode);
 
         io_service = new asio_link::IOService(auth_server, port, use_ipv4,

+ 126 - 3
src/bin/auth/tests/auth_srv_unittest.cc

@@ -14,6 +14,8 @@
 
 // $Id$
 
+#include <config.h>
+
 #include <gtest/gtest.h>
 
 #include <dns/buffer.h>
@@ -25,6 +27,8 @@
 
 #include <cc/data.h>
 
+#include <xfr/xfrout_client.h>
+
 #include <auth/auth_srv.h>
 #include <auth/asio_link.h>
 
@@ -34,6 +38,7 @@ using isc::UnitTestUtil;
 using namespace std;
 using namespace isc::dns;
 using namespace isc::data;
+using namespace isc::xfr;
 using namespace asio_link;
 
 namespace {
@@ -45,8 +50,30 @@ const char* BADCONFIG_TESTDB =
     "{ \"database_file\": \"" TEST_DATA_DIR "/nodir/notexist\"}";
 
 class AuthSrvTest : public ::testing::Test {
+private:
+    class MockXfroutClient : public AbstractXfroutClient {
+    public:
+        MockXfroutClient() :
+            is_connected_(false), connect_ok_(true), send_ok_(true),
+            disconnect_ok_(true)
+        {}
+        virtual void connect();
+        virtual void disconnect();
+        virtual int sendXfroutRequestInfo(int tcp_sock, const void* msg_data,
+                                          uint16_t msg_len);
+        bool isConnected() const { return (is_connected_); }
+        void disableConnect() { connect_ok_ = false; }
+        void disableDisconnect() { disconnect_ok_ = false; }
+        void enableDisconnect() { disconnect_ok_ = true; }
+        void disableSend() { send_ok_ = false; }
+    private:
+        bool is_connected_;
+        bool connect_ok_;
+        bool send_ok_;
+        bool disconnect_ok_;
+    };
 protected:
-    AuthSrvTest() : request_message(Message::RENDER),
+    AuthSrvTest() : server(xfrout), request_message(Message::RENDER),
                     parse_message(Message::PARSE), default_qid(0x1035),
                     opcode(Opcode(Opcode::QUERY())), qname("www.example.com"),
                     qclass(RRClass::IN()), qtype(RRType::A()),
@@ -58,6 +85,7 @@ protected:
         delete io_message;
         delete endpoint;
     }
+    MockXfroutClient xfrout;
     AuthSrv server;
     Message request_message;
     Message parse_message;
@@ -80,6 +108,35 @@ protected:
                        int protocol);
 };
 
+void
+AuthSrvTest::MockXfroutClient::connect() {
+    if (!connect_ok_) {
+        isc_throw(XfroutError, "xfrout connection disabled for test");
+    }
+    is_connected_ = true;
+}
+
+void
+AuthSrvTest::MockXfroutClient::disconnect() {
+    if (!disconnect_ok_) {
+        isc_throw(XfroutError,
+                  "closing xfrout connection is disabled for test");
+    }
+    is_connected_ = false;
+}
+
+int
+AuthSrvTest::MockXfroutClient::sendXfroutRequestInfo(
+    const int tcp_sock UNUSED_PARAM,
+    const void* msg_data UNUSED_PARAM,
+    const uint16_t msg_len UNUSED_PARAM)
+{
+    if (!send_ok_) {
+        isc_throw(XfroutError, "xfrout connection send for test");
+    }
+    return (0);
+}
+
 // These are flags to indicate whether the corresponding flag bit of the
 // DNS header is to be set in the test cases.  (Note that the flag values
 // is irrelevant to their wire-format values)
@@ -102,7 +159,9 @@ AuthSrvTest::createDataFromFile(const char* const datafile,
     endpoint = IOEndpoint::create(protocol, IOAddress("192.0.2.1"), 5300);
     UnitTestUtil::readWireData(datafile, data);
     io_message = new IOMessage(&data[0], data.size(),
-                               IOSocket::getDummyUDPSocket(), *endpoint);
+                               protocol == IPPROTO_UDP ?
+                               IOSocket::getDummyUDPSocket() :
+                               IOSocket::getDummyTCPSocket(), *endpoint);
 }
 
 void
@@ -110,6 +169,7 @@ AuthSrvTest::createRequest(const Opcode& opcode, const Name& request_name,
                            const RRClass& rrclass, const RRType& rrtype,
                            const int protocol = IPPROTO_UDP)
 {
+    request_message.clear(Message::RENDER);
     request_message.setOpcode(opcode);
     request_message.setQid(default_qid);
     request_message.addQuestion(Question(request_name, rrclass, rrtype));
@@ -119,7 +179,9 @@ AuthSrvTest::createRequest(const Opcode& opcode, const Name& request_name,
     endpoint = IOEndpoint::create(protocol, IOAddress("192.0.2.1"), 5300);
     io_message = new IOMessage(request_renderer.getData(),
                                request_renderer.getLength(),
-                               IOSocket::getDummyUDPSocket(), *endpoint);
+                               protocol == IPPROTO_UDP ?
+                               IOSocket::getDummyUDPSocket() :
+                               IOSocket::getDummyTCPSocket(), *endpoint);
 }
 
 void
@@ -276,6 +338,67 @@ TEST_F(AuthSrvTest, AXFROverUDP) {
                 QR_FLAG, 1, 0, 0, 0);
 }
 
+TEST_F(AuthSrvTest, AXFRSuccess) {
+    EXPECT_FALSE(xfrout.isConnected());
+    createRequest(opcode, Name("example.com"), RRClass::IN(), RRType::AXFR(),
+                  IPPROTO_TCP);
+    // On success, the AXFR query has been passed to a separate process,
+    // so we shouldn't have to respond.
+    EXPECT_EQ(false, server.processMessage(*io_message, parse_message,
+                                           response_renderer));
+    EXPECT_TRUE(xfrout.isConnected());
+}
+
+TEST_F(AuthSrvTest, AXFRConnectFail) {
+    EXPECT_FALSE(xfrout.isConnected()); // check prerequisite
+    xfrout.disableConnect();
+    createRequest(opcode, Name("example.com"), RRClass::IN(), RRType::AXFR(),
+                  IPPROTO_TCP);
+    EXPECT_TRUE(server.processMessage(*io_message, parse_message,
+                                      response_renderer));
+    headerCheck(parse_message, default_qid, Rcode::SERVFAIL(),
+                opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
+    EXPECT_FALSE(xfrout.isConnected());
+}
+
+TEST_F(AuthSrvTest, AXFRSendFail) {
+    // first send a valid query, making the connection with the xfr process
+    // open.
+    createRequest(opcode, Name("example.com"), RRClass::IN(), RRType::AXFR(),
+                  IPPROTO_TCP);
+    server.processMessage(*io_message, parse_message, response_renderer);
+    EXPECT_TRUE(xfrout.isConnected());
+
+    xfrout.disableSend();
+    parse_message.clear(Message::PARSE);
+    response_renderer.clear();
+    createRequest(opcode, Name("example.com"), RRClass::IN(), RRType::AXFR(),
+                  IPPROTO_TCP);
+    EXPECT_TRUE(server.processMessage(*io_message, parse_message,
+                                      response_renderer));
+    headerCheck(parse_message, default_qid, Rcode::SERVFAIL(),
+                opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
+
+    // The connection should have been closed due to the send failure.
+    EXPECT_FALSE(xfrout.isConnected());
+}
+
+TEST_F(AuthSrvTest, AXFRDisconnectFail) {
+    // In our usage disconnect() shouldn't fail.  So we'll see the exception
+    // should it be thrown.
+    xfrout.disableSend();
+    xfrout.disableDisconnect();
+    createRequest(opcode, Name("example.com"), RRClass::IN(), RRType::AXFR(),
+                  IPPROTO_TCP);
+    EXPECT_THROW(server.processMessage(*io_message, parse_message,
+                                       response_renderer),
+                 XfroutError);
+    EXPECT_TRUE(xfrout.isConnected());
+    // XXX: we need to re-enable disconnect.  otherwise an exception would be
+    // thrown via the destructor of the server.
+    xfrout.enableDisconnect();
+}
+
 TEST_F(AuthSrvTest, notifyInTest) {
     createRequest(Opcode::NOTIFY(), Name("example.com"), RRClass::IN(),
                   RRType::SOA());

+ 4 - 6
src/lib/xfr/xfrout_client.cc

@@ -54,9 +54,8 @@ void
 XfroutClient::connect() {
     try {
         impl_->socket_.connect(stream_protocol::endpoint(impl_->file_path_));
-    } catch (const asio::system_error &) {
-        isc_throw(XfroutError, 
-                "socket connect failed");
+    } catch (const asio::system_error& ex) {
+        isc_throw(XfroutError, "socket connect failed: " << ex.what());
     }
 }
 
@@ -64,9 +63,8 @@ void
 XfroutClient::disconnect() {
     try {
         impl_->socket_.close();
-    } catch (const asio::system_error &) {
-        isc_throw(XfroutError,
-                "close socket failed");
+    } catch (const asio::system_error& ex) {
+        isc_throw(XfroutError, "close socket failed: " << ex.what());
     }
 }
 

+ 32 - 5
src/lib/xfr/xfrout_client.h

@@ -34,7 +34,34 @@ public:
         isc::Exception(file, line, what) {}
 };
 
-class XfroutClient {
+/// \brief The AbstractXfroutClient class is an abstract base class that
+/// defines the interfaces of XfroutClient.
+///
+/// The intended primary usage of abstraction is to allow tests for the
+/// user class of XfroutClient without requiring actual communication.
+class AbstractXfroutClient {
+    ///
+    /// \name Constructors, Assignment Operator and Destructor.
+    ///
+    /// Note: The copy constructor and the assignment operator are
+    /// intentionally defined as private to make it explicit that this is a
+    /// pure base class.
+    //@{
+private:
+    AbstractXfroutClient(const AbstractXfroutClient& source);
+    AbstractXfroutClient& operator=(const AbstractXfroutClient& source);
+protected:
+    AbstractXfroutClient() {}
+public:
+    virtual ~AbstractXfroutClient() {}
+    //@}
+    virtual void connect() = 0;
+    virtual void disconnect() = 0;
+    virtual int sendXfroutRequestInfo(int tcp_sock, const void* msg_data,
+                                      uint16_t msg_len) = 0;
+};
+
+class XfroutClient : public AbstractXfroutClient {
 public:
     XfroutClient(const std::string& file);
     ~XfroutClient();
@@ -43,10 +70,10 @@ private:
     XfroutClient(const XfroutClient& source);
     XfroutClient& operator=(const XfroutClient& source);
 public:
-    void connect();
-    void disconnect();
-    int sendXfroutRequestInfo(int tcp_sock, const void* msg_data,
-                              uint16_t msg_len);
+    virtual void connect();
+    virtual void disconnect();
+    virtual int sendXfroutRequestInfo(int tcp_sock, const void* msg_data,
+                                      uint16_t msg_len);
 private:
     XfroutClientImpl* impl_;
 };