package org.zstack.network.l3;
import org.zstack.core.db.SimpleQuery;
import org.zstack.core.db.SimpleQuery.Op;
import org.zstack.header.network.l3.*;
import org.zstack.utils.Utils;
import org.zstack.utils.logging.CLogger;
import org.zstack.utils.network.NetworkUtils;
import java.util.Collections;
import java.util.List;
import java.util.Random;
public class RandomIpAllocatorStrategy extends AbstractIpAllocatorStrategy {
private static final CLogger logger = Utils.getLogger(RandomIpAllocatorStrategy.class);
public static final IpAllocatorType type = new IpAllocatorType(L3NetworkConstant.RANDOM_IP_ALLOCATOR_STRATEGY);
@Override
public IpAllocatorType getType() {
return type;
}
@Override
public UsedIpInventory allocateIp(IpAllocateMessage msg) {
if (msg.getRequiredIp() != null) {
return allocateRequiredIp(msg);
}
SimpleQuery<IpRangeVO> query = dbf.createQuery(IpRangeVO.class);
query.add(IpRangeVO_.l3NetworkUuid, Op.EQ, msg.getL3NetworkUuid());
List<IpRangeVO> ranges = query.list();
Collections.shuffle(ranges);
do {
String ip = null;
IpRangeVO tr = null;
for (IpRangeVO r : ranges) {
ip = allocateIp(r);
tr = r;
if (ip != null) {
break;
}
}
if (ip == null) {
/* No available ip in ranges */
return null;
}
UsedIpInventory inv = l3NwMgr.reserveIp(IpRangeInventory.valueOf(tr), ip);
if (inv != null) {
return inv;
}
} while (true);
}
private String steppingAllocate(long s, long e, int total, String rangeUuid) {
int step = 254;
int failureCount = 0;
int failureCheckPoint = 5;
while (s <= e) {
// if failing failureCheckPoint times, the range is probably full,
// we check the range.
// why don't we check before steppingAllocate()? because in that case we
// have to count the used IP every time allocating a IP, and count operation
// is a full scan in DB, which is very costly
if (failureCheckPoint == failureCount++) {
SimpleQuery<UsedIpVO> q = dbf.createQuery(UsedIpVO.class);
q.add(UsedIpVO_.ipRangeUuid, Op.EQ, rangeUuid);
long count = q.count();
if (count == total) {
logger.debug(String.format("ip range[uuid:%s] has no ip available, try next one", rangeUuid));
return null;
} else {
failureCount = 0;
}
}
long te = s + step;
te = te > e ? e : te;
SimpleQuery<UsedIpVO> q = dbf.createQuery(UsedIpVO.class);
q.select(UsedIpVO_.ipInLong);
q.add(UsedIpVO_.ipInLong, Op.GTE, s);
q.add(UsedIpVO_.ipInLong, Op.LTE, te);
q.add(UsedIpVO_.ipRangeUuid, Op.EQ, rangeUuid);
List<Long> used = q.listValue();
if (te - s + 1 == used.size()) {
s += step;
continue;
}
Collections.sort(used);
return NetworkUtils.randomAllocateIpv4Address(s, te, used);
}
return null;
}
private String allocateIp(IpRangeVO vo) {
int total = vo.size();
Random random = new Random();
long s = random.nextInt(total) + NetworkUtils.ipv4StringToLong(vo.getStartIp());
long e = NetworkUtils.ipv4StringToLong(vo.getEndIp());
String ret = steppingAllocate(s ,e, total, vo.getUuid());
if (ret != null) {
return ret;
}
e = s;
s = NetworkUtils.ipv4StringToLong(vo.getStartIp());
return steppingAllocate(s ,e, total, vo.getUuid());
}
}