Browse Source

[1959] Created TestControl class, unit tests and main function.

Marcin Siodelski 12 years ago
parent
commit
625cc0a45d

+ 16 - 14
tests/tools/perfdhcp/Makefile.am

@@ -18,23 +18,25 @@ if USE_STATIC_LINK
 AM_LDFLAGS += -static
 endif
 
-lib_LTLIBRARIES = libperfdhcp++.la
-libperfdhcp___la_SOURCES = command_options.cc command_options.h
-libperfdhcp___la_SOURCES += localized_option.h
-libperfdhcp___la_SOURCES += perf_pkt6.cc perf_pkt6.h
-libperfdhcp___la_SOURCES += perf_pkt4.cc perf_pkt4.h
-libperfdhcp___la_SOURCES += pkt_transform.cc pkt_transform.h
-
-libperfdhcp___la_CXXFLAGS = $(AM_CXXFLAGS)
+pkglibexec_PROGRAMS = perfdhcp2
+perfdhcp2_SOURCES = main.cc
+perfdhcp2_SOURCES += command_options.cc command_options.h
+perfdhcp2_SOURCES += localized_option.h
+perfdhcp2_SOURCES += perf_pkt6.cc perf_pkt6.h
+perfdhcp2_SOURCES += perf_pkt4.cc perf_pkt4.h
+perfdhcp2_SOURCES += pkt_transform.cc pkt_transform.h
+perfdhcp2_SOURCES += test_control.cc test_control.h
+
+perfdhcp2_CXXFLAGS = $(AM_CXXFLAGS)
 if USE_CLANGPP
 # Disable unused parameter warning caused by some of the
 # Boost headers when compiling with clang.
-libperfdhcp___la_CXXFLAGS += -Wno-unused-parameter
+perfdhcp2_CXXFLAGS += -Wno-unused-parameter
 endif
 
-libperfdhcp___la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
-libperfdhcp___la_LIBADD += $(top_builddir)/src/lib/dhcp/libdhcp++.la
-libperfdhcp___la_LIBADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
+perfdhcp2_LDADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
+perfdhcp2_LDADD += $(top_builddir)/src/lib/dhcp/libdhcp++.la
+perfdhcp2_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
 
-pkglibexec_PROGRAMS  = perfdhcp
-perfdhcp_SOURCES  = perfdhcp.c
+#pkglibexec_PROGRAMS  = perfdhcp
+#perfdhcp_SOURCES  = perfdhcp.c

+ 46 - 0
tests/tools/perfdhcp/main.cc

@@ -0,0 +1,46 @@
+// Copyright (C) 2012  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 <iostream>
+#include <stdint.h>
+
+#include <config.h>
+#include <exceptions/exceptions.h>
+
+#include "test_control.h"
+#include "command_options.h"
+
+using namespace isc::perfdhcp;
+
+int
+main(int argc, char* argv[]) {
+    CommandOptions& command_options = CommandOptions::instance();
+    try {
+        command_options.parse(argc, argv);
+    } catch(isc::Exception& e) {
+        std::cout << "Error parsing command line options: "
+                  << e.what() << std::endl;
+        command_options.usage();
+        return(1);
+    }
+    try{
+        TestControl& test_control = TestControl::instance();
+        test_control.run();
+    } catch (isc::Exception& e) {
+        std::cout << "Error starting perfdhcp: " << e.what() << std::endl;
+        return(1);
+    }
+    return(0);
+}
+

+ 239 - 0
tests/tools/perfdhcp/test_control.cc

