shared_network.cc 14 KB
Newer Older
1
// Copyright (C) 2017-2018 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
#include <exceptions/exceptions.h>
8 9
#include <dhcpsrv/shared_network.h>

10
using namespace isc;
11
using namespace isc::data;
12 13
using namespace isc::dhcp;

14
namespace {
15

16 17 18 19 20 21 22 23
/// @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:
24

25 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
    /// @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);
67 68
    }

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
    /// @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,
160
                                       const SubnetID& current_subnet) {
161 162 163 164 165 166 167 168 169 170
        // 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>();
171
        auto subnet_id_it = index.find(current_subnet);
172
        if (subnet_id_it == index.cend()) {
173
            isc_throw(BadValue, "no such subnet " << current_subnet
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
                      << " 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);
    }
197 198 199 200 201 202 203 204 205 206 207 208 209 210

    /// @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
211
    /// The preferred subnet must also fulfil the condition of equal client class
212 213 214 215 216
    /// with the @c selected_subnet.
    ///
    /// @param subnets Container holding subnets belonging to this shared
    /// network.
    /// @param selected_subnet Pointer to a currently selected subnet.
217 218
    /// @param lease_type Type of the lease for which preferred subnet should be
    /// returned.
219 220 221 222 223
    ///
    /// @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,
224 225
                                            const SubnetPtrType& selected_subnet,
                                            const Lease::Type& lease_type) {
226

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

        return (preferred_subnet);
    }
238 239 240 241 242 243
};

} // end of anonymous namespace

namespace isc {
namespace dhcp {
244

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

void
SharedNetwork4::add(const Subnet4Ptr& subnet) {
252 253 254
    Impl::add(subnets_, subnet);
    // Associate the subnet with this network.
    setSharedNetwork(subnet);
255 256 257 258
}

void
SharedNetwork4::del(const SubnetID& subnet_id) {
259 260
    Subnet4Ptr subnet = Impl::del<Subnet4Ptr>(subnets_, subnet_id);
    clearSharedNetwork(subnet);
261 262
}

263 264 265 266 267 268 269 270
void
SharedNetwork4::delAll() {
    for (auto subnet = subnets_.cbegin(); subnet != subnets_.cend(); ++subnet) {
        clearSharedNetwork(*subnet);
    }
    subnets_.clear();
}

271 272
Subnet4Ptr
SharedNetwork4::getSubnet(const SubnetID& subnet_id) const {
273
    return (Impl::getSubnet<Subnet4Ptr>(subnets_, subnet_id));
274 275 276 277
}

Subnet4Ptr
SharedNetwork4::getNextSubnet(const Subnet4Ptr& first_subnet,
278
                              const SubnetID& current_subnet) const {
279
    return (Impl::getNextSubnet(subnets_, first_subnet, current_subnet));
280 281
}

282 283
Subnet4Ptr
SharedNetwork4::getPreferredSubnet(const Subnet4Ptr& selected_subnet) const {
284 285
    return (Impl::getPreferredSubnet<Subnet4Ptr>(subnets_, selected_subnet,
                                                 Lease::TYPE_V4));
286 287
}

288 289
ElementPtr
SharedNetwork4::toElement() const {
290
    ElementPtr map = Network4::toElement();
291 292 293 294 295

    // Set shared network name.
    if (!name_.empty()) {
        map->set("name", Element::create(name_));
    }
296 297 298 299 300 301 302 303 304 305 306

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

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

    return (map);
}

307 308 309 310 311 312 313
NetworkPtr
SharedNetwork6::sharedFromThis() {
    return (shared_from_this());
}

void
SharedNetwork6::add(const Subnet6Ptr& subnet) {
314 315 316
    Impl::add(subnets_, subnet);
    // Associate the subnet with this network.
    setSharedNetwork(subnet);
317 318 319 320
}

void
SharedNetwork6::del(const SubnetID& subnet_id) {
321 322
    Subnet6Ptr subnet = Impl::del<Subnet6Ptr>(subnets_, subnet_id);
    clearSharedNetwork(subnet);
323 324
}

325 326 327 328 329 330 331
void
SharedNetwork6::delAll() {
    for (auto subnet = subnets_.cbegin(); subnet != subnets_.cend(); ++subnet) {
        clearSharedNetwork(*subnet);
    }
    subnets_.clear();
}
332 333
Subnet6Ptr
SharedNetwork6::getSubnet(const SubnetID& subnet_id) const {
334
    return (Impl::getSubnet<Subnet6Ptr>(subnets_, subnet_id));
335 336 337 338
}

Subnet6Ptr
SharedNetwork6::getNextSubnet(const Subnet6Ptr& first_subnet,
339
                              const SubnetID& current_subnet) const {
340
    return (Impl::getNextSubnet(subnets_, first_subnet, current_subnet));
341 342
}

343 344 345 346 347 348
Subnet6Ptr
SharedNetwork6::getPreferredSubnet(const Subnet6Ptr& selected_subnet,
                                   const Lease::Type& lease_type) const {
    return (Impl::getPreferredSubnet(subnets_, selected_subnet, lease_type));
}

349 350
ElementPtr
SharedNetwork6::toElement() const {
351
    ElementPtr map = Network6::toElement();
352 353 354 355 356

    // Set shared network name.
    if (!name_.empty()) {
        map->set("name", Element::create(name_));
    }
357 358 359 360 361 362 363 364 365 366 367

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

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

    return (map);
}

368 369
} // end of namespace isc::dhcp
} // end of namespace isc