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.zstack.core.cloudbus.CloudBus; import org.zstack.core.cloudbus.CloudBusListCallBack; import org.zstack.core.db.DatabaseFacade; import org.zstack.core.db.SimpleQuery.Op; import org.zstack.core.db.UpdateQuery; 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.errorcode.ErrorCode; import org.zstack.header.exception.CloudRuntimeException; import org.zstack.header.image.ImageConstant.ImageMediaType; import org.zstack.header.message.MessageReply; import org.zstack.header.vm.VmInstanceConstant; import org.zstack.header.vm.VmInstanceSpec; import org.zstack.header.vm.VmInstanceSpec.VolumeSpec; import org.zstack.header.vm.VmInstanceVO; import org.zstack.header.vm.VmInstanceVO_; import org.zstack.header.volume.*; import org.zstack.header.volume.VolumeDeletionPolicyManager.VolumeDeletionPolicy; import org.zstack.identity.AccountManager; import org.zstack.utils.CollectionUtils; import org.zstack.utils.function.Function; import java.util.ArrayList; import java.util.List; import java.util.Map; import static org.zstack.core.progress.ProgressReportService.taskProgress; @Configurable(preConstruction = true, autowire = Autowire.BY_TYPE) public class VmAllocateVolumeFlow implements Flow { @Autowired protected DatabaseFacade dbf; @Autowired protected CloudBus bus; @Autowired protected AccountManager acntMgr; @Autowired protected ErrorFacade errf; private List<CreateVolumeMsg> prepareMsg(Map<String, Object> ctx) { taskProgress("create volumes"); VmInstanceSpec spec = (VmInstanceSpec) ctx.get(VmInstanceConstant.Params.VmInstanceSpec.toString()); String accountUuid = acntMgr.getOwnerAccountUuidOfResource(spec.getVmInventory().getUuid()); if (accountUuid == null) { throw new CloudRuntimeException(String.format("accountUuid for vm[uuid:%s] is null", spec.getVmInventory().getUuid())); } List<VolumeSpec> volumeSpecs = spec.getVolumeSpecs(); List<CreateVolumeMsg> msgs = new ArrayList<>(volumeSpecs.size()); for (VolumeSpec vspec : volumeSpecs) { CreateVolumeMsg msg = new CreateVolumeMsg(); if (vspec.isRoot()) { msg.setName("ROOT-for-" + spec.getVmInventory().getName()); msg.setDescription(String.format("Root volume for VM[uuid:%s]", spec.getVmInventory().getUuid())); msg.setRootImageUuid(spec.getImageSpec().getInventory().getUuid()); if (ImageMediaType.ISO.toString().equals(spec.getImageSpec().getInventory().getMediaType())) { msg.setFormat(VolumeFormat.getVolumeFormatByMasterHypervisorType(spec.getDestHost().getHypervisorType()).toString()); } else { VolumeFormat imageFormat = VolumeFormat.valueOf(spec.getImageSpec().getInventory().getFormat()); msg.setFormat(imageFormat.getOutputFormat(spec.getDestHost().getHypervisorType())); } } else { msg.setName(String.format("DATA-for-%s", spec.getVmInventory().getName())); msg.setDescription(String.format("DataVolume-%s", spec.getVmInventory().getUuid())); msg.setFormat(VolumeFormat.getVolumeFormatByMasterHypervisorType(spec.getDestHost().getHypervisorType()).toString()); } msg.setDiskOfferingUuid(vspec.getDiskOfferingUuid()); msg.setSize(vspec.getSize()); msg.setPrimaryStorageUuid(vspec.getPrimaryStorageInventory().getUuid()); msg.setVmInstanceUuid(spec.getVmInventory().getUuid()); msg.setVolumeType(vspec.isRoot() ? VolumeType.Root.toString() : VolumeType.Data.toString()); msg.setAccountUuid(accountUuid); bus.makeLocalServiceId(msg, VolumeConstant.SERVICE_ID); msgs.add(msg); } return msgs; } @Override public void run(final FlowTrigger trigger, final Map data) { final VmInstanceSpec spec = (VmInstanceSpec) data.get(VmInstanceConstant.Params.VmInstanceSpec.toString()); List<CreateVolumeMsg> msgs = prepareMsg(data); bus.send(msgs, 1, new CloudBusListCallBack(trigger) { @Override public void run(List<MessageReply> replies) { ErrorCode err = null; for (MessageReply r : replies) { VolumeSpec vspec = spec.getVolumeSpecs().get(replies.indexOf(r)); if (r.isSuccess()) { CreateVolumeReply cr = r.castReply(); VolumeInventory inv = cr.getInventory(); if (inv.getType().equals(VolumeType.Root.toString())) { UpdateQuery.New(VmInstanceVO.class) .set(VmInstanceVO_.rootVolumeUuid, inv.getUuid()) .condAnd(VmInstanceVO_.uuid, Op.EQ, spec.getVmInventory().getUuid()) .update(); spec.setDestRootVolume(inv); } else { spec.getDestDataVolumes().add(inv); } vspec.setIsVolumeCreated(true); } else { err = r.getError(); vspec.setIsVolumeCreated(false); } } if (err != null) { trigger.fail(err); } else { trigger.next(); } } }); } @Override public void rollback(final FlowRollback chain, Map data) { VmInstanceSpec spec = (VmInstanceSpec) data.get(VmInstanceConstant.Params.VmInstanceSpec.toString()); List<VolumeInventory> destVolumes = new ArrayList<>(spec.getDestDataVolumes().size() + 1); if (spec.getDestRootVolume() != null) { destVolumes.add(spec.getDestRootVolume()); } destVolumes.addAll(spec.getDestDataVolumes()); final List<DeleteVolumeMsg> msgs = CollectionUtils.transformToList(destVolumes, new Function<DeleteVolumeMsg, VolumeInventory>() { @Override public DeleteVolumeMsg call(VolumeInventory arg) { DeleteVolumeMsg msg = new DeleteVolumeMsg(); msg.setDeletionPolicy(VolumeDeletionPolicy.Direct.toString()); msg.setUuid(arg.getUuid()); // don't do detach; because the VM is in state of Starting, it cannot do a detach operation. msg.setDetachBeforeDeleting(false); bus.makeTargetServiceIdByResourceUuid(msg, VolumeConstant.SERVICE_ID, arg.getUuid()); return msg; } }); if (msgs.isEmpty()) { chain.rollback(); return; } bus.send(msgs, new CloudBusListCallBack(chain) { @Override public void run(List<MessageReply> replies) { chain.rollback(); } }); } }