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

com.sap.cloud.mt.subscription.HdiContainerManager Maven / Gradle / Ivy

There is a newer version: 3.3.1
Show newest version
/******************************************************************************
 * © 2020 SAP SE or an SAP affiliate company. All rights reserved.            *
 ******************************************************************************/
package com.sap.cloud.mt.subscription;

import com.sap.cloud.mt.subscription.exceptions.HdiDeploymentCommunicationProblem;
import com.sap.cloud.mt.subscription.exceptions.InternalError;
import com.sap.cloud.mt.subscription.exits.UserProvidedSchemasExit;
import com.sap.cloud.mt.subscription.json.AsyncDeploymentReturn;
import com.sap.cloud.mt.subscription.json.Credentials;
import com.sap.cloud.mt.subscription.json.DbHost;
import com.sap.cloud.mt.subscription.json.DeploymentPayload;
import com.sap.cloud.mt.subscription.json.DeploymentReturn;
import com.sap.cloud.mt.subscription.json.Hana;
import com.sap.cloud.mt.subscription.json.ImBasedCredentialsHdiDeployment;
import com.sap.cloud.mt.subscription.json.ImBasedHdiDeploymentPayload;
import com.sap.cloud.mt.subscription.json.UserProvidedSchema;
import com.sap.cloud.mt.subscription.json.VcapService;
import com.sap.cloud.mt.tools.api.ServiceCall;
import com.sap.cloud.mt.tools.api.ServiceEndpoint;
import com.sap.cloud.mt.tools.api.ServiceResponse;
import com.sap.cloud.mt.tools.exception.InternalException;
import com.sap.cloud.mt.tools.exception.ServiceException;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import static org.apache.http.HttpStatus.SC_BAD_GATEWAY;
import static org.apache.http.HttpStatus.SC_GATEWAY_TIMEOUT;
import static org.apache.http.HttpStatus.SC_INTERNAL_SERVER_ERROR;
import static org.apache.http.HttpStatus.SC_NOT_FOUND;
import static org.apache.http.HttpStatus.SC_SERVICE_UNAVAILABLE;
import static org.slf4j.helpers.MessageFormatter.format;


public class HdiContainerManager implements DbDeployer {
    public static final String HDI_DEPLOYER_DESTINATION = "com.sap.cds.mtHdiDynamicDeployer";

    public static final String TARGET_CONTAINER_NAME = "COMSAPICDMTHDI";
    private static final Logger logger = LoggerFactory.getLogger(HdiContainerManager.class);
    private static final String DEPLOY_SYNC_PATH = "/v1/deploy";
    private static final String DEPLOY_ASYNC_PATH = "/v1/deploy/to/instance/async";
    private static final String STATUS_PATH = "/v1/status/";
    private static final String DEPLOYMENT_ENDPOINT_RETURNED = "HDI deployment endpoint returned http response code {}";
    private static final String DEPLOYMENT_RETURNED_AN_ERROR = "HDI deployment returned an error";
    private static final String HDI_DEPLOYMENT_FAILED_FOR_JOB_ID_IT_RETURNED_AN_EMPTY_PAYLOAD = "HDI deployment failed for job-id {}. It returned an empty payload";
    private static final String FINISHED = "FINISHED";
    private static final String HDI_DEPLOYMENT_RETURNED_NO_PAYLOAD = "HDI deployment returned no payload";
    private static final String HDI_DEPLOYMENT_RETURNED_WITH_EXIT_CODE_BUT_NO_RETURN_MESSAGES_PROVIDED = "HDI deployment returned with exit code {}, but no return messages provided";
    private static final String ERROR = "ERROR";
    private static final String PATH_MESSAGE = "Path: {} .Message {} ";
    private static final String ASYNCHRONOUS_HDI_DEPLOYMENT_DIDN_T_RETURN_A_JOB_GUID = "Asynchronous HDI deployment didn't return a job-id";
    private static final String HDI_DEPLOYMENT_SUCCEEDED = "HDI deployment succeeded for tenant {}";
    private static final String DYNAMIC_DEPLOYER_WAS_TRIGGERED_FOR_TENANT_AND_JOB_ID = "HDI deployment was triggered for tenant {} and job-id {}";
    private static final String DEPLOYMENT_DIDN_T_FINISH_IN_MAXIMUM_TIME = "HDI deployment didn't finish in maximum time {}";
    public static final String FAILED = "FAILED";
    public static final String RUNNING = "RUNNING";
    private final UserProvidedSchemasExit userProvidedSchemasExit;
    private final ServiceEndpoint deployEndpoint;
    private final ServiceEndpoint asyncDeployEndpoint;
    private final ServiceEndpoint statusEndpoint;
    private final ServiceSpecification serviceSpecification;

    public HdiContainerManager(ServiceSpecification serviceSpecification, UserProvidedSchemasExit userProvidedSchemasExit) throws InternalError {
        this.serviceSpecification = serviceSpecification;
        this.userProvidedSchemasExit = userProvidedSchemasExit;
        Set retryCodes = new HashSet<>();
        retryCodes.add(SC_BAD_GATEWAY);
        retryCodes.add(SC_GATEWAY_TIMEOUT);
        retryCodes.add(SC_INTERNAL_SERVER_ERROR);
        retryCodes.add(SC_SERVICE_UNAVAILABLE);
        retryCodes.add(SC_NOT_FOUND);
        try {
            deployEndpoint = ServiceEndpoint.create()
                    .destinationName(HDI_DEPLOYER_DESTINATION)
                    .path(DEPLOY_SYNC_PATH)
                    .returnCodeChecker(c -> {
                        if (c != HttpStatus.SC_OK) {
                            return new HdiDeploymentCommunicationProblem(format(DEPLOYMENT_ENDPOINT_RETURNED, c).getMessage());
                        }
                        return null;
                    }).retry().forReturnCodes(retryCodes)
                    .config(serviceSpecification.getResilienceConfig())
                    .end();
            asyncDeployEndpoint = ServiceEndpoint.create()
                    .destinationName(HDI_DEPLOYER_DESTINATION)
                    .path(DEPLOY_ASYNC_PATH)
                    .returnCodeChecker(c -> {
                        if (c != HttpStatus.SC_OK && c != HttpStatus.SC_CREATED) {
                            return new HdiDeploymentCommunicationProblem(format(DEPLOYMENT_ENDPOINT_RETURNED, c).getMessage());
                        }
                        return null;
                    }).retry().forReturnCodes(retryCodes)
                    .config(serviceSpecification.getResilienceConfig())
                    .end();
            statusEndpoint = ServiceEndpoint.create()
                    .destinationName(HDI_DEPLOYER_DESTINATION)
                    .path(STATUS_PATH)
                    .returnCodeChecker(c -> {
                        if (c != HttpStatus.SC_OK) {
                            return new HdiDeploymentCommunicationProblem(format(DEPLOYMENT_ENDPOINT_RETURNED, c).getMessage());
                        }
                        return null;
                    }).retry().forReturnCodes(retryCodes)
                    .config(serviceSpecification.getResilienceConfig())
                    .end();
        } catch (InternalException e) {
            throw new InternalError(e);
        }
    }

