package org.ovirt.engine.core.bll.storage.connection; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.concurrent.Callable; import java.util.stream.Collectors; import org.ovirt.engine.core.bll.Backend; import org.ovirt.engine.core.bll.ValidationResult; import org.ovirt.engine.core.bll.context.CommandContext; import org.ovirt.engine.core.bll.provider.storage.OpenStackVolumeProviderProxy; import org.ovirt.engine.core.common.AuditLogType; import org.ovirt.engine.core.common.action.ConnectHostToStoragePoolServersParameters; import org.ovirt.engine.core.common.action.HostStoragePoolParametersBase; import org.ovirt.engine.core.common.businessentities.NonOperationalReason; import org.ovirt.engine.core.common.businessentities.Provider; import org.ovirt.engine.core.common.businessentities.StorageDomain; import org.ovirt.engine.core.common.businessentities.StorageDomainStatus; import org.ovirt.engine.core.common.businessentities.StoragePoolIsoMap; import org.ovirt.engine.core.common.businessentities.StoragePoolIsoMapId; import org.ovirt.engine.core.common.businessentities.StorageServerConnections; import org.ovirt.engine.core.common.businessentities.VDS; import org.ovirt.engine.core.common.businessentities.storage.DiskImage; import org.ovirt.engine.core.common.businessentities.storage.LibvirtSecret; import org.ovirt.engine.core.common.businessentities.storage.StorageType; import org.ovirt.engine.core.common.errors.EngineException; import org.ovirt.engine.core.common.errors.EngineFault; import org.ovirt.engine.core.common.errors.EngineMessage; import org.ovirt.engine.core.common.utils.Pair; import org.ovirt.engine.core.common.vdscommands.RegisterLibvirtSecretsVDSParameters; import org.ovirt.engine.core.common.vdscommands.UnregisterLibvirtSecretsVDSParameters; import org.ovirt.engine.core.common.vdscommands.VDSCommandType; import org.ovirt.engine.core.common.vdscommands.VDSReturnValue; import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.core.dal.dbbroker.DbFacade; import org.ovirt.engine.core.dal.dbbroker.auditloghandling.AuditLogDirector; import org.ovirt.engine.core.dal.dbbroker.auditloghandling.AuditLogable; import org.ovirt.engine.core.dal.dbbroker.auditloghandling.AuditLogableImpl; import org.ovirt.engine.core.dao.DiskImageDao; import org.ovirt.engine.core.dao.LibvirtSecretDao; import org.ovirt.engine.core.dao.StoragePoolIsoMapDao; import org.ovirt.engine.core.dao.VdsDao; import org.ovirt.engine.core.dao.provider.ProviderDao; import org.ovirt.engine.core.utils.transaction.TransactionSupport; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class CINDERStorageHelper extends StorageHelperBase { private static Logger log = LoggerFactory.getLogger(CINDERStorageHelper.class); private boolean runInNewTransaction = true; public boolean isRunInNewTransaction() { return runInNewTransaction; } public void setRunInNewTransaction(boolean runInNewTransaction) { this.runInNewTransaction = runInNewTransaction; } @Override protected Pair<Boolean, EngineFault> runConnectionStorageToDomain(StorageDomain storageDomain, Guid vdsId, int type) { Provider provider = getProviderDao().get(Guid.createGuidFromString(storageDomain.getStorage())); List<LibvirtSecret> libvirtSecrets = getLibvirtSecretDao().getAllByProviderId(provider.getId()); VDS vds = getVdsDao().get(vdsId); if (!isLibrbdAvailable(vds)) { log.error("Couldn't found librbd1 package on vds {} (needed for storage domain {}).", vds.getName(), storageDomain.getName()); addMessageToAuditLog(AuditLogType.NO_LIBRBD_PACKAGE_AVAILABLE_ON_VDS, null, vds); return new Pair<>(false, null); } return registerLibvirtSecrets(storageDomain, vds, libvirtSecrets); } public static boolean isLibrbdAvailable(VDS vds) { return vds.getLibrbdVersion() != null; } @Override public boolean disconnectStorageFromDomainByVdsId(StorageDomain storageDomain, Guid vdsId) { Provider provider = getProviderDao().get(Guid.createGuidFromString(storageDomain.getStorage())); List<LibvirtSecret> libvirtSecrets = getLibvirtSecretDao().getAllByProviderId(provider.getId()); VDS vds = getVdsDao().get(vdsId); return unregisterLibvirtSecrets(storageDomain, vds, libvirtSecrets); } @Override public boolean prepareConnectHostToStoragePoolServers(CommandContext cmdContext, ConnectHostToStoragePoolServersParameters parameters, List<StorageServerConnections> connections) { boolean connectSucceeded = true; if (isActiveCinderDomainAvailable(parameters.getStoragePoolId())) { // Validate librbd1 package availability boolean isLibrbdAvailable = isLibrbdAvailable(parameters.getVds()); if (!isLibrbdAvailable) { log.error("Couldn't found librbd1 package on vds {} (needed for Cinder storage domains).", parameters.getVds().getName()); setNonOperational(cmdContext, parameters.getVdsId(), NonOperationalReason.LIBRBD_PACKAGE_NOT_AVAILABLE); } connectSucceeded = isLibrbdAvailable; // Register libvirt secrets if needed connectSucceeded &= handleLibvirtSecrets(cmdContext, parameters.getVds(), parameters.getStoragePoolId()); } return connectSucceeded; } /** * A utility method for committing a previewed snapshot. The method filters out all the snapshots which will not be * part of volume chain once the snapshot get committed, and returns a list of redundant snapshots that should be * deleted. * * @param diskId * - Disk id to fetch all volumes related to it. * @param criticalSnapshotsChain * - The snapshot's ids which are critical for the VM since they are used and can not be deleted. * @return - A list of redundant snapshots that should be deleted. */ public static List<Guid> getRedundantVolumesToDeleteAfterCommitSnapshot(Guid diskId, Set<Guid> criticalSnapshotsChain) { List<Guid> redundantSnapshotIdsToDelete = new ArrayList<>(); // Fetch all the relevant snapshots to remove. List<DiskImage> allVolumesInCinderDisk = getDiskImageDao().getAllSnapshotsForImageGroup(diskId); for (DiskImage diskImage : allVolumesInCinderDisk) { if (!criticalSnapshotsChain.contains(diskImage.getVmSnapshotId())) { redundantSnapshotIdsToDelete.add(diskImage.getVmSnapshotId()); } } return redundantSnapshotIdsToDelete; } public static Pair<Boolean, EngineFault> registerLibvirtSecrets(StorageDomain storageDomain, VDS vds, List<LibvirtSecret> libvirtSecrets) { VDSReturnValue returnValue; if (!libvirtSecrets.isEmpty()) { try { returnValue = Backend.getInstance().getResourceManager().runVdsCommand( VDSCommandType.RegisterLibvirtSecrets, new RegisterLibvirtSecretsVDSParameters(vds.getId(), libvirtSecrets)); } catch (RuntimeException e) { log.error("Failed to register libvirt secret for storage domain {} on vds {}. Error: {}", storageDomain.getName(), vds.getName(), e.getMessage()); log.debug("Exception", e); return new Pair<>(false, null); } if (!returnValue.getSucceeded()) { addMessageToAuditLog(AuditLogType.FAILED_TO_REGISTER_LIBVIRT_SECRET, storageDomain, vds); log.error("Failed to register libvirt secret for storage domain {} on vds {}.", storageDomain.getName(), vds.getName()); EngineFault engineFault = new EngineFault(); engineFault.setError(returnValue.getVdsError().getCode()); return new Pair<>(false, engineFault); } } return new Pair<>(true, null); } public static boolean unregisterLibvirtSecrets( StorageDomain storageDomain, VDS vds, List<LibvirtSecret> libvirtSecrets) { List<Guid> libvirtSecretsUuids = libvirtSecrets.stream().map(LibvirtSecret::getId).collect(Collectors.toList()); if (!libvirtSecrets.isEmpty()) { VDSReturnValue returnValue; try { returnValue = Backend.getInstance().getResourceManager().runVdsCommand( VDSCommandType.UnregisterLibvirtSecrets, new UnregisterLibvirtSecretsVDSParameters(vds.getId(), libvirtSecretsUuids)); } catch (RuntimeException e) { addMessageToAuditLog(AuditLogType.FAILED_TO_UNREGISTER_LIBVIRT_SECRET, storageDomain, vds); log.error("Failed to unregister libvirt secret for storage domain {} on vds {}. Error: {}", storageDomain.getName(), vds.getName(), e.getMessage()); log.debug("Exception", e); return false; } if (!returnValue.getSucceeded()) { addMessageToAuditLog(AuditLogType.FAILED_TO_UNREGISTER_LIBVIRT_SECRET, storageDomain, vds); log.error("Failed to unregister libvirt secret for storage domain {} on vds {}.", storageDomain.getName(), vds.getName()); return false; } } return true; } @Override public Pair<Boolean, AuditLogType> disconnectHostFromStoragePoolServersCommandCompleted(HostStoragePoolParametersBase parameters) { // unregister all libvirt secrets if needed VDSReturnValue returnValue = Backend.getInstance().getResourceManager().runVdsCommand( VDSCommandType.RegisterLibvirtSecrets, new RegisterLibvirtSecretsVDSParameters(parameters.getVds().getId(), Collections.emptyList(), true)); if (!returnValue.getSucceeded()) { log.error("Failed to unregister libvirt secret on vds {}.", parameters.getVds().getName()); return new Pair<>(false, AuditLogType.FAILED_TO_REGISTER_LIBVIRT_SECRET_ON_VDS); } return new Pair<>(true, null); } public boolean isActiveCinderDomainAvailable(Guid poolId) { return isActiveStorageDomainAvailable(StorageType.CINDER, poolId); } private boolean handleLibvirtSecrets(CommandContext cmdContext, VDS vds, Guid poolId) { List<LibvirtSecret> libvirtSecrets = DbFacade.getInstance().getLibvirtSecretDao().getAllByStoragePoolIdFilteredByActiveStorageDomains(poolId); if (!libvirtSecrets.isEmpty() && !registerLibvirtSecretsImpl(vds, libvirtSecrets, false)) { log.error("Failed to register libvirt secret on vds {}.", vds.getName()); setNonOperational(cmdContext, vds.getId(), NonOperationalReason.LIBVIRT_SECRETS_REGISTRATION_FAILURE); return false; } return true; } private boolean registerLibvirtSecretsImpl(VDS vds, List<LibvirtSecret> libvirtSecrets, boolean clearUnusedSecrets) { VDSReturnValue returnValue = Backend.getInstance().getResourceManager().runVdsCommand( VDSCommandType.RegisterLibvirtSecrets, new RegisterLibvirtSecretsVDSParameters(vds.getId(), libvirtSecrets, clearUnusedSecrets)); return returnValue.getSucceeded(); } private <T> void execute(final Callable<T> callable) { if (runInNewTransaction) { TransactionSupport.executeInNewTransaction(() -> { invokeCallable(callable); return null; }); } else { invokeCallable(callable); } } private <T> void invokeCallable(Callable<T> callable) { try { callable.call(); } catch (Exception e) { log.error("Error in CinderStorageHelper.", e); } } public void attachCinderDomainToPool(final Guid storageDomainId, final Guid storagePoolId) { execute(() -> { StoragePoolIsoMap storagePoolIsoMap = new StoragePoolIsoMap(storageDomainId, storagePoolId, StorageDomainStatus.Maintenance); getStoragePoolIsoMapDao().save(storagePoolIsoMap); return null; }); } public static ValidationResult isCinderHasNoImages(Guid storageDomainId) { List<DiskImage> cinderDisks = getDiskImageDao().getAllForStorageDomain(storageDomainId); if (!cinderDisks.isEmpty()) { return new ValidationResult(EngineMessage.ERROR_CANNOT_DETACH_CINDER_PROVIDER_WITH_IMAGES); } return ValidationResult.VALID; } public void activateCinderDomain(Guid storageDomainId, Guid storagePoolId) { OpenStackVolumeProviderProxy proxy = OpenStackVolumeProviderProxy.getFromStorageDomainId(storageDomainId); if (proxy == null) { log.error("Couldn't create an OpenStackVolumeProviderProxy for storage domain ID: {}", storageDomainId); return; } try { proxy.testConnection(); updateCinderDomainStatus(storageDomainId, storagePoolId, StorageDomainStatus.Active); } catch (EngineException e) { AuditLogable loggable = new AuditLogableImpl(); loggable.addCustomValue("CinderException", e.getCause().getCause() != null ? e.getCause().getCause().getMessage() : e.getCause().getMessage()); new AuditLogDirector().log(loggable, AuditLogType.CINDER_PROVIDER_ERROR); throw e; } } public void detachCinderDomainFromPool(final StoragePoolIsoMap mapToRemove) { execute(() -> { getStoragePoolIsoMapDao().remove(new StoragePoolIsoMapId(mapToRemove.getStorageId(), mapToRemove.getStoragePoolId())); return null; }); } private void updateCinderDomainStatus(final Guid storageDomainId, final Guid storagePoolId, final StorageDomainStatus storageDomainStatus) { execute(() -> { StoragePoolIsoMap map = getStoragePoolIsoMapDao().get(new StoragePoolIsoMapId(storageDomainId, storagePoolId)); map.setStatus(storageDomainStatus); getStoragePoolIsoMapDao().updateStatus(map.getId(), map.getStatus()); return null; }); } public void deactivateCinderDomain(Guid storageDomainId, Guid storagePoolId) { updateCinderDomainStatus(storageDomainId, storagePoolId, StorageDomainStatus.Maintenance); } private StoragePoolIsoMapDao getStoragePoolIsoMapDao() { return getDbFacade().getStoragePoolIsoMapDao(); } private ProviderDao getProviderDao() { return getDbFacade().getProviderDao(); } private VdsDao getVdsDao() { return getDbFacade().getVdsDao(); } private LibvirtSecretDao getLibvirtSecretDao() { return getDbFacade().getLibvirtSecretDao(); } private static DbFacade getDbFacade() { return DbFacade.getInstance(); } private static DiskImageDao getDiskImageDao() { return getDbFacade().getDiskImageDao(); } }