blob: 94b0ddca0d6110d658516a1b8162f63a5818aff2 [file] [log] [blame]
/*
* Copyright 2015-present Open Networking Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.dhcp.impl;
import com.google.common.collect.ImmutableSet;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.Service;
import org.onlab.packet.Ip4Address;
import org.onlab.packet.MacAddress;
import org.onlab.util.KryoNamespace;
import org.onosproject.dhcp.DhcpStore;
import org.onosproject.dhcp.IpAssignment;
import org.onosproject.net.HostId;
import org.onosproject.store.serializers.KryoNamespaces;
import org.onosproject.store.service.ConsistentMap;
import org.onosproject.store.service.DistributedSet;
import org.onosproject.store.service.Serializer;
import org.onosproject.store.service.StorageService;
import org.onosproject.store.service.Versioned;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Date;
import java.util.Map;
import java.util.HashMap;
import static org.onosproject.dhcp.IpAssignment.AssignmentStatus.Option_Assigned;
import static org.onosproject.dhcp.IpAssignment.AssignmentStatus.Option_RangeNotEnforced;
/**
* Manages the pool of available IP Addresses in the network and
* Remembers the mapping between MAC ID and IP Addresses assigned.
*/
@Component(immediate = true)
@Service
public class DistributedDhcpStore implements DhcpStore {
private final Logger log = LoggerFactory.getLogger(getClass());
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected StorageService storageService;
private ConsistentMap<HostId, IpAssignment> allocationMap;
private DistributedSet<Ip4Address> freeIPPool;
private static Ip4Address startIPRange;
private static Ip4Address endIPRange;
// Hardcoded values are default values.
private static int timeoutForPendingAssignments = 60;
@Activate
protected void activate() {
allocationMap = storageService.<HostId, IpAssignment>consistentMapBuilder()
.withName("onos-dhcp-assignedIP")
.withSerializer(Serializer.using(
new KryoNamespace.Builder()
.register(KryoNamespaces.API)
.register(IpAssignment.class,
IpAssignment.AssignmentStatus.class,
Date.class)
.build("dhcp")))
.build();
freeIPPool = storageService.<Ip4Address>setBuilder()
.withName("onos-dhcp-freeIP")
.withSerializer(Serializer.using(KryoNamespaces.API))
.build()
.asDistributedSet();
log.info("Started");
}
@Deactivate
protected void deactivate() {
log.info("Stopped");
}
@Override
public Ip4Address suggestIP(HostId hostId, Ip4Address requestedIP) {
IpAssignment assignmentInfo;
if (allocationMap.containsKey(hostId)) {
assignmentInfo = allocationMap.get(hostId).value();
IpAssignment.AssignmentStatus status = assignmentInfo.assignmentStatus();
Ip4Address ipAddr = assignmentInfo.ipAddress();
if (assignmentInfo.assignmentStatus().equals(Option_RangeNotEnforced)) {
return assignmentInfo.ipAddress();
} else if (status == Option_Assigned ||
status == IpAssignment.AssignmentStatus.Option_Requested) {
// Client has a currently Active Binding.
if (ipWithinRange(ipAddr)) {
return ipAddr;
}
} else if (status == IpAssignment.AssignmentStatus.Option_Expired) {
// Client has a Released or Expired Binding.
if (freeIPPool.contains(ipAddr)) {
assignmentInfo = IpAssignment.builder()
.ipAddress(ipAddr)
.timestamp(new Date())
.leasePeriod(timeoutForPendingAssignments)
.assignmentStatus(IpAssignment.AssignmentStatus.Option_Requested)
.build();
if (freeIPPool.remove(ipAddr)) {
allocationMap.put(hostId, assignmentInfo);
return ipAddr;
}
}
}
} else if (requestedIP.toInt() != 0) {
// Client has requested an IP.
if (freeIPPool.contains(requestedIP)) {
assignmentInfo = IpAssignment.builder()
.ipAddress(requestedIP)
.timestamp(new Date())
.leasePeriod(timeoutForPendingAssignments)
.assignmentStatus(IpAssignment.AssignmentStatus.Option_Requested)
.build();
if (freeIPPool.remove(requestedIP)) {
allocationMap.put(hostId, assignmentInfo);
return requestedIP;
}
}
}
// Allocate a new IP from the server's pool of available IP.
Ip4Address nextIPAddr = fetchNextIP();
if (nextIPAddr != null) {
assignmentInfo = IpAssignment.builder()
.ipAddress(nextIPAddr)
.timestamp(new Date())
.leasePeriod(timeoutForPendingAssignments)
.assignmentStatus(IpAssignment.AssignmentStatus.Option_Requested)
.build();
allocationMap.put(hostId, assignmentInfo);
}
return nextIPAddr;
}
@Override
public boolean assignIP(HostId hostId, IpAssignment ipAssignment) {
log.trace("Assign IP Called HostId: {}, ipAssignment: {}",
hostId, ipAssignment);
IpAssignment newAssignment = null;
Versioned<IpAssignment> versionedAssignment = allocationMap.get(hostId);
Ip4Address requestedIp = ipAssignment.ipAddress();
if (versionedAssignment == null) {
// this is new IP assignment of static mapping
// dynamic assignment is done in suggestIP
if (ipAssignment.assignmentStatus().equals(Option_RangeNotEnforced)) {
newAssignment = ipAssignment;
} else if (freeIPPool.remove(requestedIp)) {
newAssignment = IpAssignment.builder(ipAssignment)
.assignmentStatus(Option_Assigned)
.timestamp(new Date())
.build();
} else {
log.trace("Failed to assign IP for {}", ipAssignment);
return false;
}
log.trace("Assigned {}", newAssignment);
return allocationMap.putIfAbsent(hostId, newAssignment) == null;
// TODO: handle the case where map changed.
} else {
// this is lease renew or rebinding
// update assignment status and time stamp, and keep the others
IpAssignment existingAssignment = versionedAssignment.value();
if (!existingAssignment.ipAddress().equals(requestedIp)) {
// return false if existing assignment is not for the
// requested host
log.trace("Failed to assign IP for {}", ipAssignment);
return false;
}
switch (existingAssignment.assignmentStatus()) {
case Option_RangeNotEnforced:
newAssignment = IpAssignment.builder(existingAssignment)
.timestamp(new Date())
.build();
break;
case Option_Expired:
if (!freeIPPool.remove(requestedIp)) {
// requested IP is expired for this host and reserved to the other host
return false;
}
case Option_Assigned:
case Option_Requested:
newAssignment = IpAssignment.builder(existingAssignment)
.timestamp(new Date())
.assignmentStatus(Option_Assigned)
.build();
break;
default:
break;
}
log.trace("Assigned {}", newAssignment);
return allocationMap.replace(hostId, versionedAssignment.version(), newAssignment);
}
}
@Override
public Ip4Address releaseIP(HostId hostId) {
if (allocationMap.containsKey(hostId)) {
// If the IP has been assigned with Option_RangeNotEnforced,
// we do not release the IP address nor remove the host from HostService.
// Therefore, if the IP is assigned statically, the IP needs to be released statically.
Versioned<IpAssignment> assignmentVersioned = allocationMap.get(hostId);
if (Versioned.valueOrNull(assignmentVersioned) != null &&
assignmentVersioned.value().assignmentStatus().equals(Option_RangeNotEnforced)) {
return null;
}
IpAssignment newAssignment = IpAssignment.builder(allocationMap.get(hostId).value())
.assignmentStatus(IpAssignment.AssignmentStatus.Option_Expired)
.build();
Ip4Address freeIP = newAssignment.ipAddress();
allocationMap.put(hostId, newAssignment);
if (ipWithinRange(freeIP)) {
freeIPPool.add(freeIP);
}
return freeIP;
}
return null;
}
@Override
public void setDefaultTimeoutForPurge(int timeInSeconds) {
timeoutForPendingAssignments = timeInSeconds;
}
@Override
public Map<HostId, IpAssignment> listAssignedMapping() {
Map<HostId, IpAssignment> validMapping = new HashMap<>();
IpAssignment assignment;
for (Map.Entry<HostId, Versioned<IpAssignment>> entry: allocationMap.entrySet()) {
assignment = entry.getValue().value();
if (assignment.assignmentStatus() == Option_Assigned
|| assignment.assignmentStatus() == Option_RangeNotEnforced) {
validMapping.put(entry.getKey(), assignment);
}
}
return validMapping;
}
@Override
public Map<HostId, IpAssignment> listAllMapping() {
Map<HostId, IpAssignment> validMapping = new HashMap<>();
for (Map.Entry<HostId, Versioned<IpAssignment>> entry: allocationMap.entrySet()) {
validMapping.put(entry.getKey(), entry.getValue().value());
}
return validMapping;
}
@Override
public boolean assignStaticIP(MacAddress macAddress, IpAssignment ipAssignment) {
HostId host = HostId.hostId(macAddress);
return assignIP(host, ipAssignment);
}
@Override
public boolean removeStaticIP(MacAddress macID) {
HostId host = HostId.hostId(macID);
if (allocationMap.containsKey(host)) {
IpAssignment assignment = allocationMap.get(host).value();
if (assignment.assignmentStatus().equals(Option_RangeNotEnforced)) {
allocationMap.remove(host);
return true;
}
Ip4Address freeIP = assignment.ipAddress();
if (assignment.leasePeriod() < 0) {
allocationMap.remove(host);
if (ipWithinRange(freeIP)) {
freeIPPool.add(freeIP);
}
return true;
}
}
return false;
}
@Override
public Iterable<Ip4Address> getAvailableIPs() {
return ImmutableSet.copyOf(freeIPPool);
}
@Override
public void populateIPPoolfromRange(Ip4Address startIP, Ip4Address endIP) {
// Clear all entries from previous range.
allocationMap.clear();
freeIPPool.clear();
startIPRange = startIP;
endIPRange = endIP;
int lastIP = endIP.toInt();
Ip4Address nextIP;
for (int loopCounter = startIP.toInt(); loopCounter <= lastIP; loopCounter++) {
nextIP = Ip4Address.valueOf(loopCounter);
freeIPPool.add(nextIP);
}
log.debug("Updated free IP pool {}:{} size:{}", startIP, endIP, freeIPPool.size());
}
@Override
public IpAssignment getIpAssignmentFromAllocationMap(HostId hostId) {
if (allocationMap.get(hostId) != null) {
return allocationMap.get(hostId).value();
} else {
return null;
}
}
/**
* Fetches the next available IP from the free pool pf IPs.
*
* @return the next available IP address
*/
private Ip4Address fetchNextIP() {
for (Ip4Address freeIP : freeIPPool) {
if (freeIPPool.remove(freeIP)) {
return freeIP;
}
}
return null;
}
/**
* Returns true if the given ip is within the range of available IPs.
*
* @param ip given ip address
* @return true if within range, false otherwise
*/
private boolean ipWithinRange(Ip4Address ip) {
if ((ip.toInt() >= startIPRange.toInt()) && (ip.toInt() <= endIPRange.toInt())) {
return true;
}
return false;
}
}