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
/*******************************************************************************
 *   © 2019-2024 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