From 55e2828cf2bc115cd332844f43938a6cbddc5103 Mon Sep 17 00:00:00 2001 From: Francis Dupont Date: Sun, 13 Jan 2019 11:40:20 +0100 Subject: [PATCH] [313-return-a-list-of-all-reservations-by-subnet-id] Finished the getAll[46] implementation --- doc/Makefile.am | 3 +- doc/api/reservation-get-all.json | 14 ++ doc/docgen/cmds-list | 1 + doc/guide/api.xml | 59 +++++++ doc/guide/hooks.xml | 26 ++- src/lib/dhcpsrv/cql_host_data_source.cc | 150 +++++++++++++++++- src/lib/dhcpsrv/mysql_host_data_source.cc | 82 +++++++++- src/lib/dhcpsrv/mysql_host_data_source.h | 4 +- src/lib/dhcpsrv/pgsql_host_data_source.cc | 84 +++++++++- src/lib/dhcpsrv/pgsql_host_data_source.h | 4 +- src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc | 58 +++++++ .../tests/cql_host_data_source_unittest.cc | 12 +- .../tests/mysql_host_data_source_unittest.cc | 12 +- .../tests/pgsql_host_data_source_unittest.cc | 12 +- .../generic_host_data_source_unittest.cc | 94 ++++++++++- .../generic_host_data_source_unittest.h | 16 +- .../testutils/memory_host_data_source.cc | 23 ++- .../testutils/memory_host_data_source.h | 7 +- 18 files changed, 625 insertions(+), 36 deletions(-) create mode 100644 doc/api/reservation-get-all.json diff --git a/doc/Makefile.am b/doc/Makefile.am index a049d354ba..95f03b1849 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -106,7 +106,8 @@ EXTRA_DIST += api/network6-add.json api/network6-del.json EXTRA_DIST += api/network6-get.json api/network6-list.json EXTRA_DIST += api/network6-subnet-add.json api/network6-subnet-del.json EXTRA_DIST += api/reservation-add.json api/reservation-del.json -EXTRA_DIST += api/reservation-get.json api/shutdown.json +EXTRA_DIST += api/reservation-get.json api/reservation-get-all.json +EXTRA_DIST += api/shutdown.json EXTRA_DIST += api/statistic-get-all.json api/statistic-get.json EXTRA_DIST += api/statistic-remove-all.json api/statistic-remove.json EXTRA_DIST += api/statistic-reset-all.json api/statistic-reset.json diff --git a/doc/api/reservation-get-all.json b/doc/api/reservation-get-all.json new file mode 100644 index 0000000000..75b9ef85df --- /dev/null +++ b/doc/api/reservation-get-all.json @@ -0,0 +1,14 @@ +{ + "name": "reservation-get-all", + "brief": "Retrieve all host reservations for a specified subnet.", + "support": [ "kea-dhcp4", "kea-dhcp6" ], + "hook": "host_cmds", + "avail": "1.6.0", + + "cmd-syntax": "{ + \"command\": \"reservation-get-all\", + \"arguments\": { + \"subnet-id\": +}", + "resp-comment": "reservation-get-all command may result in very large responses." +} diff --git a/doc/docgen/cmds-list b/doc/docgen/cmds-list index 64408e552b..adbdffdb03 100644 --- a/doc/docgen/cmds-list +++ b/doc/docgen/cmds-list @@ -51,6 +51,7 @@ network6-subnet-del reservation-add reservation-del reservation-get +reservation-get-all shutdown stat-lease4-get stat-lease6-get diff --git a/doc/guide/api.xml b/doc/guide/api.xml index 7b807384c3..e20fa59f6b 100644 --- a/doc/guide/api.xml +++ b/doc/guide/api.xml @@ -63,6 +63,7 @@ , reservation-add , reservation-del , reservation-get +, reservation-get-all , shutdown , stat-lease4-get , stat-lease6-get @@ -143,6 +144,7 @@ , reservation-add , reservation-del , reservation-get +, reservation-get-all , shutdown , stat-lease4-get , statistic-get @@ -198,6 +200,7 @@ , reservation-add , reservation-del , reservation-get +, reservation-get-all , shutdown , stat-lease6-get , statistic-get @@ -233,6 +236,7 @@ Commands supported by host_cmds hook library: reservation-add , reservation-del , reservation-get +, reservation-get-all . Commands supported by lease_cmds hook library: lease4-add , lease4-del @@ -2511,6 +2515,61 @@ object appear only if specific field is set. + +
+reservation-get reference +reservation-get-all - Retrieve all host reservations for a specified subnet. + +Supported by: kea-dhcp4, kea-dhcp6 + +Availability: 1.6.0 (host_cmds hook) + +Description and examples: See + +Command syntax: + { + "command": "reservation-get-all", + "arguments": { + "subnet-id": <integer> + } +} +Host reservations can be identified by subnet-id. + +Response syntax: + { + "result": <integer>, + "text": <string>, + "arguments": { + "hosts": [ + { + "boot-file-name": <string>, + "comment": <string> + "client-id": <string>, + "circuit-id": <string>, + "duid": <string>, + "flex-id": <string>, + "ip-address": <string (IPv4 address)>, + "ip-addresses": [ <comma separated strings> ], + "hw-address": <string>, + "hostname": <string>, + "next-server": <string (IPv4 address)>, + "option-data-list": [ <comma separated structures defining options> ], + "prefixes": [ <comma separated IPv6 prefixes> ], + "reservation-client-classes": [ <comma separated strings> ], + "server-hostname": <string>, + "subnet-id": <integer>, + "user-context": <any valid JSON>, + }, + ... + ] + } +} + +The reservation-get-all command may result in very large responses. + +
+ +
shutdown reference diff --git a/doc/guide/hooks.xml b/doc/guide/hooks.xml index fb36d462af..bc4bdd67cc 100644 --- a/doc/guide/hooks.xml +++ b/doc/guide/hooks.xml @@ -1,5 +1,5 @@ + + The response returned by reservation-get-all + can be very long. + +
+
reservation-del command reservation-del can be used to delete a diff --git a/src/lib/dhcpsrv/cql_host_data_source.cc b/src/lib/dhcpsrv/cql_host_data_source.cc index 6f7648ebef..ad9dd5d71b 100644 --- a/src/lib/dhcpsrv/cql_host_data_source.cc +++ b/src/lib/dhcpsrv/cql_host_data_source.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2018-2019 Internet Systems Consortium, Inc. ("ISC") // Copyright (C) 2016-2017 Deutsche Telekom AG. // // Author: Andrei Pavel @@ -311,6 +311,16 @@ public: // Deletes a host reservation. static constexpr StatementTag DELETE_HOST = "DELETE_HOST"; + + // Retrieves host information along with the IPv4 options associated + // with it using a subnet identifier. + static constexpr StatementTag GET_HOST_BY_IPV4_SUBNET_ID = + "GET_HOST_BY_IPV4_SUBNET_ID"; + + // Retrieves host information; IPv6 reservations and IPv6 options + // associated with a host using subnet identifier. + static constexpr StatementTag GET_HOST_BY_IPV6_SUBNET_ID = + "GET_HOST_BY_IPV6_SUBNET_ID"; /// @} /// @brief Cassandra statements @@ -420,6 +430,8 @@ constexpr StatementTag CqlHostExchange::GET_HOST_BY_IPV4_SUBNET_ID_AND_ADDRESS; constexpr StatementTag CqlHostExchange::GET_HOST_BY_IPV6_PREFIX; constexpr StatementTag CqlHostExchange::GET_HOST_BY_IPV6_SUBNET_ID_AND_ADDRESS; constexpr StatementTag CqlHostExchange::DELETE_HOST; +constexpr StatementTag CqlHostExchange::GET_HOST_BY_IPV4_SUBNET_ID; +constexpr StatementTag CqlHostExchange::GET_HOST_BY_IPV6_SUBNET_ID; StatementMap CqlHostExchange::tagged_statements_ = { {INSERT_HOST, @@ -762,6 +774,78 @@ StatementMap CqlHostExchange::tagged_statements_ = { {DELETE_HOST, "DELETE FROM host_reservations WHERE id = ? " "IF EXISTS " + }}, + + {GET_HOST_BY_IPV4_SUBNET_ID, + {GET_HOST_BY_IPV4_SUBNET_ID, + "SELECT " + "id, " + "host_identifier, " + "host_identifier_type, " + "host_ipv4_subnet_id, " + "host_ipv6_subnet_id, " + "host_ipv4_address, " + "host_ipv4_next_server, " + "host_ipv4_server_hostname, " + "host_ipv4_boot_file_name, " + "auth_key, " + "hostname, " + "user_context, " + "host_ipv4_client_classes, " + "host_ipv6_client_classes, " + "reserved_ipv6_prefix_address, " + "reserved_ipv6_prefix_length, " + "reserved_ipv6_prefix_address_type, " + "iaid, " + "option_universe, " + "option_code, " + "option_value, " + "option_formatted_value, " + "option_space, " + "option_is_persistent, " + "option_client_class, " + "option_subnet_id, " + "option_user_context, " + "option_scope_id " + "FROM host_reservations " + "WHERE host_ipv4_subnet_id = ? " + "ALLOW FILTERING " + }}, + + {GET_HOST_BY_IPV6_SUBNET_ID, + {GET_HOST_BY_IPV6_SUBNET_ID, + "SELECT " + "id, " + "host_identifier, " + "host_identifier_type, " + "host_ipv4_subnet_id, " + "host_ipv6_subnet_id, " + "host_ipv4_address, " + "host_ipv4_next_server, " + "host_ipv4_server_hostname, " + "host_ipv4_boot_file_name, " + "auth_key, " + "hostname, " + "user_context, " + "host_ipv4_client_classes, " + "host_ipv6_client_classes, " + "reserved_ipv6_prefix_address, " + "reserved_ipv6_prefix_length, " + "reserved_ipv6_prefix_address_type, " + "iaid, " + "option_universe, " + "option_code, " + "option_value, " + "option_formatted_value, " + "option_space, " + "option_is_persistent, " + "option_client_class, " + "option_subnet_id, " + "option_user_context, " + "option_scope_id " + "FROM host_reservations " + "WHERE host_ipv6_subnet_id = ? " + "ALLOW FILTERING " }} }; @@ -905,7 +989,7 @@ CqlHostExchange::prepareExchange(const HostPtr& host, // auth_key: varchar auth_key_ = host->getKey().ToText(); - + // hostname: text hostname_ = host->getHostname(); if (hostname_.size() > HOSTNAME_MAX_LENGTH) { @@ -1428,6 +1512,20 @@ public: const uint8_t* identifier_begin, const size_t identifier_len) const; + /// @brief Implementation of @ref CqlHostDataSource::getAll4() + /// + /// See @ref CqlHostDataSource::getAll4() for parameter details. + /// + /// @param subnet_id identifier of the subnet to which hosts belong + virtual ConstHostCollection getAll4(const SubnetID& subnet_id) const; + + /// @brief Implementation of @ref CqlHostDataSource::getAll6() + /// + /// See @ref CqlHostDataSource::getAll6() for parameter details. + /// + /// @param subnet_id identifier of the subnet to which hosts belong + virtual ConstHostCollection getAll6(const SubnetID& subnet_id) const; + /// @brief Implementation of @ref CqlHostDataSource::getAll4() /// /// See @ref CqlHostDataSource::getAll4() for parameter details. @@ -1813,6 +1911,40 @@ CqlHostDataSourceImpl::getAll(const Host::IdentifierType& identifier_type, return (result); } +ConstHostCollection +CqlHostDataSourceImpl::getAll4(const SubnetID& subnet_id) const { + // Convert to CQL data types. + cass_int32_t host_ipv4_subnet_id = static_cast(subnet_id); + + // Bind to array. + AnyArray where_values; + where_values.add(&host_ipv4_subnet_id); + + // Run statement. + ConstHostCollection result = + getHostCollection(CqlHostExchange::GET_HOST_BY_IPV4_SUBNET_ID, + where_values); + + return (result); +} + +ConstHostCollection +CqlHostDataSourceImpl::getAll6(const SubnetID& subnet_id) const { + // Convert to CQL data types. + cass_int32_t host_ipv6_subnet_id = static_cast(subnet_id); + + // Bind to array. + AnyArray where_values; + where_values.add(&host_ipv6_subnet_id); + + // Run statement. + ConstHostCollection result = + getHostCollection(CqlHostExchange::GET_HOST_BY_IPV6_SUBNET_ID, + where_values); + + return (result); +} + ConstHostCollection CqlHostDataSourceImpl::getAll4(const asiolink::IOAddress& address) const { // Convert to CQL data types. @@ -2099,6 +2231,20 @@ CqlHostDataSource::getAll(const Host::IdentifierType& identifier_type, return (impl_->getAll(identifier_type, identifier_begin, identifier_len)); } +ConstHostCollection +CqlHostDataSource::getAll4(const SubnetID& subnet_id) const { + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_CQL_HOST_GET_ALL); + + return (impl_->getAll4(subnet_id)); +} + +ConstHostCollection +CqlHostDataSource::getAll6(const SubnetID& subnet_id) const { + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_CQL_HOST_GET_ALL); + + return (impl_->getAll6(subnet_id)); +} + ConstHostCollection CqlHostDataSource::getAll4(const asiolink::IOAddress& address) const { LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_CQL_HOST_GET_ALL); diff --git a/src/lib/dhcpsrv/mysql_host_data_source.cc b/src/lib/dhcpsrv/mysql_host_data_source.cc index fa9dfb2365..448bceca19 100644 --- a/src/lib/dhcpsrv/mysql_host_data_source.cc +++ b/src/lib/dhcpsrv/mysql_host_data_source.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2015-2018 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2015-2019 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -129,7 +129,7 @@ public: bind_(columns_num_), columns_(columns_num_), error_(columns_num_, MLM_FALSE), host_id_(0), dhcp_identifier_length_(0), dhcp_identifier_type_(0), - dhcp4_subnet_id_(SUBNET_ID_UNUSED), + dhcp4_subnet_id_(SUBNET_ID_UNUSED), dhcp6_subnet_id_(SUBNET_ID_UNUSED), ipv4_address_(0), hostname_length_(0), dhcp4_client_classes_length_(0), dhcp6_client_classes_length_(0), @@ -407,7 +407,7 @@ public: auth_key_null_ = auth_key.empty() ? MLM_TRUE : MLM_FALSE; bind_[13].buffer = auth_key_; bind_[13].buffer_length = auth_key.length(); - + } catch (const std::exception& ex) { isc_throw(DbOperationError, "Could not create bind array from Host: " @@ -542,7 +542,7 @@ public: bind_[13].buffer_length = auth_key_length_; bind_[13].length = &auth_key_length_; bind_[13].is_null = &auth_key_null_; - + // Add the error flags setErrorIndicators(bind_, error_); @@ -800,7 +800,7 @@ private: /// The length of the string for holding keys unsigned long auth_key_length_; - + /// @name Boolean values indicating if values of specific columns in /// the database are NULL. //@{ @@ -1951,6 +1951,8 @@ public: DEL_HOST_ADDR4, // Delete v4 host (subnet-id, addr4) DEL_HOST_SUBID4_ID, // Delete v4 host (subnet-id, ident.type, identifier) DEL_HOST_SUBID6_ID, // Delete v6 host (subnet-id, ident.type, identifier) + GET_HOST_SUBID4, // Gets host by IPv4 SubnetID + GET_HOST_SUBID6, // Gets host by IPv6 SubnetID NUM_STATEMENTS // Number of statements }; @@ -2330,7 +2332,41 @@ TaggedStatementArray tagged_statements = { { {MySqlHostDataSourceImpl::DEL_HOST_SUBID6_ID, "DELETE FROM hosts WHERE dhcp6_subnet_id = ? AND dhcp_identifier_type=? " - "AND dhcp_identifier = ?"} + "AND dhcp_identifier = ?"}, + + {MySqlHostDataSourceImpl::GET_HOST_SUBID4, + "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, " + "h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, " + "h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, " + "h.dhcp4_next_server, h.dhcp4_server_hostname, " + "h.dhcp4_boot_file_name, h.auth_key, " + "o.option_id, o.code, o.value, o.formatted_value, o.space, " + "o.persistent, o.user_context " + "FROM hosts AS h " + "LEFT JOIN dhcp4_options AS o " + "ON h.host_id = o.host_id " + "WHERE h.dhcp4_subnet_id = ? " + "ORDER BY h.host_id, o.option_id"}, + + {MySqlHostDataSourceImpl::GET_HOST_SUBID6, + "SELECT h.host_id, h.dhcp_identifier, " + "h.dhcp_identifier_type, h.dhcp4_subnet_id, " + "h.dhcp6_subnet_id, h.ipv4_address, h.hostname, " + "h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, " + + "h.dhcp4_next_server, h.dhcp4_server_hostname, " + "h.dhcp4_boot_file_name, h.auth_key, " + "o.option_id, o.code, o.value, o.formatted_value, o.space, " + "o.persistent, o.user_context, " + "r.reservation_id, r.address, r.prefix_len, r.type, " + "r.dhcp6_iaid " + "FROM hosts AS h " + "LEFT JOIN dhcp6_options AS o " + "ON h.host_id = o.host_id " + "LEFT JOIN ipv6_reservations AS r " + "ON h.host_id = r.host_id " + "WHERE h.dhcp6_subnet_id = ? " + "ORDER BY h.host_id, o.option_id, r.reservation_id"} } }; @@ -2870,6 +2906,40 @@ MySqlHostDataSource::getAll(const Host::IdentifierType& identifier_type, return (result); } +ConstHostCollection +MySqlHostDataSource::getAll4(const SubnetID& subnet_id) const { + // Set up the WHERE clause value + MYSQL_BIND inbind[1]; + memset(inbind, 0, sizeof(inbind)); + uint32_t subnet = subnet_id; + inbind[0].buffer_type = MYSQL_TYPE_LONG; + inbind[0].buffer = reinterpret_cast(&subnet); + inbind[0].is_unsigned = MLM_TRUE; + + ConstHostCollection result; + impl_->getHostCollection(MySqlHostDataSourceImpl::GET_HOST_SUBID4, + inbind, impl_->host_exchange_, + result, false); + return (result); +} + +ConstHostCollection +MySqlHostDataSource::getAll6(const SubnetID& subnet_id) const { + // Set up the WHERE clause value + MYSQL_BIND inbind[1]; + memset(inbind, 0, sizeof(inbind)); + uint32_t subnet = subnet_id; + inbind[0].buffer_type = MYSQL_TYPE_LONG; + inbind[0].buffer = reinterpret_cast(&subnet); + inbind[0].is_unsigned = MLM_TRUE; + + ConstHostCollection result; + impl_->getHostCollection(MySqlHostDataSourceImpl::GET_HOST_SUBID6, + inbind, impl_->host_ipv6_exchange_, + result, false); + return (result); +} + ConstHostCollection MySqlHostDataSource::getAll4(const asiolink::IOAddress& address) const { diff --git a/src/lib/dhcpsrv/mysql_host_data_source.h b/src/lib/dhcpsrv/mysql_host_data_source.h index 73b3058c4e..2d9509710c 100644 --- a/src/lib/dhcpsrv/mysql_host_data_source.h +++ b/src/lib/dhcpsrv/mysql_host_data_source.h @@ -140,7 +140,7 @@ public: /// /// @return Collection of const @ref Host objects. virtual ConstHostCollection - getAll4(const SubnetID& subnet_id) const override; + getAll4(const SubnetID& subnet_id) const; /// @brief Return all hosts in a DHCPv6 subnet. /// @@ -151,7 +151,7 @@ public: /// /// @return Collection of const @ref Host objects. virtual ConstHostCollection - getAll6(const SubnetID& subnet_id) const override; + getAll6(const SubnetID& subnet_id) const; /// @brief Returns a collection of hosts using the specified IPv4 address. /// diff --git a/src/lib/dhcpsrv/pgsql_host_data_source.cc b/src/lib/dhcpsrv/pgsql_host_data_source.cc index 6c2aa8cb5d..0edb189939 100644 --- a/src/lib/dhcpsrv/pgsql_host_data_source.cc +++ b/src/lib/dhcpsrv/pgsql_host_data_source.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2016-2018 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2016-2019 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -257,12 +257,12 @@ public: // add auth keys std::string key = host->getKey().ToText(); - if (key.empty()) { + if (key.empty()) { bind_array->addNull(); } else { bind_array->add(key); } - + } catch (const std::exception& ex) { host_.reset(); isc_throw(DbOperationError, @@ -1293,6 +1293,8 @@ public: DEL_HOST_ADDR4, // Delete v4 host (subnet-id, addr4) DEL_HOST_SUBID4_ID, // Delete v4 host (subnet-id, ident.type, identifier) DEL_HOST_SUBID6_ID, // Delete v6 host (subnet-id, ident.type, identifier) + GET_HOST_SUBID4, // Gets host by IPv4 SubnetID + GET_HOST_SUBID6, // Gets host by IPv6 SubnetID NUM_STATEMENTS // Number of statements }; @@ -1714,6 +1716,50 @@ TaggedStatementArray tagged_statements = { { "DELETE FROM hosts WHERE dhcp6_subnet_id = $1 " "AND dhcp_identifier_type = $2 " "AND dhcp_identifier = $3" + }, + + // PgSqlHostDataSourceImpl::GET_HOST_SUBID4 + // Retrieves host information along with the DHCPv4 options associated with + // it. Left joining the dhcp4_options table results in multiple rows being + // returned for the same host. The host is retrieved by subnet id. + {1, + { OID_INT8 }, "get_host_subid4", + "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, " + " h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, " + " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, " + " h.dhcp4_next_server, h.dhcp4_server_hostname, " + " h.dhcp4_boot_file_name, h.auth_key, " + " o.option_id, o.code, o.value, o.formatted_value, o.space, " + " o.persistent, o.user_context " + "FROM hosts AS h " + "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id " + "WHERE h.dhcp4_subnet_id = $1 " + "ORDER BY h.host_id, o.option_id" + }, + + // PgSqlHostDataSourceImpl::GET_HOST_SUBID6 + // Retrieves host information, IPv6 reservations and DHCPv6 options + // associated with a host using IPv6 subnet id. This query returns + // host information for a single host. However, multiple rows are + // returned due to left joining IPv6 reservations and DHCPv6 options. + // The number of rows returned is multiplication of number of existing + // IPv6 reservations and DHCPv6 options. + {1, + { OID_INT8 }, "get_host_subid6", + "SELECT h.host_id, h.dhcp_identifier, " + " h.dhcp_identifier_type, h.dhcp4_subnet_id, " + " h.dhcp6_subnet_id, h.ipv4_address, h.hostname, " + " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, " + " h.dhcp4_next_server, h.dhcp4_server_hostname, " + " h.dhcp4_boot_file_name, h.auth_key, " + " o.option_id, o.code, o.value, o.formatted_value, o.space, " + " o.persistent, o.user_context, " + " r.reservation_id, r.address, r.prefix_len, r.type, r.dhcp6_iaid " + "FROM hosts AS h " + "LEFT JOIN dhcp6_options AS o ON h.host_id = o.host_id " + "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id " + "WHERE h.dhcp6_subnet_id = $1 " + "ORDER BY h.host_id, o.option_id, r.reservation_id" } } }; @@ -2097,6 +2143,38 @@ PgSqlHostDataSource::getAll(const Host::IdentifierType& identifier_type, return (result); } +ConstHostCollection +PgSqlHostDataSource::getAll4(const SubnetID& subnet_id) const { + // Set up the WHERE clause value + PsqlBindArrayPtr bind_array(new PsqlBindArray()); + + // Add the subnet id. + bind_array->add(subnet_id); + + ConstHostCollection result; + impl_->getHostCollection(PgSqlHostDataSourceImpl::GET_HOST_SUBID4, + bind_array, impl_->host_exchange_, + result, false); + + return (result); +} + +ConstHostCollection +PgSqlHostDataSource::getAll6(const SubnetID& subnet_id) const { + // Set up the WHERE clause value + PsqlBindArrayPtr bind_array(new PsqlBindArray()); + + // Add the subnet id. + bind_array->add(subnet_id); + + ConstHostCollection result; + impl_->getHostCollection(PgSqlHostDataSourceImpl::GET_HOST_SUBID6, + bind_array, impl_->host_ipv6_exchange_, + result, false); + + return (result); +} + ConstHostCollection PgSqlHostDataSource::getAll4(const asiolink::IOAddress& address) const { diff --git a/src/lib/dhcpsrv/pgsql_host_data_source.h b/src/lib/dhcpsrv/pgsql_host_data_source.h index f829d973bb..e93f4f9e1f 100644 --- a/src/lib/dhcpsrv/pgsql_host_data_source.h +++ b/src/lib/dhcpsrv/pgsql_host_data_source.h @@ -167,7 +167,7 @@ public: /// /// @return Collection of const @ref Host objects. virtual ConstHostCollection - getAll4(const SubnetID& subnet_id) const override; + getAll4(const SubnetID& subnet_id) const; /// @brief Return all hosts in a DHCPv6 subnet. /// @@ -178,7 +178,7 @@ public: /// /// @return Collection of const @ref Host objects. virtual ConstHostCollection - getAll6(const SubnetID& subnet_id) const override; + getAll6(const SubnetID& subnet_id) const; /// @brief Returns a collection of hosts using the specified IPv4 address. /// diff --git a/src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc b/src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc index b105750b4f..010550a5ae 100644 --- a/src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc +++ b/src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc @@ -206,6 +206,64 @@ TEST_F(CfgHostsTest, getAllRepeatingHosts) { } } +// This test checks that hosts in the same subnet can be retrieved from +// the host configuration. +TEST_F(CfgHostsTest, getAll4BySubnet) { + CfgHosts cfg; + // Add 25 hosts identified by HW address in the same subnet. + for (unsigned i = 0; i < 25; ++i) { + cfg.add(HostPtr(new Host(hwaddrs_[i]->toText(false), + "hw-address", + SubnetID(1), SubnetID(1), + addressesa_[i]))); + } + + // Check that other subnets are empty. + HostCollection hosts = cfg.getAll4(SubnetID(100)); + EXPECT_EQ(0, hosts.size()); + + // Try to retrieve all added reservations. + hosts = cfg.getAll4(SubnetID(1)); + ASSERT_EQ(25, hosts.size()); + for (unsigned i = 0; i < 25; ++i) { + EXPECT_EQ(1, hosts[i]->getIPv4SubnetID()); + EXPECT_EQ(addressesa_[i].toText(), + hosts[i]->getIPv4Reservation().toText()); + } +} + +// This test checks that hosts in the same subnet can be retrieved from +// the host configuration. +TEST_F(CfgHostsTest, getAll6BySubnet) { + CfgHosts cfg; + // Add 25 hosts identified by DUID in the same subnet. + for (unsigned i = 0; i < 25; ++i) { + HostPtr host = HostPtr(new Host(duids_[i]->toText(), "duid", + SubnetID(1), SubnetID(1), + IOAddress("0.0.0.0"))); + host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA, + increase(IOAddress("2001:db8:1::1"), + i))); + cfg.add(host); + } + + // Check that other subnets are empty. + HostCollection hosts = cfg.getAll6(SubnetID(100)); + EXPECT_EQ(0, hosts.size()); + + // Try to retrieve all added reservations. + hosts = cfg.getAll6(SubnetID(1)); + ASSERT_EQ(25, hosts.size()); + for (unsigned i = 0; i < 25; ++i) { + EXPECT_EQ(1, hosts[i]->getIPv6SubnetID()); + IPv6ResrvRange reservations = + hosts[i]->getIPv6Reservations(IPv6Resrv::TYPE_NA); + ASSERT_EQ(1, std::distance(reservations.first, reservations.second)); + EXPECT_EQ(increase(IOAddress("2001:db8:1::1"), i), + reservations.first->second.getPrefix()); + } +} + // This test checks that all reservations for the specified IPv4 address can // be retrieved. TEST_F(CfgHostsTest, getAll4ByAddress) { diff --git a/src/lib/dhcpsrv/tests/cql_host_data_source_unittest.cc b/src/lib/dhcpsrv/tests/cql_host_data_source_unittest.cc index 5900136d48..7380f95010 100644 --- a/src/lib/dhcpsrv/tests/cql_host_data_source_unittest.cc +++ b/src/lib/dhcpsrv/tests/cql_host_data_source_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2017-2018 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2017-2019 Internet Systems Consortium, Inc. ("ISC") // Copyright (C) 2016-2017 Deutsche Telekom AG. // // Author: Andrei Pavel @@ -293,6 +293,16 @@ TEST_F(CqlHostDataSourceTest, DISABLED_testReadOnlyDatabase) { testReadOnlyDatabase(CQL_VALID_TYPE); } +// Verifies that IPv4 host reservations in the same subnet can be retrieved +TEST_F(CqlHostDataSourceTest, getAll4BySubnet) { + testGetAll4(Host::IDENT_HWADDR); +} + +// Verifies that IPv6 host reservations in the same subnet can be retrieved +TEST_F(CqlHostDataSourceTest, getAll6BySubnet) { + testGetAll6(Host::IDENT_DUID); +} + // Test verifies if a host reservation can be added and later retrieved by IPv4 // address. Host uses hw address as identifier. TEST_F(CqlHostDataSourceTest, basic4HWAddr) { diff --git a/src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc b/src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc index 32c0272ec3..7fb48610a1 100644 --- a/src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc +++ b/src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2015-2018 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2015-2019 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -311,6 +311,16 @@ TEST_F(MySqlHostDataSourceTest, maxSubnetId6) { testMaxSubnetId6(); } +// Verifies that IPv4 host reservations in the same subnet can be retrieved +TEST_F(MySqlHostDataSourceTest, getAll4BySubnet) { + testGetAll4(Host::IDENT_HWADDR); +} + +// Verifies that IPv6 host reservations in the same subnet can be retrieved +TEST_F(MySqlHostDataSourceTest, getAll6BySubnet) { + testGetAll6(Host::IDENT_DUID); +} + // Test verifies if a host reservation can be added and later retrieved by IPv4 // address. Host uses client-id (DUID) as identifier. TEST_F(MySqlHostDataSourceTest, basic4ClientId) { diff --git a/src/lib/dhcpsrv/tests/pgsql_host_data_source_unittest.cc b/src/lib/dhcpsrv/tests/pgsql_host_data_source_unittest.cc index 89bd6db260..22bd07fa67 100644 --- a/src/lib/dhcpsrv/tests/pgsql_host_data_source_unittest.cc +++ b/src/lib/dhcpsrv/tests/pgsql_host_data_source_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2016-2018 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2016-2019 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -296,6 +296,16 @@ TEST_F(PgSqlHostDataSourceTest, maxSubnetId6) { testMaxSubnetId6(); } +// Verifies that IPv4 host reservations in the same subnet can be retrieved +TEST_F(PgSqlHostDataSourceTest, getAll4BySubnet) { + testGetAll4(Host::IDENT_HWADDR); +} + +// Verifies that IPv6 host reservations in the same subnet can be retrieved +TEST_F(PgSqlHostDataSourceTest, getAll6BySubnet) { + testGetAll6(Host::IDENT_DUID); +} + // Test verifies if a host reservation can be added and later retrieved by IPv4 // address. Host uses client-id (DUID) as identifier. TEST_F(PgSqlHostDataSourceTest, basic4ClientId) { diff --git a/src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.cc b/src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.cc index 8a53dd5930..e1530a0aec 100644 --- a/src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.cc +++ b/src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.cc @@ -356,6 +356,92 @@ void GenericHostDataSourceTest::testMaxSubnetId6() { EXPECT_FALSE(host_by_id); } +void +GenericHostDataSourceTest::testGetAll4(const Host::IdentifierType& id) { + // Make sure we have a pointer to the host data source. + ASSERT_TRUE(hdsptr_); + + // Let's create a couple of hosts... + HostPtr host1 = HostDataSourceUtils::initializeHost4("192.0.2.1", id); + HostPtr host2 = HostDataSourceUtils::initializeHost4("192.0.2.2", id); + HostPtr host3 = HostDataSourceUtils::initializeHost4("192.0.2.3", id); + HostPtr host4 = HostDataSourceUtils::initializeHost4("192.0.2.4", id); + + // Set them in the same subnets. + SubnetID subnet4 = host1->getIPv4SubnetID(); + host2->setIPv4SubnetID(subnet4); + host3->setIPv4SubnetID(subnet4); + host4->setIPv4SubnetID(subnet4); + SubnetID subnet6 = host1->getIPv6SubnetID(); + host2->setIPv6SubnetID(subnet6); + host3->setIPv6SubnetID(subnet6); + host4->setIPv6SubnetID(subnet6); + + // ... and add them to the data source. + ASSERT_NO_THROW(hdsptr_->add(host1)); + ASSERT_NO_THROW(hdsptr_->add(host2)); + ASSERT_NO_THROW(hdsptr_->add(host3)); + ASSERT_NO_THROW(hdsptr_->add(host4)); + + // And then try to retrieve them back. + ConstHostCollection from_hds = hdsptr_->getAll4(subnet4); + + // Make sure we got something back. + ASSERT_EQ(4, from_hds.size()); + + // Then let's check that what we got seems correct. + // There is no ORDER BY in Cassandra so skip it. + if (hdsptr_->getType() != "cql") { + HostDataSourceUtils::compareHosts(host1, from_hds[0]); + HostDataSourceUtils::compareHosts(host2, from_hds[1]); + HostDataSourceUtils::compareHosts(host3, from_hds[2]); + HostDataSourceUtils::compareHosts(host4, from_hds[3]); + } +} + +void +GenericHostDataSourceTest::testGetAll6(const Host::IdentifierType& id) { + // Make sure we have a pointer to the host data source. + ASSERT_TRUE(hdsptr_); + + // Let's create a couple of hosts... + HostPtr host1 = HostDataSourceUtils::initializeHost6("2001:db8::1", id, false); + HostPtr host2 = HostDataSourceUtils::initializeHost6("2001:db8::2", id, false); + HostPtr host3 = HostDataSourceUtils::initializeHost6("2001:db8::3", id, false); + HostPtr host4 = HostDataSourceUtils::initializeHost6("2001:db8::4", id, false); + + // Set them in the same subnets. + SubnetID subnet4 = host1->getIPv4SubnetID(); + host2->setIPv4SubnetID(subnet4); + host3->setIPv4SubnetID(subnet4); + host4->setIPv4SubnetID(subnet4); + SubnetID subnet6 = host1->getIPv6SubnetID(); + host2->setIPv6SubnetID(subnet6); + host3->setIPv6SubnetID(subnet6); + host4->setIPv6SubnetID(subnet6); + + // ... and add them to the data source. + ASSERT_NO_THROW(hdsptr_->add(host1)); + ASSERT_NO_THROW(hdsptr_->add(host2)); + ASSERT_NO_THROW(hdsptr_->add(host3)); + ASSERT_NO_THROW(hdsptr_->add(host4)); + + // And then try to retrieve them back. + ConstHostCollection from_hds = hdsptr_->getAll6(subnet6); + + // Make sure we got something back. + ASSERT_EQ(4, from_hds.size()); + + // Then let's check that what we got seems correct. + // There is no ORDER BY in Cassandra so skip it. + if (hdsptr_->getType() != "cql") { + HostDataSourceUtils::compareHosts(host1, from_hds[0]); + HostDataSourceUtils::compareHosts(host2, from_hds[1]); + HostDataSourceUtils::compareHosts(host3, from_hds[2]); + HostDataSourceUtils::compareHosts(host4, from_hds[3]); + } +} + void GenericHostDataSourceTest::testGetByIPv4(const Host::IdentifierType& id) { // Make sure we have a pointer to the host data source. @@ -1024,8 +1110,8 @@ void GenericHostDataSourceTest::testOptionsReservations4(const bool formatted, // getAll4(subnet_id) ConstHostCollection hosts_by_subnet = hdsptr_->getAll4(subnet_id); - // Not yet implemented. - EXPECT_EQ(0, hosts_by_subnet.size()); + ASSERT_EQ(1, hosts_by_subnet.size()); + ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, *hosts_by_subnet.begin())); // getAll4(address) ConstHostCollection hosts_by_addr = @@ -1079,8 +1165,8 @@ GenericHostDataSourceTest::testOptionsReservations46(const bool formatted) { // getAll6(subnet_id) ConstHostCollection hosts_by_subnet = hdsptr_->getAll6(subnet_id); - // Not yet implemented. - EXPECT_EQ(0, hosts_by_subnet.size()); + EXPECT_EQ(1, hosts_by_subnet.size()); + // Don't compare as getAll6() returns the v6 part only. // getAll(identifier_type, identifier, identifier_size) ConstHostCollection hosts_by_id = diff --git a/src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.h b/src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.h index 935b8922c3..c6cc028c19 100644 --- a/src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.h +++ b/src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.h @@ -1,4 +1,4 @@ -// Copyright (C) 2015-2018 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2015-2019 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -178,6 +178,20 @@ public: /// Uses gtest macros to report failures. void testMaxSubnetId6(); + /// @brief Test that Verifies that IPv4 host reservations in the + /// same subnet can be retrieved properly. + /// + /// Uses gtest macros to report failures. + /// @param id Identifier type. + void testGetAll4(const Host::IdentifierType& id); + + /// @brief Test that Verifies that IPv6 host reservations in the + /// same subnet can be retrieved properly. + /// + /// Uses gtest macros to report failures. + /// @param id Identifier type. + void testGetAll6(const Host::IdentifierType& id); + /// @brief Test inserts several hosts with unique IPv4 address and /// checks that they can be retrieved properly. /// diff --git a/src/lib/dhcpsrv/testutils/memory_host_data_source.cc b/src/lib/dhcpsrv/testutils/memory_host_data_source.cc index 3c28949d5f..fd07827c65 100644 --- a/src/lib/dhcpsrv/testutils/memory_host_data_source.cc +++ b/src/lib/dhcpsrv/testutils/memory_host_data_source.cc @@ -23,13 +23,27 @@ MemHostDataSource::getAll(const Host::IdentifierType& /*identifier_type*/, } ConstHostCollection -MemHostDataSource::getAll4(const SubnetID& /*subnet_id*/) const { - return (ConstHostCollection()); +MemHostDataSource::getAll4(const SubnetID& subnet_id) const { + ConstHostCollection hosts; + for (auto h = store_.begin(); h != store_.end(); ++h) { + // Keep it when subnet_id matchs. + if ((*h)->getIPv4SubnetID() == subnet_id) { + hosts.push_back(*h); + } + } + return (hosts); } ConstHostCollection -MemHostDataSource::getAll6(const SubnetID& /*subnet_id*/) const { - return (ConstHostCollection()); +MemHostDataSource::getAll6(const SubnetID& subnet_id) const { + ConstHostCollection hosts; + for (auto h = store_.begin(); h != store_.end(); ++h) { + // Keep it when subnet_id matchs. + if ((*h)->getIPv6SubnetID() == subnet_id) { + hosts.push_back(*h); + } + } + return (hosts); } ConstHostCollection @@ -129,6 +143,7 @@ MemHostDataSource::get6(const SubnetID& subnet_id, void MemHostDataSource::add(const HostPtr& host) { + host->setHostId(++next_host_id_); store_.push_back(host); } diff --git a/src/lib/dhcpsrv/testutils/memory_host_data_source.h b/src/lib/dhcpsrv/testutils/memory_host_data_source.h index 380a6dba23..370e5fe6eb 100644 --- a/src/lib/dhcpsrv/testutils/memory_host_data_source.h +++ b/src/lib/dhcpsrv/testutils/memory_host_data_source.h @@ -48,16 +48,12 @@ public: /// @brief Return all hosts in a DHCPv4 subnet. /// - /// Currently not implemented. - /// /// @param subnet_id Subnet identifier. virtual ConstHostCollection getAll4(const SubnetID& subnet_id) const; /// @brief Return all hosts in a DHCPv6 subnet. /// - /// Currently not implemented. - /// /// @param subnet_id Subnet identifier. virtual ConstHostCollection getAll6(const SubnetID& subnet_id) const; @@ -211,6 +207,9 @@ protected: /// @brief Store std::vector store_; + + /// @brief Next host id + uint64_t next_host_id_; }; /// Pointer to the Mem host data source. -- 2.47.2