package org.ovirt.engine.core.bll.hostedengine; import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Properties; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import javax.enterprise.inject.Instance; import javax.inject.Inject; import javax.inject.Singleton; import org.ovirt.engine.core.bll.RetrieveImageDataParameters; import org.ovirt.engine.core.bll.interfaces.BackendInternal; import org.ovirt.engine.core.common.action.VdcActionType; import org.ovirt.engine.core.common.action.VdcReturnValueBase; import org.ovirt.engine.core.common.businessentities.storage.DiskImage; import org.ovirt.engine.core.common.config.Config; import org.ovirt.engine.core.common.config.ConfigValues; import org.ovirt.engine.core.common.errors.EngineError; import org.ovirt.engine.core.common.errors.EngineException; import org.ovirt.engine.core.common.utils.Pair; import org.ovirt.engine.core.common.vdscommands.GetImageInfoVDSCommandParameters; import org.ovirt.engine.core.common.vdscommands.GetImagesListVDSCommandParameters; import org.ovirt.engine.core.common.vdscommands.StoragePoolDomainAndGroupIdBaseVDSCommandParameters; 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.utils.archivers.tar.TarInMemoryExport; import org.ovirt.engine.core.vdsbroker.ResourceManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Singleton public class HostedEngineConfigFetcher { public static final String HOSTED_ENGINE_CONFIGURATION_IMAGE = "HostedEngineConfigurationImage"; private static final String HOSTED_ENGINE_CONF = "hosted-engine.conf"; private static final Logger log = LoggerFactory.getLogger(HostedEngineConfigFetcher.class); @Inject private ResourceManager resourceManager; @Inject private Instance<HostedEngineHelper> heHelper; @Inject private BackendInternal backend; /** * Retrieve the hosted engine configuration from the configuration disk on the hosted engine domain. * The disk contains the config in a key value format * * @return A {@link CompletableFuture} holding the configuration of the hosted engine * agent as kept on the shared storage */ public CompletableFuture<Map<String, String>> fetchPromise() { return CompletableFuture.supplyAsync(() -> { Map<String, String> configFromImage = Collections.emptyMap(); final Guid spId = heHelper.get().getStoragePoolId(); final Guid sdId = heHelper.get().getStorageDomainId(); final Guid hostId = heHelper.get().getRunningHostId(); // Get Images List for the HE domain final List<Guid> diskIds = getHEDomainImages(spId, sdId); // Get all volumes for each image and trace the volume by a certain description final Optional<DiskImage> configDisk = traceConfigurationDisk(spId, sdId, diskIds); // If exist, download its content. It holds all the configuration that was saved during the // install of the 1st hosted engine host. if (configDisk.isPresent()) { final byte[] diskData = downloadDisk(spId, sdId, configDisk.get()); if (diskData != null) { // the content of the disk is a tar which contains the hosted-engine.conf file configFromImage = extractFileFromDisk(configFromImage, diskData); } } return configFromImage; }); } /** * Retrieve the hosted engine configuration from the configuration disk on the hosted engine domain. * The disk contains the config in a key value format * * @return The configuration of the hosted engine agent as kept on the shared storage */ public Map<String, String> fetch() { try { return fetchPromise().get(); } catch (ExecutionException | InterruptedException e) { log.error("Failed to fetch the hosted engine config disk due to {}", e); } return Collections.emptyMap(); } private List<Guid> getHEDomainImages(Guid spId, Guid sdId) { return (List<Guid>) resourceManager.runVdsCommand( VDSCommandType.GetImagesList, new GetImagesListVDSCommandParameters(sdId, spId) ).getReturnValue(); } private Optional<DiskImage> traceConfigurationDisk(Guid spId, Guid sdId, List<Guid> diskIds) { if (diskIds == null) { return Optional.empty(); } return diskIds.stream() .map(diskId -> new Pair<>(diskId, (List<Guid>) resourceManager.runVdsCommand( VDSCommandType.GetVolumesList, new StoragePoolDomainAndGroupIdBaseVDSCommandParameters(spId, sdId, diskId)) .getReturnValue())) .flatMap(diskToVolumes -> diskToVolumes.getSecond().stream() .map(volumeId -> getImageInfo(spId, sdId, diskToVolumes.getFirst(), volumeId))) .filter(Objects::nonNull) .map(diskImageCall -> (DiskImage)diskImageCall.getReturnValue()) .filter(Objects::nonNull) .filter(diskImage -> HOSTED_ENGINE_CONFIGURATION_IMAGE .equals(diskImage.getDescription())) .findAny(); } private VDSReturnValue getImageInfo(Guid spId, Guid sdId, Guid diskId, Guid volumeId) { return resourceManager.runVdsCommand( VDSCommandType.GetImageInfo, new GetImageInfoVDSCommandParameters(spId, sdId, diskId, volumeId)); } private byte[] downloadDisk(Guid spId, Guid sdId, DiskImage diskImage) { long downloadSize = Config.<Integer>getValue(ConfigValues.HostedEngineConfigDiskSizeInBytes); log.info("Found the HE configuration disk. Downloading the content in size of {} bytes", downloadSize); VdcReturnValueBase returnValue = backend.runInternalAction(VdcActionType.RetrieveImageData, new RetrieveImageDataParameters(spId, sdId, diskImage.getId(), diskImage.getImageId(), downloadSize)); if (returnValue.getSucceeded()) { return (byte[]) returnValue.getActionReturnValue(); } else { throw new EngineException(EngineError.ENGINE, "Failed to download the HE configuration disk"); } } private Map<String, String> extractFileFromDisk(Map<String, String> configFromImage, byte[] diskData) { log.info("Untar the HE config disk"); try (TarInMemoryExport tar = new TarInMemoryExport(new ByteArrayInputStream(diskData))) { Optional<Map.Entry<String, ByteBuffer>> taredConfig = tar.unTar().entrySet().stream() .peek(entry -> log.debug("File name in HE config tar '{}'. Looking for '{}'", entry.getKey(), HOSTED_ENGINE_CONF)) .filter(entry -> HOSTED_ENGINE_CONF.equals(entry.getKey())) .findAny(); if (taredConfig.isPresent()) { configFromImage = loadConfigMap(taredConfig.get().getValue().array()); } } catch (Exception e) { log.error("Failed to untar the hosted engine configuration disk due to {}", e); } return configFromImage; } /** * Take a array of data, parse it to a Map of key: String -> val: String * @param data byte[] loaded by the previously fetched file * @return A key value map of the hosted engine configuration 'as is' represented by the data passed or * {@linkplain Collections#emptyMap} in case some error occurred. */ protected Map<String, String> loadConfigMap(final byte[] data) { try (ByteArrayInputStream input = new ByteArrayInputStream(data)) { Properties properties = new Properties(); properties.load(input); return properties.entrySet().stream() .collect(Collectors.toMap(e -> (String) e.getKey(), e -> (String) e.getValue() )); } catch (IOException e) { log.error("Failed to load hosted-engine.conf map from file due to {}", e); return Collections.emptyMap(); } } }