]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#2753] Rebased with new schemas
authorFrancis Dupont <fdupont@isc.org>
Thu, 30 Mar 2023 20:51:46 +0000 (22:51 +0200)
committerFrancis Dupont <fdupont@isc.org>
Fri, 31 Mar 2023 12:29:41 +0000 (14:29 +0200)
src/lib/dhcpsrv/dhcpsrv_messages.mes
src/lib/dhcpsrv/mysql_lease_mgr.cc
src/lib/dhcpsrv/mysql_lease_mgr.h
src/lib/dhcpsrv/pgsql_lease_mgr.cc
src/lib/dhcpsrv/pgsql_lease_mgr.h
src/lib/dhcpsrv/tests/Makefile.am
src/lib/dhcpsrv/tests/mysql_lease_extended_info_unittest.cc [new file with mode: 0644]
src/lib/dhcpsrv/tests/pgsql_lease_extended_info_unittest.cc [new file with mode: 0644]

index 643b0b74c98103b465d91e9f01e4e8abf4d7605c..f9f15d034ecc40da26a543ee753ae3d0b28f6a3a 100644 (file)
@@ -865,6 +865,16 @@ of leases beginning with the specified address.
 A debug message issued when the server is attempting to obtain a page
 of leases beginning with the specified address.
 
+% DHCPSRV_MYSQL_GET_RELAYID4 obtaining at most %1 IPv4 leases starting from address %2 with relay id %3 and cltt between %4 and %5
+A debug message issued when the server is attempting to obtain a page of
+IPv4 leases beginning with the specified address with a relay id and client
+transaction time between start and end dates.
+
+% DHCPSRV_MYSQL_GET_REMOTEID4 obtaining at most %1 IPv4 leases starting from address %2 with remote id %3 and cltt between %4 and %5
+A debug message issued when the server is attempting to obtain a page of
+IPv4 leases beginning with the specified address with a remote id and client
+transaction time between start and end dates.
+
 % DHCPSRV_MYSQL_GET_SUBID4 obtaining IPv4 leases for subnet ID %1
 A debug message issued when the server is attempting to obtain all IPv4
 leases for a given subnet identifier from the MySQL database.
@@ -1097,6 +1107,16 @@ of leases beginning with the specified address.
 A debug message issued when the server is attempting to obtain a page
 of leases beginning with the specified address.
 
+% DHCPSRV_PGSQL_GET_RELAYID4 obtaining at most %1 IPv4 leases starting from address %2 with relay id %3 and cltt between %4 and %5
+A debug message issued when the server is attempting to obtain a page of
+IPv4 leases beginning with the specified address with a relay id and client
+transaction time between start and end dates.
+
+% DHCPSRV_PGSQL_GET_REMOTEID4 obtaining at most %1 IPv4 leases starting from address %2 with remote id %3 and cltt between %4 and %5
+A debug message issued when the server is attempting to obtain a page of
+IPv4 leases beginning with the specified address with a remote id and client
+transaction time between start and end dates.
+
 % DHCPSRV_PGSQL_GET_SUBID4 obtaining IPv4 leases for subnet ID %1
 A debug message issued when the server is attempting to obtain all IPv4
 leases for a given subnet identifier from the PostgreSQL database.
index 7175bec17186ca7e2e297656ca00fe2b78716c12..3e5d61f7ff93cd285099cf7b318fdea9475bf62d 100644 (file)
@@ -116,48 +116,48 @@ tagged_statements = { {
                     "SELECT address, hwaddr, client_id, "
                         "valid_lifetime, expire, subnet_id, "
                         "fqdn_fwd, fqdn_rev, hostname, "
-                        "state, user_context "
+                        "state, user_context, relay_id, remote_id "
                             "FROM lease4"},
     {MySqlLeaseMgr::GET_LEASE4_ADDR,
                     "SELECT address, hwaddr, client_id, "
                         "valid_lifetime, expire, subnet_id, "
                         "fqdn_fwd, fqdn_rev, hostname, "
-                        "state, user_context "
+                        "state, user_context, relay_id, remote_id "
                             "FROM lease4 "
                             "WHERE address = ?"},
     {MySqlLeaseMgr::GET_LEASE4_CLIENTID,
                     "SELECT address, hwaddr, client_id, "
                         "valid_lifetime, expire, subnet_id, "
                         "fqdn_fwd, fqdn_rev, hostname, "
-                        "state, user_context "
+                        "state, user_context, relay_id, remote_id "
                             "FROM lease4 "
                             "WHERE client_id = ?"},
     {MySqlLeaseMgr::GET_LEASE4_CLIENTID_SUBID,
                     "SELECT address, hwaddr, client_id, "
                         "valid_lifetime, expire, subnet_id, "
                         "fqdn_fwd, fqdn_rev, hostname, "
-                        "state, user_context "
+                        "state, user_context, relay_id, remote_id "
                             "FROM lease4 "
                             "WHERE client_id = ? AND subnet_id = ?"},
     {MySqlLeaseMgr::GET_LEASE4_HWADDR,
                     "SELECT address, hwaddr, client_id, "
                         "valid_lifetime, expire, subnet_id, "
                         "fqdn_fwd, fqdn_rev, hostname, "
-                        "state, user_context "
+                        "state, user_context, relay_id, remote_id "
                             "FROM lease4 "
                             "WHERE hwaddr = ?"},
     {MySqlLeaseMgr::GET_LEASE4_HWADDR_SUBID,
                     "SELECT address, hwaddr, client_id, "
                         "valid_lifetime, expire, subnet_id, "
                         "fqdn_fwd, fqdn_rev, hostname, "
-                        "state, user_context "
+                        "state, user_context, relay_id, remote_id "
                             "FROM lease4 "
                             "WHERE hwaddr = ? AND subnet_id = ?"},
     {MySqlLeaseMgr::GET_LEASE4_PAGE,
                     "SELECT address, hwaddr, client_id, "
                         "valid_lifetime, expire, subnet_id, "
                         "fqdn_fwd, fqdn_rev, hostname, "
-                        "state, user_context "
+                        "state, user_context, relay_id, remote_id "
                             "FROM lease4 "
                             "WHERE address > ? "
                             "ORDER BY address "
@@ -166,27 +166,123 @@ tagged_statements = { {
                     "SELECT address, hwaddr, client_id, "
                         "valid_lifetime, expire, subnet_id, "
                         "fqdn_fwd, fqdn_rev, hostname, "
-                        "state, user_context "
+                        "state, user_context, relay_id, remote_id "
                             "FROM lease4 "
                             "WHERE subnet_id = ?"},
     {MySqlLeaseMgr::GET_LEASE4_HOSTNAME,
                     "SELECT address, hwaddr, client_id, "
                         "valid_lifetime, expire, subnet_id, "
                         "fqdn_fwd, fqdn_rev, hostname, "
-                        "state, user_context "
+                        "state, user_context, relay_id, remote_id "
                             "FROM lease4 "
                             "WHERE hostname = ?"},
     {MySqlLeaseMgr::GET_LEASE4_EXPIRE,
                     "SELECT address, hwaddr, client_id, "
                         "valid_lifetime, expire, subnet_id, "
                         "fqdn_fwd, fqdn_rev, hostname, "
-                        "state, user_context "
+                        "state, user_context, relay_id, remote_id "
                             "FROM lease4 "
                             "WHERE state != ? "
                             "AND valid_lifetime != 4294967295 "
                             "AND expire < ? "
                             "ORDER BY expire ASC "
                             "LIMIT ?"},
+    {MySqlLeaseMgr::GET_LEASE4_RELAYID,
+                    "SELECT address, hwaddr, client_id, "
+                        "valid_lifetime, expire, subnet_id, "
+                        "fqdn_fwd, fqdn_rev, hostname, "
+                        "state, user_context, relay_id, remote_id "
+                            "FROM lease4 "
+                            "WHERE relay_id = ? and address > ? "
+                            "ORDER BY address "
+                            "LIMIT ?"},
+    {MySqlLeaseMgr::GET_LEASE4_RELAYID_QST,
+                    "SELECT address, hwaddr, client_id, "
+                        "valid_lifetime, expire, subnet_id, "
+                        "fqdn_fwd, fqdn_rev, hostname, "
+                        "state, user_context, relay_id, remote_id "
+                            "FROM lease4 "
+                            "WHERE relay_id = ? and address > ? "
+                            " and UNIX_TIMESTAMP(expire) - IF"
+                             "(valid_lifetime = 4294967295, 0, valid_lifetime)"
+                             " >= ? "
+                            "ORDER BY address "
+                            "LIMIT ?"},
+    {MySqlLeaseMgr::GET_LEASE4_RELAYID_QSET,
+                    "SELECT address, hwaddr, client_id, "
+                        "valid_lifetime, expire, subnet_id, "
+                        "fqdn_fwd, fqdn_rev, hostname, "
+                        "state, user_context, relay_id, remote_id "
+                            "FROM lease4 "
+                            "WHERE relay_id = ? and address > ? "
+                            " and UNIX_TIMESTAMP(expire) - IF"
+                             "(valid_lifetime = 4294967295, 0, valid_lifetime)"
+                             " >= ? "
+                            " and UNIX_TIMESTAMP(expire) - IF"
+                             "(valid_lifetime = 4294967295, 0, valid_lifetime)"
+                             " <= ? "
+                            "ORDER BY address "
+                            "LIMIT ?"},
+    {MySqlLeaseMgr::GET_LEASE4_RELAYID_QET,
+                    "SELECT address, hwaddr, client_id, "
+                        "valid_lifetime, expire, subnet_id, "
+                        "fqdn_fwd, fqdn_rev, hostname, "
+                        "state, user_context, relay_id, remote_id "
+                            "FROM lease4 "
+                            "WHERE relay_id = ? and address > ? "
+                            " and UNIX_TIMESTAMP(expire) - IF"
+                             "(valid_lifetime = 4294967295, 0, valid_lifetime)"
+                             " <= ? "
+                            "ORDER BY address "
+                            "LIMIT ?"},
+    {MySqlLeaseMgr::GET_LEASE4_REMOTEID,
+                    "SELECT address, hwaddr, client_id, "
+                        "valid_lifetime, expire, subnet_id, "
+                        "fqdn_fwd, fqdn_rev, hostname, "
+                        "state, user_context, relay_id, remote_id "
+                            "FROM lease4 "
+                            "WHERE remote_id = ? and address > ? "
+                            "ORDER BY address "
+                            "LIMIT ?"},
+    {MySqlLeaseMgr::GET_LEASE4_REMOTEID_QST,
+                    "SELECT address, hwaddr, client_id, "
+                        "valid_lifetime, expire, subnet_id, "
+                        "fqdn_fwd, fqdn_rev, hostname, "
+                        "state, user_context, relay_id, remote_id "
+                            "FROM lease4 "
+                            "WHERE remote_id = ? and address > ? "
+                            " and UNIX_TIMESTAMP(expire) - IF"
+                             "(valid_lifetime = 4294967295, 0, valid_lifetime)"
+                             " >= ? "
+                            "ORDER BY address "
+                            "LIMIT ?"},
+    {MySqlLeaseMgr::GET_LEASE4_REMOTEID_QSET,
+                    "SELECT address, hwaddr, client_id, "
+                        "valid_lifetime, expire, subnet_id, "
+                        "fqdn_fwd, fqdn_rev, hostname, "
+                        "state, user_context, relay_id, remote_id "
+                            "FROM lease4 "
+                            "WHERE remote_id = ? and address > ? "
+                            " and UNIX_TIMESTAMP(expire) - IF"
+                             "(valid_lifetime = 4294967295, 0, valid_lifetime)"
+                             " >= ? "
+                            " and UNIX_TIMESTAMP(expire) - IF"
+                             "(valid_lifetime = 4294967295, 0, valid_lifetime)"
+                             " <= ? "
+                            "ORDER BY address "
+                            "LIMIT ?"},
+    {MySqlLeaseMgr::GET_LEASE4_REMOTEID_QET,
+                    "SELECT address, hwaddr, client_id, "
+                        "valid_lifetime, expire, subnet_id, "
+                        "fqdn_fwd, fqdn_rev, hostname, "
+                        "state, user_context, relay_id, remote_id "
+                            "FROM lease4 "
+                            "WHERE remote_id = ? and address > ? "
+                            " and UNIX_TIMESTAMP(expire) - IF"
+                             "(valid_lifetime = 4294967295, 0, valid_lifetime)"
+                             " <= ? "
+                            "ORDER BY address "
+                            "LIMIT ?"},
     {MySqlLeaseMgr::GET_LEASE6,
                     "SELECT address, duid, valid_lifetime, "
                         "expire, subnet_id, pref_lifetime, "
@@ -278,8 +374,8 @@ tagged_statements = { {
                     "INSERT INTO lease4(address, hwaddr, client_id, "
                         "valid_lifetime, expire, subnet_id, "
                         "fqdn_fwd, fqdn_rev, hostname, "
-                        "state, user_context) "
-                            "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"},
+                        "state, user_context, relay_id, remote_id) "
+                            "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"},
     {MySqlLeaseMgr::INSERT_LEASE6,
                     "INSERT INTO lease6(address, duid, valid_lifetime, "
                         "expire, subnet_id, pref_lifetime, "
@@ -293,7 +389,8 @@ tagged_statements = { {
                         "client_id = ?, valid_lifetime = ?, expire = ?, "
                         "subnet_id = ?, fqdn_fwd = ?, fqdn_rev = ?, "
                         "hostname = ?, "
-                        "state = ?, user_context = ? "
+                        "state = ?, user_context = ?, "
+                        "relay_id = ?, remote_id = ? "
                             "WHERE address = ? AND expire = ?"},
     {MySqlLeaseMgr::UPDATE_LEASE6,
                     "UPDATE lease6 SET address = ?, duid = ?, "
@@ -426,7 +523,7 @@ public:
 
 class MySqlLease4Exchange : public MySqlLeaseExchange {
     /// @brief Set number of database columns for this lease structure
-    static const size_t LEASE_COLUMNS = 11;
+    static const size_t LEASE_COLUMNS = 13;
 
 public:
 
@@ -439,11 +536,15 @@ public:
                             subnet_id_(0), valid_lifetime_(0),
                             fqdn_fwd_(false), fqdn_rev_(false), hostname_length_(0),
                             state_(0), user_context_length_(0),
-                            user_context_null_(MLM_FALSE) {
+                            user_context_null_(MLM_FALSE),
+                            relay_id_null_(MLM_FALSE),
+                            remote_id_null_(MLM_FALSE) {
         memset(hwaddr_buffer_, 0, sizeof(hwaddr_buffer_));
         memset(client_id_buffer_, 0, sizeof(client_id_buffer_));
         memset(hostname_buffer_, 0, sizeof(hostname_buffer_));
         memset(user_context_, 0, sizeof(user_context_));
+        memset(relay_id_buffer_, 0, sizeof(relay_id_buffer_));
+        memset(remote_id_buffer_, 0, sizeof(remote_id_buffer_));
         std::fill(&error_[0], &error_[LEASE_COLUMNS], MLM_FALSE);
 
         // Set the column names (for error messages)
@@ -458,7 +559,9 @@ public:
         columns_[8] = "hostname";
         columns_[9] = "state";
         columns_[10] = "user_context";
-        BOOST_STATIC_ASSERT(10 < LEASE_COLUMNS);
+        columns_[11] = "relay_id";
+        columns_[12] = "remote_id";
+        BOOST_STATIC_ASSERT(12 < LEASE_COLUMNS);
     }
 
     /// @brief Create MYSQL_BIND objects for Lease4 Pointer
@@ -639,6 +742,36 @@ public:
                 bind_[10].buffer_type = MYSQL_TYPE_NULL;
             }
 
+            // relay_id: varbinary(128)
+            relay_id_ = lease_->relay_id_;
+            if (!relay_id_.empty()) {
+                bind_[11].buffer_type = MYSQL_TYPE_BLOB;
+                bind_[11].buffer = reinterpret_cast<char*>(&relay_id_[0]);
+                relay_id_length_ = relay_id_.size();
+                bind_[11].buffer_length = relay_id_length_;
+                bind_[11].length = &relay_id_length_;
+            } else {
+                bind_[11].buffer_type = MYSQL_TYPE_NULL;
+                relay_id_null_ = MLM_TRUE;
+                bind_[11].buffer = NULL;
+                bind_[11].is_null = &relay_id_null_;
+            }
+
+            // remote_id: varbinary(128)
+            remote_id_ = lease_->remote_id_;
+            if (!remote_id_.empty()) {
+                bind_[12].buffer_type = MYSQL_TYPE_BLOB;
+                bind_[12].buffer = reinterpret_cast<char*>(&remote_id_[0]);
+                remote_id_length_ = remote_id_.size();
+                bind_[12].buffer_length = remote_id_length_;
+                bind_[12].length = &remote_id_length_;
+            } else {
+                bind_[12].buffer_type = MYSQL_TYPE_NULL;
+                remote_id_null_ = MLM_TRUE;
+                bind_[12].buffer = NULL;
+                bind_[12].is_null = &remote_id_null_;
+            }
+
             // Add the error flags
             setErrorIndicators(bind_, error_, LEASE_COLUMNS);
 
@@ -759,6 +892,22 @@ public:
         bind_[10].length = &user_context_length_;
         bind_[10].is_null = &user_context_null_;
 
+        // relay_id: varbinary(128)
+        relay_id_length_ = sizeof(relay_id_buffer_);
+        bind_[11].buffer_type = MYSQL_TYPE_BLOB;
+        bind_[11].buffer = reinterpret_cast<char*>(relay_id_buffer_);
+        bind_[11].buffer_length = relay_id_length_;
+        bind_[11].length = &relay_id_length_;
+        bind_[11].is_null = &relay_id_null_;
+
+        // remote_id: varbinary(128)
+        remote_id_length_ = sizeof(remote_id_buffer_);
+        bind_[12].buffer_type = MYSQL_TYPE_BLOB;
+        bind_[12].buffer = reinterpret_cast<char*>(remote_id_buffer_);
+        bind_[12].buffer_length = remote_id_length_;
+        bind_[12].length = &remote_id_length_;
+        bind_[12].is_null = &remote_id_null_;
+
         // Add the error flags
         setErrorIndicators(bind_, error_, LEASE_COLUMNS);
 
@@ -837,6 +986,18 @@ public:
             lease->setContext(ctx);
         }
 
+        // Set relay id if it was set.
+        if (relay_id_null_ == MLM_FALSE) {
+            lease->relay_id_.assign(relay_id_buffer_,
+                                    relay_id_buffer_ + relay_id_length_);
+        }
+
+        // Set remote id if it was set.
+        if (remote_id_null_ == MLM_FALSE) {
+            lease->remote_id_.assign(remote_id_buffer_,
+                                     remote_id_buffer_ + remote_id_length_);
+        }
+
         return (lease);
     }
 
@@ -883,6 +1044,14 @@ private:
     char                 user_context_[USER_CONTEXT_MAX_LEN];            ///< User context
     unsigned long        user_context_length_;                           ///< Length of user context
     my_bool              user_context_null_;                             ///< Used when user context is null
+    std::vector<uint8_t> relay_id_;                                      ///< Relay id
+    uint8_t              relay_id_buffer_[ClientId::MAX_CLIENT_ID_LEN];  ///< Relay id buffer
+    unsigned long        relay_id_length_;                               ///< Relay id length
+    my_bool              relay_id_null_;                                 ///< Used when Relay id is null
+    std::vector<uint8_t> remote_id_;                                     ///< Remote id
+    uint8_t              remote_id_buffer_[ClientId::MAX_CLIENT_ID_LEN]; ///< Remote id buffer
+    unsigned long        remote_id_length_;                              ///< Remote id length
+    my_bool              remote_id_null_;                                ///< Used when Remote id is null
 };
 
 /// @brief Exchange MySQL and Lease6 Data
@@ -2487,9 +2656,9 @@ MySqlLeaseMgr::getLeases4(const IOAddress& lower_bound_address,
     inbind[0].is_unsigned = MLM_TRUE;
 
     // Bind page size value
-    size_t* ps = const_cast<size_t*>(&page_size.page_size_);
+    uint32_t ps = static_cast<uint32_t>(page_size.page_size_);
     inbind[1].buffer_type = MYSQL_TYPE_LONG;
-    inbind[1].buffer = reinterpret_cast<char*>(ps);
+    inbind[1].buffer = reinterpret_cast<char*>(&ps);
     inbind[1].is_unsigned = MLM_TRUE;
 
     // Get the leases
@@ -2784,9 +2953,9 @@ MySqlLeaseMgr::getLeases6(const IOAddress& lower_bound_address,
     inbind[0].length = &lb_address_data_size;
 
     // Bind page size value
-    size_t* ps = const_cast<size_t*>(&page_size.page_size_);
+    uint32_t ps = static_cast<uint32_t>(page_size.page_size_);
     inbind[1].buffer_type = MYSQL_TYPE_LONG;
-    inbind[1].buffer = reinterpret_cast<char*>(ps);
+    inbind[1].buffer = reinterpret_cast<char*>(&ps);
     inbind[1].is_unsigned = MLM_TRUE;
 
     // Get the leases
@@ -3461,22 +3630,249 @@ MySqlLeaseMgr::addRemoteId6(const IOAddress& /* lease_addr */,
     isc_throw(NotImplemented, "MySqlLeaseMgr::addRemoteId6 not implemented");
 }
 
+namespace {
+
+std::string
+idToText(const OptionBuffer& id) {
+    std::stringstream tmp;
+    tmp << std::hex;
+    bool delim = false;
+    for (std::vector<uint8_t>::const_iterator it = id.begin();
+         it != id.end(); ++it) {
+        if (delim) {
+            tmp << ":";
+        }
+        tmp << std::setw(2) << std::setfill('0')
+            << static_cast<unsigned int>(*it);
+        delim = true;
+    }
+    return (tmp.str());
+}
+
+} // anonymous namespace
+
 Lease4Collection
-MySqlLeaseMgr::getLeases4ByRelayId(const OptionBuffer& /* relay_id */,
-                                   const IOAddress& /* lower_bound_address */,
-                                   const LeasePageSize& /* page_size */,
-                                   const time_t& /* qry_start_time = 0 */,
-                                   const time_t& /* qry_end_time = 0 */) {
-    isc_throw(NotImplemented, "MySqlLeaseMgr::getLeases4ByRelayId not implemented");
+MySqlLeaseMgr::getLeases4ByRelayId(const OptionBuffer& relay_id,
+                                   const IOAddress& lower_bound_address,
+                                   const LeasePageSize& page_size,
+                                   const time_t& qry_start_time /* = 0 */,
+                                   const time_t& qry_end_time /* = 0 */) {
+    // Expecting IPv4 address.
+    if (!lower_bound_address.isV4()) {
+        isc_throw(InvalidAddressFamily, "expected IPv4 address while "
+                  "retrieving leases from the lease database, got "
+                  << lower_bound_address);
+    }
+
+    // Catch 2038 bug with 32 bit time_t.
+    if ((qry_start_time < 0) || (qry_end_time < 0)) {
+        isc_throw(BadValue, "negative time value");
+    }
+
+    bool have_qst = (qry_start_time > 0);
+    bool have_qet = (qry_end_time > 0);
+
+    // Start time must be before end time.
+    if (have_qst && have_qet && (qry_start_time > qry_end_time)) {
+        isc_throw(BadValue, "start time must be before end time");
+    }
+
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_MYSQL_GET_RELAYID4)
+        .arg(page_size.page_size_)
+        .arg(lower_bound_address.toText())
+        .arg(idToText(relay_id))
+        .arg(qry_start_time)
+        .arg(qry_end_time);
+
+    // Prepare WHERE clause
+    size_t bindings = 3;
+    if (have_qst) {
+        ++bindings;
+    }
+    if (have_qet) {
+        ++bindings;
+    }
+    MYSQL_BIND inbind[bindings];
+    memset(inbind, 0, sizeof(inbind));
+
+    std::vector<uint8_t> relay_id_data = relay_id;
+    unsigned long relay_id_length = relay_id.size();
+
+    // If the relay id happens to be empty, we have to create a
+    // 1 byte dummy buffer and pass it to the binding.
+    if (relay_id_data.empty()) {
+        relay_id_data.resize(1);
+    }
+
+    // Bind relay id
+    inbind[0].buffer_type = MYSQL_TYPE_BLOB;
+    inbind[0].buffer = reinterpret_cast<char*>(&relay_id_data[0]);
+    inbind[0].buffer_length = relay_id_length;
+    inbind[0].length = &relay_id_length;
+
+    // Bind lower bound address
+    uint32_t lb_address_data = lower_bound_address.toUint32();
+    inbind[1].buffer_type = MYSQL_TYPE_LONG;
+    inbind[1].buffer = reinterpret_cast<char*>(&lb_address_data);
+    inbind[1].is_unsigned = MLM_TRUE;
+
+    size_t index = 2;
+    // Bind query start time.
+    uint32_t start_time = static_cast<uint32_t>(qry_start_time);
+    if (have_qst) {
+        inbind[index].buffer_type = MYSQL_TYPE_LONG;
+        inbind[index].buffer = reinterpret_cast<char*>(&start_time);
+        inbind[index].is_unsigned = MLM_TRUE;
+        ++index;
+    }
+
+    // Bind query end time.
+    uint32_t end_time = static_cast<uint32_t>(qry_end_time);
+    if (have_qet) {
+        inbind[index].buffer_type = MYSQL_TYPE_LONG;
+        inbind[index].buffer = reinterpret_cast<char*>(&end_time);
+        inbind[index].is_unsigned = MLM_TRUE;
+        ++index;
+    }
+
+    // Bind page size value
+    uint32_t ps = static_cast<uint32_t>(page_size.page_size_);
+    inbind[index].buffer_type = MYSQL_TYPE_LONG;
+    inbind[index].buffer = reinterpret_cast<char*>(&ps);
+    inbind[index].is_unsigned = MLM_TRUE;
+
+    StatementIndex stindex = GET_LEASE4_RELAYID;
+    if (have_qst && !have_qet) {
+        stindex = GET_LEASE4_RELAYID_QST;
+    } else if (have_qst && have_qet) {
+        stindex = GET_LEASE4_RELAYID_QSET;
+    } else if (!have_qst && have_qet) {
+        stindex = GET_LEASE4_RELAYID_QET;
+    }
+
+    // Get the leases
+    Lease4Collection result;
+
+    // Get a context
+    MySqlLeaseContextAlloc get_context(*this);
+    MySqlLeaseContextPtr ctx = get_context.ctx_;
+
+    getLeaseCollection(ctx, stindex, inbind, result);
+
+    return (result);
 }
 
 Lease4Collection
-MySqlLeaseMgr::getLeases4ByRemoteId(const OptionBuffer& /* remote_id */,
-                                    const IOAddress& /* lower_bound_address */,
-                                    const LeasePageSize& /* page_size */,
-                                    const time_t& /* qry_start_time = 0 */,
-                                    const time_t& /* qry_end_time = 0 */) {
-    isc_throw(NotImplemented, "MySqlLeaseMgr::getLeases4ByRemoteId not implemented");
+MySqlLeaseMgr::getLeases4ByRemoteId(const OptionBuffer& remote_id,
+                                    const IOAddress& lower_bound_address,
+                                    const LeasePageSize& page_size,
+                                    const time_t& qry_start_time /* = 0 */,
+                                    const time_t& qry_end_time /* = 0 */) {
+    // Expecting IPv4 address.
+    if (!lower_bound_address.isV4()) {
+        isc_throw(InvalidAddressFamily, "expected IPv4 address while "
+                  "retrieving leases from the lease database, got "
+                  << lower_bound_address);
+    }
+
+    // Catch 2038 bug with 32 bit time_t.
+    if ((qry_start_time < 0) || (qry_end_time < 0)) {
+        isc_throw(BadValue, "negative time value");
+    }
+
+    bool have_qst = (qry_start_time > 0);
+    bool have_qet = (qry_end_time > 0);
+
+    // Start time must be before end time.
+    if (have_qst && have_qet && (qry_start_time > qry_end_time)) {
+        isc_throw(BadValue, "start time must be before end time");
+    }
+
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_MYSQL_GET_REMOTEID4)
+        .arg(page_size.page_size_)
+        .arg(lower_bound_address.toText())
+        .arg(idToText(remote_id))
+        .arg(qry_start_time)
+        .arg(qry_end_time);
+
+    // Prepare WHERE clause
+    size_t bindings = 3;
+    if (have_qst) {
+        ++bindings;
+    }
+    if (have_qet) {
+        ++bindings;
+    }
+    MYSQL_BIND inbind[bindings];
+    memset(inbind, 0, sizeof(inbind));
+
+    std::vector<uint8_t> remote_id_data = remote_id;
+    unsigned long remote_id_length = remote_id.size();
+
+    // If the remote id happens to be empty, we have to create a
+    // 1 byte dummy buffer and pass it to the binding.
+    if (remote_id_data.empty()) {
+        remote_id_data.resize(1);
+    }
+
+    // Bind remote id
+    inbind[0].buffer_type = MYSQL_TYPE_BLOB;
+    inbind[0].buffer = reinterpret_cast<char*>(&remote_id_data[0]);
+    inbind[0].buffer_length = remote_id_length;
+    inbind[0].length = &remote_id_length;
+
+    // Bind lower bound address
+    uint32_t lb_address_data = lower_bound_address.toUint32();
+    inbind[1].buffer_type = MYSQL_TYPE_LONG;
+    inbind[1].buffer = reinterpret_cast<char*>(&lb_address_data);
+    inbind[1].is_unsigned = MLM_TRUE;
+
+    size_t index = 2;
+    // Bind query start time.
+    uint32_t start_time = static_cast<uint32_t>(qry_start_time);
+    if (have_qst) {
+        inbind[index].buffer_type = MYSQL_TYPE_LONG;
+        inbind[index].buffer = reinterpret_cast<char*>(&start_time);
+        inbind[index].is_unsigned = MLM_TRUE;
+        ++index;
+    }
+
+    // Bind query end time.
+    uint32_t end_time = static_cast<uint32_t>(qry_end_time);
+    if (have_qet) {
+        inbind[index].buffer_type = MYSQL_TYPE_LONG;
+        inbind[index].buffer = reinterpret_cast<char*>(&end_time);
+        inbind[index].is_unsigned = MLM_TRUE;
+        ++index;
+    }
+
+    // Bind page size value
+    uint32_t ps = static_cast<uint32_t>(page_size.page_size_);
+    inbind[index].buffer_type = MYSQL_TYPE_LONG;
+    inbind[index].buffer = reinterpret_cast<char*>(&ps);
+    inbind[index].is_unsigned = MLM_TRUE;
+
+    StatementIndex stindex = GET_LEASE4_REMOTEID;
+    if (have_qst && !have_qet) {
+        stindex = GET_LEASE4_REMOTEID_QST;
+    } else if (have_qst && have_qet) {
+        stindex = GET_LEASE4_REMOTEID_QSET;
+    } else if (!have_qst && have_qet) {
+        stindex = GET_LEASE4_REMOTEID_QET;
+    }
+
+    // Get the leases
+    Lease4Collection result;
+
+    // Get a context
+    MySqlLeaseContextAlloc get_context(*this);
+    MySqlLeaseContextPtr ctx = get_context.ctx_;
+
+    getLeaseCollection(ctx, stindex, inbind, result);
+
+    return (result);
 }
 
 Lease6Collection
index 0def8c6aa9f02d38ac97216fb3fd3c9f74d833a3..26a83fc675885416d32ab9e5f6c92a9b6b222dc4 100644 (file)
@@ -705,6 +705,14 @@ public:
         GET_LEASE4_SUBID,            // Get IPv4 leases by subnet ID
         GET_LEASE4_HOSTNAME,         // Get IPv4 leases by hostname
         GET_LEASE4_EXPIRE,           // Get lease4 by expiration.
+        GET_LEASE4_RELAYID,          // Get page of lease by relay ID.
+        GET_LEASE4_RELAYID_QST,      // Get page of leases by relay ID and query start time.
+        GET_LEASE4_RELAYID_QSET,     // Get page of leases by relay ID and query start and end times.
+        GET_LEASE4_RELAYID_QET,      // Get page of leases by relay ID and query end time.
+        GET_LEASE4_REMOTEID,         // Get page of lease by remote ID.
+        GET_LEASE4_REMOTEID_QST,     // Get page of leases by remote ID and query start time.
+        GET_LEASE4_REMOTEID_QSET,    // Get page of leases by remote ID and query start and end times.
+        GET_LEASE4_REMOTEID_QET,     // Get page of leases by remote ID and query end time.
         GET_LEASE6,                  // Get all IPv6 leases
         GET_LEASE6_ADDR,             // Get lease6 by address
         GET_LEASE6_DUID_IAID,        // Get lease6 by DUID and IAID
index 795db37eb2b028b4ac819a1472695fda62027126..2f590f839662f4c76ce4c14a34047aea9dbbdd13 100644 (file)
@@ -69,7 +69,7 @@ PgSqlTaggedStatement tagged_statements[] = {
       "SELECT address, hwaddr, client_id, "
         "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
         "fqdn_fwd, fqdn_rev, hostname, "
-        "state, user_context "
+        "state, user_context, relay_id, remote_id "
       "FROM lease4"},
 
     // GET_LEASE4_ADDR
@@ -78,7 +78,7 @@ PgSqlTaggedStatement tagged_statements[] = {
       "SELECT address, hwaddr, client_id, "
         "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
         "fqdn_fwd, fqdn_rev, hostname, "
-        "state, user_context "
+        "state, user_context, relay_id, remote_id "
       "FROM lease4 "
       "WHERE address = $1"},
 
@@ -88,7 +88,7 @@ PgSqlTaggedStatement tagged_statements[] = {
       "SELECT address, hwaddr, client_id, "
         "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
         "fqdn_fwd, fqdn_rev, hostname, "
-        "state, user_context "
+        "state, user_context, relay_id, remote_id "
       "FROM lease4 "
       "WHERE client_id = $1"},
 
@@ -98,7 +98,7 @@ PgSqlTaggedStatement tagged_statements[] = {
       "SELECT address, hwaddr, client_id, "
         "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
         "fqdn_fwd, fqdn_rev, hostname, "
-        "state, user_context "
+        "state, user_context, relay_id, remote_id "
       "FROM lease4 "
       "WHERE client_id = $1 AND subnet_id = $2"},
 
@@ -108,7 +108,7 @@ PgSqlTaggedStatement tagged_statements[] = {
       "SELECT address, hwaddr, client_id, "
         "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
         "fqdn_fwd, fqdn_rev, hostname, "
-        "state, user_context "
+        "state, user_context, relay_id, remote_id "
       "FROM lease4 "
       "WHERE hwaddr = $1"},
 
@@ -118,7 +118,7 @@ PgSqlTaggedStatement tagged_statements[] = {
       "SELECT address, hwaddr, client_id, "
         "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
         "fqdn_fwd, fqdn_rev, hostname, "
-        "state, user_context "
+        "state, user_context, relay_id, remote_id "
       "FROM lease4 "
       "WHERE hwaddr = $1 AND subnet_id = $2"},
 
@@ -128,7 +128,7 @@ PgSqlTaggedStatement tagged_statements[] = {
       "SELECT address, hwaddr, client_id, "
         "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
         "fqdn_fwd, fqdn_rev, hostname, "
-        "state, user_context "
+        "state, user_context, relay_id, remote_id "
       "FROM lease4 "
       "WHERE address > $1 "
       "ORDER BY address "
@@ -140,7 +140,7 @@ PgSqlTaggedStatement tagged_statements[] = {
       "SELECT address, hwaddr, client_id, "
         "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
         "fqdn_fwd, fqdn_rev, hostname, "
-      "state, user_context "
+      "state, user_context, relay_id, remote_id "
       "FROM lease4 "
       "WHERE subnet_id = $1"},
 
@@ -150,7 +150,7 @@ PgSqlTaggedStatement tagged_statements[] = {
       "SELECT address, hwaddr, client_id, "
         "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
         "fqdn_fwd, fqdn_rev, hostname, "
-      "state, user_context "
+      "state, user_context, relay_id, remote_id "
       "FROM lease4 "
       "WHERE lower(hostname) = $1"},
 
@@ -160,12 +160,124 @@ PgSqlTaggedStatement tagged_statements[] = {
       "SELECT address, hwaddr, client_id, "
         "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
         "fqdn_fwd, fqdn_rev, hostname, "
-        "state, user_context "
+        "state, user_context, relay_id, remote_id "
       "FROM lease4 "
       "WHERE state != $1 AND valid_lifetime != 4294967295 AND expire < $2 "
       "ORDER BY expire "
       "LIMIT $3"},
 
+    // GET_LEASE4_RELAYID
+    { 3, { OID_BYTEA, OID_INT8, OID_INT8 },
+      "get_lease4_relayid",
+      "SELECT address, hwaddr, client_id, "
+        "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
+        "fqdn_fwd, fqdn_rev, hostname, "
+        "state, user_context, relay_id, remote_id "
+      "FROM lease4 "
+      "WHERE relay_id = $1 and address > $2 "
+      "ORDER BY address "
+      "LIMIT $3"},
+
+    // GET_LEASE4_RELAYID_QST
+    { 4, { OID_BYTEA, OID_INT8, OID_INT8, OID_INT8 },
+      "get_lease4_relayid_qst",
+      "SELECT address, hwaddr, client_id, "
+        "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
+        "fqdn_fwd, fqdn_rev, hostname, "
+        "state, user_context, relay_id, remote_id "
+      "FROM lease4 "
+      "WHERE relay_id = $1 and address > $2 "
+      "and EXTRACT(EPOCH FROM expire) - (CASE valid_lifetime WHEN 4294967295 "
+       "THEN 0 ELSE valid_lifetime END) >= $3 "
+      "ORDER BY address "
+      "LIMIT $4"},
+
+    // GET_LEASE4_RELAYID_QSET
+    { 5, { OID_BYTEA, OID_INT8, OID_INT8, OID_INT8, OID_INT8 },
+      "get_lease4_relayid_qset",
+      "SELECT address, hwaddr, client_id, "
+        "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
+        "fqdn_fwd, fqdn_rev, hostname, "
+        "state, user_context, relay_id, remote_id "
+      "FROM lease4 "
+      "WHERE relay_id = $1 and address > $2 "
+      "and EXTRACT(EPOCH FROM expire) - (CASE valid_lifetime WHEN 4294967295 "
+       "THEN 0 ELSE valid_lifetime END) >= $3 "
+      "and EXTRACT(EPOCH FROM expire) - (CASE valid_lifetime WHEN 4294967295 "
+       "THEN 0 ELSE valid_lifetime END) <= $4 "
+      "ORDER BY address "
+      "LIMIT $5"},
+
+    // GET_LEASE4_RELAYID_QET
+    { 4, { OID_BYTEA, OID_INT8, OID_INT8, OID_INT8 },
+      "get_lease4_relayid_qet",
+      "SELECT address, hwaddr, client_id, "
+        "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
+        "fqdn_fwd, fqdn_rev, hostname, "
+        "state, user_context, relay_id, remote_id "
+      "FROM lease4 "
+      "WHERE relay_id = $1 and address > $2 "
+      "and EXTRACT(EPOCH FROM expire) - (CASE valid_lifetime WHEN 4294967295 "
+       "THEN 0 ELSE valid_lifetime END) <= $3 "
+      "ORDER BY address "
+      "LIMIT $4"},
+
+    // GET_LEASE4_REMOTEID
+    { 3, { OID_BYTEA, OID_INT8, OID_INT8 },
+      "get_lease4_remoteid",
+      "SELECT address, hwaddr, client_id, "
+        "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
+        "fqdn_fwd, fqdn_rev, hostname, "
+        "state, user_context, relay_id, remote_id "
+      "FROM lease4 "
+      "WHERE remote_id = $1 and address > $2 "
+      "ORDER BY address "
+      "LIMIT $3"},
+
+    // GET_LEASE4_REMOTEID_QST
+    { 4, { OID_BYTEA, OID_INT8, OID_INT8, OID_INT8 },
+      "get_lease4_remoteid_qst",
+      "SELECT address, hwaddr, client_id, "
+        "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
+        "fqdn_fwd, fqdn_rev, hostname, "
+        "state, user_context, relay_id, remote_id "
+      "FROM lease4 "
+      "WHERE remote_id = $1 and address > $2 "
+      "and EXTRACT(EPOCH FROM expire) - (CASE valid_lifetime WHEN 4294967295 "
+       "THEN 0 ELSE valid_lifetime END) >= $3 "
+      "ORDER BY address "
+      "LIMIT $4"},
+
+    // GET_LEASE4_REMOTEID_QSET
+    { 5, { OID_BYTEA, OID_INT8, OID_INT8, OID_INT8, OID_INT8 },
+      "get_lease4_remoteid_qset",
+      "SELECT address, hwaddr, client_id, "
+        "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
+        "fqdn_fwd, fqdn_rev, hostname, "
+        "state, user_context, relay_id, remote_id "
+      "FROM lease4 "
+      "WHERE remote_id = $1 and address > $2 "
+      "and EXTRACT(EPOCH FROM expire) - (CASE valid_lifetime WHEN 4294967295 "
+       "THEN 0 ELSE valid_lifetime END) >= $3 "
+      "and EXTRACT(EPOCH FROM expire) - (CASE valid_lifetime WHEN 4294967295 "
+       "THEN 0 ELSE valid_lifetime END) <= $4 "
+      "ORDER BY address "
+      "LIMIT $5"},
+
+    // GET_LEASE4_REMOTEID_QET
+    { 4, { OID_BYTEA, OID_INT8, OID_INT8, OID_INT8 },
+      "get_lease4_remoteid_qet",
+      "SELECT address, hwaddr, client_id, "
+        "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
+        "fqdn_fwd, fqdn_rev, hostname, "
+        "state, user_context, relay_id, remote_id "
+      "FROM lease4 "
+      "WHERE remote_id = $1 and address > $2 "
+      "and EXTRACT(EPOCH FROM expire) - (CASE valid_lifetime WHEN 4294967295 "
+       "THEN 0 ELSE valid_lifetime END) <= $3 "
+      "ORDER BY address "
+      "LIMIT $4"},
+
     // GET_LEASE6
     { 0, { OID_NONE },
       "get_lease6",
@@ -271,13 +383,14 @@ PgSqlTaggedStatement tagged_statements[] = {
       "LIMIT $3"},
 
     // INSERT_LEASE4
-    { 11, { OID_INT8, OID_BYTEA, OID_BYTEA, OID_INT8, OID_TIMESTAMP, OID_INT8,
-            OID_BOOL, OID_BOOL, OID_VARCHAR, OID_INT8, OID_TEXT },
+    { 13, { OID_INT8, OID_BYTEA, OID_BYTEA, OID_INT8, OID_TIMESTAMP, OID_INT8,
+            OID_BOOL, OID_BOOL, OID_VARCHAR, OID_INT8, OID_TEXT, OID_BYTEA,
+            OID_BYTEA },
       "insert_lease4",
       "INSERT INTO lease4(address, hwaddr, client_id, "
         "valid_lifetime, expire, subnet_id, fqdn_fwd, fqdn_rev, hostname, "
-        "state, user_context) "
-      "VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)"},
+        "state, user_context, relay_id, remote_id) "
+      "VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)"},
 
     // INSERT_LEASE6
     { 17, { OID_VARCHAR, OID_BYTEA, OID_INT8, OID_TIMESTAMP, OID_INT8,
@@ -292,14 +405,15 @@ PgSqlTaggedStatement tagged_statements[] = {
       "VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17)"},
 
     // UPDATE_LEASE4
-    { 13, { OID_INT8, OID_BYTEA, OID_BYTEA, OID_INT8, OID_TIMESTAMP, OID_INT8,
-            OID_BOOL, OID_BOOL, OID_VARCHAR, OID_INT8, OID_TEXT, OID_INT8, OID_TIMESTAMP },
+    { 15, { OID_INT8, OID_BYTEA, OID_BYTEA, OID_INT8, OID_TIMESTAMP, OID_INT8,
+            OID_BOOL, OID_BOOL, OID_VARCHAR, OID_INT8, OID_TEXT, OID_BYTEA,
+            OID_BYTEA, OID_INT8, OID_TIMESTAMP },
       "update_lease4",
       "UPDATE lease4 SET address = $1, hwaddr = $2, "
         "client_id = $3, valid_lifetime = $4, expire = $5, "
         "subnet_id = $6, fqdn_fwd = $7, fqdn_rev = $8, hostname = $9, "
-        "state = $10, user_context = $11 "
-      "WHERE address = $12 AND expire = $13"},
+        "state = $10, user_context = $11, relay_id = $12, remote_id = $13 "
+      "WHERE address = $14 AND expire = $15"},
 
     // UPDATE_LEASE6
     { 19, { OID_VARCHAR, OID_BYTEA, OID_INT8, OID_TIMESTAMP, OID_INT8, OID_INT8,
@@ -457,19 +571,24 @@ private:
     static const size_t HOSTNAME_COL = 8;
     static const size_t STATE_COL = 9;
     static const size_t USER_CONTEXT_COL = 10;
+    static const size_t RELAY_ID_COL = 11;
+    static const size_t REMOTE_ID_COL = 12;
     /// @brief Number of columns in the table holding DHCPv4 leases.
-    static const size_t LEASE_COLUMNS = 11;
+    static const size_t LEASE_COLUMNS = 13;
 
 public:
 
     /// @brief Constructor
     PgSqlLease4Exchange()
-        : lease_(), addr4_(0), client_id_length_(0) {
+        : lease_(), addr4_(0), client_id_length_(0),
+          relay_id_length_(0), remote_id_length_(0) {
 
         BOOST_STATIC_ASSERT(9 < LEASE_COLUMNS);
 
         memset(hwaddr_buffer_, 0, sizeof(hwaddr_buffer_));
         memset(client_id_buffer_, 0, sizeof(client_id_buffer_));
+        memset(relay_id_buffer_, 0, sizeof(relay_id_buffer_));
+        memset(remote_id_buffer_, 0, sizeof(remote_id_buffer_));
 
         // Set the column names (for error messages)
         columns_.push_back("address");
@@ -483,6 +602,8 @@ public:
         columns_.push_back("hostname");
         columns_.push_back("state");
         columns_.push_back("user_context");
+        columns_.push_back("relay_id");
+        columns_.push_back("remote_id");
     }
 
     /// @brief Creates the bind array for sending Lease4 data to the database.
@@ -565,6 +686,19 @@ public:
                 user_context_ = "";
             }
             bind_array.add(user_context_);
+
+            if (!lease->relay_id_.empty()) {
+                bind_array.add(lease->relay_id_);
+            } else {
+                bind_array.addNull();
+            }
+
+            if (!lease->remote_id_.empty()) {
+                bind_array.add(lease->remote_id_);
+            } else {
+                bind_array.addNull();
+            }
+
         } catch (const std::exception& ex) {
             isc_throw(DbOperationError,
                       "Could not create bind array from Lease4: "
@@ -626,6 +760,12 @@ public:
                 }
             }
 
+            convertFromBytea(r, row, RELAY_ID_COL, relay_id_buffer_,
+                             sizeof(relay_id_buffer_), relay_id_length_);
+
+            convertFromBytea(r, row, REMOTE_ID_COL, remote_id_buffer_,
+                             sizeof(remote_id_buffer_), remote_id_length_);
+
             Lease4Ptr result(boost::make_shared<Lease4>(addr4_, hwaddr,
                                                         client_id_buffer_,
                                                         client_id_length_,
@@ -639,6 +779,16 @@ public:
                 result->setContext(ctx);
             }
 
+            if (relay_id_length_) {
+                result->relay_id_.assign(relay_id_buffer_,
+                                         relay_id_buffer_ + relay_id_length_);
+            }
+
+            if (remote_id_length_) {
+                result->remote_id_.assign(remote_id_buffer_,
+                                          remote_id_buffer_ + remote_id_length_);
+            }
+
             return (result);
         } catch (const std::exception& ex) {
             isc_throw(DbOperationError,
@@ -658,6 +808,10 @@ private:
     uint32_t addr4_;
     size_t   client_id_length_;
     uint8_t  client_id_buffer_[ClientId::MAX_CLIENT_ID_LEN];
+    size_t   relay_id_length_;
+    uint8_t  relay_id_buffer_[ClientId::MAX_CLIENT_ID_LEN];
+    size_t   remote_id_length_;
+    uint8_t  remote_id_buffer_[ClientId::MAX_CLIENT_ID_LEN];
 };
 
 /// @brief Supports exchanging IPv6 leases with PostgreSQL.
@@ -2657,22 +2811,203 @@ PgSqlLeaseMgr::addRemoteId6(const IOAddress& /* lease_addr */,
     isc_throw(NotImplemented, "PgSqlLeaseMgr::addRemoteId6 not implemented");
 }
 
+namespace {
+
+std::string
+idToText(const OptionBuffer& id) {
+    std::stringstream tmp;
+    tmp << std::hex;
+    bool delim = false;
+    for (std::vector<uint8_t>::const_iterator it = id.begin();
+         it != id.end(); ++it) {
+        if (delim) {
+            tmp << ":";
+        }
+        tmp << std::setw(2) << std::setfill('0')
+            << static_cast<unsigned int>(*it);
+        delim = true;
+    }
+    return (tmp.str());
+}
+
+} // anonymous namespace
+
 Lease4Collection
-PgSqlLeaseMgr::getLeases4ByRelayId(const OptionBuffer& /* relay_id */,
-                                   const IOAddress& /* lower_bound_address */,
-                                   const LeasePageSize& /* page_size */,
-                                   const time_t& /* qry_start_time = 0 */,
-                                   const time_t& /* qry_end_time = 0 */) {
-    isc_throw(NotImplemented, "PgSqlLeaseMgr::getLeases4ByRelayId not implemented");
+PgSqlLeaseMgr::getLeases4ByRelayId(const OptionBuffer& relay_id,
+                                   const IOAddress& lower_bound_address,
+                                   const LeasePageSize& page_size,
+                                   const time_t& qry_start_time /* = 0 */,
+                                   const time_t& qry_end_time /* = 0 */) {
+    // Expecting IPv4 address.
+    if (!lower_bound_address.isV4()) {
+        isc_throw(InvalidAddressFamily, "expected IPv4 address while "
+                  "retrieving leases from the lease database, got "
+                  << lower_bound_address);
+    }
+
+    // Catch 2038 bug with 32 bit time_t.
+    if ((qry_start_time < 0) || (qry_end_time < 0)) {
+        isc_throw(BadValue, "negative time value");
+    }
+
+    bool have_qst = (qry_start_time > 0);
+    bool have_qet = (qry_end_time > 0);
+
+    // Start time must be before end time.
+    if (have_qst && have_qet && (qry_start_time > qry_end_time)) {
+        isc_throw(BadValue, "start time must be before end time");
+    }
+
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_PGSQL_GET_RELAYID4)
+        .arg(page_size.page_size_)
+        .arg(lower_bound_address.toText())
+        .arg(idToText(relay_id))
+        .arg(qry_start_time)
+        .arg(qry_end_time);
+
+    // Prepare WHERE clause
+    PsqlBindArray bind_array;
+
+    // Bind relay id
+    if (!relay_id.empty()) {
+        bind_array.add(relay_id);
+    } else {
+        bind_array.add("");
+    }
+
+    // Bind lower bound address
+    std::string lb_address_data =
+        boost::lexical_cast<std::string>(lower_bound_address.toUint32());
+    bind_array.add(lb_address_data);
+
+    // Bind query start time.
+    std::string start_time_str;
+    if (have_qst) {
+        start_time_str = boost::lexical_cast<std::string>(qry_start_time);
+        bind_array.add(start_time_str);
+    }
+
+    // Bind query end time.
+    std::string end_time_str;
+    if (have_qet) {
+        end_time_str = boost::lexical_cast<std::string>(qry_end_time);
+        bind_array.add(end_time_str);
+    }
+
+    // Bind page size value
+    std::string page_size_data =
+        boost::lexical_cast<std::string>(page_size.page_size_);
+    bind_array.add(page_size_data);
+
+    StatementIndex stindex = GET_LEASE4_RELAYID;
+    if (have_qst && !have_qet) {
+        stindex = GET_LEASE4_RELAYID_QST;
+    } else if (have_qst && have_qet) {
+        stindex = GET_LEASE4_RELAYID_QSET;
+    } else if (!have_qst && have_qet) {
+        stindex = GET_LEASE4_RELAYID_QET;
+    }
+
+    // Get the leases
+    Lease4Collection result;
+
+    // Get a context
+    PgSqlLeaseContextAlloc get_context(*this);
+    PgSqlLeaseContextPtr ctx = get_context.ctx_;
+
+    getLeaseCollection(ctx, stindex, bind_array, result);
+
+    return (result);
 }
 
 Lease4Collection
-PgSqlLeaseMgr::getLeases4ByRemoteId(const OptionBuffer& /* remote_id */,
-                                    const IOAddress& /* lower_bound_address */,
-                                    const LeasePageSize& /* page_size */,
-                                    const time_t& /* qry_start_time = 0 */,
-                                    const time_t& /* qry_end_time = 0 */) {
-    isc_throw(NotImplemented, "PgSqlLeaseMgr::getLeases4ByRemoteId not implemented");
+PgSqlLeaseMgr::getLeases4ByRemoteId(const OptionBuffer& remote_id,
+                                    const IOAddress& lower_bound_address,
+                                    const LeasePageSize& page_size,
+                                    const time_t& qry_start_time /* = 0 */,
+                                    const time_t& qry_end_time /* = 0 */) {
+    // Expecting IPv4 address.
+    if (!lower_bound_address.isV4()) {
+        isc_throw(InvalidAddressFamily, "expected IPv4 address while "
+                  "retrieving leases from the lease database, got "
+                  << lower_bound_address);
+    }
+
+    // Catch 2038 bug with 32 bit time_t.
+    if ((qry_start_time < 0) || (qry_end_time < 0)) {
+        isc_throw(BadValue, "negative time value");
+    }
+
+    bool have_qst = (qry_start_time > 0);
+    bool have_qet = (qry_end_time > 0);
+
+    // Start time must be before end time.
+    if (have_qst && have_qet && (qry_start_time > qry_end_time)) {
+        isc_throw(BadValue, "start time must be before end time");
+    }
+
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_PGSQL_GET_REMOTEID4)
+        .arg(page_size.page_size_)
+        .arg(lower_bound_address.toText())
+        .arg(idToText(remote_id))
+        .arg(qry_start_time)
+        .arg(qry_end_time);
+
+    // Prepare WHERE clause
+    PsqlBindArray bind_array;
+
+    // Bind remote id
+    if (!remote_id.empty()) {
+        bind_array.add(remote_id);
+    } else {
+        bind_array.add("");
+    }
+
+    // Bind lower bound address
+    std::string lb_address_data =
+        boost::lexical_cast<std::string>(lower_bound_address.toUint32());
+    bind_array.add(lb_address_data);
+
+    // Bind query start time.
+    std::string start_time_str;
+    if (have_qst) {
+        start_time_str = boost::lexical_cast<std::string>(qry_start_time);
+        bind_array.add(start_time_str);
+    }
+
+    // Bind query end time.
+    std::string end_time_str;
+    if (have_qet) {
+        end_time_str = boost::lexical_cast<std::string>(qry_end_time);
+        bind_array.add(end_time_str);
+    }
+
+    // Bind page size value
+    std::string page_size_data =
+        boost::lexical_cast<std::string>(page_size.page_size_);
+    bind_array.add(page_size_data);
+
+    StatementIndex stindex = GET_LEASE4_REMOTEID;
+    if (have_qst && !have_qet) {
+        stindex = GET_LEASE4_REMOTEID_QST;
+    } else if (have_qst && have_qet) {
+        stindex = GET_LEASE4_REMOTEID_QSET;
+    } else if (!have_qst && have_qet) {
+        stindex = GET_LEASE4_REMOTEID_QET;
+    }
+
+    // Get the leases
+    Lease4Collection result;
+
+    // Get a context
+    PgSqlLeaseContextAlloc get_context(*this);
+    PgSqlLeaseContextPtr ctx = get_context.ctx_;
+
+    getLeaseCollection(ctx, stindex, bind_array, result);
+
+    return (result);
 }
 
 Lease6Collection
index 0b79629a2ae71a442eb97b33461b54ccb5ba135f..9e4d7f126f8d11038c59dc1e19c5fbc55ae99ae7 100644 (file)
@@ -681,6 +681,14 @@ public:
         GET_LEASE4_SUBID,            // Get IPv4 leases by subnet ID
         GET_LEASE4_HOSTNAME,         // Get IPv4 leases by hostname
         GET_LEASE4_EXPIRE,           // Get lease4 by expiration.
+        GET_LEASE4_RELAYID,          // Get page of lease by relay ID.
+        GET_LEASE4_RELAYID_QST,      // Get page of leases by relay ID and query start time.
+        GET_LEASE4_RELAYID_QSET,     // Get page of leases by relay ID and query start and end times.
+        GET_LEASE4_RELAYID_QET,      // Get page of leases by relay ID and query end time.
+        GET_LEASE4_REMOTEID,         // Get page of lease by remote ID.
+        GET_LEASE4_REMOTEID_QST,     // Get page of leases by remote ID and query start time.
+        GET_LEASE4_REMOTEID_QSET,    // Get page of leases by remote ID and query start and end times.
+        GET_LEASE4_REMOTEID_QET,     // Get page of leases by remote ID and query end time.
         GET_LEASE6,                  // Get all IPv6 leases
         GET_LEASE6_ADDR,             // Get lease6 by address
         GET_LEASE6_DUID_IAID,        // Get lease6 by DUID and IAID
index 99e5deae76e9a131e44dd5c5f76577aa3fa1320f..a73d0f34a74b493b649f370d1d4cdf1e5ede3e88 100644 (file)
@@ -116,10 +116,12 @@ libdhcpsrv_unittests_SOURCES += dhcp_parsers_unittest.cc
 libdhcpsrv_unittests_SOURCES += ncr_generator_unittest.cc
 if HAVE_MYSQL
 libdhcpsrv_unittests_SOURCES += mysql_lease_mgr_unittest.cc
+libdhcpsrv_unittests_SOURCES += mysql_lease_extended_info_unittest.cc
 libdhcpsrv_unittests_SOURCES += mysql_host_data_source_unittest.cc
 endif
 if HAVE_PGSQL
 libdhcpsrv_unittests_SOURCES += pgsql_lease_mgr_unittest.cc
+libdhcpsrv_unittests_SOURCES += pgsql_lease_extended_info_unittest.cc
 libdhcpsrv_unittests_SOURCES += pgsql_host_data_source_unittest.cc
 endif
 libdhcpsrv_unittests_SOURCES += pool_unittest.cc
diff --git a/src/lib/dhcpsrv/tests/mysql_lease_extended_info_unittest.cc b/src/lib/dhcpsrv/tests/mysql_lease_extended_info_unittest.cc
new file mode 100644 (file)
index 0000000..ba34f0c
--- /dev/null
@@ -0,0 +1,617 @@
+// Copyright (C) 2023 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
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/io_address.h>
+#include <cc/data.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/mysql_lease_mgr.h>
+#include <mysql/testutils/mysql_schema.h>
+#include <testutils/gtest_utils.h>
+#include <testutils/multi_threading_utils.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::db;
+using namespace isc::db::test;
+using namespace isc::dhcp;
+using namespace isc::test;
+using namespace isc::util;
+using namespace std;
+
+namespace {
+
+/// @brief IPv4 addresses used in the tests.
+const vector<string> ADDRESS4 = {
+    "192.0.2.0", "192.0.2.1", "192.0.2.2", "192.0.2.3",
+    "192.0.2.4", "192.0.2.5", "192.0.2.6", "192.0.2.7"
+};
+
+/// @brief DUIDs used in the tests.
+const vector<string> DUIDS = {
+    "wwwwwwww", "BBBBBBBB", "::::::::", "0123456789acdef",
+    "BBBBBBBB", "$$$$$$$$", "^^^^^^^^", "\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5"
+};
+
+/// @brief Test fixture class for extended info tests.
+class MySqlExtendedInfoTest : public ::testing::Test {
+public:
+    /// @brief Constructor.
+    MySqlExtendedInfoTest() {
+        // Ensure we have the proper schema with no transient data.
+        createMySQLSchema();
+
+        // Connect to the database.
+        try {
+            LeaseMgrFactory::create(validMySQLConnectionString());
+        } catch (...) {
+            std::cerr << "*** ERROR: unable to open database. The test\n"
+                         "*** environment is broken and must be fixed before\n"
+                         "*** the MySQL tests will run correctly.\n"
+                         "*** The reason for the problem is described in the\n"
+                         "*** accompanying exception output.\n";
+            throw;
+        }
+
+        lease_mgr_ = &(LeaseMgrFactory::instance());
+        leases4.clear();
+        MultiThreadingMgr::instance().setMode(false);
+        now_ = time(0);
+    }
+
+    /// @brief Destructor.
+    ~MySqlExtendedInfoTest() {
+        LeaseMgrFactory::destroy();
+        // If data wipe enabled, delete transient data otherwise destroy
+        // the schema.
+        destroyMySQLSchema();
+
+        leases4.clear();
+        MultiThreadingMgr::instance().setMode(false);
+    }
+
+    /// @brief Create and set v4 leases.
+    ///
+    /// @param insert When true insert in the database.
+    void initLease4(bool insert = true) {
+        ASSERT_EQ(ADDRESS4.size(), DUIDS.size());
+        for (size_t i = 0; i < ADDRESS4.size(); ++i) {
+            Lease4Ptr lease;
+            vector<uint8_t> hwaddr_data(5, 0x08);
+            hwaddr_data.push_back(0x80 + i);
+            HWAddrPtr hwaddr(new HWAddr(hwaddr_data, HTYPE_ETHER));
+            vector<uint8_t> client_id = createFromString(DUIDS[i]);
+            IOAddress address(ADDRESS4[i]);
+            ASSERT_NO_THROW(lease.reset(new Lease4(address, hwaddr,
+                                                   &client_id[0],
+                                                   client_id.size(),
+                                                   1000, now_,
+                                                   static_cast<SubnetID>(i))));
+            leases4.push_back(lease);
+            if (insert) {
+                EXPECT_TRUE(lease_mgr_->addLease(lease));
+            }
+        }
+        ASSERT_EQ(ADDRESS4.size(), leases4.size());
+    }
+
+    /// @brief Create a vector of uint8_t from a string.
+    ///
+    /// @param content A not empty string holding the content.
+    /// @return A vector of uint8_t with the given content.
+    inline vector<uint8_t> createFromString(const string& content) {
+        vector<uint8_t> v;
+        v.resize(content.size());
+        memmove(&v[0], &content[0], v.size());
+        return (v);
+    }
+
+    /// @brief Test initLease4.
+    void testInitLease4();
+
+    /// @brief Test getLease4ByRelayId.
+    void testGetLeases4ByRelayId();
+
+    /// @brief Test getLease4ByRemoteId.
+    void testGetLeases4ByRemoteId();
+
+    /// @brief Lease manager.
+    LeaseMgr* lease_mgr_;
+
+    /// @brief V4 leases.
+    Lease4Collection leases4;
+
+    /// @brief Current timestamp.
+    time_t now_;
+};
+
+/// @brief Verifies that the lease manager can add the v4 leases.
+void
+MySqlExtendedInfoTest::testInitLease4() {
+    initLease4();
+    EXPECT_EQ(8, leases4.size());
+    IOAddress zero = IOAddress::IPV4_ZERO_ADDRESS();
+    Lease4Collection got;
+    // Use the page version as it returns leases in order.
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4(zero, LeasePageSize(100)));
+    ASSERT_EQ(leases4.size(), got.size());
+    for (size_t i = 0; i < leases4.size(); ++i) {
+        ConstElementPtr expected = leases4[i]->toElement();
+        LeasePtr lease = got[i];
+        ASSERT_TRUE(lease);
+        EXPECT_TRUE(expected->equals(*lease->toElement()))
+            << "expected: " << expected->str() << "\n"
+            << "got: " << lease->toElement()->str() << "\n";
+    }
+}
+
+TEST_F(MySqlExtendedInfoTest, initLease4) {
+    testInitLease4();
+}
+
+TEST_F(MySqlExtendedInfoTest, initLease4MultiThreading) {
+    MultiThreadingTest mt(true);
+    testInitLease4();
+}
+
+/// @brief Verifies that getLeases4ByRelayId works as expected.
+void
+MySqlExtendedInfoTest::testGetLeases4ByRelayId() {
+    // Lease manager is created with empty tables.
+    initLease4(false);
+
+    // Create leases.
+    IOAddress addr0(ADDRESS4[0]);
+    IOAddress addr1(ADDRESS4[1]);
+    IOAddress addr2(ADDRESS4[2]);
+    IOAddress addr3(ADDRESS4[3]);
+    IOAddress addr4(ADDRESS4[4]);
+    IOAddress zero = IOAddress::IPV4_ZERO_ADDRESS();
+    vector<uint8_t> relay_id0 = { 0xaa, 0xbb, 0xcc };
+    vector<uint8_t> relay_id1 = { 1, 2, 3, 4 };
+    vector<uint8_t> relay_id2 = createFromString(DUIDS[2]);
+    string user_context_txt0 = "{ \"ISC\": { \"relay-agent-info\": {";
+    user_context_txt0 += " \"sub-options\": \"0C03AABBCC\",";
+    user_context_txt0 += " \"relay-id\": \"AABBCC\" } } }";
+    ElementPtr user_context0;
+    ASSERT_NO_THROW(user_context0 = Element::fromJSON(user_context_txt0));
+    string user_context_txt1 = "{ \"ISC\": { \"relay-agent-info\": {";
+    user_context_txt1 += " \"sub-options\": \"0C0401020304\",";
+    user_context_txt1 += " \"relay-id\": \"01020304\" } } }";
+    ElementPtr user_context1;
+    ASSERT_NO_THROW(user_context1 = Element::fromJSON(user_context_txt1));
+
+    Lease4Ptr lease;
+    // lease0: addr0, id0, now.
+    lease = leases4[0];
+    ASSERT_TRUE(lease);
+    lease->relay_id_ = relay_id0;
+    lease->setContext(user_context0);
+
+    // lease1: addr1, id1, now.
+    lease = leases4[1];
+    ASSERT_TRUE(lease);
+    lease->relay_id_ = relay_id1;
+    lease->setContext(user_context1);
+
+    // lease2: addr2, id0, now - 500.
+    lease = leases4[2];
+    ASSERT_TRUE(lease);
+    lease->relay_id_ = relay_id0;
+    lease->setContext(user_context0);
+    lease->cltt_ = now_ - 500;
+
+    // lease3: addr3, id0, now - 800.
+    lease = leases4[3];
+    ASSERT_TRUE(lease);
+    lease->relay_id_ = relay_id0;
+    lease->setContext(user_context0);
+    lease->cltt_ = now_ - 800;
+
+    // lease4: addr4, id0, now - 100.
+    lease = leases4[4];
+    ASSERT_TRUE(lease);
+    lease->relay_id_ = relay_id0;
+    lease->setContext(user_context0);
+    lease->cltt_ = now_ - 100;
+
+    // Add leases.
+    for (size_t i = 0; i < leases4.size(); ++i) {
+        EXPECT_TRUE(lease_mgr_->addLease(leases4[i]));
+    }
+
+    Lease4Collection got;
+    // Unknown relay id #2: nothing.
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id2,
+                                                          zero,
+                                                          LeasePageSize(100)));
+    EXPECT_EQ(0, got.size());
+
+    // Unknown relay id #2, now - 1000, now + 1000: nothing.
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id2,
+                                                          zero,
+                                                          LeasePageSize(100),
+                                                          now_ - 1000,
+                                                          now_ + 1000));
+    EXPECT_EQ(0, got.size());
+
+    // Relay id #0, now - 2000, now - 1000: nothing.
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+                                                          zero,
+                                                          LeasePageSize(100),
+                                                          now_ - 2000,
+                                                          now_ - 1000));
+    EXPECT_EQ(0, got.size());
+
+    // Relay id #0, now + 1000, now + 2000: nothing.
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+                                                          zero,
+                                                          LeasePageSize(100),
+                                                          now_ + 1000,
+                                                          now_ + 2000));
+    EXPECT_EQ(0, got.size());
+
+    // Relay id #0: 3 entries (0, 2, 3, 4).
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+                                                          zero,
+                                                          LeasePageSize(100)));
+    ASSERT_EQ(4, got.size());
+    lease = got[0];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[0]);
+    EXPECT_EQ(relay_id0, lease->relay_id_);
+    lease = got[1];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[2]);
+    EXPECT_EQ(relay_id0, lease->relay_id_);
+    lease = got[2];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[3]);
+    EXPECT_EQ(relay_id0, lease->relay_id_);
+    lease = got[3];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[4]);
+    EXPECT_EQ(relay_id0, lease->relay_id_);
+
+    // Relay id #0, partial: 2 entries (0, 2).
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+                                                          zero,
+                                                          LeasePageSize(2)));
+    ASSERT_EQ(2, got.size());
+    lease = got[0];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[0]);
+    EXPECT_EQ(relay_id0, lease->relay_id_);
+    lease = got[1];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[2]);
+    EXPECT_EQ(relay_id0, lease->relay_id_);
+
+    // Relay id #0, partial from previous: 2 entries (3, 4).
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+                                                          addr2,
+                                                          LeasePageSize(2)));
+    ASSERT_EQ(2, got.size());
+    lease = got[0];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[3]);
+    EXPECT_EQ(relay_id0, lease->relay_id_);
+    lease = got[1];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[4]);
+    EXPECT_EQ(relay_id0, lease->relay_id_);
+
+    // Relay id #0, final partial: no entries.
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+                                                          addr4,
+                                                          LeasePageSize(2)));
+    EXPECT_EQ(0, got.size());
+
+    // Relay id #0, from now - 500: 3 entries (0, 2, 4).
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+                                                          zero,
+                                                          LeasePageSize(100),
+                                                          now_ - 500));
+    ASSERT_EQ(3, got.size());
+    lease = got[0];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[0]);
+    EXPECT_EQ(relay_id0, lease->relay_id_);
+    lease = got[1];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[2]);
+    EXPECT_EQ(relay_id0, lease->relay_id_);
+    lease = got[2];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[4]);
+    EXPECT_EQ(relay_id0, lease->relay_id_);
+
+    // Relay id #0, to now - 200: 3 entries (2, 3).
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+                                                          zero,
+                                                          LeasePageSize(100),
+                                                          0, now_ - 200));
+    ASSERT_EQ(2, got.size());
+    lease = got[0];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[2]);
+    EXPECT_EQ(relay_id0, lease->relay_id_);
+    lease = got[1];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[3]);
+    EXPECT_EQ(relay_id0, lease->relay_id_);
+
+    // Relay id #0, from now - 500 to now - 100, partial: 1 entry.
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+                                                          zero,
+                                                          LeasePageSize(1),
+                                                          now_ - 500,
+                                                          now_ - 100));
+    ASSERT_EQ(1, got.size());
+    lease = got[0];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[2]);
+    EXPECT_EQ(relay_id0, lease->relay_id_);
+
+    // Relay id #0, from now - 500 to now - 100, partial from 2: 1 entry.
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+                                                          addr2,
+                                                          LeasePageSize(1),
+                                                          now_ - 500,
+                                                          now_ - 100));
+    ASSERT_EQ(1, got.size());
+    lease = got[0];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[4]);
+    EXPECT_EQ(relay_id0, lease->relay_id_);
+
+    // Relay id #0, from now - 500 to now - 100, final partial.
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+                                                          addr4,
+                                                          LeasePageSize(1),
+                                                          now_ - 500,
+                                                          now_ - 100));
+    EXPECT_EQ(0, got.size());
+}
+
+TEST_F(MySqlExtendedInfoTest, getLeases4ByRelayId) {
+    testGetLeases4ByRelayId();
+}
+
+TEST_F(MySqlExtendedInfoTest, getLeases4ByRelayIdMultiThreading) {
+    MultiThreadingTest mt(true);
+    testGetLeases4ByRelayId();
+}
+
+/// @brief Verifies that getLeases4ByRemoteId works as expected.
+void
+MySqlExtendedInfoTest::testGetLeases4ByRemoteId() {
+    // Lease manager is created with empty tables.
+    initLease4(true);
+
+    // Update leases.
+    IOAddress addr0(ADDRESS4[0]);
+    IOAddress addr1(ADDRESS4[1]);
+    IOAddress addr2(ADDRESS4[2]);
+    IOAddress addr3(ADDRESS4[3]);
+    IOAddress addr4(ADDRESS4[4]);
+    IOAddress zero = IOAddress::IPV4_ZERO_ADDRESS();
+    vector<uint8_t> remote_id0 = { 1, 2, 3, 4 };
+    vector<uint8_t> remote_id1 = { 0xaa, 0xbb, 0xcc };
+    vector<uint8_t> remote_id2 = createFromString(DUIDS[2]);
+    string user_context_txt0 = "{ \"ISC\": { \"relay-agent-info\": {";
+    user_context_txt0 += " \"sub-options\": \"020401020304\",";
+    user_context_txt0 += " \"remote-id\": \"01020304\" } } }";
+    ElementPtr user_context0;
+    ASSERT_NO_THROW(user_context0 = Element::fromJSON(user_context_txt0));
+    string user_context_txt1 = "{ \"ISC\": { \"relay-agent-info\": {";
+    user_context_txt1 += " \"sub-options\": \"0203AABBCC\",";
+    user_context_txt1 += " \"remote-id\": \"AABBCC\" } } }";
+    ElementPtr user_context1;
+    ASSERT_NO_THROW(user_context1 = Element::fromJSON(user_context_txt1));
+
+    Lease4Ptr lease;
+    // lease0: addr0, id0, now.
+    lease = leases4[0];
+    lease->remote_id_ = remote_id0;
+    lease->setContext(user_context0);
+
+    // lease1: addr1, id1, now.
+    lease = leases4[1];
+    lease->remote_id_ = remote_id1;
+    lease->setContext(user_context1);
+
+    // lease2: addr2, id0, now - 500.
+    lease = leases4[2];
+    lease->remote_id_ = remote_id0;
+    lease->setContext(user_context0);
+    lease->cltt_ = now_ - 500;
+
+    // lease3: addr3, id0, now - 800.
+    lease = leases4[3];
+    lease->remote_id_ = remote_id0;
+    lease->setContext(user_context0);
+    lease->cltt_ = now_ - 800;
+
+    // lease4: addr4, id0, now - 100.
+    lease = leases4[4];
+    lease->remote_id_ = remote_id0;
+    lease->setContext(user_context0);
+    lease->cltt_ = now_ - 100;
+
+    // Update leases.
+    for (size_t i = 0; i < leases4.size(); ++i) {
+        EXPECT_NO_THROW(lease_mgr_->updateLease4(leases4[i]));
+    }
+
+    Lease4Collection got;
+    // Unknown remote id #2: nothing.
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id2,
+                                                          zero,
+                                                          LeasePageSize(100)));
+    EXPECT_EQ(0, got.size());
+
+    // Unknown remote id #2, now - 1000, now + 1000: nothing.
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id2,
+                                                          zero,
+                                                          LeasePageSize(100),
+                                                          now_ - 1000,
+                                                          now_ + 1000));
+    EXPECT_EQ(0, got.size());
+
+    // Remote id #0, now - 2000, now - 1000: nothing.
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+                                                          zero,
+                                                          LeasePageSize(100),
+                                                          now_ - 2000,
+                                                          now_ - 1000));
+    EXPECT_EQ(0, got.size());
+
+    // Remote id #0, now + 1000, now + 2000: nothing.
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+                                                          zero,
+                                                          LeasePageSize(100),
+                                                          now_ + 1000,
+                                                          now_ + 2000));
+    EXPECT_EQ(0, got.size());
+
+    // Remote id #0: 3 entries (0, 2, 3, 4).
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+                                                          zero,
+                                                          LeasePageSize(100)));
+    ASSERT_EQ(4, got.size());
+    lease = got[0];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[0]);
+    EXPECT_EQ(remote_id0, lease->remote_id_);
+    lease = got[1];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[2]);
+    EXPECT_EQ(remote_id0, lease->remote_id_);
+    lease = got[2];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[3]);
+    EXPECT_EQ(remote_id0, lease->remote_id_);
+    lease = got[3];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[4]);
+    EXPECT_EQ(remote_id0, lease->remote_id_);
+
+    // Remote id #0, partial: 2 entries (0, 2).
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+                                                          zero,
+                                                          LeasePageSize(2)));
+    ASSERT_EQ(2, got.size());
+    lease = got[0];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[0]);
+    EXPECT_EQ(remote_id0, lease->remote_id_);
+    lease = got[1];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[2]);
+    EXPECT_EQ(remote_id0, lease->remote_id_);
+
+    // Remote id #0, partial from previous: 2 entries (3, 4).
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+                                                          addr2,
+                                                          LeasePageSize(2)));
+    ASSERT_EQ(2, got.size());
+    lease = got[0];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[3]);
+    EXPECT_EQ(remote_id0, lease->remote_id_);
+    lease = got[1];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[4]);
+    EXPECT_EQ(remote_id0, lease->remote_id_);
+
+    // Remote id #0, final partial: no entries.
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+                                                          addr4,
+                                                          LeasePageSize(2)));
+    EXPECT_EQ(0, got.size());
+
+    // Remote id #0, from now - 500: 3 entries (0, 2, 4).
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+                                                          zero,
+                                                          LeasePageSize(100),
+                                                          now_ - 500));
+    ASSERT_EQ(3, got.size());
+    lease = got[0];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[0]);
+    EXPECT_EQ(remote_id0, lease->remote_id_);
+    lease = got[1];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[2]);
+    EXPECT_EQ(remote_id0, lease->remote_id_);
+    lease = got[2];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[4]);
+    EXPECT_EQ(remote_id0, lease->remote_id_);
+
+    // Remote id #0, to now - 200: 3 entries (2, 3).
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+                                                          zero,
+                                                          LeasePageSize(100),
+                                                          0, now_ - 200));
+    ASSERT_EQ(2, got.size());
+    lease = got[0];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[2]);
+    EXPECT_EQ(remote_id0, lease->remote_id_);
+    lease = got[1];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[3]);
+    EXPECT_EQ(remote_id0, lease->remote_id_);
+
+    // Remote id #0, from now - 500 to now - 100, partial: 1 entry.
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+                                                          zero,
+                                                          LeasePageSize(1),
+                                                          now_ - 500,
+                                                          now_ - 100));
+    ASSERT_EQ(1, got.size());
+    lease = got[0];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[2]);
+    EXPECT_EQ(remote_id0, lease->remote_id_);
+
+    // Remote id #0, from now - 500 to now - 100, partial from 2: 1 entry.
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+                                                          addr2,
+                                                          LeasePageSize(1),
+                                                          now_ - 500,
+                                                          now_ - 100));
+    ASSERT_EQ(1, got.size());
+    lease = got[0];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[4]);
+    EXPECT_EQ(remote_id0, lease->remote_id_);
+
+    // Remote id #0, from now - 500 to now - 100, final partial.
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+                                                          addr4,
+                                                          LeasePageSize(1),
+                                                          now_ - 500,
+                                                          now_ - 100));
+    EXPECT_EQ(0, got.size());
+}
+
+TEST_F(MySqlExtendedInfoTest, getLeases4ByRemoteId) {
+    testGetLeases4ByRemoteId();
+}
+
+TEST_F(MySqlExtendedInfoTest, getLeases4ByRemoteIdMultiThreading) {
+    MultiThreadingTest mt(true);
+    testGetLeases4ByRemoteId();
+}
+
+}  // namespace
diff --git a/src/lib/dhcpsrv/tests/pgsql_lease_extended_info_unittest.cc b/src/lib/dhcpsrv/tests/pgsql_lease_extended_info_unittest.cc
new file mode 100644 (file)
index 0000000..0f7d21c
--- /dev/null
@@ -0,0 +1,617 @@
+// Copyright (C) 2023 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
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/io_address.h>
+#include <cc/data.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/pgsql_lease_mgr.h>
+#include <pgsql/testutils/pgsql_schema.h>
+#include <testutils/gtest_utils.h>
+#include <testutils/multi_threading_utils.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::db;
+using namespace isc::db::test;
+using namespace isc::dhcp;
+using namespace isc::test;
+using namespace isc::util;
+using namespace std;
+
+namespace {
+
+/// @brief IPv4 addresses used in the tests.
+const vector<string> ADDRESS4 = {
+    "192.0.2.0", "192.0.2.1", "192.0.2.2", "192.0.2.3",
+    "192.0.2.4", "192.0.2.5", "192.0.2.6", "192.0.2.7"
+};
+
+/// @brief DUIDs used in the tests.
+const vector<string> DUIDS = {
+    "wwwwwwww", "BBBBBBBB", "::::::::", "0123456789acdef",
+    "BBBBBBBB", "$$$$$$$$", "^^^^^^^^", "\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5"
+};
+
+/// @brief Test fixture class for extended info tests.
+class PgSqlExtendedInfoTest : public ::testing::Test {
+public:
+    /// @brief Constructor.
+    PgSqlExtendedInfoTest() {
+        // Ensure we have the proper schema with no transient data.
+        createPgSQLSchema();
+
+        // Connect to the database.
+        try {
+            LeaseMgrFactory::create(validPgSQLConnectionString());
+        } catch (...) {
+            std::cerr << "*** ERROR: unable to open database. The test\n"
+                         "*** environment is broken and must be fixed before\n"
+                         "*** the PostgreSQL tests will run correctly.\n"
+                         "*** The reason for the problem is described in the\n"
+                         "*** accompanying exception output.\n";
+            throw;
+        }
+
+        lease_mgr_ = &(LeaseMgrFactory::instance());
+        leases4.clear();
+        MultiThreadingMgr::instance().setMode(false);
+        now_ = time(0);
+    }
+
+    /// @brief Destructor.
+    ~PgSqlExtendedInfoTest() {
+        LeaseMgrFactory::destroy();
+        // If data wipe enabled, delete transient data otherwise destroy
+        // the schema.
+        destroyPgSQLSchema();
+
+        leases4.clear();
+        MultiThreadingMgr::instance().setMode(false);
+    }
+
+    /// @brief Create and set v4 leases.
+    ///
+    /// @param insert When true insert in the database.
+    void initLease4(bool insert = true) {
+        ASSERT_EQ(ADDRESS4.size(), DUIDS.size());
+        for (size_t i = 0; i < ADDRESS4.size(); ++i) {
+            Lease4Ptr lease;
+            vector<uint8_t> hwaddr_data(5, 0x08);
+            hwaddr_data.push_back(0x80 + i);
+            HWAddrPtr hwaddr(new HWAddr(hwaddr_data, HTYPE_ETHER));
+            vector<uint8_t> client_id = createFromString(DUIDS[i]);
+            IOAddress address(ADDRESS4[i]);
+            ASSERT_NO_THROW(lease.reset(new Lease4(address, hwaddr,
+                                                   &client_id[0],
+                                                   client_id.size(),
+                                                   1000, now_,
+                                                   static_cast<SubnetID>(i))));
+            leases4.push_back(lease);
+            if (insert) {
+                EXPECT_TRUE(lease_mgr_->addLease(lease));
+            }
+        }
+        ASSERT_EQ(ADDRESS4.size(), leases4.size());
+    }
+
+    /// @brief Create a vector of uint8_t from a string.
+    ///
+    /// @param content A not empty string holding the content.
+    /// @return A vector of uint8_t with the given content.
+    inline vector<uint8_t> createFromString(const string& content) {
+        vector<uint8_t> v;
+        v.resize(content.size());
+        memmove(&v[0], &content[0], v.size());
+        return (v);
+    }
+
+    /// @brief Test initLease4.
+    void testInitLease4();
+
+    /// @brief Test getLease4ByRelayId.
+    void testGetLeases4ByRelayId();
+
+    /// @brief Test getLease4ByRemoteId.
+    void testGetLeases4ByRemoteId();
+
+    /// @brief Lease manager.
+    LeaseMgr* lease_mgr_;
+
+    /// @brief V4 leases.
+    Lease4Collection leases4;
+
+    /// @brief Current timestamp.
+    time_t now_;
+};
+
+/// @brief Verifies that the lease manager can add the v4 leases.
+void
+PgSqlExtendedInfoTest::testInitLease4() {
+    initLease4();
+    EXPECT_EQ(8, leases4.size());
+    IOAddress zero = IOAddress::IPV4_ZERO_ADDRESS();
+    Lease4Collection got;
+    // Use the page version as it returns leases in order.
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4(zero, LeasePageSize(100)));
+    ASSERT_EQ(leases4.size(), got.size());
+    for (size_t i = 0; i < leases4.size(); ++i) {
+        ConstElementPtr expected = leases4[i]->toElement();
+        LeasePtr lease = got[i];
+        ASSERT_TRUE(lease);
+        EXPECT_TRUE(expected->equals(*lease->toElement()))
+            << "expected: " << expected->str() << "\n"
+            << "got: " << lease->toElement()->str() << "\n";
+    }
+}
+
+TEST_F(PgSqlExtendedInfoTest, initLease4) {
+    testInitLease4();
+}
+
+TEST_F(PgSqlExtendedInfoTest, initLease4MultiThreading) {
+    MultiThreadingTest mt(true);
+    testInitLease4();
+}
+
+/// @brief Verifies that getLeases4ByRelayId works as expected.
+void
+PgSqlExtendedInfoTest::testGetLeases4ByRelayId() {
+    // Lease manager is created with empty tables.
+    initLease4(false);
+
+    // Create leases.
+    IOAddress addr0(ADDRESS4[0]);
+    IOAddress addr1(ADDRESS4[1]);
+    IOAddress addr2(ADDRESS4[2]);
+    IOAddress addr3(ADDRESS4[3]);
+    IOAddress addr4(ADDRESS4[4]);
+    IOAddress zero = IOAddress::IPV4_ZERO_ADDRESS();
+    vector<uint8_t> relay_id0 = { 0xaa, 0xbb, 0xcc };
+    vector<uint8_t> relay_id1 = { 1, 2, 3, 4 };
+    vector<uint8_t> relay_id2 = createFromString(DUIDS[2]);
+    string user_context_txt0 = "{ \"ISC\": { \"relay-agent-info\": {";
+    user_context_txt0 += " \"sub-options\": \"0C03AABBCC\",";
+    user_context_txt0 += " \"relay-id\": \"AABBCC\" } } }";
+    ElementPtr user_context0;
+    ASSERT_NO_THROW(user_context0 = Element::fromJSON(user_context_txt0));
+    string user_context_txt1 = "{ \"ISC\": { \"relay-agent-info\": {";
+    user_context_txt1 += " \"sub-options\": \"0C0401020304\",";
+    user_context_txt1 += " \"relay-id\": \"01020304\" } } }";
+    ElementPtr user_context1;
+    ASSERT_NO_THROW(user_context1 = Element::fromJSON(user_context_txt1));
+
+    Lease4Ptr lease;
+    // lease0: addr0, id0, now.
+    lease = leases4[0];
+    ASSERT_TRUE(lease);
+    lease->relay_id_ = relay_id0;
+    lease->setContext(user_context0);
+
+    // lease1: addr1, id1, now.
+    lease = leases4[1];
+    ASSERT_TRUE(lease);
+    lease->relay_id_ = relay_id1;
+    lease->setContext(user_context1);
+
+    // lease2: addr2, id0, now - 500.
+    lease = leases4[2];
+    ASSERT_TRUE(lease);
+    lease->relay_id_ = relay_id0;
+    lease->setContext(user_context0);
+    lease->cltt_ = now_ - 500;
+
+    // lease3: addr3, id0, now - 800.
+    lease = leases4[3];
+    ASSERT_TRUE(lease);
+    lease->relay_id_ = relay_id0;
+    lease->setContext(user_context0);
+    lease->cltt_ = now_ - 800;
+
+    // lease4: addr4, id0, now - 100.
+    lease = leases4[4];
+    ASSERT_TRUE(lease);
+    lease->relay_id_ = relay_id0;
+    lease->setContext(user_context0);
+    lease->cltt_ = now_ - 100;
+
+    // Add leases.
+    for (size_t i = 0; i < leases4.size(); ++i) {
+        EXPECT_TRUE(lease_mgr_->addLease(leases4[i]));
+    }
+
+    Lease4Collection got;
+    // Unknown relay id #2: nothing.
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id2,
+                                                          zero,
+                                                          LeasePageSize(100)));
+    EXPECT_EQ(0, got.size());
+
+    // Unknown relay id #2, now - 1000, now + 1000: nothing.
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id2,
+                                                          zero,
+                                                          LeasePageSize(100),
+                                                          now_ - 1000,
+                                                          now_ + 1000));
+    EXPECT_EQ(0, got.size());
+
+    // Relay id #0, now - 2000, now - 1000: nothing.
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+                                                          zero,
+                                                          LeasePageSize(100),
+                                                          now_ - 2000,
+                                                          now_ - 1000));
+    EXPECT_EQ(0, got.size());
+
+    // Relay id #0, now + 1000, now + 2000: nothing.
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+                                                          zero,
+                                                          LeasePageSize(100),
+                                                          now_ + 1000,
+                                                          now_ + 2000));
+    EXPECT_EQ(0, got.size());
+
+    // Relay id #0: 3 entries (0, 2, 3, 4).
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+                                                          zero,
+                                                          LeasePageSize(100)));
+    ASSERT_EQ(4, got.size());
+    lease = got[0];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[0]);
+    EXPECT_EQ(relay_id0, lease->relay_id_);
+    lease = got[1];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[2]);
+    EXPECT_EQ(relay_id0, lease->relay_id_);
+    lease = got[2];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[3]);
+    EXPECT_EQ(relay_id0, lease->relay_id_);
+    lease = got[3];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[4]);
+    EXPECT_EQ(relay_id0, lease->relay_id_);
+
+    // Relay id #0, partial: 2 entries (0, 2).
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+                                                          zero,
+                                                          LeasePageSize(2)));
+    ASSERT_EQ(2, got.size());
+    lease = got[0];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[0]);
+    EXPECT_EQ(relay_id0, lease->relay_id_);
+    lease = got[1];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[2]);
+    EXPECT_EQ(relay_id0, lease->relay_id_);
+
+    // Relay id #0, partial from previous: 2 entries (3, 4).
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+                                                          addr2,
+                                                          LeasePageSize(2)));
+    ASSERT_EQ(2, got.size());
+    lease = got[0];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[3]);
+    EXPECT_EQ(relay_id0, lease->relay_id_);
+    lease = got[1];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[4]);
+    EXPECT_EQ(relay_id0, lease->relay_id_);
+
+    // Relay id #0, final partial: no entries.
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+                                                          addr4,
+                                                          LeasePageSize(2)));
+    EXPECT_EQ(0, got.size());
+
+    // Relay id #0, from now - 500: 3 entries (0, 2, 4).
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+                                                          zero,
+                                                          LeasePageSize(100),
+                                                          now_ - 500));
+    ASSERT_EQ(3, got.size());
+    lease = got[0];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[0]);
+    EXPECT_EQ(relay_id0, lease->relay_id_);
+    lease = got[1];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[2]);
+    EXPECT_EQ(relay_id0, lease->relay_id_);
+    lease = got[2];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[4]);
+    EXPECT_EQ(relay_id0, lease->relay_id_);
+
+    // Relay id #0, to now - 200: 3 entries (2, 3).
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+                                                          zero,
+                                                          LeasePageSize(100),
+                                                          0, now_ - 200));
+    ASSERT_EQ(2, got.size());
+    lease = got[0];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[2]);
+    EXPECT_EQ(relay_id0, lease->relay_id_);
+    lease = got[1];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[3]);
+    EXPECT_EQ(relay_id0, lease->relay_id_);
+
+    // Relay id #0, from now - 500 to now - 100, partial: 1 entry.
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+                                                          zero,
+                                                          LeasePageSize(1),
+                                                          now_ - 500,
+                                                          now_ - 100));
+    ASSERT_EQ(1, got.size());
+    lease = got[0];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[2]);
+    EXPECT_EQ(relay_id0, lease->relay_id_);
+
+    // Relay id #0, from now - 500 to now - 100, partial from 2: 1 entry.
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+                                                          addr2,
+                                                          LeasePageSize(1),
+                                                          now_ - 500,
+                                                          now_ - 100));
+    ASSERT_EQ(1, got.size());
+    lease = got[0];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[4]);
+    EXPECT_EQ(relay_id0, lease->relay_id_);
+
+    // Relay id #0, from now - 500 to now - 100, final partial.
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+                                                          addr4,
+                                                          LeasePageSize(1),
+                                                          now_ - 500,
+                                                          now_ - 100));
+    EXPECT_EQ(0, got.size());
+}
+
+TEST_F(PgSqlExtendedInfoTest, getLeases4ByRelayId) {
+    testGetLeases4ByRelayId();
+}
+
+TEST_F(PgSqlExtendedInfoTest, getLeases4ByRelayIdMultiThreading) {
+    MultiThreadingTest mt(true);
+    testGetLeases4ByRelayId();
+}
+
+/// @brief Verifies that getLeases4ByRemoteId works as expected.
+void
+PgSqlExtendedInfoTest::testGetLeases4ByRemoteId() {
+    // Lease manager is created with empty tables.
+    initLease4(true);
+
+    // Update leases.
+    IOAddress addr0(ADDRESS4[0]);
+    IOAddress addr1(ADDRESS4[1]);
+    IOAddress addr2(ADDRESS4[2]);
+    IOAddress addr3(ADDRESS4[3]);
+    IOAddress addr4(ADDRESS4[4]);
+    IOAddress zero = IOAddress::IPV4_ZERO_ADDRESS();
+    vector<uint8_t> remote_id0 = { 1, 2, 3, 4 };
+    vector<uint8_t> remote_id1 = { 0xaa, 0xbb, 0xcc };
+    vector<uint8_t> remote_id2 = createFromString(DUIDS[2]);
+    string user_context_txt0 = "{ \"ISC\": { \"relay-agent-info\": {";
+    user_context_txt0 += " \"sub-options\": \"020401020304\",";
+    user_context_txt0 += " \"remote-id\": \"01020304\" } } }";
+    ElementPtr user_context0;
+    ASSERT_NO_THROW(user_context0 = Element::fromJSON(user_context_txt0));
+    string user_context_txt1 = "{ \"ISC\": { \"relay-agent-info\": {";
+    user_context_txt1 += " \"sub-options\": \"0203AABBCC\",";
+    user_context_txt1 += " \"remote-id\": \"AABBCC\" } } }";
+    ElementPtr user_context1;
+    ASSERT_NO_THROW(user_context1 = Element::fromJSON(user_context_txt1));
+
+    Lease4Ptr lease;
+    // lease0: addr0, id0, now.
+    lease = leases4[0];
+    lease->remote_id_ = remote_id0;
+    lease->setContext(user_context0);
+
+    // lease1: addr1, id1, now.
+    lease = leases4[1];
+    lease->remote_id_ = remote_id1;
+    lease->setContext(user_context1);
+
+    // lease2: addr2, id0, now - 500.
+    lease = leases4[2];
+    lease->remote_id_ = remote_id0;
+    lease->setContext(user_context0);
+    lease->cltt_ = now_ - 500;
+
+    // lease3: addr3, id0, now - 800.
+    lease = leases4[3];
+    lease->remote_id_ = remote_id0;
+    lease->setContext(user_context0);
+    lease->cltt_ = now_ - 800;
+
+    // lease4: addr4, id0, now - 100.
+    lease = leases4[4];
+    lease->remote_id_ = remote_id0;
+    lease->setContext(user_context0);
+    lease->cltt_ = now_ - 100;
+
+    // Update leases.
+    for (size_t i = 0; i < leases4.size(); ++i) {
+        EXPECT_NO_THROW(lease_mgr_->updateLease4(leases4[i]));
+    }
+
+    Lease4Collection got;
+    // Unknown remote id #2: nothing.
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id2,
+                                                          zero,
+                                                          LeasePageSize(100)));
+    EXPECT_EQ(0, got.size());
+
+    // Unknown remote id #2, now - 1000, now + 1000: nothing.
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id2,
+                                                          zero,
+                                                          LeasePageSize(100),
+                                                          now_ - 1000,
+                                                          now_ + 1000));
+    EXPECT_EQ(0, got.size());
+
+    // Remote id #0, now - 2000, now - 1000: nothing.
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+                                                          zero,
+                                                          LeasePageSize(100),
+                                                          now_ - 2000,
+                                                          now_ - 1000));
+    EXPECT_EQ(0, got.size());
+
+    // Remote id #0, now + 1000, now + 2000: nothing.
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+                                                          zero,
+                                                          LeasePageSize(100),
+                                                          now_ + 1000,
+                                                          now_ + 2000));
+    EXPECT_EQ(0, got.size());
+
+    // Remote id #0: 3 entries (0, 2, 3, 4).
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+                                                          zero,
+                                                          LeasePageSize(100)));
+    ASSERT_EQ(4, got.size());
+    lease = got[0];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[0]);
+    EXPECT_EQ(remote_id0, lease->remote_id_);
+    lease = got[1];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[2]);
+    EXPECT_EQ(remote_id0, lease->remote_id_);
+    lease = got[2];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[3]);
+    EXPECT_EQ(remote_id0, lease->remote_id_);
+    lease = got[3];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[4]);
+    EXPECT_EQ(remote_id0, lease->remote_id_);
+
+    // Remote id #0, partial: 2 entries (0, 2).
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+                                                          zero,
+                                                          LeasePageSize(2)));
+    ASSERT_EQ(2, got.size());
+    lease = got[0];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[0]);
+    EXPECT_EQ(remote_id0, lease->remote_id_);
+    lease = got[1];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[2]);
+    EXPECT_EQ(remote_id0, lease->remote_id_);
+
+    // Remote id #0, partial from previous: 2 entries (3, 4).
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+                                                          addr2,
+                                                          LeasePageSize(2)));
+    ASSERT_EQ(2, got.size());
+    lease = got[0];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[3]);
+    EXPECT_EQ(remote_id0, lease->remote_id_);
+    lease = got[1];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[4]);
+    EXPECT_EQ(remote_id0, lease->remote_id_);
+
+    // Remote id #0, final partial: no entries.
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+                                                          addr4,
+                                                          LeasePageSize(2)));
+    EXPECT_EQ(0, got.size());
+
+    // Remote id #0, from now - 500: 3 entries (0, 2, 4).
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+                                                          zero,
+                                                          LeasePageSize(100),
+                                                          now_ - 500));
+    ASSERT_EQ(3, got.size());
+    lease = got[0];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[0]);
+    EXPECT_EQ(remote_id0, lease->remote_id_);
+    lease = got[1];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[2]);
+    EXPECT_EQ(remote_id0, lease->remote_id_);
+    lease = got[2];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[4]);
+    EXPECT_EQ(remote_id0, lease->remote_id_);
+
+    // Remote id #0, to now - 200: 3 entries (2, 3).
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+                                                          zero,
+                                                          LeasePageSize(100),
+                                                          0, now_ - 200));
+    ASSERT_EQ(2, got.size());
+    lease = got[0];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[2]);
+    EXPECT_EQ(remote_id0, lease->remote_id_);
+    lease = got[1];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[3]);
+    EXPECT_EQ(remote_id0, lease->remote_id_);
+
+    // Remote id #0, from now - 500 to now - 100, partial: 1 entry.
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+                                                          zero,
+                                                          LeasePageSize(1),
+                                                          now_ - 500,
+                                                          now_ - 100));
+    ASSERT_EQ(1, got.size());
+    lease = got[0];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[2]);
+    EXPECT_EQ(remote_id0, lease->remote_id_);
+
+    // Remote id #0, from now - 500 to now - 100, partial from 2: 1 entry.
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+                                                          addr2,
+                                                          LeasePageSize(1),
+                                                          now_ - 500,
+                                                          now_ - 100));
+    ASSERT_EQ(1, got.size());
+    lease = got[0];
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(*lease, *leases4[4]);
+    EXPECT_EQ(remote_id0, lease->remote_id_);
+
+    // Remote id #0, from now - 500 to now - 100, final partial.
+    EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+                                                          addr4,
+                                                          LeasePageSize(1),
+                                                          now_ - 500,
+                                                          now_ - 100));
+    EXPECT_EQ(0, got.size());
+}
+
+TEST_F(PgSqlExtendedInfoTest, getLeases4ByRemoteId) {
+    testGetLeases4ByRemoteId();
+}
+
+TEST_F(PgSqlExtendedInfoTest, getLeases4ByRemoteIdMultiThreading) {
+    MultiThreadingTest mt(true);
+    testGetLeases4ByRemoteId();
+}
+
+}  // namespace