text: package org.ovirt.engine.core.bll.storage.disk;
  
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.List;
  import java.util.Map;
  import java.util.concurrent.ExecutionException;
  import java.util.concurrent.Future;
  
  import javax.enterprise.inject.Instance;
  import javax.inject.Inject;
  
  import org.apache.commons.lang.StringUtils;
  import org.ovirt.engine.core.bll.DisableInPrepareMode;
  import org.ovirt.engine.core.bll.LockMessagesMatchUtil;
  import org.ovirt.engine.core.bll.MultiLevelAdministrationHandler;
  import org.ovirt.engine.core.bll.NonTransactiveCommandAttribute;
  import org.ovirt.engine.core.bll.PredefinedRoles;
  import org.ovirt.engine.core.bll.ValidationResult;
  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.domain.LunHelper;
  import org.ovirt.engine.core.bll.tasks.CommandCoordinatorUtil;
  import org.ovirt.engine.core.bll.tasks.interfaces.CommandCallback;
  import org.ovirt.engine.core.bll.utils.PermissionSubject;
  import org.ovirt.engine.core.bll.validator.VmValidator;
  import org.ovirt.engine.core.bll.validator.storage.CinderDisksValidator;
  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.bll.validator.storage.StoragePoolValidator;
  import org.ovirt.engine.core.common.AuditLogType;
  import org.ovirt.engine.core.common.VdcObjectType;
  import org.ovirt.engine.core.common.action.ActionParametersBase;
  import org.ovirt.engine.core.common.action.ActionParametersBase.EndProcedure;
  import org.ovirt.engine.core.common.action.ActionReturnValue;
  import org.ovirt.engine.core.common.action.ActionType;
  import org.ovirt.engine.core.common.action.AddDiskParameters;
  import org.ovirt.engine.core.common.action.AddImageFromScratchParameters;
  import org.ovirt.engine.core.common.action.AddManagedBlockStorageDiskParameters;
  import org.ovirt.engine.core.common.action.LockProperties;
  import org.ovirt.engine.core.common.action.VmDiskOperationParameterBase;
  import org.ovirt.engine.core.common.asynctasks.EntityInfo;
  import org.ovirt.engine.core.common.businessentities.ActionGroup;
  import org.ovirt.engine.core.common.businessentities.Permission;
  import org.ovirt.engine.core.common.businessentities.Snapshot.SnapshotType;
  import org.ovirt.engine.core.common.businessentities.StoragePool;
  import org.ovirt.engine.core.common.businessentities.StoragePoolIsoMapId;
  import org.ovirt.engine.core.common.businessentities.VDS;
  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.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.DiskLunMap;
  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.LUNs;
  import org.ovirt.engine.core.common.businessentities.storage.LunDisk;
  import org.ovirt.engine.core.common.businessentities.storage.ScsiGenericIO;
  import org.ovirt.engine.core.common.businessentities.storage.StorageType;
  import org.ovirt.engine.core.common.config.Config;
  import org.ovirt.engine.core.common.config.ConfigValues;
  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.GetDeviceListVDSCommandParameters;
  import org.ovirt.engine.core.common.vdscommands.VDSCommandType;
  import org.ovirt.engine.core.compat.Guid;
  import org.ovirt.engine.core.dal.dbbroker.auditloghandling.AuditLogDirector;
  import org.ovirt.engine.core.dao.BaseDiskDao;
  import org.ovirt.engine.core.dao.DiskLunMapDao;
  import org.ovirt.engine.core.dao.DiskVmElementDao;
  import org.ovirt.engine.core.dao.SnapshotDao;
  import org.ovirt.engine.core.dao.StoragePoolIsoMapDao;
  import org.ovirt.engine.core.dao.VmStaticDao;
  import org.ovirt.engine.core.utils.transaction.TransactionSupport;
  
  @DisableInPrepareMode
  @NonTransactiveCommandAttribute(forceCompensation = true)
  public class AddDiskCommand<T extends AddDiskParameters> extends AbstractDiskVmCommand<T>
          implements QuotaStorageDependent {
  
      private LUNs lunFromStorage;
  
      @Inject
      private AuditLogDirector auditLogDirector;
  
      @Inject
      private DiskProfileHelper diskProfileHelper;
  
      @Inject
      private LunHelper lunHelper;
      @Inject
      private DiskLunMapDao diskLunMapDao;
      @Inject
      private StoragePoolIsoMapDao storagePoolIsoMapDao;
      @Inject
      private BaseDiskDao baseDiskDao;
      @Inject
      private DiskVmElementDao diskVmElementDao;
      @Inject
      private SnapshotDao snapshotDao;
      @Inject
      private CommandCoordinatorUtil commandCoordinatorUtil;
      @Inject
      private VmStaticDao vmStaticDao;
      @Inject
      private Instance<AddDiskCommandCallback> callbackProvider;
  
      @Inject
      private MultiLevelAdministrationHandler multiLevelAdministrationHandler;
  
      /**
       * Constructor for command creation when compensation is applied on startup
       */
      public AddDiskCommand(Guid commandId) {
          super(commandId);
      }
  
      public AddDiskCommand(T parameters, CommandContext commandContext) {
          super(parameters, commandContext);
          setVdsId(parameters.getVdsId());
      }
  
      @Override
      protected LockProperties applyLockProperties(LockProperties lockProperties) {
          return lockProperties.withScope(LockProperties.Scope.Execution);
      }
  
      @Override
      protected boolean validateInputs() {
          return super.validateInputs();
      }
  
      @Override
      protected boolean validate() {
          if (!isFloatingDisk() && (!validateVm() || !validateDiskVmData())) {
              return false;
          }
  
          Disk diskInfo = getParameters().getDiskInfo();
          if (diskInfo.getDiskStorageType() == DiskStorageType.IMAGE ||
                  diskInfo.getDiskStorageType() == DiskStorageType.CINDER) {
              getDiskImageInfo().setDiskSnapshot(false);
          }
  
          VM vm = getVm();
  
          DiskValidator diskValidator = getDiskValidator(getParameters().getDiskInfo());
          DiskVmElementValidator diskVmElementValidator =
                  getDiskVmElementValidator(getParameters().getDiskInfo(), getDiskVmElement());
          if (vm != null) {
              if (!validateDiskVmData() || !canRunActionOnNonManagedVm()) {
                  return false;
              }
  
              updateDisksFromDb();
  
              if (getDiskVmElement().isBoot() && !validate(diskValidator.isVmNotContainsBootDisk(vm))) {
                  return false;
              }
  
              if (!validate(diskVmElementValidator.isPassDiscardSupported(getStorageDomainId()))) {
                  return false;
              }
  
              // if user sent drive check that its not in use
              if (!isDiskPassPciAndIdeLimit()) {
                  return false;
              }
  
              if (!validate(diskVmElementValidator.isReadOnlyPropertyCompatibleWithInterface())) {
                  return false;
              }
          } else if (Boolean.TRUE.equals(getParameters().getPlugDiskToVm())) {
              return failValidation(EngineMessage.CANNOT_ADD_FLOATING_DISK_WITH_PLUG_VM_SET);
          }
  
          if (!validateQuota()) {
              return false;
          }
  
          switch (getParameters().getDiskInfo().getDiskStorageType()) {
              case IMAGE:
                  if (!checkIfImageDiskCanBeAdded(vm, diskVmElementValidator)) {
                      return false;
                  }
                  return setAndValidateDiskProfiles() &&
                          validate(diskValidator.validateDiskSize());
              case LUN:
                  return checkIfLunDiskCanBeAdded(diskValidator) &&
                          validate(diskValidator.validateDiskSize());
              case CINDER:
                  CinderDisk cinderDisk = (CinderDisk) getParameters().getDiskInfo();
                  cinderDisk.setStorageIds(new ArrayList<>(Collections.singletonList(getStorageDomainId())));
                  StorageDomainValidator storageDomainValidator = createStorageDomainValidator();
                  CinderDisksValidator cinderDisksValidator = new CinderDisksValidator(cinderDisk);
                  return validate(storageDomainValidator.isDomainExistAndActive()) &&
                          validate(cinderDisksValidator.validateCinderDiskLimits()) &&
                          validate(cinderDisksValidator.validateCinderVolumeTypesExist()) &&
                          validate(diskValidator.validateDiskSize());
              case MANAGED_BLOCK_STORAGE:
                  return isSupportedByManagedBlockStorageDomain(getStorageDomain()) &&
                          validate(diskValidator.validateDiskSize());
              default:
                  return true;
          }
      }
  
      protected boolean validateVm() {
          return validate(new VmValidator(getVm()).isVmExists());
      }
  
      protected boolean checkIfLunDiskCanBeAdded(DiskValidator diskValidator) {
          LunDisk lunDisk = (LunDisk) getParameters().getDiskInfo();
          LUNs lun = lunDisk.getLun();
  
          if (!validate(diskValidator.validateConnectionsInLun(lun.getLunType()))) {
              return false;
          }
          if (!validate(diskValidator.validateLunAlreadyInUse())) {
              return false;
          }
  
          if (getVm() != null && !(validate(new VmValidator(getVm()).vmNotLocked()) && isVmNotInPreviewSnapshot())) {
              return false;
          }
  
          DiskVmElementValidator diskVmElementValidator =
                  getDiskVmElementValidator(getParameters().getDiskInfo(), getDiskVmElement());
  
          if (!validate(diskVmElementValidator.isVirtIoScsiValid(getVm()))) {
              return false;
          }
  
          if (!validate(diskVmElementValidator.isDiskInterfaceSupported(getVm()))) {
              return false;
          }
  
          if (getVds() != null) {
              lunFromStorage = getLunDisk(lun, getVds());
              if (lunFromStorage == null) {
                  return failValidation(EngineMessage.ACTION_TYPE_FAILED_DISK_LUN_INVALID);
              }
          }
  
          if (!validate(diskValidator.isUsingScsiReservationValid(getVm(), getDiskVmElement(), lunDisk))) {
              return false;
          }
  
          return true;
      }
  
      /**
       * Retrieves the specified LUN if it's visible to the specified host, or null otherwise.
       *
       * @param lun the LUN to examine.
       * @param vds the host to query from.
       *
       * @return the specified LUN if it's visible to the specified host, or null otherwise.
       */
      protected LUNs getLunDisk(final LUNs lun, VDS vds) {
          return executeGetDeviceList(vds.getId(), lun.getLunType(), lun.getLUNId()).stream().findFirst().orElse(null);
      }
  
      protected List<LUNs> executeGetDeviceList(Guid vdsId, StorageType storageType, String lunId) {
          GetDeviceListVDSCommandParameters parameters =
                  new GetDeviceListVDSCommandParameters(vdsId, storageType, false, Collections.singleton(lunId));
          return (List<LUNs>) runVdsCommand(VDSCommandType.GetDeviceList, parameters).getReturnValue();
      }
  
      protected boolean checkIfImageDiskCanBeAdded(VM vm, DiskVmElementValidator diskVmElementValidator) {
          if (Guid.Empty.equals(getStorageDomainId())) {
              return failValidation(EngineMessage.ACTION_TYPE_FAILED_STORAGE_DOMAIN_NOT_SPECIFIED);
          }
  
          boolean returnValue;
          StorageDomainValidator storageDomainValidator = createStorageDomainValidator();
          // vm agnostic checks
          returnValue =
                  (getParameters().isSkipDomainCheck() || validate(storageDomainValidator.isDomainExistAndActive())) &&
                  validateShareableDiskOnGlusterDomain() &&
                  checkImageConfiguration() &&
                  validate(storageDomainValidator.domainIsValidDestination()) &&
                  validate(storageDomainValidator.hasSpaceForNewDisk(getDiskImageInfo())) &&
                  validate(storageDomainValidator.isDomainWithinThresholds()) &&
                  checkExceedingMaxBlockDiskSize() &&
                  canAddShareableDisk() &&
                  validate(diskVmElementValidator.isVirtIoScsiValid(vm)) &&
                  validate(diskVmElementValidator.isDiskInterfaceSupported(getVm()));
  
          if (returnValue && vm != null) {
              StoragePool sp = getStoragePool(); // Note this is done according to the VM's spId.
              returnValue =
                      validate(new StoragePoolValidator(sp).existsAndUp()) &&
                      isStoragePoolMatching(vm) &&
                      validate(new VmValidator(getVm()).vmNotLocked()) &&
                      isVmNotInPreviewSnapshot();
          }
  
          return returnValue;
      }
  
      private boolean validateShareableDiskOnGlusterDomain() {
          return validate(ValidationResult.failWith(
                  EngineMessage.ACTION_TYPE_FAILED_SHAREABLE_DISKS_NOT_SUPPORTED_ON_GLUSTER_DOMAIN
          ).when(getParameters().getDiskInfo().isShareable() && getStorageDomain().getStorageType() == StorageType.GLUSTERFS));
      }
  
      private boolean canAddShareableDisk() {
          if (getParameters().getDiskInfo().isShareable()) {
              if (!isVolumeFormatSupportedForShareable(((DiskImage) getParameters().getDiskInfo()).getVolumeFormat())) {
                  return failValidation(EngineMessage.SHAREABLE_DISK_IS_NOT_SUPPORTED_BY_VOLUME_FORMAT);
              }
          }
          return true;
      }
  
      private boolean checkExceedingMaxBlockDiskSize() {
          if (isExceedMaxBlockDiskSize()) {
              return failValidation(EngineMessage.ACTION_TYPE_FAILED_DISK_MAX_SIZE_EXCEEDED,
                      String.format("$max_disk_size %1$s", Config.<Integer> getValue(ConfigValues.MaxBlockDiskSizeInGibiBytes)));
          }
          return true;
      }
  
      private boolean isStoragePoolMatching(VM vm) {
          if (storagePoolIsoMapDao.get(new StoragePoolIsoMapId(getStorageDomainId(), vm.getStoragePoolId())) == null) {
              return failValidation(EngineMessage.ACTION_TYPE_FAILED_STORAGE_POOL_OF_VM_NOT_MATCH);
          }
          return true;
      }
  
  
      /** Checks if the image's configuration is legal */
      protected boolean checkImageConfiguration() {
          return ImagesHandler.checkImageConfiguration(
                  getStorageDomain().getStorageStaticData(),
                  getDiskImageInfo(),
                  getReturnValue().getValidationMessages());
      }
  
      private double getRequestDiskSpace() {
          if (getParameters().getDiskInfo().getDiskStorageType().isInternal()) {
              return getDiskImageInfo().getSizeInGigabytes();
          }
          return 0;
      }
  
      private boolean isFloatingDisk() {
          return Guid.isNullOrEmpty(getParameters().getVmId());
      }
  
      /** @return The disk from the parameters, cast to a {@link DiskImage} */
      private DiskImage getDiskImageInfo() {
          return (DiskImage) getParameters().getDiskInfo();
      }
  
      private boolean isExceedMaxBlockDiskSize() {
          if (getStorageDomain().getStorageType().isBlockDomain()) {
              return getRequestDiskSpace() > Config.<Integer> getValue(ConfigValues.MaxBlockDiskSizeInGibiBytes);
          }
  
          return false;
      }
  
      @Override
      public Guid getStorageDomainId() {
          if (super.getStorageDomainId() == null) {
              Guid storageDomainId = getParameters().getStorageDomainId();
              if (storageDomainId == null) {
                  storageDomainId = Guid.Empty;
  
                  // this set is done so that in case we will execute an async task
                  // the correct storage domain id will be set during the call to the end methods
                  getParameters().setStorageDomainId(storageDomainId);
              }
              setStorageDomainId(storageDomainId);
              return storageDomainId;
          }
          return super.getStorageDomainId();
      }
  
      @Override
      public List<PermissionSubject> getPermissionCheckSubjects() {
          List<PermissionSubject> listPermissionSubjects = getVmPermissionSubject();
          // If the storage domain ID is empty/null, it means we are going to create an external disk
          // In order to do that we need CREATE_DISK permissions on System level
          if (getParameters().getStorageDomainId() == null || Guid.Empty.equals(getParameters().getStorageDomainId())) {
              listPermissionSubjects.add(new PermissionSubject(Guid.SYSTEM,
                      VdcObjectType.System,
                      ActionGroup.CREATE_DISK));
              if (getParameters().getDiskInfo().getSgio() == ScsiGenericIO.UNFILTERED) {
                  listPermissionSubjects.add(new PermissionSubject(Guid.SYSTEM,
                          VdcObjectType.System,
                          ActionGroup.CONFIGURE_SCSI_GENERIC_IO));
              }
          } else {
              listPermissionSubjects.add(new PermissionSubject(getParameters().getStorageDomainId(),
                      VdcObjectType.Storage,
                      ActionGroup.CREATE_DISK));
          }
          return listPermissionSubjects;
      }
  
      protected List<PermissionSubject> getVmPermissionSubject() {
          return isFloatingDisk() ? new ArrayList<>() : super.getPermissionCheckSubjects();
      }
  
      @Override
      protected void setActionMessageParameters() {
          addValidationMessage(EngineMessage.VAR__ACTION__ADD);
          addValidationMessage(EngineMessage.VAR__TYPE__DISK);
      }
  
      @Override
      protected void executeVmCommand() {
          createNewDiskId();
          getParameters().setEntityInfo(new EntityInfo(VdcObjectType.Disk, getParameters().getDiskInfo().getId()));
          setDiskAlias();
          switch (getParameters().getDiskInfo().getDiskStorageType()) {
              case IMAGE:
              case KUBERNETES:
                  createDiskBasedOnImage();
                  break;
              case LUN:
                  createDiskBasedOnLun();
                  break;
              case CINDER:
                  createDiskBasedOnCinder();
                  break;
              case MANAGED_BLOCK_STORAGE:
                  createManagedBlockStorageDisk();
                  break;
          }
      }
  
      private void createManagedBlockStorageDisk() {
          Future<ActionReturnValue> future = commandCoordinatorUtil.executeAsyncCommand(
                  ActionType.AddManagedBlockStorageDisk,
                  buildManagedBlockParams(),
                  cloneContextAndDetachFromParent());
          try {
              setReturnValue(future.get());
              setSucceeded(getReturnValue().getSucceeded());
          } catch (InterruptedException | ExecutionException e) {
              log.error("Error creating Cinder disk '{}': {}",
                      getParameters().getDiskInfo().getDiskAlias(),
                      e.getMessage());
              log.debug("Exception", e);
          }
      }
  
      private ActionParametersBase buildManagedBlockParams() {
          AddManagedBlockStorageDiskParameters parameters =
                  new AddManagedBlockStorageDiskParameters(
                          getParameters().getDiskInfo(),
                          getVm() == null ? false: shouldDiskBePlugged());
          parameters.setPlugDiskToVm(getParameters().getPlugDiskToVm());
          parameters.setStorageDomainId(getParameters().getStorageDomainId());
          parameters.setParentParameters(getParameters());
          parameters.setParentCommand(getActionType());
          parameters.setShouldRemainLockedOnSuccesfulExecution(true);
  
          if (getVm() != null) {
              parameters.setVmSnapshotId(snapshotDao.getId(getVmId(), SnapshotType.ACTIVE));
              parameters.setVmId(getVmId());
              parameters.setDiskVmElement(getDiskVmElement());
          }
  
          return parameters;
      }
  
      protected void setDiskAlias() {
          ImagesHandler.setDiskAlias(getParameters().getDiskInfo(), getVm());
      }
  
      private void createNewDiskId() {
          Guid diskId = getParameters().isUsePassedDiskId() ? getParameters().getDiskInfo().getId() : Guid.newGuid();
          getParameters().getDiskInfo().setId(diskId);
          if (!isFloatingDisk()) {
              getDiskVmElement().getId().setDeviceId(diskId);
          }
      }
  
      private void createDiskBasedOnLun() {
          final LUNs lun;
          if (lunFromStorage == null) {
              lun = ((LunDisk) getParameters().getDiskInfo()).getLun();
          } else {
              lun = lunFromStorage;
          }
          TransactionSupport.executeInNewTransaction(() -> {
              lunHelper.proceedDirectLUNInDb(lun, lun.getLunType());
              baseDiskDao.save(getParameters().getDiskInfo());
              diskLunMapDao.save(new DiskLunMap(getParameters().getDiskInfo().getId(), lun.getLUNId()));
              if (getVm() != null) {
                  // The disk VM element has to be added before the VM device since as a part of the VM device creation the
                  // boot order is determined so the VM device creation depends on the existence of the disk VM element
                  addDiskVmElementForDisk(getDiskVmElement());
                  addManagedDeviceForDisk(getParameters().getDiskInfo().getId());
                  vmStaticDao.incrementDbGeneration(getVmId());
              }
              return null;
          });
          getReturnValue().setActionReturnValue(getParameters().getDiskInfo().getId());
          plugDiskToVmIfNeeded();
          setSucceeded(true);
      }
  
      protected VmDevice addManagedDeviceForDisk(Guid diskId) {
          return  getVmDeviceUtils().addDiskDevice(
                  getVmId(),
                  diskId,
                  shouldDiskBePlugged(),
                  Boolean.TRUE.equals(getParameters().getDiskVmElement().isReadOnly()));
      }
  
      protected DiskVmElement addDiskVmElementForDisk(DiskVmElement diskVmElement) {
          diskVmElementDao.save(diskVmElement);
          return diskVmElement;
      }
  
      protected boolean shouldDiskBePlugged() {
          return getVm().getStatus() == VMStatus.Down && !Boolean.FALSE.equals(getParameters().getPlugDiskToVm());
      }
  
      protected boolean useCallback() {
          return (getParameters().getDiskInfo().getDiskStorageType() == DiskStorageType.IMAGE
                  && (parentHasCallback() || !isExecutedAsChildCommand())) ||
                  getParameters().getDiskInfo().getDiskStorageType() == DiskStorageType.MANAGED_BLOCK_STORAGE;
      }
  
      private void createDiskBasedOnImage() {
          if(!getParameters().getDiskInfo().isWipeAfterDeleteSet()) {
              getParameters().getDiskInfo().setWipeAfterDelete(getStorageDomain().getWipeAfterDelete());
          }
          // create from blank template, create new vm snapshot id
          AddImageFromScratchParameters parameters =
                  new AddImageFromScratchParameters(Guid.Empty,
                          getParameters().getVmId(),
                          getDiskImageInfo());
          parameters.setQuotaId(getQuotaId());
          parameters.setDiskProfileId(getDiskImageInfo().getDiskProfileId());
          parameters.setDiskAlias(getDiskAlias());
          if (getParameters().isUsePassedImageId()) {
              parameters.setDestinationImageId(getDiskImageInfo().getImageId());
          }
          parameters.setLeaveLocked(getParameters().isShouldRemainLockedOnSuccesfulExecution());
          parameters.setShouldRemainIllegalOnFailedExecution(getParameters().isShouldRemainIllegalOnFailedExecution());
          parameters.setStorageDomainId(getStorageDomainId());
  
          if (useCallback()) {
              parameters.setParentCommand(ActionType.AddDisk);
              parameters.setParentParameters(getParameters());
          } else {
              parameters.setParentCommand(getParameters().getParentCommand());
              parameters.setParentParameters(getParameters().getParentParameters());
          }
  
          parameters.setEntityInfo(getParameters().getEntityInfo());
          parameters.setStoragePoolId(getStorageDomain().getStoragePoolId());
          setVmSnapshotIdForDisk(parameters);
          ActionReturnValue tmpRetValue =
                  runInternalActionWithTasksContext(ActionType.AddImageFromScratch,
                          parameters,
                          getLock());
          // Setting lock to null because the lock is released in the child command
          setLock(null);
          getTaskIdList().addAll(tmpRetValue.getInternalVdsmTaskIdList());
  
          if (tmpRetValue.getActionReturnValue() != null) {
              attachImage();
              DiskImage diskImage = tmpRetValue.getActionReturnValue();
              addDiskPermissions(diskImage);
              getReturnValue().setActionReturnValue(diskImage.getId());
  
              // We do not want PVCDisk to trigger callbacks, so updating and persisting
              // the parameters is not needed
              if (getParameters().getDiskInfo().getDiskStorageType() == DiskStorageType.IMAGE) {
                  getParameters().setDiskInfo(diskImage);
                  persistCommandIfNeeded();
              }
          }
          getReturnValue().setFault(tmpRetValue.getFault());
          setSucceeded(tmpRetValue.getSucceeded());
      }
  
      protected void attachImage() {
          if (getVm() != null) {
              // The disk VM element has to be added before the VM device since as a part of the VM device creation the
              // boot order is determined so the VM device creation depends on the existence of the disk VM element
              getCompensationContext().snapshotEntity(addDiskVmElementForDisk(getDiskVmElement()));
              getCompensationContext().snapshotNewEntity(addManagedDeviceForDisk(getParameters().getDiskInfo().getId()));
              getCompensationContext().stateChanged();
          }
      }
  
      @Override
      public CommandCallback getCallback() {
          return useCallback() ? callbackProvider.get() :  null;
      }
  
      private void createDiskBasedOnCinder() {
          // ToDo: upon using CoCo infra in this commnad, move this logic.
          Future<ActionReturnValue> future = commandCoordinatorUtil.executeAsyncCommand(
                  ActionType.AddCinderDisk,
                  buildAddCinderDiskParameters(),
                  cloneContextAndDetachFromParent());
          try {
              setReturnValue(future.get());
              setSucceeded(getReturnValue().getSucceeded());
          } catch (InterruptedException | ExecutionException e) {
              log.error("Error creating Cinder disk '{}': {}",
                      getParameters().getDiskInfo().getDiskAlias(),
                      e.getMessage());
              log.debug("Exception", e);
          }
      }
  
      private ActionParametersBase buildAddCinderDiskParameters() {
          AddDiskParameters parameters = new AddDiskParameters(new DiskVmElement(null, getVmId()), getParameters().getDiskInfo());
          parameters.setPlugDiskToVm(getParameters().getPlugDiskToVm());
          parameters.setStorageDomainId(getParameters().getStorageDomainId());
          parameters.setQuotaId(getQuotaId());
          parameters.setEndProcedure(EndProcedure.COMMAND_MANAGED);
          if (getVm() != null) {
              parameters.setVmSnapshotId(snapshotDao.getId(getVmId(), SnapshotType.ACTIVE));
              parameters.setDiskVmElement(getParameters().getDiskVmElement());
          }
          return parameters;
      }
  
      /**
       * If disk is not allow to have snapshot no VM snapshot Id should be updated.
       */
      private void setVmSnapshotIdForDisk(AddImageFromScratchParameters parameters) {
          if (getVm() == null) {
              parameters.setVmSnapshotId(getParameters().getVmSnapshotId());
          } else if (getParameters().getDiskInfo().isAllowSnapshot()) {
              parameters.setVmSnapshotId(snapshotDao.getId(getVmId(), SnapshotType.ACTIVE));
          }
      }
  
      private void addDiskPermissions(Disk disk) {
          if (getCurrentUser() != null) {
              Permission perms =
                      new Permission(getCurrentUser().getId(),
                              PredefinedRoles.DISK_OPERATOR.getId(),
                              disk.getId(),
                              VdcObjectType.Disk);
              multiLevelAdministrationHandler.addPermission(perms);
          }
      }
  
      @Override
      public AuditLogType getAuditLogTypeValue() {
          switch (getActionState()) {
          case EXECUTE:
              if (isDiskStorageTypeRequiresExecuteState()) {
                  return getExecuteAuditLogTypeValue(getSucceeded());
              } else {
                  return getEndSuccessAuditLogTypeValue(getSucceeded());
              }
          case END_SUCCESS:
              return getEndSuccessAuditLogTypeValue(getSucceeded());
  
          default:
              return AuditLogType.USER_ADD_DISK_FINISHED_FAILURE;
          }
      }
  
      private boolean isDiskStorageTypeRequiresExecuteState() {
          return getParameters().getDiskInfo().getDiskStorageType() == DiskStorageType.IMAGE ||
                  getParameters().getDiskInfo().getDiskStorageType() == DiskStorageType.CINDER;
      }
  
      private AuditLogType getExecuteAuditLogTypeValue(boolean successful) {
          boolean isVmNameExist = StringUtils.isNotEmpty(getVmName());
          if (successful) {
              if (isInternalExecution()) {
                  return AuditLogType.ADD_DISK_INTERNAL;
              }
              if (isVmNameExist) {
                  return AuditLogType.USER_ADD_DISK_TO_VM;
              } else {
                  return AuditLogType.USER_ADD_DISK;
              }
          } else {
              if (isInternalExecution()) {
                  return AuditLogType.ADD_DISK_INTERNAL_FAILURE;
              }
              if (isVmNameExist) {
                  return AuditLogType.USER_FAILED_ADD_DISK_TO_VM;
              } else {
                  return AuditLogType.USER_FAILED_ADD_DISK;
              }
          }
      }
  
      protected AuditLogType getEndSuccessAuditLogTypeValue(boolean successful) {
          boolean isVmNameExist = StringUtils.isNotEmpty(getVmName());
          if (successful) {
              if (isVmNameExist) {
                  return AuditLogType.USER_ADD_DISK_TO_VM_FINISHED_SUCCESS;
              } else {
                  return AuditLogType.USER_ADD_DISK_FINISHED_SUCCESS;
              }
          } else {
              if (isVmNameExist) {
                  return AuditLogType.USER_ADD_DISK_TO_VM_FINISHED_FAILURE;
              } else {
                  return AuditLogType.USER_ADD_DISK_FINISHED_FAILURE;
              }
          }
      }
  
      @Override
      protected ActionType getChildActionType() {
          switch (getParameters().getDiskInfo().getDiskStorageType()) {
          case IMAGE:
          case KUBERNETES:
              return ActionType.AddImageFromScratch;
          case CINDER:
              return ActionType.AddCinderDisk;
          case MANAGED_BLOCK_STORAGE:
              return ActionType.AddManagedBlockStorageDisk;
          default:
              return ActionType.Unknown;
          }
      }
  
      @Override
      protected List<Class<?>> getValidationGroups() {
          // Validation of parameters is only required for VM disks as the rest is validated in the validate() phase
          if (!isFloatingDisk()) {
              addValidationGroup(UpdateEntity.class);
          }
          return super.getValidationGroups();
      }
  
      @Override
      protected Map<String, Pair<String, String>> getExclusiveLocks() {
          if (!isFloatingDisk() && getDiskVmElement() != null && getDiskVmElement().isBoot()) {
              return Collections.singletonMap(getParameters().getVmId().toString(),
                      LockMessagesMatchUtil.makeLockingPair(LockingGroup.VM_DISK_BOOT, EngineMessage.ACTION_TYPE_FAILED_OBJECT_LOCKED));
          }
          return null;
      }
  
      @Override
      protected Map<String, Pair<String, String>> getSharedLocks() {
          if (!Guid.isNullOrEmpty(getParameters().getVmId()) && !isInternalExecution()) {
              return Collections.singletonMap(getParameters().getVmId().toString(),
                      LockMessagesMatchUtil.makeLockingPair(LockingGroup.VM, EngineMessage.ACTION_TYPE_FAILED_OBJECT_LOCKED));
          }
          return null;
      }
  
      @Override
      protected void setLoggingForCommand() {
          setCommandShouldBeLogged(true);
      }
  
      private Guid getQuotaId() {
          if (getParameters().getDiskInfo() != null && getParameters().getDiskInfo().getDiskStorageType().isInternal()) {
              Guid quotaId = ((DiskImage) getParameters().getDiskInfo()).getQuotaId();
              return getQuotaManager().getFirstQuotaForUser(
                      quotaId,
                      getStoragePoolId(),
                      getCurrentUser());
          }
          return null;
      }
  
      @Override
      protected void endSuccessfully() {
          plugDiskToVmIfNeeded();
          super.endSuccessfully();
      }
  
      private void plugDiskToVmIfNeeded() {
          if (Boolean.TRUE.equals(getParameters().getPlugDiskToVm()) && getVm() != null) {
              if (!getVm().getStatus().isUpOrPaused()) {
                  log.warn("Disk id - '{}' wasn't plug to VM - '{}'({}) because the VM status is - '{}'",
                          getParameters().getDiskInfo().getId(),
                          getVmId(),
                          getVmName(),
                          getVm().getStatus());
              } else {
                  VmDiskOperationParameterBase params =
                          new VmDiskOperationParameterBase(new DiskVmElement(getParameters().getDiskInfo().getId(),
                                  getVmId()));
                  params.setShouldBeLogged(false);
                  ActionReturnValue returnValue = runInternalAction(ActionType.HotPlugDiskToVm, params);
                  if (!returnValue.getSucceeded()) {
                      auditLogDirector.log(this, AuditLogType.USER_FAILED_HOTPLUG_DISK);
                  }
              }
          }
      }
  
      protected boolean setAndValidateDiskProfiles() {
          return validate(diskProfileHelper.setAndValidateDiskProfiles(Collections.singletonMap(getDiskImageInfo(),
                  getStorageDomainId()), getCurrentUser()));
      }
  
      @Override
      public List<QuotaConsumptionParameter> getQuotaStorageConsumptionParameters() {
          List<QuotaConsumptionParameter> list = new ArrayList<>();
  
          if (getParameters().getDiskInfo().getDiskStorageType().isInternal()) {
              list.add(new QuotaStorageConsumptionParameter(
                  getQuotaId(),
                  QuotaConsumptionParameter.QuotaAction.CONSUME,
                  getStorageDomainId(),
                  getRequestDiskSpace()));
          }
          return list;
      }
  
      protected StorageDomainValidator createStorageDomainValidator() {
          return new StorageDomainValidator(getStorageDomain());
      }
  }
  