@@ -0,0 +1,239 @@
+// Copyright (C) 2012 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 <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <unistd.h>
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+#include <dhcp/libdhcp++.h>
+#include <dhcp/dhcp4.h>
+#include <exceptions/exceptions.h>
+#include "test_control.h"
+#include "command_options.h"
+
+using namespace std;
+using namespace boost;
+using namespace boost::posix_time;
+using namespace isc;
+using namespace isc::dhcp;
+
+namespace isc {
+namespace perfdhcp {
+
+TestControl&
+TestControl::instance() {
+    static TestControl test_control;
+    return (test_control);
+}
+
+TestControl::TestControl() :
+    send_due_(microsec_clock::universal_time()),
+    last_sent_(send_due_) {
+}
+
+bool
+TestControl::checkExitConditions() const {
+    CommandOptions& options = CommandOptions::instance();
+    if ((options.getNumRequests().size() > 0) &&
+        (sent_packets_0_ >= options.getNumRequests()[0])) {
+        return(true);
+    } else if ((options.getNumRequests().size() == 2) &&
+               (sent_packets_1_ >= options.getNumRequests()[1])) {
+        return(true);
+    }
+    return(false);
+}
+
+Pkt4*
+TestControl::createDiscoverPkt4() {
+    const uint32_t transid = static_cast<uint32_t>(random());
+    Pkt4* pkt4 = new Pkt4(DHCPDISCOVER, transid);
+
+    OptionBuffer opt_request_list_buf();
+    // createRequestListBuffer4(opt_request_list_buf);
+    return NULL;
+}
+
+OptionPtr
+TestControl::factoryRequestList4(Option::Universe u,
+                                 uint16_t type,
+                                 const OptionBuffer& buf)
+{
+    const uint8_t buf_array[] = {
+        DHO_SUBNET_MASK,
+        DHO_BROADCAST_ADDRESS,
+        DHO_TIME_OFFSET,
+        DHO_ROUTERS,
+        DHO_DOMAIN_NAME,
+        DHO_DOMAIN_NAME_SERVERS,
+        DHO_HOST_NAME
+    };
+
+    OptionBuffer buf_with_options(buf_array, buf_array + sizeof(buf_array));
+    Option* opt = new Option(u, type, buf);
+    opt->setData(buf_with_options.begin(),
+                 buf_with_options.end());
+    return OptionPtr(opt);
+}
+
+uint64_t
+TestControl::getNextExchangesNum() const {
+    CommandOptions& options = CommandOptions::instance();
+    // Reset number of exchanges.
+    uint64_t due_exchanges = 0;
+    // Get current time.
+    ptime now(microsec_clock::universal_time());
+    // The due time indicates when we should start sening next chunk
+    // of packets. If it is already due time, we should calculate
+    // how many packets to send.
+    if (now >= send_due_) {
+        // If rate is specified from the command line we have to
+        // synchornize with it.
+        if (options.getRate() != 0) {
+            time_period period(send_due_, now);
+            // Null condition should not occur because we
+            // have checked it in the first if statement but
+            // let's keep this check just in case.
+            if (period.is_null()) {
+                return (0);
+            }
+            time_duration duration = period.length();
+            // due_factor indicates the number of seconds that
+            // sending next chunk of packets will take.
+            double due_factor = duration.fractional_seconds() /
+                time_duration::ticks_per_second();
+            due_factor += duration.total_seconds();
+            // Multiplying due_factor by expected rate gives the number
+            // of exchanges to be initiated.
+            due_exchanges = static_cast<uint64_t>(due_factor * options.getRate());
+            // We want to make sure that at least one packet goes out.
+            due_exchanges += 1;
+            // We should not exceed aggressivity as it could have been
+            // restricted from command line.
+            if (due_exchanges > options.getAggressivity()) {
+                due_exchanges = options.getAggressivity();
+            }
+        } else {
+            // Rate is not specified so we rely on aggressivity
+            // which is the number of packets to be sent in
+            // one chunk.
+            due_exchanges = options.getAggressivity();
+        }
+        return (due_exchanges);
+    }
+    return (0);
+}
+
+void
+TestControl::registerOptionFactories4() const {
+    static bool factories_registered = false;
+    if (!factories_registered) {
+        LibDHCP::OptionFactoryRegister(Option::V4,
+                                       DHO_DHCP_PARAMETER_REQUEST_LIST,
+                                       &TestControl::factoryRequestList4);
+    }
+    factories_registered = true;
+}
+
+void
+TestControl::registerOptionFactories6() const {
+    static bool factories_registered = false;
+    if (!factories_registered) {
+    }
+    factories_registered = true;
+}
+
+void
+TestControl::registerOptionFactories() const {
+    CommandOptions& options = CommandOptions::instance();
+    switch(options.getIpVersion()) {
+    case 4:
+        registerOptionFactories4();
+        break;
+    case 6:
+        registerOptionFactories6();
+        break;
+    default:
+        isc_throw(InvalidOperation, "command line options have to be parsed "
+                  "before DHCP option factories can be registered");
+    }
+}
+
+void
+TestControl::run() {
+    sent_packets_0_ = 0;
+    sent_packets_1_ = 0;
+    CommandOptions& options = CommandOptions::instance();
+    // Ip version is not set ONLY in case the command options
+    // where not parsed. This surely means that parse() function
+    // was not called prior to starting the test. This is fatal
+    // error.
+    if (options.getIpVersion() == 0) {
+        isc_throw(InvalidOperation, "command options must be parsed before running " 
+                  "a test");
+    }
+    registerOptionFactories();
+    uint64_t packets_sent = 0;
+    for (;;) {
+        updateSendDue();
+        if (checkExitConditions()) {
+            break;
+        }
+        uint64_t packets_due = getNextExchangesNum();
+        for (uint64_t i = packets_due; i > 0; --i) {
+            startExchange();
+            ++packets_sent;
+            cout << "Packets sent " << packets_sent << endl;
+        }
+    }
+
+}
+
+void
+TestControl::startExchange() {
+    ++sent_packets_0_;
+    last_sent_ = microsec_clock::universal_time();
+}
+
+void
+TestControl::updateSendDue() {
+    // If default constructor was called, this should not happen but
+    // if somebody has changed default constructor it is better to
+    // keep this check.
+    if (last_sent_.is_not_a_date_time()) {
+        isc_throw(Unexpected, "time of last sent packet not initialized");
+    }
+    // Get the expected exchange rate.
+    CommandOptions& options = CommandOptions::instance();
+    int rate = options.getRate();
+    // If rate was not specified we will wait just one clock tick to
+    // send next packet. This simulates best effort conditions.
+    long duration = 1;
+    if (rate != 0) {
+        // We use number of ticks instead of nanoseconds because
+        // nanosecond resolution may not be available on some
+        // machines. Number of ticks guarantees the highest possible
+        // timer resolution.
+        duration = time_duration::ticks_per_second() / rate;
+    }
+    // Calculate due time to initate next chunk of exchanges.
+    send_due_ = last_sent_ + time_duration(0, 0, 0, duration);
+}
+
+
+} // namespace perfdhcp
+} // namespace isc

