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.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