com.sap.cloud.mt.runtime.DataSourceLookup Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of multi-tenant-runtime Show documentation
Show all versions of multi-tenant-runtime Show documentation
Spring Boot Enablement Parent
The newest version!
/*******************************************************************************
* © 2019-2024 SAP SE or an SAP affiliate company. All rights reserved.
******************************************************************************/
package com.sap.cloud.mt.runtime;
import com.sap.cloud.mt.subscription.DataSourceInfo;
import com.sap.cloud.mt.subscription.DbIdentifiers;
import com.sap.cloud.mt.subscription.InstanceLifecycleManager;
import com.sap.cloud.mt.subscription.MtxTools;
import com.sap.cloud.mt.subscription.SqlOperations;
import com.sap.cloud.mt.subscription.TenantMutexFactory;
import com.sap.cloud.mt.subscription.exceptions.InternalError;
import com.sap.cloud.mt.subscription.exceptions.UnknownTenant;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;
public abstract class DataSourceLookup {
private static final Logger logger = LoggerFactory.getLogger(DataSourceLookup.class);
private final ConcurrentHashMap tenantToDataSource = new ConcurrentHashMap<>();
private final InstanceLifecycleManager instanceLifecycleManager;
private final LibContainerCache libContainerCache = new LibContainerCache();
private boolean oneDataSourcePerDb = false;
protected DataSourceLookup(InstanceLifecycleManager instanceLifecycleManager, boolean oneDataSourcePerDb) {
this.instanceLifecycleManager = instanceLifecycleManager;
this.oneDataSourcePerDb = oneDataSourcePerDb;
}
protected DataSourceLookup(InstanceLifecycleManager instanceLifecycleManager) {
this(instanceLifecycleManager, false);
}
DataSourceAndInfo getDataSourceAndInfo(String tenantId) throws InternalError, UnknownTenant {
logger.debug("Determine data source information for tenant {}", tenantId);
DataSourceAndInfo dataSourceAndInfo = tenantToDataSource.get(tenantId);
if (dataSourceAndInfo != null) {
return dataSourceAndInfo;
} else {
synchronized (TenantMutexFactory.get(tenantId)) {
dataSourceAndInfo = tenantToDataSource.get(tenantId);
if (dataSourceAndInfo != null) {
return dataSourceAndInfo;
}
logger.debug("Access instance manager for tenant {}", tenantId);
DataSourceInfo tenantDataSourceInfo = instanceLifecycleManager.getDataSourceInfo(tenantId, false);
String dbKey = tenantDataSourceInfo.getDbKey();
DataSource dataSource = null;
if (oneDataSourcePerDb) {
dataSource = libContainerCache.get(dbKey);
if (dataSource == null) {
preparePool(tenantDataSourceInfo);
dataSource = libContainerCache.get(dbKey);
if (dataSource == null) {
throw new InternalError("Could not find database pool for db key " + dbKey);
}
}
} else {
//make sure that also in mode "one pool per tenant" lib containers are created
preparePool(tenantDataSourceInfo);
dataSource = create(tenantDataSourceInfo);
}
DataSourceAndInfo newEntry = new DataSourceAndInfo(dataSource, tenantDataSourceInfo);
tenantToDataSource.put(tenantId, newEntry);
return newEntry;
}
}
}
public void loadOneTenantPerDb() throws InternalError {
Map> dbKeyToTenants = new HashMap<>();
List tenantList = new ArrayList<>(instanceLifecycleManager.getAllTenants(false));
tenantList.sort(Comparator.comparing(String::toString));
tenantList.stream()
.forEach(t -> {
try {
DataSourceInfo info = instanceLifecycleManager.getDataSourceInfo(t, false);
String dbKey = info.getDbKey();
List tenants = dbKeyToTenants.get(dbKey);
if (tenants == null) {
dbKeyToTenants.put(dbKey, new ArrayList<>());
tenants = dbKeyToTenants.get(dbKey);
}
tenants.add(t);
} catch (Exception e) {
//NOSONAR
}
});
dbKeyToTenants.entrySet().stream().forEach(e -> {
for (String tenant : e.getValue()) {
try {
getDataSourceAndInfo(tenant);
// Only one tenant per db
break;
} catch (InternalError | UnknownTenant error) {
//try the next tenant for this db
}
}
});
}
public boolean isNotAuthenticationProblem(SQLException sqlException) {
return !isAuthenticationProblem(sqlException);
}
public boolean isAuthenticationProblem(SQLException sqlException) {
try {
SqlOperations sqlOperations = SqlOperations.build(instanceLifecycleManager.getDbType());
return sqlOperations.isAuthenticationProblem(determineSqlState(sqlException));
} catch (InternalError e) {
logger.error("Not supported DB", e);
//Not supported DB, should never happen
return false;
}
}
protected String determineSqlState(Throwable exception) {
if (exception instanceof SQLException sqlException) {
String sqlState = sqlException.getSQLState();
if (StringUtils.isNotBlank(sqlState)) {
return sqlState;
}
}
if (exception.getCause() != null) {
return determineSqlState(exception.getCause());
}
return "";
}
private synchronized void preparePool(DataSourceInfo dataSourceInfo) throws InternalError {
logger.debug("Prepare pool for database key {}", dataSourceInfo.getDbKey());
if (libContainerCache.isContained(dataSourceInfo.getDbKey())) {
return;
}
List libContainers = instanceLifecycleManager.createAndGetLibContainers(dataSourceInfo);
libContainers.stream().forEach(this::createAndCachePoolsForLibContainerPools);
}
private void createAndCachePoolsForLibContainerPools(DataSourceInfo libContainerInfo) {
libContainerCache.createIfNotExist(libContainerInfo.getDbKey(), () -> create(libContainerInfo));
}
void fixDataSourceAfterCredentialChange(String tenantId, DataSource usedPool) throws InternalError {
if (StringUtils.isBlank(tenantId)) {
logger.debug("No tenant specified");
throw new InternalError("No tenant specified");
}
synchronized (TenantMutexFactory.get(tenantId)) {
// Refresh only, if the container was really recreated for the same tenant
// A parallel task could have fixed it already.
// It must be avoided that pools that were fixed and are used are accidentally deleted
DataSourceAndInfo dataSourceAndInfo = tenantToDataSource.get(tenantId);
if (dataSourceAndInfo == null || usedPool != dataSourceAndInfo.getDataSource()) {
//already removed from cache, or new pool created
return;
}
DataSourceInfo cachedInfo = dataSourceAndInfo.getDataSourceInfo();
DataSourceInfo freshInfo;
try {
freshInfo = instanceLifecycleManager.getDataSourceInfo(tenantId, true);
} catch (UnknownTenant unknownTenant) {
freshInfo = null;
}
if (freshInfo == null) {
adjustCacheAfterTenantDeletion(tenantId);
logger.debug("Tenant was deleted");
throw new InternalError("Tenant was deleted");
}
if (credentialsChanged(cachedInfo, freshInfo)) {
adjustCacheAfterCredentialChange(tenantId, dataSourceAndInfo, freshInfo);
} else {
// credentials haven't changed => nothing to fix
logger.debug("Normal database error");
throw new InternalError("Normal database error");
}
}
}
private void adjustCacheAfterCredentialChange(String tenantId, DataSourceAndInfo dataSourceAndInfo, DataSourceInfo currentInfo) {
if (!oneDataSourcePerDb) {
deleteFromCache(tenantId);
} else {
//credentials must be updated
dataSourceAndInfo.setDataSourceInfo(currentInfo);
}
}
private boolean credentialsChanged(DataSourceInfo cached, DataSourceInfo current) {
return !StringUtils.equals(current.getPassword(), cached.getPassword()) ||
!StringUtils.equals(current.getUser(), cached.getUser()) ||
!StringUtils.equals(current.getSchema(), cached.getSchema()) ||
!StringUtils.equals(current.getDbKey(), cached.getDbKey());
}
private void adjustCacheAfterTenantDeletion(String tenantId) {
if (!oneDataSourcePerDb) {
deleteFromCache(tenantId);
} else {
tenantToDataSource.remove(tenantId);
}
}
void deleteFromCache(String tenantId) {
if (tenantId == null) {
return;
}
logger.debug("Instance for tenant {} deleted from cache", tenantId);
synchronized (TenantMutexFactory.get(tenantId)) {
DataSourceAndInfo dataSourceAndInfo = tenantToDataSource.get(tenantId);
if (dataSourceAndInfo == null) {
return;
}
tenantToDataSource.remove(tenantId);
if (oneDataSourcePerDb) {
long numberOfEntries = tenantToDataSource.entrySet().stream().filter(e ->
e.getValue().getDataSource() == dataSourceAndInfo.getDataSource()).count();
if (numberOfEntries == 0) {
libContainerCache.remove(dataSourceAndInfo.getDataSourceInfo().getDbKey());
closeDataSource(dataSourceAndInfo.getDataSource());
}
} else {
closeDataSource(dataSourceAndInfo.getDataSource());
}
}
}
public void reset() {
if (oneDataSourcePerDb) {
tenantToDataSource.clear();
libContainerCache.reset();
} else {
tenantToDataSource.entrySet().stream().forEach(e -> closeDataSource(e.getValue().getDataSource()));
tenantToDataSource.clear();
}
}
public List getCachedDataSource() {
List result = new ArrayList<>();
tenantToDataSource.entrySet().stream().forEach(e -> result.add(new DataSourceInfo(e.getValue().getDataSourceInfo())));
return result;
}
public List checkDataSourcePerDb(String dummySelectStatement) {
List result = new ArrayList<>();
try {
instanceLifecycleManager.getLibContainers().stream().forEach(this::createAndCachePoolsForLibContainerPools);
} catch (InternalError e) {
logger.error("Could not access containers owned by the mt-lib", e);
HealtCheckResult healtCheckResult = new HealtCheckResult("", false, e);
result.add(healtCheckResult);
return result;
}
libContainerCache.stream()
.forEach(e -> {
try (Connection connection = e.getValue().getConnection()) {
SqlOperations sqlOperations = SqlOperations.build(instanceLifecycleManager.getDbType());
sqlOperations.setDummySelectStatement(dummySelectStatement);
sqlOperations.dummySelect(connection);
HealtCheckResult healtCheckResult = new HealtCheckResult(e.getKey(), true, null);
result.add(healtCheckResult);
} catch (InternalError | SQLException exception) {
HealtCheckResult healtCheckResult = new HealtCheckResult(e.getKey(), false, exception);
result.add(healtCheckResult);
}
});
return result;
}
public void checkDataSource(String tenantId, String dummySelectStatement) throws SQLException {
DataSourceAndInfo dataSourceAndInfo = tenantToDataSource.get(tenantId);
if (dataSourceAndInfo == null) {
throw new SQLException("Tenant " + tenantId + " doesn't exist");
} else {
try (Connection connection = dataSourceAndInfo.getDataSource().getConnection()) {
SqlOperations sqlOperations = SqlOperations.build(instanceLifecycleManager.getDbType());
sqlOperations.setDummySelectStatement(dummySelectStatement);
sqlOperations.dummySelect(connection);
} catch (InternalError exception) {
throw new SQLException(exception);
}
}
}
boolean doesTenantExist(String tenantId) throws InternalError {
try {
instanceLifecycleManager.checkThatTenantExists(tenantId);
return true;
} catch (UnknownTenant unknownTenant) {
return false;
}
}
protected abstract DataSource create(DataSourceInfo info) throws InternalError;
protected abstract void closeDataSource(DataSource dataSource);
public boolean isOneDataSourcePerDb() {
return oneDataSourcePerDb;
}
public DbIdentifiers.DB getDbType() {
return instanceLifecycleManager.getDbType();
}
public boolean hasDbIdentifiers() {
return instanceLifecycleManager.hasDbIdentifiers();
}
public boolean knowsDbCredentials() {
return instanceLifecycleManager.knowsDbCredentials();
}
private class LibContainerCache {
private final ConcurrentHashMap dbKeyToLibContainer = new ConcurrentHashMap<>();
private boolean isContained(String dbKey) {
return dbKeyToLibContainer.containsKey(dbKey);
}
private DataSource get(String dbKey) {
return dbKeyToLibContainer.get(dbKey);
}
private DataSource remove(String dbKey) {
return dbKeyToLibContainer.remove(dbKey);
}
private void reset() {
dbKeyToLibContainer.entrySet().stream().forEach(e -> closeDataSource(e.getValue()));
dbKeyToLibContainer.clear();
}
private Stream> stream() {
return dbKeyToLibContainer.entrySet().stream();
}
private void createIfNotExist(String dbKey, MtxTools.SupplierWithInternalError dataSourceSupplier) {
try {
dbKeyToLibContainer.putIfAbsent(dbKey, dataSourceSupplier.get());
} catch (InternalError e) {
logger.error("Cannot create data source pool", e);
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy