/*
* Copyright 2011 Edmunds.com, Inc.
*
* 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 com.edmunds.etm.loadbalancer.impl;
import com.edmunds.etm.loadbalancer.api.LoadBalancerConfig;
import com.google.common.collect.Sets;
import org.apache.commons.lang.Validate;
import org.apache.log4j.Logger;
import java.util.Collection;
import java.util.Set;
/**
* Implementation of an in memory address pool.
* <p/>
* TODO Replace this with a ZooKeeper implementation.
*/
public class Inet4AddressPool {
private static final Logger logger = Logger.getLogger(Inet4AddressPool.class);
private final Set<OrderedInet4Address> issuedAddresses;
private final OrderedInet4Address minAddress;
private final OrderedInet4Address maxAddress;
private OrderedInet4Address lastIssuedAddress;
public Inet4AddressPool(LoadBalancerConfig loadBalancerConfig) {
this(loadBalancerConfig.getIpPoolStart(), loadBalancerConfig.getIpPoolEnd());
}
/**
* Creates a pool of address from the min address to the max address (inclusive of both addresses).
*
* @param minAddress the first address available for use.
* @param maxAddress the last address available for use.
*/
public Inet4AddressPool(String minAddress, String maxAddress) {
this(new OrderedInet4Address(minAddress), new OrderedInet4Address(maxAddress));
}
/**
* Creates a pool of address from the min address to the max address (inclusive of both addresses).
*
* @param minAddress the first address available for use.
* @param maxAddress the last address available for use.
*/
public Inet4AddressPool(OrderedInet4Address minAddress, OrderedInet4Address maxAddress) {
// Technically the pool could have exactly one entry (minAddress == maxAddress).
Validate.notNull(minAddress);
Validate.notNull(maxAddress);
Validate.isTrue(minAddress.compareTo(maxAddress) <= 0);
this.issuedAddresses = Sets.newHashSet();
this.minAddress = minAddress;
this.maxAddress = maxAddress;
this.lastIssuedAddress = maxAddress;
}
/**
* Tests if the given address has already been allocated.
*
* @param candidate the candidate to test.
* @return true if the address has been allocated.
*/
public synchronized boolean isAddressAllocated(OrderedInet4Address candidate) {
return issuedAddresses.contains(candidate);
}
/**
* Tests an address to see if it falls inside the range managed by this pool.
*
* @param candidate the candidate address to be tested
* @return true if the address is in the range.
*/
public boolean isAddressInRange(OrderedInet4Address candidate) {
return minAddress.compareTo(candidate) <= 0 && maxAddress.compareTo(candidate) >= 0;
}
/**
* Resets the internal pool to the list of address that have been allocated.
*
* @param addresses the list of allocated addresses (typically loaded from a load balancer)
*/
public synchronized void setAllocatedAddresses(Collection<OrderedInet4Address> addresses) {
logger.debug("Setting allocated IP addresses");
issuedAddresses.clear();
for (OrderedInet4Address candidate : addresses) {
if (isAddressInRange(candidate)) {
logger.debug("Address previously allocated: " + candidate.toString());
issuedAddresses.add(candidate);
}
}
}
/**
* Issues an address from the pool.
*
* @return the issued address.
*/
public synchronized OrderedInet4Address issueAddress() {
OrderedInet4Address candidate = lastIssuedAddress;
int wrap = 0;
while (wrap < 2) {
candidate = candidate.getNextAddress();
if (!isAddressInRange(candidate)) {
wrap++;
candidate = minAddress;
}
if (!isAddressAllocated(candidate)) {
issuedAddresses.add(candidate);
lastIssuedAddress = candidate;
return candidate;
}
}
return null;
}
/**
* Releases an address back to the pool to be re-used.
*
* @param address the address being released.
* @throws IllegalArgumentException if the address is not currently in the list of issues addresses.
*/
public synchronized void releaseAddress(String address) throws IllegalArgumentException {
releaseAddress(new OrderedInet4Address(address));
}
/**
* Releases an address back to the pool to be re-used.
*
* @param address the address being released.
*/
public synchronized void releaseAddress(OrderedInet4Address address) {
boolean removed = issuedAddresses.remove(address);
if (!removed) {
logger.warn("Attempt to remove an address that is not in the pool: " + address);
}
}
}