Browse Source

[2977] Implemented tests covering concurrent DNSUpdate send.

Marcin Siodelski 11 years ago
parent
commit
326b3dc5b7
2 changed files with 104 additions and 42 deletions
  1. 4 0
      src/bin/d2/dns_client.h
  2. 100 42
      src/bin/d2/tests/dns_client_unittests.cc

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

@@ -25,6 +25,10 @@
 namespace isc {
 namespace d2 {
 
+class DNSClient;
+typedef boost::shared_ptr<DNSClient> DNSClientPtr;
+
+/// DNSClient class implementation.
 class DNSClientImpl;
 
 /// @brief The @c DNSClient class handles communication with the DNS server.

+ 100 - 42
src/bin/d2/tests/dns_client_unittests.cc

@@ -56,6 +56,9 @@ public:
     D2UpdateMessagePtr response_;
     DNSClient::Status status_;
     uint8_t receive_buffer_[MAX_SIZE];
+    DNSClientPtr dns_client_;
+    bool corrupt_response_;
+    bool expect_response_;
 
     // @brief Constructor.
     //
@@ -67,9 +70,12 @@ public:
     // become messy if such errors were logged.
     DNSClientTest()
         : service_(),
-          status_(DNSClient::SUCCESS) {
+          status_(DNSClient::SUCCESS),
+          corrupt_response_(false),
+          expect_response_(true) {
         asiodns::logger.setSeverity(log::INFO);
         response_.reset(new D2UpdateMessage(D2UpdateMessage::INBOUND));
+        dns_client_.reset(new DNSClient(response_, this));
     }
 
     // @brief Destructor.
@@ -79,7 +85,7 @@ public:
         asiodns::logger.setSeverity(log::DEBUG);
     };
 
-    // @brief Exchange completion calback.
+    // @brief Exchange completion callback.
     //
     // This callback is called when the exchange with the DNS server is
     // complete or an error occured. This includes the occurence of a timeout.
@@ -88,6 +94,29 @@ public:
     virtual void operator()(DNSClient::Status status) {
         status_ = status;
         service_.stop();
+
+        if (expect_response_) {
+            if (!corrupt_response_) {
+                // We should have received a response.
+                EXPECT_EQ(DNSClient::SUCCESS, status_);
+
+                ASSERT_TRUE(response_);
+                EXPECT_EQ(D2UpdateMessage::RESPONSE, response_->getQRFlag());
+                ASSERT_EQ(1, response_->getRRCount(D2UpdateMessage::SECTION_ZONE));
+                D2ZonePtr zone = response_->getZone();
+                ASSERT_TRUE(zone);
+                EXPECT_EQ("example.com.", zone->getName().toText());
+                EXPECT_EQ(RRClass::IN().getCode(), zone->getClass().getCode());
+
+            } else {
+                EXPECT_EQ(DNSClient::INVALID_RESPONSE, status_);
+
+            }
+        // If we don't expect a response, the status should indicate a timeout.
+        } else {
+            EXPECT_EQ(DNSClient::TIMEOUT, status_);
+
+        }
     }
 
     // @brief Handler invoked when test request is received.
@@ -102,22 +131,27 @@ public:
     // @param remote A pointer to an object which specifies the host (address
     // and port) from which a request has come.
     // @param receive_length A length (in bytes) of the received data.
+    // @param corrupt_response A bool value which indicates that the server's
+    // response should be invalid (true) or valid (false)
     void udpReceiveHandler(udp::socket* socket, udp::endpoint* remote,
-                           size_t receive_length) {
+                           size_t receive_length, const bool corrupt_response) {
         // The easiest way to create a response message is to copy the entire
         // request.
         OutputBuffer response_buf(receive_length);
         response_buf.writeData(receive_buffer_, receive_length);
-
-        // What must be different between a request and response is the QR
-        // flag bit. This bit differentiates both types of messages. We have
-        // to set this bit to 1. Note that the 3rd byte of the message header
-        // comprises this bit in the front followed by the message code and
-        // reserved zeros. Therefore, this byte comprises:
-        //             10101000,
-        // where a leading bit is a QR flag. The hexadecimal value is 0xA8.
-        // Write it at message offset 2.
-        response_buf.writeUint8At(0xA8, 2);
+        // If a response is to be valid, we have to modify it slightly. If not,
+        // we leave it as is.
+        if (!corrupt_response) {
+            // For a valid response the QR bit must be set. This bit
+            // differentiates both types of messages. Note that the 3rd byte of
+            // the message header comprises this bit in the front followed by
+            // the message code and reserved zeros. Therefore, this byte
+            // has the following value:
+            //             10101000,
+            // where a leading bit is a QR flag. The hexadecimal value is 0xA8.
+            // Write it at message offset 2.
+            response_buf.writeUint8At(0xA8, 2);
+        }
         // A response message is now ready to send. Send it!
         socket->send_to(asio::buffer(response_buf.getData(),
                                      response_buf.getLength()),
@@ -138,6 +172,9 @@ public:
     // 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() {
+        // We expect no response from a server.
+        expect_response_ = false;
+
         // 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.
@@ -145,12 +182,6 @@ public:
         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.
-        ASSERT_NO_THROW(dns_client.reset(new DNSClient(response_, this)));
-
         // Set the response wait time to 0 so as our test is not hanging. This
         // should cause instant timeout.
         const int timeout = 0;
@@ -158,31 +189,26 @@ public:
         // 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),
+        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 status_ with the TIMEOUT error code.
-        EXPECT_EQ(DNSClient::TIMEOUT, status_);
     }
 
     // This test verifies that DNSClient can send DNS Update and receive a
     // corresponding response from a server.
-    void runSendReceiveTest() {
+    void runSendReceiveTest(const bool corrupt_response,
+                            const bool two_sends = false) {
+        corrupt_response_ = corrupt_response;
+
         // Create a request DNS Update message.
         D2UpdateMessage message(D2UpdateMessage::OUTBOUND);
         ASSERT_NO_THROW(message.setRcode(Rcode(Rcode::NOERROR_CODE)));
         ASSERT_NO_THROW(message.setZone(Name("example.com"), RRClass::IN()));
 
-        // Create an instance of the DNSClient. Constructor may throw an
-        // exception, so we guard it with EXPECT_NO_THROW.
-        boost::scoped_ptr<DNSClient> dns_client;
-        EXPECT_NO_THROW(dns_client.reset(new DNSClient(response_, this)));
-
         // In order to perform the full test, when the client sends the request
         // and receives a response from the server, we have to emulate the
         // server's response in the test. A request will be sent via loopback
@@ -213,43 +239,75 @@ public:
                                                    sizeof(receive_buffer_)),
                                       remote,
                                       boost::bind(&DNSClientTest::udpReceiveHandler,
-                                                  this, &udp_socket, &remote, _2));
+                                                  this, &udp_socket, &remote, _2,
+                                                  corrupt_response));
 
         // The socket is now ready to receive the data. Let's post some request
         // message then.
         const int timeout = 5;
-        dns_client->doUpdate(service_, IOAddress(TEST_ADDRESS), TEST_PORT,
+        dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS), TEST_PORT,
                              message, timeout);
 
+        // It is possible to request that two packets are sent concurrently.
+        if (two_sends) {
+            dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS), TEST_PORT,
+                                  message, timeout);
+
+        }
+
         // Kick of the message exchange by actually running the scheduled
         // "send" and "receive" operations.
         service_.run();
 
         udp_socket.close();
 
-        // We should have received a response.
-        EXPECT_EQ(DNSClient::SUCCESS, status_);
-
-        ASSERT_TRUE(response_);
-        EXPECT_EQ(D2UpdateMessage::RESPONSE, response_->getQRFlag());
-        ASSERT_EQ(1, response_->getRRCount(D2UpdateMessage::SECTION_ZONE));
-        D2ZonePtr zone = response_->getZone();
-        ASSERT_TRUE(zone);
-        EXPECT_EQ("example.com.", zone->getName().toText());
-        EXPECT_EQ(RRClass::IN().getCode(), zone->getClass().getCode());
     }
 };
 
+// Verify that the DNSClient object can be created if provided parameters are
+// valid. Constructor should throw exceptions when parameters are invalid.
 TEST_F(DNSClientTest, constructor) {
     runConstructorTest();
 }
 
+// Verify that timeout is reported when no response is received from DNS.
 TEST_F(DNSClientTest, timeout) {
     runSendNoReceiveTest();
 }
 
+// Verify that the DNSClient receives the response from DNS and the received
+// buffer can be decoded as DNS Update Response.
 TEST_F(DNSClientTest, sendReceive) {
-    runSendReceiveTest();
+    // false means that server response is not corrupted.
+    runSendReceiveTest(false);
+}
+
+// Verify that the DNSClient reports an error when the response is received from
+// a DNS and this response is corrupted.
+TEST_F(DNSClientTest, sendReceiveCurrupted) {
+    // true means that server's response is corrupted.
+    runSendReceiveTest(true);
+}
+
+// Verify that it is possible to use the same DNSClient instance to
+// perform the following sequence of message exchanges:
+// 1. send
+// 2. receive
+// 3. send
+// 4. receive
+TEST_F(DNSClientTest, sendReceiveTwice) {
+    runSendReceiveTest(false);
+    runSendReceiveTest(false);
+}
+
+// Verify that it is possible to use the DNSClient instance to perform the
+// following  sequence of message exchanges:
+// 1. send
+// 2. send
+// 3. receive
+// 4. receive
+TEST_F(DNSClientTest, concurrentSendReceive) {
+    runSendReceiveTest(true, true);
 }
 
 } // End of anonymous namespace