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

com.sap.cloud.mt.subscription.SubscriberImpl 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.HanaEncryptionTool.DbEncryptionMode;
import com.sap.cloud.mt.subscription.InstanceLifecycleManager.ContainerStatus;
import com.sap.cloud.mt.subscription.exceptions.AuthorityError;
import com.sap.cloud.mt.subscription.exceptions.InternalError;
import com.sap.cloud.mt.subscription.exceptions.NotSupported;
import com.sap.cloud.mt.subscription.exceptions.ParameterError;
import com.sap.cloud.mt.subscription.exceptions.UnknownTenant;
import com.sap.cloud.mt.subscription.exits.AfterSubscribeMethod;
import com.sap.cloud.mt.subscription.exits.AfterUnSubscribeMethod;
import com.sap.cloud.mt.subscription.exits.Exits;
import com.sap.cloud.mt.subscription.json.ApplicationDependency;
import com.sap.cloud.mt.subscription.json.Cloner;
import com.sap.cloud.mt.subscription.json.DeletePayload;
import com.sap.cloud.mt.subscription.json.SubscriptionPayload;
import com.sap.cloud.mt.tools.api.ResilienceConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;


/**
 * Class Subscriber provides methods to subscribe new tenants, start DB artifact deployments and unsubscribe tenants.
 */
public class SubscriberImpl implements Subscriber {
	public static final String ALL_TENANTS = "all";
	private static final Logger logger = LoggerFactory.getLogger(SubscriberImpl.class);
	private final InstanceLifecycleManager instanceLifecycleManager;
	private final DbDeployer dbDeployer;
	private final Exits exits;
	private final String baseUiUrl;
	private final String urlSeparator;
	private final SecurityChecker securityChecker;
	private final SaasRegistry saasRegistry;
	private final boolean withoutAuthorityCheck;
	private final DbEncryptionMode hanaEncryptionMode;
	private final ResilienceConfig resilienceConfig;

	SubscriberImpl(InstanceLifecycleManager instanceLifecycleManager, DbDeployer dbDeployer,
				   String baseUiUrl,
				   String urlSeparator,
				   Exits exits,
				   SecurityChecker securityChecker,
				   SaasRegistry saasRegistry,
				   boolean withoutAuthorityCheck,
				   DbEncryptionMode hanaEncryptionMode,
				   ResilienceConfig resilienceConfig) throws InternalError {
		this.resilienceConfig = resilienceConfig != null ? resilienceConfig : ResilienceConfig.NONE;
		this.instanceLifecycleManager = instanceLifecycleManager;
		this.dbDeployer = dbDeployer;
		this.exits = exits;
		this.baseUiUrl = baseUiUrl;
		this.urlSeparator = urlSeparator;
		if (exits.getUnSubscribeExit() == null) throw new InternalError("No unsubscribe exit found");
		this.securityChecker = securityChecker;
		this.saasRegistry = saasRegistry;
		this.withoutAuthorityCheck = withoutAuthorityCheck;
		this.hanaEncryptionMode = hanaEncryptionMode;
	}

	@Override
	public void unsubscribe(String tenantId, DeletePayload deletePayload) throws InternalError, ParameterError, AuthorityError {
		if (!withoutAuthorityCheck) {
			securityChecker.checkSubscriptionAuthority();
		}
		Tools.checkExternalTenantId(tenantId);
		boolean processUnsubscribe = exits.getBeforeUnSubscribeMethod().call(tenantId, Cloner.clone(deletePayload));
		//The exit has to return true, otherwise nothing is deleted
		if (processUnsubscribe) {
			deleteInstance(tenantId, deletePayload, exits.getAfterUnSubscribeMethod());
		} else {
			logger.debug("Unsubscribe exit returned false=> No unsubscription performed");
		}
	}

	@Override
	public List getApplicationDependencies() throws AuthorityError {
		if (!withoutAuthorityCheck) {
			securityChecker.checkSubscriptionAuthority();
		}
		return exits.getDependencyExit().onGetDependencies();
	}

