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

org.cloudfoundry.multiapps.controller.process.steps.BuildCloudUndeployModelStep Maven / Gradle / Ivy

There is a newer version: 1.183.0
Show newest version
package org.cloudfoundry.multiapps.controller.process.steps;

import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.mapping;
import static java.util.stream.Collectors.toList;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import jakarta.inject.Inject;
import jakarta.inject.Named;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.ListUtils;
import org.cloudfoundry.multiapps.controller.api.model.ProcessType;
import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudServiceInstanceExtended;
import org.cloudfoundry.multiapps.controller.core.cf.v2.ApplicationCloudModelBuilder;
import org.cloudfoundry.multiapps.controller.core.helpers.ModuleToDeployHelper;
import org.cloudfoundry.multiapps.controller.core.model.DeployedMta;
import org.cloudfoundry.multiapps.controller.core.model.DeployedMtaApplication;
import org.cloudfoundry.multiapps.controller.core.model.DeployedMtaService;
import org.cloudfoundry.multiapps.controller.core.model.DeployedMtaServiceKey;
import org.cloudfoundry.multiapps.controller.core.model.SupportedParameters;
import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerialization;
import org.cloudfoundry.multiapps.controller.core.util.CloudModelBuilderUtil;
import org.cloudfoundry.multiapps.controller.persistence.model.ConfigurationSubscription;
import org.cloudfoundry.multiapps.controller.persistence.services.ConfigurationSubscriptionService;
import org.cloudfoundry.multiapps.controller.process.Messages;
import org.cloudfoundry.multiapps.controller.process.util.ProcessTypeParser;
import org.cloudfoundry.multiapps.controller.process.variables.Variables;
import org.cloudfoundry.multiapps.mta.model.DeploymentDescriptor;
import org.cloudfoundry.multiapps.mta.model.Module;
import org.cloudfoundry.multiapps.mta.model.Resource;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Scope;

import com.sap.cloudfoundry.client.facade.CloudControllerClient;
import com.sap.cloudfoundry.client.facade.domain.CloudApplication;
import com.sap.cloudfoundry.client.facade.domain.CloudServiceKey;

@Named("buildCloudUndeployModelStep")
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class BuildCloudUndeployModelStep extends SyncFlowableStep {

    @Inject
    private ConfigurationSubscriptionService configurationSubscriptionService;
    @Inject
    private ModuleToDeployHelper moduleToDeployHelper;
    @Inject
    private ProcessTypeParser processTypeParser;

    @Override
    protected StepPhase executeStep(ProcessContext context) {
        getStepLogger().debug(Messages.BUILDING_CLOUD_UNDEPLOY_MODEL);
        DeployedMta deployedMta = context.getVariable(Variables.DEPLOYED_MTA);

        if (deployedMta == null) {
            setComponentsToUndeploy(context, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(),
                                    Collections.emptyList());
            return StepPhase.DONE;
        }

        List deploymentDescriptorModules = getDeploymentDescriptorModules(context);
        List subscriptionsToCreate = context.getVariable(Variables.SUBSCRIPTIONS_TO_CREATE);
        Set mtaModules = context.getVariable(Variables.MTA_MODULES);
        List appNames = context.getVariable(Variables.APPS_TO_DEPLOY);
        List serviceNames = getAllServiceNames(context);

        getStepLogger().debug(Messages.MTA_MODULES, mtaModules);

        List deployedAppsToUndeploy = computeModulesToUndeploy(deployedMta, mtaModules, appNames,
                                                                                       deploymentDescriptorModules);
        getStepLogger().debug(Messages.MODULES_TO_UNDEPLOY, SecureSerialization.toJson(deployedAppsToUndeploy));

        List appsWithoutChange = computeModulesWithoutChange(deployedAppsToUndeploy, mtaModules, deployedMta);
        getStepLogger().debug(Messages.MODULES_NOT_TO_BE_CHANGED, SecureSerialization.toJson(appsWithoutChange));

        List subscriptionsToDelete = computeSubscriptionsToDelete(subscriptionsToCreate, deployedMta,
                                                                                             context.getVariable(Variables.SPACE_GUID));
        getStepLogger().debug(Messages.SUBSCRIPTIONS_TO_DELETE, SecureSerialization.toJson(subscriptionsToDelete));

        Set servicesForApplications = getServicesForApplications(context);
        List servicesToDelete = computeServicesToDelete(context, appsWithoutChange, deployedMta.getServices(),
                                                                servicesForApplications, serviceNames);
        getStepLogger().debug(Messages.SERVICES_TO_DELETE, servicesToDelete);

        List serviceKeysToDelete = computeServiceKeysToDelete(context);
        getStepLogger().debug(Messages.SERVICE_KEYS_FOR_DELETION, serviceKeysToDelete);

        List appsToUndeploy = computeAppsToUndeploy(deployedAppsToUndeploy, context.getControllerClient());
        getStepLogger().debug(Messages.APPS_TO_UNDEPLOY, SecureSerialization.toJson(appsToUndeploy));

        setComponentsToUndeploy(context, servicesToDelete, appsToUndeploy, subscriptionsToDelete, serviceKeysToDelete);

        getStepLogger().debug(Messages.CLOUD_UNDEPLOY_MODEL_BUILT);
        return StepPhase.DONE;
    }

    @Override
    protected String getStepErrorMessage(ProcessContext context) {
        return Messages.ERROR_BUILDING_CLOUD_UNDEPLOY_MODEL;
    }

    private List getDeploymentDescriptorModules(ProcessContext context) {
        DeploymentDescriptor deploymentDescriptor = context.getVariable(Variables.COMPLETE_DEPLOYMENT_DESCRIPTOR);
        if (deploymentDescriptor == null) {
            return Collections.emptyList();
        }
        return deploymentDescriptor.getModules()
                                   .stream()
                                   .map(Module::getName)
                                   .collect(Collectors.toList());
    }

    private List getAllServiceNames(ProcessContext context) {
        return context.getVariableBackwardsCompatible(Variables.BATCHES_TO_PROCESS)
                      .stream()
                      .flatMap(List::stream)
                      .map(CloudServiceInstanceExtended::getName)
                      .collect(Collectors.toList());
    }

    private Set getServicesForApplications(ProcessContext context) {
        List modules = context.getVariable(Variables.MODULES_TO_DEPLOY);
        if (CollectionUtils.isEmpty(modules)) {
            return Collections.emptySet();
        }
        Set servicesForApplications = new HashSet<>();
        ApplicationCloudModelBuilder applicationCloudModelBuilder = getApplicationCloudModelBuilder(context);
        for (Module module : modules) {
            if (moduleToDeployHelper.isApplication(module)) {
                servicesForApplications.addAll(applicationCloudModelBuilder.getAllApplicationServices(module));
            }
        }
        return servicesForApplications;
    }

    private List computeModulesWithoutChange(List modulesToUndeploy, Set mtaModules,
                                                                     DeployedMta deployedMta) {
        return deployedMta.getApplications()
                          .stream()
                          .filter(existingModule -> shouldNotUndeployModule(modulesToUndeploy, existingModule))
                          .filter(existingModule -> shouldNotDeployModule(mtaModules, existingModule))
                          .collect(Collectors.toList());
    }

    private boolean shouldNotUndeployModule(List modulesToUndeploy, DeployedMtaApplication existingModule) {
        String existingModuleName = existingModule.getModuleName();
        return modulesToUndeploy.stream()
                                .map(DeployedMtaApplication::getModuleName)
                                .noneMatch(existingModuleName::equals);
    }

    private boolean shouldNotDeployModule(Set mtaModules, DeployedMtaApplication existingModule) {
        String existingModuleName = existingModule.getModuleName();
        return !mtaModules.contains(existingModuleName);
    }

    private void setComponentsToUndeploy(ProcessContext context, List services, List apps,
                                         List subscriptions, List serviceKeys) {
        context.setVariable(Variables.SUBSCRIPTIONS_TO_DELETE, subscriptions);
        context.setVariable(Variables.SERVICES_TO_DELETE, services);
        context.setVariable(Variables.APPS_TO_UNDEPLOY, apps);
        context.setVariable(Variables.SERVICE_KEYS_TO_DELETE, serviceKeys);
    }

    private List computeServicesToDelete(ProcessContext context, List appsWithoutChange,
                                                 List deployedMtaServices, Set servicesForApplications,
                                                 List servicesForCurrentDeployment) {
        return deployedMtaServices.stream()
                                  .filter(service -> shouldDeleteService(context, service, appsWithoutChange, servicesForApplications,
                                                                         servicesForCurrentDeployment))
                                  .map(DeployedMtaService::getName)
                                  .sorted()
                                  .collect(Collectors.toList());
    }

    private boolean shouldDeleteService(ProcessContext context, DeployedMtaService service, List appsToKeep,
                                        Set servicesForApplications, List servicesForCurrentDeployment) {
        if (isExistingService(context, service.getName())) {
            // service, whose type was changed from "managed" to "existing"
            // flag to "delete" as we read that for DetachServicesFromMta step
            return true;
        }
        return appsToKeep.stream()
                         .flatMap(module -> module.getBoundMtaServices()
                                                  .stream())
                         .noneMatch(serviceName -> serviceName.equalsIgnoreCase(service.getName()))
            && !servicesForApplications.contains(service.getName()) && !servicesForCurrentDeployment.contains(service.getName());
    }

    private boolean isExistingService(ProcessContext context, String serviceName) {
        var deploymentDescriptor = context.getVariable(Variables.COMPLETE_DEPLOYMENT_DESCRIPTOR);
        // the process type is checked because the deployment descriptor is null during undeployment
        return !ProcessType.UNDEPLOY.equals(processTypeParser.getProcessType(context.getExecution()))
            && CloudModelBuilderUtil.isExistingService(deploymentDescriptor.getResources(), serviceName);
    }

    private List computeServiceKeysToDelete(ProcessContext context) {
        getStepLogger().debug(Messages.DETECTING_SERVICE_KEYS_FOR_DELETION);
        List deployedServiceKeys = context.getVariable(Variables.DEPLOYED_MTA_SERVICE_KEYS);

        if (deployedServiceKeys == null || deployedServiceKeys.isEmpty()) {
            getStepLogger().debug(Messages.NO_SERVICE_KEYS_FOR_DELETION);
            return Collections.emptyList();
        }

        if (ProcessType.UNDEPLOY.equals(processTypeParser.getProcessType(context.getExecution()))) {
            if (StepsUtil.canDeleteServiceKeys(context)) {
                return deployedServiceKeys;
            } else {
                getStepLogger().debug(Messages.WILL_NOT_DELETE_SERVICE_KEYS);
                return Collections.emptyList();
            }
        }

        return computeUnusedServiceKeys(context, deployedServiceKeys);

    }

    private List computeUnusedServiceKeys(ProcessContext context, List deployedServiceKeys) {
        Map> deployedServiceKeysByService = deployedServiceKeys.stream()
                                                                                                   .collect(groupingBy(key -> key.getServiceInstance()
                                                                                                                                 .getName()));
        getStepLogger().debug(Messages.DEPLOYED_SERVICE_KEYS, deployedServiceKeysByService);

        Map> additionalServiceKeys = context.getVariable(Variables.SERVICE_KEYS_FOR_CONTENT_DEPLOY);
        Map> serviceKeysToCreate = context.getVariable(Variables.SERVICE_KEYS_TO_CREATE);
        Map> newKeyNamesByService = mapKeysByServiceName(serviceKeysToCreate, additionalServiceKeys);
        getStepLogger().debug(Messages.NEW_SERVICE_KEYS, newKeyNamesByService);

        DeploymentDescriptor deploymentDescriptor = context.getVariable(Variables.COMPLETE_DEPLOYMENT_DESCRIPTOR);
        Map> existingKeysByService = getExistingServiceKeysByServiceName(deploymentDescriptor.getResources());
        getStepLogger().debug(Messages.EXISTING_SERVICE_KEYS_BY_SERVICE, existingKeysByService);

        List unusedKeysForAllServices = new ArrayList<>();

        for (Entry> deployedKeys : deployedServiceKeysByService.entrySet()) {
            String serviceName = deployedKeys.getKey();

            List newManagedKeys = newKeyNamesByService.getOrDefault(serviceName, Collections.emptyList());
            List existingKeys = existingKeysByService.getOrDefault(serviceName, Collections.emptyList());

            List unusedKeys = deployedKeys.getValue()
                                                                 .stream()
                                                                 .filter(deployedKey -> keyIsMissingInBothLists(deployedKey.getName(),
                                                                                                                newManagedKeys,
                                                                                                                existingKeys))
                                                                 .collect(toList());

            unusedKeysForAllServices.addAll(unusedKeys);
        }

        return unusedKeysForAllServices;
    }

    private boolean keyIsMissingInBothLists(String keyName, List firstList, List secondList) {
        return !(firstList.contains(keyName) || secondList.contains(keyName));
    }

    private Map> mapKeysByServiceName(Map> keysByResource,
                                                           Map> additionalKeysByResource) {
        Map> keyNamesMap = new HashMap<>();

        addKeysNamesToMap(keyNamesMap, keysByResource);
        addKeysNamesToMap(keyNamesMap, additionalKeysByResource);

        return keyNamesMap;
    }

    private void addKeysNamesToMap(Map> allKeysMap, Map> keysByResource) {
        if (keysByResource == null || keysByResource.isEmpty()) {
            return;
        }

        for (List keysForResource : keysByResource.values()) {
            if (!CollectionUtils.isEmpty(keysForResource)) {
                addServiceKeysToMap(keysForResource, allKeysMap);
            }
        }
    }

    private void addServiceKeysToMap(List keysForResource, Map> allKeysMappedByService) {
        String serviceName = keysForResource.get(0)
                                            .getServiceInstance()
                                            .getName();

        List keyNames = keysForResource.stream()
                                               .map(key -> key.getName())
                                               .collect(toList());

        allKeysMappedByService.merge(serviceName, keyNames, ListUtils::union);
    }

    private Map> getExistingServiceKeysByServiceName(List resources) {

        return resources.stream()
                        .filter(CloudModelBuilderUtil::isExistingServiceKey)
                        .collect(groupingBy(this::getServiceName, mapping(this::getServiceKeyName, toList())));
    }

    private String getServiceName(Resource resource) {
        return (String) resource.getParameters()
                                .get(SupportedParameters.SERVICE_NAME);
    }

    private String getServiceKeyName(Resource resource) {
        return (String) resource.getParameters()
                                .getOrDefault(SupportedParameters.SERVICE_KEY_NAME, resource.getName());
    }

    private List computeModulesToUndeploy(DeployedMta deployedMta, Set mtaModules,
                                                                  List appsToDeploy, List deploymentDescriptorModules) {
        return deployedMta.getApplications()
                          .stream()
                          .filter(deployedApplication -> shouldBeCheckedForUndeployment(deployedApplication, mtaModules,
                                                                                        deploymentDescriptorModules))
                          .filter(deployedApplication -> shouldUndeployModule(deployedApplication, appsToDeploy))
                          .collect(Collectors.toList());
    }

    private boolean shouldBeCheckedForUndeployment(DeployedMtaApplication deployedApplication, Set mtaModules,
                                                   List deploymentDescriptorModules) {
        return mtaModules.contains(deployedApplication.getModuleName())
            || !deploymentDescriptorModules.contains(deployedApplication.getModuleName());
    }

    private boolean shouldUndeployModule(DeployedMtaApplication deployedMtaApplication, List appsToDeploy) {
        // The deployed module may be in the list of MTA modules, but the actual application that was created from it may have a
        // different name:
        return !appsToDeploy.contains(deployedMtaApplication.getName());
    }

    private List computeAppsToUndeploy(List modulesToUndeploy, CloudControllerClient client) {
        return modulesToUndeploy.stream()
                                .map(appToUndeploy -> client.getApplication(appToUndeploy.getName(), false))
                                .filter(Objects::nonNull)
                                .collect(Collectors.toList());
    }

    private List computeSubscriptionsToDelete(List subscriptionsToCreate,
                                                                         DeployedMta deployedMta, String spaceId) {
        String mtaId = deployedMta.getMetadata()
                                  .getId();
        List existingSubscriptions = configurationSubscriptionService.createQuery()
                                                                                                .mtaId(mtaId)
                                                                                                .spaceId(spaceId)
                                                                                                .list();
        return existingSubscriptions.stream()
                                    .filter(subscription -> !willBeCreatedOrUpdated(subscription, subscriptionsToCreate))
                                    .collect(Collectors.toList());
    }

    private boolean willBeCreatedOrUpdated(ConfigurationSubscription existingSubscription,
                                           List createdOrUpdatedSubscriptions) {
        return createdOrUpdatedSubscriptions.stream()
                                            .anyMatch(subscription -> areEqual(subscription, existingSubscription));
    }

    private boolean areEqual(ConfigurationSubscription subscription1, ConfigurationSubscription subscription2) {
        return Objects.equals(subscription1.getAppName(), subscription2.getAppName())
            && Objects.equals(subscription1.getSpaceId(), subscription2.getSpaceId()) && Objects.equals(subscription1.getResourceDto()
                                                                                                                     .getName(),
                                                                                                        subscription2.getResourceDto()
                                                                                                                     .getName());
    }

    protected ApplicationCloudModelBuilder getApplicationCloudModelBuilder(ProcessContext context) {
        return StepsUtil.getApplicationCloudModelBuilder(context);
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy