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

com.sap.cloud.mt.subscription.SubscriberSidecarImpl 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.sap.cloud.mt.subscription.exceptions.InternalError;
import com.sap.cloud.mt.subscription.exceptions.*;
import com.sap.cloud.mt.subscription.exits.Exits;
import com.sap.cloud.mt.subscription.json.*;
import com.sap.xsa.core.instancemanager.client.InstanceCreationOptions;
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.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

public class SubscriberSidecarImpl extends AbstractSubscriber {
    private static final String SIDECAR_PROVISIONING_ENDPOINT = "/mtx/v1/provisioning/tenant/";
    private static final String SIDECAR_UPGRADE_ENDPOINT = "/mtx/v1/model/asyncUpgrade";
    private static final String SIDECAR_STATUS_ENDPOINT = "/mtx/v1/model/status/";
    private static final Logger logger = LoggerFactory.getLogger(SubscriberSidecarImpl.class);
    private static final String APPLICATION_JSON = "application/json";
    private static final String STATUS_CALLBACK = "STATUS_CALLBACK";
    private static final String MT_LIB_CALLBACK_URL = "MTX_STATUS_CALLBACK";
    private final Exits exits;
    private final String sidecarUrl;
    private final String baseUiUrl;
    private final String urlSeparator;
    private final SecurityChecker securityChecker;
    private final SaasRegistry saasRegistry;

    SubscriberSidecarImpl(String sidecarUrl,
                          String baseUiUrl,
                          String urlSeparator,
                          Exits exits,
                          SecurityChecker securityChecker, SaasRegistry saasRegistry) throws InternalError {
        this.sidecarUrl = sidecarUrl;
        this.exits = exits;
        this.baseUiUrl = baseUiUrl;
        this.urlSeparator = urlSeparator;
        this.saasRegistry = saasRegistry;
        if (exits.getUnSubscribeExit() == null) throw new InternalError("No unsubscribe exit found");
        this.securityChecker = securityChecker;
    }

    @Override
    public void unsubscribe(String tenantId, DeletePayload deletePayload, String jwt) throws InternalError, UnknownTenant, ParameterError, AuthorityError {
        unsubscribe(tenantId, deletePayload, jwt, null, null);
    }

    @Override
    public void unsubscribe(String tenantId, DeletePayload deletePayload, String jwt, String saasRegistryUrl,
                            String asyncUnsubscribeCallBackUrl) throws InternalError, UnknownTenant, ParameterError, AuthorityError {
        boolean asyncCall = isAsyncCall(saasRegistryUrl);
        securityChecker.checkSubscriptionAuthority();
        Tools.checkTenantId(tenantId);
        boolean processUnsubscribe = false;
        if (asyncCall) {
            processUnsubscribe = exits.getUnSubscribeExit().onBeforeAsyncUnsubscribe(tenantId, Cloner.clone(deletePayload));
        } else {
            processUnsubscribe = exits.getUnSubscribeExit().onBeforeUnsubscribe(tenantId, Cloner.clone(deletePayload));
        }
        if (processUnsubscribe) {
            if (asyncCall) {
                long startTime = System.nanoTime();
                try {
                    unsubscribeViaSidecar(tenantId, jwt, deletePayload, true, saasRegistryUrl, asyncUnsubscribeCallBackUrl);
                } catch (InternalError e) {
                    waitSomeTime(startTime);
                    saasRegistry.callBackSaasRegistry(false, e.getMessage(), null, saasRegistryUrl);
                    throw e;
                }
            } else {
                unsubscribeViaSidecar(tenantId, jwt, deletePayload, false, null, asyncUnsubscribeCallBackUrl);
                exits.getUnSubscribeExit().onAfterUnsubscribe(tenantId, Cloner.clone(deletePayload));
            }
        } else {
            logger.debug("Unsubscribe exit returned false=> No un-subscription performed");
            throw new InternalError("Unsubscribe exit returned false=> No unsubscription performed");
        }
    }

    private boolean isAsyncCall(String saasRegistryUrl) {
        return saasRegistryUrl != null && !saasRegistryUrl.isEmpty();
    }

    @Override
    public void unsubscribeCallback(SidecarUnSubscribeCallBackPayload sidecarPayload, String jwt) throws AuthorityError, InternalError {
        securityChecker.checkSubscriptionAuthority();
        boolean success = sidecarPayload.status.equals(SaasRegistry.SUCCEEDED);
        if (success) {
            exits.getUnSubscribeExit().onAfterAsyncUnsubscribe(sidecarPayload.tenantId, Cloner.clone(sidecarPayload.saasRequestPayload));
        } else {
            logger.error(sidecarPayload.message);
        }
        try {
            saasRegistry.callBackSaasRegistry(success, sidecarPayload.message, null, sidecarPayload.saasCallbackUrl);
        } catch (InternalError e) {
            logger.error(e.getMessage());
            try {
                saasRegistry.callBackSaasRegistry(false, e.getMessage(), null, sidecarPayload.saasCallbackUrl);
            } catch (InternalError internalError) {
                logger.error(internalError.getMessage());
            }
        }
    }

    @Override
    public List getApplicationDependencies(String jwt) throws AuthorityError {
        securityChecker.checkSubscriptionAuthority();
        return exits.getDependencyExit() != null ? exits.getDependencyExit().onGetDependencies() : new ArrayList<>();
    }

    @Override
    public String subscribe(String tenantId, SubscriptionPayload subscriptionPayload, String jwt) throws InternalError, ParameterError, AuthorityError {
        return subscribe(tenantId, subscriptionPayload, jwt, null, null);
    }

    @Override
    public String subscribe(String tenantId, SubscriptionPayload subscriptionPayload, String jwt, String saasRegistryUrl,
                            String asyncSubscribeCallBackUrl) throws InternalError, ParameterError, AuthorityError {
        boolean asyncCall = isAsyncCall(saasRegistryUrl);
        securityChecker.checkSubscriptionAuthority();
        Tools.checkTenantId(tenantId);
        // for future use, currently not supported by sidecar
        InstanceCreationOptions instanceCreationOptions = null;
        if (asyncCall) {
            instanceCreationOptions = exits.getSubscribeExit().onBeforeAsyncSubscribe(tenantId, Cloner.clone(subscriptionPayload));
        } else {
            instanceCreationOptions = exits.getSubscribeExit().onBeforeSubscribe(tenantId, Cloner.clone(subscriptionPayload));
        }
        //get application URL for CIS from exit
        String applicationUrl = exits.getSubscribeExit().uiURL() != null ? exits.getSubscribeExit().uiURL().toExternalForm() : null;
        //build URL from properties if not set by exit
        if (applicationUrl == null) applicationUrl = UiUrlCreator.createUrl(subscriptionPayload.subscribedSubdomain, baseUiUrl, urlSeparator);
        if (asyncCall) {
            long startTime = System.nanoTime();
            try {
                subscribeViaSidecar(tenantId, jwt, subscriptionPayload, instanceCreationOptions, applicationUrl, true, saasRegistryUrl,
                        asyncSubscribeCallBackUrl);
            } catch (InternalError e) {
                exits.getSubscribeExit().onAfterAsyncSubscribe(tenantId, Cloner.clone(subscriptionPayload), false);
                waitSomeTime(startTime);
                saasRegistry.callBackSaasRegistry(false, e.getMessage(), null, saasRegistryUrl);
                throw e;
            }
        } else {
            try {
                subscribeViaSidecar(tenantId, jwt, subscriptionPayload, instanceCreationOptions, applicationUrl, false, null,
                        asyncSubscribeCallBackUrl);
                exits.getSubscribeExit().onAfterSubscribe(tenantId, Cloner.clone(subscriptionPayload), true);
            } catch (InternalError internalError) {
                exits.getSubscribeExit().onAfterSubscribe(tenantId, Cloner.clone(subscriptionPayload), false);
                throw internalError;
            }
        }
        return applicationUrl;
    }

    @Override
    public void subscribeCallback(String jwt, SidecarSubscribeCallBackPayload sidecarPayload) throws AuthorityError, InternalError {
        securityChecker.checkSubscriptionAuthority();
        boolean success = sidecarPayload.status.equals(SaasRegistry.SUCCEEDED);
        exits.getSubscribeExit().onAfterAsyncSubscribe(sidecarPayload.tenantId, Cloner.clone(sidecarPayload.saasRequestPayload), success);
        if (!success) {
            logger.error(sidecarPayload.message);
        }
        //be careful: sidecarPayload.subscriptionUrl contains the url determined by sidecar and not the url determined  by us. It will be empty.
        //Our url is put into the subscription payload extended by us, which is returned as sidecarPayload.saasRequestPayload
        saasRegistry.callBackSaasRegistry(success, sidecarPayload.message, sidecarPayload.saasRequestPayload._applicationUrlFromJava_,
                sidecarPayload.saasCallbackUrl);
    }

