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
/*******************************************************************************
 *   © 2019-2024 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