All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sap.cloud.mt.subscription.InstanceLifecycleManagerImpl Maven / Gradle / Ivy

There is a newer version: 3.3.1
Show newest version
/******************************************************************************
 * © 2020 SAP SE or an SAP affiliate company. All rights reserved.            *
 ******************************************************************************/
package com.sap.cloud.mt.subscription;

import com.sap.cloud.mt.subscription.exceptions.InternalError;
import com.sap.cloud.mt.subscription.exceptions.UnknownTenant;
import com.sap.cloud.mt.tools.api.UuidChecker;
import com.sap.xsa.core.instancemanager.client.GetOptions;
import com.sap.xsa.core.instancemanager.client.ImClientException;
import com.sap.xsa.core.instancemanager.client.InstanceCreationOptions;
import com.sap.xsa.core.instancemanager.client.InstanceManagerClient;
import com.sap.xsa.core.instancemanager.client.LastOperation;
import com.sap.xsa.core.instancemanager.client.ManagedServiceInstance;
import com.sap.xsa.core.instancemanager.client.OperationStatus;
import com.sap.xsa.core.instancemanager.client.ServiceBinding;
import com.sap.xsa.core.instancemanager.client.ServiceManagerClient;
import com.sap.xsa.core.instancemanager.client.impl.servicemanager.ServiceManagerClientImpl;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.TimerTask;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BooleanSupplier;
import java.util.stream.Collectors;

public class InstanceLifecycleManagerImpl implements InstanceLifecycleManager {
    private static final Logger logger = LoggerFactory.getLogger(InstanceLifecycleManagerImpl.class);
    private static final String HOST = "host";
    private static final GetOptions NO_NEW_SERVICE_BINDINGS;
    private static final GetOptions FORCE_CACHE_UPDATE_NO_NEW_SERVICE_BINDINGS;
    private final InstanceManagerClient instanceManagerClient;
    private final ServiceManagerClient serviceManagerClient;
    private final int timeout;
    private static BooleanSupplier blockRefresh = () -> false;
    private final DbIdentifiersProxy dbIdentifiersProxy = new DbIdentifiersProxy();
    private final Method getInstancesMethod;
    private final AtomicBoolean planIsSet = new AtomicBoolean(false);

    static {
        NO_NEW_SERVICE_BINDINGS = new GetOptions();
        NO_NEW_SERVICE_BINDINGS.setCreateBindingIfMissing(false);
        FORCE_CACHE_UPDATE_NO_NEW_SERVICE_BINDINGS = new GetOptions();
        FORCE_CACHE_UPDATE_NO_NEW_SERVICE_BINDINGS.setCreateBindingIfMissing(false);
        FORCE_CACHE_UPDATE_NO_NEW_SERVICE_BINDINGS.setForceCacheUpdate(true);
    }

    /**
     * @param instanceManagerClient the client lib
     * @param timeout               timeout for some IM client operations
     * @param dbIdentifiers         optional provided DB identifiers
     */
    public InstanceLifecycleManagerImpl(InstanceManagerClient instanceManagerClient, int timeout,
                                        DbIdentifiersHana dbIdentifiers,
                                        Duration smCacheRefreshInterval) {
        this.instanceManagerClient = instanceManagerClient;
        if (instanceManagerClient instanceof ServiceManagerClient) {
            serviceManagerClient = (ServiceManagerClient) instanceManagerClient;
            try {
                getInstancesMethod = ServiceManagerClientImpl.class.getDeclaredMethod("getManagedInstancesFromCache", null);
                getInstancesMethod.setAccessible(true);//NOSONAR
            } catch (NoSuchMethodException e) {
                throw new RuntimeException(e);
            }
        } else {
            serviceManagerClient = null;
            getInstancesMethod = null;
        }
        this.timeout = timeout;
        if (dbIdentifiers != null) {
            dbIdentifiers.getDbIds().stream().forEach(dbIdentifiersProxy::add);
        }
        startRefreshScheduler(smCacheRefreshInterval);
    }

    /**
     * @deprecated (release 1.20, new constructor with more parameters, use new constructor)
     */
    @Deprecated
    public InstanceLifecycleManagerImpl(InstanceManagerClient instanceManagerClient, int timeout) {
        this(instanceManagerClient, timeout, null, null);
    }


