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.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
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.ResilienceConfig;
import com.sap.cloud.mt.tools.impl.Retry;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.List;

import static org.slf4j.helpers.MessageFormatter.format;


public class HdiContainerManager implements DbDeployer {
    public static final String TARGET_CONTAINER_NAME = "COMSAPICDMTHDI";
    private static final String APPLICATION_JSON = "application/json";
    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 WRONG_URL_FOR_HDI_DEPLOYMENT_SPECIFIED = "Wrong URL for HDI deployment specified: {}";
    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 COULD_NOT_CREATE_DEPLOYER_PAYLOAD = "Could not create deployer payload";
    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_FOR_TENANT_RETURNED_WITH_IOEXCEPTION = "HDI deployment for tenant {} returned with IOException.";
    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 {}";
    private static final String HDI_DEPLOYMENT_FAILED_IT_RETURNED_1 = "HDI deployment failed with return code -1";
    private final URI dynamicDeploymentUri;
    private final URI dynamicDeploymentUriAsync;
    private final String user;
    private final String password;
    private final String dynamicDeploymentUrlStr;
    private final PollingParameters pollingParameters;
    private final UserProvidedSchemasExit userProvidedSchemasExit;
    private final ResilienceConfig resilienceConfig;

    public HdiContainerManager(DynamicHdiDeploymentParameters hdiDeploymentParas, UserProvidedSchemasExit userProvidedSchemasExit) throws InternalError {
        this.user = hdiDeploymentParas.getUser();
        this.password = hdiDeploymentParas.getPassword();
        this.dynamicDeploymentUrlStr = hdiDeploymentParas.getUrl();
        this.resilienceConfig = hdiDeploymentParas.getResilienceConfig();
        try {
            this.dynamicDeploymentUri = new URL(dynamicDeploymentUrlStr + DEPLOY_SYNC_PATH).toURI();
            this.dynamicDeploymentUriAsync = new URL(dynamicDeploymentUrlStr + DEPLOY_ASYNC_PATH).toURI();
        } catch (MalformedURLException | URISyntaxException e) {
            logger.error(WRONG_URL_FOR_HDI_DEPLOYMENT_SPECIFIED, dynamicDeploymentUrlStr);
            throw new InternalError(format(WRONG_URL_FOR_HDI_DEPLOYMENT_SPECIFIED, dynamicDeploymentUrlStr).getMessage(), e);
        }
        pollingParameters = hdiDeploymentParas.getPolling();
        this.userProvidedSchemasExit = userProvidedSchemasExit;
    }

    @Override
    public void populate(DataSourceInfo dataSourceInfo, String tenantId) throws InternalError {
        Retry retry = Retry.RetryBuilder.create()
                .waitTime(resilienceConfig.getRetryInterval())
                .numOfRetries(resilienceConfig.getNumOfRetries())
                .retryExceptions(HdiDeploymentCommunicationProblem.class)
                .build();
        try {
            if (userProvidedSchemasExit != null) {
                // user provided schemas are supported only for synchronous HDI access
                retry.execute(() -> populateHdiContainerSync(dataSourceInfo, tenantId));
            } else {
                String jobGuid = retry.execute(() -> populateHdiContainerAsync(dataSourceInfo, tenantId));
                logger.debug(DYNAMIC_DEPLOYER_WAS_TRIGGERED_FOR_TENANT_AND_JOB_ID, tenantId, jobGuid);
                Instant start = Instant.now();
                boolean finished = false;
                do {
                    waitSomeTime(pollingParameters.getInterval());
                    finished = retry.execute(() -> isDeploymentFinishedInternal(jobGuid));
                } while (!finished &&
                        Duration.between(start, Instant.now()).compareTo(pollingParameters.getRequestTimeout()) <= 0);
                if (!finished) {
                    logger.error(DEPLOYMENT_DIDN_T_FINISH_IN_MAXIMUM_TIME, pollingParameters.getRequestTimeout());
                    throw new InternalError(format(DEPLOYMENT_DIDN_T_FINISH_IN_MAXIMUM_TIME, pollingParameters.getRequestTimeout()).getMessage());
                }
            }
        } catch (Exception e) {
            throw new InternalError(e);
        }
    }

