package org.ovirt.engine.ui.uicommonweb.models.vms; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import org.ovirt.engine.core.common.action.VdcActionType; import org.ovirt.engine.core.common.businessentities.VM; import org.ovirt.engine.core.common.businessentities.storage.Disk; import org.ovirt.engine.core.common.businessentities.storage.DiskVmElement; import org.ovirt.engine.core.common.validation.VmActionByVmOriginTypeValidator; import org.ovirt.engine.ui.uicommonweb.ICommandTarget; import org.ovirt.engine.ui.uicommonweb.UICommand; import org.ovirt.engine.ui.uicommonweb.dataprovider.AsyncDataProvider; import org.ovirt.engine.ui.uicommonweb.models.EntityModel; import org.ovirt.engine.ui.uicommonweb.models.ListModel; import org.ovirt.engine.ui.uicommonweb.models.Model; public class InstanceImagesModel extends ListModel<InstanceImageLineModel> { private Model parentListModel; private UnitVmModel unitVmModel; private List<RemoveDiskModel> removeDiskModels = new ArrayList<>(); private RemoveDiskModel removeDiskModel; private RemoveApprovedCallback callback; private VM vm; public InstanceImagesModel(UnitVmModel unitVmModel, Model parentListModel) { this.parentListModel = parentListModel; this.unitVmModel = unitVmModel; } public Model getParentListModel() { return parentListModel; } public UnitVmModel getUnitVmModel() { return unitVmModel; } public void approveRemoveDisk(InstanceImageLineModel lineModel, RemoveApprovedCallback callback) { if (!lineModel.isDiskExists() || lineModel.getVm() == null) { // no problem, the item can be removed without explicitly asking the user callback.removeApproved(true); return; } this.callback = callback; removeDiskModel = new RemoveDiskModel(); List<Disk> disksToRemove = Arrays.asList(lineModel.getDiskModel().getEntity().getDisk()); VM vm = lineModel.getVm(); if (getParentListModel() instanceof HasDiskWindow) { ((HasDiskWindow) getParentListModel()).setDiskWindow(removeDiskModel); } removeDiskModel.initialize(vm, disksToRemove, this); } private void onRemove() { if (!removeDiskModel.validate()) { return; } removeDiskModels.add(removeDiskModel); callback.removeApproved(true); hideRemoveDiskAndShowEditVm(); } private void onCancel() { callback.removeApproved(false); hideRemoveDiskAndShowEditVm(); } private void hideRemoveDiskAndShowEditVm() { if (getParentListModel() instanceof HasDiskWindow) { ((HasDiskWindow) getParentListModel()).setDiskWindow(null); } } @Override public void executeCommand(UICommand command) { super.executeCommand(command); if (RemoveDiskModel.ON_REMOVE.equals(command.getName())) { onRemove(); } else if (RemoveDiskModel.CANCEL_REMOVE.equals(command.getName())) { onCancel(); } } public void setVm(VM vm) { this.vm = vm; } public VM getVm() { return vm; } public static interface RemoveApprovedCallback { void removeApproved(boolean approved); } public void executeDiskModifications(VM vm) { // this is done on the background - the window is not visible anymore disableLineModels(); if (isDiskUpdateAllowed(vm)) { executeDeleteAndCallNew(vm); } } private boolean isDiskUpdateAllowed(VM vm) { return VmActionByVmOriginTypeValidator.isCommandAllowed(vm, VdcActionType.UpdateVmDisk); } private void disableLineModels() { for (InstanceImageLineModel model : getItems()) { model.deactivate(); } } private void executeDeleteAndCallNew(final VM vm) { if (removeDiskModels.size() == 0) { executeNewAndEdit(vm); return; } for (RemoveDiskModel removeDisk : removeDiskModels ) { removeDisk.onRemove(new ICommandTarget() { @Override public void executeCommand(UICommand command) { executeNewAndEdit(vm); } @Override public void executeCommand(UICommand uiCommand, Object... parameters) { executeNewAndEdit(vm); } }); } } private void executeNewAndEdit(final VM vm) { if (getItems() == null) { return; } AsyncDataProvider.getInstance().getVmDiskList(new AsyncQuery<>(disks -> { Iterator<InstanceImageLineModel> lineModelIterator = orderedDisksIterator(disks, vm); storeNextDisk(lineModelIterator, vm); }), vm.getId()); } /** * Finds the disk which is boot on the VM but has been configured to be non boot. If finds such a disk, the resulting * list will contain as a first command the one which executes this operation. * * It is needed because they can be other which make an another disk boot and the VM can not have more than one boot * disk - so the validation on server would fail. */ private Iterator<InstanceImageLineModel> orderedDisksIterator(List<Disk> disks, VM vm) { if (disks.size() == 0) { return getItems().iterator(); } Disk previouslyBootDisk = findBoot(disks, vm); if (previouslyBootDisk == null) { return getItems().iterator(); } InstanceImageLineModel fromBootToNonBoot = findBecomeNonBoot(previouslyBootDisk); if (fromBootToNonBoot == null) { return getItems().iterator(); } // now we know that the disk changed from boot to non boot so this command has to be executed as first Set<InstanceImageLineModel> res = new LinkedHashSet<>(); res.add(fromBootToNonBoot); res.addAll(getItems()); return res.iterator(); } private InstanceImageLineModel findBecomeNonBoot(Disk bootDisk) { for (InstanceImageLineModel model : getItems()) { if (model.isGhost()) { // this is not an actual item continue; } Disk disk = model.getDiskModel().getEntity().getDisk(); if (disk.getId() == null) { // does not yet exist on the server so no need to edit it continue; } if (disk.getId().equals(bootDisk.getId())) { if (disk.getDiskVmElementForVm(getVm().getId()).isBoot()) { return null; } else { // removed boot flag, this command has to be executed first so if other disk is marked as boot, // it will not fail with the message that you can have only one boot return model; } } } return null; } private Disk findBoot(List<Disk> disks, VM vm) { for (Disk disk : disks) { DiskVmElement dve = disk.getDiskVmElementForVm(vm.getId()); if (dve != null && dve.isBoot()) { return disk; } } return null; } private void storeNextDisk(final Iterator<InstanceImageLineModel> lineModelIterator, final VM vm) { if (!lineModelIterator.hasNext()) { return; } InstanceImageLineModel instanceImageLineModel = lineModelIterator.next(); AbstractDiskModel diskModel = instanceImageLineModel.getDiskModel().getEntity(); if (diskModel == null || !instanceImageLineModel.isChanged()) { storeNextDisk(lineModelIterator, vm); } else { diskModel.setVm(vm); diskModel.getDiskVmElement().getId().setVmId(vm.getId()); diskModel.store(result -> { // have to wait until the previous returned because the operation needs a lock on the VM storeNextDisk(lineModelIterator, vm); }); } } public List<Disk> getAllCurrentDisks() { if (getItems() == null) { return Collections.emptyList(); } List<Disk> res = new ArrayList<>(); for (InstanceImageLineModel line : getItems()) { if (line.isGhost()) { continue; } res.add(line.getDisk()); } return res; } public List<DiskModel> getAllCurrentDisksModels() { if (getItems() == null) { return Collections.emptyList(); } List<DiskModel> diskModels = new ArrayList<>(); for (InstanceImageLineModel line : getItems()) { if (line.isGhost()) { continue; } diskModels.add(line.getDiskModel().getEntity()); } return diskModels; } /** * Returns a list of non-sharable disks which have been set as to attach in the new/edit VM dialog but the dialog has not yet been submitted */ public List<Disk> getNotYetAttachedNotAttachableDisks() { List<Disk> res = new ArrayList<>(); for (InstanceImageLineModel line : getItems()) { if (line.isGhost()) { continue; } EntityModel<AbstractDiskModel> diskModel = line.getDiskModel(); if (diskModel == null) { continue; } // it will be InstanceImagesAttachDiskModel only if not yet submitted if (!(diskModel.getEntity() instanceof InstanceImagesAttachDiskModel)) { continue; } Disk disk = line.getDisk(); if (disk == null || disk.isShareable()) { continue; } res.add(disk); } return res; } public void updateActionsAvailability() { boolean clusterSelected = unitVmModel.getSelectedCluster() != null; boolean osSelected = unitVmModel.getOSType().getSelectedItem() != null; if (getItems() == null) { return; } for (InstanceImageLineModel model : getItems()) { if (model.isGhost()) { continue; } model.setEnabled(clusterSelected && osSelected); } setIsChangeable(clusterSelected && osSelected); } }