    private static void waitSomeTime(Duration waitTime) {
        try {
            Thread.sleep(waitTime.toMillis());
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    @Override
    public void populate(DataSourceInfo dataSourceInfo, String tenantId) throws InternalError {
        if (!FilterTenants.realTenants().test(tenantId)) return;
        try {
            if (userProvidedSchemasExit != null) {
                // user provided schemas are supported only for synchronous HDI access
                populateHdiContainerSync(dataSourceInfo, tenantId);
            } else {
                String jobGuid = populateHdiContainerAsync(dataSourceInfo);
                logger.debug(DYNAMIC_DEPLOYER_WAS_TRIGGERED_FOR_TENANT_AND_JOB_ID, tenantId, jobGuid);
                Instant start = Instant.now();
                boolean finished = false;
                do {
                    waitSomeTime(serviceSpecification.getPolling().getInterval());
                    finished = isDeploymentFinishedInternal(jobGuid);
                } while (!finished &&
                        Duration.between(start, Instant.now()).compareTo(serviceSpecification.getPolling().getRequestTimeout()) <= 0);
                if (!finished) {
                    logger.error(DEPLOYMENT_DIDN_T_FINISH_IN_MAXIMUM_TIME, serviceSpecification.getPolling().getRequestTimeout());
                    throw new InternalError(format(DEPLOYMENT_DIDN_T_FINISH_IN_MAXIMUM_TIME, serviceSpecification.getPolling().getRequestTimeout()).getMessage());
                }
            }
        } catch (Exception e) {
            throw new InternalError(e);
        }
    }

    private void populateHdiContainerSync(DataSourceInfo dataSourceInfo, String tenantId) throws InternalError,
            HdiDeploymentCommunicationProblem {
        ServiceResponse response = null;
        try {
            ServiceCall deploy = deployEndpoint.createServiceCall()
                    .http()
                    .post()
                    .payload(getBody(dataSourceInfo, userProvidedSchemasExit.onCallDynamicHdiDeployment()))
                    .noPathParameter()
                    .noQuery()
                    .enhancer(serviceSpecification.getRequestEnhancer())
                    .end();
            response = deploy.execute(DeploymentReturn.class);
        } catch (ServiceException e) {
            if (e.getCause() instanceof HdiDeploymentCommunicationProblem cause) {
                throw cause;
            }
            throw new InternalError(e);
        } catch (InternalException e) {
            throw new InternalError(e);
        }
        DeploymentReturn deploymentReturn = response.getPayload().orElse(null);
        if (deploymentReturn == null || deploymentReturn.exitCode == null || deploymentReturn.exitCode != 0) {
            logger.error(DEPLOYMENT_RETURNED_AN_ERROR);
            writeDeployReturnIntoLog(deploymentReturn);
            throw new InternalError(DEPLOYMENT_RETURNED_AN_ERROR);
        } else {
            logger.debug(HDI_DEPLOYMENT_SUCCEEDED, tenantId);
        }
    }

    private String populateHdiContainerAsync(DataSourceInfo dataSourceInfo) throws InternalError, HdiDeploymentCommunicationProblem {
        ServiceResponse response = null;
        try {
            ServiceCall deploy = asyncDeployEndpoint.createServiceCall()
                    .http()
                    .post()
                    .payload(getBodyWithInstanceManagerBasedPayload(dataSourceInfo))
                    .noPathParameter()
                    .noQuery()
                    .enhancer(serviceSpecification.getRequestEnhancer())
                    .end();
            response = deploy.execute(AsyncDeploymentReturn.class);
        } catch (ServiceException e) {
            if (e.getCause() instanceof HdiDeploymentCommunicationProblem cause) {
                throw cause;
            }
            throw new InternalError(e);
        } catch (InternalException e) {
            throw new InternalError(e);
        }
        AsyncDeploymentReturn deploymentReturn = response.getPayload().orElse(null);
        if (deploymentReturn == null || deploymentReturn.guid == null || deploymentReturn.guid.isEmpty()) {
            throw new InternalError(ASYNCHRONOUS_HDI_DEPLOYMENT_DIDN_T_RETURN_A_JOB_GUID);
        } else {
            return deploymentReturn.guid;
        }
    }

    public boolean isDeploymentFinishedInternal(String jobGuid) throws InternalError, HdiDeploymentCommunicationProblem {
        ServiceResponse response = null;
        try {
            ServiceCall status = statusEndpoint.createServiceCall()
                    .http()
                    .get()
                    .withoutPayload()
                    .pathParameter(jobGuid)
                    .noQuery()
                    .enhancer(serviceSpecification.getRequestEnhancer())
                    .end();
            response = status.execute(DeploymentReturn.class);
        } catch (ServiceException e) {
            if (e.getCause() instanceof HdiDeploymentCommunicationProblem cause) {
                throw cause;
            }
            throw new InternalError(e);
        } catch (InternalException e) {
            throw new InternalError(e);
        }
        DeploymentReturn deploymentReturn = response.getPayload().orElse(null);
        if (deploymentReturn != null) {
            if (FINISHED.equalsIgnoreCase(deploymentReturn.status)) {
                return true;
            } else if (FAILED.equalsIgnoreCase(deploymentReturn.status)) {
                writeDeployReturnIntoLog(deploymentReturn);
                throw new InternalError("Hdi deployment failed");
            } else if (RUNNING.equalsIgnoreCase(deploymentReturn.status)) {
                return false;
            } else {
                throw new InternalError("Wrong or missing status %s from HDI deployer".formatted(deploymentReturn.status));
            }
        } else {
            logger.error(HDI_DEPLOYMENT_FAILED_FOR_JOB_ID_IT_RETURNED_AN_EMPTY_PAYLOAD, jobGuid);
            throw new HdiDeploymentCommunicationProblem(format(HDI_DEPLOYMENT_FAILED_FOR_JOB_ID_IT_RETURNED_AN_EMPTY_PAYLOAD, jobGuid).getMessage());
        }
    }

    protected DeploymentPayload getBody(DataSourceInfo dataSourceInfo, List userProvidedSchemas) {

        DeploymentPayload payload = new DeploymentPayload();
        //hosts
        DbHost[] dbHosts = new DbHost[1];
        dbHosts[0] = new DbHost();
        dbHosts[0].port = Integer.parseInt(dataSourceInfo.getPort());
        dbHosts[0].host = dataSourceInfo.getHost();
        // Credentials
        Credentials credentials = new Credentials();
        credentials.schema = dataSourceInfo.getSchema();
        credentials.driver = dataSourceInfo.getDriver();
        credentials.port = dataSourceInfo.getPort();
        credentials.host = dataSourceInfo.getHost();
        credentials.db_hosts = dbHosts;
        credentials.user = dataSourceInfo.getUser();
        credentials.password = dataSourceInfo.getPassword();
        credentials.hdi_user = dataSourceInfo.getHdiUser();
        credentials.hdi_password = dataSourceInfo.getHdiPassword();
        credentials.url = dataSourceInfo.getUrl();
        credentials.certificate = dataSourceInfo.getCertificate();
        // Hana
        Hana[] hanas = new Hana[1];
        hanas[0] = new Hana();
        hanas[0].name = TARGET_CONTAINER_NAME;
        hanas[0].credentials = credentials;
        // Root
        payload.ADDITIONAL_VCAP_SERVICES = new VcapService();
        payload.ADDITIONAL_VCAP_SERVICES.hana = hanas;
        if (!CollectionUtils.isEmpty(userProvidedSchemas)) {
            UserProvidedSchema[] userProvidedSchemaArray = new UserProvidedSchema[userProvidedSchemas.size()];
            userProvidedSchemas.toArray(userProvidedSchemaArray);
            payload.ADDITIONAL_VCAP_SERVICES.user_provided = userProvidedSchemaArray;
        }
        payload.TARGET_CONTAINER = TARGET_CONTAINER_NAME;
        return payload;
    }

    //The dynamic HDI deployment provides additional endpoints that take as payload, what is returned from the instance manager.
    //This method provides a payload in this format.
    private ImBasedHdiDeploymentPayload getBodyWithInstanceManagerBasedPayload(DataSourceInfo dataSourceInfo) {
        ImBasedHdiDeploymentPayload payload = new ImBasedHdiDeploymentPayload();
        // Credentials
        ImBasedCredentialsHdiDeployment credentials = new ImBasedCredentialsHdiDeployment();
        payload.credentials = credentials;
        credentials.schema = dataSourceInfo.getSchema();
        credentials.driver = dataSourceInfo.getDriver();
        credentials.port = dataSourceInfo.getPort();
        credentials.host = dataSourceInfo.getHost();
        credentials.user = dataSourceInfo.getUser();
        credentials.password = dataSourceInfo.getPassword();
        credentials.hdi_user = dataSourceInfo.getHdiUser();
        credentials.hdi_password = dataSourceInfo.getHdiPassword();
        credentials.url = dataSourceInfo.getUrl();
        credentials.certificate = dataSourceInfo.getCertificate();
        // Root
        payload.tenant_id = dataSourceInfo.getTenantId();
        payload.id = dataSourceInfo.getId();
        payload.status = dataSourceInfo.getStatusAsText();
        return payload;
    }

    private void writeDeployReturnIntoLog(DeploymentReturn deploymentReturn) {
        if (deploymentReturn == null || deploymentReturn.messages == null) {
            if (deploymentReturn == null) {
                logger.error(HDI_DEPLOYMENT_RETURNED_NO_PAYLOAD);
            } else {
                logger.error(HDI_DEPLOYMENT_RETURNED_WITH_EXIT_CODE_BUT_NO_RETURN_MESSAGES_PROVIDED, deploymentReturn.exitCode);
            }
            return;
        }
        Arrays.stream(deploymentReturn.messages)
                .filter(m -> ERROR.equalsIgnoreCase(m.severity))
                .forEach(m -> logger.error(PATH_MESSAGE, m.path, m.message));
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy