/*
* Claudia Project
* http://claudia.morfeo-project.org
*
* (C) Copyright 2010 Telefonica Investigacion y Desarrollo
* S.A.Unipersonal (Telefonica I+D)
*
* See CREDITS file for info about members and contributors.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the Affero GNU General Public License (AGPL) as
* published by the Free Software Foundation; either version 3 of the License,
* or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the Affero GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* If you want to use this software an plan to distribute a
* proprietary application in any way, and you are not licensing and
* distributing your source code under AGPL, you probably need to
* purchase a commercial license of the product. Please contact
* claudia-support@lists.morfeo-project.org for more information.
*/
package com.telefonica.claudia.slm.lifecyclemanager;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.PostLoad;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import javax.persistence.Transient;
import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;
import org.dmtf.schemas.ovf.envelope._1.ContentType;
import org.dmtf.schemas.ovf.envelope._1.ProductSectionType;
import org.dmtf.schemas.ovf.envelope._1.VirtualSystemCollectionType;
import org.dmtf.schemas.ovf.envelope._1.VirtualSystemType;
import org.dmtf.schemas.ovf.envelope._1.ProductSectionType.Property;
import com.abiquo.ovf.OVFEnvelopeUtils;
import com.abiquo.ovf.exceptions.EmptyEnvelopeException;
import com.abiquo.ovf.section.OVFProductUtils;
import com.telefonica.claudia.slm.common.SMConfiguration;
import edu.emory.mathcs.backport.java.util.Arrays;
/**
* Network range for making subnets. It stores a network address with its
* mask; whenever a new network address is needed, the NetworkRange can
* calculate the size of the address based on the maximum number of hosts
* needed, and create a subnet in its range that does not conflict with previous
* leases (method getNetwork() ).
*
* Aditionally, it offers static methods related to IP to String conversion. The
* IP format is a 4 byte array (represented as a short array where only the lsb
* part of the number). IPv4 support only.
*
*
*/
@Entity
public class NetworkRange {
private static Logger logger = Logger.getLogger(NetworkRange.class);
private static Logger monitoringLog = Logger.getLogger("Monitoring");
static {
Logger.getLogger("com.telefonica.claudia.slm.NetworkRange").setLevel(Level.INFO);
Logger.getLogger("com.telefonica.claudia.slm.NetworkRange").addAppender(
new ConsoleAppender(new PatternLayout("%-5p [%t] %c{2}: %m%n"),
"System.out"));
}
@Id
@GeneratedValue
public long internalId;
protected short[] IP;
protected short[] mask;
protected String DNS;
protected String gateway;
/**
* Map containing the actual leased Networks in the range (the key) and the
* corresponding netmask.
*/
@Transient
private Map<String, short[]> subnetLeases = new HashMap<String, short[]>();
private String serializedSubnetLeases;
/**
* Map containing the actual leased IPs for each network.
*/
@Transient
private Map<String, Set<String> > addressLeases = new HashMap<String, Set<String> >();
private String serializedAddressLeases;
protected boolean publicNetwork;
@PrePersist
@PreUpdate
public void serialize() {
StringBuffer sbSubnet =new StringBuffer();
StringBuffer sbAddress =new StringBuffer();
for (String subnet: subnetLeases.keySet()) {
sbSubnet.append(subnet).append("=");
for (short lease: subnetLeases.get(subnet)) {
sbSubnet.append(lease).append("/");
}
sbSubnet.append(";");
}
for (String address: addressLeases.keySet()) {
sbAddress.append(address).append("=");
for (String lease: addressLeases.get(address)) {
sbAddress.append(lease).append("/");
}
sbAddress.append(";");
}
this.serializedSubnetLeases=sbSubnet.toString();
this.serializedAddressLeases=sbAddress.toString();
}
@SuppressWarnings("unchecked")
@PostLoad
public void deserialize() {
String[] subnets;
String[] addresses;
if (serializedSubnetLeases.equals(""))
subnets = new String[0];
else
subnets = serializedSubnetLeases.split(";");
if (serializedSubnetLeases.equals(""))
addresses = new String[0];
else
addresses = serializedAddressLeases.split(";");
for (String subnet: subnets) {
String name = subnet.split("=")[0];
String[] contents = subnet.split("=")[1].split("/");
short [] address = new short[4];
for (int i=0; i< 4; i++)
address[i] = Short.parseShort(contents[i]);
subnetLeases.put(name, address);
}
for (String address: addresses) {
String[] components = address.split("=");
String name = components[0];
if (components.length >1) {
String[] contents = components[1].split("/");
HashSet<String> lease = new HashSet<String>();
lease.addAll(Arrays.asList(contents));
addressLeases.put(name, lease);
} else
addressLeases.put(name, new HashSet<String>());
}
}
/**
* Transform a string representation of an IPv4 address into an array
* of short (only the lesser byte is used in each short, beware the use
* boolean operators).
*
* @param ip
* A string representing an IP address in the format x.y.z.t being
* x, y, z and t in the range [0-255].
*
* @return
* A array of short with the same address in binary representation.
*
* @throws IllegalArgumentException
* If the ip address is not in the IPv4 format.
*/
public static short[] string2Ip(String ip) {
if (!ip.matches("\\d+\\.\\d+\\.\\d+\\.\\d+"))
throw new IllegalArgumentException("Wrong IPv4 format. Expected example: 192.168.0.0 Got: " + ip);
String[] ipBytes = ip.split("\\.");
short[] result = new short[4];
for (int i=0; i < 4; i++) {
try {
result[i] = Short.parseShort(ipBytes[i]);
if (result[i]>255) throw new NumberFormatException("Should be in the range [0-255].");
} catch (NumberFormatException nfe) {
throw new IllegalArgumentException("Number out of bounds. Bytes should be on the range 0-255.");
}
}
return result;
}
public static String ip2String(short[] ip) {
StringBuffer result = new StringBuffer();
for (int i=0; i < 4; i++) {
result.append(String.valueOf(ip[i]));
if (i!=3) result.append(".");
}
return result.toString();
}
public String getIP() {
return ip2String(IP);
}
public void setIP(String ip) {
this.IP = string2Ip(ip);
}
public String getMask() {
return ip2String(mask);
}
public void setMask(String mask) {
this.mask = string2Ip(mask);
}
public String getDNS() {
return DNS;
}
public void setDNS(String dns) {
this.DNS = dns;
}
public String getGateway() {
return gateway;
}
public void setGateway(String gw) {
this.gateway = gw;
}
/**
* Calculate the minimum number of bits needed to encode the given number of hosts.
* Two addresses are added to the number of hosts, representing the broadcast
* address and the network one.
*
* @param size
* @return
*/
public static int bitNumber(long size) {
return (int) Math.ceil(Math.log(size+2)/Math.log(2));
}
public static short[] numberToByteArray(int number) {
short[] result = new short[4];
result[0] = (short) ((number >> 24) & 255);
result[1] = (short) (((number & 16711680) >> 16) & 255);
result[2] = (short) (((number & 65280) >> 8) & 255);
result[3] = (short) (((number & 255)) & 255);
return result;
}
public static int byteArrayToNumber(short[] array) {
int result = 0;
for (short i=0; i < 4; i++)
result += array[i] << (3-i)*8;
return result;
}
/**
* Calculate the appropiate number of bits to assign for the new network to
* meet the size requirements.
*
*
* @param size
* @return
* The method returns an array containing the IP Address assigned for the
* network in its first position and the network mask in the second one.
*
*/
public String[] getNetwork (long size) {
int bitNumber = bitNumber(size);
int maskNumber = byteArrayToNumber(mask);
int ipNumber = byteArrayToNumber(IP);
if (ip2String(mask).equals("255.255.255.255")) {
if (subnetLeases.containsKey(ip2String(IP))) {
return null;
} else {
return new String[] {ip2String(IP), ip2String(mask)};
}
} else {
int lastMaskBit = Integer.toBinaryString(maskNumber).indexOf('0');
int solutionSize = (int) Math.pow(2, 32 - (bitNumber+lastMaskBit));
short[] maskAssigned = numberToByteArray(~0 << bitNumber);
for (int i=1; i < solutionSize; i++) {
short[] ipSolution = numberToByteArray(((i << bitNumber) & ~maskNumber) ^ ipNumber);
if (available(ipSolution, maskAssigned)) {
subnetLeases.put(ip2String(ipSolution), maskAssigned);
addressLeases.put(ip2String(ipSolution), new HashSet<String>());
return new String[] {ip2String(ipSolution), ip2String(maskAssigned)};
}
}
}
return null;
}
/**
* Gets an IP from the selected Network Address, if theres is one free. If there
* aren't any IPs left, return null
*
* @param networkAddress
* IP Address of the target network.
*
* @return
* A String representation of the leased address or null if there weren't any.
*/
public String getNetworkAddress (String networkAddress) {
int maskNumber = byteArrayToNumber(subnetLeases.get(networkAddress));
int ipNumber = byteArrayToNumber(string2Ip(networkAddress));
if (ip2String(subnetLeases.get(networkAddress)).equals("255.255.255.255")) {
if (subnetLeases.containsKey(networkAddress)) {
return null;
} else {
return networkAddress;
}
} else {
int lastMaskBit = Integer.toBinaryString(maskNumber).indexOf('0');
int solutionSize = (int) Math.pow(2, 32 - lastMaskBit);
Set<String> actualLeases = addressLeases.get(networkAddress);
for (int i=1; i < solutionSize; i++) {
String ipSolution = ip2String(numberToByteArray((i & ~maskNumber) ^ ipNumber));
if (!actualLeases.contains(ipSolution)) {
actualLeases.add(ipSolution);
return ipSolution;
}
}
return null;
}
}
public String getNetworkAddress (String networkAddress, String staticIpProp) {
int maskNumber = byteArrayToNumber(subnetLeases.get(networkAddress));
int ipNumber = byteArrayToNumber(string2Ip(networkAddress));
if (ip2String(subnetLeases.get(networkAddress)).equals("255.255.255.255")) {
if (subnetLeases.containsKey(networkAddress)) {
return null;
} else {
return networkAddress;
}
} else {
int lastMaskBit = Integer.toBinaryString(maskNumber).indexOf('0');
int solutionSize = (int) Math.pow(2, 32 - lastMaskBit);
Set<String> actualLeases = addressLeases.get(networkAddress);
for (int i=1; i < solutionSize; i++) {
String ipSolution = ip2String(numberToByteArray((i & ~maskNumber) ^ ipNumber));
if (!actualLeases.contains(ipSolution)) {
actualLeases.add(ipSolution);
return ipSolution;
}
}
return null;
}
}
private boolean available(short[] ipAssigned, short[] maskAssigned) {
leaseLoop: for (String leaseStr: subnetLeases.keySet()) {
short[] lease = string2Ip(leaseStr);
for (short i=0; i < 4; i++) {
short maskByte = subnetLeases.get(leaseStr)[i];
// Choose the bigger mask.
if (maskByte > maskAssigned[i]) {
maskByte = maskAssigned[i];
}
if ((maskByte&lease[i]) != (maskByte&ipAssigned[i])) continue leaseLoop;
}
// If the whole mask have been tested without finding a difference, the proposed network address
// is a subnet of the current lease or viceversa, so it mustn't be accepted.
return false;
}
return true;
}
/**
* Releases the subnet specified. Releasing a subnet also release all
* the given IP addresses in this subnet.
*
* @param ip
* IP Address of the subnet to be released.
*/
public void releaseSubnet(String ip) {
subnetLeases.remove(ip);
addressLeases.remove(ip);
}
/**
* Release the selected ip from the selected subnet.
*
* @param ip
* IP address to release.
*
* @param subnet
* Subnet associated to the address.
*
*/
public void releaseAddress(String ip, String subnet) {
addressLeases.get(subnet).remove(ip);
}
/**
* Marks the network range as a public IP network range, or as a private one.
* All the subnets obtained will be of the same type of the range (it makes sense).
*
* @param pub
*
* True if the network is on a public IP range; false if it falls into the
* private network ranges.
*/
public void setPublic(boolean pub) {
this.publicNetwork = pub;
}
/**
* Indicates whether the Network Range belongs to the public IP network ranges or to
* the private ones.
*
* @return
* True if the network is on a public IP range; false if it falls into the
* private network ranges.
*/
public boolean isPublic() {
return publicNetwork;
}
/**
* Check whether the network address passed as a parameter is one of the leased
* subnets of the Network Range.
*
* @param network
* Network address to be checked.
*
* @return
* True if the network address is a subnet lease of this network range. False
* otherwise.
*/
public boolean isNetworkInRange(String network) {
return subnetLeases.containsKey(network);
}
public String toString() {
StringBuffer definition= new StringBuffer();
definition.append("<Range Address=\"" + this.getIP() + "\" >\n");
for (String ipSubnet: subnetLeases.keySet()) {
definition.append("<Subnet IP=\"" + ipSubnet + "\" mask=\"" + ip2String(subnetLeases.get(ipSubnet)) + "\" />\n");
}
definition.append("</Range>\n");
return definition.toString();
}
public Set<String> getSubnets() {
return subnetLeases.keySet();
}
}