    /**
     * The instance manager client lib caches service bindings. This information is needed for the health check
     * (one health check per DB),for the creation of mt-lib's HDI containers (one mt-lib container per
     * DB) and to determine all tenants. For performance reasons the SM cannot be accessed whenever a list of all instances is needed,
     * especially if triggered by the health check. Therefore, the instance manager client lib
     * is called normally in a way that assures that only cached data is accessed.
     * It cannot be assured that the cache always contains a complete list of instances. If for example a new instance is created
     * in another Java instance, it isn't part of the local cache until a cache refresh is processed. Therefore, operations that
     * must be accurate (getAllTenants) provide a flag to force a cache update.
     * This method fills the cache periodically with fresh data. The boolean supplier "blockRefresh" is used only by unit tests. To be able
     * to test this parallel initialization a controlled way to switch it on and off in tests is needed. Tests can implement a blockRefresh supplier
     * that gives them control about this aspect.
     */
    private void startRefreshScheduler(Duration smCacheRefreshInterval) {
        if (smCacheRefreshInterval == null || smCacheRefreshInterval.isZero()) {
            return;
        }
        TimerTask refreshSmClientCache = new TimerTask() {
            @Override
            public void run() {
                try {
                    if (!blockRefresh.getAsBoolean()) {
                        if (serviceManagerClient != null) {
                            serviceManagerClient.getManagedInstances(FORCE_CACHE_UPDATE_NO_NEW_SERVICE_BINDINGS);
                        } else {
                            instanceManagerClient.getManagedInstances();
                        }
                        logger.debug("Read all managed instances into instance manager client lib cache");
                    }
                } catch (ImClientException e) {//NOSONAR
                    logger.warn("Could not access Service Manager", e);
                }
            }
        };
        ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor((Runnable r) -> {
            Thread t = Executors.defaultThreadFactory().newThread(r);
            t.setDaemon(true);
            return t;
        });
        executor.scheduleAtFixedRate(refreshSmClientCache, 0, smCacheRefreshInterval.toMillis(), TimeUnit.MILLISECONDS);
    }

    @Override
    public void createNewInstance(String tenantId, InstanceCreationOptions instanceCreationOptions) throws InternalError {
        if (instanceCreationOptions != null && instanceCreationOptions.getProvisioningParameters() != null) {
            String databaseId = (String) instanceCreationOptions.getProvisioningParameters().get(DATABASE_ID);
            if (databaseId != null) {
                logger.debug("Using database id {}", databaseId);
            }
        }
        try {
            instanceManagerClient.createManagedInstance(tenantId, instanceCreationOptions, timeout);
        } catch (ImClientException e) {
            throw new InternalError(e);
        }
    }

    @Override
    public void deleteInstance(String tenantId) throws InternalError {
        try {
            checkThatTenantExists(tenantId);
        } catch (UnknownTenant unknownTenant) {
            logger.warn("No HDI container for tenant {} found", tenantId);
            return;
        }
        try {
            instanceManagerClient.deleteManagedInstance(tenantId, timeout);
        } catch (ImClientException e) {
            throw new InternalError(e);
        }
    }

    @Override
    public DataSourceInfo getDataSourceInfo(String tenantId, boolean forceCacheUpdate) throws InternalError, UnknownTenant {
        try {
            return getDataSourceInfoInternal(tenantId, forceCacheUpdate);
        } catch (InternalError | UnknownTenant e) {
            if (forceCacheUpdate) {
                throw e;
            } else {
                return getDataSourceInfoInternal(tenantId, true);
            }
        }
    }

    protected DataSourceInfo getDataSourceInfoInternal(String tenantId, boolean forceCacheUpdate) throws InternalError, UnknownTenant {
        ManagedServiceInstance instance = null;
        try {
            GetOptions getOptions = new GetOptions();
            getOptions.setForceCacheUpdate(forceCacheUpdate);
            getOptions.setCreateBindingIfMissing(false);
            if (serviceManagerClient != null) {
                instance = serviceManagerClient.getManagedInstance(tenantId, getOptions);
            } else {
                instance = instanceManagerClient.getManagedInstance(tenantId, forceCacheUpdate);
            }
            if (instance == null) throw new UnknownTenant("Tenant [" + tenantId + "] is not known");
        } catch (ImClientException e) {
            throw new UnknownTenant(e, "Tenant [" + tenantId + "] is not known");
        }
        Map credentials;
        String statusText;
        if (serviceManagerClient != null) {
            Optional binding = getBinding(instance);
            if (!binding.isPresent()) {
                logger.error("Database container service instance for tenant {} doesn't have a ready binding", tenantId);
                throw new InternalError(String.format("Database container service instance for tenant %s doesn't have a ready binding", tenantId));
            }
            credentials = binding.get().getCredentials();
            statusText = OperationStatus.CREATION_SUCCEEDED.toString();
        } else {
            if (getContainerStatus(instance.getStatus()) != ContainerStatus.OK) {
                logger.error("Database for tenant {} has a wrong status {}", tenantId, instance.getStatus());
                throw new InternalError("Database for tenant " + tenantId + " has a wrong status " + instance.getStatus());
            }
            credentials = instance.getCredentials();
            statusText = instance.getStatus().toString();
        }
        return DataSourceInfoBuilder.createBuilder()
                .host((String) credentials.get(HOST))
                .port((String) credentials.get("port"))
                .driver((String) credentials.get("driver"))
                .url((String) credentials.get("url"))
                .schema((String) credentials.get("schema"))
                .hdiUser((String) credentials.get("hdi_user"))
                .hdiPassword((String) credentials.get("hdi_password"))
                .user((String) credentials.get("user"))
                .password((String) credentials.get("password"))
                .certificate((String) credentials.get("certificate"))
                .tenantId(tenantId)
                .id(instance.getId())
                .statusAsText(statusText)
                .dbKey(createDbKey((String) credentials.get(HOST), (String) credentials.get("port")))
                .databaseId(getDatabaseId(credentials))
                .build();
    }