	@Override
	public String subscribe(String tenantId, SubscriptionPayload subscriptionPayload) throws InternalError, ParameterError, AuthorityError {
		if (!withoutAuthorityCheck) {
			securityChecker.checkSubscriptionAuthority();
		}
		Tools.checkExternalTenantId(tenantId);
		ServiceCreateOptions serviceCreateOptions = null;
		try {
			serviceCreateOptions = new ServiceCreateOptions(exits.getBeforeSubscribeMethod().call(tenantId, Cloner.clone(subscriptionPayload)));
		} catch (InternalError internalError) {
			exits.getAfterSubscribeMethod().call(tenantId, Cloner.clone(subscriptionPayload), false);
			throw internalError;
		}
		String subscriptionUrl = Tools.getApplicationUrl(subscriptionPayload, exits.getSubscribeExit()::uiURL, exits.getSubscribeExit()::uiURL, baseUiUrl, urlSeparator);
		var payloadAccess = SubscriptionPayloadAccess.create(subscriptionPayload.getMap());
		HanaEncryptionTool.addEncryptionParameter(serviceCreateOptions, hanaEncryptionMode, payloadAccess);
		createInstanceAndOnBoard(tenantId, subscriptionPayload, subscriptionUrl, serviceCreateOptions, exits.getAfterSubscribeMethod(), resilienceConfig);
		return subscriptionUrl;
	}

	@Override
	public String getSubscribeUrl(SubscriptionPayload subscriptionPayload) throws InternalError, ParameterError, AuthorityError {
		if (!withoutAuthorityCheck) {
			securityChecker.checkSubscriptionAuthority();
		}
		return Tools.getApplicationUrl(subscriptionPayload, exits.getSubscribeExit()::uiURL, exits.getSubscribeExit()::uiURL, baseUiUrl, urlSeparator);
	}

	@Override
	public void setupDbTables(List tenants) throws InternalError, ParameterError, AuthorityError {
		if (!withoutAuthorityCheck) {
			securityChecker.checkInitDbAuthority();
		}
		setupDbTablesInt(tenants);
	}

	@Override
	public String setupDbTablesAsync(List tenants) throws ParameterError, AuthorityError {
		if (!withoutAuthorityCheck) {
			securityChecker.checkInitDbAuthority();
		}
		for (String tenantId : tenants) {
			Tools.checkExternalTenantId(tenantId);
		}
		ExecutorService executor = null;
		try {
			executor = Executors.newSingleThreadExecutor();
			CompletableFuture.supplyAsync(() -> {
						try {
							setupDbTablesInt(tenants);
						} catch (InternalError internalError) {
							logger.error("Could not init DB asynchronously. Error is {}", internalError.getMessage());
						} catch (ParameterError | AuthorityError parameterError) {
							//cannot happen, tenant Id already checked
						}
						return "";
					}
					, executor);
		} finally {
			if (executor != null) executor.shutdown();
		}
		return "";
	}

	@Override
	public String updateStatus(String jobId) throws NotSupported, InternalError, AuthorityError {
		if (!withoutAuthorityCheck) {
			securityChecker.checkInitDbAuthority();
		}
		logger.debug("Update status is only supported with sidecar");
		throw new NotSupported("Update status is only supported with sidecar");
	}

	@Override
	public void callSaasRegistry(boolean ok, String message, String applicationUrl, String saasRegistryUrl) throws InternalError {
		saasRegistry.callBackSaasRegistry(ok, message, applicationUrl, saasRegistryUrl);
	}

	@Override
	public void checkAuthority(SecurityChecker.Authority authority) throws AuthorityError {
		securityChecker.checkAuthority(authority);
	}

	private void setupDbTablesInt(List tenants) throws InternalError, ParameterError, AuthorityError {
		for (String tenantId : tenants) {
			Tools.checkExternalTenantId(tenantId);
		}
		if (tenants.size() == 1 && tenants.get(0).equals(ALL_TENANTS)) {
			setupDbTables(new ArrayList<>(instanceLifecycleManager.getAllTenants(true)));
			return;
		}
		if (exits.getInitDbExit() != null) exits.getInitDbExit().onBeforeInitDb(tenants);
		final String[] message = {""};
		tenants.stream()
				.filter(FilterTenants.realTenants())
				.forEach(tenantId -> {
					try {
						DataSourceInfo dataSourceAndInfo = instanceLifecycleManager.getDataSourceInfo(tenantId, false);
						dbDeployer.populate(dataSourceAndInfo, tenantId);
					} catch (InternalError e) {
						if (message[0].isEmpty()) {
							message[0] = "Error in deployment:";
						}
						message[0] += "\n Could not perform deployment for tenant " + tenantId + " Error is:" + e.getMessage();
					} catch (UnknownTenant unknownTenant) {
						// ignore, seems to be deleted in the meantime
					}
				});
		if (exits.getInitDbExit() != null) exits.getInitDbExit().onAfterInitDb(message[0].isEmpty());
		if (!message[0].isEmpty()) throw new InternalError(message[0]);
	}

	private void deleteInstance(String tenantId, DeletePayload deletePayload, AfterUnSubscribeMethod afterUnSubscribeMethod)
			throws InternalError {
		instanceLifecycleManager.deleteInstance(tenantId);
		afterUnSubscribeMethod.call(tenantId, Cloner.clone(deletePayload));
	}

	private String createInstanceAndOnBoard(String tenantId, SubscriptionPayload subscriptionPayload,
											String url, ServiceCreateOptions serviceCreateOptions,
											AfterSubscribeMethod exit, ResilienceConfig resilienceConfig) throws InternalError {
		final AtomicReference instanceStatusRef = new AtomicReference<>(null);
		resilienceConfig.tryWhile(() -> {
			try {
				instanceStatusRef.set(instanceLifecycleManager.getContainerStatus(tenantId));
				return (instanceStatusRef.get() == ContainerStatus.CREATION_IN_PROGRESS)
						|| (instanceStatusRef.get() == ContainerStatus.OK && !instanceLifecycleManager.hasCredentials(tenantId, false));
			} catch (InternalError e) {
				logger.error("Could not get status for tenant %s".formatted(tenantId), e);
				return true;
			}
		});
		var instanceStatus = instanceStatusRef.get();
		if (instanceStatus == ContainerStatus.CREATION_ERROR) {
			logger.debug("Container for tenant {} has status CREATION_FAILED", tenantId);
			logger.debug("Delete container to fix problem");
			instanceLifecycleManager.deleteInstance(tenantId);
			instanceStatus = instanceLifecycleManager.getContainerStatus(tenantId);
		}
		logger.debug("Subscribe tenant {}", tenantId);
		if (instanceStatus == ContainerStatus.OK) {
			deploy(tenantId, subscriptionPayload, exit);
			return url;
		} else if (instanceStatus == ContainerStatus.DOES_NOT_EXIST) {
			logger.debug("Create new instance for tenant {}", tenantId);
			var provisioningParameters = new ProvisioningParameters(serviceCreateOptions.getProvisioningParameters());
			var bindingParameters = new BindingParameters(serviceCreateOptions.getBindingParameters());
			try {
				instanceLifecycleManager.createNewInstance(tenantId, provisioningParameters, bindingParameters);
			} catch (InternalError internalError) {
				exit.call(tenantId, Cloner.clone(subscriptionPayload), false);
				throw internalError;
			}
			deploy(tenantId, subscriptionPayload, exit);
			return url;
		} else {
			logger.error("Instance for tenant id {} has wrong status {}", tenantId, instanceStatus);
			exit.call(tenantId, Cloner.clone(subscriptionPayload), false);
			throw new InternalError("Instance has wrong status");
		}
	}

	private void deploy(String tenantId, SubscriptionPayload subscriptionPayload, AfterSubscribeMethod exit) throws InternalError {
		try {
			DataSourceInfo dataSourceInfo = instanceLifecycleManager.getDataSourceInfo(tenantId, false);
			logger.debug("Deploy to DB container for tenant {}", tenantId);
			dbDeployer.populate(dataSourceInfo, tenantId);
			exit.call(tenantId, Cloner.clone(subscriptionPayload), true);
		} catch (UnknownTenant unknownTenant) {
			// is actually impossible as the status was ok and cache is used
			logger.error("Tenant {} was deleted in parallel session", tenantId);
			exit.call(tenantId, Cloner.clone(subscriptionPayload), false);
			throw new InternalError("Tenant was deleted in parallel session");
		} catch (InternalError internalError) {
			logger.error("Could not deploy to DB container for tenant {}", tenantId);
			exit.call(tenantId, Cloner.clone(subscriptionPayload), false);
			throw internalError;
		}
	}

	public DbDeployer getDbDeployer() {
		return dbDeployer;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy