From: Razvan Becheriu Date: Tue, 5 Oct 2021 17:51:47 +0000 (+0300) Subject: [#2089] added d2 stats unittests X-Git-Tag: Kea-2.1.0~73 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3b6a8ac153a3d439713abd36e61cddf8a020ca0d;p=thirdparty%2Fkea.git [#2089] added d2 stats unittests --- diff --git a/src/lib/d2srv/tests/dns_client_unittests.cc b/src/lib/d2srv/tests/dns_client_unittests.cc index a5e39c15d7..36ba16e49a 100644 --- a/src/lib/d2srv/tests/dns_client_unittests.cc +++ b/src/lib/d2srv/tests/dns_client_unittests.cc @@ -58,32 +58,58 @@ const long TEST_TIMEOUT = 5 * 1000; // timeout is hit. This will result in test failure. class DNSClientTest : public virtual D2StatTest, DNSClient::Callback { public: + /// @brief The IOService which handles IO operations. IOService service_; + + /// @brief The UDP socket. + std::unique_ptr socket_; + + /// @brief The UDP socket endpoint. + std::unique_ptr endpoint_; + + /// @brief DNS client response. D2UpdateMessagePtr response_; + + /// @brief The status of the DNS client update callback. DNSClient::Status status_; + + /// @brief The receive buffer. uint8_t receive_buffer_[MAX_SIZE]; + + /// @brief The DNS client performing DNS update. DNSClientPtr dns_client_; + + /// @brief The flag which indicates if the response should be corrupted. bool corrupt_response_; + + /// @brief The flag which indicates if a response is expected. bool expect_response_; + + /// @brief The timeout timer. asiolink::IntervalTimer test_timer_; + + /// @brief The number of received DNS updates. int received_; + + /// @brief The number of expected DNS updates. int expected_; + /// @brief The flag which indicates if the server should continue + /// with receiving DNS updates. + bool go_on_; + /// @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_(), - status_(DNSClient::SUCCESS), - corrupt_response_(false), - expect_response_(true), - test_timer_(service_), - received_(0), expected_(0) { + /// + /// 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_(), socket_(), endpoint_(), + status_(DNSClient::SUCCESS), corrupt_response_(false), + expect_response_(true), test_timer_(service_), + received_(0), expected_(0), go_on_(false) { asiodns::logger.setSeverity(isc::log::INFO); response_.reset(); dns_client_.reset(new DNSClient(response_, this)); @@ -94,22 +120,21 @@ public: } /// @brief Destructor - // - // Sets the asiodns logging level back to DEBUG. + /// + /// Sets the asiodns logging level back to DEBUG. virtual ~DNSClientTest() { asiodns::logger.setSeverity(isc::log::DEBUG); }; /// @brief Exchange completion callback - // - // This callback is called when the exchange with the DNS server is - // complete or an error occurred. This includes the occurrence of a timeout. - // - // @param status A status code returned by DNSClient. + /// + /// This callback is called when the exchange with the DNS server is + /// complete or an error occurred. This includes the occurrence of a timeout. + /// + /// @param status A status code returned by DNSClient. virtual void operator()(DNSClient::Status status) { status_ = status; - if (!expected_ || (expected_ == ++received_)) - { + if (!expected_ || (expected_ == ++received_)) { service_.stop(); } @@ -139,27 +164,27 @@ public: } /// @brief Handler invoked when test timeout is hit - // - // This callback stops all running (hanging) tasks on IO service. + /// + /// This callback stops all running (hanging) tasks on IO service. void testTimeoutHandler() { service_.stop(); FAIL() << "Test timeout hit."; } /// @brief Handler invoked when test request is received - // - // This callback handler is installed when performing async read on a - // socket to emulate reception of the DNS Update request by a server. - // As a result, this handler will send an appropriate DNS Update response - // message back to the address from which the request has come. - // - // @param socket A pointer to a socket used to receive a query and send a - // response. - // @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) + /// + /// This callback handler is installed when performing async read on a + /// socket to emulate reception of the DNS Update request by a server. + /// As a result, this handler will send an appropriate DNS Update response + /// message back to the address from which the request has come. + /// + /// @param socket A pointer to a socket used to receive a query and send a + /// response. + /// @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, const bool corrupt_response) { // The easiest way to create a response message is to copy the entire @@ -183,31 +208,39 @@ public: socket->send_to(boost::asio::buffer(response_buf.getData(), response_buf.getLength()), *remote); + + if (go_on_) { + socket_->async_receive_from(boost::asio::buffer(receive_buffer_, + sizeof(receive_buffer_)), + *endpoint_, + std::bind(&DNSClientTest::udpReceiveHandler, + this, socket_.get(), + endpoint_.get(), ph::_2, + corrupt_response)); + } } /// @brief Request handler for testing clients using TSIG - // - // This callback handler is installed when performing async read on a - // socket to emulate reception of the DNS Update request with TSIG by a - // server. As a result, this handler will send an appropriate DNS Update - // response message back to the address from which the request has come. - // - // @param socket A pointer to a socket used to receive a query and send a - // response. - // @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) - // @param client_key TSIG key the server should use to verify the inbound - // request. If the pointer is NULL, the server will not attempt to - // verify the request. - // @param server_key TSIG key the server should use to sign the outbound - // request. If the pointer is NULL, the server will not sign the outbound - // response. If the pointer is not NULL and not the same value as the - // client_key, the server will use a new context to sign the response then - // the one used to verify it. This allows us to simulate the server - // signing with the wrong key. + /// + /// This callback handler is installed when performing async read on a + /// socket to emulate reception of the DNS Update request with TSIG by a + /// server. As a result, this handler will send an appropriate DNS Update + /// response message back to the address from which the request has come. + /// + /// @param socket A pointer to a socket used to receive a query and send a + /// response. + /// @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 client_key TSIG key the server should use to verify the inbound + /// request. If the pointer is NULL, the server will not attempt to + /// verify the request. + /// @param server_key TSIG key the server should use to sign the outbound + /// request. If the pointer is NULL, the server will not sign the outbound + /// response. If the pointer is not NULL and not the same value as the + /// client_key, the server will use a new context to sign the response then + /// the one used to verify it. This allows us to simulate the server + /// signing with the wrong key. void TSIGReceiveHandler(udp::socket* socket, udp::endpoint* remote, size_t receive_length, D2TsigKeyPtr client_key, @@ -255,14 +288,15 @@ public: response.toWire(renderer, context.get()); // A response message is now ready to send. Send it! - socket->send_to(boost::asio::buffer(renderer.getData(), renderer.getLength()), + socket->send_to(boost::asio::buffer(renderer.getData(), + renderer.getLength()), *remote); } - // 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. + /// @brief This test verifies that when invalid response placeholder object + /// is passed to a constructor which throws the appropriate exception. + /// It also verifies that the constructor will not throw if the supplied + /// callback object is NULL. void runConstructorTest() { EXPECT_NO_THROW(DNSClient(response_, NULL, DNSClient::UDP)); @@ -273,8 +307,8 @@ public: isc::NotImplemented); } - // This test verifies that it accepted timeout values belong to the range of - // <0, DNSClient::getMaxTimeout()>. + /// @brief This test verifies that it accepted timeout values belong to the + /// range of <0, DNSClient::getMaxTimeout()>. void runInvalidTimeoutTest() { expect_response_ = false; @@ -299,9 +333,9 @@ public: isc::BadValue); } - // 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. + /// @brief 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() { // We expect no response from a server. expect_response_ = false; @@ -329,7 +363,7 @@ public: // completion callback will be triggered. The doUpdate function returns // immediately. EXPECT_NO_THROW(dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS), - TEST_PORT, message, timeout)); + TEST_PORT, message, timeout)); // This starts the execution of tasks posted to IOService. run() blocks // until stop() is called in the completion callback function. @@ -337,10 +371,11 @@ public: } - // This test verifies that DNSClient can send DNS Update and receive a - // corresponding response from a server. + /// @brief This test verifies that DNSClient can send DNS Update and receive + /// a corresponding response from a server. void runSendReceiveTest(const bool corrupt_response, const bool two_sends) { + go_on_ = two_sends; corrupt_response_ = corrupt_response; // Create a request DNS Update message. @@ -357,10 +392,11 @@ public: // responses. The reuse address option is set so as both sockets can // use the same address. This new socket is bound to the test address // and port, where requests will be sent. - udp::socket udp_socket(service_.get_io_service(), boost::asio::ip::udp::v4()); - udp_socket.set_option(socket_base::reuse_address(true)); - udp_socket.bind(udp::endpoint(address::from_string(TEST_ADDRESS), - TEST_PORT)); + socket_.reset(new udp::socket(service_.get_io_service(), + boost::asio::ip::udp::v4())); + socket_->set_option(socket_base::reuse_address(true)); + socket_->bind(udp::endpoint(address::from_string(TEST_ADDRESS), + TEST_PORT)); // Once socket is created, we can post an IO request to receive some // packet from this socket. This is asynchronous operation and // nothing is received until another IO request to send a query is @@ -373,13 +409,14 @@ public: // Callback function will send a response to this address and port. // The last parameter holds a length of the received request. It is // required to construct a response. - udp::endpoint remote; - udp_socket.async_receive_from(boost::asio::buffer(receive_buffer_, - sizeof(receive_buffer_)), - remote, - std::bind(&DNSClientTest::udpReceiveHandler, - this, &udp_socket, &remote, ph::_2, - corrupt_response)); + endpoint_.reset(new udp::endpoint()); + socket_->async_receive_from(boost::asio::buffer(receive_buffer_, + sizeof(receive_buffer_)), + *endpoint_, + std::bind(&DNSClientTest::udpReceiveHandler, + this, socket_.get(), + endpoint_.get(), ph::_2, + corrupt_response)); // The socket is now ready to receive the data. Let's post some request // message then. Set timeout to some reasonable value to make sure that @@ -402,7 +439,7 @@ public: // "send" and "receive" operations. service_.run(); - udp_socket.close(); + socket_->close(); // Since the callback, operator(), calls stop() on the io_service, // we must reset it in order for subsequent calls to run() or @@ -410,14 +447,13 @@ public: service_.get_io_service().reset(); } - // Performs a single request-response exchange with or without TSIG - // - // @param client_key TSIG passed to dns_client and also used by the - // "server" to verify the request. - // request. - // @param server_key TSIG key the "server" should use to sign the response. - // If this is NULL, then client_key is used. - // @param should_pass indicates if the test should pass. + /// @brief Performs a single request-response exchange with or without TSIG. + /// + /// @param client_key TSIG passed to dns_client and also used by the + /// "server" to verify the request. + /// @param server_key TSIG key the "server" should use to sign the response. + /// If this is NULL, then client_key is used. + /// @param should_pass indicates if the test should pass. void runTSIGTest(D2TsigKeyPtr client_key, D2TsigKeyPtr server_key, bool should_pass = true) { // Tell operator() method if we expect an invalid response. @@ -468,6 +504,15 @@ public: // valid. Constructor should throw exceptions when parameters are invalid. TEST_F(DNSClientTest, constructor) { runConstructorTest(); + StatMap stats_upd = { + { "update-sent", 0}, + { "update-signed", 0}, + { "update-unsigned", 0}, + { "update-success", 0}, + { "update-timeout", 0}, + { "update-error", 0} + }; + checkStats(stats_upd); } // This test verifies that the maximal allowed timeout value is maximal int @@ -479,6 +524,15 @@ TEST_F(DNSClientTest, getMaxTimeout) { // Verify that timeout is reported when no response is received from DNS. TEST_F(DNSClientTest, timeout) { runSendNoReceiveTest(); + StatMap stats_upd = { + { "update-sent", 1}, + { "update-signed", 0}, + { "update-unsigned", 1}, + { "update-success", 0}, + { "update-timeout", 1}, + { "update-error", 0} + }; + checkStats(stats_upd); } // Verify that exception is thrown when invalid (too high) timeout value is @@ -553,6 +607,15 @@ TEST_F(DNSClientTest, runTSIGTest) { TEST_F(DNSClientTest, sendReceive) { // false means that server response is not corrupted. runSendReceiveTest(false, false); + StatMap stats_upd = { + { "update-sent", 1}, + { "update-signed", 0}, + { "update-unsigned", 1}, + { "update-success", 1}, + { "update-timeout", 0}, + { "update-error", 0} + }; + checkStats(stats_upd); } // Verify that the DNSClient reports an error when the response is received from @@ -560,6 +623,15 @@ TEST_F(DNSClientTest, sendReceive) { TEST_F(DNSClientTest, sendReceiveCorrupted) { // true means that server's response is corrupted. runSendReceiveTest(true, false); + StatMap stats_upd = { + { "update-sent", 1}, + { "update-signed", 0}, + { "update-unsigned", 1}, + { "update-success", 0}, + { "update-timeout", 0}, + { "update-error", 1} + }; + checkStats(stats_upd); } // Verify that it is possible to use the same DNSClient instance to @@ -572,6 +644,15 @@ TEST_F(DNSClientTest, sendReceiveTwice) { runSendReceiveTest(false, false); runSendReceiveTest(false, false); EXPECT_EQ(2, received_); + StatMap stats_upd = { + { "update-sent", 2}, + { "update-signed", 0}, + { "update-unsigned", 2}, + { "update-success", 2}, + { "update-timeout", 0}, + { "update-error", 0} + }; + checkStats(stats_upd); } // Verify that it is possible to use the DNSClient instance to perform the @@ -580,14 +661,17 @@ TEST_F(DNSClientTest, sendReceiveTwice) { // 2. send // 3. receive // 4. receive -// @todo THIS Test does not function. The method runSendReceive only -// schedules one "server" receive. In other words only one request is -// listened for and then received. Once it is received, the operator() -// method calls stop() on the io_service, which causes the second receive -// to be cancelled. It is also unclear, what the asio layer does with a -// second receive on the same socket. -TEST_F(DNSClientTest, DISABLED_concurrentSendReceive) { +TEST_F(DNSClientTest, concurrentSendReceive) { runSendReceiveTest(false, true); + StatMap stats_upd = { + { "update-sent", 2}, + { "update-signed", 0}, + { "update-unsigned", 2}, + { "update-success", 2}, + { "update-timeout", 0}, + { "update-error", 0} + }; + checkStats(stats_upd); } } // End of anonymous namespace