com.sap.cloud.mt.subscription.HdiContainerManager Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of multi-tenant-subscription Show documentation
Show all versions of multi-tenant-subscription Show documentation
Spring Boot Enablement Parent
/******************************************************************************
* © 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));
}
}