    @Override
    public ContainerStatus getContainerStatus(String tenantId) throws InternalError {
        try {
            ManagedServiceInstance instance;
            if (serviceManagerClient != null) {
                instance = serviceManagerClient.getManagedInstance(tenantId, FORCE_CACHE_UPDATE_NO_NEW_SERVICE_BINDINGS);
            } else {
                instance = instanceManagerClient.getManagedInstance(tenantId, true);
            }
            if (instance == null) return ContainerStatus.DOES_NOT_EXIST;
            OperationStatus status = instance.getStatus();
            return getContainerStatus(status);
        } catch (ImClientException e) {
            throw new InternalError(e);
        }
    }

    private ContainerStatus getContainerStatus(OperationStatus status) throws InternalError {
        switch (status) {
            case CREATION_SUCCEEDED:
            case UPDATE_SUCCEEDED:
            case UPDATE_FAILED:
                return ContainerStatus.OK;
            case CREATION_IN_PROGRESS:
            case UPDATE_IN_PROGRESS:
            case DELETION_IN_PROGRESS:
                return ContainerStatus.IN_PROGRESS;
            case DELETION_FAILED:
                return ContainerStatus.ERRONEOUS;
            case CREATION_FAILED:
                return ContainerStatus.CREATION_ERROR;
            default:
                logger.error("Undefined status {}", status);
                throw new InternalError("Unknown status");
        }
    }

    @Override
    public Set getAllTenants(boolean forceCacheUpdate) throws InternalError {
        List instances = null;
        try {
            if (serviceManagerClient != null) {
                if (forceCacheUpdate) {
                    instances = serviceManagerClient.getManagedInstances(FORCE_CACHE_UPDATE_NO_NEW_SERVICE_BINDINGS);
                } else {
                    instances = getInstancesFromSmCache((ServiceManagerClientImpl) serviceManagerClient);
                }
            } else {
                instances = instanceManagerClient.getManagedInstances();
            }
        } catch (ImClientException e) {
            throw new InternalError(e);
        }
        return instances.stream().filter(i -> {
            try {
                return getContainerStatus(i.getStatus()) == ContainerStatus.OK;
            } catch (InternalError internalError) {
                return false;
            }
        }).map(ManagedServiceInstance::getId).filter(FilterTenants.realTenants()).collect(Collectors.toSet());
    }

    @Override
    public void checkThatTenantExists(String tenantId) throws UnknownTenant, InternalError {
        try {
            ManagedServiceInstance instance;
            if (serviceManagerClient != null) {
                instance = serviceManagerClient.getManagedInstance(tenantId, FORCE_CACHE_UPDATE_NO_NEW_SERVICE_BINDINGS);
            } else {
                instance = instanceManagerClient.getManagedInstance(tenantId, true);
            }
            if (instance == null) throw new UnknownTenant("Tenant " + tenantId + " is not known");
        } catch (ImClientException e) {
            throw new InternalError("Instance manager client lib reports an error", e);
        }
    }

    @Override
    public List createAndGetLibContainers(DataSourceInfo dataSourceInfo) throws InternalError {
        String databaseId = null;
        if (dataSourceInfo != null) {
            databaseId = dataSourceInfo.getDatabaseId();
        }
        Set missingLibContainersDbIds = new HashSet<>();
        if (databaseId != null) {
            // determine missing container of DB used by dataSourceInfo
            if (isLibContainerMissing(databaseId)) {
                missingLibContainersDbIds.add(databaseId);
            }
        } else {
            // determine missing containers of all used DBs
            missingLibContainersDbIds = getMissingLibContainers(dbIdentifiersProxy.getDbIds());
        }
        //create missing lib containers
        missingLibContainersDbIds.stream().forEach(id -> {
            try {
                logger.debug("Create new mt-lib container for database {}", id);
                createNewInstance(getMtLibContainerName(id), createInstanceCreationOptions(id));
            } catch (InternalError internalError) {
                logger.debug("Could not create new mt-lib container for database {} because of {} ", id, internalError.getMessage());
            }
        });
        return getLibContainers();
    }

    @Override
    public List getLibContainers() {
        List dsInfo = new ArrayList<>();
        dbIdentifiersProxy.getDbIds().stream().forEach(id -> {
            try {
                dsInfo.add(getDataSourceInfo(getMtLibContainerName(id), false));
            } catch (InternalError | UnknownTenant error) {
                //NOSONAR
            }
        });
        return dsInfo;
    }

    private Set getMissingLibContainers(Set dbIds) {
        return dbIds.stream().filter(this::isLibContainerMissing).collect(Collectors.toSet());
    }

    private boolean isLibContainerMissing(String dbId) {
        try {
            if (serviceManagerClient != null) {
                return serviceManagerClient.getManagedInstance(getMtLibContainerName(dbId), NO_NEW_SERVICE_BINDINGS) == null;
            } else {
                return instanceManagerClient.getManagedInstance(getMtLibContainerName(dbId)) == null;
            }
        } catch (ImClientException e) {
            return true;
        }
    }

    private DbIdentifiers getDbIdentifiers() { //NOSONAR
        return dbIdentifiersProxy.createCopy();
    }

    @Override
    public boolean hasDbIdentifiers() {
        return dbIdentifiersProxy.areSet();
    }

    @Override
    public void insertDbIdentifiers(DbIdentifiers dbIdentifiers) {
        ((DbIdentifiersHana) dbIdentifiers).getDbIds().stream()
                .forEach(dbIdentifiersProxy::add);
    }

    @Override
    public DbIdentifiers.DB getDbType() {
        return dbIdentifiersProxy.getDbType();
    }

    private String createDbKey(String host, String port) {
        return host + ":" + port;
    }

    private String getDatabaseId(Map credentials) {
        String databaseId = (String) credentials.get("database_id");
        if (StringUtils.isNotEmpty(databaseId)) {
            return databaseId;
        }
        databaseId = StringUtils.substringBefore((String) credentials.get(HOST), ".");
        return UuidChecker.isUUId(databaseId) ? databaseId : null;
    }

    private class DbIdentifiersProxy {
        private final DbIdentifiersHana dbIdentifiers = new DbIdentifiersHana(new HashSet<>());

        public void add(String dbId) {
            dbIdentifiers.add(dbId);
        }

        public DbIdentifiers.DB getDbType() {
            return dbIdentifiers.getDB();
        }

        public boolean areSet() {
            updateDbIdentifiers();
            return dbIdentifiers.areSet();
        }

        public DbIdentifiers createCopy() {
            updateDbIdentifiers();
            return dbIdentifiers.createCopy();
        }

        public Set getDbIds() {
            updateDbIdentifiers();
            return dbIdentifiers.getDbIds();
        }

        private synchronized void updateDbIdentifiers() {
            List instances;
            try {
                if (serviceManagerClient != null) {
                    instances = getInstancesFromSmCache((ServiceManagerClientImpl) serviceManagerClient);
                } else {
                    instances = instanceManagerClient.getManagedInstances();
                }
                instances.stream()
                        .map(ManagedServiceInstance::getCredentials)
                        .filter(Objects::nonNull)
                        .map(InstanceLifecycleManagerImpl.this::getDatabaseId)
                        .filter(Objects::nonNull)
                        .map(Object::toString)
                        .forEach(this::add);
            } catch (ImClientException e) {
                logger.debug("Could not determine db identifiers", e);
            }
        }

    }

    private List getInstancesFromSmCache(ServiceManagerClientImpl client) {
        if (!planIsSet.get()) {
            try {
                serviceManagerClient.setDefaultManagedServicePlan("hana", "hdi-shared");
            } catch (ImClientException e) {
                throw new RuntimeException(e);//NOSONAR
            }
            planIsSet.set(true);
        }
        try {
            return (List) getInstancesMethod.invoke(client);
        } catch (InvocationTargetException | IllegalAccessException e) {
            throw new RuntimeException(e); //NOSONAR
        }
    }

    /**
     * @param blockRefresh lambda expression that is called before each refresh to decide if a refresh
     *                     shall be executed. Needed for unit tests to enable refresh in a controlled way.
     */
    public static void setBlockRefresh(BooleanSupplier blockRefresh) {
        InstanceLifecycleManagerImpl.blockRefresh = blockRefresh;
    }

    private Optional getBinding(ManagedServiceInstance managedServiceInstance) {
        List bindings = managedServiceInstance.getBindings();
        if (bindings == null || bindings.isEmpty()) {
            return Optional.empty();
        }
        return bindings.stream().filter(ServiceBinding::isReady)
                .filter(b -> (b.getCredentials() != null && !b.getCredentials().isEmpty()))
                .max(Comparator.comparing(ServiceBinding::getCreatedAt));
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy