diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/clients/CustomServiceKeysClient.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/clients/CustomServiceKeysClient.java index 733843fd25..6a51e401e6 100644 --- a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/clients/CustomServiceKeysClient.java +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/clients/CustomServiceKeysClient.java @@ -3,8 +3,10 @@ import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.function.Function; +import java.util.Objects; +import java.util.UUID; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceType; import org.cloudfoundry.multiapps.controller.client.facade.CloudCredentials; @@ -30,7 +32,8 @@ public CustomServiceKeysClient(ApplicationConfiguration configuration, WebClient } public List getServiceKeysByMetadataAndGuids(String spaceGuid, String mtaId, String mtaNamespace, - List services) { + List services, + List existingServiceGuids) { String labelSelector = MtaMetadataCriteriaBuilder.builder() .label(MtaMetadataLabels.SPACE_GUID) .hasValue(spaceGuid) @@ -43,31 +46,40 @@ public List getServiceKeysByMetadataAndGuids(String space .build() .get(); + List managedGuids = extractManagedServiceGuids(services); + + List allServiceGuids = Stream.concat(managedGuids.stream(), existingServiceGuids.stream()) + .filter(Objects::nonNull) + .toList(); + + if (allServiceGuids.isEmpty()) { + return List.of(); + } return new CustomControllerClientErrorHandler().handleErrorsOrReturnResult( - () -> getServiceKeysByMetadataInternal(labelSelector, services)); + () -> getServiceKeysByMetadataInternal(labelSelector, allServiceGuids)); } - private List getServiceKeysByMetadataInternal(String labelSelector, List services) { - String uriSuffix = INCLUDE_SERVICE_INSTANCE_RESOURCES_PARAM; - List managedServices = getManagedServices(services); - if (managedServices != null) { - uriSuffix += "&service_instance_guids=" + managedServices.stream() - .map(service -> service.getGuid() - .toString()) - .collect(Collectors.joining(",")); - } - return getListOfResources(new ServiceKeysResponseMapper(managedServices), SERVICE_KEYS_BY_METADATA_SELECTOR_URI + uriSuffix, + private List extractManagedServiceGuids(List services) { + return getManagedServices(services).stream() + .map(DeployedMtaService::getGuid) + .map(UUID::toString) + .toList(); + } + + private List getServiceKeysByMetadataInternal(String labelSelector, List guids) { + + String uriSuffix = INCLUDE_SERVICE_INSTANCE_RESOURCES_PARAM + + "&service_instance_guids=" + String.join(",", guids); + + return getListOfResources(new ServiceKeysResponseMapper(), + SERVICE_KEYS_BY_METADATA_SELECTOR_URI + uriSuffix, labelSelector); } private List getManagedServices(List services) { - if (services == null) { - return null; - } - List managedServices = services.stream() - .filter(this::serviceIsNotUserProvided) - .collect(Collectors.toList()); - return managedServices.isEmpty() ? null : managedServices; + return services.stream() + .filter(this::serviceIsNotUserProvided) + .toList(); } private boolean serviceIsNotUserProvided(DeployedMtaService service) { @@ -76,23 +88,12 @@ private boolean serviceIsNotUserProvided(DeployedMtaService service) { } protected class ServiceKeysResponseMapper extends ResourcesResponseMapper { - - List mtaServices; - - public ServiceKeysResponseMapper(List mtaServices) { - this.mtaServices = mtaServices; + public ServiceKeysResponseMapper() { } @Override public List getMappedResources() { - Map serviceMapping; - if (mtaServices != null) { - serviceMapping = mtaServices.stream() - .collect(Collectors.toMap(service -> service.getGuid() - .toString(), Function.identity())); - } else { - serviceMapping = getIncludedServiceInstancesMapping(); - } + Map serviceMapping = getIncludedServiceInstancesMapping(); return getQueriedResources().stream() .map(resource -> resourceMapper.mapServiceKeyResource(resource, serviceMapping)) .collect(Collectors.toList()); diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/Messages.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/Messages.java index a2f1261b98..7597d33d0d 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/Messages.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/Messages.java @@ -789,6 +789,7 @@ public class Messages { public static final String GETTING_FEATURES_FOR_APPLICATION_0 = "Getting features for application \"{0}\""; public static final String TOTAL_SIZE_OF_ALL_RESOLVED_CONTENT_0 = "Total size for all resolved content {0}"; + public static final String IGNORING_NOT_FOUND_OPTIONAL_OR_INACTIVE_SERVICE = "Service {0} not found but is optional or inactive"; // Not log messages public static final String SERVICE_TYPE = "{0}/{1}"; diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/BuildCloudUndeployModelStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/BuildCloudUndeployModelStep.java index 75c29b4619..50641076be 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/BuildCloudUndeployModelStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/BuildCloudUndeployModelStep.java @@ -65,9 +65,17 @@ protected StepPhase executeStep(ProcessContext context) { getStepLogger().debug(Messages.BUILDING_CLOUD_UNDEPLOY_MODEL); DeployedMta deployedMta = context.getVariable(Variables.DEPLOYED_MTA); + List serviceKeysToDelete = computeServiceKeysToDelete(context); + getStepLogger().debug(Messages.SERVICE_KEYS_FOR_DELETION, serviceKeysToDelete); + if (deployedMta == null) { setComponentsToUndeploy(context, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); + + if (!serviceKeysToDelete.isEmpty()) { + context.setVariable(Variables.SERVICE_KEYS_TO_DELETE, + getServiceKeysToDelete(context, serviceKeysToDelete)); + } return StepPhase.DONE; } @@ -98,9 +106,6 @@ protected StepPhase executeStep(ProcessContext context) { 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()); DeployedMta backupMta = context.getVariable(Variables.BACKUP_MTA); diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetectDeployedMtaStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetectDeployedMtaStep.java index 09711ac4c2..81b348242e 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetectDeployedMtaStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetectDeployedMtaStep.java @@ -2,6 +2,7 @@ import java.text.MessageFormat; import java.util.List; +import java.util.Objects; import java.util.Optional; import jakarta.inject.Inject; @@ -9,19 +10,25 @@ import org.apache.commons.lang3.StringUtils; import org.cloudfoundry.multiapps.controller.client.facade.CloudControllerClient; import org.cloudfoundry.multiapps.controller.client.facade.CloudCredentials; +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; import org.cloudfoundry.multiapps.controller.core.cf.clients.CustomServiceKeysClient; import org.cloudfoundry.multiapps.controller.core.cf.clients.WebClientFactory; import org.cloudfoundry.multiapps.controller.core.cf.detect.DeployedMtaDetector; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadata; +import org.cloudfoundry.multiapps.controller.core.cf.v2.ResourceType; import org.cloudfoundry.multiapps.controller.core.model.DeployedMta; import org.cloudfoundry.multiapps.controller.core.model.DeployedMtaService; import org.cloudfoundry.multiapps.controller.core.model.DeployedMtaServiceKey; import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerialization; import org.cloudfoundry.multiapps.controller.core.security.token.TokenService; +import org.cloudfoundry.multiapps.controller.core.util.CloudModelBuilderUtil; import org.cloudfoundry.multiapps.controller.core.util.NameUtil; import org.cloudfoundry.multiapps.controller.process.Constants; import org.cloudfoundry.multiapps.controller.process.Messages; import org.cloudfoundry.multiapps.controller.process.variables.Variables; +import org.cloudfoundry.multiapps.mta.model.DeploymentDescriptor; +import org.cloudfoundry.multiapps.mta.model.Resource; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Scope; @@ -94,7 +101,9 @@ private void detectBackupMta(String mtaId, String mtaNamespace, CloudControllerC private List detectDeployedServiceKeys(String mtaId, String mtaNamespace, DeployedMta deployedMta, ProcessContext context) { - List deployedMtaServices = deployedMta == null ? null : deployedMta.getServices(); + List deployedManagedMtaServices = Optional.ofNullable(deployedMta) + .map(DeployedMta::getServices) + .orElse(List.of()); String spaceGuid = context.getVariable(Variables.SPACE_GUID); String user = context.getVariable(Variables.USER); String userGuid = context.getVariable(Variables.USER_GUID); @@ -102,7 +111,52 @@ private List detectDeployedServiceKeys(String mtaId, Stri var creds = new CloudCredentials(token, true); CustomServiceKeysClient serviceKeysClient = getCustomServiceKeysClient(creds, context.getVariable(Variables.CORRELATION_ID)); - return serviceKeysClient.getServiceKeysByMetadataAndGuids(spaceGuid, mtaId, mtaNamespace, deployedMtaServices); + + List existingInstanceGuids = getExistingServiceGuids(context); + + return serviceKeysClient.getServiceKeysByMetadataAndGuids( + spaceGuid, mtaId, mtaNamespace, deployedManagedMtaServices, existingInstanceGuids + ); + } + + private List getExistingServiceGuids(ProcessContext context) { + CloudControllerClient client = context.getControllerClient(); + List resources = getExistingServiceResourcesFromDescriptor(context); + + return resources.stream() + .map(resource -> resolveServiceGuid(client, resource)) + .filter(Objects::nonNull) + .toList(); + } + + private String resolveServiceGuid(CloudControllerClient client, Resource resource) { + try { + CloudServiceInstance instance = client.getServiceInstance(resource.getName()); + return instance.getGuid() + .toString(); + } catch (CloudOperationException e) { + if (isOptionalOrInactive(resource)) { + getStepLogger().debug(Messages.IGNORING_NOT_FOUND_OPTIONAL_OR_INACTIVE_SERVICE, resource.getName()); + return null; + } + throw e; + } + } + + private boolean isOptionalOrInactive(Resource resource) { + return resource.isOptional() || !resource.isActive(); + } + + private List getExistingServiceResourcesFromDescriptor(ProcessContext context) { + DeploymentDescriptor descriptor = context.getVariable(Variables.DEPLOYMENT_DESCRIPTOR); + + if (descriptor == null) { + return List.of(); + } + return descriptor.getResources() + .stream() + .filter(resource -> CloudModelBuilderUtil.getResourceType(resource) == ResourceType.EXISTING_SERVICE) + .toList(); } private void logNoMtaDeployedDetected(String mtaId, String mtaNamespace) { diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetectDeployedMtaStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetectDeployedMtaStepTest.java index 39faae73aa..bbb856972c 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetectDeployedMtaStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetectDeployedMtaStepTest.java @@ -6,7 +6,7 @@ import org.cloudfoundry.client.v3.Metadata; import org.cloudfoundry.multiapps.common.test.TestUtil; -import org.cloudfoundry.multiapps.common.test.Tester.Expectation; +import org.cloudfoundry.multiapps.common.test.Tester; import org.cloudfoundry.multiapps.common.util.JsonUtil; import org.cloudfoundry.multiapps.controller.client.facade.CloudControllerClient; import org.cloudfoundry.multiapps.controller.client.facade.CloudCredentials; @@ -31,6 +31,12 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.when; class DetectDeployedMtaStepTest extends SyncFlowableStepTest { @@ -62,19 +68,30 @@ void testExecuteWithDeployedMta() { .getKeys(); List deployedComponents = List.of(deployedMta); - when(deployedMtaDetector.detectDeployedMtas(Mockito.any(CloudControllerClient.class))).thenReturn(deployedComponents); - when(deployedMtaDetector.detectDeployedMtaByNameAndNamespace(Mockito.eq(MTA_ID), Mockito.eq(null), - Mockito.any( - CloudControllerClient.class))).thenReturn( - Optional.of(deployedMta)); - when(customClientMock.getServiceKeysByMetadataAndGuids(Mockito.eq(SPACE_GUID), Mockito.eq(MTA_ID), Mockito.isNull(), - Mockito.eq(deployedMta.getServices()))).thenReturn(deployedKeys); + when(deployedMtaDetector.detectDeployedMtas(any(CloudControllerClient.class))) + .thenReturn(deployedComponents); + when(deployedMtaDetector.detectDeployedMtaByNameAndNamespace(eq(MTA_ID), eq(null), + any(CloudControllerClient.class))) + .thenReturn(Optional.of(deployedMta)); + + when(customClientMock.getServiceKeysByMetadataAndGuids( + eq(SPACE_GUID), eq(MTA_ID), isNull(), + eq(deployedMta.getServices()), + argThat(List::isEmpty))) + .thenReturn(deployedKeys); + + when(customClientMock.getServiceKeysByMetadataAndGuids( + eq(SPACE_GUID), eq(MTA_ID), isNull(), + anyList(), + argThat(list -> list != null && !list.isEmpty()))) + .thenReturn(Collections.emptyList()); step.execute(execution); assertStepFinishedSuccessfully(); - tester.test(() -> context.getVariable(Variables.DEPLOYED_MTA), new Expectation(Expectation.Type.JSON, DEPLOYED_MTA_LOCATION)); + tester.test(() -> context.getVariable(Variables.DEPLOYED_MTA), + new Tester.Expectation(Tester.Expectation.Type.JSON, DEPLOYED_MTA_LOCATION)); assertEquals(deployedKeys, context.getVariable(Variables.DEPLOYED_MTA_SERVICE_KEYS)); } @@ -82,8 +99,16 @@ void testExecuteWithDeployedMta() { void testExecuteWithoutDeployedMta() { when(deployedMtaDetector.detectDeployedMtas(client)).thenReturn(Collections.emptyList()); when(deployedMtaDetector.detectDeployedMtaByNameAndNamespace(MTA_ID, null, client)).thenReturn(Optional.empty()); - when(customClientMock.getServiceKeysByMetadataAndGuids(SPACE_GUID, MTA_ID, null, - Collections.emptyList())).thenReturn(Collections.emptyList()); + when(customClientMock.getServiceKeysByMetadataAndGuids( + eq(SPACE_GUID), eq(MTA_ID), isNull(), + eq(Collections.emptyList()), + eq(Collections.emptyList()))) + .thenReturn(Collections.emptyList()); + + when(customClientMock.getServiceKeysByMetadataAndGuids( + anyString(), anyString(), isNull(), + anyList(), anyList())) + .thenReturn(Collections.emptyList()); step.execute(execution); @@ -130,10 +155,10 @@ void testExecuteWithBackupdMta() { .build()) .build(); - when(deployedMtaDetector.detectDeployedMtaByNameAndNamespace(Mockito.eq(MTA_ID), Mockito.eq(null), + when(deployedMtaDetector.detectDeployedMtaByNameAndNamespace(eq(MTA_ID), eq(null), Mockito.any())).thenReturn(Optional.of(deployedMta)); - when(deployedMtaDetector.detectDeployedMtaByNameAndNamespace(Mockito.eq(MTA_ID), - Mockito.eq(NameUtil.computeUserNamespaceWithSystemNamespace( + when(deployedMtaDetector.detectDeployedMtaByNameAndNamespace(eq(MTA_ID), + eq(NameUtil.computeUserNamespaceWithSystemNamespace( Constants.MTA_BACKUP_NAMESPACE, null)), Mockito.any())).thenReturn(Optional.of(backupMta));