+ 117 - 0
tests/tools/perfdhcp/test_control.h

@@ -0,0 +1,117 @@
+// Copyright (C) 2012 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 __TEST_CONTROL_H
+#define __TEST_CONTROL_H
+
+#include <string>
+#include <vector>
+
+#include <boost/noncopyable.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt6.h>
+
+namespace isc {
+namespace perfdhcp {
+
+/// \brief Test Control class.
+///
+/// This class is responsible for running whole perfdhcp test.
+///
+class TestControl : public boost::noncopyable {
+public:
+    /// TestControl is a singleton class. This method returns reference
+    /// to its sole instance.
+    ///
+    /// \return the only existing instance of test control
+    static TestControl& instance();
+
+    /// Run performance test.
+    ///
+    /// Method runs whole performance test. Command line options must
+    /// be parsed prior to running this function. Othewise function will
+    /// throw exception.
+    ///
+    /// \throw isc::InvalidOperation if command line options are not parsed.
+    /// \throw isc::Unexpected if internal Test Controler error occured.
+    void run();
+
+private:
+
+    /// \brief Private default constructor.
+    ///
+    /// Default constructor is private as the object can be created
+    /// only via \ref instance method.
+    TestControl();
+
+    /// \brief Check if test exit condtitions fulfiled.
+    ///
+    /// Method checks if test exit conditions are fulfiled.
+    /// Exit conditions are checked periodically from the
+    /// main loop. Program should break the main loop when
+    /// this method returns true. It is calling function
+    /// responsibility to break main loop gracefully and
+    /// cleanup after test execution.
+    ///
+    /// \return true if any of the exit conditions is fulfiled.
+    bool checkExitConditions() const;
+
+    dhcp::Pkt4* createDiscoverPkt4();
+
+    static dhcp::OptionPtr factoryRequestList4(dhcp::Option::Universe u,
+                                               uint16_t type,
+                                               const dhcp::OptionBuffer& buf);
+
+    /// \brief Returns number of exchanges to be started.
+    ///
+    /// Method returns number of new exchanges to be started as soon
+    /// as possible to satisfy expected rate. Calculation used here
+    /// is based on current time, due time calculated with
+    /// \ref updateSendTime function and expected rate.
+    ///
+    /// \return number of exchanges to be started immediatelly.
+    uint64_t getNextExchangesNum() const;
+
+    void registerOptionFactories4() const;
+
+    void registerOptionFactories6() const;
+
+    void registerOptionFactories() const;
+
+    /// \brief Start new exchange of DHCP messages.
+    ///
+    void startExchange();
+
+    /// \brief Update due time to initiate next chunk of exchanges.
+    ///
+    /// Method updates due time to initiate next chunk of exchanges.
+    /// Function takes current time, last sent packet's time and
+    /// expected rate in its calculations.
+    void updateSendDue();
+
+    boost::posix_time::ptime send_due_;    ///< Due time to initiate next chunk
+                                           ///< of exchanges.
+    boost::posix_time::ptime last_sent_;   ///< Indicates when the last exchange
+                                           /// was initiated.
+
+    uint64_t sent_packets_0_;
+    uint64_t sent_packets_1_;
+};
+
+} // namespace perfdhcp
+} // namespace isc
+
+#endif // __COMMAND_OPTIONS_H

