com.sap.cloud.mt.subscription.InstanceLifecycleManagerImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of multi-tenant-subscription Show documentation
Show all versions of multi-tenant-subscription Show documentation
Spring Boot Enablement Parent
/******************************************************************************
* © 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);
}
}