package org.ovirt.engine.core.bll.storage.disk;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.inject.Inject;
import org.apache.commons.lang.StringUtils;
import org.ovirt.engine.core.bll.LockMessagesMatchUtil;
import org.ovirt.engine.core.bll.VmCommand;
import org.ovirt.engine.core.bll.context.CommandContext;
import org.ovirt.engine.core.bll.storage.connection.IStorageHelper;
import org.ovirt.engine.core.bll.storage.connection.StorageHelperBase;
import org.ovirt.engine.core.bll.storage.connection.StorageHelperDirector;
import org.ovirt.engine.core.bll.storage.disk.cinder.CinderBroker;
import org.ovirt.engine.core.bll.validator.VmValidator;
import org.ovirt.engine.core.bll.validator.storage.DiskValidator;
import org.ovirt.engine.core.bll.validator.storage.DiskVmElementValidator;
import org.ovirt.engine.core.common.action.VmDiskOperationParameterBase;
import org.ovirt.engine.core.common.businessentities.StorageServerConnections;
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.network.VmNic;
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.LUNs;
import org.ovirt.engine.core.common.businessentities.storage.LunDisk;
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.EngineError;
import org.ovirt.engine.core.common.errors.EngineException;
import org.ovirt.engine.core.common.errors.EngineMessage;
import org.ovirt.engine.core.common.locks.LockingGroup;
import org.ovirt.engine.core.common.vdscommands.HotPlugDiskVDSParameters;
import org.ovirt.engine.core.common.vdscommands.VDSCommandType;
import org.ovirt.engine.core.compat.Guid;
import org.ovirt.engine.core.dao.DiskVmElementDao;
import org.ovirt.engine.core.dao.StorageServerConnectionDao;
import org.ovirt.engine.core.dao.VmDao;
import org.ovirt.engine.core.dao.VmDeviceDao;
import org.ovirt.engine.core.dao.network.VmNicDao;
import org.ovirt.engine.core.utils.StringMapUtils;
import org.ovirt.engine.core.utils.archstrategy.ArchStrategyFactory;
import org.ovirt.engine.core.utils.lock.EngineLock;
import org.ovirt.engine.core.vdsbroker.architecture.GetControllerIndices;
import org.ovirt.engine.core.vdsbroker.builder.vminfo.VmInfoBuildUtils;
import org.ovirt.engine.core.vdsbroker.vdsbroker.VdsProperties;
public abstract class AbstractDiskVmCommand<T extends VmDiskOperationParameterBase> extends VmCommand<T> {
private CinderBroker cinderBroker;
@Inject
private VmInfoBuildUtils vmInfoBuildUtils;
@Inject
private StorageServerConnectionDao storageServerConnectionDao;
@Inject
private VmNicDao vmNicDao;
@Inject
private DiskVmElementDao diskVmElementDao;
@Inject
private VmDeviceDao vmDeviceDao;
@Inject
private VmDao vmDao;
protected AbstractDiskVmCommand(Guid commandId) {
super(commandId);
}
public AbstractDiskVmCommand(T parameters, CommandContext commandContext) {
super(parameters, commandContext);
}
protected void performPlugCommand(VDSCommandType commandType,
Disk disk, VmDevice vmDevice) {
if (disk.getDiskStorageType() == DiskStorageType.LUN) {
LunDisk lunDisk = (LunDisk) disk;
if (commandType == VDSCommandType.HotPlugDisk) {
LUNs lun = lunDisk.getLun();
updateLUNConnectionsInfo(lun);
Map<StorageType, List<StorageServerConnections>> lunsByStorageType =
StorageHelperBase.filterConnectionsByStorageType(lun);
for (StorageType storageType : lunsByStorageType.keySet()) {
if (!getStorageHelper(storageType).connectStorageToLunByVdsId(null,
getVm().getRunOnVds(),
lun,
getVm().getStoragePoolId())) {
throw new EngineException(EngineError.StorageServerConnectionError);
}
}
}
} else if (disk.getDiskStorageType() == DiskStorageType.CINDER) {
CinderDisk cinderDisk = (CinderDisk) disk;
setStorageDomainId(cinderDisk.getStorageIds().get(0));
getCinderBroker().updateConnectionInfoForDisk(cinderDisk);
}
Map<String, String> diskAddressMap = getDiskAddressMap(vmDevice, getDiskVmElement().getDiskInterface());
runVdsCommand(commandType, new HotPlugDiskVDSParameters(getVm().getRunOnVds(),
getVm(), disk, vmDevice, diskAddressMap, getDiskVmElement().getDiskInterface(),
getDiskVmElement().isPassDiscard()));
}
private IStorageHelper getStorageHelper(StorageType storageType) {
return StorageHelperDirector.getInstance().getItem(storageType);
}
/**
* Sets the LUN connection list from the DB.
*
* @param lun
* - The lun we set the connection at.
*/
private void updateLUNConnectionsInfo(LUNs lun) {
lun.setLunConnections(new ArrayList<>(storageServerConnectionDao.getAllForLun(lun.getLUNId())));
}
protected boolean isDiskPassPciAndIdeLimit() {
List<VmNic> vmInterfaces = vmNicDao.getAllForVm(getVmId());
List<DiskVmElement> diskVmElements = diskVmElementDao.getAllForVm(getVmId());
diskVmElements.add(getDiskVmElement());
return validate(VmValidator.checkPciAndIdeLimit(getVm().getOs(),
getVm().getCompatibilityVersion(),
getVm().getNumOfMonitors(),
vmInterfaces,
diskVmElements,
isVirtioScsiControllerAttached(getVmId()),
hasWatchdog(getVmId()),
isBalloonEnabled(getVmId()),
isSoundDeviceEnabled(getVmId())));
}
protected boolean isVirtioScsiControllerAttached(Guid vmId) {
return getVmDeviceUtils().hasVirtioScsiController(vmId);
}
protected boolean isBalloonEnabled(Guid vmId) {
return getVmDeviceUtils().hasMemoryBalloon(vmId);
}
protected boolean isSoundDeviceEnabled(Guid vmId) {
return getVmDeviceUtils().hasSoundDevice(vmId);
}
protected boolean hasWatchdog(Guid vmId) {
return getVmDeviceUtils().hasWatchdog(vmId);
}
/** Updates the VM's disks from the database */
protected void updateDisksFromDb() {
vmHandler.updateDisksFromDb(getVm());
}
protected boolean isVolumeFormatSupportedForShareable(VolumeFormat volumeFormat) {
return volumeFormat == VolumeFormat.RAW;
}
protected boolean isVmInUpPausedDownStatus() {
if (getVm().getStatus() != VMStatus.Up && getVm().getStatus() != VMStatus.Down
&& getVm().getStatus() != VMStatus.Paused) {
return failVmStatusIllegal();
}
return true;
}
protected boolean isDiskExistAndAttachedToVm(Disk disk) {
DiskValidator diskValidator = getDiskValidator(disk);
return validate(diskValidator.isDiskExists()) && validate(diskValidator.isDiskAttachedToVm(getVm()));
}
protected boolean checkDiskUsedAsOvfStore(Disk disk) {
return checkDiskUsedAsOvfStore(getDiskValidator(disk));
}
protected boolean checkDiskUsedAsOvfStore(DiskValidator diskValidator) {
return validate(diskValidator.isDiskUsedAsOvfStore());
}
public String getDiskAlias() {
return getParameters().getDiskInfo().getDiskAlias();
}
protected boolean validateDiskVmData() {
if (getDiskVmElement() == null || getDiskVmElement().getId() == null ||
!Objects.equals(getDiskVmElement().getId().getVmId(), getVmId())) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_DISK_VM_DATA_MISSING);
}
return true;
}
protected boolean validateQuota() {
if (!getParameters().getDiskInfo().getDiskStorageType().isInternal()) {
return true;
}
return validateQuota(((DiskImage) getParameters().getDiskInfo()).getQuotaId());
}
protected DiskVmElement getDiskVmElement() {
return getParameters().getDiskVmElement();
}
@Override
public Map<String, String> getJobMessageProperties() {
if (jobProperties == null) {
jobProperties = super.getJobMessageProperties();
jobProperties.put("diskalias", getDiskAlias());
}
return jobProperties;
}
protected DiskValidator getDiskValidator(Disk disk) {
return new DiskValidator(disk);
}
protected DiskVmElementValidator getDiskVmElementValidator(Disk disk, DiskVmElement diskVmElement) {
return new DiskVmElementValidator(disk, diskVmElement);
}
protected boolean isVmNotInPreviewSnapshot() {
return
getVmId() != null &&
validate(snapshotsValidator.vmNotDuringSnapshot(getVmId())) &&
validate(snapshotsValidator.vmNotInPreview(getVmId()));
}
public CinderBroker getCinderBroker() {
if (cinderBroker == null) {
cinderBroker = new CinderBroker(getStorageDomainId(), getReturnValue().getExecuteFailedMessages());
}
return cinderBroker;
}
/**
* Returns disk's address map by specified VmDevice and DiskInterface
* (note: for VirtIO_SCSI/SPAPR_VSCSI interfaces, the method updates the VM device's address accordingly).
* @return disk's address map
*/
public Map<String, String> getDiskAddressMap(VmDevice vmDevice, DiskInterface diskInterface) {
String address = vmDevice.getAddress();
if (diskInterface != DiskInterface.VirtIO_SCSI && diskInterface != DiskInterface.SPAPR_VSCSI) {
if (StringUtils.isNotBlank(address)) {
return StringMapUtils.string2Map(address);
}
} else {
EngineLock vmDiskHotPlugEngineLock = null;
try {
vmDiskHotPlugEngineLock = lockVmDiskHotPlugWithWait();
VM vm = vmDao.get(getParameters().getVmId());
Map<DiskInterface, Integer> controllerIndexMap =
ArchStrategyFactory.getStrategy(vm.getClusterArch()).run(new GetControllerIndices()).returnValue();
int virtioScsiIndex = controllerIndexMap.get(DiskInterface.VirtIO_SCSI);
int sPaprVscsiIndex = controllerIndexMap.get(DiskInterface.SPAPR_VSCSI);
if (diskInterface == DiskInterface.VirtIO_SCSI) {
Map<Integer, Map<VmDevice, Integer>> vmDeviceUnitMap = vmInfoBuildUtils.getVmDeviceUnitMapForVirtioScsiDisks(getVm());
return getAddressMapForScsiDisk(address, vmDeviceUnitMapForController(vmDevice, vmDeviceUnitMap), vmDevice, virtioScsiIndex, false);
} else if (diskInterface == DiskInterface.SPAPR_VSCSI) {
Map<Integer, Map<VmDevice, Integer>> vmDeviceUnitMap = vmInfoBuildUtils.getVmDeviceUnitMapForSpaprScsiDisks(getVm());
return getAddressMapForScsiDisk(address, vmDeviceUnitMapForController(vmDevice, vmDeviceUnitMap), vmDevice, sPaprVscsiIndex, true);
}
} finally {
lockManager.releaseLock(vmDiskHotPlugEngineLock);
}
}
return null;
}
private Map<VmDevice, Integer> vmDeviceUnitMapForController(VmDevice vmDevice,
Map<Integer, Map<VmDevice, Integer>> vmDeviceUnitMap) {
int numOfDisks = getVm().getDiskMap().values().size();
int controllerId = vmInfoBuildUtils.getControllerForScsiDisk(vmDevice, getVm(), numOfDisks);
if (!vmDeviceUnitMap.containsKey(controllerId)) {
return new HashMap<>();
}
return vmDeviceUnitMap.get(controllerId);
}
private Map<String, String> getAddressMapForScsiDisk(String address,
Map<VmDevice, Integer> vmDeviceUnitMap,
VmDevice vmDevice,
int controllerIndex,
boolean reserveFirstAddress) {
Map<String, String> addressMap;
int availableUnit = vmInfoBuildUtils.getAvailableUnitForScsiDisk(vmDeviceUnitMap, reserveFirstAddress);
// If address has been already set before, verify its uniqueness;
// Otherwise, set address according to the next available unit.
if (StringUtils.isNotBlank(address)) {
addressMap = StringMapUtils.string2Map(address);
int unit = Integer.parseInt(addressMap.get(VdsProperties.Unit));
if (vmDeviceUnitMap.containsValue(unit)) {
addressMap = vmInfoBuildUtils.createAddressForScsiDisk(controllerIndex, availableUnit);
}
} else {
addressMap = vmInfoBuildUtils.createAddressForScsiDisk(controllerIndex, availableUnit);
}
// Updating device's address immediately (instead of waiting to VmsMonitoring)
// to prevent a duplicate unit value (i.e. ensuring a unique unit value).
updateVmDeviceAddress(addressMap.toString(), vmDevice);
return addressMap;
}
protected void updateVmDeviceAddress(final String address, final VmDevice vmDevice) {
vmDevice.setAddress(address);
getCompensationContext().snapshotEntity(vmDevice);
getCompensationContext().stateChanged();
vmDeviceDao.update(vmDevice);
}
protected EngineLock lockVmDiskHotPlugWithWait() {
EngineLock vmDiskHotPlugEngineLock = new EngineLock();
vmDiskHotPlugEngineLock.setExclusiveLocks(Collections.singletonMap(getVmId().toString(),
LockMessagesMatchUtil.makeLockingPair(LockingGroup.VM_DISK_HOT_PLUG,
EngineMessage.ACTION_TYPE_FAILED_OBJECT_LOCKED)));
lockManager.acquireLockWait(vmDiskHotPlugEngineLock);
return vmDiskHotPlugEngineLock;
}
}