/***************************************************************************
* Copyright (c) 2012-2013 VMware, Inc. All Rights Reserved.
* 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.vmware.bdd.entity;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import com.vmware.bdd.utils.AuAssert;
import com.vmware.bdd.utils.ConfigInfo;
import com.vmware.bdd.utils.Functor.Predicate;
import com.vmware.bdd.utils.IpAddressUtil;
@Entity
@SequenceGenerator(name = "IdSequence",
sequenceName = "ip_block_seq",
allocationSize = 1)
@Table(name = "ip_block")
public class IpBlockEntity extends EntityBase implements Comparable<IpBlockEntity> {
public enum BlockType {
FREE, ASSIGNED
}
/*
* must not be null and not in the range of valid sequence number
*/
public static Long FREE_BLOCK_OWNER_ID = -1L;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "network_id", nullable = false)
private NetworkEntity network;
@Enumerated(EnumType.STRING)
@Column(name = "type", nullable = false)
private BlockType type;
@Column(name = "owner_id", nullable = false)
private Long ownerId;
// an IP block is a closed interval
@Column(name = "begin_ip", nullable = false)
private Long beginIp;
@Column(name = "end_ip", nullable = false)
private Long endIp;
public IpBlockEntity() {
}
public IpBlockEntity(NetworkEntity network, Long ownerId, BlockType type,
long beginIp,
long endIp) {
this.network = network;
this.ownerId = ownerId;
this.type = type;
this.beginIp = beginIp;
this.endIp = endIp;
validate();
}
public NetworkEntity getNetwork() {
return network;
}
public void setNetwork(NetworkEntity network) {
this.network = network;
}
public BlockType getType() {
return type;
}
public void setType(BlockType type) {
this.type = type;
}
public Long getOwnerId() {
return ownerId;
}
public void setOwnerId(Long ownerId) {
this.ownerId = ownerId;
}
public Long getBeginIp() {
return beginIp;
}
public void setBeginIp(Long beginIp) {
this.beginIp = beginIp;
}
public Long getEndIp() {
return endIp;
}
public void setEndIp(Long endIp) {
this.endIp = endIp;
}
public String getBeginAddress() {
return IpAddressUtil.getAddressFromLong(beginIp).getHostAddress();
}
public String getEndAddress() {
return IpAddressUtil.getAddressFromLong(endIp).getHostAddress();
}
public long getLength() {
return endIp - beginIp + 1;
}
/**
* order by (beginIp, type, owner, endIp)
*/
@Override
public int compareTo(IpBlockEntity o) {
/**
* compare by beginIp, this is important for merge and subtract method
*/
int cmp = getBeginIp().compareTo(o.getBeginIp());
if (cmp != 0) {
return cmp;
}
cmp = getType().compareTo(o.getType());
if (cmp != 0) {
return cmp;
}
cmp = getOwnerId().compareTo(o.getOwnerId());
if (cmp != 0) {
return cmp;
}
cmp = getEndIp().compareTo(o.getEndIp());
if (cmp != 0) {
return cmp;
}
cmp = getNetwork().compareTo(o.getNetwork());
return cmp;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((beginIp == null) ? 0 : beginIp.hashCode());
result = prime * result + ((endIp == null) ? 0 : endIp.hashCode());
result = prime * result + ((network == null) ? 0 : network.hashCode());
result = prime * result + ((ownerId == null) ? 0 : ownerId.hashCode());
result = prime * result + ((type == null) ? 0 : type.hashCode());
return result;
}
@Override
public boolean equals(Object o) {
return o instanceof IpBlockEntity && this.compareTo((IpBlockEntity) o) == 0;
}
@Override
public String toString() {
return "[" + getType() + ", " + getOwnerId() + ", " + getBeginAddress() + ", "
+ getEndAddress() + ", " + getLength() + "]";
}
public void validate() {
// the rest api layer should do the real param check
if (ConfigInfo.isDebugEnabled()) {
AuAssert.check(this.getLength() > 0, "ip block size should > 0");
AuAssert.check((this.getType() == BlockType.ASSIGNED &&
!this.getOwnerId().equals(FREE_BLOCK_OWNER_ID)) ||
(this.getType() == BlockType.FREE &&
this.getOwnerId().equals(FREE_BLOCK_OWNER_ID)),
"block type shold match owner id: " + getType() + ", " + getOwnerId());
}
}
/**
* Check whether the current block contains the other one.
*
* @param o
* the other one
* @return contained or not
*/
public boolean contains(IpBlockEntity o) {
return this.getBeginIp() <= o.getBeginIp() && this.getEndIp() >= o.getEndIp();
}
/**
* Check whether the current block is overlapped with the other one.
*
* @param o
* the other one
* @return overlapped or not
*/
public boolean isOverlapedWith(IpBlockEntity o) {
return this.getBeginIp() <= o.getEndIp() && o.getBeginIp() <= this.getEndIp();
}
/**
* Check whether the two block can be concatenated as one.
*
* @param next
* next should be greater or equal with the current one
* @return flag
*/
public boolean canConcatWith(IpBlockEntity next) {
AuAssert.check(this.getBeginIp() <= next.getBeginIp());
return this.getEndIp() + 1 >= next.getBeginIp();
}
/**
* Duplicate an entity, just like clone, but clear the id
*
* @return new transient entity
*/
public IpBlockEntity dup() {
return new IpBlockEntity(network, ownerId, type, beginIp, endIp);
}
/**
* Duplicate a IP block list, with id field cleared. This is used to create
* Hibernate transient entities.
*
* @param ipBlocks
* list to be duplicated.
* @return new transient entities
*/
public static List<IpBlockEntity> dup(Collection<IpBlockEntity> ipBlocks) {
List<IpBlockEntity> dup = new ArrayList<IpBlockEntity>(ipBlocks.size());
for (IpBlockEntity blk : ipBlocks) {
dup.add(blk.dup());
}
return dup;
}
/**
* Count the IP addresses in the list.
*
* @param ipBlocks
* @return
*/
public static long count(Collection<IpBlockEntity> ipBlocks) {
long count = 0;
for (IpBlockEntity blk : ipBlocks) {
count += blk.getLength();
}
return count;
}
/**
* Filter a list according to the predicate functor.
*
* Side effects: the objects in the returned list still referencing the
* original ones.
*
* @param ipBlocks
* input list
* @param pred
* predicate functor
* @return filtered objects which pass the predicate eveluation (true)
*/
public static List<IpBlockEntity> filter(Collection<IpBlockEntity> ipBlocks,
Predicate<IpBlockEntity> pred) {
List<IpBlockEntity> dest = new ArrayList<IpBlockEntity>(ipBlocks.size());
for (IpBlockEntity blk : ipBlocks) {
blk.validate();
if (pred.evaluate(blk)) {
dest.add(blk);
}
}
return dest;
}
/**
* Get the difference set of A and B, i.e the IPs that are in A but are not
* in B. The two set should be well-formed, i.e. well merged.
*
* @param setA
* a list of IP blocks
* @param setB
* a list of IP blocks
*
* @return difference set by A - B
*/
public static List<IpBlockEntity> subtract(Collection<IpBlockEntity> setA,
Collection<IpBlockEntity> setB) {
return subtract(new TreeSet<IpBlockEntity>(setA),
new TreeSet<IpBlockEntity>(setB));
}
/**
* Get the difference set of A and B, i.e the IPs that are in A but are not
* in B. The two set should be well-formed, i.e. well merged.
*
* Note: we don't care about the network and block type here, so do not
* subtract two sets with different network/types and save them.
*
* @param setA
* a set of IP blocks sorted by beginIp in ascending order
* @param setB
* a set of IP blocks sorted by beginIp in ascending order
*
* @return difference set by A - B
*/
public static List<IpBlockEntity> subtract(SortedSet<IpBlockEntity> setA,
SortedSet<IpBlockEntity> setB) {
List<IpBlockEntity> diffSet = new ArrayList<IpBlockEntity>();
NetworkEntity network = null;
BlockType blockType = null;
Long ownerId = null;
Iterator<IpBlockEntity> iterA = setA.iterator();
Iterator<IpBlockEntity> iterB = setB.iterator();
long a0 = 1, a1 = 0; // set to invalid block: a0 > a1
long b0 = 1, b1 = 0;
while (true) {
if (a0 > a1) {
if (!iterA.hasNext()) {
break;
}
IpBlockEntity blockA = iterA.next();
network = blockA.getNetwork(); // just to get a valid value
blockType = blockA.getType();
ownerId = blockA.getOwnerId();
a0 = IpAddressUtil.getAddressAsLong(blockA.getBeginAddress());
a1 = IpAddressUtil.getAddressAsLong(blockA.getEndAddress());
}
if (b0 > b1) {
if (!iterB.hasNext()) {
// then add all remaining blocks
diffSet.add(new IpBlockEntity(network, ownerId, blockType, a0, a1));
a0 = a1 + 1;
continue;
}
IpBlockEntity blockB = iterB.next();
network = blockB.getNetwork();
blockType = blockB.getType();
ownerId = blockB.getOwnerId();
b0 = IpAddressUtil.getAddressAsLong(blockB.getBeginAddress());
b1 = IpAddressUtil.getAddressAsLong(blockB.getEndAddress());
}
if (a0 < b0) {
if (a1 < b0) {
diffSet.add(new IpBlockEntity(network, ownerId, blockType, a0, a1));
a0 = a1 + 1;
continue;
} else {
long end = Math.min(a1, b0 - 1);
// diff
diffSet.add(new IpBlockEntity(network, ownerId, blockType, a0, end));
a0 = b0 = Math.min(a1 + 1, b1 + 1); // skip the A-B and common
}
} else {
b0 = Math.min(a1 + 1, b1 + 1); // skip the B-A and the common
a0 = Math.max(a0, b0);
}
}
return diffSet;
}
}