Commit b0568282 authored by Marcin Siodelski's avatar Marcin Siodelski
Browse files

[master] Merge branch 'trac5651'

# Conflicts:
#	doc/guide/hooks.xml
#	src/lib/dhcpsrv/cql_lease_mgr.cc
parents b9411839 ed42dddc
......@@ -7,9 +7,9 @@ dist_html_DATA = $(HTMLDOCS) kea-guide.css kea-logo-100x70.png
DOCBOOK = kea-guide.xml intro.xml quickstart.xml install.xml admin.xml config.xml
DOCBOOK += keactrl.xml dhcp4-srv.xml dhcp6-srv.xml lease-expiration.xml logging.xml
DOCBOOK += ddns.xml hooks.xml hooks-ha.xml hooks-host-cache.xml hooks-radius.xml
DOCBOOK += hooks-stat-cmds.xml libdhcp.xml lfc.xml stats.xml ctrl-channel.xml
DOCBOOK += faq.xml classify.xml shell.xml agent.xml
DOCBOOK += ddns.xml hooks.xml hooks-ha.xml hooks-host-cache.xml hooks-lease-cmds.xml
DOCBOOK += hooks-radius.xml hooks-stat-cmds.xml libdhcp.xml lfc.xml stats.xml
DOCBOOK += ctrl-channel.xml faq.xml classify.xml shell.xml agent.xml
EXTRA_DIST = $(DOCBOOK)
......
This diff is collapsed.
This diff is collapsed.
......@@ -24,6 +24,7 @@
#include <util/strutil.h>
#include <boost/bind.hpp>
#include <boost/scoped_ptr.hpp>
#include <string>
#include <sstream>
......@@ -143,6 +144,23 @@ public:
int
leaseGetAllHandler(CalloutHandle& handle);
/// @brief lease4-get-page, lease6-get-page commands handler
///
/// These commands attempt to retrieve 1 page of leases. The maximum size
/// of the page is specified by the caller. The caller also specifies
/// the last address returned in the previous page. The new page
/// starts from the first address following the address specified by
/// the caller. If the first page should be returned the IPv4
/// zero address, IPv6 zero address or the keyword "start" should
/// be provided instead of the last address.
///
/// @param handle Callout context - which is expected to contain the
/// get commands JSON text in the "command" argument.
/// @return 0 if the handler has been invoked successfully, 1 if an
/// error occurs, 3 if no leases are returned.
int
leaseGetPageHandler(hooks::CalloutHandle& handle);
/// @brief lease4-del command handler
///
/// Provides the implementation for @ref isc::lease_cmds::LeaseCmds::lease4DelHandler
......@@ -554,6 +572,125 @@ LeaseCmdsImpl::leaseGetAllHandler(CalloutHandle& handle) {
return (0);
}
int
LeaseCmdsImpl::leaseGetPageHandler(CalloutHandle& handle) {
bool v4 = true;
try {
extractCommand(handle);
v4 = (cmd_name_ == "lease4-get-page");
// arguments must always be present
if (!cmd_args_) {
isc_throw(BadValue, "no parameters specified for the " << cmd_name_
<< " command");
}
// The 'from' argument denotes from which lease we should start the
// results page. The results page excludes this lease.
ConstElementPtr from = cmd_args_->get("from");
if (!from) {
isc_throw(BadValue, "'from' parameter not specified");
}
// The 'from' argument is a string. It may contain a 'start' keyword or
// an IP address.
if (from->getType() != Element::string) {
isc_throw(BadValue, "'from' parameter must be a string");
}
boost::scoped_ptr<IOAddress> from_address;
try {
if (from->stringValue() == "start") {
from_address.reset(new IOAddress(v4 ? "0.0.0.0" : "::"));
} else {
// Conversion of a string to an IP address may throw.
from_address.reset(new IOAddress(from->stringValue()));
}
} catch (...) {
isc_throw(BadValue, "'from' parameter value is neither 'start' keyword nor "
"a valid IPv" << (v4 ? "4" : "6") << " address");
}
// It must be either IPv4 address for lease4-get-page or IPv6 address for
// lease6-get-page.
if (v4 && (!from_address->isV4())) {
isc_throw(BadValue, "'from' parameter value " << from_address->toText()
<< " is not an IPv4 address");
} else if (!v4 && from_address->isV4()) {
isc_throw(BadValue, "'from' parameter value " << from_address->toText()
<< " is not an IPv6 address");
}
// The 'limit' is a desired page size. It must always be present.
ConstElementPtr page_limit = cmd_args_->get("limit");
if (!page_limit) {
isc_throw(BadValue, "'limit' parameter not specified");
}
// The 'limit' must be a number.
if (page_limit->getType() != Element::integer) {
isc_throw(BadValue, "'limit' parameter must be a number");
}
// Retrieve the desired page size.
size_t page_limit_value = static_cast<size_t>(page_limit->intValue());
ElementPtr leases_json = Element::createList();
if (v4) {
// Get page of IPv4 leases.
Lease4Collection leases =
LeaseMgrFactory::instance().getLeases4(*from_address,
LeasePageSize(page_limit_value));
// Convert leases into JSON list.
for (auto lease : leases) {
ElementPtr lease_json = lease->toElement();
leases_json->add(lease_json);
}
} else {
// Get page of IPv6 leases.
Lease6Collection leases =
LeaseMgrFactory::instance().getLeases6(*from_address,
LeasePageSize(page_limit_value));
// Convert leases into JSON list.
for (auto lease : leases) {
ElementPtr lease_json = lease->toElement();
leases_json->add(lease_json);
}
}
// Prepare textual status.
std::ostringstream s;
s << leases_json->size()
<< " IPv" << (v4 ? "4" : "6")
<< " lease(s) found.";
ElementPtr args = Element::createMap();
// Put gathered data into arguments map.
args->set("leases", leases_json);
args->set("count", Element::create(static_cast<int64_t>(leases_json->size())));
// Create the response.
ConstElementPtr response =
createAnswer(leases_json->size() > 0 ?
CONTROL_RESULT_SUCCESS :
CONTROL_RESULT_EMPTY,
s.str(), args);
setResponse(handle, response);
} catch (std::exception& ex) {
setErrorResponse(handle, ex.what());
return (CONTROL_RESULT_ERROR);
}
return (CONTROL_RESULT_SUCCESS);
}
int
LeaseCmdsImpl::lease4DelHandler(CalloutHandle& handle) {
Parameters p;
......@@ -867,6 +1004,11 @@ LeaseCmds::leaseGetAllHandler(hooks::CalloutHandle& handle) {
return (impl_->leaseGetAllHandler(handle));
}
int
LeaseCmds::leaseGetPageHandler(hooks::CalloutHandle& handle) {
return (impl_->leaseGetPageHandler(handle));
}
int
LeaseCmds::lease4DelHandler(CalloutHandle& handle) {
return(impl_->lease4DelHandler(handle));
......
......@@ -159,6 +159,23 @@ public:
int
leaseGetAllHandler(hooks::CalloutHandle& handle);
/// @brief lease4-get-page, lease6-get-page commands handler
///
/// These commands attempt to retrieve 1 page of all IPv4 or IPv6
/// leases. The size of the page is specified by the caller. The
/// caller also specifies the last address returned in the previous
/// page. The new page starts from the first address following the
/// address specified by the caller. If the first page should be
/// returned the IPv4 zero address, IPv6 zero address or the keyword
/// "start" should be provided instead of the last address.
///
/// @param handle Callout context - which is expected to contain the
/// get commands JSON text in the "command" argument.
/// @return 0 if the handler has been invoked successfully, 1 if an
/// error occurs, 3 if no leases are returned.
int
leaseGetPageHandler(hooks::CalloutHandle& handle);
/// @brief lease4-del command handler
///
/// This command attempts to delete an IPv4 lease that match selected
......
......@@ -88,6 +88,28 @@ int lease6_get_all(CalloutHandle& handle) {
return (lease_cmds.leaseGetAllHandler(handle));
}
/// @brief This is a command callout for 'lease4-get-page' command.
///
/// @param handle Callout handle used to retrieve a command and
/// provide a response.
/// @return 0 if this callout has been invoked successfully,
/// 1 if an error occurs, 3 if no leases are returned.
int lease4_get_page(CalloutHandle& handle) {
LeaseCmds lease_cmds;
return (lease_cmds.leaseGetPageHandler(handle));
}
/// @brief This is a command callout for 'lease6-get-page' command.
///
/// @param handle Callout handle used to retrieve a command and
/// provide a response.
/// @return 0 if this callout has been invoked successfully,
/// 1 if an error occurs, 3 if no leases are returned.
int lease6_get_page(CalloutHandle& handle) {
LeaseCmds lease_cmds;
return (lease_cmds.leaseGetPageHandler(handle));
}
/// @brief This is a command callout for 'lease4-del' command.
///
/// @param handle Callout handle used to retrieve a command and
......@@ -162,9 +184,11 @@ int load(LibraryHandle& handle) {
handle.registerCommandCallout("lease4-add", lease4_add);
handle.registerCommandCallout("lease6-add", lease6_add);
handle.registerCommandCallout("lease4-get", lease4_get);
handle.registerCommandCallout("lease4-get-all", lease4_get_all);
handle.registerCommandCallout("lease6-get", lease6_get);
handle.registerCommandCallout("lease4-get-all", lease4_get_all);
handle.registerCommandCallout("lease6-get-all", lease6_get_all);
handle.registerCommandCallout("lease4-get-page", lease4_get_page);
handle.registerCommandCallout("lease6-get-page", lease6_get_page);
handle.registerCommandCallout("lease4-del", lease4_del);
handle.registerCommandCallout("lease6-del", lease6_del);
handle.registerCommandCallout("lease4-update", lease4_update);
......
......@@ -17,6 +17,7 @@
#include <testutils/user_context_utils.h>
#include <gtest/gtest.h>
#include <errno.h>
#include <set>
using namespace std;
using namespace isc;
......@@ -478,11 +479,13 @@ public:
// Simple test that checks the library really registers the commands.
TEST_F(LeaseCmdsTest, commands) {
vector<string> cmds = { "lease4-add", "lease6-add",
"lease4-get", "lease6-get",
"lease4-del", "lease6-del",
"lease4-update", "lease6-update",
"lease4-wipe", "lease6-wipe" };
vector<string> cmds = { "lease4-add", "lease6-add",
"lease4-get", "lease6-get",
"lease4-get-all", "lease6-get-all",
"lease4-get-page", "lease6-get-page",
"lease4-del", "lease6-del",
"lease4-update", "lease6-update",
"lease4-wipe", "lease6-wipe" };
testCommands(cmds);
}
......@@ -1731,6 +1734,139 @@ TEST_F(LeaseCmdsTest, Lease4GetBySubnetIdInvalidArguments) {
testCommand(cmd, CONTROL_RESULT_ERROR, exp_rsp);
}
// Checks that multiple calls to lease4-get-pages return all leases.
TEST_F(LeaseCmdsTest, Lease4GetPaged) {
// Initialize lease manager (false = v4, true = add a lease)
initLeaseMgr(false, true);
// Gather all returned addresses to verify that all were returned.
std::set<std::string> lease_addresses;
// Keyword start indicates that we want to retrieve the first page.
std::string last_address = "start";
// There are 4 leases in the database, so the first two pages should
// include leases and the 3 page should be empty.
for (auto i = 0; i < 3; ++i) {
// Query for a page of leases.
string cmd =
"{\n"
" \"command\": \"lease4-get-page\",\n"
" \"arguments\": {"
" \"from\": \"" + last_address + "\","
" \"limit\": 2"
" }"
"}";
// For the first two pages we shuould get success. For the last
// one an empty status code.
ConstElementPtr rsp;
if (i < 2) {
string exp_rsp = "2 IPv4 lease(s) found.";
rsp = testCommand(cmd, CONTROL_RESULT_SUCCESS, exp_rsp);
} else {
string exp_rsp = "0 IPv4 lease(s) found.";
rsp = testCommand(cmd, CONTROL_RESULT_EMPTY, exp_rsp);
}
// Now check that the lease parameters were indeed returned.
ASSERT_TRUE(rsp);
// Arguments must exist.
ConstElementPtr args = rsp->get("arguments");
ASSERT_TRUE(args);
ASSERT_EQ(Element::map, args->getType());
// For convenience, we return the number of returned leases,
// so as the client can check whether there was anything returned
// before parsing the leases structure.
ConstElementPtr page_count = args->get("count");
ASSERT_TRUE(page_count);
ASSERT_EQ(Element::integer, page_count->getType());
// leases must exist, but may be empty.
ConstElementPtr leases = args->get("leases");
ASSERT_TRUE(leases);
ASSERT_EQ(Element::list, leases->getType());
if (!leases->empty()) {
EXPECT_EQ(2, page_count->intValue());
// Go over each lease and verify its correctness.
for (ConstElementPtr lease : leases->listValue()) {
ASSERT_EQ(Element::map, lease->getType());
ASSERT_TRUE(lease->contains("ip-address"));
ConstElementPtr ip_address = lease->get("ip-address");
ASSERT_EQ(Element::string, ip_address->getType());
last_address = ip_address->stringValue();
lease_addresses.insert(last_address);
// The easiest way to retrieve the subnet id and HW address is to
// ask the Lease Manager.
Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(IOAddress(last_address));
ASSERT_TRUE(from_mgr);
checkLease4(leases, last_address, from_mgr->subnet_id_,
from_mgr->hwaddr_->toText(false), true);
}
} else {
// In the third iteration the page should be empty.
EXPECT_EQ(0, page_count->intValue());
}
}
// Check if all addresses were returned.
EXPECT_EQ(1, lease_addresses.count("192.0.2.1"));
EXPECT_EQ(1, lease_addresses.count("192.0.2.2"));
EXPECT_EQ(1, lease_addresses.count("192.0.3.1"));
EXPECT_EQ(1, lease_addresses.count("192.0.3.2"));
}
// Verifies that first page of IPv4 leases can be retrieved by specifying
// zero IPv4 address.
TEST_F(LeaseCmdsTest, Lease4GetPagedZeroAddress) {
// Initialize lease manager (false = v4, true = add a lease)
initLeaseMgr(false, true);
// Query for a page of leases.
string cmd =
"{\n"
" \"command\": \"lease4-get-page\",\n"
" \"arguments\": {"
" \"from\": \"0.0.0.0\","
" \"limit\": 2"
" }"
"}";
string exp_rsp = "2 IPv4 lease(s) found.";
testCommand(cmd, CONTROL_RESULT_SUCCESS, exp_rsp);
}
// Verifies that IPv6 address as a start address is rejected.
TEST_F(LeaseCmdsTest, Lease4GetPagedIPv4Address) {
// Initialize lease manager (false = v4, true = add a lease)
initLeaseMgr(false, true);
// Query for a page of leases.
string cmd =
"{\n"
" \"command\": \"lease4-get-page\",\n"
" \"arguments\": {"
" \"from\": \"2001:db8::1\","
" \"limit\": 2"
" }"
"}";
string exp_rsp = "'from' parameter value 2001:db8::1 is not an IPv4 address";
testCommand(cmd, CONTROL_RESULT_ERROR, exp_rsp);
}
// Checks that lease6-get-all returns all leases.
TEST_F(LeaseCmdsTest, Lease6GetAll) {
......@@ -1933,6 +2069,221 @@ TEST_F(LeaseCmdsTest, Lease6GetBySubnetIdInvalidArguments) {
testCommand(cmd, CONTROL_RESULT_ERROR, exp_rsp);
}
// Checks that multiple calls to lease6-get-page return all leases.
TEST_F(LeaseCmdsTest, Lease6GetPaged) {
// Initialize lease manager (true = v6, true = add a lease)
initLeaseMgr(true, true);
// Gather all returned addresses to verify that all were returned.
std::set<std::string> lease_addresses;
// Keyword start indicates that we want to retrieve the first page.
std::string last_address = "start";
// There are 4 leases in the database, so the first two pages should
// include leases and the 3 page should be empty.
for (auto i = 0; i < 3; ++i) {
// Query for a page of leases.
string cmd =
"{\n"
" \"command\": \"lease6-get-page\",\n"
" \"arguments\": {"
" \"from\": \"" + last_address + "\","
" \"limit\": 2"
" }"
"}";
// For the first two pages we shuould get success. For the last
// one an empty status code.
ConstElementPtr rsp;
if (i < 2) {
string exp_rsp = "2 IPv6 lease(s) found.";
rsp = testCommand(cmd, CONTROL_RESULT_SUCCESS, exp_rsp);
} else {
string exp_rsp = "0 IPv6 lease(s) found.";
rsp = testCommand(cmd, CONTROL_RESULT_EMPTY, exp_rsp);
}
// Now check that the lease parameters were indeed returned.
ASSERT_TRUE(rsp);
// Arguments must exist.
ConstElementPtr args = rsp->get("arguments");
ASSERT_TRUE(args);
ASSERT_EQ(Element::map, args->getType());
// For convenience, we return the number of returned leases,
// so as the client can check whether there was anything returned
// before parsing the leases structure.
ConstElementPtr page_count = args->get("count");
ASSERT_TRUE(page_count);
ASSERT_EQ(Element::integer, page_count->getType());
// leases must exist, but may be empty.
ConstElementPtr leases = args->get("leases");
ASSERT_TRUE(leases);
ASSERT_EQ(Element::list, leases->getType());
if (!leases->empty()) {
EXPECT_EQ(2, page_count->intValue());
// Go over each lease and verify its correctness.
for (ConstElementPtr lease : leases->listValue()) {
ASSERT_EQ(Element::map, lease->getType());
ASSERT_TRUE(lease->contains("ip-address"));
ConstElementPtr ip_address = lease->get("ip-address");
ASSERT_EQ(Element::string, ip_address->getType());
last_address = ip_address->stringValue();
lease_addresses.insert(last_address);
// The easiest way to retrieve the subnet id and HW address is to
// ask the Lease Manager.
Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
IOAddress(last_address));
ASSERT_TRUE(from_mgr);
checkLease6(leases, last_address, 0, from_mgr->subnet_id_,
from_mgr->duid_->toText(), false);
}
} else {
// In the third iteration the page should be empty.
EXPECT_EQ(0, page_count->intValue());
}
}
// Check if all addresses were returned.
EXPECT_EQ(1, lease_addresses.count("2001:db8:1::1"));
EXPECT_EQ(1, lease_addresses.count("2001:db8:1::2"));
EXPECT_EQ(1, lease_addresses.count("2001:db8:2::1"));
EXPECT_EQ(1, lease_addresses.count("2001:db8:2::2"));
}
// Verifies that first page of IPv6 leases can be retrieved by specifying
// zero IPv6 address.
TEST_F(LeaseCmdsTest, Lease6GetPagedZeroAddress) {
// Initialize lease manager (true = v6, true = add a lease)
initLeaseMgr(true, true);
// Query for a page of leases.
string cmd =
"{\n"
" \"command\": \"lease6-get-page\",\n"
" \"arguments\": {"
" \"from\": \"::\","
" \"limit\": 2"
" }"
"}";
string exp_rsp = "2 IPv6 lease(s) found.";
testCommand(cmd, CONTROL_RESULT_SUCCESS, exp_rsp);
}
// Verifies that IPv4 address as a start address is rejected.
TEST_F(LeaseCmdsTest, Lease6GetPagedIPv4Address) {
// Initialize lease manager (true = v6, true = add a lease)
initLeaseMgr(true, true);
// Query for a page of leases.
string cmd =
"{\n"
" \"command\": \"lease6-get-page\",\n"
" \"arguments\": {"
" \"from\": \"192.0.2.3\","
" \"limit\": 2"
" }"
"}";
string exp_rsp = "'from' parameter value 192.0.2.3 is not an IPv6 address";
testCommand(cmd, CONTROL_RESULT_ERROR, exp_rsp);
}
// Verifies that value of 'from' parameter other than 'start' or an IPv6
// address is rejected.
TEST_F(LeaseCmdsTest, Lease6GetPagedInvalidFrom) {
// Initialize lease manager (true = v6, true = add a lease)
initLeaseMgr(true, true);
// Query for a page of leases.
string cmd =
"{\n"
" \"command\": \"lease6-get-page\",\n"
" \"arguments\": {"
" \"from\": \"foo\","
" \"limit\": 2"
" }"
"}";
string exp_rsp = "'from' parameter value is neither 'start' keyword "
"nor a valid IPv6 address";
testCommand(cmd, CONTROL_RESULT_ERROR, exp_rsp);
}
// Verifies that limit is mandatory.
TEST_F(LeaseCmdsTest, Lease6GetPagedNoLimit) {
// Initialize lease manager (true = v6, true = add a lease)
initLeaseMgr(true, true);
// Query for a page of leases.
string cmd =
"{\n"
" \"command\": \"lease6-get-page\",\n"
" \"arguments\": {"
" \"from\": \"start\""
" }"
"}";