com.sap.cloud.mt.subscription.InstanceLifecycleManagerSqlDb 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
/*******************************************************************************
* © 2019-2024 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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
public class InstanceLifecycleManagerSqlDb implements InstanceLifecycleManager {
protected static final String SCHEMA_PREFIX = "MT-";
private static Logger logger = LoggerFactory.getLogger(InstanceLifecycleManagerSqlDb.class);
private DbIdentifiersSql dbIdentifiers;
private final SqlOperations sqlOperations;
private static final ConcurrentHashMap tenantToMutex = new ConcurrentHashMap<>();
private final DbIdentifiers.DB db;
public InstanceLifecycleManagerSqlDb(DbIdentifiersSql dbIdentifiers) throws InternalError {
if (dbIdentifiers == null || !dbIdentifiers.areSet()) {
throw new InternalError("No databases specified");
}
this.dbIdentifiers = dbIdentifiers;
this.sqlOperations = SqlOperations.build(dbIdentifiers.getDB());
this.db = dbIdentifiers.getDB();
}
@Override
public void createNewInstance(String tenantId, ProvisioningParameters provisioningParameters, BindingParameters bindingParameters) throws InternalError {
synchronized (getMutex(tenantId)) {
if (doesTenantExist(tenantId)) {
return;
}
String databaseId = null;
if (provisioningParameters != null) {
databaseId = provisioningParameters.getDatabaseId();
}
Optional credentials;
if (databaseId != null) {
credentials = dbIdentifiers.getCredentials(databaseId);
} else {
credentials = dbIdentifiers.getLast();
}
if (!credentials.isPresent()) {
throw new InternalError("No database credentials available for database ID " + databaseId);
}
try {
DbCredentials cred = credentials.get();
try (Connection connection = getConnection(cred)) {
sqlOperations.createSchema(getSchemaName(tenantId), connection);
if (!connection.getAutoCommit()) {
connection.commit();
}
}
} catch (SQLException e) {
throw new InternalError(e);
}
}
}
@Override
public void deleteInstance(String tenantId) throws InternalError {
synchronized (getMutex(tenantId)) {
Optional credentials = getCredentials(tenantId);
if (!credentials.isPresent()) {
logger.warn("No schema for tenant {} found", tenantId);
return;
}
try (Connection connection = getConnection(credentials.get())) {
sqlOperations.deleteSchema(getSchemaName(tenantId), connection);
if (!connection.getAutoCommit()) {
connection.commit();
}
} catch (SQLException e) {
throw new InternalError(e);
}
}
}
@Override
public DataSourceInfo getDataSourceInfo(String tenantId, boolean forceCacheUpdate) throws InternalError, UnknownTenant {
Optional credentials = getCredentials(tenantId);
if (!credentials.isPresent()) {
throw new UnknownTenant("No schema found for tenant " + tenantId);
}
DbCredentials cred = credentials.get();
return DataSourceInfoBuilder.createBuilder().driver(cred.getDriver()).host(cred.getHost())
.port(cred.getPort()).user(cred.getUser()).password(cred.getPassword()).statusAsText("ok")
.url(cred.getUrl()).schema(getSchemaName(tenantId)).tenantId(tenantId).id(tenantId)
.dbKey(cred.getUrl())
.databaseId(cred.getDatabaseId())
.build();
}
@Override
public ContainerStatus getContainerStatus(String tenantId) throws InternalError {
if (!doesTenantExist(tenantId)) {
return ContainerStatus.DOES_NOT_EXIST;
}
return ContainerStatus.OK;
}
@Override
public boolean hasCredentials(String tenantId, boolean forceCacheUpdate) throws InternalError {
return getContainerStatus(tenantId).equals(ContainerStatus.OK);
}
@Override
public Map getAllTenantInfos(boolean forceCacheUpdate) throws InternalError {
Map tenants = new HashMap<>();
List credentialsWithError = new ArrayList<>();
List sqlExceptions = new ArrayList<>();
dbIdentifiers.asStream().forEach(cred -> {
try (Connection connection = getConnection(cred)) {
sqlOperations.getAllSchemas(connection).stream()
.filter(s -> s.startsWith(SCHEMA_PREFIX))
.map(s -> {
TenantMetadata tenantInfo = new TenantMetadata(s.replace(SCHEMA_PREFIX, ""));
if (cred.getDatabaseId() != null) {
tenantInfo.putAdditionalProperty(DATABASE_ID, cred.getDatabaseId());
}
return tenantInfo;
})
.filter(tenantInfo -> FilterTenants.realTenants().test(tenantInfo.getTenantId()))
.forEach(t -> tenants.put(t.getTenantId(), t));
} catch (SQLException e) {
logger.error("Cannot access database {} because of {}", cred.getUrl(), e.getMessage());
sqlExceptions.add(e);
credentialsWithError.add(cred);
}
});
if (!sqlExceptions.isEmpty()) {
throw new InternalError("Cannot access database " + credentialsWithError.get(0).getUrl(), sqlExceptions.get(0));
}
return tenants;
}
@Override
public void checkThatTenantExists(String tenantId) throws UnknownTenant {
try {
if (!doesTenantExist(tenantId)) {
throw new UnknownTenant("No schema for tenant " + tenantId);
}
} catch (InternalError internalError) {
throw new UnknownTenant(internalError, "Could not access DB.");
}
}
@Override
public List createAndGetLibContainers(DataSourceInfo dataSourceInfo) throws InternalError {
List internalErrors = new ArrayList<>();
dbIdentifiers.asStream().forEach(cred -> {
String mtLibTenantId = getMtLibContainerName(cred.getDatabaseId());
try {
if (!doesTenantExist(mtLibTenantId)) {
synchronized (getMutex(mtLibTenantId)) {
createNewInstance(mtLibTenantId, createProvisioningParameters(cred.getDatabaseId()), new BindingParameters());
}
}
} catch (InternalError internalError) {
logger.error("Could not access DB with {}, error is {}", cred.getUrl(), internalError.getMessage());
internalErrors.add(internalError);
}
});
if (!internalErrors.isEmpty()) {
throw internalErrors.get(0);
}
return getLibContainers();
}
@Override
public List getLibContainers() throws InternalError {
List internalErrors = new ArrayList<>();
List dsInfo = new ArrayList<>();
dbIdentifiers.asStream().forEach(cred -> {
String mtLibTenantId = getMtLibContainerName(cred.getDatabaseId());
try {
if (!doesTenantExist(mtLibTenantId)) {
return;
}
try {
dsInfo.add(getDataSourceInfo(mtLibTenantId, true));
} catch (InternalError | UnknownTenant error) {
logger.error("Could not retrieve credentials for schema {}", mtLibTenantId);
internalErrors.add(new InternalError(error));
}
} catch (InternalError internalError) {
logger.error("Could not access DB with {}, error is {}", cred.getUrl(), internalError.getMessage());
internalErrors.add(internalError);
}
});
if (!internalErrors.isEmpty()) {
throw internalErrors.get(0);
}
return dsInfo;
}
private Connection getConnection(DbCredentials cred) throws SQLException {
return DriverManager.getConnection(cred.getUrl(), cred.getUser(), cred.getPassword());
}
private boolean doesTenantExist(String tenantId) throws InternalError {
Optional credentials = getCredentials(tenantId);
return credentials.isPresent();
}
private Optional getCredentials(String tenantId) throws InternalError {
List errors = new ArrayList<>();
Optional credentials = dbIdentifiers.asStream().filter(cred -> {
try (Connection connection = getConnection(cred)) {
return sqlOperations.doesSchemaExist(getSchemaName(tenantId), connection);
} catch (SQLException e) {
logger.error("Could not access DB {}", cred.getUrl());
errors.add(new InternalError(e));
return false;
}
}).findFirst();
if (!errors.isEmpty()) {
throw new InternalError(errors.get(0));
}
return credentials;
}
private String getSchemaName(String tenantId) {
return SCHEMA_PREFIX + tenantId;
}
private static Mutex getMutex(String tenantId) {
Mutex mutex = tenantToMutex.get(tenantId);
if (mutex != null) {
return mutex;
}
Mutex newMutex = new Mutex();
Mutex storedMutex = tenantToMutex.putIfAbsent(tenantId, newMutex);
if (storedMutex != null) {
return storedMutex;
} else {
return newMutex;
}
}
@Override
public void insertDbIdentifiers(DbIdentifiers dbIdentifiers) {
this.dbIdentifiers = (DbIdentifiersSql) dbIdentifiers;
}
protected DbIdentifiers getDbIdentifiers() {
return dbIdentifiers.createCopy();
}
@Override
public boolean hasDbIdentifiers() {
return dbIdentifiers != null && dbIdentifiers.areSet();
}
@Override
public DbIdentifiers.DB getDbType() {
return db;
}
private static class Mutex {//NOSONAR
}
}