/** The following method will create a Shareable DiskImage with a specified format */
 private DiskImage createShareableDisk(VolumeFormat volumeFormat) {
   DiskImage disk = createDiskImage();
   disk.setvolumeFormat(volumeFormat);
   disk.setShareable(true);
   disk.setDiskInterface(DiskInterface.VirtIO);
   return disk;
 }
 /**
  * This method is used for storage allocation validations, where the disks are the template's,
  * which could have another volume type/format than the target disk volume type/format, which is
  * not yet created. "Real" override for these values is done in CreateSnapshotCommand, when
  * creating the new DiskImages.
  */
 public static List<DiskImage> getDisksDummiesForStorageAllocations(
     Collection<DiskImage> originalDisks) {
   List<DiskImage> diskDummies = new ArrayList<>(originalDisks.size());
   for (DiskImage diskImage : originalDisks) {
     DiskImage clone = DiskImage.copyOf(diskImage);
     clone.setVolumeType(VolumeType.Sparse);
     clone.setvolumeFormat(VolumeFormat.COW);
     diskDummies.add(clone);
   }
   return diskDummies;
 }
 @Test
 public void canDoActionFailsOnNullDiskInterface() throws Exception {
   Guid storageId = Guid.newGuid();
   DiskImage image = new DiskImage();
   image.setvolumeFormat(VolumeFormat.COW);
   image.setVolumeType(VolumeType.Preallocated);
   AddDiskParameters params = new AddDiskParameters(Guid.newGuid(), image);
   initializeCommand(storageId, params);
   assertFalse(command.validateInputs());
   assertTrue(
       command
           .getReturnValue()
           .getCanDoActionMessages()
           .contains("VALIDATION_DISK_INTERFACE_NOT_NULL"));
 }
  /** CanDoAction should succeed when creating a Shareable Disk with RAW volume format */
  @Test
  public void canDoActionShareableDiskVolumeFormatSucceeds() {
    DiskImage image = createShareableDiskImage();
    image.setvolumeFormat(VolumeFormat.RAW);

    AddDiskParameters parameters = createParameters();
    parameters.setDiskInfo(image);
    Guid storageId = Guid.newGuid();
    initializeCommand(storageId, parameters);

    mockVm();
    mockStorageDomain(storageId, Version.v3_1);
    mockStoragePoolIsoMap();
    mockInterfaceList();
    mockMaxPciSlots();

    runAndAssertCanDoActionSuccess();
  }
  public static DiskImage cloneDiskImage(
      Guid storageDomainId,
      Guid newImageGroupId,
      Guid newImageGuid,
      DiskImage srcDiskImage,
      Guid diskProfileId,
      Guid snapshotId,
      DiskImage diskImageFromClient) {

    DiskImage clonedDiskImage = DiskImage.copyOf(srcDiskImage);
    clonedDiskImage.setImageId(newImageGuid);
    clonedDiskImage.setParentId(Guid.Empty);
    clonedDiskImage.setImageTemplateId(Guid.Empty);
    clonedDiskImage.setVmSnapshotId(snapshotId);
    clonedDiskImage.setId(newImageGroupId);
    clonedDiskImage.setLastModifiedDate(new Date());
    clonedDiskImage.setvolumeFormat(srcDiskImage.getVolumeFormat());
    clonedDiskImage.setVolumeType(srcDiskImage.getVolumeType());
    ArrayList<Guid> storageIds = new ArrayList<>();
    storageIds.add(storageDomainId);
    clonedDiskImage.setStorageIds(storageIds);
    clonedDiskImage.setDiskProfileId(diskProfileId);

    // If volume information was changed at client , use its volume information.
    // If volume information was not changed at client - use the volume information of the ancestral
    // image
    if (diskImageFromClient != null) {
      if (volumeInfoChanged(diskImageFromClient, srcDiskImage)) {
        changeVolumeInfo(clonedDiskImage, diskImageFromClient);
      } else {
        DiskImage ancestorDiskImage = getDiskImageDao().getAncestor(srcDiskImage.getImageId());
        changeVolumeInfo(clonedDiskImage, ancestorDiskImage);
      }
    } else {
      DiskImage ancestorDiskImage = getDiskImageDao().getAncestor(srcDiskImage.getImageId());
      changeVolumeInfo(clonedDiskImage, ancestorDiskImage);
    }

    return clonedDiskImage;
  }
  /** CanDoAction should fail when creating a Shareable Disk with COW volume format */
  @Test
  public void canDoActionShareableDiskVolumeFormatFails() {
    DiskImage image = createShareableDiskImage();
    image.setvolumeFormat(VolumeFormat.COW);

    AddDiskParameters parameters = createParameters();
    parameters.setDiskInfo(image);
    Guid storageId = Guid.newGuid();
    initializeCommand(storageId, parameters);

    mockVm();
    mockStorageDomain(storageId, Version.v3_1);
    mockStoragePoolIsoMap();
    mockMaxPciSlots();

    assertFalse(command.canDoAction());
    assertTrue(
        command
            .getReturnValue()
            .getCanDoActionMessages()
            .contains(EngineMessage.SHAREABLE_DISK_IS_NOT_SUPPORTED_BY_VOLUME_FORMAT.toString()));
  }
  @Test
  public void canDoActionShareableDiskOnGlusterFails() {
    DiskImage image = createShareableDiskImage();
    image.setvolumeFormat(VolumeFormat.RAW);

    AddDiskParameters parameters = createParameters();
    parameters.setDiskInfo(image);

    Guid storageId = Guid.newGuid();
    initializeCommand(storageId, parameters);
    mockVm();
    mockStorageDomain(storageId, StorageType.GLUSTERFS, Version.v3_1);
    mockStoragePoolIsoMap();
    mockMaxPciSlots();

    assertFalse(command.canDoAction());
    assertTrue(
        command
            .getReturnValue()
            .getCanDoActionMessages()
            .contains(
                EngineMessage.ACTION_TYPE_FAILED_SHAREABLE_DISKS_NOT_SUPPORTED_ON_GLUSTER_DOMAIN
                    .toString()));
  }
 protected static void changeVolumeInfo(DiskImage clonedDiskImage, DiskImage diskImageFromClient) {
   clonedDiskImage.setvolumeFormat(diskImageFromClient.getVolumeFormat());
   clonedDiskImage.setVolumeType(diskImageFromClient.getVolumeType());
 }
  /**
   * After merging the snapshots, update the image and snapshot records in the database to reflect
   * the changes. This handles either forward or backwards merge (detected). It will either then
   * remove the images, or mark them illegal (to handle the case where image deletion failed).
   *
   * @param removeImages Remove the images from the database, or if false, only mark them illegal
   */
  private void syncDbRecords(boolean removeImages) {
    // If deletion failed after a backwards merge, the snapshots' images need to be swapped
    // as they would upon success.  Instead of removing them, mark them illegal.
    DiskImage baseImage = getDiskImage();
    DiskImage topImage = getDestinationDiskImage();

    // The vdsm merge verb may decide to perform a forward or backward merge.
    if (topImage == null) {
      log.debug("No merge destination image, not updating image/snapshot association");
    } else if (getParameters().getMergeStatusReturnValue().getBlockJobType()
        == VmBlockJobType.PULL) {
      // For forward merge, the volume format and type may change.
      topImage.setvolumeFormat(baseImage.getVolumeFormat());
      topImage.setVolumeType(baseImage.getVolumeType());
      topImage.setParentId(baseImage.getParentId());
      topImage.setImageStatus(ImageStatus.OK);

      getBaseDiskDao().update(topImage);
      getImageDao().update(topImage.getImage());
      updateDiskImageDynamic(topImage);

      updateVmConfigurationForImageRemoval(
          baseImage.getImage().getSnapshotId(), baseImage.getImageId());
    } else {
      // For backwards merge, the prior base image now has the data associated with the newer
      // snapshot we want to keep.  Re-associate this older image with the newer snapshot.
      // The base snapshot is deleted if everything went well.  In case it's not deleted, we
      // hijack it to preserve a link to the broken image.  This makes the image discoverable
      // so that we can retry the deletion later, yet doesn't corrupt the VM image chain.
      List<DiskImage> children =
          DbFacade.getInstance().getDiskImageDao().getAllSnapshotsForParent(topImage.getImageId());
      if (!children.isEmpty()) {
        DiskImage childImage = children.get(0);
        childImage.setParentId(baseImage.getImageId());
        getImageDao().update(childImage.getImage());
      }

      Image oldTopImage = topImage.getImage();
      topImage.setImage(baseImage.getImage());
      baseImage.setImage(oldTopImage);

      Guid oldTopSnapshotId = topImage.getImage().getSnapshotId();
      topImage.getImage().setSnapshotId(baseImage.getImage().getSnapshotId());
      baseImage.getImage().setSnapshotId(oldTopSnapshotId);

      boolean oldTopIsActive = topImage.getImage().isActive();
      topImage.getImage().setActive(baseImage.getImage().isActive());
      VolumeClassification baseImageVolumeClassification =
          VolumeClassification.getVolumeClassificationByActiveFlag(baseImage.getImage().isActive());
      topImage.getImage().setVolumeClassification(baseImageVolumeClassification);
      baseImage.getImage().setActive(oldTopIsActive);
      VolumeClassification oldTopVolumeClassification =
          VolumeClassification.getVolumeClassificationByActiveFlag(oldTopIsActive);
      topImage.getImage().setVolumeClassification(oldTopVolumeClassification);

      topImage.setSize(baseImage.getSize());
      topImage.setImageStatus(ImageStatus.OK);
      getBaseDiskDao().update(topImage);
      getImageDao().update(topImage.getImage());
      updateDiskImageDynamic(topImage);

      getBaseDiskDao().update(baseImage);
      getImageDao().update(baseImage.getImage());

      updateVmConfigurationForImageChange(
          topImage.getImage().getSnapshotId(), baseImage.getImageId(), topImage);
      updateVmConfigurationForImageRemoval(
          baseImage.getImage().getSnapshotId(), topImage.getImageId());
    }

    Set<Guid> imagesToUpdate = getParameters().getMergeStatusReturnValue().getImagesToRemove();
    if (imagesToUpdate == null) {
      log.error("Failed to update orphaned images in db: image list could not be retrieved");
      return;
    }
    for (Guid imageId : imagesToUpdate) {
      if (removeImages) {
        getImageDao().remove(imageId);
      } else {
        // The (illegal && no-parent && no-children) status indicates an orphaned image.
        Image image = getImageDao().get(imageId);
        image.setStatus(ImageStatus.ILLEGAL);
        image.setParentId(Guid.Empty);
        getImageDao().update(image);
      }
    }
  }