package org.zstack.compute.vm; import org.springframework.beans.factory.annotation.Autowire; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Configurable; import org.springframework.transaction.annotation.Transactional; import org.zstack.core.cloudbus.CloudBus; import org.zstack.core.cloudbus.CloudBusListCallBack; import org.zstack.core.db.DatabaseFacade; import org.zstack.core.errorcode.ErrorFacade; import org.zstack.header.core.workflow.Flow; import org.zstack.header.core.workflow.FlowRollback; import org.zstack.header.core.workflow.FlowTrigger; import org.zstack.header.message.MessageReply; import org.zstack.header.network.l3.*; import org.zstack.header.vm.*; import org.zstack.utils.CollectionUtils; import org.zstack.utils.function.Function; import javax.persistence.TypedQuery; import java.util.ArrayList; import java.util.List; import java.util.Map; @Configurable(preConstruction = true, autowire = Autowire.BY_TYPE) public class VmAllocateNicForStartingVmFlow implements Flow { @Autowired protected DatabaseFacade dbf; @Autowired protected CloudBus bus; @Autowired protected ErrorFacade errf; @Override public void run(final FlowTrigger trigger, Map data) { final VmInstanceSpec spec = (VmInstanceSpec) data.get(VmInstanceConstant.Params.VmInstanceSpec.toString()); final VmInstanceInventory vm = spec.getVmInventory(); if (!VmInstanceConstant.USER_VM_TYPE.equals(vm.getType())) { trigger.next(); return; } final List<VmNicInventory> nicsNeedNewIp = new ArrayList<VmNicInventory>(vm.getVmNics().size()); List<String> usedIpUuids = new ArrayList<String>(); for (VmNicInventory nic : vm.getVmNics()) { if (nic.getUsedIpUuid() == null) { nicsNeedNewIp.add(nic); } else { usedIpUuids.add(nic.getUsedIpUuid()); } } // normally, the usedIpUuid of VmNicVO will be set to NULL after an IP range is deleted. // however, ill database foreign keys (developers' fault) may cause usedIpUuid not to // be cleaned; so in addition to check NULL usedIpUuid, we double check if IP range for every // nic is still alive. if (!usedIpUuids.isEmpty()) { nicsNeedNewIp.addAll(findNicsNeedNewIps(usedIpUuids, vm.getVmNics())); } if (nicsNeedNewIp.isEmpty()) { trigger.next(); return; } final Map<String, String> vmStaticIps = new StaticIpOperator().getStaticIpbyVmUuid(vm.getUuid()); List<AllocateIpMsg> amsgs = CollectionUtils.transformToList(nicsNeedNewIp, new Function<AllocateIpMsg, VmNicInventory>() { @Override public AllocateIpMsg call(VmNicInventory arg) { AllocateIpMsg msg = new AllocateIpMsg(); String staticIp = vmStaticIps.get(arg.getL3NetworkUuid()); if (staticIp != null) { msg.setRequiredIp(staticIp); } msg.setL3NetworkUuid(arg.getL3NetworkUuid()); msg.setAllocateStrategy(spec.getIpAllocatorStrategy()); bus.makeTargetServiceIdByResourceUuid(msg, L3NetworkConstant.SERVICE_ID, arg.getL3NetworkUuid()); return msg; } }); final List<UsedIpInventory> allocatedIPs = new ArrayList<UsedIpInventory>(); data.put(VmAllocateNicForStartingVmFlow.class, allocatedIPs); bus.send(amsgs, new CloudBusListCallBack(trigger) { @Override public void run(List<MessageReply> replies) { for (MessageReply reply : replies) { if (!reply.isSuccess()) { trigger.fail(reply.getError()); return; } final AllocateIpReply ar = reply.castReply(); final UsedIpInventory ip = ar.getIpInventory(); allocatedIPs.add(ip); String nicUuid = CollectionUtils.find(nicsNeedNewIp, new Function<String, VmNicInventory>() { @Override public String call(VmNicInventory arg) { return arg.getL3NetworkUuid().equals(ip.getL3NetworkUuid()) ? arg.getUuid() : null; } }); VmNicVO nicvo = dbf.findByUuid(nicUuid, VmNicVO.class); nicvo.setGateway(ip.getGateway()); nicvo.setNetmask(ip.getNetmask()); nicvo.setIp(ip.getIp()); nicvo.setUsedIpUuid(ip.getUuid()); dbf.update(nicvo); } VmInstanceVO vmvo = dbf.findByUuid(vm.getUuid(), VmInstanceVO.class); spec.setVmInventory(VmInstanceInventory.valueOf(vmvo)); spec.setDestNics(spec.getVmInventory().getVmNics()); trigger.next(); } }); } @Transactional(readOnly = true) private List<VmNicInventory> findNicsNeedNewIps(List<String> usedIpUuids, List<VmNicInventory> nics) { String sql = "select nic.uuid from VmNicVO nic, UsedIpVO ip, IpRangeVO r where nic.usedIpUuid = ip.uuid and ip.ipRangeUuid = r.uuid and ip.uuid in (:uuids)"; TypedQuery<String> q = dbf.getEntityManager().createQuery(sql, String.class); q.setParameter("uuids", usedIpUuids); List<String> nicUuids = q.getResultList(); List<VmNicInventory> needIps = new ArrayList<VmNicInventory>(); for (VmNicInventory nic : nics) { if (!nicUuids.contains(nic.getUuid())) { needIps.add(nic); } } return needIps; } @Override public void rollback(FlowRollback trigger, Map data) { List<UsedIpInventory> allocatedIps = (List<UsedIpInventory>) data.get(VmAllocateNicForStartingVmFlow.class); if (allocatedIps != null && !allocatedIps.isEmpty()) { List<ReturnIpMsg> rmsgs = CollectionUtils.transformToList(allocatedIps, new Function<ReturnIpMsg, UsedIpInventory>() { @Override public ReturnIpMsg call(UsedIpInventory arg) { ReturnIpMsg rmsg = new ReturnIpMsg(); rmsg.setL3NetworkUuid(arg.getL3NetworkUuid()); rmsg.setUsedIpUuid(arg.getUuid()); bus.makeTargetServiceIdByResourceUuid(rmsg, L3NetworkConstant.SERVICE_ID, arg.getL3NetworkUuid()); return rmsg; } }); bus.send(rmsgs); } trigger.rollback(); } }