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.ServiceOperation.Status;
import com.sap.cloud.mt.subscription.ServiceOperation.Type;
import com.sap.cloud.mt.subscription.exceptions.InternalError;
import com.sap.cloud.mt.subscription.exceptions.UnknownTenant;
import com.sap.cloud.mt.tools.api.ResilienceConfig;
import com.sap.cloud.mt.tools.api.UuidChecker;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.BooleanSupplier;
import java.util.function.Function;
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";
    public static final String CREATION_SUCCEEDED = "CREATION_SUCCEEDED";
    private final ServiceManagerCache serviceManagerCache;
    private final DbIdentifiersProxy dbIdentifiersProxy = new DbIdentifiersProxy();

    /**
     * @param serviceManager the service manager rest api wrapper
     * @param dbIdentifiers  optional provided DB identifiers
     */
    InstanceLifecycleManagerImpl(ServiceManager serviceManager,
                                 DbIdentifiersHana dbIdentifiers,
                                 Duration smCacheRefreshInterval,
                                 ResilienceConfig serviceManagerCacheResilienceConfig,
                                 boolean acceptInstancesWithoutTenant) {
        if (dbIdentifiers != null) {
            dbIdentifiers.getDbIds().stream().forEach(dbIdentifiersProxy::add);
        }
        this.serviceManagerCache = new ServiceManagerCache(serviceManager, smCacheRefreshInterval,
                serviceManagerCacheResilienceConfig != null ? serviceManagerCacheResilienceConfig : ResilienceConfig.NONE,
                acceptInstancesWithoutTenant);
    }

    @Override
    public void createNewInstance(String tenantId, ProvisioningParameters instanceParameters,
                                  BindingParameters bindingParameters) throws InternalError {
        if (!MapUtils.isEmpty(instanceParameters)) {
            String databaseId = (String) instanceParameters.get(DATABASE_ID);
            if (databaseId != null) {
                logger.debug("Using database id {}", databaseId);
            }
        }
        serviceManagerCache.createInstance(tenantId, instanceParameters, bindingParameters);
    }

    @Override
    public void deleteInstance(String tenantId) throws InternalError {
        try {
            checkThatTenantExists(tenantId);
        } catch (UnknownTenant unknownTenant) {
            logger.warn("No HDI container for tenant {} found", tenantId);
            return;
        }
        serviceManagerCache.deleteInstance(tenantId);
    }

    @Override
    public DataSourceInfo getDataSourceInfo(String tenantId, boolean forceCacheUpdate) throws InternalError, UnknownTenant {
        return getDataSourceInfoInternal(tenantId, forceCacheUpdate);
    }

    protected DataSourceInfo getDataSourceInfoInternal(String tenantId, boolean forceCacheUpdate) throws InternalError, UnknownTenant {
        ServiceInstance instance = null;
        try {
            instance = serviceManagerCache.getInstance(tenantId, forceCacheUpdate).orElseThrow(() -> new UnknownTenant("Tenant [" + tenantId + "] is not known"));
        } catch (InternalError e) {
            throw new UnknownTenant(e, "Tenant [" + tenantId + "] is not known");
        }
        Map credentials;
        var binding = instance.getBinding().orElseThrow(() -> new InternalError("Database container service instance for tenant %s doesn't have a ready binding".formatted(tenantId)));
        credentials = binding.getCredentials();
        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(CREATION_SUCCEEDED)
                .dbKey(createDbKey((String) credentials.get(HOST), (String) credentials.get("port")))
                .databaseId(getDatabaseId(credentials))
                .build();
    }

    @Override
    public ContainerStatus getContainerStatus(String tenantId) throws InternalError {
        return serviceManagerCache.getInstance(tenantId, true).stream().map(instance -> {
            var status = instance.getLastOperation().getStatus();
            var type = instance.getLastOperation().getType();
            // As a container status "creation error" triggers a deletion, it is only set if in
            // addition the ready flag is not set.
            if (type == Type.CREATE && !instance.isReady() && status == Status.FAILED) {
                return ContainerStatus.CREATION_ERROR;
            }
            if (type == Type.CREATE && status == Status.IN_PROGRESS) {
                return ContainerStatus.CREATION_IN_PROGRESS;
            }
            // all other types do not determine the container state. Think for example of a patch operation
            // that fails. The container is still working. Even if a delete operation fails the container is still there.
            return ContainerStatus.OK;
        }).findFirst().orElse(ContainerStatus.DOES_NOT_EXIST);
    }

    @Override
    public boolean hasCredentials(String tenantId, boolean forceCacheUpdate) throws InternalError {
        return serviceManagerCache.getInstance(tenantId, forceCacheUpdate).stream()
                .map(i -> i.getBinding().isPresent())
                .findFirst().orElse(false);
    }

    @Override
    public Map getAllTenantInfos(boolean forceCacheUpdate) throws InternalError {
        List instances = null;
        instances = serviceManagerCache.getInstances(forceCacheUpdate);
        if (instances == null) {
            return new HashMap<>();
        }
        return instances.stream().filter(i -> i.isUsable()).flatMap(instance -> {
                    var tenantInfoList = new ArrayList();
                    var binding = instance.getBinding();
                    String dbId = null;
                    if (binding.isPresent()) {
                        dbId = getDatabaseId(instance.getBinding().get().getCredentials());
                    }
                    for (var tenantId : instance.getTenants()) {
                        TenantMetadata tenantInfo = new TenantMetadata(tenantId);
                        if (dbId != null) {
                            tenantInfo.putAdditionalProperty(DATABASE_ID, dbId);
                        }
                        tenantInfoList.add(tenantInfo);
                    }
                    return tenantInfoList.stream();
                }).filter(tenantInfo -> FilterTenants.realTenants().test(tenantInfo.getTenantId()))
                .collect(Collectors.toMap(TenantMetadata::getTenantId, Function.identity()));
    }

    @Override
    public void checkThatTenantExists(String tenantId) throws UnknownTenant, InternalError {
        if (serviceManagerCache.getInstance(tenantId, false).isEmpty()) {
            throw new UnknownTenant("Tenant " + tenantId + " is not known");
        }
    }

    @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), createProvisioningParameters(id), new BindingParameters());
            } catch (InternalError internalError) {
                logger.error("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;
    }

    public void clearCache() {
        serviceManagerCache.clearCache();
    }

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

    private boolean isLibContainerMissing(String dbId) {
        try {
            return serviceManagerCache.getInstance(getMtLibContainerName(dbId), false).isEmpty();
        } catch (InternalError 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;
    }

    @Override
    public boolean knowsDbCredentials() {
        try {
            List instances = serviceManagerCache.getInstances(false);
            return instances != null && !instances.isEmpty();
        } catch (InternalError e) {
            throw new RuntimeException(e);
        }
    }

    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 {
                instances = serviceManagerCache.getInstances(false);
                if (instances != null) {
                    instances.stream()
                            .filter(i -> i.getBinding().isPresent())
                            .map(i -> i.getBinding().get().getCredentials())
                            .filter(Objects::nonNull)
                            .map(InstanceLifecycleManagerImpl.this::getDatabaseId)
                            .filter(Objects::nonNull)
                            .map(Object::toString)
                            .forEach(this::add);
                }
            } catch (InternalError e) {
                logger.error("Could not access SM", e);
            }
        }
    }

    /**
     * @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) {
        ServiceManagerCache.setBlockRefresh(blockRefresh);
    }
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy