package org.zstack.storage.primary.local; 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.compute.allocator.HostAllocatorManager; import org.zstack.core.cloudbus.CloudBus; import org.zstack.core.cloudbus.CloudBusListCallBack; import org.zstack.core.db.DatabaseFacade; import org.zstack.core.db.SimpleQuery; import org.zstack.core.db.SimpleQuery.Op; import org.zstack.header.configuration.DiskOfferingInventory; 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.image.ImageConstant.ImageMediaType; import org.zstack.header.message.MessageReply; import org.zstack.header.storage.backup.BackupStorageVO; import org.zstack.header.storage.backup.BackupStorageVO_; import org.zstack.header.storage.primary.*; import org.zstack.header.vm.VmInstanceConstant; import org.zstack.header.vm.VmInstanceConstant.VmOperation; import org.zstack.header.vm.VmInstanceSpec; import org.zstack.header.vm.VmInstanceSpec.VolumeSpec; import org.zstack.utils.CollectionUtils; import org.zstack.utils.DebugUtils; import org.zstack.utils.Utils; import org.zstack.utils.function.Function; import org.zstack.utils.logging.CLogger; import javax.persistence.TypedQuery; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * Created by frank on 7/2/2015. */ @Configurable(preConstruction = true, autowire = Autowire.BY_TYPE) public class LocalStorageAllocateCapacityFlow implements Flow { private final static CLogger logger = Utils.getLogger(LocalStorageAllocateCapacityFlow.class); @Autowired protected DatabaseFacade dbf; @Autowired protected CloudBus bus; @Autowired protected HostAllocatorManager hostAllocatorMgr; @Transactional(readOnly = true) private boolean isThereOtherNonLocalStoragePrimaryStorageForTheHost(String hostUuid, String localStorageUuid) { String sql = "select count(pri)" + " from PrimaryStorageVO pri, PrimaryStorageClusterRefVO ref, HostVO host" + " where pri.uuid = ref.primaryStorageUuid" + " and ref.clusterUuid = host.clusterUuid" + " and host.uuid = :huuid" + " and pri.uuid != :puuid" + " and pri.type != :pstype"; TypedQuery<Long> q = dbf.getEntityManager().createQuery(sql, Long.class); q.setParameter("huuid", hostUuid); q.setParameter("puuid", localStorageUuid); q.setParameter("pstype", LocalStorageConstants.LOCAL_STORAGE_TYPE); return q.getSingleResult() > 0; } @Transactional(readOnly = true) private String getMostFreeLocalStorageUuid(String hostUuid) { String sql = "select ref.primaryStorageUuid" + " from LocalStorageHostRefVO ref, PrimaryStorageCapacityVO cap" + " where cap.uuid = ref.primaryStorageUuid" + " and ref.hostUuid = :huuid" + " group by ref.primaryStorageUuid" + " order by cap.availableCapacity desc"; TypedQuery<String> q = dbf.getEntityManager().createQuery(sql, String.class); q.setParameter("huuid", hostUuid); return q.getResultList().get(0); } @Override public void run(final FlowTrigger trigger, Map data) { final VmInstanceSpec spec = (VmInstanceSpec) data.get(VmInstanceConstant.Params.VmInstanceSpec.toString()); String localStorageUuid = getMostFreeLocalStorageUuid(spec.getDestHost().getUuid()); SimpleQuery<BackupStorageVO> bq = dbf.createQuery(BackupStorageVO.class); bq.select(BackupStorageVO_.type); bq.add(BackupStorageVO_.uuid, Op.EQ, spec.getImageSpec().getSelectedBackupStorage().getBackupStorageUuid()); String bsType = bq.findValue(); List<String> primaryStorageTypes = hostAllocatorMgr.getBackupStoragePrimaryStorageMetrics().get(bsType); DebugUtils.Assert(primaryStorageTypes != null, "why primaryStorageTypes is null"); List<AllocatePrimaryStorageMsg> msgs = new ArrayList<>(); AllocatePrimaryStorageMsg rmsg = new AllocatePrimaryStorageMsg(); rmsg.setAllocationStrategy(LocalStorageConstants.LOCAL_STORAGE_ALLOCATOR_STRATEGY); rmsg.setVmInstanceUuid(spec.getVmInventory().getUuid()); rmsg.setImageUuid(spec.getImageSpec().getInventory().getUuid()); rmsg.setRequiredPrimaryStorageUuid(localStorageUuid); rmsg.setRequiredHostUuid(spec.getDestHost().getUuid()); if (ImageMediaType.ISO.toString().equals(spec.getImageSpec().getInventory().getMediaType())) { rmsg.setSize(spec.getRootDiskOffering().getDiskSize()); rmsg.setDiskOfferingUuid(spec.getRootDiskOffering().getUuid()); } else { //TODO: find a way to allow specifying strategy for root disk rmsg.setSize(spec.getImageSpec().getInventory().getSize()); } if (spec.getCurrentVmOperation() == VmOperation.NewCreate) { rmsg.setPurpose(PrimaryStorageAllocationPurpose.CreateNewVm.toString()); } else if (spec.getCurrentVmOperation() == VmOperation.AttachVolume) { rmsg.setPurpose(PrimaryStorageAllocationPurpose.CreateVolume.toString()); } bus.makeLocalServiceId(rmsg, PrimaryStorageConstant.SERVICE_ID); rmsg.setRequiredPrimaryStorageTypes(primaryStorageTypes); msgs.add(rmsg); if (!spec.getDataDiskOfferings().isEmpty()) { boolean hasOtherNonLocalStoragePrimaryStorage = isThereOtherNonLocalStoragePrimaryStorageForTheHost( spec.getDestHost().getUuid(), localStorageUuid); for (DiskOfferingInventory dinv : spec.getDataDiskOfferings()) { AllocatePrimaryStorageMsg amsg = new AllocatePrimaryStorageMsg(); amsg.setSize(dinv.getDiskSize()); amsg.setRequiredHostUuid(spec.getDestHost().getUuid()); if (hasOtherNonLocalStoragePrimaryStorage) { amsg.setAllocationStrategy(dinv.getAllocatorStrategy()); amsg.addExcludePrimaryStorageUuid(localStorageUuid); amsg.addExcludeAllocatorStrategy(LocalStorageConstants.LOCAL_STORAGE_ALLOCATOR_STRATEGY); logger.debug("there are non-local primary storage in the cluster, use it for data volumes"); } else { amsg.setAllocationStrategy(LocalStorageConstants.LOCAL_STORAGE_ALLOCATOR_STRATEGY); amsg.setRequiredPrimaryStorageUuid(localStorageUuid); } amsg.setRequiredPrimaryStorageTypes(primaryStorageTypes); amsg.setDiskOfferingUuid(dinv.getUuid()); bus.makeLocalServiceId(amsg, PrimaryStorageConstant.SERVICE_ID); msgs.add(amsg); } } bus.send(msgs, new CloudBusListCallBack(trigger) { @Override public void run(List<MessageReply> replies) { for (int i = 0; i < replies.size(); i++) { MessageReply reply = replies.get(i); if (!reply.isSuccess()) { trigger.fail(reply.getError()); return; } AllocatePrimaryStorageReply ar = reply.castReply(); VolumeSpec vspec = new VolumeSpec(); if (i == 0) { vspec.setSize(ar.getSize()); vspec.setPrimaryStorageInventory(ar.getPrimaryStorageInventory()); vspec.setRoot(true); } else { vspec.setSize(ar.getSize()); vspec.setPrimaryStorageInventory(ar.getPrimaryStorageInventory()); vspec.setDiskOfferingUuid(spec.getDataDiskOfferings().get(i - 1).getUuid()); vspec.setRoot(false); } spec.getVolumeSpecs().add(vspec); } trigger.next(); } }); } @Override public void rollback(FlowRollback trigger, Map data) { VmInstanceSpec spec = (VmInstanceSpec) data.get(VmInstanceConstant.Params.VmInstanceSpec.toString()); if (spec.getVolumeSpecs().isEmpty()) { trigger.rollback(); return; } List<IncreasePrimaryStorageCapacityMsg> msgs = CollectionUtils.transformToList(spec.getVolumeSpecs(), new Function<IncreasePrimaryStorageCapacityMsg, VolumeSpec>() { @Override public IncreasePrimaryStorageCapacityMsg call(VolumeSpec arg) { if (arg.isVolumeCreated()) { // don't return capacity as it has been returned when the volume is deleted return null; } IncreasePrimaryStorageCapacityMsg msg = new IncreasePrimaryStorageCapacityMsg(); msg.setDiskSize(arg.getSize()); msg.setPrimaryStorageUuid(arg.getPrimaryStorageInventory().getUuid()); bus.makeTargetServiceIdByResourceUuid(msg, PrimaryStorageConstant.SERVICE_ID, arg.getPrimaryStorageInventory().getUrl()); return msg; } }); bus.send(msgs); trigger.rollback(); } }