package io.fathom.cloud.compute.networks;
import io.fathom.cloud.CloudException;
import io.fathom.cloud.compute.scheduler.SchedulerHost;
import io.fathom.cloud.compute.scheduler.SchedulerHost.SchedulerHostNetwork;
import io.fathom.cloud.compute.state.ComputeRepository;
import io.fathom.cloud.compute.state.NetworkStateStore;
import io.fathom.cloud.protobuf.CloudModel.InstanceData;
import io.fathom.cloud.protobuf.CloudModel.VirtualIpPoolData;
import io.fathom.cloud.server.model.Project;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.List;
import java.util.Random;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Lists;
@Singleton
public class NetworkPools {
private static final Logger log = LoggerFactory.getLogger(NetworkPools.class);
@Inject
NetworkStateStore networkStateStore;
@Inject
ComputeRepository repository;
static final Random random = new Random();
public NetworkPool buildPool(VirtualIpPoolData data) {
switch (data.getType()) {
case AMAZON_EC2:
return new Ec2IpNetworkPool(this, data);
case LAYER_3:
return new MappableIpNetworkPool(this, data);
default:
throw new IllegalArgumentException();
}
}
public NetworkPoolAllocation allocateIp(Project project, NetworkPool pool) throws CloudException {
List<NetworkPool> pools = Lists.newArrayList();
pools.add(pool);
return allocateIps(project, pools).get(0);
}
public List<NetworkPoolAllocation> allocateIps(Project project, SchedulerHost host, InstanceData instance)
throws CloudException {
List<NetworkPool> pools = Lists.newArrayList();
for (SchedulerHostNetwork network : host.getNetworks()) {
NetworkPool pool = new HostNetworkPool(this, host, network, instance);
pools.add(pool);
}
return allocateIps(project, pools);
}
private List<NetworkPoolAllocation> allocateIps(Project project, List<NetworkPool> pools) throws CloudException {
for (int attempt = 0; attempt < 16; attempt++) {
List<InetAddress> ips = Lists.newArrayList();
List<NetworkPoolAllocation> allocated = Lists.newArrayList();
// We try to align the addresses across the networks, no matter what
// size they are
// TODO: This ties us to the smallest network. We really should pick
// that one first
// TODO: We should actually let the smallest network generate the
// random, and then continue
byte[] rand = new byte[16];
synchronized (random) {
random.nextBytes(rand);
// IPs ending in 0, while usually valid, definitely cause
// confusion
if (rand[15] == 0) {
rand[15] = 1;
}
}
boolean ok = true;
for (NetworkPool pool : pools) {
InetAddress ip = pool.checkIpAvailable(rand);
if (ip == null) {
log.warn("Concurrent creation while allocating IP; retrying");
ok = false;
break;
}
ips.add(ip);
}
if (!ok) {
continue;
}
for (int i = 0; i < pools.size(); i++) {
NetworkPool pool = pools.get(i);
NetworkPoolAllocation alloc = pool.markIpAllocated(project, ips.get(i));
if (alloc == null) {
for (NetworkPoolAllocation free : allocated) {
try {
free.releaseIp();
} catch (CloudException e) {
log.error("Error while releasing IPs", e);
}
}
log.warn("Error allocating ip due to concurrent activity");
ok = false;
break;
}
allocated.add(alloc);
}
if (ok) {
return allocated;
}
}
// Very suspicious...
throw new IllegalStateException("Unable to allocate IP due to concurrent activity (despite retries)");
}
public static InetAddress toAddress(byte[] ip) {
try {
return InetAddress.getByAddress(ip);
} catch (UnknownHostException e) {
throw new IllegalArgumentException();
}
}
}