    private void populateHdiContainerSync(DataSourceInfo dataSourceInfo, String tenantId) throws InternalError,
            HdiDeploymentCommunicationProblem {
        if (!FilterTenants.realTenants().test(tenantId)) return;
        try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) {
            HttpUriRequest postRequest = RequestBuilder.post(dynamicDeploymentUri)
                    .setHeader(HttpHeaders.AUTHORIZATION, getAuthHeader())
                    .setHeader(HttpHeaders.ACCEPT, APPLICATION_JSON)
                    .setHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON)
                    .setEntity(new StringEntity(getBody(dataSourceInfo, userProvidedSchemasExit.onCallDynamicHdiDeployment())))
                    .build();
            try (CloseableHttpResponse response = httpClient.execute(postRequest)) {
                int responseCode = response.getStatusLine().getStatusCode();
                if (responseCode == HttpStatus.SC_OK) {
                    StreamToJsonConverter converter = new StreamToJsonConverter<>(DeploymentReturn.class);
                    DeploymentReturn deploymentReturn = null;
                    if (response.getEntity() != null) {
                        String payload = EntityUtils.toString(response.getEntity());
                        deploymentReturn = converter.asJson(payload);
                    }
                    //The hdi deployment sometimes crashes with segmentation fault. Then -1
                    //is returned. In this case a another try can help.
                    if (deploymentReturn != null && deploymentReturn.exitCode == -1) {
                        logger.error(HDI_DEPLOYMENT_FAILED_IT_RETURNED_1);
                        writeDeployReturnIntoLog(deploymentReturn);
                        throw new HdiDeploymentCommunicationProblem(HDI_DEPLOYMENT_FAILED_IT_RETURNED_1);
                    } else 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);
                        return;
                    }
                } else {
                    throw new HdiDeploymentCommunicationProblem(format(DEPLOYMENT_ENDPOINT_RETURNED, responseCode).getMessage());
                }
            }
        } catch (IOException e) {
            logger.error(HDI_DEPLOYMENT_FOR_TENANT_RETURNED_WITH_IOEXCEPTION, tenantId);
            throw new HdiDeploymentCommunicationProblem(format(HDI_DEPLOYMENT_FOR_TENANT_RETURNED_WITH_IOEXCEPTION, tenantId).getMessage(), e);
        }
    }

    private String populateHdiContainerAsync(DataSourceInfo dataSourceInfo, String tenantId) throws InternalError, HdiDeploymentCommunicationProblem {
        if (!FilterTenants.realTenants().test(tenantId)) return null;
        try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) {
            HttpUriRequest postRequest = RequestBuilder.post(dynamicDeploymentUriAsync)
                    .setHeader(HttpHeaders.AUTHORIZATION, getAuthHeader())
                    .setHeader(HttpHeaders.ACCEPT, APPLICATION_JSON)
                    .setHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON)
                    .setEntity(new StringEntity(getBodyWithInstanceManagerBasedPayload(dataSourceInfo)))
                    .build();
            try (CloseableHttpResponse response = httpClient.execute(postRequest)) {
                int responseCode = response.getStatusLine().getStatusCode();
                if (responseCode == HttpStatus.SC_OK || responseCode == HttpStatus.SC_CREATED) {
                    AsyncDeploymentReturn deploymentReturn = null;
                    if (response.getEntity() != null) {
                        deploymentReturn = (new StreamToJsonConverter<>(AsyncDeploymentReturn.class)).asJson(EntityUtils.toString(response.getEntity()));
                    }
                    //The hdi deployment sometimes crashes with segmentation fault. Then -1
                    //is returned. In this case a another try can help.
                    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;
                    }
                } else {
                    throw new HdiDeploymentCommunicationProblem(format(DEPLOYMENT_ENDPOINT_RETURNED, responseCode).getMessage());
                }
            }
        } catch (IOException e) {
            logger.error(HDI_DEPLOYMENT_FOR_TENANT_RETURNED_WITH_IOEXCEPTION, tenantId);
            throw new HdiDeploymentCommunicationProblem(format(HDI_DEPLOYMENT_FOR_TENANT_RETURNED_WITH_IOEXCEPTION, tenantId).getMessage(), e);
        }
    }

    public boolean isDeploymentFinishedInternal(String jobGuid) throws InternalError, HdiDeploymentCommunicationProblem {
        URI statusUri;
        String statusUrlString = dynamicDeploymentUrlStr + STATUS_PATH + jobGuid;
        try {
            statusUri = new URL(statusUrlString).toURI();
        } catch (MalformedURLException | URISyntaxException e) {
            logger.error(WRONG_URL_FOR_HDI_DEPLOYMENT_SPECIFIED, statusUrlString);
            throw new InternalError(format(WRONG_URL_FOR_HDI_DEPLOYMENT_SPECIFIED, statusUrlString).getMessage(), e);
        }
        try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) {
            HttpUriRequest getRequest = RequestBuilder.get(statusUri)
                    .setHeader(HttpHeaders.AUTHORIZATION, getAuthHeader())
                    .setHeader(HttpHeaders.ACCEPT, APPLICATION_JSON)
                    .setHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON)
                    .build();
            try (CloseableHttpResponse response = httpClient.execute(getRequest)) {
                int responseCode = response.getStatusLine().getStatusCode();
                if (responseCode == HttpStatus.SC_OK) {
                    DeploymentReturn deploymentReturn = null;
                    if (response.getEntity() != null) {
                        deploymentReturn = (new StreamToJsonConverter<>(DeploymentReturn.class)).asJson(EntityUtils.toString(response.getEntity()));
                    }
                    if (deploymentReturn != null && FINISHED.equalsIgnoreCase(deploymentReturn.status)) {
                        return true;
                    } else if (deploymentReturn == null) {
                        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());
                    } else {
                        return false;
                    }
                } else {
                    throw new HdiDeploymentCommunicationProblem(format(DEPLOYMENT_ENDPOINT_RETURNED, responseCode).getMessage());
                }
            }
        } catch (IOException e) {
            logger.error(DEPLOYMENT_RETURNED_AN_ERROR, e);
            throw new HdiDeploymentCommunicationProblem(DEPLOYMENT_RETURNED_AN_ERROR, e);
        }
    }

    protected String getBody(DataSourceInfo dataSourceInfo, List userProvidedSchemas) throws InternalError {

        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 (userProvidedSchemas != null && userProvidedSchemas.size() > 0) {
            UserProvidedSchema[] userProvidedSchemaArray = new UserProvidedSchema[userProvidedSchemas.size()];
            userProvidedSchemas.toArray(userProvidedSchemaArray);
            payload.ADDITIONAL_VCAP_SERVICES.user_provided = userProvidedSchemaArray;
        }
        payload.TARGET_CONTAINER = TARGET_CONTAINER_NAME;
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            return objectMapper.writeValueAsString(payload);
        } catch (JsonProcessingException e) {
            logger.error(COULD_NOT_CREATE_DEPLOYER_PAYLOAD, e);
            throw new InternalError(e);
        }
    }

    //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 String getBodyWithInstanceManagerBasedPayload(DataSourceInfo dataSourceInfo) throws InternalError {
        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();
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            return objectMapper.writeValueAsString(payload);
        } catch (JsonProcessingException e) {
            logger.error(COULD_NOT_CREATE_DEPLOYER_PAYLOAD, e);
            throw new InternalError(COULD_NOT_CREATE_DEPLOYER_PAYLOAD, e);
        }
    }

    private String getAuthHeader() {
        String authorization = user + ":" + password;
        byte[] asBase64 = Base64.encodeBase64(authorization.getBytes(StandardCharsets.ISO_8859_1));
        return "Basic " + new String(asBase64);
    }

    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);
                });
    }

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

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy