Commit 8e7ce816 authored by Marcin Siodelski's avatar Marcin Siodelski

[1959] Extended unit testing of TestControl class.

parent ce6188b3
......@@ -57,8 +57,9 @@ CommandOptions::reset() {
report_delay_ = 0;
clients_num_ = 0;
mac_prefix_.assign(mac, mac + 6);
base_.resize(0);
num_request_.resize(0);
duid_prefix_.clear();
base_.clear();
num_request_.clear();
period_ = 0;
drop_time_set_ = 0;
drop_time_.assign(dt, dt + 2);
......@@ -83,6 +84,8 @@ CommandOptions::reset() {
diags_.clear();
wrapped_.clear();
server_name_.clear();
generateDuidPrefix();
}
void
......@@ -461,6 +464,7 @@ CommandOptions::decodeMac(const std::string& base) {
void
CommandOptions::decodeDuid(const std::string& base) {
// Strip argument from duid=
std::vector<uint8_t> duid_prefix;
size_t found = base.find('=');
check(found == std::string::npos, "expected -b<base> format for duid is -b duid=<duid>");
std::string b = base.substr(found + 1);
......@@ -480,8 +484,10 @@ CommandOptions::decodeDuid(const std::string& base) {
isc_throw(isc::InvalidParameter,
"invalid characters in DUID provided, exepected hex digits");
}
duid_prefix_.push_back(static_cast<uint8_t>(ui));
duid_prefix.push_back(static_cast<uint8_t>(ui));
}
// Assign the new duid only if successfully generated.
std::swap(duid_prefix, duid_prefix_);
}
void
......
......@@ -97,10 +97,16 @@ TestControl::checkExitConditions() const {
}
OptionPtr
TestControl::factoryElapsedTimeSolicit6(Option::Universe, uint16_t,
const OptionBuffer&) {
return OptionPtr(new Option(Option::V6, D6O_ELAPSED_TIME,
OptionBuffer(2, 0)));
TestControl::factoryElapsedTime6(Option::Universe, uint16_t,
const OptionBuffer& buf) {
if (buf.size() == 2) {
return OptionPtr(new Option(Option::V6, D6O_ELAPSED_TIME, buf));
} else if (buf.size() == 0) {
return OptionPtr(new Option(Option::V6, D6O_ELAPSED_TIME,
OptionBuffer(2, 0)));
}
isc_throw(isc::BadValue,
"elapsed time option buffer size has to be 0 or 2");
}
OptionPtr
......@@ -112,14 +118,17 @@ TestControl::factoryGeneric(Option::Universe u, uint16_t type,
OptionPtr
TestControl::factoryIana6(Option::Universe, uint16_t,
const OptionBuffer&) {
const OptionBuffer& buf) {
const uint8_t buf_array[] = {
0, 0, 0, 1, // IAID = 1
0, 0, 3600 >> 8, 3600 && 0xff, // T1 = 3600
0, 0, 5400 >> 8, 5400 & 0xff, // T2 = 5400
};
OptionBuffer buf(buf_array, buf_array + sizeof(buf_array));
return OptionPtr(new Option(Option::V6, D6O_IA_NA, buf));
OptionBuffer buf_ia_na(buf_array, buf_array + sizeof(buf_array));
for (int i = 0; i < buf.size(); ++i) {
buf_ia_na.push_back(buf[i]);
}
return OptionPtr(new Option(Option::V6, D6O_IA_NA, buf_ia_na));
}
OptionPtr
......@@ -207,8 +216,13 @@ TestControl::generateDuid() const {
if ((clients_num == 0) || (clients_num == 1)) {
return options.getDuidPrefix();
}
// Get the base MAC address. We are going to randomize part of it.
// Get the base DUID. We are going to randomize part of it.
std::vector<uint8_t> duid(options.getDuidPrefix());
std::vector<uint8_t> mac_addr(generateMacAddress());
duid.resize(duid.size() - mac_addr.size());
for (int i = 0; i < mac_addr.size(); ++i) {
duid.push_back(mac_addr[i]);
}
return duid;
}
......@@ -227,12 +241,6 @@ TestControl::getNextExchangesNum() const {
// 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.
......@@ -261,17 +269,22 @@ TestControl::getNextExchangesNum() const {
}
int
TestControl::openSocket() const {
TestControl::openSocket(uint16_t port) const {
CommandOptions& options = CommandOptions::instance();
std::string localname = options.getLocalName();
std::string servername = options.getServerName();
uint8_t family = AF_INET;
uint16_t port = 67;
int sock = 0;
IOAddress remoteaddr(servername);
if (port == 0) {
if (options.getIpVersion() == 6) {
port = 547;
} else if (port == 0) {
port = 67;
}
}
if (options.getIpVersion() == 6) {
family = AF_INET6;
port = 547;
}
// Local name is specified along with '-l' option.
// It may point to interface name or local address.
......@@ -332,7 +345,7 @@ TestControl::openSocket() const {
&idx, sizeof(idx));
}
if (ret < 0) {
isc_throw(InvalidOperation,
isc_throw(InvalidOperation,
"unable to enable multicast on socket " << sock
<< ". errno = " << errno);
}
......@@ -389,7 +402,7 @@ TestControl::registerOptionFactories6() const {
if (!factories_registered) {
LibDHCP::OptionFactoryRegister(Option::V6,
D6O_ELAPSED_TIME,
&TestControl::factoryElapsedTimeSolicit6);
&TestControl::factoryElapsedTime6);
LibDHCP::OptionFactoryRegister(Option::V6,
D6O_RAPID_COMMIT,
&TestControl::factoryRapidCommit6);
......@@ -528,6 +541,8 @@ TestControl::setDefaults4(const TestControlSocket& socket,
pkt->setRemotePort(DHCP4_SERVER_PORT);
// The remote server's name or IP.
pkt->setRemoteAddr(IOAddress(options.getServerName()));
// Set local addresss.
pkt->setLocalAddr(IOAddress(socket.getAddress()));
// Set relay (GIADDR) address to local address.
pkt->setGiaddr(IOAddress(socket.getAddress()));
// Pretend that we have one relay (which is us).
......@@ -546,6 +561,8 @@ TestControl::setDefaults6(const TestControlSocket& socket,
pkt->setLocalPort(DHCP6_CLIENT_PORT);
// Server's port (548)
pkt->setRemotePort(DHCP6_SERVER_PORT);
// Set local address.
pkt->setLocalAddr(socket.getAddress());
// The remote server's name or IP.
pkt->setRemoteAddr(IOAddress(options.getServerName()));
}
......
......@@ -33,6 +33,14 @@ namespace perfdhcp {
/// This class is responsible for executing DHCP performance
/// test end to end.
///
/// Option factory functions are registered using
/// \ref LibDHCP::OptionFactoryRegister. Registered factory functions
/// provide a way to create options of the same type in the same way.
/// When new option instance is needed the corresponding factory
/// function is called to create it. This is done by calling
/// \ref Option::factory with DHCP message type specified as one of
/// parameters. Some of the parameters passed to factory function
/// may be ignored (e.g. option buffer).
class TestControl : public boost::noncopyable {
public:
......@@ -119,11 +127,16 @@ public:
/// \throw isc::Unexpected if internal Test Controler error occured.
void run();
private:
protected:
// We would really like these methods and members to be private but
// they have to be accessible for unit-testing. Another, possibly better,
// solution is to make this class friend of test class but this is not
// what's followed in other classes.
/// \brief Private default constructor.
/// \brief Default constructor.
///
/// Default constructor is private as the object can be created
/// Default constructor is protected as the object can be created
/// only via \ref instance method.
TestControl();
......@@ -139,40 +152,76 @@ private:
/// \return true if any of the exit conditions is fulfiled.
bool checkExitConditions() const;
/// \brief Factory function to create DHCPv6 ELAPSED_TIME option.
///
/// This factory function creates DHCPv6 ELAPSED_TIME option instance.
/// If empty buffer is passed the option buffer will be initialized
/// to length 2 and values will be initialized to zeros. Otherwise
/// function will initialize option buffer with values in passed buffer.
///
/// \param u universe (V6 or V4).
/// \param type option-type.
/// \param buf option-buffer.
/// \throw if elapsed time buffer size is neither 2 nor 0.
/// \return instance o the option.
static dhcp::OptionPtr
factoryElapsedTimeSolicit6(dhcp::Option::Universe u,
uint16_t type,
const dhcp::OptionBuffer& buf);
factoryElapsedTime6(dhcp::Option::Universe u,
uint16_t type,
const dhcp::OptionBuffer& buf);
/// \brief Factory function to create generic option.
///
/// Factory function is registered using \ref LibDHCP::OptionFactoryRegister.
/// Registered factory functions provide a way to create options of the
/// same type in the same way. When new option instance is needed the
/// corresponding factory function is called to create it. This is done
/// by calling \ref Option::factory with DHCP message type specified as
/// one of parameters. Some of the parameters passed to factory function
/// may be ignored (e.g. option buffer). For generic option however, factory
/// function creates option using contents of the buffer.
/// This factory function creates option with specified universe,
/// type and buf. It does not have any additional logic validating
/// the buffer contents, size etc.
///
/// \param u universe (V6 or V4).
/// \param type option-type.
/// \param buf option-buffer.
/// \return instance o the generic option.
/// \return instance o the option.
static dhcp::OptionPtr factoryGeneric(dhcp::Option::Universe u,
uint16_t type,
const dhcp::OptionBuffer& buf);
/// \brief Factory function to create IA_NA option.
///
/// This factory function creates DHCPv6 IA_NA option instance.
/// \TODO: add support for IA Address options.
/// \param u universe (V6 or V4).
/// \param type option-type.
/// \param buf option-buffer.
/// \return instance of IA_NA option.
static dhcp::OptionPtr factoryIana6(dhcp::Option::Universe u,
uint16_t type,
const dhcp::OptionBuffer& buf);
/// \brief Factory function to create DHCPv6 ORO option.
///
/// This factory function creates DHCPv6 Option Request Option instance.
/// The created option will contain the following set of requested options:
/// - D6O_NAME_SERVERS
/// - D6O_DOMAIN_SEARCH
///
/// \param u universe (V6 or V4).
/// \param type option-type.
/// \param buf option-buffer (ignored and should be empty).
/// \return instance of ORO option.
static dhcp::OptionPtr
factoryOptionRequestOption6(dhcp::Option::Universe u,
uint16_t type,
const dhcp::OptionBuffer& buf);
/// \brief Factory function to create DHCPv6 RAPID_COMMIT option instance.
///
/// This factory function creates DHCPv6 RAPID_COMMIT option instance.
/// The buffer passed to this option must be empty because option does
/// not have any payload.
///
/// \param u universe (V6 or V4).
/// \param type option-type.
/// \param buf option-buffer (ignored and should be empty).
/// \return instance of RAPID_COMMIT option..
static dhcp::OptionPtr factoryRapidCommit6(dhcp::Option::Universe u,
uint16_t type,
const dhcp::OptionBuffer& buf);
......@@ -180,14 +229,8 @@ private:
/// \brief Factory function to create DHCPv4 Request List option.
///
/// Factory function is registered using \ref LibDHCP::OptionFactoryRegister.
/// Registered factory functions provide a way to create options of the
/// same type in the same way. When new option instance is needed the
/// corresponding factory function is called to create it. This is done
/// by calling \ref Option::factory with DHCP message type specified as
/// one of parameters. This factory function ignores contents of the
/// buffer provided and creates option buffer internally with the following
/// list of requested options:
/// This factory function creayes DHCPv4 PARAMETER_REQUEST_LIST option
/// instance with the following set of requested options:
/// - DHO_SUBNET_MASK,
/// - DHO_BROADCAST_ADDRESS,
/// - DHO_TIME_OFFSET,
......@@ -198,12 +241,23 @@ private:
///
/// \param u universe (V6 or V4).
/// \param type option-type.
/// \param buf option-buffer.
/// \param buf option-buffer (ignored and should be empty).
/// \return instance o the generic option.
static dhcp::OptionPtr factoryRequestList4(dhcp::Option::Universe u,
uint16_t type,
const dhcp::OptionBuffer& buf);
/// \brief Generate DUID.
///
/// Method generates unique DUID. The number of DUIDs it can generate
/// depends on the number of simulated clinets, which is specified
/// from the command line. It uses \ref CommandOptions object to retrieve
/// number of clinets. Since the last six octets of DUID are constructed
/// from the MAC address, this function uses \ref generateMacAddress
/// internally to randomize the DUID.
///
/// \throw isc::BadValue if \ref generateMacAddress throws.
/// \return vector representing DUID.
std::vector<uint8_t> generateDuid() const;
/// \brief Generate MAC address.
......@@ -241,6 +295,7 @@ private:
/// (for DHCPv6) than broadcast or multicast option is set on
/// the socket.
///
/// \param port port to bound socket to.
/// \throw isc::BadValue if socket can't be created for given
/// interface, local address or remote address.
/// \throw isc::InvalidOperation if broadcast option can't be
......@@ -248,8 +303,11 @@ private:
/// for the v6 socket.
/// \throw isc::Unexpected if interal unexpected error occured.
/// \return socket descriptor.
int openSocket() const;
int openSocket(uint16_t port = 0) const;
/// \brief Receive DHCPv4 or DHCPv6 packets from the server.
///
/// Method receives DHCPv4 or DHCPv6 packets from the server.
void receivePackets();
/// \brief Register option factory functions for DHCPv4
......@@ -292,11 +350,23 @@ private:
/// \throw isc::BadValue if MAC address has invalid length.
void sendDiscover4(const TestControlSocket& socket);
/// \brief Send DHCPv6 SOLICIT message.
///
/// Method creates and sends DHCPv6 SOLICIT message to the server
/// with the following options:
/// - D6O_ELAPSED_TIME,
/// - D6O_RAPID_COMMIT if rapid commit is requested in command line,
/// - D6O_CLIENTID,
/// - D6O_ORO (Option Request Option),
/// - D6O_IA_NA.
///
/// \param socket socket to be used to send the message.
/// \throw isc::Unexpected if failed to create new packet instance.
void sendSolicit6(const TestControlSocket& socket);
/// \brief Set default DHCPv4 packet data.
/// \brief Set default DHCPv4 packet parameters.
///
/// This method sets default data on the DHCPv4 packet:
/// This method sets default parameters on the DHCPv4 packet:
/// - interface name,
/// - local port = 68 (DHCP client port),
/// - remote port = 67 (DHCP server port),
......@@ -305,10 +375,22 @@ private:
/// - hops = 1 (pretending that we are a relay)
///
/// \param socket socket used to send the packet.
/// \param pkt packet to be configured.
/// \param pkt reference to packet to be configured.
void setDefaults4(const TestControlSocket& socket,
const boost::shared_ptr<dhcp::Pkt4>& pkt);
/// \brief Set default DHCPv6 packet parameters.
///
/// This method sets default parameters on the DHCPv6 packet:
/// - interface name,
/// - interface index,
/// - local port,
/// - remote port,
/// - local address,
/// - remote address (server).
///
/// \param socket socket used to send the packet.
/// \param pkt reference to packet to be configured.
void setDefaults6(const TestControlSocket& socket,
const boost::shared_ptr<dhcp::Pkt6>& pkt);
......@@ -319,6 +401,8 @@ private:
/// expected rate in its calculations.
void updateSendDue();
private:
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
......
// 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 __COMMAND_OPTIONS_HELPER_H
#define __COMMAND_OPTIONS_HELPER_H
#include <string>
#include <vector>
#include <exceptions/exceptions.h>
#include "../command_options.h"
namespace isc {
namespace perfdhcp {
/// \brief Command Options Helper class.
///
/// This helper class can be shared between unit tests that
/// need to initialize CommandOptions objects and feed it with
/// specific command line. The command line can be given as a
/// string represinting program name, options and arguments.
/// The static method exposed by this class can be used to
/// tokenize this string into array of C-strings that are later
/// consumed by \ref CommandOptions::parse. The state of the
/// CommandOptions object is reset every time the process
/// function is invoked. Also, when command line parsing is
/// ended the array of C-string is freed from the memory.
class CommandOptionsHelper {
public:
class ArgvPtr {
public:
ArgvPtr(char** argv, int argc) : argv_(argv), argc_(argc) { }
char** getArgv() const { return(argv_); }
int getArgc() const { return(argc_); }
~ArgvPtr() {
if (argv_ != NULL) {
for(int i = 0; i < argc_; ++i) {
free(argv_[i]);
argv_[i] = NULL;
}
free(argv_);
}
}
public:
char** argv_;
int argc_;
};
static void process(const std::string& cmdline) {
CommandOptions& opt = CommandOptions::instance();
int argc = 0;
char** argv = tokenizeString(cmdline, argc);
ArgvPtr args(argv, argc);
opt.reset();
opt.parse(args.getArgc(), args.getArgv());
}
private:
static char** tokenizeString(const std::string& text_to_split, int& num) {
char** results = NULL;
// Tokenization with std streams
std::stringstream text_stream(text_to_split);
// Iterators to be used for tokenization
std::istream_iterator<std::string> text_iterator(text_stream);
std::istream_iterator<std::string> text_end;
// Tokenize string (space is a separator) using begin and end iteratos
std::vector<std::string> tokens(text_iterator, text_end);
if (tokens.size() > 0) {
// Allocate array of C-strings where we will store tokens
results = static_cast<char**>(malloc(tokens.size() * sizeof(char*)));
if (results == NULL) {
isc_throw(Unexpected, "unable to allocate array of c-strings");
}
// Store tokens in C-strings array
for (int i = 0; i < tokens.size(); ++i) {
char* cs = static_cast<char*>(malloc(tokens[i].length() + 1));
strcpy(cs, tokens[i].c_str());
results[i] = cs;
}
// Return number of tokens to calling function
num = tokens.size();
}
return results;
}
};
} // namespace perfdhcp
} // namespace isc
#endif // __COMMAND_OPTIONS_HELPER_H
......@@ -245,7 +245,8 @@ TEST_F(CommandOptionsTest, Base) {
process("perfdhcp -6 -b MAC=10::20::30::40::50::60 "
"-l ethx -b duiD=1AB7F5670901FF all");
uint8_t mac[6] = {0x10, 0x20, 0x30, 0x40, 0x50, 0x60 };
uint8_t duid[7] = { 0x1A, 0xB7, 0xF5, 0x67, 0x09, 0x01, 0xFF };
uint8_t duid[14] = { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x10, 0x11, 0x1F, 0x14 };
// Test Mac
std::vector<uint8_t> v1 = opt.getMacPrefix();
......@@ -256,11 +257,15 @@ TEST_F(CommandOptionsTest, Base) {
isc::InvalidParameter);
// Test DUID
EXPECT_NO_THROW(
process("perfdhcp -b duid=0101010101010101010110111F14 -l 127.0.0.1 all")
);
std::vector<uint8_t> v2 = opt.getDuidPrefix();
ASSERT_EQ(sizeof(duid) / sizeof(uint8_t), v2.size());
EXPECT_TRUE(std::equal(v2.begin(), v2.end(), duid));
// "t" is invalid digit in DUID
EXPECT_THROW(process("perfdhcp -6 -l ethx -b duiD=1AB7Ft670901FF all"),
// "t" is invalid character in DUID
EXPECT_THROW(process("perfdhcp -6 -l ethx -b "
"duiD=010101010101010101t110111F14 all"),
isc::InvalidParameter);
// Some more negative test cases
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment