package cloudone.cumulonimbus;
import cloudone.cumulonimbus.model.Cluster;
import cloudone.cumulonimbus.model.RegisteredRuntime;
import org.slf4j.LoggerFactory;
import java.security.InvalidParameterException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* Manages for running services.
*
* @author Martin Mares (martin.mares at oracle.com)
*/
public class PortService {
private static class PortRange {
private final int from;
private final int to;
private int index = -1;
public PortRange(String range) throws Exception {
if (range == null) {
throw new Exception("Invalid port range " + range);
}
int ind = range.indexOf('-');
String sFrom = range;
String sTo = "";
if (ind >= 0) {
sFrom = range.substring(0, ind).trim();
sTo = range.substring(ind + 1).trim();
if (sTo.length() == 0) {
sTo = String.valueOf(Integer.MAX_VALUE);
}
}
if (sFrom.length() == 0) {
sFrom = sTo;
} else if (sTo.length() == 0) {
sTo = sFrom;
}
if (sFrom.length() == 0 && sTo.length() == 0) {
throw new Exception("Invalid port range " + range);
}
from = Integer.parseInt(sFrom);
to = Integer.parseInt(sTo);
}
public PortRange(int from, int to) {
this.from = from;
this.to = to;
}
public synchronized int getNext() {
if (index < 0) {
index = from;
return index;
}
index++;
if (index > to) {
index = from;
}
return index;
}
}
private static final long RESERVATION_PARIOD = 5 * 60 * 1000L; //5 minutes
public static final String KEY_PORT_RANGE = "port.range";
public static final String KEY_PORT_RANGE_ADMIN = "port.range.admin";
private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(PortService.class);
private static PortService INSTANCE;
private final PortRange appRange;
private final PortRange adminRange;
private final Map<Integer, RegisteredRuntime> ports = new HashMap<>();
private final Map<Integer, Long> reservations = new HashMap<>();
private PortService(Properties properties) throws Exception {
adminRange = new PortRange(properties.getProperty(KEY_PORT_RANGE_ADMIN, "4300-4399"));
appRange = new PortRange(properties.getProperty(KEY_PORT_RANGE, "4400-4499"));
}
/**
* Registers new runtime with all its ports.
*
* @param runtime Rutime for registration
* @return application registretion id
* @throws Exception in any case of the conflict
*/
private synchronized void registerRuntime(RegisteredRuntime runtime) throws Exception {
if (runtime == null) {
throw new InvalidParameterException("RegisteredRuntime parameter cannot be null!");
}
RegisteredRuntime rr2 = ports.get(runtime.getAdminPort());
if (rr2 != null) {
if (rr2.equals(runtime)) {
return; //This is the same => OK
} else {
throw new Exception("Admin port conflict!");
}
}
for (Integer port : runtime.getApplicationPorts().values()) {
if (ports.containsKey(port)) {
throw new Exception("Application port conflict!");
}
}
//ALL is ok. Do registration
ports.put(runtime.getAdminPort(), runtime);
reservations.remove(runtime.getAdminPort());
for (Integer port : runtime.getApplicationPorts().values()) {
ports.put(port, runtime);
reservations.remove(port);
}
}
private synchronized void unregisterRuntime(RegisteredRuntime runtime) {
ports.remove(runtime.getAdminPort());
for (Integer port : runtime.getApplicationPorts().values()) {
ports.remove(port);
}
}
private synchronized int reserve(PortRange range) throws Exception {
int firstPort = range.getNext();
int result = firstPort;
while (true) {
Long validTo = reservations.get(result);
if (validTo != null && validTo < System.currentTimeMillis()) {
reservations.remove(result);
validTo = null;
}
if (validTo == null && !ports.containsKey(result)) {
reservations.put(result, System.currentTimeMillis() + RESERVATION_PARIOD);
return result;
} else {
result = range.getNext();
if (result == firstPort) {
throw new Exception("No avialble port!");
}
}
}
}
public synchronized int reserveAdminPort() throws Exception {
return reserve(adminRange);
}
public synchronized int reserveApplicationPort() throws Exception {
return reserve(appRange);
}
public synchronized RegisteredRuntime getRegisteredRuntime(int port) {
return ports.get(port);
}
ServiceRegistryService.RegistrationListener getNewListener() {
return new ServiceRegistryService.RegistrationListener() {
@Override
public void register(RegisteredRuntime runtime, Cluster cluster) throws Exception {
registerRuntime(runtime);
}
@Override
public void unregister(RegisteredRuntime runtime, Cluster cluster) {
unregisterRuntime(runtime);
}
};
}
static PortService init(Properties properties) throws Exception {
LOGGER.info("Starting PortService");
INSTANCE = new PortService(properties);
return INSTANCE;
}
public static PortService getInstance() {
return INSTANCE;
}
}