    @Override
    public void setupDbTables(List tenants, String jwt) throws InternalError, NotSupported, AuthorityError {
        securityChecker.checkInitDbAuthority();
        logger.debug("Synchronous init db is not supported with sidecar");
        throw new NotSupported("Synchronous init db is not supported with sidecar");
    }

    @Override
    public String setupDbTablesAsync(List tenants, String jwt) throws InternalError, AuthorityError {
        securityChecker.checkInitDbAuthority();
        SidecarUpgradePayload upgradePayload = new SidecarUpgradePayload();
        upgradePayload.tenants = tenants.toArray(new String[tenants.size()]);
        try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) {
            HttpUriRequest postRequest = RequestBuilder.post(new URL(sidecarUrl + SIDECAR_UPGRADE_ENDPOINT).toURI())
                    .setHeader(HttpHeaders.AUTHORIZATION, jwt)
                    .setHeader(HttpHeaders.ACCEPT, APPLICATION_JSON)
                    .setHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON)
                    .setEntity(new StringEntity(convertPayloadToString(upgradePayload)))
                    .build();
            try (CloseableHttpResponse response = httpClient.execute(postRequest)) {
                int responseCode = response.getStatusLine().getStatusCode();
                if (responseCode != HttpStatus.SC_OK) {
                    logger.error("Sidecar returned http status {} ", responseCode);
                    throw new InternalError("Sidecar returned http status " + responseCode);
                }
                try {
                    if (response.getEntity() == null) {
                        return "";
                    }
                    return EntityUtils.toString(response.getEntity());
                } catch (IOException e) {
                    logger.error("Cannot access return value from sidecar. Error is {}", e.getMessage());
                    throw new InternalError("Cannot access return value from sidecar.", e);
                }
            }
        } catch (URISyntaxException | MalformedURLException e) {
            throw new InternalError(e);
        } catch (IOException e) {
            logger.error(e.getMessage());
            throw new InternalError(e);
        }
    }


    @Override
    public String updateStatus(String jobId, String jwt) throws InternalError, ParameterError, NotFound, AuthorityError {
        securityChecker.checkInitDbAuthority();
        if (!jobId.matches(Tools.SECURE_CHARS)) {
            throw new ParameterError("Job id contains illegal characters");
        }
        try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) {
            HttpUriRequest getRequest = RequestBuilder.get(new URL(sidecarUrl + SIDECAR_STATUS_ENDPOINT + jobId).toURI())
                    .setHeader(HttpHeaders.AUTHORIZATION, jwt)
                    .setHeader(HttpHeaders.ACCEPT, APPLICATION_JSON)
                    .build();
            try (CloseableHttpResponse response = httpClient.execute(getRequest)) {
                int responseCode = response.getStatusLine().getStatusCode();
                if (responseCode == HttpStatus.SC_NOT_FOUND) {
                    throw new NotFound("Job id not known");
                }
                if (responseCode != HttpStatus.SC_OK) {
                    throw new InternalError("Sidecar returned http status " + responseCode);
                }
                if (response.getEntity() == null) {
                    return "";
                }
                try {
                    return EntityUtils.toString(response.getEntity());
                } catch (IOException e) {
                    throw new InternalError("Could not access return message from sidecar", e);
                }
            }
        } catch (URISyntaxException | MalformedURLException e) {
            throw new InternalError(e);
        } catch (IOException e) {
            logger.error(e.getMessage());
            throw new InternalError(e);
        }
    }

    private String convertPayloadToString(Object payload) throws JsonProcessingException {
        String body = "{}";
        if (payload != null) {
            body = Tools.getObjectMapper().writeValueAsString(payload);
        }
        return body;
    }

    private SidecarSubscriptionPayload getSubscriptionPayloadForSidecar(SubscriptionPayload subscriptionPayload, InstanceCreationOptions instanceCreationOptions) {
        SidecarSubscriptionPayload payload = new SidecarSubscriptionPayload(subscriptionPayload);
        boolean hasProvisioningParameters = false;
        boolean hasBindingParameters = false;
        if (instanceCreationOptions != null) {
            if (instanceCreationOptions.getProvisioningParameters() != null && instanceCreationOptions.getProvisioningParameters().size() > 0) {
                hasProvisioningParameters = true;
            }
            if (instanceCreationOptions.getBindingParameters() != null && instanceCreationOptions.getBindingParameters().size() > 0) {
                hasBindingParameters = true;
            }
        }
        if (hasProvisioningParameters || hasBindingParameters) {
            payload._application_ = new ApplicationForSidecar();
            payload._application_.sap = new SAPForSidecar();
            payload._application_.sap.managedHana = new ManagedHanaForSidecar();
            if (hasProvisioningParameters) {
                payload._application_.sap.managedHana.provisioning_parameters = instanceCreationOptions.getProvisioningParameters();
            }
            if (hasBindingParameters) {
                payload._application_.sap.managedHana.binding_parameters = instanceCreationOptions.getBindingParameters();
            }
        }
        return payload;
    }

    private void subscribeViaSidecar(String tenantId, String jwt, SubscriptionPayload subscriptionPayload,
                                     InstanceCreationOptions instanceCreationOptions, String applicationUrl,
                                     boolean asyncCall, String saasRegistryUrl,
                                     String asyncSubscribeCallBackUrl) throws InternalError {
        try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) {
            SidecarSubscriptionPayload sidecarPayload = getSubscriptionPayloadForSidecar(subscriptionPayload, instanceCreationOptions);
            sidecarPayload._applicationUrlFromJava_ = applicationUrl;
            RequestBuilder buildPutRequest = RequestBuilder.put(new URL(sidecarUrl + SIDECAR_PROVISIONING_ENDPOINT + tenantId).toURI())
                    .setHeader(HttpHeaders.AUTHORIZATION, jwt)
                    .setHeader(HttpHeaders.ACCEPT, APPLICATION_JSON)
                    .setHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON)
                    .setEntity(new StringEntity(convertPayloadToString(sidecarPayload)));
            if (asyncCall) {
                buildPutRequest.setHeader(MT_LIB_CALLBACK_URL, asyncSubscribeCallBackUrl);
                buildPutRequest.setHeader(STATUS_CALLBACK, saasRegistryUrl);
            }
            HttpUriRequest putRequest = buildPutRequest.build();
            try (CloseableHttpResponse response = httpClient.execute(putRequest)) {
                int responseCode = response.getStatusLine().getStatusCode();
                if (responseCode != HttpStatus.SC_CREATED && responseCode != HttpStatus.SC_OK && responseCode != HttpStatus.SC_ACCEPTED) {
                    logger.error("Sidecar returned http status {} ", responseCode);
                    throw new InternalError("Sidecar returned http status " + responseCode);
                }
            }
        } catch (URISyntaxException | IOException e) {
            logger.error(e.getMessage());
            exits.getSubscribeExit().onAfterSubscribe(tenantId, Cloner.clone(subscriptionPayload), false);
            throw new InternalError(e);
        }
    }

    private void unsubscribeViaSidecar(String tenantId, String jwt, DeletePayload deletePayloadCopy, boolean asyncCall,
                                       String saasRegistryUrl,
                                       String asyncUnsubscribeCallBackUrl) throws InternalError {
        try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) {
            SidecarUnSubscriptionPayload sidecarPayload = new SidecarUnSubscriptionPayload(deletePayloadCopy);
            RequestBuilder buildDelRequest = RequestBuilder.delete(new URL(sidecarUrl + SIDECAR_PROVISIONING_ENDPOINT + tenantId).toURI())
                    .setHeader(HttpHeaders.AUTHORIZATION, jwt)
                    .setHeader(HttpHeaders.ACCEPT, APPLICATION_JSON)
                    .setHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON);
            if (asyncCall) {
                buildDelRequest.setHeader(MT_LIB_CALLBACK_URL, asyncUnsubscribeCallBackUrl);
                buildDelRequest.setHeader(STATUS_CALLBACK, saasRegistryUrl);
                buildDelRequest.setEntity(new StringEntity(convertPayloadToString(sidecarPayload)));
                ;
            }
            HttpUriRequest delRequest = buildDelRequest.build();
            try (CloseableHttpResponse response = httpClient.execute(delRequest)) {
                int responseCode = response.getStatusLine().getStatusCode();
                if (responseCode != HttpStatus.SC_ACCEPTED && responseCode != HttpStatus.SC_NO_CONTENT) {
                    throw new InternalError("Sidecar returned http status " + responseCode);
                }
            }
        } catch (URISyntaxException | MalformedURLException e) {
            logger.error("Url cannot be constructed. Error is {}", e.getMessage());
            throw new InternalError(e);
        } catch (IOException e) {
            logger.error(e.getMessage());
            throw new InternalError(e);
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy