shared_network.cc 14.2 KB
Newer Older
1
// Copyright (C) 2017-2019 Internet Systems Consortium, Inc. ("ISC")
2 3 4 5 6
//
// 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/.

7 8
#include <config.h>

9
#include <exceptions/exceptions.h>
10 11
#include <dhcpsrv/shared_network.h>

12
using namespace isc;
13
using namespace isc::data;
14 15
using namespace isc::dhcp;

16
namespace {
17

18 19 20 21 22 23 24 25
/// @brief Implements common functionality for SharedNetwork4 and
/// SharedNetwork6 classes.
///
/// It provides mechanisms to add, remove and find subnets within shared
/// networks. It also provides means to walk over the subnets within a
/// shared network.
class Impl {
public:
26

27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
    /// @brief Adds a subnet to a shared network.
    ///
    /// This is a generic method for adding a new subnet to a shared network.
    ///
    /// @param [out] subnets Container holding subnets for this shared network.
    /// @param subnet Pointer to a subnet being added to this shared network.
    ///
    /// @tparam SubnetPtrType Type of a pointer to a subnet, i.e. Subnet4Ptr
    /// or @ref Subnet6Ptr.
    /// @tparam SubnetCollectionType Type of a container holding subnets, i.e.
    /// @ref Subnet4Collection or @ref Subnet6Collection.
    ///
    /// @throw isc::BadValue if subnet is null.
    /// @throw isc::DuplicateSubnetID if a subnet with the given subnet id
    /// already exists in this shared network.
    /// @throw InvalidOperation if a subnet is already associated with some
    /// shared network.
    template<typename SubnetPtrType, typename SubnetCollectionType>
    static void add(SubnetCollectionType& subnets, const SubnetPtrType& subnet) {
        //  Subnet must be non-null.
        if (!subnet) {
            isc_throw(BadValue, "null pointer specified when adding a subnet"
                      " to a shared network");
        }

        // Check if a subnet with this id already exists.
        if (getSubnet<SubnetPtrType>(subnets, subnet->getID())) {
            isc_throw(DuplicateSubnetID, "attempted to add subnet with a"
                      " duplicated subnet identifier " << subnet->getID());
        }

        // Check if the subnet is already associated with some network.
        NetworkPtr network;
        subnet->getSharedNetwork(network);
        if (network) {
            isc_throw(InvalidOperation, "subnet " << subnet->getID()
                      << " being added to a shared network"
                      " already belongs to a shared network");
        }

        // Add a subnet to the collection of subnets for this shared network.
        subnets.push_back(subnet);
69 70
    }

71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
    /// @brief Removes a subnet from the shared network.
    ///
    /// @param [out] subnets Container holding subnets for this shared network.
    /// @param subnet_id Identifier of a subnet to be removed.
    ///
    /// @tparam SubnetCollectionType Type of a container holding subnets, i.e.
    /// @ref Subnet4Collection or @ref Subnet6Collection.
    ///
    /// @return Erased subnet.
    /// @throw BadValue if a subnet with specified identifier doesn't exist.
    template<typename SubnetPtrType, typename SubnetCollectionType>
    static SubnetPtrType del(SubnetCollectionType& subnets,
                             const SubnetID& subnet_id) {
        auto& index = subnets.template get<SubnetSubnetIdIndexTag>();
        auto subnet_it = index.find(subnet_id);
        if (subnet_it == index.end()) {
            isc_throw(BadValue, "unable to delete subnet " << subnet_id
                      << " from shared network. Subnet doesn't belong"
                      " to this shared network");
        }
        auto subnet = *subnet_it;
        index.erase(subnet_it);
        return (subnet);
    }

    /// @brief Returns a subnet belonging to this network for a given subnet id.
    ///
    /// @param subnets Container holding subnets for this shared network.
    /// @param subnet_id Identifier of a subnet being retrieved.
    ///
    /// @tparam SubnetPtrType Type of a pointer to a subnet, i.e. Subnet4Ptr
    /// or @ref Subnet6Ptr.
    /// @tparam SubnetCollectionType Type of a container holding subnets, i.e.
    /// @ref Subnet4Collection or @ref Subnet6Collection.
    ///
    /// @return Pointer to the subnet or null if the subnet doesn't exist.
    template<typename SubnetPtrType, typename SubnetCollectionType>
    static SubnetPtrType getSubnet(const SubnetCollectionType& subnets,
                                   const SubnetID& subnet_id) {
        const auto& index = subnets.template get<SubnetSubnetIdIndexTag>();
        auto subnet_it = index.find(subnet_id);
        if (subnet_it != index.cend()) {
            return (*subnet_it);
        }

        // Subnet not found.
        return (SubnetPtrType());
    }

    /// @brief Retrieves next available subnet within shared network.
    ///
    /// This method returns next available subnet within a shared network.
    /// The subnets are ordered and retrieved using random access index
    /// (first in/first out). The next subnet means next in turn after
    /// the current subnet, which is specified as an argument. A caller
    /// can iterate over all subnets starting from any of the subnets
    /// belonging to a shared network. This subnet is called here as
    /// a first subnet and is also specified as a method argument. When the
    /// method detects that the next available subnet is a first subnet, it
    /// returns a null pointer to indicate that there are no more subnets
    /// available.
    ///
    /// The typical use case for this method is to allow DHCP server's
    /// allocation engine to walk over the available subnets within a shared
    /// network, starting from a subnet that has been selected during the
    /// "subnet selection" processing step. In some cases the allocation
    /// engine is unable to allocate resources from a selected subnet due
    /// to client classification restrictions or address shortage within
    /// its pools. It then uses this mechanism to move to another subnet
    /// belonging to the same shared network.
    ///
    /// @param subnets Container holding subnets belonging to this shared
    /// network.
    /// @param first_subnet Pointer to a subnet from which the caller is
    /// iterating over subnets within shared network. This is typically a
    /// subnet selected during "subnet selection" step.
    /// @param current_subnet Pointer to a subnet for which next subnet is
    /// to be found.
    ///
    /// @tparam SubnetPtrType Type of the pointer to a subnet, i.e.
    /// @ref Subnet4Ptr or @ref Subnet6Ptr.
    /// @tparam SubnetCollectionType Type of the container holding subnets, i.e.
    /// @ref Subnet4Collection or @ref Subnet6Collection.
    ///
    /// @return Pointer to next subnet or null pointer if no more subnets found.
    ///
    /// @throw isc::BadValue if invalid arguments specified, e.g. unable to
    /// find first or current subnet within the container.
    template<typename SubnetPtrType, typename SubnetCollectionType>
    static SubnetPtrType getNextSubnet(const SubnetCollectionType& subnets,
                                       const SubnetPtrType& first_subnet,
162
                                       const SubnetID& current_subnet) {
163 164 165 166 167 168 169 170 171 172
        // It is ok to have a shared network without any subnets, but in this
        // case there is nothing else we can return but null pointer.
        if (subnets.empty()) {
            return (SubnetPtrType());
        }

        // Need to retrieve an iterator to the current subnet first. The
        // subnet must exist in this container, thus we throw if the iterator
        // is not found.
        const auto& index = subnets.template get<SubnetSubnetIdIndexTag>();
173
        auto subnet_id_it = index.find(current_subnet);
174
        if (subnet_id_it == index.cend()) {
175
            isc_throw(BadValue, "no such subnet " << current_subnet
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
                      << " within shared network");
        }

        // We need to transform this iterator (by subnet id) to a random access
        // index iterator. Multi index container has a nice way of doing it.
        auto subnet_it = subnets.template project<SubnetRandomAccessIndexTag>(subnet_id_it);

        // Step to a next subnet within random access index.
        if (++subnet_it == subnets.cend()) {
            // If we reached the end of the container, start over from the
            // beginning.
            subnet_it = subnets.cbegin();
        }

        // Check if we have made a full circle. If we did, return a null pointer
        // to indicate that there are no more subnets.
        if ((*subnet_it)->getID() == first_subnet->getID()) {
            return (SubnetPtrType());
        }

        // Got the next subnet, so return it.
        return (*subnet_it);
    }
199 200 201 202 203 204 205 206 207 208 209 210 211 212

    /// @brief Attempts to find a subnet which is more likely to include available
    /// leases than selected subnet.
    ///
    /// When allocating unreserved leases from a shared network it is important to
    /// remember from which subnet within the shared network we have been recently
    /// handing out leases. The allocation engine can use that information to start
    /// trying allocation of the leases from that subnet rather than from the default
    /// subnet selected for this client. Starting from the default subnet causes a
    /// risk of having to walk over many subnets with exhausted address pools before
    /// getting to the subnet with available leases. This method attempts to find
    /// such subnet by inspecting "last allocation" timestamps. The one with most
    /// recent timestamp is selected.
    ///
Francis Dupont's avatar
Francis Dupont committed
213
    /// The preferred subnet must also fulfil the condition of equal client class
214 215 216 217 218
    /// with the @c selected_subnet.
    ///
    /// @param subnets Container holding subnets belonging to this shared
    /// network.
    /// @param selected_subnet Pointer to a currently selected subnet.
219 220
    /// @param lease_type Type of the lease for which preferred subnet should be
    /// returned.
221 222 223 224 225
    ///
    /// @return Pointer to a preferred subnet. It may be the same as @c selected_subnet
    /// if no better subnet was found.
    template<typename SubnetPtrType, typename SubnetCollectionType>
    static SubnetPtrType getPreferredSubnet(const SubnetCollectionType& subnets,
226 227
                                            const SubnetPtrType& selected_subnet,
                                            const Lease::Type& lease_type) {
228

229
        auto preferred_subnet = selected_subnet;
230
        for (auto s = subnets.begin(); s != subnets.end(); ++s) {
Francis Dupont's avatar
Francis Dupont committed
231
            if (((*s)->getClientClass() == selected_subnet->getClientClass()) &&
232 233
                ((*s)->getLastAllocatedTime(lease_type) >
                 selected_subnet->getLastAllocatedTime(lease_type))) {
234 235 236 237 238 239
                preferred_subnet = (*s);
            }
        }

        return (preferred_subnet);
    }
240 241 242 243 244 245
};

} // end of anonymous namespace

namespace isc {
namespace dhcp {
246

247 248 249 250 251 252 253
NetworkPtr
SharedNetwork4::sharedFromThis() {
    return (shared_from_this());
}

void
SharedNetwork4::add(const Subnet4Ptr& subnet) {
254 255 256
    Impl::add(subnets_, subnet);
    // Associate the subnet with this network.
    setSharedNetwork(subnet);
257
    subnet->setSharedNetworkName(name_);
258 259 260 261
}

void
SharedNetwork4::del(const SubnetID& subnet_id) {
262 263
    Subnet4Ptr subnet = Impl::del<Subnet4Ptr>(subnets_, subnet_id);
    clearSharedNetwork(subnet);
264
    subnet->setSharedNetworkName("");
265 266
}

267 268 269 270
void
SharedNetwork4::delAll() {
    for (auto subnet = subnets_.cbegin(); subnet != subnets_.cend(); ++subnet) {
        clearSharedNetwork(*subnet);
271
        (*subnet)->setSharedNetworkName("");
272 273 274 275
    }
    subnets_.clear();
}

276 277
Subnet4Ptr
SharedNetwork4::getSubnet(const SubnetID& subnet_id) const {
278
    return (Impl::getSubnet<Subnet4Ptr>(subnets_, subnet_id));
279 280 281 282
}

Subnet4Ptr
SharedNetwork4::getNextSubnet(const Subnet4Ptr& first_subnet,
283
                              const SubnetID& current_subnet) const {
284
    return (Impl::getNextSubnet(subnets_, first_subnet, current_subnet));
285 286
}

287 288
Subnet4Ptr
SharedNetwork4::getPreferredSubnet(const Subnet4Ptr& selected_subnet) const {
289 290
    return (Impl::getPreferredSubnet<Subnet4Ptr>(subnets_, selected_subnet,
                                                 Lease::TYPE_V4));
291 292
}

293 294
ElementPtr
SharedNetwork4::toElement() const {
295
    ElementPtr map = Network4::toElement();
296 297 298 299 300

    // Set shared network name.
    if (!name_.empty()) {
        map->set("name", Element::create(name_));
    }
301 302 303 304 305 306 307 308 309 310 311

    ElementPtr subnet4 = Element::createList();
    for (auto subnet = subnets_.cbegin(); subnet != subnets_.cend(); ++subnet) {
        subnet4->add((*subnet)->toElement());
    }

    map->set("subnet4", subnet4);

    return (map);
}

312 313 314 315 316 317 318
NetworkPtr
SharedNetwork6::sharedFromThis() {
    return (shared_from_this());
}

void
SharedNetwork6::add(const Subnet6Ptr& subnet) {
319 320 321
    Impl::add(subnets_, subnet);
    // Associate the subnet with this network.
    setSharedNetwork(subnet);
322
    subnet->setSharedNetworkName(name_);
323 324 325 326
}

void
SharedNetwork6::del(const SubnetID& subnet_id) {
327 328
    Subnet6Ptr subnet = Impl::del<Subnet6Ptr>(subnets_, subnet_id);
    clearSharedNetwork(subnet);
329
    subnet->setSharedNetworkName("");
330 331
}

332 333 334 335 336 337 338
void
SharedNetwork6::delAll() {
    for (auto subnet = subnets_.cbegin(); subnet != subnets_.cend(); ++subnet) {
        clearSharedNetwork(*subnet);
    }
    subnets_.clear();
}
339 340
Subnet6Ptr
SharedNetwork6::getSubnet(const SubnetID& subnet_id) const {
341
    return (Impl::getSubnet<Subnet6Ptr>(subnets_, subnet_id));
342 343 344 345
}

Subnet6Ptr
SharedNetwork6::getNextSubnet(const Subnet6Ptr& first_subnet,
346
                              const SubnetID& current_subnet) const {
347
    return (Impl::getNextSubnet(subnets_, first_subnet, current_subnet));
348 349
}

350 351 352 353 354 355
Subnet6Ptr
SharedNetwork6::getPreferredSubnet(const Subnet6Ptr& selected_subnet,
                                   const Lease::Type& lease_type) const {
    return (Impl::getPreferredSubnet(subnets_, selected_subnet, lease_type));
}

356 357
ElementPtr
SharedNetwork6::toElement() const {
358
    ElementPtr map = Network6::toElement();
359 360 361 362 363

    // Set shared network name.
    if (!name_.empty()) {
        map->set("name", Element::create(name_));
    }
364 365 366 367 368 369 370 371 372 373 374

    ElementPtr subnet6 = Element::createList();
    for (auto subnet = subnets_.cbegin(); subnet != subnets_.cend(); ++subnet) {
        subnet6->add((*subnet)->toElement());
    }

    map->set("subnet6", subnet6);

    return (map);
}

375 376
} // end of namespace isc::dhcp
} // end of namespace isc