+ 2 - 0
tests/tools/perfdhcp/tests/Makefile.am

@@ -21,10 +21,12 @@ run_unittests_SOURCES += command_options_unittest.cc
 run_unittests_SOURCES += perf_pkt6_unittest.cc
 run_unittests_SOURCES += perf_pkt4_unittest.cc
 run_unittests_SOURCES += localized_option_unittest.cc
+run_unittests_SOURCES += test_control_unittest.cc
 run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/command_options.cc
 run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/pkt_transform.cc
 run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/perf_pkt6.cc
 run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/perf_pkt4.cc
+run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/test_control.cc
 
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_LDFLAGS  = $(AM_LDFLAGS)  $(GTEST_LDFLAGS)

+ 67 - 0
tests/tools/perfdhcp/tests/test_control_unittest.cc

@@ -0,0 +1,67 @@
+// Copyright (C) 2012 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 <cstddef>
+#include <stdint.h>
+#include <string>
+#include <gtest/gtest.h>
+
+#include "../test_control.h"
+#include "../command_options.h"
+#include "exceptions/exceptions.h"
+
+using namespace std;
+using namespace isc;
+using namespace isc::perfdhcp;
+
+/// \brief Test Fixture Class
+///
+/// This test fixture class is used to perform
+/// unit tests on perfdhcp TestControl class.
+class TestControlTest : public virtual ::testing::Test
+{
+public:
+    /// \brief Default Constructor
+    TestControlTest() { }
+
+protected:
+};
+
+TEST_F(TestControlTest, Run) {
+    // Get the instance of TestControl object.
+    TestControl& test_control = TestControl::instance();
+    // Running test without parsing command line arguments is
+    // expected to cause exception.
+    EXPECT_THROW(test_control.run(), isc::InvalidOperation);
+
+    // The command line is to run single test iteration and exit.
+    // We have to declare argv as const walk around the problem
+    // of deprecated conversion from string to char*.
+    const char* argv[] = { "perfdhcp", "-l", "eth0", "-r", "10", "-n", "1" };
+    const int argc = sizeof(argv) / sizeof(argv[0]);
+    CommandOptions& options = CommandOptions::instance();
+
+    // const_cast is odd but it seems to be the most straight forward way
+    // to achive the goal. In the future we might think about creating
+    // a tokenizing function that would dynamically produce non-const
+    // argv value.
+    ASSERT_NO_THROW(options.parse(argc, const_cast<char**>(argv)));
+    EXPECT_NO_THROW(test_control.run());
+
+    // This is ok to run the test again with the same parameters.
+    // It may trigger exception if TestControl singleton is in
+    // invalid state after test run. We want to make sure it is
+    // safe to rerun the test.
+    EXPECT_NO_THROW(test_control.run());
+}