package org.zstack.network.service.vip;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.zstack.core.Platform;
import org.zstack.core.cascade.CascadeFacade;
import org.zstack.core.cloudbus.CloudBus;
import org.zstack.core.cloudbus.CloudBusCallBack;
import org.zstack.core.cloudbus.MessageSafe;
import org.zstack.core.componentloader.PluginExtension;
import org.zstack.core.componentloader.PluginRegistry;
import org.zstack.core.db.DatabaseFacade;
import org.zstack.core.db.SQLBatchWithReturn;
import org.zstack.core.errorcode.ErrorFacade;
import org.zstack.core.workflow.FlowChainBuilder;
import org.zstack.core.workflow.ShareFlow;
import org.zstack.header.AbstractService;
import org.zstack.header.apimediator.ApiMessageInterceptionException;
import org.zstack.header.core.workflow.*;
import org.zstack.header.errorcode.ErrorCode;
import org.zstack.header.errorcode.OperationFailureException;
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.MessageReply;
import org.zstack.header.message.NeedQuotaCheckMessage;
import org.zstack.header.network.l3.*;
import org.zstack.identity.AccountManager;
import org.zstack.identity.QuotaUtil;
import org.zstack.tag.TagManager;
import org.zstack.utils.DebugUtils;
import org.zstack.utils.Utils;
import org.zstack.utils.logging.CLogger;
import javax.persistence.TypedQuery;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.zstack.utils.CollectionDSL.list;
/**
*/
public class VipManagerImpl extends AbstractService implements VipManager, ReportQuotaExtensionPoint {
private static final CLogger logger = Utils.getLogger(VipManagerImpl.class);
@Autowired
private CloudBus bus;
@Autowired
private DatabaseFacade dbf;
@Autowired
private ErrorFacade errf;
@Autowired
private PluginRegistry pluginRgty;
@Autowired
private AccountManager acntMgr;
@Autowired
private CascadeFacade casf;
@Autowired
private TagManager tagMgr;
private Map<String, VipReleaseExtensionPoint> vipReleaseExts = new HashMap<String, VipReleaseExtensionPoint>();
private Map<String, VipBackend> vipBackends = new HashMap<String, VipBackend>();
private Map<String, VipFactory> factories = new HashMap<>();
private List<String> releaseVipByApiFlowNames;
private FlowChainBuilder releaseVipByApiFlowChainBuilder;
private void populateExtensions() {
List<PluginExtension> exts = pluginRgty.getExtensionByInterfaceName(VipReleaseExtensionPoint.class.getName());
for (PluginExtension ext : exts) {
VipReleaseExtensionPoint extp = (VipReleaseExtensionPoint) ext.getInstance();
VipReleaseExtensionPoint old = vipReleaseExts.get(extp.getVipUse());
if (old != null) {
throw new CloudRuntimeException(String.format("duplicate VirtualRouterVipReleaseExtensionPoint for %s, old[%s], new[%s]", old.getClass().getName(), extp.getClass().getName(), old.getVipUse()));
}
vipReleaseExts.put(extp.getVipUse(), extp);
}
exts = pluginRgty.getExtensionByInterfaceName(VipBackend.class.getName());
for (PluginExtension ext : exts) {
VipBackend extp = (VipBackend) ext.getInstance();
VipBackend old = vipBackends.get(extp.getServiceProviderTypeForVip());
if (old != null) {
throw new CloudRuntimeException(
String.format("duplicate VipBackend[%s, %s] for provider type[%s]", old.getClass().getName(), extp.getClass().getName(), extp.getServiceProviderTypeForVip())
);
}
vipBackends.put(extp.getServiceProviderTypeForVip(), extp);
}
for (VipFactory ext : pluginRgty.getExtensionList(VipFactory.class)) {
VipFactory old = factories.get(ext.getNetworkServiceProviderType());
if (old != null) {
throw new CloudRuntimeException(String.format("duplicate VipFactory[%s, %s] for the network service provider type[%s]",
old.getClass(), ext.getClass(), ext.getNetworkServiceProviderType()));
}
factories.put(ext.getNetworkServiceProviderType(), ext);
}
}
public VipReleaseExtensionPoint getVipReleaseExtensionPoint(String use) {
VipReleaseExtensionPoint extp = vipReleaseExts.get(use);
if (extp == null) {
throw new CloudRuntimeException(String.format("cannot VipReleaseExtensionPoint for use[%s]", use));
}
return extp;
}
@Override
public FlowChain getReleaseVipChain() {
return releaseVipByApiFlowChainBuilder.build();
}
@Override
public VipFactory getVipFactory(String networkServiceProviderType) {
VipFactory f = factories.get(networkServiceProviderType);
DebugUtils.Assert(f != null, String.format("cannot find the VipFactory for the network service provider type[%s]", networkServiceProviderType));
return f;
}
@Override
@MessageSafe
public void handleMessage(Message msg) {
if (msg instanceof VipMessage) {
passThrough((VipMessage) msg);
} else if (msg instanceof APIMessage) {
handleApiMessage((APIMessage) msg);
} else {
handleLocalMessage(msg);
}
}
private void passThrough(VipMessage msg) {
VipVO vip = dbf.findByUuid(msg.getVipUuid(), VipVO.class);
if (vip == null) {
throw new OperationFailureException(errf.instantiateErrorCode(SysErrors.RESOURCE_NOT_FOUND,
String.format("cannot find the vip[uuid:%s], it may have been deleted", msg.getVipUuid())
));
}
VipBase v = new VipBase(vip);
v.handleMessage((Message) msg);
}
private void handleLocalMessage(Message msg) {
bus.dealWithUnknownMessage(msg);
}
private void handleApiMessage(APIMessage msg) {
if (msg instanceof APICreateVipMsg) {
handle((APICreateVipMsg) msg);
} else {
bus.dealWithUnknownMessage(msg);
}
}
private void handle(final APICreateVipMsg msg) {
final APICreateVipEvent evt = new APICreateVipEvent(msg.getId());
FlowChain chain = FlowChainBuilder.newShareFlowChain();
chain.setName(String.format("create-vip-%s-from-l3-%s", msg.getName(), msg.getL3NetworkUuid()));
chain.then(new ShareFlow() {
UsedIpInventory ip;
VipInventory vip;
@Override
public void setup() {
flow(new Flow() {
String __name__ = String.format("allocate-ip-for-vip");
@Override
public void run(final FlowTrigger trigger, Map data) {
String strategyType = msg.getAllocatorStrategy() == null ? L3NetworkConstant.RANDOM_IP_ALLOCATOR_STRATEGY : msg.getAllocatorStrategy();
AllocateIpMsg amsg = new AllocateIpMsg();
amsg.setL3NetworkUuid(msg.getL3NetworkUuid());
amsg.setAllocateStrategy(strategyType);
amsg.setRequiredIp(msg.getRequiredIp());
bus.makeTargetServiceIdByResourceUuid(amsg, L3NetworkConstant.SERVICE_ID, msg.getL3NetworkUuid());
bus.send(amsg, new CloudBusCallBack(trigger) {
@Override
public void run(MessageReply reply) {
if (reply.isSuccess()) {
AllocateIpReply re = reply.castReply();
ip = re.getIpInventory();
trigger.next();
} else {
trigger.fail(reply.getError());
}
}
});
}
@Override
public void rollback(final FlowRollback trigger, Map data) {
if (ip == null) {
trigger.rollback();
return;
}
ReturnIpMsg rmsg = new ReturnIpMsg();
rmsg.setL3NetworkUuid(ip.getL3NetworkUuid());
rmsg.setUsedIpUuid(ip.getUuid());
bus.makeTargetServiceIdByResourceUuid(rmsg, L3NetworkConstant.SERVICE_ID, rmsg.getL3NetworkUuid());
bus.send(rmsg, new CloudBusCallBack(trigger) {
@Override
public void run(MessageReply reply) {
if (!reply.isSuccess()) {
logger.warn(String.format("failed to return ip[uuid:%s, ip:%s] to l3Network[uuid:%s], %s",
ip.getUuid(), ip.getIp(), ip.getL3NetworkUuid(), reply.getError()));
}
trigger.rollback();
}
});
}
});
flow(new NoRollbackFlow() {
String __name__ = String.format("create-vip-in-db");
@Override
public void run(FlowTrigger trigger, Map data) {
VipVO vipvo = new VipVO();
if (msg.getResourceUuid() != null) {
vipvo.setUuid(msg.getResourceUuid());
} else {
vipvo.setUuid(Platform.getUuid());
}
vipvo.setName(msg.getName());
vipvo.setDescription(msg.getDescription());
vipvo.setState(VipState.Enabled);
vipvo.setGateway(ip.getGateway());
vipvo.setIp(ip.getIp());
vipvo.setIpRangeUuid(ip.getIpRangeUuid());
vipvo.setL3NetworkUuid(ip.getL3NetworkUuid());
vipvo.setNetmask(ip.getNetmask());
vipvo.setUsedIpUuid(ip.getUuid());
VipVO finalVipvo = vipvo;
vipvo = new SQLBatchWithReturn<VipVO>() {
@Override
protected VipVO scripts() {
persist(finalVipvo);
reload(finalVipvo);
acntMgr.createAccountResourceRef(msg.getSession().getAccountUuid(), finalVipvo.getUuid(), VipVO.class);
tagMgr.createTagsFromAPICreateMessage(msg, finalVipvo.getUuid(), VipVO.class.getSimpleName());
return finalVipvo;
}
}.execute();
vip = VipInventory.valueOf(vipvo);
trigger.next();
}
});
done(new FlowDoneHandler(msg) {
@Override
public void handle(Map data) {
logger.debug(String.format("successfully acquired vip[uuid:%s, address:%s] on l3NetworkUuid[uuid:%s]", vip.getUuid(), ip.getIp(), ip.getL3NetworkUuid()));
evt.setInventory(vip);
bus.publish(evt);
}
});
error(new FlowErrorHandler(msg) {
@Override
public void handle(ErrorCode errCode, Map data) {
evt.setError(errCode);
bus.publish(evt);
}
});
}
}).start();
}
@Override
public String getId() {
return bus.makeLocalServiceId(VipConstant.SERVICE_ID);
}
private void prepareFlows() {
releaseVipByApiFlowChainBuilder = FlowChainBuilder.newBuilder().setFlowClassNames(releaseVipByApiFlowNames).construct();
}
@Override
public boolean start() {
populateExtensions();
prepareFlows();
return true;
}
@Override
public boolean stop() {
return true;
}
public void setReleaseVipByApiFlowNames(List<String> releaseVipByApiFlowNames) {
this.releaseVipByApiFlowNames = releaseVipByApiFlowNames;
}
@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 APICreateVipMsg) {
check((APICreateVipMsg) 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.setUsed(getUsedVip(accountUuid));
usage.setName(VipConstant.QUOTA_VIP_NUM);
return list(usage);
}
@Transactional(readOnly = true)
private long getUsedVip(String accountUuid) {
String sql = "select count(vip) from VipVO vip, AccountResourceRefVO ref where ref.resourceUuid = vip.uuid" +
" and ref.accountUuid = :auuid and ref.resourceType = :rtype";
TypedQuery<Long> q = dbf.getEntityManager().createQuery(sql, Long.class);
q.setParameter("auuid", accountUuid);
q.setParameter("rtype", VipVO.class.getSimpleName());
Long vn = q.getSingleResult();
vn = vn == null ? 0 : vn;
return vn;
}
private void check(APICreateVipMsg msg, Map<String, QuotaPair> pairs) {
long vipNum = pairs.get(VipConstant.QUOTA_VIP_NUM).getValue();
long vn = getUsedVip(msg.getSession().getAccountUuid());
if (vn + 1 > vipNum) {
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(), VipConstant.QUOTA_VIP_NUM, vipNum)
));
}
}
};
Quota quota = new Quota();
quota.addMessageNeedValidation(APICreateVipMsg.class);
quota.setOperator(checker);
QuotaPair p = new QuotaPair();
p.setName(VipConstant.QUOTA_VIP_NUM);
p.setValue(20);
quota.addPair(p);
return list(quota);
}
}