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
/******************************************************************************
* © 2020 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.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;
import static com.sap.cloud.mt.runtime.DbUtils.getDbKey;
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 {
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 = getDbKey(tenantDataSourceInfo);
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 = getDbKey(info);
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
}
}
});
}
private synchronized void preparePool(DataSourceInfo dataSourceInfo) throws InternalError {
logger.debug("Prepare pool for database key {}", getDbKey(dataSourceInfo));
if (libContainerCache.isContained(getDbKey(dataSourceInfo))) {
return;
}
List libContainers = instanceLifecycleManager.createAndGetLibContainers(dataSourceInfo);
libContainers.stream().forEach(libContainerInfo -> {
String dbKey = getDbKey(libContainerInfo);
if (!libContainerCache.isContained(dbKey)) {
try {
libContainerCache.insert(dbKey, create(libContainerInfo));
} catch (InternalError internalError) {
logger.error("Cannot create data source pool for database key {}", dbKey);
}
}
});
}
boolean fixDataSourceAfterCredentialChange(String tenantId, DataSource usedPool) throws InternalError {
if (tenantId == null || tenantId.isEmpty()) {
return false;
}
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 true;
}
DataSourceInfo cachedInfo = dataSourceAndInfo.getDataSourceInfo();
DataSourceInfo currentInfo;
try {
currentInfo = instanceLifecycleManager.getDataSourceInfo(tenantId, true);
} catch (UnknownTenant unknownTenant) {
currentInfo = null;
}
if (currentInfo == null) {
adjustCacheAfterTenantDeletion(tenantId);
return false;
}
if (credentialsChanged(cachedInfo, currentInfo)) {
adjustCacheAfterCredentialChange(tenantId, dataSourceAndInfo, currentInfo);
return true;
} else {
// credentials haven't changed => nothing to fix
return false;
}
}
}
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(getDbKey(dataSourceAndInfo.getDataSourceInfo()));
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(e.getValue().getDataSourceInfo().clone()));
return result;
}
public List checkDataSourcePerDb(String dummySelectStatement) {
List result = new ArrayList<>();
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();
}
private class LibContainerCache {
private final ConcurrentHashMap dbKeyToLibContainer = new ConcurrentHashMap<>();
private boolean isContained(String dbKey) {
return dbKeyToLibContainer.containsKey(dbKey);
}
private void insert(String dbKey, DataSource dataSource) {
dbKeyToLibContainer.put(dbKey, dataSource);
}
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();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy