All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sap.cloud.mt.runtime.DbHealthIndicatorImpl Maven / Gradle / Ivy

There is a newer version: 3.3.3
Show newest version
/******************************************************************************
 * © 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.SqlOperations;
import com.sap.cloud.mt.subscription.exceptions.InternalError;
import com.sap.cloud.mt.subscription.exceptions.ParameterException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;

public class DbHealthIndicatorImpl {
    private static final Logger logger = LoggerFactory.getLogger(DbHealthIndicatorImpl.class);
    private static final int STACK_TRACE_WRITE_PERIOD = 100;
    private static final String DETAIL_INFORMATION = "Detail information:";
    private final TenantAwareDataSource dataSource;
    private volatile long lastChecked = 0;
    private AtomicReference lastHealth = new AtomicReference<>(null);
    private final Long healthCheckIntervalMillis;
    private final HealthUp healthUp;
    private final HealthDownDetails healthDownDetails;
    private final HealthUpDetails healthUpDetails;
    private AtomicInteger callCounter = new AtomicInteger(0);
    private final ReentrantLock lock = new ReentrantLock();
    private final SqlOperations sqlOperations;
    private final String healthDummySelect;

    public DbHealthIndicatorImpl(String healthDummySelect, TenantAwareDataSource dataSource,
                                 Long healthCheckIntervalMillis,
                                 HealthUp healthUp, HealthDownDetails healthDownDetails,
                                 HealthUpDetails healthUpDetails) {
        this.healthDummySelect = healthDummySelect;
        this.dataSource = dataSource;
        this.healthCheckIntervalMillis = healthCheckIntervalMillis;
        this.healthUp = healthUp;
        this.healthDownDetails = healthDownDetails;
        this.healthUpDetails = healthUpDetails;
        try {
            if (dataSource.getDbType() != null) {
                this.sqlOperations = SqlOperations.build(dataSource.getDbType());
            } else {
                this.sqlOperations = SqlOperations.build(DbIdentifiers.DB.HANA);
            }
            this.sqlOperations.setDummySelectStatement(healthDummySelect);
        } catch (InternalError internalError) {
            throw new ParameterException(internalError);
        }
    }

    public T health() {
        boolean hasLock = false;
        try {
            hasLock = lock.tryLock();
            if (!hasLock) {
                if (lastHealth.get() == null) {
                    logger.debug("Lock for health check couldn't be acquired. Return positive result as no last health check exists");
                    return healthUp.execute();
                }
                logger.debug("Return result of last health check as no lock could be acquired. Result was {}", lastHealth.get().toString());
                return lastHealth.get();
            }
            callCounter.incrementAndGet();
            if (callCounter.get() > STACK_TRACE_WRITE_PERIOD) {
                callCounter.set(0);
            }
            if (lastChecked != 0 && ((System.currentTimeMillis() - lastChecked) < healthCheckIntervalMillis)) {
                logger.debug("Result of last health check is returned as not much time has passed, result is {}", lastHealth.get().toString());
                return lastHealth.get();
            }
            try {
                List detailInfo = new ArrayList<>();
                boolean down = false;
                // If database identifiers are set, mt-lib's own containers can be used for the health check
                if (dataSource.getDataSourceLookup().hasDbIdentifiers()) {
                    down = isDownNewCheck(detailInfo);
                } else {
                    down = isDown(detailInfo, getOneDataSourceInfoPerDb());
                }
                if (down) {
                    lastHealth.set(healthDownDetails.execute(DETAIL_INFORMATION, detailInfo));
                } else {
                    lastHealth.set(healthUpDetails != null ?
                            healthUpDetails.execute(DETAIL_INFORMATION, detailInfo) : healthUp.execute());
                }
            } catch (Exception e) {
                logger.error("Unexpected exception was thrown in health check", e);
                lastHealth.set(healthDownDetails.execute(DETAIL_INFORMATION, Arrays.asList(e.getMessage())));
            }
            lastChecked = System.currentTimeMillis();
            return lastHealth.get();
        } finally {
            if (hasLock) {
                lock.unlock();
            }
        }
    }

    private Map> getOneDataSourceInfoPerDb() throws InternalError {
        List dataSourceInfos = dataSource.getDataSourceLookup().getCachedDataSource();
        //maybe service was just restarted and no pools available, yet
        if (dataSourceInfos.isEmpty()) {
            logger.debug("No datasource cached. Load one data source for health check");
            dataSource.getDataSourceLookup().loadOneTenantPerDb();
            dataSourceInfos = dataSource.getDataSourceLookup().getCachedDataSource();
            if (dataSourceInfos.isEmpty()) {
                logger.error("Could not determine a data source for health check");
            }
        }
        return dataSourceInfoPerDb(dataSourceInfos);
    }

    private boolean isDown(List detailInfo, Map> uriToDbInfo) {
        logger.debug("Execute the old implementation of the health check.");
        if (uriToDbInfo.isEmpty()) {
            detailInfo.add("No DB schemas for test available");
            return false;
        }
        AtomicBoolean down = new AtomicBoolean(false);
        uriToDbInfo.entrySet().forEach(infoListEntry -> {
            try {
                checkOneDB(detailInfo, down, infoListEntry);
            } catch (InternalError internalError) {
                detailInfo.add("Error occurred:" + internalError.getMessage());
                down.set(true);
                //problem with DB or pool
            }
        });
        return down.get();
    }

    private void checkOneDB(List detailInfo, AtomicBoolean down, Map.Entry> infoListEntry) throws InternalError {
        List infoList = infoListEntry.getValue();
        for (DataSourceInfo info : infoList) {
            try {
                dataSource.getDataSourceLookup().checkDataSource(info.getTenantId(), healthDummySelect);
                detailInfo.add("Connection for DB " + info.getHost() + ":" + info.getPort() + " is ok");
                //This DB is ok
                break;
            } catch (SQLException e) {
                //not necessarily an error, tenant could be unsubscribed, this doesn't mean that the DB is broken
                if (dataSource.doesTenantExist(info.getTenantId())) {
                    logger.error("Could not open connection for DB {}", info.getHost() + ":" + info.getPort());
                    logger.debug("The following error was reported: {}", e.getMessage());
                    detailInfo.add("Could not open connection for DB " + info.getHost() + ":" + info.getPort());
                    down.set(true);
                    //problem with DB or pool
                    break;
                }
            }
        }
    }

    private boolean isDownNewCheck(List detailInfo) {
        logger.debug("Execute the new implementation of the health check.");
        List healthCheckResults = dataSource.getDataSourceLookup().checkDataSourcePerDb(healthDummySelect);
        Boolean[] down = {false};
        healthCheckResults.stream().forEach(result -> {
            if (!result.isOk()) {
                logger.error("Could not open connection for DB {}", result.getDbIdentifier());
                if (callCounter.get() == STACK_TRACE_WRITE_PERIOD) {
                    logger.error("The following error was reported: ", result.getException());
                } else {
                    logger.error("The following error was reported: {}", result.getException().getMessage());
                }
                detailInfo.add("Could not open connection for DB " + result.getDbIdentifier() + ". Error is: " +
                        result.getException().getMessage());
                down[0] = true;
            } else {
                logger.debug("Connection for DB {} is ok", result.getDbIdentifier());
                detailInfo.add("Connection for DB " + result.getDbIdentifier() + " is ok");
            }
        });
        return down[0];
    }

    public String getHealthDummySelect() {
        return healthDummySelect;
    }

    private Map> dataSourceInfoPerDb(List infoList) {
        Map> urlToInfo = new HashMap<>();
        infoList.stream().forEach(i -> {
            List list = urlToInfo.get(DbUtils.getDbKey(i));
            if (list == null) {
                list = new ArrayList<>();
                urlToInfo.put(DbUtils.getDbKey(i), list);
            }
            list.add(i);
        });
        return urlToInfo;
    }

    @FunctionalInterface
    public interface HealthUp {
        public T execute();
    }

    @FunctionalInterface
    public interface HealthDown {
        public T execute();
    }

    @FunctionalInterface
    public interface HealthDownDetails {
        public T execute(String text, List detailInfo);
    }

    @FunctionalInterface
    public interface HealthUpDetails {
        public T execute(String text, List detailInfo);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy