package org.zstack.network.l3;
import com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.transaction.annotation.Transactional;
import org.zstack.core.Platform;
import org.zstack.core.cloudbus.CloudBus;
import org.zstack.core.cloudbus.MessageSafe;
import org.zstack.core.componentloader.PluginRegistry;
import org.zstack.core.db.DatabaseFacade;
import org.zstack.core.db.DbEntityLister;
import org.zstack.core.db.SQLBatchWithReturn;
import org.zstack.core.db.SimpleQuery;
import org.zstack.core.db.SimpleQuery.Op;
import org.zstack.core.errorcode.ErrorFacade;
import org.zstack.header.AbstractService;
import org.zstack.header.apimediator.ApiMessageInterceptionException;
import org.zstack.header.errorcode.ErrorCode;
import org.zstack.header.errorcode.SysErrors;
import org.zstack.header.exception.CloudRuntimeException;
import org.zstack.header.identity.*;
import org.zstack.header.identity.Quota.QuotaOperator;
import org.zstack.header.identity.Quota.QuotaPair;
import org.zstack.header.message.APIMessage;
import org.zstack.header.message.Message;
import org.zstack.header.message.NeedQuotaCheckMessage;
import org.zstack.header.network.l2.L2NetworkVO;
import org.zstack.header.network.l2.L2NetworkVO_;
import org.zstack.header.network.l3.*;
import org.zstack.identity.AccountManager;
import org.zstack.identity.QuotaUtil;
import org.zstack.search.GetQuery;
import org.zstack.search.SearchQuery;
import org.zstack.tag.TagManager;
import org.zstack.utils.ObjectUtils;
import org.zstack.utils.Utils;
import org.zstack.utils.logging.CLogger;
import org.zstack.utils.network.NetworkUtils;
import javax.persistence.Tuple;
import javax.persistence.TypedQuery;
import java.util.*;
import java.util.concurrent.Callable;
import static org.zstack.utils.CollectionDSL.list;
public class L3NetworkManagerImpl extends AbstractService implements L3NetworkManager, ReportQuotaExtensionPoint,
ResourceOwnerPreChangeExtensionPoint {
private static final CLogger logger = Utils.getLogger(L3NetworkManagerImpl.class);
@Autowired
private CloudBus bus;
@Autowired
private DatabaseFacade dbf;
@Autowired
private PluginRegistry pluginRgty;
@Autowired
private DbEntityLister dl;
@Autowired
private AccountManager acntMgr;
@Autowired
private ErrorFacade errf;
@Autowired
private TagManager tagMgr;
private Map<String, L3NetworkFactory> l3NetworkFactories = Collections.synchronizedMap(new HashMap<String, L3NetworkFactory>());
private Map<String, IpAllocatorStrategy> ipAllocatorStrategies = Collections.synchronizedMap(new HashMap<String, IpAllocatorStrategy>());
private static final Set<Class> allowedMessageAfterSoftDeletion = new HashSet<Class>();
static {
allowedMessageAfterSoftDeletion.add(L3NetworkDeletionMsg.class);
}
@Override
@MessageSafe
public void handleMessage(Message msg) {
if (msg instanceof APIMessage) {
handleApiMessage((APIMessage) msg);
} else {
handleLocalMessage(msg);
}
}
private void handleLocalMessage(Message msg) {
if (msg instanceof L3NetworkMessage) {
passThrough((L3NetworkMessage) msg);
} else {
bus.dealWithUnknownMessage(msg);
}
}
private void handleApiMessage(APIMessage msg) {
if (msg instanceof APICreateL3NetworkMsg) {
handle((APICreateL3NetworkMsg) msg);
} else if (msg instanceof APIListL3NetworkMsg) {
handle((APIListL3NetworkMsg) msg);
} else if (msg instanceof L3NetworkMessage) {
passThrough((L3NetworkMessage) msg);
} else if (msg instanceof APIListIpRangeMsg) {
handle((APIListIpRangeMsg) msg);
} else if (msg instanceof APISearchL3NetworkMsg) {
handle((APISearchL3NetworkMsg) msg);
} else if (msg instanceof APIGetL3NetworkMsg) {
handle((APIGetL3NetworkMsg) msg);
} else if (msg instanceof APIGetL3NetworkTypesMsg) {
handle((APIGetL3NetworkTypesMsg) msg);
} else if (msg instanceof APIGetIpAddressCapacityMsg) {
handle((APIGetIpAddressCapacityMsg) msg);
} else {
bus.dealWithUnknownMessage(msg);
}
}
private void handle(final APIGetIpAddressCapacityMsg msg) {
APIGetIpAddressCapacityReply reply = new APIGetIpAddressCapacityReply();
class IpCapacity {
long total;
long avail;
}
IpCapacity ret = new Callable<IpCapacity>() {
private long calcTotalIp(List<Tuple> ts) {
long total = 0;
for (Tuple t : ts) {
String sip = t.get(0, String.class);
String eip = t.get(1, String.class);
total += NetworkUtils.getTotalIpInRange(sip, eip);
}
return total;
}
@Override
@Transactional(readOnly = true)
public IpCapacity call() {
IpCapacity ret = new IpCapacity();
if (msg.getIpRangeUuids() != null && !msg.getIpRangeUuids().isEmpty()) {
String sql = "select ipr.startIp, ipr.endIp from IpRangeVO ipr where ipr.uuid in (:uuids)";
TypedQuery<Tuple> q = dbf.getEntityManager().createQuery(sql, Tuple.class);
q.setParameter("uuids", msg.getIpRangeUuids());
List<Tuple> ts = q.getResultList();
ret.total = calcTotalIp(ts);
sql = "select count(uip) from UsedIpVO uip where uip.ipRangeUuid in (:uuids)";
TypedQuery<Long> cq = dbf.getEntityManager().createQuery(sql, Long.class);
cq.setParameter("uuids", msg.getIpRangeUuids());
Long used = cq.getSingleResult();
ret.avail = ret.total - used;
return ret;
} else if (msg.getL3NetworkUuids() != null && !msg.getL3NetworkUuids().isEmpty()) {
String sql = "select ipr.startIp, ipr.endIp from IpRangeVO ipr, L3NetworkVO l3 where ipr.l3NetworkUuid = l3.uuid and l3.uuid in (:uuids)";
TypedQuery<Tuple> q = dbf.getEntityManager().createQuery(sql, Tuple.class);
q.setParameter("uuids", msg.getL3NetworkUuids());
List<Tuple> ts = q.getResultList();
ret.total = calcTotalIp(ts);
sql = "select count(uip) from UsedIpVO uip where uip.l3NetworkUuid in (:uuids)";
TypedQuery<Long> cq = dbf.getEntityManager().createQuery(sql, Long.class);
cq.setParameter("uuids", msg.getL3NetworkUuids());
Long used = cq.getSingleResult();
ret.avail = ret.total - used;
return ret;
} else if (msg.getZoneUuids() != null && !msg.getZoneUuids().isEmpty()) {
String sql = "select ipr.startIp, ipr.endIp from IpRangeVO ipr, L3NetworkVO l3, ZoneVO zone where ipr.l3NetworkUuid = l3.uuid and l3.zoneUuid = zone.uuid and zone.uuid in (:uuids)";
TypedQuery<Tuple> q = dbf.getEntityManager().createQuery(sql, Tuple.class);
q.setParameter("uuids", msg.getZoneUuids());
List<Tuple> ts = q.getResultList();
ret.total = calcTotalIp(ts);
sql = "select count(uip) from UsedIpVO uip, L3NetworkVO l3, ZoneVO zone where uip.l3NetworkUuid = l3.uuid and l3.zoneUuid = zone.uuid and zone.uuid in (:uuids)";
TypedQuery<Long> cq = dbf.getEntityManager().createQuery(sql, Long.class);
cq.setParameter("uuids", msg.getZoneUuids());
Long used = cq.getSingleResult();
ret.avail = ret.total - used;
return ret;
}
throw new CloudRuntimeException("should not be here");
}
}.call();
reply.setTotalCapacity(ret.total);
reply.setAvailableCapacity(ret.avail);
bus.reply(msg, reply);
}
private void handle(APIGetL3NetworkTypesMsg msg) {
APIGetL3NetworkTypesReply reply = new APIGetL3NetworkTypesReply();
List<String> lst = new ArrayList<String>();
lst.addAll(L3NetworkType.getAllTypeNames());
reply.setL3NetworkTypes(lst);
bus.reply(msg, reply);
}
private void handle(APIGetL3NetworkMsg msg) {
GetQuery q = new GetQuery();
String res = q.getAsString(msg, L3NetworkInventory.class);
APIGetL3NetworkReply reply = new APIGetL3NetworkReply();
reply.setInventory(res);
bus.reply(msg, reply);
}
private void handle(APISearchL3NetworkMsg msg) {
SearchQuery<L3NetworkInventory> sq = SearchQuery.create(msg, L3NetworkInventory.class);
String content = sq.listAsString();
APISearchL3NetworkReply reply = new APISearchL3NetworkReply();
reply.setContent(content);
bus.reply(msg, reply);
}
private void handle(APIListIpRangeMsg msg) {
List<IpRangeVO> vos = dl.listByApiMessage(msg, IpRangeVO.class);
List<IpRangeInventory> invs = IpRangeInventory.valueOf(vos);
APIListIpRangeReply reply = new APIListIpRangeReply();
reply.setInventories(invs);
bus.reply(msg, reply);
}
private void passThrough(String l3NetworkUuid, Message msg) {
L3NetworkVO vo = dbf.findByUuid(l3NetworkUuid, L3NetworkVO.class);
if (vo == null && allowedMessageAfterSoftDeletion.contains(msg.getClass())) {
L3NetworkEO eo = dbf.findByUuid(l3NetworkUuid, L3NetworkEO.class);
vo = ObjectUtils.newAndCopy(eo, L3NetworkVO.class);
}
if (vo == null) {
ErrorCode err = errf.instantiateErrorCode(SysErrors.RESOURCE_NOT_FOUND,
String.format("Unable to find L3Network[uuid:%s], it may have been deleted", l3NetworkUuid));
bus.replyErrorByMessageType(msg, err);
return;
}
L3NetworkFactory factory = getL3NetworkFactory(L3NetworkType.valueOf(vo.getType()));
L3Network nw = factory.getL3Network(vo);
nw.handleMessage(msg);
}
private void passThrough(L3NetworkMessage msg) {
passThrough(msg.getL3NetworkUuid(), (Message) msg);
}
private void handle(APIListL3NetworkMsg msg) {
List<L3NetworkVO> vos = dl.listByApiMessage(msg, L3NetworkVO.class);
List<L3NetworkInventory> invs = L3NetworkInventory.valueOf(vos);
APIListL3NetworkReply reply = new APIListL3NetworkReply();
reply.setInventories(invs);
bus.reply(msg, reply);
}
private void handle(APICreateL3NetworkMsg msg) {
SimpleQuery<L2NetworkVO> query = dbf.createQuery(L2NetworkVO.class);
query.select(L2NetworkVO_.zoneUuid);
query.add(L2NetworkVO_.uuid, Op.EQ, msg.getL2NetworkUuid());
String zoneUuid = query.findValue();
assert zoneUuid != null;
L3NetworkVO vo = new L3NetworkVO();
if (msg.getResourceUuid() != null) {
vo.setUuid(msg.getResourceUuid());
} else {
vo.setUuid(Platform.getUuid());
}
vo.setDescription(msg.getDescription());
vo.setDnsDomain(msg.getDnsDomain());
vo.setL2NetworkUuid(msg.getL2NetworkUuid());
vo.setName(msg.getName());
vo.setSystem(msg.isSystem());
vo.setZoneUuid(zoneUuid);
vo.setState(L3NetworkState.Enabled);
L3NetworkFactory factory = getL3NetworkFactory(L3NetworkType.valueOf(msg.getType()));
L3NetworkInventory inv = new SQLBatchWithReturn<L3NetworkInventory>() {
@Override
protected L3NetworkInventory scripts() {
L3NetworkInventory inv = factory.createL3Network(vo, msg);
acntMgr.createAccountResourceRef(msg.getSession().getAccountUuid(), vo.getUuid(), L3NetworkVO.class);
tagMgr.createTagsFromAPICreateMessage(msg, vo.getUuid(), L3NetworkVO.class.getSimpleName());
return inv;
}
}.execute();
APICreateL3NetworkEvent evt = new APICreateL3NetworkEvent(msg.getId());
evt.setInventory(inv);
logger.debug(String.format("Successfully created L3Network[name:%s, uuid:%s]", inv.getName(), inv.getUuid()));
bus.publish(evt);
}
@Override
public String getId() {
return bus.makeLocalServiceId(L3NetworkConstant.SERVICE_ID);
}
@Override
public boolean start() {
populateExtensions();
return true;
}
private void populateExtensions() {
for (L3NetworkFactory f : pluginRgty.getExtensionList(L3NetworkFactory.class)) {
L3NetworkFactory old = l3NetworkFactories.get(f.getType().toString());
if (old != null) {
throw new CloudRuntimeException(String.format("duplicate L3NetworkFactory[%s, %s] for type[%s]", f.getClass().getName(),
old.getClass().getName(), f.getType()));
}
l3NetworkFactories.put(f.getType().toString(), f);
}
for (IpAllocatorStrategy f : pluginRgty.getExtensionList(IpAllocatorStrategy.class)) {
IpAllocatorStrategy old = ipAllocatorStrategies.get(f.getType().toString());
if (old != null) {
throw new CloudRuntimeException(String.format("duplicate IpAllocatorStrategy[%s, %s] for type[%s]", f.getClass().getName(),
old.getClass().getName(), f.getType()));
}
ipAllocatorStrategies.put(f.getType().toString(), f);
}
}
@Override
public boolean stop() {
return true;
}
@Override
public L3NetworkFactory getL3NetworkFactory(L3NetworkType type) {
L3NetworkFactory factory = l3NetworkFactories.get(type.toString());
if (factory == null) {
throw new CloudRuntimeException(String.format("Cannot find L3NetworkFactory for type(%s)", type));
}
return factory;
}
@Override
public IpAllocatorStrategy getIpAllocatorStrategy(IpAllocatorType type) {
IpAllocatorStrategy factory = ipAllocatorStrategies.get(type.toString());
if (factory == null) {
throw new CloudRuntimeException(String.format("Cannot find IpAllocatorStrategy for type(%s)", type));
}
return factory;
}
@Override
public UsedIpInventory reserveIp(IpRangeInventory ipRange, String ip) {
try {
UsedIpVO vo = new UsedIpVO(ipRange.getUuid(), ip);
vo.setIpInLong(NetworkUtils.ipv4StringToLong(ip));
String uuid = ipRange.getUuid() + ip;
uuid = UUID.nameUUIDFromBytes(uuid.getBytes()).toString().replaceAll("-", "");
vo.setUuid(uuid);
vo.setL3NetworkUuid(ipRange.getL3NetworkUuid());
vo.setNetmask(ipRange.getNetmask());
vo.setGateway(ipRange.getGateway());
vo = dbf.persistAndRefresh(vo);
return UsedIpInventory.valueOf(vo);
} catch (JpaSystemException e) {
if (e.getRootCause() instanceof MySQLIntegrityConstraintViolationException) {
logger.debug(String.format("Concurrent ip allocation. " +
"Ip[%s] in ip range[uuid:%s] has been allocated, try allocating another one. " +
"The error[Duplicate entry] printed by jdbc.spi.SqlExceptionHelper is no harm, " +
"we will try finding another ip", ip, ipRange.getUuid()));
logger.trace("", e);
} else {
throw e;
}
}
return null;
}
@Override
public boolean isIpRangeFull(IpRangeVO vo) {
int total = NetworkUtils.getTotalIpInRange(vo.getStartIp(), vo.getEndIp());
SimpleQuery<UsedIpVO> query = dbf.createQuery(UsedIpVO.class);
query.add(UsedIpVO_.ipRangeUuid, Op.EQ, vo.getUuid());
long used = query.count();
return used >= total;
}
@Override
public List<Long> getUsedIpInRange(String ipRangeUuid) {
SimpleQuery<UsedIpVO> query = dbf.createQuery(UsedIpVO.class);
query.select(UsedIpVO_.ipInLong);
query.add(UsedIpVO_.ipRangeUuid, Op.EQ, ipRangeUuid);
List<Long> used = query.listValue();
Collections.sort(used);
return used;
}
@Override
public List<Quota> reportQuota() {
QuotaOperator checker = new QuotaOperator() {
@Override
public void checkQuota(APIMessage msg, Map<String, QuotaPair> pairs) {
if (!new QuotaUtil().isAdminAccount(msg.getSession().getAccountUuid())) {
if (msg instanceof APICreateL3NetworkMsg) {
check((APICreateL3NetworkMsg) msg, pairs);
}
}
}
@Override
public void checkQuota(NeedQuotaCheckMessage msg, Map<String, QuotaPair> pairs) {
}
@Override
public List<Quota.QuotaUsage> getQuotaUsageByAccount(String accountUuid) {
Quota.QuotaUsage usage = new Quota.QuotaUsage();
usage.setName(L3NetworkConstant.QUOTA_L3_NUM);
usage.setUsed(getUsedL3(accountUuid));
return list(usage);
}
@Transactional(readOnly = true)
private long getUsedL3(String accountUuid) {
String sql = "select count(l3) from L3NetworkVO l3, AccountResourceRefVO ref where l3.uuid = ref.resourceUuid and " +
"ref.accountUuid = :auuid and ref.resourceType = :rtype";
TypedQuery<Long> q = dbf.getEntityManager().createQuery(sql, Long.class);
q.setParameter("auuid", accountUuid);
q.setParameter("rtype", L3NetworkVO.class.getSimpleName());
Long l3n = q.getSingleResult();
l3n = l3n == null ? 0 : l3n;
return l3n;
}
private void check(APICreateL3NetworkMsg msg, Map<String, QuotaPair> pairs) {
long l3Num = pairs.get(L3NetworkConstant.QUOTA_L3_NUM).getValue();
long l3n = getUsedL3(msg.getSession().getAccountUuid());
if (l3n + 1 > l3Num) {
throw new ApiMessageInterceptionException(errf.instantiateErrorCode(IdentityErrors.QUOTA_EXCEEDING,
String.format("quota exceeding. The account[uuid: %s] exceeds a quota[name: %s, value: %s]",
msg.getSession().getAccountUuid(), L3NetworkConstant.QUOTA_L3_NUM, l3Num)
));
}
}
};
Quota quota = new Quota();
quota.setOperator(checker);
quota.addMessageNeedValidation(APICreateL3NetworkMsg.class);
QuotaPair p = new QuotaPair();
p.setName(L3NetworkConstant.QUOTA_L3_NUM);
p.setValue(20);
quota.addPair(p);
return list(quota);
}
@Override
@Transactional(readOnly = true)
public void resourceOwnerPreChange(AccountResourceRefInventory ref, String newOwnerUuid) {
}
}