package org.ovirt.engine.core.bll.storage.disk; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import javax.inject.Inject; import org.ovirt.engine.core.bll.LockMessagesMatchUtil; import org.ovirt.engine.core.bll.NonTransactiveCommandAttribute; import org.ovirt.engine.core.bll.ValidationResult; import org.ovirt.engine.core.bll.VmSlaPolicyUtils; import org.ovirt.engine.core.bll.context.CommandContext; import org.ovirt.engine.core.bll.profiles.DiskProfileHelper; import org.ovirt.engine.core.bll.quota.QuotaConsumptionParameter; import org.ovirt.engine.core.bll.quota.QuotaStorageConsumptionParameter; import org.ovirt.engine.core.bll.quota.QuotaStorageDependent; import org.ovirt.engine.core.bll.storage.disk.image.ImagesHandler; import org.ovirt.engine.core.bll.storage.disk.image.MetadataDiskDescriptionHandler; import org.ovirt.engine.core.bll.tasks.CommandCoordinatorUtil; import org.ovirt.engine.core.bll.utils.PermissionSubject; import org.ovirt.engine.core.bll.validator.LocalizedVmStatus; import org.ovirt.engine.core.bll.validator.VmValidator; import org.ovirt.engine.core.bll.validator.storage.DiskImagesValidator; import org.ovirt.engine.core.bll.validator.storage.DiskValidator; import org.ovirt.engine.core.bll.validator.storage.DiskVmElementValidator; import org.ovirt.engine.core.bll.validator.storage.StorageDomainValidator; import org.ovirt.engine.core.common.AuditLogType; import org.ovirt.engine.core.common.FeatureSupported; import org.ovirt.engine.core.common.VdcActionUtils; import org.ovirt.engine.core.common.VdcObjectType; import org.ovirt.engine.core.common.action.AmendImageGroupVolumesCommandParameters; import org.ovirt.engine.core.common.action.ExtendImageSizeParameters; import org.ovirt.engine.core.common.action.LockProperties; import org.ovirt.engine.core.common.action.LockProperties.Scope; import org.ovirt.engine.core.common.action.VdcActionParametersBase; import org.ovirt.engine.core.common.action.VdcActionParametersBase.EndProcedure; import org.ovirt.engine.core.common.action.VdcActionType; import org.ovirt.engine.core.common.action.VdcReturnValueBase; import org.ovirt.engine.core.common.action.VmDiskOperationParameterBase; import org.ovirt.engine.core.common.businessentities.ActionGroup; import org.ovirt.engine.core.common.businessentities.Snapshot.SnapshotType; import org.ovirt.engine.core.common.businessentities.StorageDomain; import org.ovirt.engine.core.common.businessentities.StorageDomainStatic; import org.ovirt.engine.core.common.businessentities.VM; import org.ovirt.engine.core.common.businessentities.VMStatus; import org.ovirt.engine.core.common.businessentities.VmDevice; import org.ovirt.engine.core.common.businessentities.VmDeviceId; import org.ovirt.engine.core.common.businessentities.storage.CinderDisk; import org.ovirt.engine.core.common.businessentities.storage.Disk; import org.ovirt.engine.core.common.businessentities.storage.DiskImage; import org.ovirt.engine.core.common.businessentities.storage.DiskInterface; import org.ovirt.engine.core.common.businessentities.storage.DiskStorageType; import org.ovirt.engine.core.common.businessentities.storage.DiskVmElement; import org.ovirt.engine.core.common.businessentities.storage.ImageStatus; import org.ovirt.engine.core.common.businessentities.storage.StorageType; import org.ovirt.engine.core.common.businessentities.storage.VolumeFormat; import org.ovirt.engine.core.common.errors.EngineMessage; import org.ovirt.engine.core.common.locks.LockingGroup; import org.ovirt.engine.core.common.utils.Pair; import org.ovirt.engine.core.common.validation.group.UpdateEntity; import org.ovirt.engine.core.common.vdscommands.SetVolumeDescriptionVDSCommandParameters; import org.ovirt.engine.core.common.vdscommands.VDSCommandType; import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.core.dao.BaseDiskDao; import org.ovirt.engine.core.dao.DiskDao; import org.ovirt.engine.core.dao.DiskImageDao; import org.ovirt.engine.core.dao.DiskVmElementDao; import org.ovirt.engine.core.dao.ImageDao; import org.ovirt.engine.core.dao.ImageStorageDomainMapDao; import org.ovirt.engine.core.dao.SnapshotDao; import org.ovirt.engine.core.dao.StorageDomainDao; import org.ovirt.engine.core.dao.StorageDomainStaticDao; import org.ovirt.engine.core.dao.VmDao; import org.ovirt.engine.core.dao.VmDeviceDao; import org.ovirt.engine.core.dao.VmStaticDao; import org.ovirt.engine.core.utils.transaction.TransactionMethod; import org.ovirt.engine.core.utils.transaction.TransactionSupport; @NonTransactiveCommandAttribute(forceCompensation = true) public class UpdateVmDiskCommand<T extends VmDiskOperationParameterBase> extends AbstractDiskVmCommand<T> implements QuotaStorageDependent { /* Multiplier used to convert GB to bytes or vice versa. */ private static final long BYTES_IN_GB = 1024 * 1024 * 1024; private List<PermissionSubject> listPermissionSubjects; private final List<VM> vmsDiskSnapshotPluggedTo = new LinkedList<>(); private final List<VM> vmsDiskPluggedTo = new LinkedList<>(); private final List<VM> vmsDiskOrSnapshotPluggedTo = new LinkedList<>(); private final List<VM> vmsDiskOrSnapshotAttachedTo = new LinkedList<>(); @Inject VmSlaPolicyUtils vmSlaPolicyUtils; @Inject private DiskProfileHelper diskProfileHelper; @Inject private VmDao vmDao; /** * vm device for the given vm and disk */ private VmDevice vmDeviceForVm; private Disk oldDisk; private DiskVmElement oldDiskVmElement; @Inject private StorageDomainDao storageDomainDao; @Inject private StorageDomainStaticDao storageDomainStaticDao; @Inject private DiskImageDao diskImageDao; @Inject private DiskDao diskDao; @Inject private DiskVmElementDao diskVmElementDao; @Inject private VmStaticDao vmStaticDao; @Inject private BaseDiskDao baseDiskDao; @Inject private ImageDao imageDao; @Inject private VmDeviceDao vmDeviceDao; @Inject private ImageStorageDomainMapDao imageStorageDomainMapDao; @Inject private SnapshotDao snapshotDao; public UpdateVmDiskCommand(T parameters, CommandContext commandContext) { super(parameters, commandContext); } @Override protected void init() { super.init(); loadVmDiskAttachedToInfo(); } /** * This constructor is mandatory for activation of the compensation process * after the server restart. */ public UpdateVmDiskCommand(Guid commandId) { super(commandId); } @Override protected LockProperties applyLockProperties(LockProperties lockProperties) { return lockProperties.withScope(Scope.Command); } @Override protected Map<String, Pair<String, String>> getSharedLocks() { Map<String, Pair<String, String>> sharedLock = new HashMap<>(); for (VM vm : vmsDiskOrSnapshotPluggedTo) { sharedLock.put(vm.getId().toString(), LockMessagesMatchUtil.makeLockingPair(LockingGroup.VM, EngineMessage.ACTION_TYPE_FAILED_VM_IS_LOCKED)); } return sharedLock.isEmpty() ? null : sharedLock; } @Override protected Map<String, Pair<String, String>> getExclusiveLocks() { Map<String, Pair<String, String>> exclusiveLock = new HashMap<>(); if (getDiskVmElement() != null && getDiskVmElement().isBoot()) { exclusiveLock.put(getParameters().getVmId().toString(), LockMessagesMatchUtil.makeLockingPair(LockingGroup.VM_DISK_BOOT, EngineMessage.ACTION_TYPE_FAILED_OBJECT_LOCKED)); } if (resizeDiskImageRequested()) { exclusiveLock.put(getOldDisk().getId().toString(), LockMessagesMatchUtil.makeLockingPair(LockingGroup.DISK, EngineMessage.ACTION_TYPE_FAILED_DISKS_LOCKED)); } return exclusiveLock.isEmpty() ? null : exclusiveLock; } @Override protected void executeVmCommand() { ImagesHandler.setDiskAlias(getParameters().getDiskInfo(), getVm()); if (resizeDiskImageRequested()) { switch (getOldDisk().getDiskStorageType()) { case IMAGE: extendDiskImageSize(); break; case CINDER: extendCinderDiskSize(); break; } } else { try { performDiskUpdate(false); if (Objects.equals(getOldDisk().getDiskStorageType(), DiskStorageType.IMAGE) && amendDiskRequested()) { amendDiskImage(); } } finally { freeLock(); } } } @Override protected boolean validate() { if (!validate(new VmValidator(getVm()).isVmExists()) || !isDiskExistAndAttachedToVm(getOldDisk()) || !validateDiskVmData()) { return false; } boolean isDiskImageOrCinder = DiskStorageType.IMAGE == getOldDisk().getDiskStorageType() || DiskStorageType.CINDER == getOldDisk().getDiskStorageType(); if (isDiskImageOrCinder) { ValidationResult imagesNotLocked = new DiskImagesValidator((DiskImage) getOldDisk()).diskImagesNotLocked(); if (!imagesNotLocked.isValid()) { return validate(imagesNotLocked); } } DiskValidator oldDiskValidator = getDiskValidator(getOldDisk()); ValidationResult isHostedEngineDisk = oldDiskValidator.validateNotHostedEngineDisk(); if (!isHostedEngineDisk.isValid()) { return validate(isHostedEngineDisk); } if (!checkDiskUsedAsOvfStore(getOldDisk())) { return false; } if (!canRunActionOnNonManagedVm()) { return false; } boolean isDiskInterfaceUpdated = getOldDiskVmElement().getDiskInterface() != getDiskVmElement().getDiskInterface(); if (!vmsDiskOrSnapshotPluggedTo.isEmpty()) { // only virtual drive size can be updated when VMs is running if (isAtLeastOneVmIsNotDown(vmsDiskOrSnapshotPluggedTo) && updateParametersRequiringVmDownRequested()) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_VM_IS_NOT_DOWN); } boolean isUpdatedAsBootable = !getOldDiskVmElement().isBoot() && getDiskVmElement().isBoot(); // multiple boot disk snapshot can be attached to a single vm if (isUpdatedAsBootable && !validate(oldDiskValidator.isVmNotContainsBootDisk(getVm()))) { return false; } if (isDiskInterfaceUpdated && !isDiskPassPciAndIdeLimit()) { return false; } } if (isDiskImageOrCinder && !validateCanResizeDisk()) { return false; } if (resizeDiskImageRequested() && amendDiskRequested()) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_AMEND_AND_EXTEND_IN_ONE_OPERATION); } DiskVmElementValidator diskVmElementValidator = getDiskVmElementValidator(getNewDisk(), getDiskVmElement()); return validateCanUpdateShareable() && validateCanUpdateReadOnly() && validateVmPoolProperties() && validateQuota() && validate(diskVmElementValidator.isVirtIoScsiValid(getVm())) && (!isDiskInterfaceUpdated || validate(diskVmElementValidator.isDiskInterfaceSupported(getVm()))) && setAndValidateDiskProfiles() && validatePassDiscardSupported(diskVmElementValidator); } private boolean validatePassDiscardSupported(DiskVmElementValidator diskVmElementValidator) { if (!FeatureSupported.passDiscardSupported(getStoragePool().getCompatibilityVersion()) && getOldDiskVmElement().isPassDiscard() != getDiskVmElement().isPassDiscard()) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_PASS_DISCARD_NOT_SUPPORTED_BY_DC_VERSION, String.format("$dataCenterVersion %s", getStoragePool().getCompatibilityVersion().toString())); } Guid storageDomainId = getNewDisk().getDiskStorageType() == DiskStorageType.IMAGE ? ((DiskImage) getNewDisk()).getStorageIds().get(0) : null; return validate(diskVmElementValidator.isPassDiscardSupported(storageDomainId)); } protected StorageDomainValidator getStorageDomainValidator(DiskImage diskImage) { StorageDomain storageDomain = storageDomainDao.getForStoragePool( diskImage.getStorageIds().get(0), diskImage.getStoragePoolId()); return new StorageDomainValidator(storageDomain); } @Override protected void setActionMessageParameters() { addValidationMessage(EngineMessage.VAR__ACTION__UPDATE); addValidationMessage(EngineMessage.VAR__TYPE__DISK); } /** * Validate whether a disk can be shareable. Disk can be shareable if it is not based on qcow FS, * which means it should not be based on a template image with thin provisioning, * it also should not contain snapshots and it is not bootable. * @return Indication whether the disk can be shared or not. */ private boolean validateCanUpdateShareable() { if (DiskStorageType.LUN == getOldDisk().getDiskStorageType()) { return true; } // Check if VM is not during snapshot. if (!isVmNotInPreviewSnapshot()) { return false; } if (isUpdatedToShareable(getOldDisk(), getNewDisk())) { StorageDomainStatic sds = storageDomainStaticDao.get(((DiskImage)getNewDisk()).getStorageIds().get(0)); if (sds.getStorageType() == StorageType.GLUSTERFS) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_SHAREABLE_DISKS_NOT_SUPPORTED_ON_GLUSTER_DOMAIN); } List<DiskImage> diskImageList = diskImageDao.getAllSnapshotsForImageGroup(getOldDisk().getId()); // If disk image list is more than one then we assume that it has a snapshot, since one image is the active // disk and all the other images are the snapshots. if ((diskImageList.size() > 1) || !Guid.Empty.equals(((DiskImage) getOldDisk()).getImageTemplateId())) { return failValidation(EngineMessage.SHAREABLE_DISK_IS_NOT_SUPPORTED_FOR_DISK); } if (!isVolumeFormatSupportedForShareable(((DiskImage) getNewDisk()).getVolumeFormat())) { return failValidation(EngineMessage.SHAREABLE_DISK_IS_NOT_SUPPORTED_BY_VOLUME_FORMAT); } } else if (isUpdatedToNonShareable(getOldDisk(), getNewDisk())) { if (vmsDiskOrSnapshotAttachedTo.size() > 1) { return failValidation(EngineMessage.DISK_IS_ALREADY_SHARED_BETWEEN_VMS); } } return true; } protected boolean validateCanUpdateReadOnly() { if (updateReadOnlyRequested()) { if(getVm().getStatus() != VMStatus.Down && vmDeviceForVm.isPlugged()) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_VM_IS_NOT_DOWN); } DiskVmElementValidator diskVmElementValidator = getDiskVmElementValidator(getNewDisk(), getDiskVmElement()); return validate(diskVmElementValidator.isReadOnlyPropertyCompatibleWithInterface()); } return true; } protected boolean validateVmPoolProperties() { if ((updateReadOnlyRequested() || updateWipeAfterDeleteRequested()) && getVm().getVmPoolId() != null) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_VM_ATTACHED_TO_POOL); } return true; } protected boolean validateCanResizeDisk() { DiskImage newDiskImage = (DiskImage) getNewDisk(); DiskImage oldDiskImage = (DiskImage) getOldDisk(); if (newDiskImage.getSize() != oldDiskImage.getSize()) { if (Boolean.TRUE.equals(getVmDeviceForVm().getReadOnly())) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_CANNOT_RESIZE_READ_ONLY_DISK); } if (vmDeviceForVm.getSnapshotId() != null) { DiskImage snapshotDisk = diskImageDao.getDiskSnapshotForVmSnapshot(getParameters().getDiskInfo().getId(), vmDeviceForVm.getSnapshotId()); if (snapshotDisk.getSize() != newDiskImage.getSize()) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_CANNOT_RESIZE_DISK_SNAPSHOT); } } if (oldDiskImage.getSize() > newDiskImage.getSize()) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_REQUESTED_DISK_SIZE_IS_TOO_SMALL); } for (VM vm : getVmsDiskPluggedTo()) { if (!VdcActionUtils.canExecute(Collections.singletonList(vm), VM.class, VdcActionType.ExtendImageSize)) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_VM_STATUS_ILLEGAL, LocalizedVmStatus.from(vm.getStatus())); } } StorageDomainValidator storageDomainValidator = getStorageDomainValidator((DiskImage) getNewDisk()); if (!validate(storageDomainValidator.isDomainExistAndActive())) { return false; } // For size allocation validation, we'll create a dummy with the additional size required. // That way, the validator can hold all the logic about storage types. long additionalDiskSpaceInGB = newDiskImage.getSizeInGigabytes() - oldDiskImage.getSizeInGigabytes(); DiskImage dummyForValidation = DiskImage.copyOf(newDiskImage); dummyForValidation.setSizeInGigabytes(additionalDiskSpaceInGB); return validate(storageDomainValidator.hasSpaceForNewDisk(dummyForValidation)); } return true; } @Override public List<PermissionSubject> getPermissionCheckSubjects() { if (listPermissionSubjects == null) { listPermissionSubjects = new ArrayList<>(); Guid diskId = (getOldDisk() == null) ? null : getOldDisk().getId(); listPermissionSubjects.add(new PermissionSubject(diskId, VdcObjectType.Disk, ActionGroup.EDIT_DISK_PROPERTIES)); if (getOldDisk() != null && getNewDisk() != null && getOldDisk().getSgio() != getNewDisk().getSgio()) { listPermissionSubjects.add(new PermissionSubject(diskId, VdcObjectType.Disk, ActionGroup.CONFIGURE_SCSI_GENERIC_IO)); } } return listPermissionSubjects; } protected void performDiskUpdate(final boolean unlockImage) { if (shouldPerformMetadataUpdate()) { updateMetaDataDescription((DiskImage) getNewDisk()); } final Disk diskForUpdate = diskDao.get(getParameters().getDiskInfo().getId()); final DiskVmElement diskVmElementForUpdate = diskVmElementDao.get(new VmDeviceId(getOldDisk().getId(), getVmId())); applyUserChanges(diskForUpdate, diskVmElementForUpdate); TransactionSupport.executeInNewTransaction(new TransactionMethod<Object>() { @Override public Object runInTransaction() { vmStaticDao.incrementDbGeneration(getVm().getId()); updateDeviceProperties(); baseDiskDao.update(diskForUpdate); diskVmElementDao.update(diskVmElementForUpdate); switch (diskForUpdate.getDiskStorageType()) { case IMAGE: DiskImage diskImage = (DiskImage) diskForUpdate; diskImage.setQuotaId(getQuotaId()); if (unlockImage && diskImage.getImageStatus() == ImageStatus.LOCKED) { diskImage.setImageStatus(ImageStatus.OK); } imageDao.update(diskImage.getImage()); updateQuota(diskImage); updateDiskProfile(); break; case CINDER: CinderDisk cinderDisk = (CinderDisk) diskForUpdate; cinderDisk.setQuotaId(getQuotaId()); setStorageDomainId(cinderDisk.getStorageIds().get(0)); getCinderBroker().updateDisk(cinderDisk); if (unlockImage && cinderDisk.getImageStatus() == ImageStatus.LOCKED) { cinderDisk.setImageStatus(ImageStatus.OK); } imageDao.update(cinderDisk.getImage()); updateQuota(cinderDisk); break; case LUN: // No specific update for LUN disk break; } reloadDisks(); setSucceeded(true); // If necessary set the new Storage QoS values on running VMs asynchronously liveUpdateDiskProfile(); return null; } private void updateDeviceProperties() { if (updateReadOnlyRequested()) { vmDeviceForVm.setReadOnly(getDiskVmElement().isReadOnly()); vmDeviceDao.update(vmDeviceForVm); } if ((getOldDiskVmElement().getDiskInterface() != getDiskVmElement().getDiskInterface()) || ((getOldDiskVmElement().isBoot() != getDiskVmElement().isBoot()) && ( getDiskVmElement().getDiskInterface() == DiskInterface.IDE))) { vmDeviceForVm.setAddress(""); vmDeviceDao.clearDeviceAddress(getOldDisk().getId()); } } }); } private boolean shouldPerformMetadataUpdate() { return (getNewDisk().getDiskStorageType() == DiskStorageType.IMAGE) && (!Objects.equals(getOldDisk().getDiskAlias(), getNewDisk().getDiskAlias()) || !Objects.equals(getOldDisk().getDiskDescription(), getNewDisk().getDiskDescription())); } private void updateMetaDataDescription(DiskImage diskImage) { StorageDomain storageDomain = storageDomainDao.getForStoragePool(diskImage.getStorageIds().get(0), getVm().getStoragePoolId()); if (!getStorageDomainValidator((DiskImage) getNewDisk()).isDomainExistAndActive().isValid()) { auditLogForNoMetadataDescriptionUpdate(AuditLogType.UPDATE_DESCRIPTION_FOR_DISK_SKIPPED_SINCE_STORAGE_DOMAIN_NOT_ACTIVE, storageDomain, diskImage); return; } setVolumeDescription(diskImage, storageDomain); } protected void setVolumeDescription(DiskImage diskImage, StorageDomain storageDomain) { try { SetVolumeDescriptionVDSCommandParameters vdsCommandParameters = new SetVolumeDescriptionVDSCommandParameters(getVm().getStoragePoolId(), diskImage.getStorageIds().get(0), diskImage.getId(), diskImage.getImageId(), getJsonDiskDescription()); runVdsCommand(VDSCommandType.SetVolumeDescription, vdsCommandParameters); } catch (Exception e) { log.error("Exception while setting volume description for disk. ERROR: '{}'", e); auditLogForNoMetadataDescriptionUpdate(AuditLogType.UPDATE_DESCRIPTION_FOR_DISK_FAILED, storageDomain, diskImage); } } private void auditLogForNoMetadataDescriptionUpdate(AuditLogType auditLogType, StorageDomain storageDomain, DiskImage diskImage) { addCustomValue("DataCenterName", getStoragePool().getName()); addCustomValue("StorageDomainName", storageDomain.getName()); addCustomValue("DiskName", diskImage.getDiskAlias()); auditLogDirector.log(this, auditLogType); } private String getJsonDiskDescription() throws IOException { return MetadataDiskDescriptionHandler.getInstance().generateJsonDiskDescription(getParameters().getDiskInfo()); } protected void updateDiskProfile() { if (isDiskImage()) { DiskImage oldDisk = (DiskImage) getOldDisk(); DiskImage newDisk = (DiskImage) getNewDisk(); if (!Objects.equals(oldDisk.getDiskProfileId(), newDisk.getDiskProfileId())) { imageStorageDomainMapDao.updateDiskProfileByImageGroupIdAndStorageDomainId(newDisk.getId(), newDisk.getStorageIds().get(0), newDisk.getDiskProfileId()); } } } private void liveUpdateDiskProfile() { if (isDiskImage()) { DiskImage oldDisk = (DiskImage) getOldDisk(); DiskImage newDisk = (DiskImage) getNewDisk(); if (!Objects.equals(oldDisk.getDiskProfileId(), newDisk.getDiskProfileId())) { vmSlaPolicyUtils.refreshRunningVmsWithDiskProfile(newDisk.getDiskProfileId()); } } } protected void updateQuota(DiskImage diskImage) { if (isInternalManagedDisk()) { DiskImage oldDisk = (DiskImage) getOldDisk(); if (!Objects.equals(oldDisk.getQuotaId(), diskImage.getQuotaId())) { imageStorageDomainMapDao.updateQuotaForImageAndSnapshots(diskImage.getId(), diskImage.getStorageIds().get(0), diskImage.getQuotaId()); } } } private void applyUserChanges(Disk diskToUpdate, DiskVmElement dveToUpdate) { updateSnapshotIdOnShareableChange(diskToUpdate, getNewDisk()); diskToUpdate.setPropagateErrors(getNewDisk().getPropagateErrors()); diskToUpdate.setWipeAfterDelete(getNewDisk().isWipeAfterDelete()); diskToUpdate.setDiskAlias(getNewDisk().getDiskAlias()); diskToUpdate.setDiskDescription(getNewDisk().getDiskDescription()); diskToUpdate.setShareable(getNewDisk().isShareable()); diskToUpdate.setSgio(getNewDisk().getSgio()); dveToUpdate.setBoot(getDiskVmElement().isBoot()); dveToUpdate.setDiskInterface(getDiskVmElement().getDiskInterface()); dveToUpdate.setPassDiscard(getDiskVmElement().isPassDiscard()); dveToUpdate.setUsingScsiReservation(getDiskVmElement().isUsingScsiReservation()); } protected void reloadDisks() { vmHandler.updateDisksFromDb(getVm()); } private void extendDiskImageSize() { lockImageInDb(); VdcReturnValueBase ret = runInternalActionWithTasksContext( VdcActionType.ExtendImageSize, createExtendImageSizeParameters()); if (!ret.getSucceeded()) { propagateInternalCommandFailure(ret); getReturnValue().setFault(ret.getFault()); } getReturnValue().getVdsmTaskIdList().addAll(ret.getInternalVdsmTaskIdList()); setSucceeded(ret.getSucceeded()); } protected void amendDiskImage() { VdcReturnValueBase ret = runInternalActionWithTasksContext(VdcActionType.AmendImageGroupVolumes, amendImageGroupVolumesCommandParameters()); if (!ret.getSucceeded()) { propagateInternalCommandFailure(ret); getReturnValue().setFault(ret.getFault()); } setSucceeded(ret.getSucceeded()); } private void extendCinderDiskSize() { lockImageInDb(); CinderDisk newCinderDisk = (CinderDisk) getNewDisk(); Future<VdcReturnValueBase> future = CommandCoordinatorUtil.executeAsyncCommand( VdcActionType.ExtendCinderDisk, buildExtendCinderDiskParameters(newCinderDisk), cloneContextAndDetachFromParent()); addCustomValue("NewSize", String.valueOf(getNewDiskSizeInGB())); try { setReturnValue(future.get()); setSucceeded(getReturnValue().getSucceeded()); } catch (InterruptedException | ExecutionException e) { log.error("Error extending Cinder disk '{}': {}", getNewDisk().getDiskAlias(), e.getMessage()); log.debug("Exception", e); } } private VdcActionParametersBase buildExtendCinderDiskParameters(CinderDisk newCinderDisk) { VmDiskOperationParameterBase parameters = new VmDiskOperationParameterBase( DiskVmElement.copyOf(getOldDiskVmElement()), newCinderDisk); parameters.setParametersCurrentUser(getParameters().getParametersCurrentUser()); parameters.setEndProcedure(EndProcedure.COMMAND_MANAGED); return parameters; } @Override protected void endSuccessfully() { if (!isDiskImage()) { return; } VdcReturnValueBase ret = getBackend().endAction(VdcActionType.ExtendImageSize, createExtendImageSizeParameters(), getContext().clone().withoutCompensationContext().withoutExecutionContext().withoutLock()); if (ret.getSucceeded()) { performDiskUpdate(true); } else { unlockImageInDb(); } getReturnValue().setEndActionTryAgain(false); setSucceeded(ret.getSucceeded()); } @Override protected void endWithFailure() { endInternalCommandWithFailure(); unlockImageInDb(); getReturnValue().setEndActionTryAgain(false); setSucceeded(true); } private void endInternalCommandWithFailure() { ExtendImageSizeParameters params = createExtendImageSizeParameters(); params.setTaskGroupSuccess(false); getBackend().endAction(VdcActionType.ExtendImageSize, params, getContext().clone().withoutCompensationContext().withoutExecutionContext().withoutLock()); } @Override public AuditLogType getAuditLogTypeValue() { if (getSucceeded()) { return isCinderDisk() && resizeDiskImageRequested() ? AuditLogType.USER_EXTENDED_DISK_SIZE : AuditLogType.USER_UPDATE_VM_DISK; } else { return AuditLogType.USER_FAILED_UPDATE_VM_DISK; } } @Override public String getDiskAlias() { return getOldDisk().getDiskAlias(); } @Override public Map<String, String> getJobMessageProperties() { if (jobProperties == null) { jobProperties = super.getJobMessageProperties(); jobProperties.put("diskalias", getDiskAlias()); } return jobProperties; } @Override protected List<Class<?>> getValidationGroups() { addValidationGroup(UpdateEntity.class); return super.getValidationGroups(); } public long getNewDiskSizeInGB() { CinderDisk cinderDisk = (CinderDisk) getNewDisk(); return cinderDisk.getSize() / BYTES_IN_GB; } private boolean isDiskImage() { return isDiskStorageType(DiskStorageType.IMAGE); } private boolean isCinderDisk() { return isDiskStorageType(DiskStorageType.CINDER); } private boolean isDiskStorageType(DiskStorageType diskStorageType) { return getOldDisk() != null && getNewDisk() != null && diskStorageType == getOldDisk().getDiskStorageType(); } protected Guid getQuotaId() { if (getNewDisk() != null && isInternalManagedDisk()) { Guid quotaId = ((DiskImage) getNewDisk()).getQuotaId(); return getQuotaManager().getDefaultQuotaIfNull(quotaId, getStoragePoolId()); } return null; } protected boolean setAndValidateDiskProfiles() { if (isDiskImage()) { DiskImage diskImage = (DiskImage) getNewDisk(); // when disk profile isn't updated, skip check. if (diskImage.getDiskProfileId() != null && diskImage.getDiskProfileId().equals(((DiskImage) getOldDisk()).getDiskProfileId())) { return true; } Map<DiskImage, Guid> map = new HashMap<>(); map.put(diskImage, diskImage.getStorageIds().get(0)); return validate(diskProfileHelper.setAndValidateDiskProfiles(map, getCurrentUser())); } return true; } @Override public List<QuotaConsumptionParameter> getQuotaStorageConsumptionParameters() { List<QuotaConsumptionParameter> list = new ArrayList<>(); if (isInternalManagedDisk()) { DiskImage oldDiskImage = (DiskImage) getOldDisk(); DiskImage newDiskImage = (DiskImage) getNewDisk(); boolean emptyOldQuota = oldDiskImage.getQuotaId() == null || Guid.Empty.equals(oldDiskImage.getQuotaId()); boolean differentNewQuota = !emptyOldQuota && !oldDiskImage.getQuotaId().equals(newDiskImage.getQuotaId()); long diskExtendingDiff = newDiskImage.getSizeInGigabytes() - oldDiskImage.getSizeInGigabytes(); if (emptyOldQuota || differentNewQuota ) { list.add(generateQuotaConsumeParameters(newDiskImage, newDiskImage.getSizeInGigabytes())); } else if (diskExtendingDiff > 0L) { list.add(generateQuotaConsumeParameters(newDiskImage, diskExtendingDiff)); } if (differentNewQuota) { list.add(new QuotaStorageConsumptionParameter( oldDiskImage.getQuotaId(), null, QuotaStorageConsumptionParameter.QuotaAction.RELEASE, //TODO: Shared Disk? oldDiskImage.getStorageIds().get(0), (double)oldDiskImage.getSizeInGigabytes())); } } return list; } protected boolean isInternalManagedDisk() { return isDiskImage() || isCinderDisk(); } private QuotaConsumptionParameter generateQuotaConsumeParameters(DiskImage newDiskImage, long sizeInGigabytes) { return new QuotaStorageConsumptionParameter( newDiskImage.getQuotaId(), null, QuotaStorageConsumptionParameter.QuotaAction.CONSUME, //TODO: Shared Disk? newDiskImage.getStorageIds().get(0), (double) sizeInGigabytes ); } private boolean resizeDiskImageRequested() { boolean sizeChanged = getNewDisk().getSize() != getOldDisk().getSize(); switch (getNewDisk().getDiskStorageType()) { case IMAGE: return sizeChanged && vmDeviceForVm.getSnapshotId() == null; case CINDER: return sizeChanged; } return false; } protected boolean amendDiskRequested() { if (getNewDisk().getDiskStorageType() == DiskStorageType.IMAGE) { DiskImage oldDisk = (DiskImage) getOldDisk(); return (oldDisk.getVolumeFormat() == VolumeFormat.COW) && !Objects.equals(oldDisk.getQcowCompat().getCompatValue(), ((DiskImage) getNewDisk()).getQcowCompat().getCompatValue()); } return false; } private boolean updateParametersRequiringVmDownRequested() { return updateDiskParametersRequiringVmDownRequested() || updateImageParametersRequiringVmDownRequested(); } private boolean updateDiskParametersRequiringVmDownRequested() { return getOldDiskVmElement().isBoot() != getDiskVmElement().isBoot() || getOldDiskVmElement().getDiskInterface() != getDiskVmElement().getDiskInterface() || getOldDisk().getPropagateErrors() != getNewDisk().getPropagateErrors() || getOldDisk().isShareable() != getNewDisk().isShareable() || getOldDisk().getSgio() != getNewDisk().getSgio(); } /** * Command's validate conditions: requiring all connected VMs down. * @return true - if disk type is IMAGE or is CINDER, and updating quota */ private boolean updateImageParametersRequiringVmDownRequested() { if (!getOldDisk().getDiskStorageType().isInternal()) { return false; } Guid oldQuotaId = ((DiskImage) getOldDisk()).getQuotaId(); /* * oldQuotaId == null : Initial quota, not assigned yet. * happens when: quota is disabled or, * quota enabled, but disk never attached with a quota */ if (oldQuotaId == null) { return false; } return !Objects.equals(oldQuotaId, getQuotaId()); } protected boolean updateReadOnlyRequested() { boolean readOnlyNewValue = getDiskVmElement().isReadOnly(); return !getVmDeviceForVm().getReadOnly().equals(readOnlyNewValue); } protected boolean updateWipeAfterDeleteRequested() { return getNewDisk().isWipeAfterDelete() != getOldDisk().isWipeAfterDelete(); } protected boolean isAtLeastOneVmIsNotDown(List<VM> vmsDiskPluggedTo) { for (VM vm : vmsDiskPluggedTo) { if (vm.getStatus() != VMStatus.Down) { return true; } } return false; } private boolean isUpdatedToShareable(Disk oldDisk, Disk newDisk) { return newDisk.isShareable() && !oldDisk.isShareable(); } private boolean isUpdatedToNonShareable(Disk oldDisk, Disk newDisk) { return !newDisk.isShareable() && oldDisk.isShareable(); } private void updateSnapshotIdOnShareableChange(Disk oldDisk, Disk newDisk) { if (oldDisk.isShareable() != newDisk.isShareable() && oldDisk.getDiskStorageType() == DiskStorageType.IMAGE) { DiskImage oldDiskImage = (DiskImage) oldDisk; Guid vmSnapshotId = isUpdatedToShareable(oldDisk, newDisk) ? null : snapshotDao.getId(getVmId(), SnapshotType.ACTIVE); oldDiskImage.setVmSnapshotId(vmSnapshotId); } } protected Disk getOldDisk() { if (oldDisk == null && getParameters().getDiskInfo() != null) { oldDisk = diskDao.get(getParameters().getDiskInfo().getId()); } return oldDisk; } protected DiskVmElement getOldDiskVmElement() { if (oldDiskVmElement == null) { oldDiskVmElement = diskVmElementDao.get(new VmDeviceId(getOldDisk().getId(), getVmId())); } return oldDiskVmElement; } protected Disk getNewDisk() { return getParameters().getDiskInfo(); } protected VmDevice getVmDeviceForVm() { return vmDeviceForVm; } private List<VM> getVmsDiskPluggedTo() { return vmsDiskPluggedTo; } private void loadVmDiskAttachedToInfo() { if (getOldDisk() != null) { List<Pair<VM, VmDevice>> attachedVmsInfo = vmDao.getVmsWithPlugInfo(getOldDisk().getId()); for (Pair<VM, VmDevice> pair : attachedVmsInfo) { VM vm = pair.getFirst(); vmsDiskOrSnapshotAttachedTo.add(vm); if (Boolean.TRUE.equals(pair.getSecond().isPlugged())) { if (pair.getSecond().getSnapshotId() != null) { vmsDiskSnapshotPluggedTo.add(vm); } else { vmsDiskPluggedTo.add(vm); } vmsDiskOrSnapshotPluggedTo.add(vm); } if (vm.getId().equals(getParameters().getVmId())) { vmDeviceForVm = pair.getSecond(); } } } } private void lockImageInDb() { final DiskImage diskImage = (DiskImage) getOldDisk(); TransactionSupport.executeInNewTransaction(() -> { getCompensationContext().snapshotEntityStatus(diskImage.getImage()); getCompensationContext().stateChanged(); diskImage.setImageStatus(ImageStatus.LOCKED); ImagesHandler.updateImageStatus(diskImage.getImageId(), ImageStatus.LOCKED); return null; }); } public void unlockImageInDb() { final DiskImage diskImage = (DiskImage) getOldDisk(); diskImage.setImageStatus(ImageStatus.OK); ImagesHandler.updateImageStatus(diskImage.getImageId(), ImageStatus.OK); } private AmendImageGroupVolumesCommandParameters amendImageGroupVolumesCommandParameters() { DiskImage diskImage = (DiskImage) getNewDisk(); return new AmendImageGroupVolumesCommandParameters(diskImage.getId(), diskImage.getQcowCompat()); } private ExtendImageSizeParameters createExtendImageSizeParameters() { DiskImage diskImage = (DiskImage) getNewDisk(); ExtendImageSizeParameters params = new ExtendImageSizeParameters(diskImage.getImageId(), diskImage.getSize()); params.setStoragePoolId(diskImage.getStoragePoolId()); params.setStorageDomainId(diskImage.getStorageIds().get(0)); params.setImageGroupID(diskImage.getId()); params.setParentCommand(VdcActionType.UpdateVmDisk); params.setParentParameters(getParameters()); return params; } private void propagateInternalCommandFailure(VdcReturnValueBase internalReturnValue) { getReturnValue().getExecuteFailedMessages().clear(); getReturnValue().getExecuteFailedMessages().addAll(internalReturnValue.getExecuteFailedMessages()); getReturnValue().setFault(internalReturnValue.getFault()); getReturnValue().getValidationMessages().clear(); getReturnValue().getValidationMessages().addAll(internalReturnValue.getValidationMessages()); getReturnValue().setValid(internalReturnValue.isValid()); } }