From 586fa0fe521a3e12cd86272a79018ccd814a17cd Mon Sep 17 00:00:00 2001 From: Krasimir Kargov Date: Wed, 5 Aug 2026 15:12:57 +0300 Subject: [PATCH 01/15] CF Java Client PoC migration LMCROSSITXSADEPLOY-3563 --- .../ResilientCloudControllerClient.java | 2 +- .../client/facade/CloudControllerClient.java | 2 +- .../facade/CloudControllerClientImpl.java | 2 +- .../facade/adapters/RawCloudApplication.java | 2 +- .../facade/adapters/RawCloudAsyncJob.java | 3 +- .../facade/adapters/RawCloudEntity.java | 31 + .../adapters/RawCloudServiceBinding.java | 2 +- .../adapters/RawCloudServiceInstance.java | 7 +- .../facade/adapters/RawCloudServiceKey.java | 4 +- .../facade/adapters/RawInstancesInfo.java | 3 +- .../client/facade/adapters/RawUserRole.java | 3 +- .../adapters/RawV3CloudServiceInstance.java | 2 +- .../client/facade/domain/CloudAsyncJob.java | 1 - .../client/facade/domain/CloudEntity.java | 2 +- .../facade/domain/CloudServiceInstance.java | 1 - .../client/facade/domain/HealthCheckType.java | 10 +- .../client/facade/domain/InstanceState.java | 10 +- .../client/facade/domain/JobState.java | 36 + .../client/facade/domain/LastOperation.java | 9 + .../client/facade/domain/Metadata.java | 122 +++ .../ServiceCredentialBindingOperation.java | 11 +- .../facade/domain/ServiceInstanceType.java | 40 + .../facade/domain/ServiceOperation.java | 10 +- .../client/facade/domain/UserRole.java | 12 +- .../facade/dto/ApplicationToCreateDto.java | 2 +- .../facade/rest/BuildsV3Operations.java | 64 ++ .../rest/CloudControllerRestClient.java | 2 +- .../CloudControllerRestClientBuilder.java | 128 +++ .../CloudControllerRestClientFactory.java | 96 ++ .../rest/CloudControllerRestClientImpl.java | 34 +- .../rest/CloudControllerRestClientV3Impl.java | 831 ++++++++++++++++++ .../facade/rest/CloudControllerV3Client.java | 191 ++++ .../facade/rest/DomainsV3Operations.java | 184 ++++ .../facade/rest/EventsV3Operations.java | 83 ++ .../client/facade/rest/JobsV3Operations.java | 31 + .../facade/rest/PackagesV3Operations.java | 212 +++++ .../facade/rest/ProcessesV3Operations.java | 237 +++++ .../client/facade/rest/RolesV3Operations.java | 46 + .../facade/rest/RoutesV3Operations.java | 362 ++++++++ .../rest/ServiceBindingsV3Operations.java | 269 ++++++ .../rest/ServiceBrokersV3Operations.java | 228 +++++ .../rest/ServiceInstancesV3Operations.java | 442 ++++++++++ .../facade/rest/ServiceKeysV3Operations.java | 188 ++++ .../rest/ServiceOfferingsV3Operations.java | 82 ++ .../facade/rest/ServicePlansV3Operations.java | 122 +++ .../facade/rest/StacksV3Operations.java | 63 ++ .../client/facade/rest/TasksV3Operations.java | 112 +++ .../facade/rest/resources/V3Application.java | 53 ++ .../rest/resources/V3ApplicationMapper.java | 59 ++ .../facade/rest/resources/V3AuditEvent.java | 30 + .../rest/resources/V3AuditEventMapper.java | 40 + .../client/facade/rest/resources/V3Build.java | 35 + .../facade/rest/resources/V3BuildMapper.java | 60 ++ .../facade/rest/resources/V3Domain.java | 45 + .../facade/rest/resources/V3DomainMapper.java | 22 + .../facade/rest/resources/V3Droplet.java | 24 + .../rest/resources/V3InstancesInfoMapper.java | 49 ++ .../client/facade/rest/resources/V3Job.java | 45 + .../facade/rest/resources/V3JobMapper.java | 57 ++ .../facade/rest/resources/V3ListResponse.java | 49 ++ .../facade/rest/resources/V3Metadata.java | 24 + .../facade/rest/resources/V3Package.java | 36 + .../rest/resources/V3PackageMapper.java | 76 ++ .../facade/rest/resources/V3Process.java | 71 ++ .../rest/resources/V3ProcessMapper.java | 60 ++ .../rest/resources/V3ResourceMappers.java | 73 ++ .../client/facade/rest/resources/V3Role.java | 22 + .../facade/rest/resources/V3RoleMapper.java | 18 + .../client/facade/rest/resources/V3Route.java | 57 ++ .../facade/rest/resources/V3RouteMapper.java | 104 +++ .../rest/resources/V3ServiceBinding.java | 46 + .../resources/V3ServiceBindingMapper.java | 71 ++ .../rest/resources/V3ServiceBroker.java | 35 + .../rest/resources/V3ServiceBrokerMapper.java | 40 + .../rest/resources/V3ServiceInstance.java | 47 + .../resources/V3ServiceInstanceMapper.java | 88 ++ .../rest/resources/V3ServiceKeyDetails.java | 28 + .../rest/resources/V3ServiceKeyMapper.java | 59 ++ .../rest/resources/V3ServiceOffering.java | 54 ++ .../resources/V3ServiceOfferingMapper.java | 64 ++ .../facade/rest/resources/V3ServicePlan.java | 43 + .../rest/resources/V3ServicePlanMapper.java | 55 ++ .../client/facade/rest/resources/V3Stack.java | 20 + .../facade/rest/resources/V3StackMapper.java | 23 + .../client/facade/rest/resources/V3Task.java | 28 + .../facade/rest/resources/V3TaskMapper.java | 45 + ...sCloudControllerClientIntegrationTest.java | 6 +- ...sCloudControllerClientIntegrationTest.java | 4 +- .../adapters/RawCloudApplicationTest.java | 2 +- .../facade/adapters/RawCloudAsyncJobTest.java | 2 +- .../facade/adapters/RawCloudEntityTest.java | 7 + .../adapters/RawCloudServiceBindingTest.java | 5 +- .../adapters/RawCloudServiceInstanceTest.java | 6 +- .../adapters/RawCloudServiceKeyTest.java | 7 +- .../facade/adapters/RawCloudServiceTest.java | 4 +- .../cf/clients/CloudEntityResourceMapper.java | 4 +- .../cf/clients/CustomServiceKeysClient.java | 2 +- .../metadata/processor/MtaMetadataParser.java | 2 +- .../cf/metadata/util/MtaMetadataUtil.java | 2 +- .../cf/v2/ApplicationMetadataBuilder.java | 2 +- .../core/cf/v2/MtaMetadataBuilder.java | 4 +- .../core/cf/v2/ServiceKeyMetadataBuilder.java | 4 +- .../cf/v2/ServiceKeysCloudModelBuilder.java | 2 +- .../core/cf/v2/ServiceMetadataBuilder.java | 4 +- .../core/cf/v2/ServicesCloudModelBuilder.java | 2 +- .../clients/CustomServiceKeysClientTest.java | 2 +- .../cf/detect/DeployedMtaDetectorTest.java | 2 +- .../client/LoggingCloudControllerClient.java | 2 +- .../BuildApplicationDeployModelStep.java | 2 +- .../steps/ClearMtaMetadataBaseStep.java | 2 +- ...ServiceCreateUpdateServiceActionsStep.java | 2 +- .../DetermineServiceKeysToRecreateStep.java | 2 +- .../PrepareApplicationForBackupStep.java | 2 +- .../steps/RemoveMtaBackupMetadataStep.java | 2 +- .../controller/process/steps/StepsUtil.java | 2 +- .../AsyncJobToAsyncExecutionStateAdapter.java | 2 +- .../util/OperationInFinalStateHandler.java | 10 +- .../LoggingCloudControllerClientTest.java | 2 +- .../process/steps/CreateServiceStepTest.java | 2 +- .../DeleteServiceMtaMetadataStepTest.java | 2 +- .../DetachServiceKeysFromMtaStepTest.java | 2 +- .../steps/DetachServicesFromMtaStepTest.java | 2 +- .../steps/DetectDeployedMtaStepTest.java | 2 +- ...iceCreateUpdateServiceActionsStepTest.java | 2 +- ...ServiceDeleteActionsToExecuteStepTest.java | 2 +- ...etermineServiceKeysToRecreateStepTest.java | 2 +- ...WithResolvedDynamicParametersStepTest.java | 2 +- ...lServiceBindingOperationExecutionTest.java | 2 +- ...lServiceBrokerOperationsExecutionTest.java | 2 +- ...ServiceBrokersOperationsExecutionTest.java | 2 +- ...viceKeyCreationOperationExecutionTest.java | 2 +- ...rviceUnbindingsOperationExecutionTest.java | 4 +- .../PrepareApplicationForBackupStepTest.java | 2 +- ...PrepareBackupMtaForDeploymentStepTest.java | 2 +- .../RemoveMtaBackupMetadataStepTest.java | 2 +- .../process/steps/StepsTestUtil.java | 2 +- .../process/steps/SyncFlowableStepTest.java | 2 +- .../UpdateServiceKeysMetadataStepTest.java | 2 +- .../ExistingAppsToBackupCalculatorTest.java | 2 +- .../process/util/ModuleDeterminerTest.java | 2 +- 140 files changed, 6221 insertions(+), 121 deletions(-) create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/JobState.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/LastOperation.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/Metadata.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/ServiceInstanceType.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3Operations.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientBuilder.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientV3Impl.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Client.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3Operations.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/EventsV3Operations.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/JobsV3Operations.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3Operations.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3Operations.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RolesV3Operations.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3Operations.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3Operations.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBrokersV3Operations.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3Operations.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3Operations.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3Operations.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3Operations.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/StacksV3Operations.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/TasksV3Operations.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Application.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ApplicationMapper.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AuditEvent.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AuditEventMapper.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Build.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3BuildMapper.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Domain.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3DomainMapper.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Droplet.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3InstancesInfoMapper.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Job.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3JobMapper.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ListResponse.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Metadata.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Package.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3PackageMapper.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Process.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ProcessMapper.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ResourceMappers.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Role.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RoleMapper.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Route.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RouteMapper.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBinding.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBindingMapper.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBroker.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBrokerMapper.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstance.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstanceMapper.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceKeyDetails.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceKeyMapper.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceOffering.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceOfferingMapper.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServicePlan.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServicePlanMapper.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Stack.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3StackMapper.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Task.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3TaskMapper.java diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/ResilientCloudControllerClient.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/ResilientCloudControllerClient.java index 9e5a2352eb..d23bafd7ce 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/ResilientCloudControllerClient.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/ResilientCloudControllerClient.java @@ -10,7 +10,6 @@ import java.util.function.Function; import java.util.function.Supplier; -import org.cloudfoundry.client.v3.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.ApplicationServicesUpdateCallback; import org.cloudfoundry.multiapps.controller.client.facade.CloudControllerClient; import org.cloudfoundry.multiapps.controller.client.facade.CloudControllerClientImpl; @@ -35,6 +34,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.DockerInfo; import org.cloudfoundry.multiapps.controller.client.facade.domain.DropletInfo; import org.cloudfoundry.multiapps.controller.client.facade.domain.InstancesInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ServicePlanVisibility; import org.cloudfoundry.multiapps.controller.client.facade.domain.Staging; import org.cloudfoundry.multiapps.controller.client.facade.domain.Upload; diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/CloudControllerClient.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/CloudControllerClient.java index 2d8f04ccdf..89f8dec346 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/CloudControllerClient.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/CloudControllerClient.java @@ -8,7 +8,7 @@ import java.util.Set; import java.util.UUID; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudAsyncJob; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudBuild; diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/CloudControllerClientImpl.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/CloudControllerClientImpl.java index ca0386040c..bdb1855440 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/CloudControllerClientImpl.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/CloudControllerClientImpl.java @@ -12,7 +12,6 @@ import java.util.function.Supplier; import org.cloudfoundry.AbstractCloudFoundryException; -import org.cloudfoundry.client.v3.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudAsyncJob; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudBuild; @@ -32,6 +31,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.DockerInfo; import org.cloudfoundry.multiapps.controller.client.facade.domain.DropletInfo; import org.cloudfoundry.multiapps.controller.client.facade.domain.InstancesInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ServicePlanVisibility; import org.cloudfoundry.multiapps.controller.client.facade.domain.Staging; import org.cloudfoundry.multiapps.controller.client.facade.domain.Upload; diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudApplication.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudApplication.java index 898cea1a8a..63489ddd70 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudApplication.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudApplication.java @@ -32,7 +32,7 @@ public CloudApplication derive() { Application app = getApplication(); return ImmutableCloudApplication.builder() .metadata(parseResourceMetadata(app)) - .v3Metadata(app.getMetadata()) + .v3Metadata(toDomainMetadata(app.getMetadata())) .name(app.getName()) .state(parseState(app.getState())) .lifecycle(parseLifecycle(app.getLifecycle())) diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudAsyncJob.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudAsyncJob.java index 1e8bad359c..3650a8de77 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudAsyncJob.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudAsyncJob.java @@ -20,7 +20,8 @@ public CloudAsyncJob derive() { Job job = getJob(); return ImmutableCloudAsyncJob.builder() .metadata(parseResourceMetadata(job)) - .state(job.getState()) + .state(org.cloudfoundry.multiapps.controller.client.facade.domain.JobState.valueOf(job.getState() + .name())) .operation(job.getOperation()) .warnings(getWarnings(job)) .errors(getErrors(job)) diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudEntity.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudEntity.java index 537b1260e2..e5da78b0d5 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudEntity.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudEntity.java @@ -33,6 +33,37 @@ public static CloudMetadata parseResourceMetadata(Resource resource) { .build(); } + /** + * Convert the OSS {@code LastOperation} to the project-owned {@link org.cloudfoundry.multiapps.controller.client.facade.domain.LastOperation}. + * Shared by the service instance / binding / key adapters. (Part of the OSS-backed path; removed with the OSS client.) + */ + protected static org.cloudfoundry.multiapps.controller.client.facade.domain.LastOperation toDomainLastOperation( + org.cloudfoundry.client.v3.LastOperation lastOperation) { + if (lastOperation == null) { + return null; + } + return new org.cloudfoundry.multiapps.controller.client.facade.domain.LastOperation(lastOperation.getType(), + lastOperation.getState(), + lastOperation.getDescription(), + lastOperation.getCreatedAt(), + lastOperation.getUpdatedAt()); + } + + /** + * Convert the OSS v3 {@code Metadata} to the project-owned + * {@link org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata}. (OSS-backed path; removed with the OSS client.) + */ + protected static org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata toDomainMetadata( + org.cloudfoundry.client.v3.Metadata metadata) { + if (metadata == null) { + return null; + } + return org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata.builder() + .labels(metadata.getLabels()) + .annotations(metadata.getAnnotations()) + .build(); + } + protected static UUID parseNullableGuid(String guid) { return guid == null ? null : parseGuid(guid); } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceBinding.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceBinding.java index ab9ba4cad8..b91a44dac7 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceBinding.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceBinding.java @@ -30,7 +30,7 @@ public CloudServiceBinding derive() { .getServiceInstance() .getData() .getId())) - .serviceBindingOperation(ServiceCredentialBindingOperation.from(getServiceBinding().getLastOperation())) + .serviceBindingOperation(ServiceCredentialBindingOperation.from(toDomainLastOperation(getServiceBinding().getLastOperation()))) .build(); } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceInstance.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceInstance.java index af4d58b936..f66b1580aa 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceInstance.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceInstance.java @@ -29,13 +29,14 @@ public CloudServiceInstance derive() { ServiceInstanceResource resource = getResource(); return ImmutableCloudServiceInstance.builder() .metadata(parseResourceMetadata(resource)) - .v3Metadata(resource.getMetadata()) + .v3Metadata(toDomainMetadata(resource.getMetadata())) .name(resource.getName()) .plan(getServicePlanName()) .label(getLabelName()) - .type(resource.getType()) + .type(org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType.valueOf(resource.getType() + .name())) .tags(resource.getTags()) - .lastOperation(ServiceOperation.fromLastOperation(resource.getLastOperation())) + .lastOperation(ServiceOperation.fromLastOperation(toDomainLastOperation(resource.getLastOperation()))) .syslogDrainUrl(resource.getSyslogDrainUrl()) .build(); } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceKey.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceKey.java index 97f6e8eb31..87afdbe471 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceKey.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceKey.java @@ -30,11 +30,11 @@ public CloudServiceKey derive() { Derivable serviceInstance = getServiceInstance(); return ImmutableCloudServiceKey.builder() .metadata(parseResourceMetadata(serviceBindingResource)) - .v3Metadata(serviceBindingResource.getMetadata()) + .v3Metadata(toDomainMetadata(serviceBindingResource.getMetadata())) .name(serviceBindingResource.getName()) .credentials(getCredentials()) .serviceInstance(serviceInstance.derive()) - .serviceKeyOperation(ServiceCredentialBindingOperation.from(serviceBindingResource.getLastOperation())) + .serviceKeyOperation(ServiceCredentialBindingOperation.from(toDomainLastOperation(serviceBindingResource.getLastOperation()))) .build(); } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawInstancesInfo.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawInstancesInfo.java index 2c3a83c130..1b55721485 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawInstancesInfo.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawInstancesInfo.java @@ -39,7 +39,8 @@ private static List parseProcessStatistics(List { @Override public UserRole derive() { RoleResource role = getRoleResource(); - return UserRole.fromRoleType(role.getType()); + return UserRole.fromRoleType(role.getType() + .getValue()); } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawV3CloudServiceInstance.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawV3CloudServiceInstance.java index 8155a4ad41..db610973ef 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawV3CloudServiceInstance.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawV3CloudServiceInstance.java @@ -16,7 +16,7 @@ public CloudServiceInstance derive() { ServiceInstance serviceInstance = getServiceInstance(); return ImmutableCloudServiceInstance.builder() .metadata(parseResourceMetadata(serviceInstance)) - .v3Metadata(serviceInstance.getMetadata()) + .v3Metadata(toDomainMetadata(serviceInstance.getMetadata())) .name(serviceInstance.getName()) .tags(serviceInstance.getTags()) .build(); diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudAsyncJob.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudAsyncJob.java index 394a40c3b7..20b52febc8 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudAsyncJob.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudAsyncJob.java @@ -1,6 +1,5 @@ package org.cloudfoundry.multiapps.controller.client.facade.domain; -import org.cloudfoundry.client.v3.jobs.JobState; import org.immutables.value.Value; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudEntity.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudEntity.java index 16bf0aef9b..aef922d829 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudEntity.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudEntity.java @@ -2,7 +2,7 @@ import java.util.UUID; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.Nullable; diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudServiceInstance.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudServiceInstance.java index 78db202828..06f546412c 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudServiceInstance.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudServiceInstance.java @@ -4,7 +4,6 @@ import java.util.Map; import org.cloudfoundry.AllowNulls; -import org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceType; import org.immutables.value.Value; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/HealthCheckType.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/HealthCheckType.java index 6cd1a86842..e618ee4b45 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/HealthCheckType.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/HealthCheckType.java @@ -3,8 +3,16 @@ public enum HealthCheckType { HTTP, PORT, PROCESS, @Deprecated NONE; + /** + * The CF v3 wire value (lowercase), matching the OSS {@code HealthCheckType.getValue()} this enum replaces at call sites. + */ + public String getValue() { + return name().toLowerCase(); + } + @Override public String toString() { - return this.name().toLowerCase(); + return this.name() + .toLowerCase(); } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/InstanceState.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/InstanceState.java index 89d88d803d..90bd5f95d6 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/InstanceState.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/InstanceState.java @@ -1,16 +1,18 @@ package org.cloudfoundry.multiapps.controller.client.facade.domain; -import org.cloudfoundry.client.v3.processes.ProcessState; - public enum InstanceState { CRASHED, DOWN, RUNNING, STARTING, UNKNOWN; - public static InstanceState valueOfWithDefault(ProcessState state) { + /** + * Resolve from the CF v3 process {@code state} wire value (e.g. {@code RUNNING}). Takes the raw string rather than the OSS + * {@code ProcessState} enum, so the domain no longer depends on cf-java-client. Unknown/blank values map to {@link #UNKNOWN}. + */ + public static InstanceState valueOfWithDefault(String state) { if (state == null) { return UNKNOWN; } try { - return InstanceState.valueOf(state.getValue()); + return InstanceState.valueOf(state); } catch (IllegalArgumentException e) { return InstanceState.UNKNOWN; } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/JobState.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/JobState.java new file mode 100644 index 0000000000..ccb2f6521f --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/JobState.java @@ -0,0 +1,36 @@ +package org.cloudfoundry.multiapps.controller.client.facade.domain; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * State of an asynchronous Cloud Controller v3 job. Project-owned replacement for {@code org.cloudfoundry.client.v3.jobs.JobState}, + * so the domain model no longer depends on the OSS cf-java-client. JSON (de)serialization uses the lowercase wire value + * ({@code processing} / {@code polling} / {@code complete} / {@code failed}) via {@link JsonValue} / {@link JsonCreator} — matching the + * OSS enum so persisted models round-trip identically. + */ +public enum JobState { + + PROCESSING("processing"), POLLING("polling"), COMPLETE("complete"), FAILED("failed"); + + private final String value; + + JobState(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } + + @JsonCreator + public static JobState from(String value) { + for (JobState state : values()) { + if (state.value.equals(value)) { + return state; + } + } + throw new IllegalArgumentException("Unknown job state: " + value); + } +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/LastOperation.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/LastOperation.java new file mode 100644 index 0000000000..8aefe77484 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/LastOperation.java @@ -0,0 +1,9 @@ +package org.cloudfoundry.multiapps.controller.client.facade.domain; + +/** + * Project-owned view of a Cloud Controller v3 resource's {@code last_operation} (raw string fields), replacing + * {@code org.cloudfoundry.client.v3.LastOperation} so the domain no longer depends on the OSS cf-java-client. All fields are the raw CF + * wire values; any may be {@code null}. + */ +public record LastOperation(String type, String state, String description, String createdAt, String updatedAt) { +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/Metadata.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/Metadata.java new file mode 100644 index 0000000000..979e357d6d --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/Metadata.java @@ -0,0 +1,122 @@ +package org.cloudfoundry.multiapps.controller.client.facade.domain; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Project-owned replacement for {@code org.cloudfoundry.client.v3.Metadata} (CF v3 resource labels + annotations), so the domain model no + * longer depends on the OSS cf-java-client. + *

+ * It is a faithful drop-in: the fluent {@link Builder} exposes the same {@code label(k,v)} / {@code annotation(k,v)} / + * {@code labels(map)} / {@code annotations(map)} methods the codebase already calls, and JSON (de)serialization uses the same + * {@code {"labels": {...}, "annotations": {...}}} shape as the OSS type, so persisted models (e.g. {@code CloudEntity.getV3Metadata()}) + * round-trip identically. + */ +public final class Metadata { + + private final Map labels; + private final Map annotations; + + @JsonCreator + Metadata(@JsonProperty("labels") Map labels, @JsonProperty("annotations") Map annotations) { + this.labels = labels == null ? Collections.emptyMap() : Collections.unmodifiableMap(new LinkedHashMap<>(labels)); + this.annotations = annotations == null ? Collections.emptyMap() : Collections.unmodifiableMap(new LinkedHashMap<>(annotations)); + } + + @JsonProperty("labels") + public Map getLabels() { + return labels; + } + + @JsonProperty("annotations") + public Map getAnnotations() { + return annotations; + } + + public static Builder builder() { + return new Builder(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Metadata metadata = (Metadata) o; + return labels.equals(metadata.labels) && annotations.equals(metadata.annotations); + } + + @Override + public int hashCode() { + return Objects.hash(labels, annotations); + } + + @Override + public String toString() { + return "Metadata{annotations=" + annotations + ", labels=" + labels + "}"; + } + + public static final class Builder { + + private final Map labels = new LinkedHashMap<>(); + private final Map annotations = new LinkedHashMap<>(); + + private Builder() { + } + + /** + * Copy labels and annotations from an existing {@link Metadata}. Mirrors the OSS builder's {@code from(Metadata)}. + */ + public Builder from(Metadata metadata) { + if (metadata != null) { + this.labels.putAll(metadata.getLabels()); + this.annotations.putAll(metadata.getAnnotations()); + } + return this; + } + + public Builder label(String key, String value) { + this.labels.put(key, value); + return this; + } + + public Builder labels(Map labels) { + if (labels != null) { + this.labels.putAll(labels); + } + return this; + } + + public Builder putAllLabels(Map labels) { + return labels(labels); + } + + public Builder annotation(String key, String value) { + this.annotations.put(key, value); + return this; + } + + public Builder annotations(Map annotations) { + if (annotations != null) { + this.annotations.putAll(annotations); + } + return this; + } + + public Builder putAllAnnotations(Map annotations) { + return annotations(annotations); + } + + public Metadata build() { + return new Metadata(labels, annotations); + } + } +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/ServiceCredentialBindingOperation.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/ServiceCredentialBindingOperation.java index 4f202834cc..ea06444480 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/ServiceCredentialBindingOperation.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/ServiceCredentialBindingOperation.java @@ -5,7 +5,6 @@ import java.time.format.DateTimeFormatter; import java.util.Arrays; -import org.cloudfoundry.client.v3.LastOperation; import org.immutables.value.Value; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; @@ -31,11 +30,11 @@ public abstract class ServiceCredentialBindingOperation { public abstract LocalDateTime getUpdatedAt(); public static ServiceCredentialBindingOperation from(LastOperation lastOperation) { - String lastOperationType = lastOperation.getType(); - String lastOperationState = lastOperation.getState(); - String lastOperationDescription = lastOperation.getDescription(); - String lastOperationCreatedAt = lastOperation.getCreatedAt(); - String lastOperationUpdatedAt = lastOperation.getUpdatedAt(); + String lastOperationType = lastOperation.type(); + String lastOperationState = lastOperation.state(); + String lastOperationDescription = lastOperation.description(); + String lastOperationCreatedAt = lastOperation.createdAt(); + String lastOperationUpdatedAt = lastOperation.updatedAt(); return ImmutableServiceCredentialBindingOperation.builder() .type(ServiceCredentialBindingOperation.Type.fromString(lastOperationType)) .state(ServiceCredentialBindingOperation.State.fromString(lastOperationState)) diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/ServiceInstanceType.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/ServiceInstanceType.java new file mode 100644 index 0000000000..90618abbf9 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/ServiceInstanceType.java @@ -0,0 +1,40 @@ +package org.cloudfoundry.multiapps.controller.client.facade.domain; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * Type of a Cloud Controller v3 service instance. Project-owned replacement for + * {@code org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceType}, so the domain model no longer depends on the OSS + * cf-java-client. Values mirror the CF v3 service instance {@code type} field, and JSON (de)serialization uses the wire value + * ({@code managed} / {@code user-provided}) via {@link JsonValue} / {@link JsonCreator} — matching the OSS enum's behaviour so persisted + * models round-trip identically. + */ +public enum ServiceInstanceType { + + MANAGED("managed"), USER_PROVIDED("user-provided"); + + private final String value; + + ServiceInstanceType(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } + + /** + * Resolve from the CF v3 wire value ({@code managed} / {@code user-provided}). Mirrors the OSS {@code ServiceInstanceType.from}. + */ + @JsonCreator + public static ServiceInstanceType from(String value) { + for (ServiceInstanceType type : values()) { + if (type.value.equals(value)) { + return type; + } + } + throw new IllegalArgumentException("Unknown service instance type: " + value); + } +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/ServiceOperation.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/ServiceOperation.java index 151dae6bfa..b799c4c3ed 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/ServiceOperation.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/ServiceOperation.java @@ -3,8 +3,6 @@ import java.text.MessageFormat; import java.util.Objects; -import org.cloudfoundry.client.v3.LastOperation; - public class ServiceOperation { public enum Type { @@ -82,12 +80,12 @@ public State getState() { } public static ServiceOperation fromLastOperation(LastOperation lastOperation) { - if (lastOperation == null || lastOperation.getType() == null || lastOperation.getState() == null) { + if (lastOperation == null || lastOperation.type() == null || lastOperation.state() == null) { return null; } - Type type = Type.fromString(lastOperation.getType()); - State state = State.fromString(lastOperation.getState()); - String description = lastOperation.getDescription(); + Type type = Type.fromString(lastOperation.type()); + State state = State.fromString(lastOperation.state()); + String description = lastOperation.description(); return new ServiceOperation(type, description, state); } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/UserRole.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/UserRole.java index 3ee5e84386..ed0b02389f 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/UserRole.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/UserRole.java @@ -4,8 +4,6 @@ import java.util.Map; import java.util.stream.Collectors; -import org.cloudfoundry.client.v3.roles.RoleType; - public enum UserRole { ORGANIZATION_AUDITOR, @@ -20,10 +18,14 @@ public enum UserRole { .collect(Collectors.toMap(UserRole::getName, roleType -> roleType)); - public static UserRole fromRoleType(RoleType roleType) { - UserRole userRole = NAMES_TO_VALUES.get(roleType.getValue()); + /** + * Resolve from the CF v3 role {@code type} wire value (e.g. {@code space_developer}). Takes the raw string rather than the OSS + * {@code RoleType} enum, so the domain no longer depends on cf-java-client. + */ + public static UserRole fromRoleType(String roleTypeValue) { + UserRole userRole = NAMES_TO_VALUES.get(roleTypeValue); if (userRole == null) { - throw new IllegalArgumentException("Unknown user role: " + roleType.getValue()); + throw new IllegalArgumentException("Unknown user role: " + roleTypeValue); } return userRole; } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/dto/ApplicationToCreateDto.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/dto/ApplicationToCreateDto.java index e23244f2f4..c32aa17139 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/dto/ApplicationToCreateDto.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/dto/ApplicationToCreateDto.java @@ -3,7 +3,7 @@ import java.util.Map; import java.util.Set; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.immutables.value.Value; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3Operations.java new file mode 100644 index 0000000000..2605aeb4a8 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3Operations.java @@ -0,0 +1,64 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudBuild; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Build; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3BuildMapper; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.springframework.core.ParameterizedTypeReference; + +/** + * CF v3 Builds operations for the in-house {@link CloudControllerRestClientV3Impl}. Reproduces the HTTP shape of the OSS + * {@code CloudControllerRestClientImpl} build methods (endpoints from {@code builds()} / {@code applicationsV3().listBuilds}), mapping the + * v3 wire model to the {@link CloudBuild} domain type via {@link V3BuildMapper}. + */ +public class BuildsV3Operations { + + private static final int MAX_PAGE_SIZE = 5000; + + private final CloudControllerV3Client cc; + private final CloudSpace target; + + public BuildsV3Operations(CloudControllerV3Client cc, CloudSpace target) { + this.cc = cc; + this.target = target; + } + + public CloudBuild createBuild(UUID packageGuid) { + V3Build build = cc.getRestClient() + .post() + .uri("/v3/builds") + .body(Map.of("package", Map.of("guid", packageGuid.toString()))) + .retrieve() + .body(V3Build.class); + return V3BuildMapper.toCloudBuild(build); + } + + public CloudBuild getBuild(UUID buildGuid) { + V3Build build = cc.get("/v3/builds/" + buildGuid, V3Build.class); + return V3BuildMapper.toCloudBuild(build); + } + + public List getBuildsForApplication(UUID applicationGuid) { + String uri = "/v3/apps/" + applicationGuid + "/builds?per_page=" + MAX_PAGE_SIZE; + return cc.list(uri, new ParameterizedTypeReference>() { + }) + .stream() + .map(V3BuildMapper::toCloudBuild) + .toList(); + } + + public List getBuildsForPackage(UUID packageGuid) { + String uri = "/v3/builds?package_guids=" + packageGuid + "&per_page=" + MAX_PAGE_SIZE; + return cc.list(uri, new ParameterizedTypeReference>() { + }) + .stream() + .map(V3BuildMapper::toCloudBuild) + .toList(); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClient.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClient.java index 8c79aec33e..4ed03f71d4 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClient.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClient.java @@ -8,7 +8,7 @@ import java.util.Set; import java.util.UUID; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.UploadStatusCallback; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudAsyncJob; diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientBuilder.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientBuilder.java new file mode 100644 index 0000000000..4c0d158f1d --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientBuilder.java @@ -0,0 +1,128 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.net.URL; +import java.net.http.HttpClient; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.cert.X509Certificate; +import java.time.Duration; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.client.JdkClientHttpRequestFactory; +import org.springframework.http.converter.ByteArrayHttpMessageConverter; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.web.client.RestClient; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.cloudfoundry.multiapps.controller.client.facade.oauth2.OAuthClient; + +/** + * Builds the blocking Spring {@link RestClient} used by {@link CloudControllerRestClientV3Impl} to talk to the Cloud Controller v3 + * REST API directly, without the OSS cf-java-client. + *

+ * The transport is the JDK {@link java.net.http.HttpClient} (via {@link JdkClientHttpRequestFactory}). This is deliberately chosen over + * reactor-netty: it is already available through {@code spring-web} and the JDK, so it pulls no extra transitive dependencies + * (no reactor-netty, no spring-webflux) into the deploy-service WAR — which is exactly the dependency-shedding this migration is about. + * The JDK client provides connection reuse (HTTP/2 multiplexing + keep-alive) and connect/read timeouts; TLS trust is configurable for + * self-signed landscapes. Every request carries a bearer token from {@link OAuthClient} (refreshed as needed), and non-2xx responses are + * mapped to {@code CloudOperationException} by the existing {@link CloudControllerResponseErrorHandler}. + */ +public final class CloudControllerRestClientBuilder { + + private static final Duration DEFAULT_CONNECT_TIMEOUT = Duration.ofMinutes(1); + + private CloudControllerRestClientBuilder() { + } + + public static RestClient build(URL v3ApiUrl, OAuthClient oAuthClient, Map requestTags, TransportOptions options) { + JdkClientHttpRequestFactory requestFactory = new JdkClientHttpRequestFactory(buildHttpClient(options)); + options.responseTimeout() + .ifPresent(requestFactory::setReadTimeout); + return RestClient.builder() + .baseUrl(v3ApiUrl.toString()) + .requestFactory(requestFactory) + // Pass a fully-formed converter list via the List overload — deliberately NOT the Consumer overload. + // messageConverters(Consumer) first calls initMessageConverters() to build Spring's DEFAULT list and only then + // hands it to the consumer; that default init eagerly constructs AllEncompassingFormHttpMessageConverter -> + // MappingJackson2YamlHttpMessageConverter -> Jackson2ObjectMapperBuilder.yaml(), which loads a YAMLFactory that is + // incompatible with this codebase's jackson-core (mixed Jackson 2/3 classpath) and throws VerifyError at load time + // — so clearing the list afterwards is too late. The List overload skips initMessageConverters() entirely, so the + // YAML converter is never built. CF v3 is JSON-only; JSON + String + byte[] (from an ObjectMapper we control) suffice. + .messageConverters(List.of(new ByteArrayHttpMessageConverter(), + new StringHttpMessageConverter(), + new MappingJackson2HttpMessageConverter(new ObjectMapper()))) + .requestInterceptor((request, body, execution) -> { + String authorization = oAuthClient.getAuthorizationHeaderValue(); + if (authorization != null) { + request.getHeaders() + .set(HttpHeaders.AUTHORIZATION, authorization); + } + requestTags.forEach((key, value) -> request.getHeaders() + .set(key, value)); + return execution.execute(request, body); + }) + .defaultStatusHandler(new CloudControllerResponseErrorHandler()) + .build(); + } + + private static HttpClient buildHttpClient(TransportOptions options) { + HttpClient.Builder builder = HttpClient.newBuilder() + .followRedirects(HttpClient.Redirect.NORMAL) + .connectTimeout(options.connectTimeout() + .orElse(DEFAULT_CONNECT_TIMEOUT)); + if (options.trustSelfSignedCertificates()) { + builder.sslContext(buildTrustAllSslContext()); + } + return builder.build(); + } + + private static SSLContext buildTrustAllSslContext() { + try { + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, new TrustManager[] { trustAllManager() }, new SecureRandom()); + return sslContext; + } catch (NoSuchAlgorithmException | KeyManagementException e) { + throw new IllegalStateException("An error occurred setting up the SSLContext", e); + } + } + + private static X509TrustManager trustAllManager() { + return new X509TrustManager() { + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) { + // NOSONAR - self-signed certificate trust is opt-in via TransportOptions + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) { + // NOSONAR - self-signed certificate trust is opt-in via TransportOptions + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[] {}; + } + + }; + } + + /** + * The transport-tuning surface carried over from the OSS-backed factory, so operators keep the same knobs. {@code connectionPoolSize} + * is accepted for parity but the JDK client manages connection reuse internally; it is retained for a future backend swap. + */ + public record TransportOptions(Optional connectTimeout, Optional responseTimeout, + Optional connectionPoolSize, boolean trustSelfSignedCertificates) { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientFactory.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientFactory.java index d2258ed03c..47a209a587 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientFactory.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientFactory.java @@ -5,11 +5,17 @@ import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableCloudFoundryClientFactory; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; import org.cloudfoundry.multiapps.controller.client.facade.oauth2.OAuthClient; +import org.cloudfoundry.multiapps.controller.client.facade.util.JsonUtil; import org.cloudfoundry.multiapps.controller.client.facade.util.RestUtil; import org.cloudfoundry.client.CloudFoundryClient; import org.immutables.value.Value; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.client.RestClient; import java.net.URL; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; import java.time.Duration; import java.util.Collections; import java.util.Map; @@ -17,6 +23,7 @@ @Value.Immutable public abstract class CloudControllerRestClientFactory { + private static final Logger LOGGER = LoggerFactory.getLogger(CloudControllerRestClientFactory.class); private final RestUtil restUtil = new RestUtil(); public abstract Optional getSslHandshakeTimeout(); @@ -34,6 +41,19 @@ public boolean shouldTrustSelfSignedCertificates() { return false; } + /** + * Feature flag for the cf-java-client migration PoC. When {@code true}, {@link #createClient} builds the in-house + * {@link CloudControllerRestClientV3Impl} (direct CF v3 REST, no cf-java-client); when {@code false} (default), it builds the + * OSS-backed {@link CloudControllerRestClientImpl}. Kept default-off so existing behaviour is unchanged and the two can be A/B + * compared against the same live Cloud Foundry. + */ + @Value.Default + public boolean useV3RestClient() { + // cf_java_client_poc branch: default ON so the in-house CloudControllerRestClientV3Impl is always chosen for OQ verification. + // Do NOT merge this default to main — the switch belongs off there until the client is validated. + return true; + } + @Value.Derived public CloudFoundryClientFactory getCloudFoundryClientFactory() { ImmutableCloudFoundryClientFactory.Builder builder = ImmutableCloudFoundryClientFactory.builder(); @@ -61,10 +81,86 @@ public CloudControllerRestClient createClient(URL controllerUrl, CloudCredential public CloudControllerRestClient createClient(URL controllerUrl, CloudCredentials credentials, CloudSpace target, OAuthClient oAuthClient, Map requestTags) { oAuthClient.init(credentials); + if (useV3RestClient()) { + return createV3RestClient(controllerUrl, target, oAuthClient, requestTags); + } CloudFoundryClient delegate = getCloudFoundryClientFactory().createClient(controllerUrl, oAuthClient, requestTags); return new CloudControllerRestClientImpl(delegate, target); } + private CloudControllerRestClient createV3RestClient(URL controllerUrl, CloudSpace target, OAuthClient oAuthClient, + Map requestTags) { + URL v3ApiUrl = deriveV3ApiUrl(controllerUrl, requestTags); + // The RestClient baseUrl must be the API ORIGIN (scheme://host[:port]) only — every operation path already carries the + // "/v3/..." prefix, so using the ".../v3" href as the base would produce a duplicated "/v3/v3/apps" path (CF -> 404 Unknown + // request). We keep v3ApiUrl (which includes /v3) for reference/root-link semantics but strip it to the origin for the base. + URL baseUrl = toOrigin(v3ApiUrl); + LOGGER.warn("[cf-v3-poc] controllerUrl={} resolved v3ApiUrl={} restClientBaseUrl={}", controllerUrl, v3ApiUrl, baseUrl); + var transportOptions = new CloudControllerRestClientBuilder.TransportOptions(getConnectTimeout(), getResponseTimeout(), + getConnectionPoolSize(), + shouldTrustSelfSignedCertificates()); + RestClient restClient = CloudControllerRestClientBuilder.build(baseUrl, oAuthClient, requestTags, transportOptions); + return new CloudControllerRestClientV3Impl(v3ApiUrl, oAuthClient, target, restClient); + } + + // scheme://host[:port] of the given URL, dropping any path (e.g. the trailing /v3). + private URL toOrigin(URL url) { + try { + int port = url.getPort(); + String origin = url.getProtocol() + "://" + url.getHost() + (port == -1 ? "" : ":" + port); + return java.net.URI.create(origin) + .toURL(); + } catch (java.net.MalformedURLException e) { + throw new IllegalArgumentException("Could not derive the API origin from " + url, e); + } + } + + // Resolve the CF v3 API base URL the same way the OSS factory does: GET the CF root and read links.cloud_controller_v3.href. + // The conventional controllerUrl + "/v3" is only a fallback — on some landscapes the real v3 href differs, and using the naive + // path yields "404 Not Found: Unknown request" from the Cloud Controller. + @SuppressWarnings("unchecked") + private URL deriveV3ApiUrl(URL controllerUrl, Map requestTags) { + try { + HttpRequest.Builder requestBuilder = HttpRequest.newBuilder() + .GET() + .uri(controllerUrl.toURI()) + .timeout(Duration.ofMinutes(1)); + requestTags.forEach(requestBuilder::header); + HttpResponse response = rootHttpClient().send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() / 100 != 2) { + return fallbackV3ApiUrl(controllerUrl); + } + Map links = (Map) JsonUtil.convertJsonToMap(response.body()) + .get("links"); + Map ccv3 = (Map) links.get("cloud_controller_v3"); + String href = (String) ccv3.get("href"); + return java.net.URI.create(href) + .toURL(); + } catch (InterruptedException e) { + Thread.currentThread() + .interrupt(); + return fallbackV3ApiUrl(controllerUrl); + } catch (Exception e) { + return fallbackV3ApiUrl(controllerUrl); + } + } + + private static java.net.http.HttpClient rootHttpClient() { + return java.net.http.HttpClient.newBuilder() + .connectTimeout(Duration.ofMinutes(1)) + .build(); + } + + private URL fallbackV3ApiUrl(URL controllerUrl) { + try { + return java.net.URI.create(controllerUrl.toString() + "/v3") + .toURL(); + } catch (java.net.MalformedURLException e) { + throw new IllegalArgumentException("Could not derive the CF v3 API URL from " + controllerUrl, e); + } + } + + private OAuthClient createOAuthClient(URL controllerUrl, String origin) { return restUtil.createOAuthClientByControllerUrl(controllerUrl, shouldTrustSelfSignedCertificates()); } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientImpl.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientImpl.java index c1e6718d16..95172fea6c 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientImpl.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientImpl.java @@ -25,7 +25,7 @@ import org.cloudfoundry.client.v3.DockerData; import org.cloudfoundry.client.v3.Lifecycle; import org.cloudfoundry.client.v3.LifecycleType; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.client.v3.Relationship; import org.cloudfoundry.client.v3.Resource; import org.cloudfoundry.client.v3.ToOneRelationship; @@ -305,7 +305,7 @@ public void createApplication(ApplicationToCreateDto applicationToCreateDto) { assertSpaceProvided("create application"); CreateApplicationRequest createApplicationRequest = CreateApplicationRequest.builder() .name(applicationToCreateDto.getName()) - .metadata(applicationToCreateDto.getMetadata()) + .metadata(toOssMetadata(applicationToCreateDto.getMetadata())) .lifecycle(buildApplicationLifecycle( applicationToCreateDto.getStaging())) .relationships(buildApplicationRelationships()) @@ -472,7 +472,7 @@ public void createServiceInstance(CloudServiceInstance serviceInstance) { .create(CreateServiceInstanceRequest.builder() .type(ServiceInstanceType.MANAGED) .name(serviceInstance.getName()) - .metadata(serviceInstance.getV3Metadata()) + .metadata(toOssMetadata(serviceInstance.getV3Metadata())) .relationships(ServiceInstanceRelationships.builder() .servicePlan(buildToOneRelationship( servicePlanGuid.toString())) @@ -557,7 +557,7 @@ private Optional doCreateServiceKey(String name, Map par private CreateServiceBindingRequest buildServiceCredentialBindingRequest(String name, Map parameters, Metadata metadata, CloudServiceInstance serviceInstance) { - if (serviceInstance.getType() != ServiceInstanceType.MANAGED) { + if (serviceInstance.getType() != org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType.MANAGED) { throw new IllegalArgumentException( String.format(Messages.CANT_CREATE_SERVICE_KEY_FOR_USER_PROVIDED_SERVICE, serviceInstance.getName())); } @@ -566,7 +566,7 @@ private CreateServiceBindingRequest buildServiceCredentialBindingRequest(String var createBindingRequest = CreateServiceBindingRequest.builder() .type(ServiceBindingType.KEY) .name(name) - .metadata(metadata) + .metadata(toOssMetadata(metadata)) .relationships(ServiceBindingRelationships.builder() .serviceInstance( buildToOneRelationship( @@ -578,6 +578,16 @@ private CreateServiceBindingRequest buildServiceCredentialBindingRequest(String return createBindingRequest.build(); } + private static org.cloudfoundry.client.v3.Metadata toOssMetadata(Metadata metadata) { + if (metadata == null) { + return null; + } + return org.cloudfoundry.client.v3.Metadata.builder() + .labels(metadata.getLabels()) + .annotations(metadata.getAnnotations()) + .build(); + } + @Override public void createUserProvidedServiceInstance(CloudServiceInstance serviceInstance) { assertSpaceProvided("create service instance"); @@ -586,7 +596,7 @@ public void createUserProvidedServiceInstance(CloudServiceInstance serviceInstan delegate.serviceInstancesV3() .create(CreateServiceInstanceRequest.builder() .name(serviceInstance.getName()) - .metadata(serviceInstance.getV3Metadata()) + .metadata(toOssMetadata(serviceInstance.getV3Metadata())) .type(ServiceInstanceType.USER_PROVIDED) .credentials(serviceInstance.getCredentials()) .syslogDrainUrl(syslogDrainUrl) @@ -1325,11 +1335,11 @@ public void updateApplicationMemory(String applicationName, int memory) { } @Override - public void updateApplicationMetadata(UUID guid, org.cloudfoundry.client.v3.Metadata metadata) { + public void updateApplicationMetadata(UUID guid, Metadata metadata) { delegate.applicationsV3() .update(org.cloudfoundry.client.v3.applications.UpdateApplicationRequest.builder() .applicationId(guid.toString()) - .metadata(metadata) + .metadata(toOssMetadata(metadata)) .build()) .block(); } @@ -1424,21 +1434,21 @@ public String updateServiceBroker(CloudServiceBroker serviceBroker) { } @Override - public void updateServiceInstanceMetadata(UUID guid, org.cloudfoundry.client.v3.Metadata metadata) { + public void updateServiceInstanceMetadata(UUID guid, Metadata metadata) { delegate.serviceInstancesV3() .update(UpdateServiceInstanceRequest.builder() .serviceInstanceId(guid.toString()) - .metadata(metadata) + .metadata(toOssMetadata(metadata)) .build()) .block(); } @Override - public void updateServiceBindingMetadata(UUID guid, org.cloudfoundry.client.v3.Metadata metadata) { + public void updateServiceBindingMetadata(UUID guid, Metadata metadata) { delegate.serviceBindingsV3() .update(UpdateServiceBindingRequest.builder() .serviceBindingId(guid.toString()) - .metadata(metadata) + .metadata(toOssMetadata(metadata)) .build()) .block(); } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientV3Impl.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientV3Impl.java new file mode 100644 index 0000000000..d0152f3d7b --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientV3Impl.java @@ -0,0 +1,831 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.net.URL; +import java.nio.file.Path; +import java.time.Duration; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.UploadStatusCallback; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudAsyncJob; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudBuild; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudDomain; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEvent; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudPackage; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudProcess; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudRoute; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBinding; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBroker; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceOffering; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudStack; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudTask; +import org.cloudfoundry.multiapps.controller.client.facade.domain.DockerInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.DropletInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.InstancesInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServicePlanVisibility; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Staging; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Upload; +import org.cloudfoundry.multiapps.controller.client.facade.domain.UserRole; +import org.cloudfoundry.multiapps.controller.client.facade.dto.ApplicationToCreateDto; +import org.cloudfoundry.multiapps.controller.client.facade.oauth2.OAuthClient; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Application; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ApplicationMapper; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; +import org.springframework.web.client.RestClient; + +/** + * A {@link CloudControllerRestClient} implementation that talks to the Cloud Controller v3 REST API directly through a blocking Spring + * {@link RestClient}, with no dependency on the OSS cf-java-client. + *

+ * This is the target implementation of the "migrate off cf-java-client" PoC. It is selected in + * {@link CloudControllerRestClientFactory} via a feature flag; when the flag is off, the OSS-backed + * {@link CloudControllerRestClientImpl} is used instead, so the two can be A/B compared against the same live Cloud Foundry. + *

+ * SCAFFOLD STATE: every operation currently throws {@link UnsupportedOperationException}. Operations are being filled in + * resource-group by resource-group per docs/cf-java-client-migration/02-endpoint-inventory.md. The shared machinery (pagination walker, + * async job poller, JSON model) lands first and is reused by every operation. + */ +public class CloudControllerRestClientV3Impl implements CloudControllerRestClient { + + // CF v3 caps per_page at 5000; use a large page to minimise round-trips (the pagination walker still handles multiple pages). + private static final int DEFAULT_PAGE_SIZE = 5000; + // Matches the OSS impl's DELETE_JOB_TIMEOUT for async resource deletions. + private static final Duration DELETE_JOB_TIMEOUT = Duration.ofMinutes(5); + + private final URL v3ApiUrl; + private final OAuthClient oAuthClient; + private final CloudSpace target; + private final RestClient restClient; + private final CloudControllerV3Client cc; + private final BuildsV3Operations buildsOps; + private final DomainsV3Operations domainsOps; + private final EventsV3Operations eventsOps; + private final JobsV3Operations jobsOps; + private final PackagesV3Operations packagesOps; + private final ProcessesV3Operations processesOps; + private final RolesV3Operations rolesOps; + private final RoutesV3Operations routesOps; + private final ServiceBindingsV3Operations serviceBindingsOps; + private final ServiceBrokersV3Operations serviceBrokersOps; + private final ServiceInstancesV3Operations serviceInstancesOps; + private final ServiceKeysV3Operations serviceKeysOps; + private final ServiceOfferingsV3Operations serviceOfferingsOps; + private final ServicePlansV3Operations servicePlansOps; + private final StacksV3Operations stacksOps; + private final TasksV3Operations tasksOps; + + public CloudControllerRestClientV3Impl(URL v3ApiUrl, OAuthClient oAuthClient, CloudSpace target, RestClient restClient) { + this.v3ApiUrl = v3ApiUrl; + this.oAuthClient = oAuthClient; + this.target = target; + this.restClient = restClient; + this.cc = new CloudControllerV3Client(restClient); + this.buildsOps = new BuildsV3Operations(cc, target); + this.domainsOps = new DomainsV3Operations(cc, target); + this.eventsOps = new EventsV3Operations(cc, target); + this.jobsOps = new JobsV3Operations(cc, target); + this.packagesOps = new PackagesV3Operations(cc, target); + this.processesOps = new ProcessesV3Operations(cc, target); + this.rolesOps = new RolesV3Operations(cc, target); + this.routesOps = new RoutesV3Operations(cc, target); + this.serviceBindingsOps = new ServiceBindingsV3Operations(cc, target); + this.serviceBrokersOps = new ServiceBrokersV3Operations(cc, target); + this.serviceInstancesOps = new ServiceInstancesV3Operations(cc, target); + this.serviceKeysOps = new ServiceKeysV3Operations(cc, target); + this.serviceOfferingsOps = new ServiceOfferingsV3Operations(cc, target); + this.servicePlansOps = new ServicePlansV3Operations(cc, target); + this.stacksOps = new StacksV3Operations(cc, target); + this.tasksOps = new TasksV3Operations(cc, target); + } + + private static UnsupportedOperationException notImplemented() { + StackWalker.StackFrame caller = StackWalker.getInstance() + .walk(frames -> frames.skip(1) + .findFirst() + .orElse(null)); + String operation = caller == null ? "operation" : caller.getMethodName(); + return new UnsupportedOperationException( + "CloudControllerRestClientV3Impl." + operation + " is not implemented yet (cf-java-client migration PoC scaffold)."); + } + + @Override + public CloudSpace getTarget() { + return target; + } + + @Override + public void addDomain(String domainName) { + domainsOps.addDomain(domainName); + } + + @Override + public void addRoute(String host, String domainName, String path) { + routesOps.addRoute(host, domainName, path); + } + + @Override + public Optional bindServiceInstance(String bindingName, String applicationName, String serviceInstanceName) { + return serviceBindingsOps.bindServiceInstance(bindingName, applicationName, serviceInstanceName); + } + + @Override + public Optional bindServiceInstance(String bindingName, String applicationName, String serviceInstanceName, + Map parameters) { + return serviceBindingsOps.bindServiceInstance(bindingName, applicationName, serviceInstanceName, parameters); + } + + @Override + public void createApplication(ApplicationToCreateDto dto) { + if (target == null || target.getGuid() == null) { + throw new CloudOperationException(HttpStatus.BAD_REQUEST, "Bad Request", "A target space is required to create an application."); + } + Map body = new java.util.HashMap<>(); + body.put("name", dto.getName()); + body.put("lifecycle", buildLifecycle(dto.getStaging())); + body.put("relationships", Map.of("space", Map.of("data", Map.of("guid", target.getGuid() + .toString())))); + if (dto.getEnv() != null) { + body.put("environment_variables", dto.getEnv()); + } + if (dto.getMetadata() != null) { + body.put("metadata", Map.of("labels", dto.getMetadata() + .getLabels(), + "annotations", dto.getMetadata() + .getAnnotations())); + } + V3Application created = cc.getRestClient() + .post() + .uri("/v3/apps") + .body(body) + .retrieve() + .body(V3Application.class); + UUID appGuid = UUID.fromString(created.guid()); + // Follow-up attributes, mirroring the OSS updateApplicationAttributes: staging/process, memory+disk scale, then routes. + if (dto.getStaging() != null) { + processesOps.updateApplicationStaging(dto.getName(), dto.getStaging()); + } + Map scale = new java.util.HashMap<>(); + if (dto.getMemoryInMb() != null) { + scale.put("memory_in_mb", dto.getMemoryInMb()); + } + if (dto.getDiskQuotaInMb() != null) { + scale.put("disk_in_mb", dto.getDiskQuotaInMb()); + } + if (!scale.isEmpty()) { + scaleWebProcess(appGuid, scale); + } + if (dto.getRoutes() != null && !dto.getRoutes() + .isEmpty()) { + updateApplicationRoutes(dto.getName(), dto.getRoutes()); + } + } + + // Builds the CF v3 lifecycle object: docker when DockerInfo is present, otherwise buildpack/cnb with buildpacks + stack. + private Map buildLifecycle(Staging staging) { + if (staging == null) { + return Map.of("type", "buildpack", "data", Map.of()); + } + if (staging.getDockerInfo() != null) { + return Map.of("type", "docker", "data", Map.of()); + } + String type = staging.getLifecycleType() != null ? staging.getLifecycleType() + .name() + .toLowerCase() + : "buildpack"; + Map data = new java.util.HashMap<>(); + if (staging.getBuildpacks() != null) { + data.put("buildpacks", staging.getBuildpacks()); + } + if (staging.getStackName() != null) { + data.put("stack", staging.getStackName()); + } + return Map.of("type", type, "data", data); + } + + @Override + public void createServiceInstance(CloudServiceInstance serviceInstance) { + serviceInstancesOps.createServiceInstance(serviceInstance); + } + + @Override + public String createServiceBroker(CloudServiceBroker serviceBroker) { + return serviceBrokersOps.createServiceBroker(serviceBroker); + } + + @Override + public CloudServiceKey createAndFetchServiceKey(CloudServiceKey keyModel, String serviceInstanceName) { + return serviceKeysOps.createAndFetchServiceKey(keyModel, getServiceInstance(serviceInstanceName)); + } + + @Override + public Optional createServiceKey(CloudServiceKey keyModel, String serviceInstanceName) { + return serviceKeysOps.createServiceKey(keyModel, getServiceInstance(serviceInstanceName)); + } + + @Override + public Optional createServiceKey(String serviceInstanceName, String serviceKeyName, Map parameters) { + return serviceKeysOps.createServiceKey(getServiceInstance(serviceInstanceName), serviceKeyName, parameters); + } + + @Override + public void createUserProvidedServiceInstance(CloudServiceInstance serviceInstance) { + serviceInstancesOps.createUserProvidedServiceInstance(serviceInstance); + } + + @Override + public void deleteApplication(String applicationName) { + UUID applicationGuid = getApplicationGuid(applicationName); + var response = cc.getRestClient() + .delete() + .uri("/v3/apps/{guid}", applicationGuid) + .retrieve() + .toBodilessEntity(); + cc.followAsyncJob(response, DELETE_JOB_TIMEOUT); + } + + @Override + public void deleteDomain(String domainName) { + domainsOps.deleteDomain(domainName); + } + + @Override + public void deleteOrphanedRoutes() { + routesOps.deleteOrphanedRoutes(); + } + + @Override + public void deleteRoute(String host, String domainName, String path) { + routesOps.deleteRoute(host, domainName, path); + } + + @Override + public void deleteServiceInstance(String serviceInstanceName) { + serviceInstancesOps.deleteServiceInstance(serviceInstanceName); + } + + @Override + public void deleteServiceInstance(CloudServiceInstance serviceInstance) { + serviceInstancesOps.deleteServiceInstance(serviceInstance); + } + + @Override + public String deleteServiceBroker(String name) { + return serviceBrokersOps.deleteServiceBroker(name); + } + + @Override + public Optional deleteServiceBinding(String serviceInstanceName, String serviceKeyName) { + // Mirrors the OSS impl: resolve the named service key, then delete its binding by GUID. + CloudServiceKey serviceKey = getServiceKey(serviceInstanceName, serviceKeyName); + if (serviceKey == null) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Service key " + serviceKeyName + " not found."); + } + return deleteServiceBinding(serviceKey.getGuid()); + } + + @Override + public Optional deleteServiceBinding(UUID bindingGuid) { + return serviceBindingsOps.deleteServiceBinding(bindingGuid); + } + + @Override + public CloudApplication getApplication(String applicationName) { + return getApplication(applicationName, true); + } + + @Override + public CloudApplication getApplication(String applicationName, boolean required) { + V3Application app = findApplicationByName(applicationName); + if (app == null) { + if (required) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Application " + applicationName + " not found."); + } + return null; + } + return V3ApplicationMapper.toCloudApplication(app, target); + } + + @Override + public UUID getApplicationGuid(String applicationName) { + return getApplication(applicationName).getGuid(); + } + + @Override + public String getApplicationName(UUID applicationGuid) { + V3Application app = cc.get("/v3/apps/" + applicationGuid, V3Application.class); + return app == null ? null : app.name(); + } + + @Override + public Map getApplicationEnvironment(UUID applicationGuid) { + V3Application.V3EnvironmentVariables env = cc.get("/v3/apps/" + applicationGuid + "/environment_variables", + V3Application.V3EnvironmentVariables.class); + return env == null || env.var() == null ? Map.of() : env.var(); + } + + @Override + public Map getApplicationEnvironment(String applicationName) { + return getApplicationEnvironment(getApplicationGuid(applicationName)); + } + + @Override + public List getApplications() { + return listApplications(applicationsQuery(null)).stream() + .map(app -> V3ApplicationMapper.toCloudApplication(app, target)) + .toList(); + } + + @Override + public List getApplicationsByMetadataLabelSelector(String labelSelector) { + String query = applicationsQuery(null); + if (labelSelector != null) { + query = query + "&label_selector=" + labelSelector; + } + return listApplications(query).stream() + .map(app -> V3ApplicationMapper.toCloudApplication(app, target)) + .toList(); + } + + private V3Application findApplicationByName(String applicationName) { + List apps = listApplications(applicationsQuery(applicationName)); + return apps.isEmpty() ? null : apps.get(0); + } + + private List listApplications(String query) { + return cc.list(query, new ParameterizedTypeReference>() { + }); + } + + // Applications in the target space, optionally filtered by exact name. + private String applicationsQuery(String name) { + StringBuilder query = new StringBuilder("/v3/apps?per_page=" + DEFAULT_PAGE_SIZE); + if (target != null && target.getGuid() != null) { + query.append("&space_guids=") + .append(target.getGuid()); + } + if (name != null) { + query.append("&names=") + .append(name); + } + return query.toString(); + } + + @Override + public List getApplicationEvents(String applicationName) { + return eventsOps.getApplicationEvents(applicationName); + } + + @Override + public List getEventsByTarget(UUID uuid) { + return eventsOps.getEventsByTarget(uuid); + } + + @Override + public InstancesInfo getApplicationInstances(CloudApplication app) { + return processesOps.getApplicationInstances(app); + } + + @Override + public InstancesInfo getApplicationInstances(UUID applicationGuid) { + return processesOps.getApplicationInstances(applicationGuid); + } + + @Override + public CloudProcess getApplicationProcess(UUID applicationGuid) { + return processesOps.getApplicationProcess(applicationGuid); + } + + @Override + public List getApplicationRoutes(UUID applicationGuid) { + return routesOps.getApplicationRoutes(applicationGuid); + } + + @Override + public boolean getApplicationSshEnabled(UUID applicationGuid) { + return processesOps.getApplicationSshEnabled(applicationGuid); + } + + @Override + public Map getApplicationFeatures(UUID applicationGuid) { + return processesOps.getApplicationFeatures(applicationGuid); + } + + @Override + public CloudDomain getDefaultDomain() { + return domainsOps.getDefaultDomain(); + } + + @Override + public List getDomains() { + return domainsOps.getDomains(); + } + + @Override + public List getDomainsForOrganization() { + return domainsOps.getDomainsForOrganization(); + } + + @Override + public List getEvents() { + return eventsOps.getEvents(); + } + + @Override + public List getPrivateDomains() { + return domainsOps.getPrivateDomains(); + } + + @Override + public List getRoutes(String domainName) { + return routesOps.getRoutes(domainName); + } + + @Override + public UUID getRequiredServiceInstanceGuid(String name) { + return serviceInstancesOps.getRequiredServiceInstanceGuid(name); + } + + @Override + public CloudServiceInstance getServiceInstance(String serviceInstanceName) { + return serviceInstancesOps.getServiceInstance(serviceInstanceName); + } + + @Override + public CloudServiceInstance getServiceInstance(String serviceInstanceName, boolean required) { + return serviceInstancesOps.getServiceInstance(serviceInstanceName, required); + } + + @Override + public String getServiceInstanceName(UUID serviceInstanceGuid) { + return serviceInstancesOps.getServiceInstanceName(serviceInstanceGuid); + } + + @Override + public CloudServiceInstance getServiceInstanceWithoutAuxiliaryContent(String serviceInstanceName) { + return serviceInstancesOps.getServiceInstanceWithoutAuxiliaryContent(serviceInstanceName); + } + + @Override + public CloudServiceInstance getServiceInstanceWithoutAuxiliaryContent(String serviceInstanceName, boolean required) { + return serviceInstancesOps.getServiceInstanceWithoutAuxiliaryContent(serviceInstanceName, required); + } + + @Override + public CloudServiceBinding getServiceBinding(UUID serviceBindingGuid) { + return serviceBindingsOps.getServiceBinding(serviceBindingGuid); + } + + @Override + public List getServiceAppBindings(UUID serviceInstanceGuid) { + return serviceBindingsOps.getServiceAppBindings(serviceInstanceGuid); + } + + @Override + public List getAppBindings(UUID applicationGuid) { + return serviceBindingsOps.getAppBindings(applicationGuid); + } + + @Override + public List getServiceBindingsForApplication(UUID applicationId, UUID serviceInstanceGuid) { + return serviceBindingsOps.getServiceBindingsForApplication(applicationId, serviceInstanceGuid); + } + + @Override + public CloudServiceBroker getServiceBroker(String name) { + return serviceBrokersOps.getServiceBroker(name); + } + + @Override + public CloudServiceBroker getServiceBroker(String name, boolean required) { + return serviceBrokersOps.getServiceBroker(name, required); + } + + @Override + public List getServiceBrokers() { + return serviceBrokersOps.getServiceBrokers(); + } + + @Override + public CloudServiceKey getServiceKey(String serviceInstanceName, String serviceKeyName) { + return serviceKeysOps.getServiceKey(getServiceInstance(serviceInstanceName), serviceKeyName); + } + + @Override + public List getServiceKeys(String serviceInstanceName) { + return serviceKeysOps.getServiceKeys(getServiceInstance(serviceInstanceName)); + } + + @Override + public List getServiceKeysWithCredentials(String serviceInstanceName) { + return serviceKeysOps.getServiceKeysWithCredentials(getServiceInstance(serviceInstanceName)); + } + + @Override + public List getServiceKeys(CloudServiceInstance serviceInstance) { + return serviceKeysOps.getServiceKeys(serviceInstance); + } + + @Override + public List getServiceKeysWithCredentials(CloudServiceInstance serviceInstance) { + return serviceKeysOps.getServiceKeysWithCredentials(serviceInstance); + } + + @Override + public List getServiceOfferings() { + return serviceOfferingsOps.getServiceOfferings(); + } + + @Override + public List getSharedDomains() { + return domainsOps.getSharedDomains(); + } + + @Override + public CloudStack getStack(String name) { + return stacksOps.getStack(name); + } + + @Override + public CloudStack getStack(String name, boolean required) { + return stacksOps.getStack(name, required); + } + + @Override + public List getStacks() { + return stacksOps.getStacks(); + } + + @Override + public void startApplication(String applicationName) { + UUID guid = getApplicationGuid(applicationName); + cc.getRestClient() + .post() + .uri("/v3/apps/{guid}/actions/start", guid) + .retrieve() + .toBodilessEntity(); + } + + @Override + public void restartApplication(String applicationName) { + stopApplication(applicationName); + startApplication(applicationName); + } + + @Override + public void stopApplication(String applicationName) { + UUID guid = getApplicationGuid(applicationName); + cc.getRestClient() + .post() + .uri("/v3/apps/{guid}/actions/stop", guid) + .retrieve() + .toBodilessEntity(); + } + + @Override + public void rename(String applicationName, String newName) { + UUID guid = getApplicationGuid(applicationName); + cc.getRestClient() + .patch() + .uri("/v3/apps/{guid}", guid) + .body(Map.of("name", newName)) + .retrieve() + .toBodilessEntity(); + } + + @Override + public void updateApplicationInstances(String applicationName, int instances) { + scaleWebProcess(getApplicationGuid(applicationName), Map.of("instances", instances)); + } + + @Override + public void updateApplicationMemory(String applicationName, int memory) { + scaleWebProcess(getApplicationGuid(applicationName), Map.of("memory_in_mb", memory)); + } + + @Override + public void updateApplicationDiskQuota(String applicationName, int disk) { + scaleWebProcess(getApplicationGuid(applicationName), Map.of("disk_in_mb", disk)); + } + + @Override + public void updateApplicationEnv(String applicationName, Map env) { + UUID guid = getApplicationGuid(applicationName); + cc.getRestClient() + .patch() + .uri("/v3/apps/{guid}/environment_variables", guid) + .body(Map.of("var", env)) + .retrieve() + .toBodilessEntity(); + } + + @Override + public void bindDropletToApp(UUID dropletGuid, UUID applicationGuid) { + cc.getRestClient() + .patch() + .uri("/v3/apps/{guid}/relationships/current_droplet", applicationGuid) + .body(Map.of("data", Map.of("guid", dropletGuid.toString()))) + .retrieve() + .toBodilessEntity(); + } + + private void scaleWebProcess(UUID applicationGuid, Map scaleBody) { + cc.getRestClient() + .post() + .uri("/v3/apps/{guid}/processes/web/actions/scale", applicationGuid) + .body(scaleBody) + .retrieve() + .toBodilessEntity(); + } + + @Override + public List unbindServiceInstance(String applicationName, String serviceInstanceName) { + return serviceBindingsOps.unbindServiceInstance(applicationName, serviceInstanceName); + } + + @Override + public List unbindServiceInstance(UUID applicationGuid, UUID serviceInstanceGuid) { + return serviceBindingsOps.unbindServiceInstance(applicationGuid, serviceInstanceGuid); + } + + @Override + public void updateApplicationStaging(String applicationName, Staging staging) { + processesOps.updateApplicationStaging(applicationName, staging); + } + + @Override + public void updateApplicationRoutes(String applicationName, Set routes) { + routesOps.updateApplicationRoutes(applicationName, routes); + } + + @Override + public String updateServiceBroker(CloudServiceBroker serviceBroker) { + return serviceBrokersOps.updateServiceBroker(serviceBroker); + } + + @Override + public void updateServicePlanVisibilityForBroker(String name, ServicePlanVisibility visibility) { + serviceBrokersOps.updateServicePlanVisibilityForBroker(name, visibility); + } + + @Override + public void updateServicePlan(String serviceName, String planName) { + serviceInstancesOps.updateServicePlan(serviceName, planName); + } + + @Override + public void updateServiceParameters(String serviceName, Map parameters) { + serviceInstancesOps.updateServiceParameters(serviceName, parameters); + } + + @Override + public void updateServiceSyslogDrainUrl(String serviceName, String syslogDrainUrl) { + serviceInstancesOps.updateServiceSyslogDrainUrl(serviceName, syslogDrainUrl); + } + + @Override + public void updateServiceTags(String serviceName, List tags) { + serviceInstancesOps.updateServiceTags(serviceName, tags); + } + + @Override + public CloudPackage asyncUploadApplication(String applicationName, Path file, UploadStatusCallback callback, Duration uploadTimeout) { + return packagesOps.asyncUploadApplication(applicationName, file, callback, uploadTimeout); + } + + @Override + public Upload getUploadStatus(UUID packageGuid) { + return packagesOps.getUploadStatus(packageGuid); + } + + @Override + public CloudTask getTask(UUID taskGuid) { + return tasksOps.getTask(taskGuid); + } + + @Override + public List getTasks(String applicationName) { + return tasksOps.getTasks(applicationName); + } + + @Override + public CloudTask runTask(String applicationName, CloudTask task) { + return tasksOps.runTask(applicationName, task); + } + + @Override + public CloudTask cancelTask(UUID taskGuid) { + return tasksOps.cancelTask(taskGuid); + } + + @Override + public CloudBuild createBuild(UUID packageGuid) { + return buildsOps.createBuild(packageGuid); + } + + @Override + public CloudBuild getBuild(UUID packageGuid) { + return buildsOps.getBuild(packageGuid); + } + + @Override + public List getBuildsForApplication(UUID applicationGuid) { + return buildsOps.getBuildsForApplication(applicationGuid); + } + + @Override + public Map getServiceInstanceParameters(UUID guid) { + return serviceInstancesOps.getServiceInstanceParameters(guid); + } + + @Override + public Map getUserProvidedServiceInstanceParameters(UUID guid) { + return serviceInstancesOps.getUserProvidedServiceInstanceParameters(guid); + } + + @Override + public Map getServiceBindingParameters(UUID guid) { + return serviceBindingsOps.getServiceBindingParameters(guid); + } + + @Override + public List getBuildsForPackage(UUID packageGuid) { + return buildsOps.getBuildsForPackage(packageGuid); + } + + @Override + public List getServiceInstancesWithoutAuxiliaryContentByNames(List names) { + return serviceInstancesOps.getServiceInstancesWithoutAuxiliaryContentByNames(names); + } + + @Override + public List getServiceInstancesByMetadataLabelSelector(String labelSelector) { + return serviceInstancesOps.getServiceInstancesByMetadataLabelSelector(labelSelector); + } + + @Override + public List getServiceInstancesWithoutAuxiliaryContentByMetadataLabelSelector(String labelSelector) { + return serviceInstancesOps.getServiceInstancesWithoutAuxiliaryContentByMetadataLabelSelector(labelSelector); + } + + @Override + public void updateApplicationMetadata(UUID guid, Metadata metadata) { + cc.getRestClient() + .patch() + .uri("/v3/apps/{guid}", guid) + .body(Map.of("metadata", Map.of("labels", metadata.getLabels(), "annotations", metadata.getAnnotations()))) + .retrieve() + .toBodilessEntity(); + } + + @Override + public void updateServiceInstanceMetadata(UUID guid, Metadata metadata) { + serviceInstancesOps.updateServiceInstanceMetadata(guid, metadata); + } + + @Override + public void updateServiceBindingMetadata(UUID guid, Metadata metadata) { + serviceBindingsOps.updateServiceBindingMetadata(guid, metadata); + } + + @Override + public DropletInfo getCurrentDropletForApplication(UUID applicationGuid) { + return processesOps.getCurrentDropletForApplication(applicationGuid); + } + + @Override + public CloudPackage getPackage(UUID packageGuid) { + return packagesOps.getPackage(packageGuid); + } + + @Override + public List getPackagesForApplication(UUID applicationGuid) { + return packagesOps.getPackagesForApplication(applicationGuid); + } + + @Override + public Set getUserRolesBySpaceAndUser(UUID spaceGuid, UUID userGuid) { + return rolesOps.getUserRolesBySpaceAndUser(spaceGuid, userGuid); + } + + @Override + public CloudPackage createDockerPackage(UUID applicationGuid, DockerInfo dockerInfo) { + return packagesOps.createDockerPackage(applicationGuid, dockerInfo); + } + + @Override + public CloudAsyncJob getAsyncJob(String jobId) { + return jobsOps.getAsyncJob(jobId); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Client.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Client.java new file mode 100644 index 0000000000..de312d1a0b --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Client.java @@ -0,0 +1,191 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.net.URI; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudException; +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Job; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.RestClient; + +/** + * Low-level Cloud Controller v3 access shared by every operation of {@link CloudControllerRestClientV3Impl}. This is the reusable + * machinery of the cf-java-client replacement: + *

    + *
  • typed {@code GET} / list helpers over the configured {@link RestClient} (error mapping is already attached to the RestClient via + * {@link CloudControllerResponseErrorHandler});
  • + *
  • a {@link #list(String, ParameterizedTypeReference) pagination walker} that follows {@code pagination.next.href} across pages;
  • + *
  • an {@link #waitForAsyncJob(String, Duration) async job poller} reproducing the OSS {@code JobV3Util} contract (exponential + * backoff 1s→15s, capped at a timeout, until COMPLETE/FAILED).
  • + *
+ * It deals in the thin {@code resources.*} wire types; mapping those to the {@code domain.Cloud*} model happens in the per-resource + * operations that call this. + */ +public class CloudControllerV3Client { + + private static final Logger LOGGER = LoggerFactory.getLogger(CloudControllerV3Client.class); + + // Mirrors the OSS JobV3Util backoff so async behaviour is unchanged. + private static final Duration JOB_POLL_MIN_INTERVAL = Duration.ofSeconds(1); + private static final Duration JOB_POLL_MAX_INTERVAL = Duration.ofSeconds(15); + private static final Duration DEFAULT_JOB_TIMEOUT = Duration.ofMinutes(5); + + private final RestClient restClient; + + public CloudControllerV3Client(RestClient restClient) { + this.restClient = restClient; + } + + public T get(String uri, Class responseType) { + LOGGER.warn("[cf-v3-poc] GET {}", uri); + return restClient.get() + .uri(uri) + .retrieve() + .body(responseType); + } + + /** + * GET a single resource, returning empty on a 404 rather than throwing — several callers rely on "not found" being a normal, + * non-exceptional outcome (the OSS impl expressed this with {@code onErrorResume} on NOT_FOUND). + */ + public Optional getOptional(String uri, Class responseType) { + try { + return Optional.ofNullable(get(uri, responseType)); + } catch (CloudOperationException e) { + if (e.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } + throw e; + } + } + + /** + * Walk every page of a v3 list endpoint, following {@code pagination.next.href} until it is {@code null}. The caller supplies the + * fully-formed first-page URI (path + query) and a type reference for the paged {@link V3ListResponse}. + */ + public List list(String firstPageUri, ParameterizedTypeReference> pageType) { + List all = new ArrayList<>(); + String nextUri = firstPageUri; + while (nextUri != null) { + LOGGER.warn("[cf-v3-poc] LIST {}", nextUri); + V3ListResponse page = restClient.get() + .uri(nextUri) + .retrieve() + .body(pageType); + if (page == null) { + break; + } + all.addAll(page.resources()); + nextUri = toRelativeIfAbsolute(page.nextPageHref()); + } + return all; + } + + /** + * Follow the {@code Location} header of a 202-Accepted async response to its job, then poll to completion. + */ + public void followAsyncJob(ResponseEntity acceptedResponse, Duration timeout) { + URI location = acceptedResponse.getHeaders() + .getLocation(); + if (location == null) { + return; // Operation completed synchronously (no job to poll). + } + waitForAsyncJob(extractJobGuid(location.toString()), timeout); + } + + public void waitForAsyncJob(String jobGuid) { + waitForAsyncJob(jobGuid, DEFAULT_JOB_TIMEOUT); + } + + /** + * Poll {@code GET /v3/jobs/{guid}} until the job reaches a terminal state, with exponential backoff bounded by {@code timeout}. + * Throws {@link CloudOperationException} if the job fails, or if it does not complete within the timeout — matching the OSS + * {@code JobV3Util} contract. + */ + public V3Job waitForAsyncJob(String jobGuid, Duration timeout) { + long deadlineNanos = System.nanoTime() + timeout.toNanos(); + Duration interval = JOB_POLL_MIN_INTERVAL; + while (true) { + V3Job job = get("/v3/jobs/" + jobGuid, V3Job.class); + if (job != null && job.isTerminal()) { + if (job.isFailed()) { + throw jobFailed(job); + } + return job; + } + if (System.nanoTime() >= deadlineNanos) { + throw new CloudOperationException(HttpStatus.GATEWAY_TIMEOUT, "Job timeout", + "Job " + jobGuid + " did not complete within " + timeout); + } + sleep(interval); + interval = nextInterval(interval); + } + } + + private static Duration nextInterval(Duration current) { + Duration doubled = current.multipliedBy(2); + return doubled.compareTo(JOB_POLL_MAX_INTERVAL) > 0 ? JOB_POLL_MAX_INTERVAL : doubled; + } + + private static void sleep(Duration interval) { + try { + Thread.sleep(interval.toMillis()); + } catch (InterruptedException e) { + Thread.currentThread() + .interrupt(); + throw new CloudException("Interrupted while polling an async job", e); + } + } + + private static CloudOperationException jobFailed(V3Job job) { + String detail = "Job failed"; + if (job.errors() != null && !job.errors() + .isEmpty()) { + detail = job.errors() + .stream() + .map(V3Job.V3Error::detail) + .reduce((a, b) -> a + "\n" + b) + .orElse(detail); + } + return new CloudOperationException(HttpStatus.UNPROCESSABLE_ENTITY, "Job failed", detail); + } + + private static String extractJobGuid(String location) { + int idx = location.lastIndexOf("/v3/jobs/"); + if (idx < 0) { + return location.substring(location.lastIndexOf('/') + 1); + } + return location.substring(idx + "/v3/jobs/".length()); + } + + // The next.href CF returns is absolute; RestClient has a baseUrl, so strip the origin to a path+query to keep requests baseUrl-relative. + private String toRelativeIfAbsolute(String href) { + if (href == null) { + return null; + } + URI uri = URI.create(href); + if (uri.isAbsolute()) { + String pathAndQuery = uri.getRawPath(); + if (uri.getRawQuery() != null) { + pathAndQuery = pathAndQuery + "?" + uri.getRawQuery(); + } + return pathAndQuery; + } + return href; + } + + public RestClient getRestClient() { + return restClient; + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3Operations.java new file mode 100644 index 0000000000..f80b4c2ff9 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3Operations.java @@ -0,0 +1,184 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.time.Duration; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudDomain; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEntity; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Domain; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3DomainMapper; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.util.Assert; + +/** + * CF v3 Domains operations of the in-house {@code CloudControllerRestClient}. Reproduces the HTTP shape, filtering and domain + * mapping of the OSS {@code CloudControllerRestClientImpl} domain methods on top of the shared {@link CloudControllerV3Client} machinery. + * + *

+ * Endpoint map (see {@code docs/cf-java-client-migration/02-endpoint-inventory.md}): + *

+ *
    + *
  • {@code addDomain} → {@code POST /v3/domains} (org-scoped, non-async)
  • + *
  • {@code deleteDomain} → {@code DELETE /v3/domains/{guid}} (async job)
  • + *
  • {@code getDefaultDomain} → {@code GET /v3/organizations/{guid}/domains/default}
  • + *
  • {@code getDomains} / {@code getSharedDomains} / {@code getPrivateDomains} → {@code GET /v3/domains} (paginated, filtered)
  • + *
  • {@code getDomainsForOrganization} → {@code GET /v3/organizations/{guid}/domains} (paginated)
  • + *
+ */ +public class DomainsV3Operations { + + private static final int PER_PAGE = 5000; + // Mirrors the OSS DELETE_JOB_TIMEOUT (Duration.ofMinutes(5)) used for domain/route/service deletes. + private static final Duration DELETE_JOB_TIMEOUT = Duration.ofMinutes(5); + + private static final ParameterizedTypeReference> DOMAIN_LIST_TYPE = new ParameterizedTypeReference<>() { + }; + + private final CloudControllerV3Client cc; + private final CloudSpace target; + + public DomainsV3Operations(CloudControllerV3Client cc, CloudSpace target) { + this.cc = cc; + this.target = target; + } + + /** + * Create a private domain in the target organization, but only if a domain with that name does not already exist — matching the OSS + * {@code addDomain}, which no-ops when the domain is already present. + */ + public void addDomain(String domainName) { + assertSpaceProvided("add domain"); + CloudDomain domain = findDomainByName(domainName); + if (domain == null) { + doCreateDomain(domainName); + } + } + + /** + * Delete the (required) domain by name. Reproduces the OSS {@code deleteDomain}: a missing domain throws + * {@code CloudOperationException(NOT_FOUND)}, and the delete follows the async job to completion. + */ + public void deleteDomain(String domainName) { + assertSpaceProvided("delete domain"); + CloudDomain domain = findDomainByName(domainName, true); + doDeleteDomain(domain.getGuid()); + } + + /** + * {@code GET /v3/organizations/{guid}/domains/default} → {@link CloudDomain}. Non-paginated single fetch, mapping the OSS + * {@code getDefaultDomain}. + */ + public CloudDomain getDefaultDomain() { + V3Domain domain = cc.get("/v3/organizations/" + getTargetOrganizationGuid() + "/domains/default", V3Domain.class); + return V3DomainMapper.toCloudDomain(domain); + } + + /** + * All domains visible to the target ({@code GET /v3/domains}). + */ + public List getDomains() { + return getAllDomains().stream() + .map(V3DomainMapper::toCloudDomain) + .collect(Collectors.toList()); + } + + /** + * Shared domains only — those whose {@code relationships.organization.data} is {@code null}, matching the OSS + * {@code getSharedDomainResources} filter. + */ + public List getSharedDomains() { + return getAllDomains().stream() + .filter(domain -> !domain.isPrivate()) + .map(V3DomainMapper::toCloudDomain) + .collect(Collectors.toList()); + } + + /** + * Private domains only — those whose {@code relationships.organization.data} is present, matching the OSS + * {@code getPrivateDomainResources} filter. + */ + public List getPrivateDomains() { + return getAllDomains().stream() + .filter(V3Domain::isPrivate) + .map(V3DomainMapper::toCloudDomain) + .collect(Collectors.toList()); + } + + /** + * Domains available to the target organization ({@code GET /v3/organizations/{guid}/domains}), matching the OSS + * {@code getDomainsForOrganization}. + */ + public List getDomainsForOrganization() { + assertSpaceProvided("access organization domains"); + String uri = "/v3/organizations/" + getTargetOrganizationGuid() + "/domains?per_page=" + PER_PAGE; + return cc.list(uri, DOMAIN_LIST_TYPE) + .stream() + .map(V3DomainMapper::toCloudDomain) + .collect(Collectors.toList()); + } + + private List getAllDomains() { + return cc.list("/v3/domains?per_page=" + PER_PAGE, DOMAIN_LIST_TYPE); + } + + private CloudDomain findDomainByName(String name, boolean required) { + CloudDomain domain = findDomainByName(name); + if (domain == null && required) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Domain " + name + " not found."); + } + return domain; + } + + private CloudDomain findDomainByName(String name) { + String uri = "/v3/domains?names=" + name + "&per_page=" + PER_PAGE; + return cc.list(uri, DOMAIN_LIST_TYPE) + .stream() + .findFirst() + .map(V3DomainMapper::toCloudDomain) + .orElse(null); + } + + private void doCreateDomain(String name) { + cc.getRestClient() + .post() + .uri("/v3/domains") + .body(Map.of("name", name, "relationships", + Map.of("organization", Map.of("data", Map.of("guid", getTargetOrganizationGuid().toString()))))) + .retrieve() + .toBodilessEntity(); + } + + private void doDeleteDomain(UUID guid) { + ResponseEntity response = cc.getRestClient() + .delete() + .uri("/v3/domains/{guid}", guid.toString()) + .retrieve() + .toEntity(Void.class); + cc.followAsyncJob(response, DELETE_JOB_TIMEOUT); + } + + private UUID getTargetOrganizationGuid() { + return getGuid(target.getOrganization()); + } + + private UUID getGuid(CloudEntity entity) { + if (entity == null || entity.getMetadata() == null) { + return null; + } + return entity.getMetadata() + .getGuid(); + } + + private void assertSpaceProvided(String operation) { + Assert.notNull(target, "Unable to " + operation + " without specifying organization and space to use."); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/EventsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/EventsV3Operations.java new file mode 100644 index 0000000000..1566597540 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/EventsV3Operations.java @@ -0,0 +1,83 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.List; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEvent; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Application; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3AuditEvent; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3AuditEventMapper; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; + +/** + * CF v3 audit event operations for the cf-java-client replacement. Reproduces the HTTP shape and domain-mapping of the OSS + * {@code CloudControllerRestClientImpl} event methods: + *
    + *
  • {@code getEvents()} → {@code GET /v3/audit_events} (paginated);
  • + *
  • {@code getEventsByTarget(uuid)} → {@code GET /v3/audit_events?target_guids=} (paginated);
  • + *
  • {@code getApplicationEvents(name)} → resolve the app guid in the target space, then {@code getEventsByTarget}.
  • + *
+ * Audit events are a global resource, so — like the OSS impl — the listings are not filtered by the target space (only by target guid where + * requested). Application-name resolution mirrors the OSS impl's {@code getRequiredApplicationGuid}: it queries the target space and throws + * {@link CloudOperationException} with {@link HttpStatus#NOT_FOUND} when the application does not exist. + */ +public class EventsV3Operations { + + // CF v3 caps per_page at 5000; use a large page to minimise round-trips (the pagination walker still handles multiple pages). + private static final int DEFAULT_PAGE_SIZE = 5000; + + private static final ParameterizedTypeReference> EVENT_PAGE = new ParameterizedTypeReference<>() { + }; + private static final ParameterizedTypeReference> APPLICATION_PAGE = new ParameterizedTypeReference<>() { + }; + + private final CloudControllerV3Client cc; + private final CloudSpace target; + + public EventsV3Operations(CloudControllerV3Client cc, CloudSpace target) { + this.cc = cc; + this.target = target; + } + + public List getEvents() { + return cc.list("/v3/audit_events?per_page=" + DEFAULT_PAGE_SIZE, EVENT_PAGE) + .stream() + .map(V3AuditEventMapper::toCloudEvent) + .toList(); + } + + public List getEventsByTarget(UUID uuid) { + String query = "/v3/audit_events?per_page=" + DEFAULT_PAGE_SIZE + "&target_guids=" + uuid; + return cc.list(query, EVENT_PAGE) + .stream() + .map(V3AuditEventMapper::toCloudEvent) + .toList(); + } + + public List getApplicationEvents(String applicationName) { + UUID applicationGuid = getRequiredApplicationGuid(applicationName); + return getEventsByTarget(applicationGuid); + } + + // Mirrors the OSS impl's getRequiredApplicationGuid: look up the app in the target space and 404 if it is absent. + private UUID getRequiredApplicationGuid(String applicationName) { + StringBuilder query = new StringBuilder("/v3/apps?per_page=" + DEFAULT_PAGE_SIZE); + if (target != null && target.getGuid() != null) { + query.append("&space_guids=") + .append(target.getGuid()); + } + query.append("&names=") + .append(applicationName); + List apps = cc.list(query.toString(), APPLICATION_PAGE); + if (apps.isEmpty()) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Application " + applicationName + " not found."); + } + return UUID.fromString(apps.get(0) + .guid()); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/JobsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/JobsV3Operations.java new file mode 100644 index 0000000000..b2048dc6b3 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/JobsV3Operations.java @@ -0,0 +1,31 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudAsyncJob; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Job; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3JobMapper; + +/** + * CF v3 Jobs operations of the in-house {@code CloudControllerRestClient}. Reproduces the HTTP shape and domain mapping of the OSS + * {@code CloudControllerRestClientImpl#getAsyncJob} on top of the shared {@link CloudControllerV3Client} machinery. + */ +public class JobsV3Operations { + + private final CloudControllerV3Client cc; + private final CloudSpace target; + + public JobsV3Operations(CloudControllerV3Client cc, CloudSpace target) { + this.cc = cc; + this.target = target; + } + + /** + * {@code GET /v3/jobs/{guid}} → {@link CloudAsyncJob}. Non-paginated, synchronous fetch. A missing job yields a + * {@code CloudOperationException(NOT_FOUND)}, matching the OSS {@code fetch(...)} contract (which does not swallow 404s here). + */ + public CloudAsyncJob getAsyncJob(String jobId) { + V3Job job = cc.get("/v3/jobs/" + jobId, V3Job.class); + return V3JobMapper.toCloudAsyncJob(job); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3Operations.java new file mode 100644 index 0000000000..689e2a9305 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3Operations.java @@ -0,0 +1,212 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.nio.file.Path; +import java.time.Duration; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.UploadStatusCallback; +import org.cloudfoundry.multiapps.controller.client.facade.domain.BitsData; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudPackage; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.DockerCredentials; +import org.cloudfoundry.multiapps.controller.client.facade.domain.DockerInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ErrorDetails; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableErrorDetails; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableUpload; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Status; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Upload; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Package; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3PackageMapper; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.core.io.FileSystemResource; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.util.Assert; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +/** + * CF v3 packages operations of the cf-java-client replacement. Reproduces the HTTP shape and domain mapping of the OSS + * {@code CloudControllerRestClientImpl} package methods: + *
    + *
  • {@code getPackage(guid)} → {@code GET /v3/packages/{guid}};
  • + *
  • {@code getPackagesForApplication(appGuid)} → {@code GET /v3/apps/{guid}/packages} (paginated);
  • + *
  • {@code createDockerPackage(appGuid, dockerInfo)} → {@code POST /v3/packages} then re-fetch;
  • + *
  • {@code asyncUploadApplication(...)} → create a bits package ({@code POST /v3/packages}), + * upload the bits multipart ({@code POST /v3/packages/{guid}/upload}), then poll status in a background thread;
  • + *
  • {@code getUploadStatus(guid)} → derived from {@code getPackage(guid)}.
  • + *
+ */ +public class PackagesV3Operations { + + // CF v3 caps per_page at 5000; use a large page to minimise round-trips (the pagination walker still handles multiple pages). + private static final int DEFAULT_PAGE_SIZE = 5000; + // Mirrors the OSS CloudControllerRestClientImpl.PACKAGE_UPLOAD_JOB_POLLING_PERIOD. + private static final long PACKAGE_UPLOAD_JOB_POLLING_PERIOD = TimeUnit.SECONDS.toMillis(5); + + private static final ParameterizedTypeReference> PACKAGE_PAGE = new ParameterizedTypeReference<>() { + }; + + private final CloudControllerV3Client cc; + private final CloudSpace target; + + public PackagesV3Operations(CloudControllerV3Client cc, CloudSpace target) { + this.cc = cc; + this.target = target; + } + + public CloudPackage getPackage(UUID packageGuid) { + V3Package resource = cc.get("/v3/packages/" + packageGuid, V3Package.class); + return resource == null ? null : V3PackageMapper.toCloudPackage(resource); + } + + public List getPackagesForApplication(UUID applicationGuid) { + return cc.list("/v3/apps/" + applicationGuid + "/packages?per_page=" + DEFAULT_PAGE_SIZE, PACKAGE_PAGE) + .stream() + .map(V3PackageMapper::toCloudPackage) + .toList(); + } + + public CloudPackage createDockerPackage(UUID applicationGuid, DockerInfo dockerInfo) { + java.util.Map data = new java.util.HashMap<>(); + data.put("image", dockerInfo.getImage()); + DockerCredentials credentials = dockerInfo.getCredentials(); + if (credentials != null) { + if (credentials.getUsername() != null) { + data.put("username", credentials.getUsername()); + } + if (credentials.getPassword() != null) { + data.put("password", credentials.getPassword()); + } + } + V3Package created = cc.getRestClient() + .post() + .uri("/v3/packages") + .body(java.util.Map.of("type", "docker", "data", data, "relationships", + applicationRelationship(applicationGuid))) + .retrieve() + .body(V3Package.class); + return getPackage(UUID.fromString(created.guid())); + } + + public Upload getUploadStatus(UUID packageGuid) { + CloudPackage cloudPackage = getPackage(packageGuid); + ErrorDetails errorDetails = null; + if (cloudPackage.getType() == CloudPackage.Type.BITS) { + errorDetails = ImmutableErrorDetails.builder() + .description(((BitsData) cloudPackage.getData()).getError()) + .build(); + } + return ImmutableUpload.builder() + .status(cloudPackage.getStatus()) + .errorDetails(errorDetails) + .build(); + } + + public CloudPackage asyncUploadApplication(String applicationName, Path file, UploadStatusCallback callback, Duration uploadTimeout) { + CloudPackage cloudPackage = startUpload(applicationName, file, uploadTimeout); + processAsyncUploadInBackground(cloudPackage, callback); + return cloudPackage; + } + + private CloudPackage startUpload(String applicationName, Path file, Duration uploadTimeout) { + Assert.notNull(applicationName, "AppName must not be null"); + Assert.notNull(file, "File must not be null"); + + UUID applicationGuid = getRequiredApplicationGuid(applicationName); + UUID packageGuid = createBitsPackage(applicationGuid).getGuid(); + + MultiValueMap body = new LinkedMultiValueMap<>(); + body.add("bits", new FileSystemResource(file)); + cc.getRestClient() + .post() + .uri("/v3/packages/{guid}/upload", packageGuid) + .contentType(MediaType.MULTIPART_FORM_DATA) + .body(body) + .retrieve() + .toBodilessEntity(); + + return getPackage(packageGuid); + } + + private CloudPackage createBitsPackage(UUID applicationGuid) { + V3Package created = cc.getRestClient() + .post() + .uri("/v3/packages") + .body(java.util.Map.of("type", "bits", "relationships", applicationRelationship(applicationGuid))) + .retrieve() + .body(V3Package.class); + return getPackage(UUID.fromString(created.guid())); + } + + private static java.util.Map applicationRelationship(UUID applicationGuid) { + return java.util.Map.of("app", java.util.Map.of("data", java.util.Map.of("guid", applicationGuid.toString()))); + } + + private UUID getRequiredApplicationGuid(String applicationName) { + StringBuilder query = new StringBuilder("/v3/apps?per_page=" + DEFAULT_PAGE_SIZE); + if (target != null && target.getGuid() != null) { + query.append("&space_guids=") + .append(target.getGuid()); + } + query.append("&names=") + .append(applicationName); + List apps = cc.list(query.toString(), new ParameterizedTypeReference>() { + }); + if (apps.isEmpty() || apps.get(0) + .guid() == null) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Application " + applicationName + " not found."); + } + return UUID.fromString(apps.get(0) + .guid()); + } + + private void processAsyncUploadInBackground(CloudPackage cloudPackage, UploadStatusCallback callback) { + String threadName = String.format("App upload monitor: %s", cloudPackage.getGuid()); + new Thread(() -> processAsyncUpload(cloudPackage, callback), threadName).start(); + } + + private void processAsyncUpload(CloudPackage cloudPackage, UploadStatusCallback callback) { + while (true) { + Upload upload = getUploadStatus(cloudPackage.getGuid()); + Status uploadStatus = upload.getStatus(); + boolean unsubscribe = callback.onProgress(uploadStatus.toString()); + if (unsubscribe || isUploadReady(uploadStatus)) { + return; + } + if (hasUploadFailed(uploadStatus)) { + callback.onError(upload.getErrorDetails() + .getDescription()); + return; + } + try { + Thread.sleep(PACKAGE_UPLOAD_JOB_POLLING_PERIOD); + } catch (InterruptedException e) { + Thread.currentThread() + .interrupt(); + return; + } + } + } + + private boolean isUploadReady(Status status) { + return status == Status.READY; + } + + private boolean hasUploadFailed(Status status) { + return status == Status.EXPIRED || status == Status.FAILED; + } + + /** + * Minimal wire-model of a v3 app resource, used only to resolve an application's GUID by name when starting an upload. The + * fully-featured app wire model lives in {@code V3Application}; this local record keeps the packages helper self-contained. + */ + @com.fasterxml.jackson.annotation.JsonIgnoreProperties(ignoreUnknown = true) + private record V3App(@com.fasterxml.jackson.annotation.JsonProperty("guid") String guid) { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3Operations.java new file mode 100644 index 0000000000..def82d3124 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3Operations.java @@ -0,0 +1,237 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudProcess; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.DropletInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableDropletInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableInstancesInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.InstancesInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Staging; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Application; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Droplet; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3InstancesInfoMapper; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Process; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ProcessMapper; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; + +/** + * CF v3 process-family operations for the cf-java-client replacement. Reproduces the HTTP shape and domain mapping of the + * OSS {@code CloudControllerRestClientImpl} process/instance/feature/droplet methods: + *
    + *
  • {@code getApplicationProcess(appGuid)} → {@code GET /v3/apps/{guid}/processes/web};
  • + *
  • {@code getApplicationInstances(app)} / {@code getApplicationInstances(appGuid)} → + * {@code GET /v3/apps/{guid}/processes/web/stats} (empty for a non-STARTED app);
  • + *
  • {@code getApplicationSshEnabled(appGuid)} → {@code GET /v3/apps/{guid}/ssh_enabled} (default {@code false});
  • + *
  • {@code getApplicationFeatures(appGuid)} → {@code GET /v3/apps/{guid}/features} (paginated);
  • + *
  • {@code getCurrentDropletForApplication(appGuid)} → {@code GET /v3/apps/{guid}/droplets/current} (404 → + * {@link CloudOperationException});
  • + *
  • {@code updateApplicationStaging(appName, staging)} → {@code PATCH /v3/apps/{guid}} (lifecycle) then update the web process + * ({@code PATCH /v3/apps/{guid}/features/{name}} per feature and {@code PATCH /v3/processes/{guid}}).
  • + *
+ */ +public class ProcessesV3Operations { + + // CF v3 caps per_page at 5000; use a large page to minimise round-trips (the pagination walker still handles multiple pages). + private static final int DEFAULT_PAGE_SIZE = 5000; + // The "package" link key in the current-droplet response; the package GUID is the last path segment of its href. + private static final String PACKAGE_LINK = "package"; + private static final String WEB_PROCESS_TYPE = "web"; + + private final CloudControllerV3Client cc; + private final CloudSpace target; + + public ProcessesV3Operations(CloudControllerV3Client cc, CloudSpace target) { + this.cc = cc; + this.target = target; + } + + public CloudProcess getApplicationProcess(UUID applicationGuid) { + V3Process process = cc.get("/v3/apps/" + applicationGuid + "/processes/" + WEB_PROCESS_TYPE, V3Process.class); + return process == null ? null : V3ProcessMapper.toCloudProcess(process); + } + + public InstancesInfo getApplicationInstances(CloudApplication application) { + if (application.getState() + .equals(CloudApplication.State.STARTED)) { + return findApplicationInstances(application.getGuid()); + } + return ImmutableInstancesInfo.builder() + .instances(Collections.emptyList()) + .build(); + } + + public InstancesInfo getApplicationInstances(UUID applicationGuid) { + return findApplicationInstances(applicationGuid); + } + + private InstancesInfo findApplicationInstances(UUID applicationGuid) { + V3Process.V3ProcessStats stats = cc.get("/v3/apps/" + applicationGuid + "/processes/" + WEB_PROCESS_TYPE + "/stats", + V3Process.V3ProcessStats.class); + return V3InstancesInfoMapper.toInstancesInfo(stats); + } + + public boolean getApplicationSshEnabled(UUID applicationGuid) { + V3Process.V3SshEnabled sshEnabled = cc.get("/v3/apps/" + applicationGuid + "/ssh_enabled", V3Process.V3SshEnabled.class); + if (sshEnabled == null || sshEnabled.enabled() == null) { + return false; + } + return sshEnabled.enabled(); + } + + public Map getApplicationFeatures(UUID applicationGuid) { + List features = cc.list("/v3/apps/" + applicationGuid + "/features?per_page=" + DEFAULT_PAGE_SIZE, + new ParameterizedTypeReference>() { + }); + Map result = new HashMap<>(); + for (V3Process.V3AppFeature feature : features) { + result.put(feature.name(), feature.enabled()); + } + return result; + } + + public DropletInfo getCurrentDropletForApplication(UUID applicationGuid) { + V3Droplet droplet = cc.get("/v3/apps/" + applicationGuid + "/droplets/current", V3Droplet.class); + if (droplet == null) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not found", + "Application with guid " + applicationGuid + " does not have a droplet"); + } + return parseDropletInfo(droplet); + } + + private DropletInfo parseDropletInfo(V3Droplet droplet) { + String packageUrl = droplet.links() + .get(PACKAGE_LINK) + .href(); + if (packageUrl.endsWith("/")) { + packageUrl = packageUrl.substring(0, packageUrl.lastIndexOf("/")); + } + String packageGuid = packageUrl.substring(packageUrl.lastIndexOf("/") + 1); + return ImmutableDropletInfo.builder() + .guid(UUID.fromString(droplet.guid())) + .packageGuid(UUID.fromString(packageGuid)) + .build(); + } + + public void updateApplicationStaging(String applicationName, Staging staging) { + UUID applicationGuid = getRequiredApplicationGuid(applicationName); + cc.getRestClient() + .patch() + .uri("/v3/apps/{guid}", applicationGuid) + .body(Map.of("lifecycle", buildApplicationLifecycle(staging))) + .retrieve() + .toBodilessEntity(); + updateApplicationProcess(applicationGuid, staging); + } + + // Mirrors the OSS impl's buildApplicationLifecycle: Docker if DockerInfo present, otherwise a buildpack (or CNB) lifecycle. + private Map buildApplicationLifecycle(Staging staging) { + if (staging.getDockerInfo() != null) { + return Map.of("type", "docker", "data", Map.of()); + } + String lifecycleType = staging.getLifecycleType() != null ? staging.getLifecycleType() + .name() + .toLowerCase() + : "buildpack"; + if ("cnb".equals(lifecycleType) && (staging.getBuildpacks() == null || staging.getBuildpacks() + .isEmpty())) { + throw new IllegalArgumentException("Buildpacks are required for the CNB lifecycle type"); + } + Map data = new HashMap<>(); + if (staging.getStackName() != null) { + data.put("stack", staging.getStackName()); + } + if (staging.getBuildpacks() != null) { + data.put("buildpacks", staging.getBuildpacks()); + } + return Map.of("type", lifecycleType, "data", data); + } + + // Mirrors the OSS impl's updateApplicationProcess: toggle app features, then PATCH the web process command + health checks. + private void updateApplicationProcess(UUID applicationGuid, Staging staging) { + staging.getAppFeatures() + .forEach((featureName, enabled) -> updateAppFeature(applicationGuid, featureName, enabled)); + V3Process process = cc.get("/v3/apps/" + applicationGuid + "/processes/" + WEB_PROCESS_TYPE, V3Process.class); + Map updateProcessBody = new HashMap<>(); + updateProcessBody.put("command", staging.getCommand()); + if (staging.getHealthCheckType() != null) { + updateProcessBody.put("health_check", buildHealthCheck(staging)); + } + if (staging.getReadinessHealthCheckType() != null) { + updateProcessBody.put("readiness_health_check", buildReadinessHealthCheck(staging)); + } + cc.getRestClient() + .patch() + .uri("/v3/processes/{guid}", process.guid()) + .body(updateProcessBody) + .retrieve() + .toBodilessEntity(); + } + + private void updateAppFeature(UUID applicationGuid, String featureName, boolean enabled) { + cc.getRestClient() + .patch() + .uri("/v3/apps/{guid}/features/{name}", applicationGuid, featureName) + .body(Map.of("enabled", enabled)) + .retrieve() + .toBodilessEntity(); + } + + private Map buildHealthCheck(Staging staging) { + Map data = new HashMap<>(); + putIfNotNull(data, "endpoint", staging.getHealthCheckHttpEndpoint()); + putIfNotNull(data, "timeout", staging.getHealthCheckTimeout()); + putIfNotNull(data, "invocation_timeout", staging.getInvocationTimeout()); + putIfNotNull(data, "interval", staging.getHealthCheckInterval()); + Map healthCheck = new HashMap<>(); + healthCheck.put("type", staging.getHealthCheckType()); + healthCheck.put("data", data); + return healthCheck; + } + + private Map buildReadinessHealthCheck(Staging staging) { + Map data = new HashMap<>(); + putIfNotNull(data, "invocation_timeout", staging.getReadinessHealthCheckInvocationTimeout()); + putIfNotNull(data, "endpoint", staging.getReadinessHealthCheckHttpEndpoint()); + putIfNotNull(data, "interval", staging.getReadinessHealthCheckInterval()); + Map readinessHealthCheck = new HashMap<>(); + readinessHealthCheck.put("type", staging.getReadinessHealthCheckType()); + readinessHealthCheck.put("data", data); + return readinessHealthCheck; + } + + private static void putIfNotNull(Map map, String key, Object value) { + if (value != null) { + map.put(key, value); + } + } + + // Mirrors the OSS impl's getRequiredApplicationGuid: look up the app in the target space and 404 if it is absent. + private UUID getRequiredApplicationGuid(String applicationName) { + StringBuilder query = new StringBuilder("/v3/apps?per_page=" + DEFAULT_PAGE_SIZE); + if (target != null && target.getGuid() != null) { + query.append("&space_guids=") + .append(target.getGuid()); + } + query.append("&names=") + .append(applicationName); + List apps = cc.list(query.toString(), + new ParameterizedTypeReference>() { + }); + if (apps.isEmpty()) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Application " + applicationName + " not found."); + } + return UUID.fromString(apps.get(0) + .guid()); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RolesV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RolesV3Operations.java new file mode 100644 index 0000000000..9a1102b6d6 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RolesV3Operations.java @@ -0,0 +1,46 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.EnumSet; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.UserRole; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Role; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3RoleMapper; +import org.springframework.core.ParameterizedTypeReference; + +/** + * CF v3 {@code Roles} operations for the in-house {@link CloudControllerRestClient} replacement. Implements the role-related methods of + * the interface by listing {@code GET /v3/roles} filtered by space and user, and mapping each wire {@link V3Role} to the project's + * {@link UserRole} domain enum via {@link V3RoleMapper}. + * + *

+ * Mirrors the OSS {@code CloudControllerRestClientImpl#getUserRolesBySpaceAndUser}, which lists roles filtered by {@code space_id}, + * {@code user_id} and all role {@code types}, then collects the results into an {@link EnumSet}. + *

+ */ +public class RolesV3Operations { + + private static final ParameterizedTypeReference> ROLE_PAGE = new ParameterizedTypeReference<>() { + }; + + private final CloudControllerV3Client cc; + private final CloudSpace target; + + public RolesV3Operations(CloudControllerV3Client cc, CloudSpace target) { + this.cc = cc; + this.target = target; + } + + public Set getUserRolesBySpaceAndUser(UUID spaceGuid, UUID userGuid) { + String uri = "/v3/roles?per_page=5000&space_guids=" + spaceGuid + "&user_guids=" + userGuid; + return cc.list(uri, ROLE_PAGE) + .stream() + .map(V3RoleMapper::toUserRole) + .collect(Collectors.toCollection(() -> EnumSet.noneOf(UserRole.class))); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3Operations.java new file mode 100644 index 0000000000..ee9df786f2 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3Operations.java @@ -0,0 +1,362 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.time.Duration; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudRoute; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.RouteDestination; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Domain; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Route; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3RouteMapper; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +/** + * CF v3 routes operations of the cf-java-client replacement. Reproduces the HTTP shape, async handling and domain mapping of the + * OSS {@code CloudControllerRestClientImpl} route methods: + *
    + *
  • {@code addRoute(host, domainName, path)} → resolve the domain, then {@code POST /v3/routes} (idempotent: reuse an existing + * route if one already matches);
  • + *
  • {@code deleteRoute(host, domainName, path)} → resolve the route in the target space and {@code DELETE /v3/routes/{guid}} + * (async job), 404 if not found;
  • + *
  • {@code deleteOrphanedRoutes()} → {@code DELETE /v3/spaces/{guid}/routes?unmapped=true} (async job);
  • + *
  • {@code getRoutes(domainName)} → {@code GET /v3/routes?domain_guids=…&space_guids=…} (paginated);
  • + *
  • {@code getApplicationRoutes(appGuid)} → {@code GET /v3/apps/{guid}/routes} (paginated);
  • + *
  • {@code updateApplicationRoutes(appName, routes)} → diff current vs requested destinations, then insert/remove destinations.
  • + *
+ * All listings are scoped to the target space exactly where the OSS impl scopes them. + */ +public class RoutesV3Operations { + + private static final ParameterizedTypeReference> ROUTE_PAGE = new ParameterizedTypeReference<>() { + }; + private static final ParameterizedTypeReference> DOMAIN_PAGE = new ParameterizedTypeReference<>() { + }; + + // CF v3 caps per_page at 5000; matches the other V3 operations. + private static final int DEFAULT_PAGE_SIZE = 5000; + // Mirrors the OSS DELETE_JOB_TIMEOUT. + private static final Duration DELETE_JOB_TIMEOUT = Duration.ofMinutes(5); + + private final CloudControllerV3Client cc; + private final CloudSpace target; + + public RoutesV3Operations(CloudControllerV3Client cc, CloudSpace target) { + this.cc = cc; + this.target = target; + } + + public void addRoute(String host, String domainName, String path) { + assertSpaceProvided("add route for domain"); + UUID domainGuid = getRequiredDomainGuid(domainName); + doAddRoute(domainGuid, host, path); + } + + public void deleteRoute(String host, String domainName, String path) { + assertSpaceProvided("delete route for domain"); + UUID routeGuid = getRouteGuid(getRequiredDomainGuid(domainName), host, path); + if (routeGuid == null) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", + "Host " + host + " not found for domain " + domainName + "."); + } + doDeleteRoute(routeGuid); + } + + public void deleteOrphanedRoutes() { + ResponseEntity response = cc.getRestClient() + .delete() + .uri("/v3/spaces/{guid}/routes?unmapped=true", getTargetSpaceGuid()) + .retrieve() + .toEntity(Void.class); + cc.followAsyncJob(response, DELETE_JOB_TIMEOUT); + } + + public List getRoutes(String domainName) { + assertSpaceProvided("get routes for domain"); + UUID domainGuid = getRequiredDomainGuid(domainName); + return findRoutesByDomainGuid(domainGuid); + } + + public List getApplicationRoutes(UUID applicationGuid) { + return listRoutes("/v3/apps/" + applicationGuid + "/routes?per_page=" + DEFAULT_PAGE_SIZE).stream() + .map(route -> V3RouteMapper.toCloudRoute( + route, applicationGuid)) + .toList(); + } + + public void updateApplicationRoutes(String applicationName, Set updatedRoutes) { + UUID applicationGuid = getRequiredApplicationGuid(applicationName); + List appRoutes = listRoutes( + "/v3/apps/" + applicationGuid + "/routes?per_page=" + DEFAULT_PAGE_SIZE).stream() + .map(V3RouteMapper::toCloudRoute) + .toList(); + Set outdatedRoutes = getOutdatedRoutes(applicationGuid, appRoutes, updatedRoutes); + Set newRoutes = getNewRoutes(applicationGuid, appRoutes, updatedRoutes); + removeRoutes(outdatedRoutes, applicationGuid); + addRoutes(newRoutes, applicationGuid); + } + + // --- route listing helpers ------------------------------------------------------------------- + + private List findRoutesByDomainGuid(UUID domainGuid) { + StringBuilder query = new StringBuilder("/v3/routes?per_page=" + DEFAULT_PAGE_SIZE); + query.append("&domain_guids=") + .append(domainGuid); + query.append("&space_guids=") + .append(getTargetSpaceGuid()); + return listRoutes(query.toString()).stream() + .map(V3RouteMapper::toCloudRoute) + .toList(); + } + + private UUID getRouteGuid(UUID domainGuid, String host, String path) { + StringBuilder query = new StringBuilder("/v3/routes?per_page=" + DEFAULT_PAGE_SIZE); + query.append("&domain_guids=") + .append(domainGuid); + query.append("&space_guids=") + .append(getTargetSpaceGuid()); + if (host != null) { + query.append("&hosts=") + .append(host); + } + if (path != null) { + query.append("&paths=") + .append(path); + } + List routes = listRoutes(query.toString()); + if (CollectionUtils.isEmpty(routes)) { + return null; + } + return UUID.fromString(routes.get(0) + .guid()); + } + + private List listRoutes(String query) { + return cc.list(query, ROUTE_PAGE); + } + + // --- add / bind / remove --------------------------------------------------------------------- + + private void addRoutes(Set routes, UUID applicationGuid) { + Map domains = getDomainsFromRoutes(routes); + for (CloudRoute route : routes) { + validateDomainForRoute(route, domains); + UUID domainGuid = domains.get(route.getDomain() + .getName()); + UUID routeGuid = getOrAddRoute(domainGuid, route.getHost(), route.getPath()); + bindRoute(routeGuid, applicationGuid, route.getRequestedProtocol()); + } + } + + private void removeRoutes(Set routes, UUID applicationGuid) { + for (CloudRoute route : routes) { + for (RouteDestination destination : route.getDestinations()) { + if (destination.getApplicationGuid() + .equals(applicationGuid)) { + unbindRoute(route.getGuid(), destination.getGuid()); + } + } + } + } + + private UUID getOrAddRoute(UUID domainGuid, String host, String path) { + UUID routeGuid = getRouteGuid(domainGuid, host, path); + if (routeGuid == null) { + routeGuid = doAddRoute(domainGuid, host, path); + } + return routeGuid; + } + + private UUID doAddRoute(UUID domainGuid, String host, String path) { + assertSpaceProvided("add route"); + V3Route created = cc.getRestClient() + .post() + .uri("/v3/routes") + .body(Map.of("host", host == null ? "" : host, "path", path == null ? "" : path, "relationships", + Map.of("domain", toOneRelationship(domainGuid), "space", + toOneRelationship(getTargetSpaceGuid())))) + .retrieve() + .body(V3Route.class); + return UUID.fromString(created.guid()); + } + + private void doDeleteRoute(UUID guid) { + ResponseEntity response = cc.getRestClient() + .delete() + .uri("/v3/routes/{guid}", guid) + .retrieve() + .toEntity(Void.class); + cc.followAsyncJob(response, DELETE_JOB_TIMEOUT); + } + + private void bindRoute(UUID routeGuid, UUID applicationGuid, String protocol) { + cc.getRestClient() + .post() + .uri("/v3/routes/{guid}/destinations", routeGuid) + .body(Map.of("destinations", List.of(createDestination(applicationGuid, protocol)))) + .retrieve() + .toBodilessEntity(); + } + + private void unbindRoute(UUID routeGuid, UUID destinationGuid) { + cc.getRestClient() + .delete() + .uri("/v3/routes/{routeGuid}/destinations/{destinationGuid}", routeGuid, destinationGuid) + .retrieve() + .toBodilessEntity(); + } + + private Map createDestination(UUID applicationGuid, String protocol) { + if (protocol == null) { + return Map.of("app", Map.of("guid", applicationGuid.toString())); + } + return Map.of("app", Map.of("guid", applicationGuid.toString()), "protocol", protocol); + } + + private static Map toOneRelationship(UUID guid) { + return Map.of("data", Map.of("guid", guid.toString())); + } + + // --- diffing (mirror of the OSS impl) -------------------------------------------------------- + + private Set getOutdatedRoutes(UUID applicationGuid, List currentRoutes, Set updatedRoutes) { + return currentRoutes.stream() + .filter(currentRoute -> isRouteOutdated(applicationGuid, currentRoute, updatedRoutes)) + .collect(Collectors.toSet()); + } + + private boolean isRouteOutdated(UUID applicationGuid, CloudRoute currentRoute, Set updatedRoutes) { + Optional updatedRoute = findRoute(currentRoute.getUrl(), updatedRoutes); + if (updatedRoute.isEmpty()) { + return true; + } + return isProtocolChanged(applicationGuid, currentRoute, updatedRoute.get()); + } + + private Set getNewRoutes(UUID applicationGuid, List currentRoutes, Set updatedRoutes) { + return updatedRoutes.stream() + .filter(updatedRoute -> isRouteUpdated(applicationGuid, updatedRoute, currentRoutes)) + .collect(Collectors.toSet()); + } + + private boolean isRouteUpdated(UUID applicationGuid, CloudRoute updatedRoute, List currentRoutes) { + Optional currentRoute = findRoute(updatedRoute.getUrl(), currentRoutes); + if (currentRoute.isEmpty()) { + return true; + } + return isProtocolChanged(applicationGuid, currentRoute.get(), updatedRoute); + } + + private Optional findRoute(String url, java.util.Collection routes) { + return routes.stream() + .filter(route -> Objects.equals(url, route.getUrl())) + .findFirst(); + } + + private boolean isProtocolChanged(UUID applicationGuid, CloudRoute currentRoute, CloudRoute updatedRoute) { + if (updatedRoute.getRequestedProtocol() == null) { + return false; + } + return currentRoute.getDestinations() + .stream() + .filter(routeDestination -> Objects.equals(routeDestination.getApplicationGuid(), applicationGuid)) + .noneMatch(routeDestination -> Objects.equals(routeDestination.getProtocol(), + updatedRoute.getRequestedProtocol())); + } + + private void validateDomainForRoute(CloudRoute route, Map existingDomains) { + String domain = route.getDomain() + .getName(); + if (!StringUtils.hasLength(domain) || !existingDomains.containsKey(domain)) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), + "Domain '" + domain + "' not found for URI " + route.getUrl()); + } + } + + // --- domain resolution (minimal inline lookups; the Domains group owns the full wire model) -- + + private UUID getRequiredDomainGuid(String name) { + UUID guid = findDomainGuidByName(name); + if (guid == null) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Domain " + name + " not found."); + } + return guid; + } + + private UUID findDomainGuidByName(String name) { + List domains = cc.list("/v3/domains?names=" + name + "&per_page=" + DEFAULT_PAGE_SIZE, DOMAIN_PAGE); + if (CollectionUtils.isEmpty(domains)) { + return null; + } + return UUID.fromString(domains.get(0) + .guid()); + } + + private Map getDomainsFromRoutes(Set routes) { + Set domainNames = routes.stream() + .map(route -> route.getDomain() + .getName()) + .filter(StringUtils::hasLength) + .collect(Collectors.toSet()); + if (domainNames.isEmpty()) { + return Map.of(); + } + String names = String.join(",", domainNames); + return cc.list("/v3/domains?names=" + names + "&per_page=" + DEFAULT_PAGE_SIZE, DOMAIN_PAGE) + .stream() + .collect(Collectors.toMap(V3Domain::name, domain -> UUID.fromString(domain.guid()))); + } + + // --- application resolution ------------------------------------------------------------------ + + private UUID getRequiredApplicationGuid(String applicationName) { + StringBuilder query = new StringBuilder("/v3/apps?per_page=" + DEFAULT_PAGE_SIZE); + query.append("&names=") + .append(applicationName); + if (target != null && target.getGuid() != null) { + query.append("&space_guids=") + .append(target.getGuid()); + } + List apps = cc.list(query.toString(), new ParameterizedTypeReference>() { + }); + if (CollectionUtils.isEmpty(apps)) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Application " + applicationName + " not found."); + } + return UUID.fromString(apps.get(0) + .guid()); + } + + private UUID getTargetSpaceGuid() { + return target.getGuid(); + } + + private void assertSpaceProvided(String operation) { + if (target == null) { + throw new IllegalArgumentException("Unable to " + operation + " without specifying organization and space to use."); + } + } + + /** + * Minimal wire-model of a CF v3 application resource — only the {@code guid} needed to resolve an application by name. + */ + @JsonIgnoreProperties(ignoreUnknown = true) + private record V3AppRef(@JsonProperty("guid") String guid) { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3Operations.java new file mode 100644 index 0000000000..25e04c2831 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3Operations.java @@ -0,0 +1,269 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.net.URI; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBinding; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Application; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceBinding; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceBindingMapper; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.util.CollectionUtils; + +/** + * CF v3 service credential bindings (application bindings) operations of the in-house {@code CloudControllerRestClient}. + * Reproduces the HTTP shape, filtering, async handling and domain mapping of the OSS {@code CloudControllerRestClientImpl} binding + * methods on top of the shared {@link CloudControllerV3Client} machinery. + * + *

+ * Endpoint map (see {@code docs/cf-java-client-migration/02-endpoint-inventory.md}): + *

+ *
    + *
  • {@code bindServiceInstance} → {@code POST /v3/service_credential_bindings} (type=app, async job)
  • + *
  • {@code unbindServiceInstance} / {@code deleteServiceBinding} → {@code DELETE /v3/service_credential_bindings/{guid}} (async + * job)
  • + *
  • {@code getServiceBinding} → {@code GET /v3/service_credential_bindings?guids={guid}} (single)
  • + *
  • {@code getServiceAppBindings} → {@code GET /v3/service_credential_bindings?service_instance_guids=…&type=app} (paginated)
  • + *
  • {@code getAppBindings} → {@code GET /v3/service_credential_bindings?app_guids=…} (paginated)
  • + *
  • {@code getServiceBindingsForApplication} → {@code GET /v3/service_credential_bindings?app_guids=…&service_instance_guids=…} + * (paginated)
  • + *
  • {@code getServiceBindingParameters} → {@code GET /v3/service_credential_bindings/{guid}/parameters}
  • + *
  • {@code updateServiceBindingMetadata} → {@code PATCH /v3/service_credential_bindings/{guid}}
  • + *
+ * + *

+ * As in the OSS impl, the async create/delete operations do not block on the job: they return the job GUID (extracted from the + * {@code Location} header of the 202 response) so the caller can poll it. This matches + * {@code delegate.serviceBindingsV3().create(...).getJobId()} / {@code .delete(...).block()} which yield the job id without waiting. + *

+ */ +public class ServiceBindingsV3Operations { + + // CF v3 caps per_page at 5000; use a large page to minimise round-trips (the pagination walker still handles multiple pages). + private static final int PER_PAGE = 5000; + + private static final ParameterizedTypeReference> BINDING_LIST_TYPE = new ParameterizedTypeReference<>() { + }; + private static final ParameterizedTypeReference> APPLICATION_LIST_TYPE = new ParameterizedTypeReference<>() { + }; + + private final CloudControllerV3Client cc; + private final CloudSpace target; + + public ServiceBindingsV3Operations(CloudControllerV3Client cc, CloudSpace target) { + this.cc = cc; + this.target = target; + } + + public Optional bindServiceInstance(String bindingName, String applicationName, String serviceInstanceName) { + return bindServiceInstance(bindingName, applicationName, serviceInstanceName, null); + } + + public Optional bindServiceInstance(String bindingName, String applicationName, String serviceInstanceName, + Map parameters) { + UUID applicationGuid = getRequiredApplicationGuid(applicationName); + UUID serviceInstanceGuid = getRequiredServiceInstanceGuid(serviceInstanceName); + + Map body = new HashMap<>(); + body.put("name", bindingName); + body.put("type", "app"); + body.put("relationships", Map.of("app", toOneRelationship(applicationGuid), "service_instance", + toOneRelationship(serviceInstanceGuid))); + if (!CollectionUtils.isEmpty(parameters)) { + body.put("parameters", parameters); + } + + ResponseEntity response = cc.getRestClient() + .post() + .uri("/v3/service_credential_bindings") + .body(body) + .retrieve() + .toBodilessEntity(); + return extractJobGuid(response); + } + + public List unbindServiceInstance(String applicationName, String serviceInstanceName) { + UUID applicationGuid = getRequiredApplicationGuid(applicationName); + UUID serviceInstanceGuid = getRequiredServiceInstanceGuid(serviceInstanceName); + return doUnbindServiceInstance(applicationGuid, serviceInstanceGuid); + } + + public List unbindServiceInstance(UUID applicationGuid, UUID serviceInstanceGuid) { + return doUnbindServiceInstance(applicationGuid, serviceInstanceGuid); + } + + public Optional deleteServiceBinding(UUID bindingGuid) { + return deleteSingleServiceBinding(bindingGuid); + } + + public CloudServiceBinding getServiceBinding(UUID serviceBindingGuid) { + String uri = "/v3/service_credential_bindings?guids=" + serviceBindingGuid + "&per_page=" + PER_PAGE; + return cc.list(uri, BINDING_LIST_TYPE) + .stream() + .findFirst() + .map(V3ServiceBindingMapper::toCloudServiceBinding) + .orElse(null); + } + + public List getServiceAppBindings(UUID serviceInstanceGuid) { + String uri = "/v3/service_credential_bindings?service_instance_guids=" + serviceInstanceGuid + "&type=app&per_page=" + PER_PAGE; + return listBindings(uri); + } + + public List getAppBindings(UUID applicationGuid) { + String uri = "/v3/service_credential_bindings?app_guids=" + applicationGuid + "&per_page=" + PER_PAGE; + return listBindings(uri); + } + + public List getServiceBindingsForApplication(UUID applicationId, UUID serviceInstanceGuid) { + String uri = "/v3/service_credential_bindings?app_guids=" + applicationId + "&service_instance_guids=" + serviceInstanceGuid + + "&per_page=" + PER_PAGE; + return listBindings(uri); + } + + public Map getServiceBindingParameters(UUID guid) { + @SuppressWarnings("unchecked") + Map parameters = cc.get("/v3/service_credential_bindings/" + guid + "/parameters", Map.class); + return parameters; + } + + public void updateServiceBindingMetadata(UUID guid, Metadata metadata) { + Map metadataBody = new HashMap<>(); + metadataBody.put("labels", metadata.getLabels()); + metadataBody.put("annotations", metadata.getAnnotations()); + cc.getRestClient() + .patch() + .uri("/v3/service_credential_bindings/{guid}", guid) + .body(Map.of("metadata", metadataBody)) + .retrieve() + .toBodilessEntity(); + } + + private List listBindings(String uri) { + return cc.list(uri, BINDING_LIST_TYPE) + .stream() + .map(V3ServiceBindingMapper::toCloudServiceBinding) + .toList(); + } + + // Mirrors the OSS doUnbindServiceInstance: resolve the app<->service bindings and 404 when none exist, then delete each. + private List doUnbindServiceInstance(UUID applicationGuid, UUID serviceInstanceGuid) { + List serviceBindingGuids = getServiceBindingGuids(applicationGuid, serviceInstanceGuid); + if (serviceBindingGuids.isEmpty()) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", + "Service binding between service with GUID " + serviceInstanceGuid + + " and application with GUID " + applicationGuid + " not found."); + } + return doDeleteServiceBindings(serviceBindingGuids); + } + + private List getServiceBindingGuids(UUID applicationGuid, UUID serviceInstanceGuid) { + String uri = "/v3/service_credential_bindings?app_guids=" + applicationGuid + "&service_instance_guids=" + serviceInstanceGuid + + "&per_page=" + PER_PAGE; + return cc.list(uri, BINDING_LIST_TYPE) + .stream() + .map(binding -> UUID.fromString(binding.guid())) + .toList(); + } + + private List doDeleteServiceBindings(List guids) { + List jobIds = new ArrayList<>(); + List errors = new ArrayList<>(); + for (UUID guid : guids) { + try { + deleteSingleServiceBinding(guid).ifPresent(jobIds::add); + } catch (CloudOperationException e) { + errors.add(e); + } + } + throwOnErrors(errors); + return jobIds; + } + + private Optional deleteSingleServiceBinding(UUID guid) { + ResponseEntity response = cc.getRestClient() + .delete() + .uri("/v3/service_credential_bindings/{guid}", guid) + .retrieve() + .toBodilessEntity(); + return extractJobGuid(response); + } + + private void throwOnErrors(List errors) { + if (errors.isEmpty()) { + return; + } + CloudOperationException first = errors.get(0); + errors.subList(1, errors.size()) + .forEach(first::addSuppressed); + throw first; + } + + // Extract the job GUID from the Location header of a 202-Accepted response (path ends in /v3/jobs/{guid}); empty if synchronous. + private Optional extractJobGuid(ResponseEntity response) { + URI location = response.getHeaders() + .getLocation(); + if (location == null) { + return Optional.empty(); + } + String path = location.getPath(); + return Optional.of(path.substring(path.lastIndexOf('/') + 1)); + } + + private Map toOneRelationship(UUID guid) { + return Map.of("data", Map.of("guid", guid.toString())); + } + + // Mirrors the OSS getRequiredApplicationGuid: look up the app in the target space and 404 if it is absent. + private UUID getRequiredApplicationGuid(String applicationName) { + StringBuilder query = new StringBuilder("/v3/apps?per_page=" + PER_PAGE); + if (target != null && target.getGuid() != null) { + query.append("&space_guids=") + .append(target.getGuid()); + } + query.append("&names=") + .append(applicationName); + List apps = cc.list(query.toString(), APPLICATION_LIST_TYPE); + if (apps.isEmpty()) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Application " + applicationName + " not found."); + } + return UUID.fromString(apps.get(0) + .guid()); + } + + // Mirrors the OSS getRequiredServiceInstanceGuid: look up the service instance in the target space and 404 if it is absent. + private UUID getRequiredServiceInstanceGuid(String name) { + StringBuilder query = new StringBuilder("/v3/service_instances?per_page=" + PER_PAGE); + if (target != null && target.getGuid() != null) { + query.append("&space_guids=") + .append(target.getGuid()); + } + query.append("&names=") + .append(name); + List instances = cc.list(query.toString(), + new ParameterizedTypeReference>() { + }); + if (instances.isEmpty()) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Service instance " + name + " not found."); + } + return UUID.fromString(instances.get(0) + .guid()); + } + + // Minimal wire model to resolve a service instance GUID by name without depending on the (separately-owned) ServiceInstances group. + @com.fasterxml.jackson.annotation.JsonIgnoreProperties(ignoreUnknown = true) + private record V3ServiceInstanceRef(@com.fasterxml.jackson.annotation.JsonProperty("guid") String guid) { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBrokersV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBrokersV3Operations.java new file mode 100644 index 0000000000..8a7acff08a --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBrokersV3Operations.java @@ -0,0 +1,228 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.net.URI; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBroker; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServicePlanVisibility; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceBroker; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceBrokerMapper; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.util.Assert; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * CF v3 service broker operations of the in-house {@code CloudControllerRestClient}. Reproduces the HTTP shape, filtering, + * async job handling and domain mapping of the OSS {@code CloudControllerRestClientImpl} service-broker methods on top of the shared + * {@link CloudControllerV3Client} machinery. + * + *

+ * Endpoint map (see {@code docs/cf-java-client-migration/02-endpoint-inventory.md}): + *

+ *
    + *
  • {@code createServiceBroker} → {@code POST /v3/service_brokers} (async job; returns the job GUID)
  • + *
  • {@code deleteServiceBroker} → {@code DELETE /v3/service_brokers/{guid}} (async job; returns the job GUID)
  • + *
  • {@code getServiceBroker(name[, required])} → {@code GET /v3/service_brokers?names=} (paginated, first match)
  • + *
  • {@code getServiceBrokers} → {@code GET /v3/service_brokers} (paginated)
  • + *
  • {@code updateServiceBroker} → {@code PATCH /v3/service_brokers/{guid}} (async job; returns the job GUID or {@code null})
  • + *
  • {@code updateServicePlanVisibilityForBroker} → per plan {@code PATCH /v3/service_plans/{guid}/visibility}
  • + *
+ * + *

+ * As in the OSS impl, {@code createServiceBroker}/{@code deleteServiceBroker}/{@code updateServiceBroker} return the async job GUID + * without waiting for the job to complete — the caller polls the job separately. + *

+ */ +public class ServiceBrokersV3Operations { + + private static final int PER_PAGE = 5000; + + private static final ParameterizedTypeReference> BROKER_PAGE = new ParameterizedTypeReference<>() { + }; + private static final ParameterizedTypeReference> OFFERING_PAGE = new ParameterizedTypeReference<>() { + }; + private static final ParameterizedTypeReference> PLAN_PAGE = new ParameterizedTypeReference<>() { + }; + + private final CloudControllerV3Client cc; + private final CloudSpace target; + + public ServiceBrokersV3Operations(CloudControllerV3Client cc, CloudSpace target) { + this.cc = cc; + this.target = target; + } + + public String createServiceBroker(CloudServiceBroker serviceBroker) { + Assert.notNull(serviceBroker, "Service broker must not be null."); + + Map body = new HashMap<>(); + body.put("name", serviceBroker.getName()); + body.put("url", serviceBroker.getUrl()); + body.put("authentication", basicAuthentication(serviceBroker)); + if (serviceBroker.getSpaceGuid() != null) { + body.put("relationships", Map.of("space", Map.of("data", Map.of("guid", serviceBroker.getSpaceGuid())))); + } + + ResponseEntity response = cc.getRestClient() + .post() + .uri("/v3/service_brokers") + .body(body) + .retrieve() + .toBodilessEntity(); + return extractJobGuid(response); + } + + public String deleteServiceBroker(String name) { + CloudServiceBroker broker = getServiceBroker(name); + UUID guid = broker.getMetadata() + .getGuid(); + ResponseEntity response = cc.getRestClient() + .delete() + .uri("/v3/service_brokers/{guid}", guid.toString()) + .retrieve() + .toBodilessEntity(); + return extractJobGuid(response); + } + + public CloudServiceBroker getServiceBroker(String name) { + return getServiceBroker(name, true); + } + + public CloudServiceBroker getServiceBroker(String name, boolean required) { + CloudServiceBroker serviceBroker = findServiceBrokerByName(name); + if (serviceBroker == null && required) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Service broker " + name + " not found."); + } + return serviceBroker; + } + + public List getServiceBrokers() { + return cc.list("/v3/service_brokers?per_page=" + PER_PAGE, BROKER_PAGE) + .stream() + .map(V3ServiceBrokerMapper::toCloudServiceBroker) + .toList(); + } + + public String updateServiceBroker(CloudServiceBroker serviceBroker) { + Assert.notNull(serviceBroker, "Service broker must not be null."); + + CloudServiceBroker existingBroker = getServiceBroker(serviceBroker.getName()); + UUID brokerGuid = existingBroker.getMetadata() + .getGuid(); + + Map body = Map.of("name", serviceBroker.getName(), "url", serviceBroker.getUrl(), "authentication", + basicAuthentication(serviceBroker)); + + ResponseEntity response = cc.getRestClient() + .patch() + .uri("/v3/service_brokers/{guid}", brokerGuid.toString()) + .body(body) + .retrieve() + .toBodilessEntity(); + // A synchronous update returns 200 with no Location header (no job); an async update returns 202 with a job GUID. + return extractJobGuid(response); + } + + public void updateServicePlanVisibilityForBroker(String name, ServicePlanVisibility visibility) { + CloudServiceBroker broker = getServiceBroker(name); + UUID brokerGuid = broker.getMetadata() + .getGuid(); + for (UUID servicePlanGuid : findServicePlanGuidsByBrokerGuid(brokerGuid)) { + updateServicePlanVisibility(servicePlanGuid, visibility); + } + } + + private CloudServiceBroker findServiceBrokerByName(String name) { + return cc.list("/v3/service_brokers?names=" + name + "&per_page=" + PER_PAGE, BROKER_PAGE) + .stream() + .findFirst() + .map(V3ServiceBrokerMapper::toCloudServiceBroker) + .orElse(null); + } + + private List findServicePlanGuidsByBrokerGuid(UUID brokerGuid) { + return findServiceOfferingGuidsByBrokerGuid(brokerGuid).stream() + .flatMap(offeringGuid -> findServicePlanGuidsByOfferingGuid( + offeringGuid).stream()) + .toList(); + } + + private List findServiceOfferingGuidsByBrokerGuid(UUID brokerGuid) { + String uri = "/v3/service_offerings?service_broker_guids=" + brokerGuid + "&space_guids=" + getTargetSpaceGuid() + "&per_page=" + + PER_PAGE; + return cc.list(uri, OFFERING_PAGE) + .stream() + .map(ServiceOfferingRef::guid) + .map(UUID::fromString) + .toList(); + } + + private List findServicePlanGuidsByOfferingGuid(UUID serviceOfferingGuid) { + String uri = "/v3/service_plans?service_offering_guids=" + serviceOfferingGuid + "&per_page=" + PER_PAGE; + return cc.list(uri, PLAN_PAGE) + .stream() + .map(ServicePlanRef::guid) + .map(UUID::fromString) + .toList(); + } + + private void updateServicePlanVisibility(UUID servicePlanGuid, ServicePlanVisibility visibility) { + // ServicePlanVisibility.toString() yields the lowercase CF visibility type (public/admin/organization), + // matching the OSS Visibility.from(visibility.toString()) mapping. + cc.getRestClient() + .patch() + .uri("/v3/service_plans/{guid}/visibility", servicePlanGuid.toString()) + .body(Map.of("type", visibility.toString())) + .retrieve() + .toBodilessEntity(); + } + + private static Map basicAuthentication(CloudServiceBroker serviceBroker) { + return Map.of("type", "basic", "credentials", + Map.of("username", nullToEmpty(serviceBroker.getUsername()), "password", nullToEmpty(serviceBroker.getPassword()))); + } + + private static String nullToEmpty(String value) { + return value == null ? "" : value; + } + + private String getTargetSpaceGuid() { + return target.getMetadata() + .getGuid() + .toString(); + } + + // A 202-Accepted async response carries the job in its Location header (/v3/jobs/{guid}); a synchronous 200 has none. + private static String extractJobGuid(ResponseEntity response) { + URI location = response.getHeaders() + .getLocation(); + if (location == null) { + return null; + } + String value = location.toString(); + int idx = value.lastIndexOf("/v3/jobs/"); + if (idx < 0) { + return value.substring(value.lastIndexOf('/') + 1); + } + return value.substring(idx + "/v3/jobs/".length()); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + private record ServiceOfferingRef(@JsonProperty("guid") String guid) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + private record ServicePlanRef(@JsonProperty("guid") String guid) { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3Operations.java new file mode 100644 index 0000000000..1361ba9fb4 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3Operations.java @@ -0,0 +1,442 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEntity; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceInstanceMapper; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceOffering; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServicePlan; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; +import org.springframework.util.Assert; + +/** + * CF v3 service instance operations of the in-house {@code CloudControllerRestClient}. Reproduces the HTTP shape, filtering, + * space-scoping and domain-mapping of the OSS {@code CloudControllerRestClientImpl} service-instance methods on top of the shared + * {@link CloudControllerV3Client} machinery. + * + *

+ * Endpoint map (see {@code docs/cf-java-client-migration/02-endpoint-inventory.md}): + *

+ *
    + *
  • {@code createServiceInstance} → {@code POST /v3/service_instances} (type=managed)
  • + *
  • {@code createUserProvidedServiceInstance} → {@code POST /v3/service_instances} (type=user-provided)
  • + *
  • {@code deleteServiceInstance} → {@code DELETE /v3/service_instances/{guid}}
  • + *
  • {@code getServiceInstance} / {@code getRequiredServiceInstanceGuid} / {@code getServiceInstanceWithoutAuxiliaryContent} → + * {@code GET /v3/service_instances?space_guids=…&names=…} (paginated)
  • + *
  • {@code getServiceInstanceName} → {@code GET /v3/service_instances/{guid}}
  • + *
  • {@code getServiceInstanceParameters} → {@code GET /v3/service_instances/{guid}/parameters}
  • + *
  • {@code getUserProvidedServiceInstanceParameters} → {@code GET /v3/service_instances/{guid}/credentials}
  • + *
  • {@code getServiceInstancesWithoutAuxiliaryContentByNames} → {@code GET /v3/service_instances?space_guids=…&names=…} (batched)
  • + *
  • {@code getServiceInstancesByMetadataLabelSelector} (+ WithoutAuxiliaryContent) → + * {@code GET /v3/service_instances?space_guids=…&label_selector=…} (paginated)
  • + *
  • {@code updateServicePlan} / {@code updateServiceParameters} / {@code updateServiceTags} / {@code updateServiceSyslogDrainUrl} / + * {@code updateServiceInstanceMetadata} → {@code PATCH /v3/service_instances/{guid}}
  • + *
+ * + *

+ * Async note: for managed instances CF returns 202 for create/update/delete, but the OSS impl does not follow the job — it returns + * as soon as the request is accepted. This helper preserves that behaviour (no {@code followAsyncJob} on these calls). + *

+ */ +public class ServiceInstancesV3Operations { + + // CF v3 caps per_page at 5000; use a large page to minimise round-trips (the pagination walker still handles multiple pages). + private static final int PER_PAGE = 5000; + // Mirrors the OSS MAX_CHAR_LENGTH_FOR_PARAMS_IN_REQUEST used to batch the "byNames" lookups. + private static final int MAX_CHAR_LENGTH_FOR_PARAMS_IN_REQUEST = 4000; + + private static final ParameterizedTypeReference> SERVICE_INSTANCE_LIST_TYPE = new ParameterizedTypeReference<>() { + }; + + private final CloudControllerV3Client cc; + private final CloudSpace target; + + public ServiceInstancesV3Operations(CloudControllerV3Client cc, CloudSpace target) { + this.cc = cc; + this.target = target; + } + + public void createServiceInstance(CloudServiceInstance serviceInstance) { + assertSpaceProvided("create service instance"); + Assert.notNull(serviceInstance, "Service instance must not be null."); + UUID servicePlanGuid = findPlanGuidForService(serviceInstance, serviceInstance.getPlan()); + + Map body = new HashMap<>(); + body.put("type", "managed"); + body.put("name", serviceInstance.getName()); + putIfNotNull(body, "metadata", toMetadataMap(serviceInstance.getV3Metadata())); + putIfNotNull(body, "tags", serviceInstance.getTags()); + putIfNotNull(body, "parameters", serviceInstance.getCredentials()); + body.put("relationships", Map.of("service_plan", toOneRelationship(servicePlanGuid.toString()), "space", + toOneRelationship(getTargetSpaceGuid().toString()))); + + cc.getRestClient() + .post() + .uri("/v3/service_instances") + .body(body) + .retrieve() + .toBodilessEntity(); + } + + public void createUserProvidedServiceInstance(CloudServiceInstance serviceInstance) { + assertSpaceProvided("create service instance"); + Assert.notNull(serviceInstance, "Service instance must not be null."); + String syslogDrainUrl = hasText(serviceInstance.getSyslogDrainUrl()) ? serviceInstance.getSyslogDrainUrl() : ""; + + Map body = new HashMap<>(); + body.put("type", "user-provided"); + body.put("name", serviceInstance.getName()); + putIfNotNull(body, "metadata", toMetadataMap(serviceInstance.getV3Metadata())); + putIfNotNull(body, "credentials", serviceInstance.getCredentials()); + body.put("syslog_drain_url", syslogDrainUrl); + putIfNotNull(body, "tags", serviceInstance.getTags()); + body.put("relationships", Map.of("space", toOneRelationship(getTargetSpaceGuid().toString()))); + + cc.getRestClient() + .post() + .uri("/v3/service_instances") + .body(body) + .retrieve() + .toBodilessEntity(); + } + + public void deleteServiceInstance(String serviceInstanceName) { + CloudServiceInstance serviceInstance = getServiceInstanceWithoutAuxiliaryContent(serviceInstanceName); + doDeleteServiceInstance(serviceInstance.getGuid()); + } + + public void deleteServiceInstance(CloudServiceInstance serviceInstance) { + doDeleteServiceInstance(serviceInstance.getGuid()); + } + + public UUID getRequiredServiceInstanceGuid(String name) { + V3ServiceInstance resource = findServiceInstanceResourceByName(name); + if (resource == null) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Service instance " + name + " not found."); + } + return UUID.fromString(resource.guid()); + } + + public CloudServiceInstance getServiceInstance(String serviceInstanceName) { + return getServiceInstance(serviceInstanceName, true); + } + + public CloudServiceInstance getServiceInstance(String serviceInstanceName, boolean required) { + CloudServiceInstance serviceInstance = findServiceInstanceByName(serviceInstanceName); + return getServiceInstanceIfRequired(serviceInstanceName, serviceInstance, required); + } + + public String getServiceInstanceName(UUID serviceInstanceGuid) { + V3ServiceInstance serviceInstance = cc.get("/v3/service_instances/" + serviceInstanceGuid, V3ServiceInstance.class); + return serviceInstance == null ? null : serviceInstance.name(); + } + + public CloudServiceInstance getServiceInstanceWithoutAuxiliaryContent(String serviceInstanceName) { + return getServiceInstanceWithoutAuxiliaryContent(serviceInstanceName, true); + } + + public CloudServiceInstance getServiceInstanceWithoutAuxiliaryContent(String serviceInstanceName, boolean required) { + V3ServiceInstance resource = findServiceInstanceResourceByName(serviceInstanceName); + CloudServiceInstance serviceInstance = resource == null ? null : V3ServiceInstanceMapper.toCloudServiceInstance(resource, null, + null); + return getServiceInstanceIfRequired(serviceInstanceName, serviceInstance, required); + } + + public Map getServiceInstanceParameters(UUID guid) { + return getParameters("/v3/service_instances/" + guid + "/parameters"); + } + + public Map getUserProvidedServiceInstanceParameters(UUID guid) { + return getParameters("/v3/service_instances/" + guid + "/credentials"); + } + + public List getServiceInstancesWithoutAuxiliaryContentByNames(List names) { + List allServiceInstances = new ArrayList<>(); + for (List batch : toBatches(names, MAX_CHAR_LENGTH_FOR_PARAMS_IN_REQUEST)) { + String uri = "/v3/service_instances?per_page=" + PER_PAGE + "&space_guids=" + getTargetSpaceGuid() + "&names=" + + String.join(",", batch); + cc.list(uri, SERVICE_INSTANCE_LIST_TYPE) + .stream() + .map(V3ServiceInstanceMapper::toCloudServiceInstanceWithoutAuxiliaryContent) + .forEach(allServiceInstances::add); + } + return allServiceInstances; + } + + public List getServiceInstancesByMetadataLabelSelector(String labelSelector) { + String uri = "/v3/service_instances?per_page=" + PER_PAGE + "&space_guids=" + getTargetSpaceGuid() + "&label_selector=" + + labelSelector; + return cc.list(uri, SERVICE_INSTANCE_LIST_TYPE) + .stream() + .map(this::mapWithAuxiliaryContent) + .collect(Collectors.toList()); + } + + public List getServiceInstancesWithoutAuxiliaryContentByMetadataLabelSelector(String labelSelector) { + String uri = "/v3/service_instances?per_page=" + PER_PAGE + "&space_guids=" + getTargetSpaceGuid() + "&label_selector=" + + labelSelector; + return cc.list(uri, SERVICE_INSTANCE_LIST_TYPE) + .stream() + .map(resource -> V3ServiceInstanceMapper.toCloudServiceInstance(resource, null, null)) + .collect(Collectors.toList()); + } + + public void updateServicePlan(String serviceName, String planName) { + CloudServiceInstance service = getServiceInstance(serviceName); + if (service.isUserProvided()) { + return; + } + UUID planGuid = findPlanGuidForService(service, planName); + patchServiceInstance(service.getGuid(), Map.of("relationships", Map.of("service_plan", toOneRelationship(planGuid.toString())))); + } + + public void updateServiceParameters(String serviceName, Map parameters) { + CloudServiceInstance service = getServiceInstanceWithoutAuxiliaryContent(serviceName); + String key = service.isUserProvided() ? "credentials" : "parameters"; + patchServiceInstance(service.getGuid(), Map.of(key, parameters)); + } + + public void updateServiceTags(String serviceName, List tags) { + UUID serviceInstanceGuid = getRequiredServiceInstanceGuid(serviceName); + patchServiceInstance(serviceInstanceGuid, Map.of("tags", tags)); + } + + public void updateServiceSyslogDrainUrl(String serviceName, String syslogDrainUrl) { + CloudServiceInstance service = getServiceInstanceWithoutAuxiliaryContent(serviceName); + if (!service.isUserProvided()) { + return; + } + String updatedSyslogDrain = hasText(syslogDrainUrl) ? syslogDrainUrl : ""; + patchServiceInstance(service.getGuid(), Map.of("syslog_drain_url", updatedSyslogDrain)); + } + + public void updateServiceInstanceMetadata(UUID guid, Metadata metadata) { + Map metadataMap = toMetadataMap(metadata); + patchServiceInstance(guid, Map.of("metadata", metadataMap == null ? Map.of() : metadataMap)); + } + + private CloudServiceInstance getServiceInstanceIfRequired(String serviceInstanceName, CloudServiceInstance serviceInstance, + boolean required) { + if (serviceInstance == null && required) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Service instance " + serviceInstanceName + " not found."); + } + return serviceInstance; + } + + // Mirrors the OSS findServiceInstanceByName -> zipWithAuxiliaryServiceInstanceContent: for managed instances the plan and offering + // names are resolved and included; user-provided instances are mapped as-is. + private CloudServiceInstance findServiceInstanceByName(String name) { + V3ServiceInstance resource = findServiceInstanceResourceByName(name); + if (resource == null) { + return null; + } + return mapWithAuxiliaryContent(resource); + } + + private CloudServiceInstance mapWithAuxiliaryContent(V3ServiceInstance resource) { + if (isUserProvided(resource)) { + return V3ServiceInstanceMapper.toCloudServiceInstance(resource, null, null); + } + String servicePlanGuid = servicePlanGuidOf(resource); + if (servicePlanGuid == null) { + return V3ServiceInstanceMapper.toCloudServiceInstance(resource, null, null); + } + ServicePlanNames names = resolvePlanAndOfferingNames(servicePlanGuid); + return V3ServiceInstanceMapper.toCloudServiceInstance(resource, names.planName(), names.offeringName()); + } + + private V3ServiceInstance findServiceInstanceResourceByName(String name) { + String uri = "/v3/service_instances?per_page=" + PER_PAGE + "&space_guids=" + getTargetSpaceGuid() + "&names=" + name; + return cc.list(uri, SERVICE_INSTANCE_LIST_TYPE) + .stream() + .findFirst() + .orElse(null); + } + + private boolean isUserProvided(V3ServiceInstance resource) { + return "user-provided".equals(resource.type()); + } + + private String servicePlanGuidOf(V3ServiceInstance resource) { + if (resource.relationships() == null || resource.relationships() + .servicePlan() == null + || resource.relationships() + .servicePlan() + .data() == null) { + return null; + } + return resource.relationships() + .servicePlan() + .data() + .guid(); + } + + // Mirrors the OSS getServicePlanResource + getServiceOffering zip: resolve the plan's own name and its offering's name. + private ServicePlanNames resolvePlanAndOfferingNames(String servicePlanGuid) { + V3ServicePlan plan = cc.get("/v3/service_plans/" + servicePlanGuid, V3ServicePlan.class); + if (plan == null) { + return new ServicePlanNames(null, null); + } + String offeringName = null; + String offeringGuid = offeringGuidOf(plan); + if (offeringGuid != null) { + V3ServiceOffering offering = cc.get("/v3/service_offerings/" + offeringGuid, V3ServiceOffering.class); + offeringName = offering == null ? null : offering.name(); + } + return new ServicePlanNames(plan.name(), offeringName); + } + + private String offeringGuidOf(V3ServicePlan plan) { + if (plan.relationships() == null || plan.relationships() + .serviceOffering() == null + || plan.relationships() + .serviceOffering() + .data() == null) { + return null; + } + return plan.relationships() + .serviceOffering() + .data() + .guid(); + } + + // Mirrors the OSS findPlanForService: list the offerings for the service's label (optionally scoped by broker), then find the plan by + // name; a missing plan throws CloudOperationException(NOT_FOUND). + private UUID findPlanGuidForService(CloudServiceInstance service, String planName) { + List offeringGuids = findServiceOfferingGuids(service.getLabel(), service.getBroker()); + for (String offeringGuid : offeringGuids) { + String uri = "/v3/service_plans?per_page=" + PER_PAGE + "&service_offering_guids=" + offeringGuid + "&names=" + planName; + V3ServicePlan plan = cc.list(uri, new ParameterizedTypeReference>() { + }) + .stream() + .findFirst() + .orElse(null); + if (plan != null) { + return UUID.fromString(plan.guid()); + } + } + throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Service plan " + planName + " not found."); + } + + private List findServiceOfferingGuids(String label, String broker) { + StringBuilder uri = new StringBuilder("/v3/service_offerings?per_page=").append(PER_PAGE) + .append("&space_guids=") + .append(getTargetSpaceGuid()) + .append("&names=") + .append(label); + if (hasText(broker)) { + uri.append("&service_broker_names=") + .append(broker); + } + return cc.list(uri.toString(), new ParameterizedTypeReference>() { + }) + .stream() + .map(V3ServiceOffering::guid) + .collect(Collectors.toList()); + } + + private void doDeleteServiceInstance(UUID serviceInstanceGuid) { + // OSS parity: the delete is issued and the response consumed, but the async job is NOT followed. + cc.getRestClient() + .delete() + .uri("/v3/service_instances/{guid}", serviceInstanceGuid.toString()) + .retrieve() + .toBodilessEntity(); + } + + private void patchServiceInstance(UUID guid, Map body) { + // OSS parity: the update is issued and the response consumed, but the async job is NOT followed. + cc.getRestClient() + .patch() + .uri("/v3/service_instances/{guid}", guid.toString()) + .body(body) + .retrieve() + .toBodilessEntity(); + } + + @SuppressWarnings("unchecked") + private Map getParameters(String uri) { + return cc.get(uri, Map.class); + } + + private Map toOneRelationship(String guid) { + return Map.of("data", Map.of("guid", guid)); + } + + private Map toMetadataMap(Metadata metadata) { + if (metadata == null) { + return null; + } + Map map = new HashMap<>(); + map.put("labels", metadata.getLabels() == null ? Map.of() : metadata.getLabels()); + map.put("annotations", metadata.getAnnotations() == null ? Map.of() : metadata.getAnnotations()); + return map; + } + + private static void putIfNotNull(Map body, String key, Object value) { + if (value != null) { + body.put(key, value); + } + } + + private static boolean hasText(String value) { + return value != null && !value.isBlank(); + } + + // Mirrors the OSS toBatches: split a large collection into batches bounded by the total character length of the elements. + private List> toBatches(Collection largeList, int maxCharLength) { + if (largeList.isEmpty()) { + return List.of(); + } + List> batches = new ArrayList<>(); + int currentBatchLength = 0; + int currentBatchIndex = 0; + batches.add(new ArrayList<>()); + for (T element : largeList) { + int elementLength = element.toString() + .length(); + if (elementLength + currentBatchLength >= maxCharLength) { + batches.add(new ArrayList<>()); + currentBatchIndex++; + currentBatchLength = 0; + } + batches.get(currentBatchIndex) + .add(element); + currentBatchLength += elementLength; + } + return batches; + } + + private UUID getTargetSpaceGuid() { + return getGuid(target); + } + + private UUID getGuid(CloudEntity entity) { + if (entity == null || entity.getMetadata() == null) { + return null; + } + return entity.getMetadata() + .getGuid(); + } + + private void assertSpaceProvided(String operation) { + Assert.notNull(target, "Unable to " + operation + " without specifying organization and space to use."); + } + + private record ServicePlanNames(String planName, String offeringName) { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3Operations.java new file mode 100644 index 0000000000..720db7bc3f --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3Operations.java @@ -0,0 +1,188 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.net.URI; +import java.time.Duration; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType; +import org.cloudfoundry.multiapps.controller.client.facade.Messages; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceBinding; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceKeyDetails; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceKeyMapper; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.ResponseEntity; + +/** + * CF v3 service key operations for the cf-java-client replacement. A service key is a service credential binding of + * {@code type=key}, so all endpoints target {@code /v3/service_credential_bindings}. Reproduces the HTTP shape and domain-mapping of the + * OSS {@code CloudControllerRestClientImpl} service-key methods: + *
    + *
  • create → {@code POST /v3/service_credential_bindings} (type=key) — returns a job (202);
  • + *
  • list → {@code GET /v3/service_credential_bindings?type=key&service_instance_guids={guid}[&names={name}]} (paginated);
  • + *
  • credentials → {@code GET /v3/service_credential_bindings/{guid}/details}.
  • + *
+ * + *

+ * The interface methods that accept a {@code serviceInstanceName} require the owning {@link CloudServiceInstance} (for its GUID and to + * embed in the resulting {@link CloudServiceKey}). Mirroring the OSS impl — which first calls {@code getServiceInstance(name)} + * — these operations take the already-resolved {@link CloudServiceInstance}; the integrator resolves it in + * {@code CloudControllerRestClientV3Impl} before delegating. + *

+ */ +public class ServiceKeysV3Operations { + + // CF v3 caps per_page at 5000; use a large page to minimise round-trips (the pagination walker still handles multiple pages). + private static final int DEFAULT_PAGE_SIZE = 5000; + // Mirrors the OSS BINDING_OPERATIONS_TIMEOUT used when synchronously creating (and fetching) a service key. + private static final Duration BINDING_OPERATIONS_TIMEOUT = Duration.ofMinutes(5); + + private final CloudControllerV3Client cc; + private final CloudSpace target; + + public ServiceKeysV3Operations(CloudControllerV3Client cc, CloudSpace target) { + this.cc = cc; + this.target = target; + } + + // --- create ----------------------------------------------------------------------------------------------------------------- + + public CloudServiceKey createAndFetchServiceKey(CloudServiceKey keyModel, CloudServiceInstance serviceInstance) { + ResponseEntity accepted = postServiceKey(keyModel.getName(), keyModel.getCredentials(), keyModel.getV3Metadata(), + serviceInstance); + cc.followAsyncJob(accepted, BINDING_OPERATIONS_TIMEOUT); + V3ServiceBinding created = getServiceKeyResourceByNameAndServiceInstanceGuid(keyModel.getName(), serviceInstance.getGuid()); + if (created == null) { + return null; + } + Map credentials = getServiceKeyCredentials(created.guid()); + return V3ServiceKeyMapper.toCloudServiceKey(created, serviceInstance, credentials); + } + + public Optional createServiceKey(CloudServiceKey keyModel, CloudServiceInstance serviceInstance) { + ResponseEntity accepted = postServiceKey(keyModel.getName(), keyModel.getCredentials(), keyModel.getV3Metadata(), + serviceInstance); + return extractJobGuid(accepted); + } + + public Optional createServiceKey(CloudServiceInstance serviceInstance, String serviceKeyName, Map parameters) { + ResponseEntity accepted = postServiceKey(serviceKeyName, parameters, null, serviceInstance); + return extractJobGuid(accepted); + } + + // --- read ------------------------------------------------------------------------------------------------------------------- + + public CloudServiceKey getServiceKey(CloudServiceInstance serviceInstance, String serviceKeyName) { + V3ServiceBinding key = getServiceKeyResourceByNameAndServiceInstanceGuid(serviceKeyName, serviceInstance.getGuid()); + if (key == null) { + return null; + } + Map credentials = getServiceKeyCredentials(key.guid()); + return V3ServiceKeyMapper.toCloudServiceKey(key, serviceInstance, credentials); + } + + public List getServiceKeys(CloudServiceInstance serviceInstance) { + return listServiceKeyResources(serviceInstance.getGuid()).stream() + .map(key -> V3ServiceKeyMapper.toCloudServiceKey(key, serviceInstance)) + .toList(); + } + + public List getServiceKeysWithCredentials(CloudServiceInstance serviceInstance) { + return listServiceKeyResources(serviceInstance.getGuid()).stream() + .map(key -> V3ServiceKeyMapper.toCloudServiceKey(key, serviceInstance, + getServiceKeyCredentials( + key.guid()))) + .toList(); + } + + // --- helpers ---------------------------------------------------------------------------------------------------------------- + + private ResponseEntity postServiceKey(String name, Map parameters, Metadata metadata, + CloudServiceInstance serviceInstance) { + if (serviceInstance.getType() != ServiceInstanceType.MANAGED) { + throw new IllegalArgumentException( + String.format(Messages.CANT_CREATE_SERVICE_KEY_FOR_USER_PROVIDED_SERVICE, serviceInstance.getName())); + } + return cc.getRestClient() + .post() + .uri("/v3/service_credential_bindings") + .body(buildCreateServiceKeyBody(name, parameters, metadata, serviceInstance.getGuid())) + .retrieve() + .toBodilessEntity(); + } + + private Map buildCreateServiceKeyBody(String name, Map parameters, Metadata metadata, + UUID serviceInstanceGuid) { + Map body = new HashMap<>(); + body.put("type", "key"); + body.put("name", name); + body.put("relationships", Map.of("service_instance", Map.of("data", Map.of("guid", serviceInstanceGuid.toString())))); + if (parameters != null && !parameters.isEmpty()) { + body.put("parameters", parameters); + } + Map metadataBody = buildMetadataBody(metadata); + if (metadataBody != null) { + body.put("metadata", metadataBody); + } + return body; + } + + private Map buildMetadataBody(Metadata metadata) { + if (metadata == null) { + return null; + } + Map metadataBody = new HashMap<>(); + if (metadata.getLabels() != null) { + metadataBody.put("labels", metadata.getLabels()); + } + if (metadata.getAnnotations() != null) { + metadataBody.put("annotations", metadata.getAnnotations()); + } + return metadataBody.isEmpty() ? null : metadataBody; + } + + private V3ServiceBinding getServiceKeyResourceByNameAndServiceInstanceGuid(String name, UUID serviceInstanceGuid) { + String query = "/v3/service_credential_bindings?per_page=" + DEFAULT_PAGE_SIZE + "&type=key&service_instance_guids=" + + serviceInstanceGuid + "&names=" + name; + List keys = cc.list(query, new ParameterizedTypeReference>() { + }); + return keys.isEmpty() ? null : keys.get(0); + } + + private List listServiceKeyResources(UUID serviceInstanceGuid) { + String query = "/v3/service_credential_bindings?per_page=" + DEFAULT_PAGE_SIZE + "&type=key&service_instance_guids=" + + serviceInstanceGuid; + return cc.list(query, new ParameterizedTypeReference>() { + }); + } + + // CF v3 returns 404 when fetching the details of a service key whose creation failed; treat that as empty credentials, matching the OSS impl. + private Map getServiceKeyCredentials(String keyGuid) { + Optional details = cc.getOptional("/v3/service_credential_bindings/" + keyGuid + "/details", + V3ServiceKeyDetails.class); + return details.map(V3ServiceKeyDetails::credentials) + .orElse(Collections.emptyMap()); + } + + private Optional extractJobGuid(ResponseEntity accepted) { + URI location = accepted.getHeaders() + .getLocation(); + if (location == null) { + return Optional.empty(); + } + String href = location.toString(); + int idx = href.lastIndexOf("/v3/jobs/"); + String jobGuid = idx < 0 ? href.substring(href.lastIndexOf('/') + 1) : href.substring(idx + "/v3/jobs/".length()); + return Optional.of(jobGuid); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3Operations.java new file mode 100644 index 0000000000..9f5672110d --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3Operations.java @@ -0,0 +1,82 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.List; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceOffering; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServicePlan; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceOffering; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceOfferingMapper; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServicePlan; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServicePlanMapper; +import org.springframework.core.ParameterizedTypeReference; + +/** + * CF v3 service offerings operations of the in-house {@code CloudControllerRestClient}. Reproduces the HTTP shape, filtering and + * per-offering plan fan-out of the OSS {@code CloudControllerRestClientImpl#getServiceOfferings} on top of the shared + * {@link CloudControllerV3Client} machinery. + * + *

+ * Endpoint map (see {@code docs/cf-java-client-migration/02-endpoint-inventory.md}): + *

+ *
    + *
  • list offerings → {@code GET /v3/service_offerings?space_guids=} (paginated);
  • + *
  • resolve each offering's plans → {@code GET /v3/service_plans?service_offering_guids=} (paginated).
  • + *
+ * + *

+ * This mirrors the OSS {@code getServiceResources()} + {@code zipWithAuxiliaryServiceOfferingContent(...)} flow: the offerings are scoped + * to the target space, and each offering is enriched with the plans belonging to it before being mapped to the domain object. + *

+ */ +public class ServiceOfferingsV3Operations { + + // CF v3 caps per_page at 5000; use a large page to minimise round-trips (the pagination walker still handles multiple pages). + private static final int PER_PAGE = 5000; + + private static final ParameterizedTypeReference> OFFERING_LIST_TYPE = new ParameterizedTypeReference<>() { + }; + private static final ParameterizedTypeReference> PLAN_LIST_TYPE = new ParameterizedTypeReference<>() { + }; + + private final CloudControllerV3Client cc; + private final CloudSpace target; + + public ServiceOfferingsV3Operations(CloudControllerV3Client cc, CloudSpace target) { + this.cc = cc; + this.target = target; + } + + public List getServiceOfferings() { + return getServiceOfferingResources().stream() + .map(this::toCloudServiceOfferingWithPlans) + .toList(); + } + + // Mirrors the OSS getServiceResources(): list the target space's offerings (space_guids filter), walking all pages. + private List getServiceOfferingResources() { + StringBuilder query = new StringBuilder("/v3/service_offerings?per_page=" + PER_PAGE); + if (target != null && target.getGuid() != null) { + query.append("&space_guids=") + .append(target.getGuid()); + } + return cc.list(query.toString(), OFFERING_LIST_TYPE); + } + + // Mirrors the OSS zipWithAuxiliaryServiceOfferingContent(...): fetch the offering's plans, then map to the domain object. + private CloudServiceOffering toCloudServiceOfferingWithPlans(V3ServiceOffering serviceOffering) { + List servicePlans = getServicePlans(serviceOffering.guid()); + return V3ServiceOfferingMapper.toCloudServiceOffering(serviceOffering, servicePlans); + } + + // Mirrors the OSS getServicePlanResourcesByServiceOfferingGuid(...): list the plans belonging to the given offering, walking all pages. + private List getServicePlans(String serviceOfferingGuid) { + String uri = "/v3/service_plans?service_offering_guids=" + serviceOfferingGuid + "&per_page=" + PER_PAGE; + return cc.list(uri, PLAN_LIST_TYPE) + .stream() + .map(V3ServicePlanMapper::toCloudServicePlan) + .toList(); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3Operations.java new file mode 100644 index 0000000000..d3e2fd116d --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3Operations.java @@ -0,0 +1,122 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServicePlanVisibility; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServicePlan; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; + +/** + * CF v3 service plans operations of the in-house {@code CloudControllerRestClient}. Reproduces the HTTP shape, filtering and + * broker-scoped fan-out of the OSS {@code CloudControllerRestClientImpl#updateServicePlanVisibilityForBroker} on top of the shared + * {@link CloudControllerV3Client} machinery. + * + *

+ * Endpoint map (see {@code docs/cf-java-client-migration/02-endpoint-inventory.md}): + *

+ *
    + *
  • resolve broker by name → {@code GET /v3/service_brokers?names=} (404 when absent, as the broker is required);
  • + *
  • resolve the broker's offerings → {@code GET /v3/service_offerings?service_broker_guids=&space_guids=};
  • + *
  • resolve each offering's plans → {@code GET /v3/service_plans?service_offering_guids=};
  • + *
  • update visibility → {@code PATCH /v3/service_plans/{guid}/visibility} with body {@code { "type": "" }}.
  • + *
+ * + *

+ * The {@code visibility} body value is the lowercase CF visibility token ({@code public|admin|organization}); this matches the OSS impl, + * which passes {@code Visibility.from(visibility.toString())} where {@link ServicePlanVisibility#toString()} lower-cases the enum name. + *

+ */ +public class ServicePlansV3Operations { + + // CF v3 caps per_page at 5000; use a large page to minimise round-trips (the pagination walker still handles multiple pages). + private static final int PER_PAGE = 5000; + + private static final ParameterizedTypeReference> BROKER_LIST_TYPE = new ParameterizedTypeReference<>() { + }; + private static final ParameterizedTypeReference> OFFERING_LIST_TYPE = new ParameterizedTypeReference<>() { + }; + private static final ParameterizedTypeReference> PLAN_LIST_TYPE = new ParameterizedTypeReference<>() { + }; + + private final CloudControllerV3Client cc; + private final CloudSpace target; + + public ServicePlansV3Operations(CloudControllerV3Client cc, CloudSpace target) { + this.cc = cc; + this.target = target; + } + + public void updateServicePlanVisibilityForBroker(String name, ServicePlanVisibility visibility) { + UUID brokerGuid = getRequiredServiceBrokerGuid(name); + List servicePlanGuids = findServicePlanGuidsByBrokerGuid(brokerGuid); + for (UUID servicePlanGuid : servicePlanGuids) { + updateServicePlanVisibility(servicePlanGuid, visibility); + } + } + + // Mirrors the OSS getServiceBroker(name, true): find the broker by name in the (global) broker list and 404 if it is absent. + private UUID getRequiredServiceBrokerGuid(String name) { + String uri = "/v3/service_brokers?names=" + name + "&per_page=" + PER_PAGE; + return cc.list(uri, BROKER_LIST_TYPE) + .stream() + .findFirst() + .map(broker -> UUID.fromString(broker.guid())) + .orElseThrow(() -> new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", + "Service broker " + name + " not found.")); + } + + // Mirrors the OSS findServicePlansByBrokerGuid: list the broker's offerings (scoped to the target space), then each offering's plans. + private List findServicePlanGuidsByBrokerGuid(UUID brokerGuid) { + List offeringGuids = findServiceOfferingGuidsByBrokerGuid(brokerGuid); + if (offeringGuids.isEmpty()) { + return List.of(); + } + String offeringGuidsFilter = offeringGuids.stream() + .map(UUID::toString) + .reduce((a, b) -> a + "," + b) + .orElse(""); + String uri = "/v3/service_plans?service_offering_guids=" + offeringGuidsFilter + "&per_page=" + PER_PAGE; + return cc.list(uri, PLAN_LIST_TYPE) + .stream() + .map(plan -> UUID.fromString(plan.guid())) + .toList(); + } + + private List findServiceOfferingGuidsByBrokerGuid(UUID brokerGuid) { + StringBuilder query = new StringBuilder("/v3/service_offerings?service_broker_guids=" + brokerGuid + "&per_page=" + PER_PAGE); + if (target != null && target.getGuid() != null) { + query.append("&space_guids=") + .append(target.getGuid()); + } + return cc.list(query.toString(), OFFERING_LIST_TYPE) + .stream() + .map(offering -> UUID.fromString(offering.guid())) + .toList(); + } + + // Mirrors the OSS updateServicePlanVisibility(UUID, ServicePlanVisibility): PATCH the plan's visibility with the lowercase CF token. + private void updateServicePlanVisibility(UUID servicePlanGuid, ServicePlanVisibility visibility) { + cc.getRestClient() + .patch() + .uri("/v3/service_plans/{guid}/visibility", servicePlanGuid) + .body(Map.of("type", visibility.toString())) + .retrieve() + .toBodilessEntity(); + } + + // Minimal wire models to resolve GUIDs by name/relationship without depending on the (separately-owned) broker/offering groups. + @com.fasterxml.jackson.annotation.JsonIgnoreProperties(ignoreUnknown = true) + private record V3ServiceBrokerRef(@com.fasterxml.jackson.annotation.JsonProperty("guid") String guid) { + } + + @com.fasterxml.jackson.annotation.JsonIgnoreProperties(ignoreUnknown = true) + private record V3ServiceOfferingRef(@com.fasterxml.jackson.annotation.JsonProperty("guid") String guid) { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/StacksV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/StacksV3Operations.java new file mode 100644 index 0000000000..18e8537756 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/StacksV3Operations.java @@ -0,0 +1,63 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.List; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudStack; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Stack; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3StackMapper; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; + +/** + * CF v3 stacks operations of the cf-java-client replacement. Reproduces the HTTP shape and domain mapping of the OSS + * {@code CloudControllerRestClientImpl} stack methods: + *
    + *
  • {@code getStacks()} → {@code GET /v3/stacks} (paginated);
  • + *
  • {@code getStack(name[, required])} → {@code GET /v3/stacks?names=}, taking the first match.
  • + *
+ * Stacks are a global resource, so — like the OSS impl — the listing is not filtered by the target space. + */ +public class StacksV3Operations { + + private static final ParameterizedTypeReference> STACK_PAGE = new ParameterizedTypeReference<>() { + }; + + private final CloudControllerV3Client cc; + private final CloudSpace target; + + public StacksV3Operations(CloudControllerV3Client cc, CloudSpace target) { + this.cc = cc; + this.target = target; + } + + public CloudStack getStack(String name) { + return getStack(name, true); + } + + public CloudStack getStack(String name, boolean required) { + CloudStack stack = findStackByName(name); + if (stack == null && required) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Stack " + name + " not found."); + } + return stack; + } + + public List getStacks() { + return cc.list("/v3/stacks?per_page=5000", STACK_PAGE) + .stream() + .map(V3StackMapper::toCloudStack) + .toList(); + } + + private CloudStack findStackByName(String name) { + return cc.list("/v3/stacks?names=" + name + "&per_page=5000", STACK_PAGE) + .stream() + .map(V3StackMapper::toCloudStack) + .findFirst() + .orElse(null); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/TasksV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/TasksV3Operations.java new file mode 100644 index 0000000000..0fd5155e8d --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/TasksV3Operations.java @@ -0,0 +1,112 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudTask; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Application; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Task; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3TaskMapper; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; + +/** + * CF v3 task operations for the cf-java-client replacement. Reproduces the HTTP shape and domain-mapping of the OSS + * {@code CloudControllerRestClientImpl} task methods: + *
    + *
  • {@code getTask} → {@code GET /v3/tasks/{guid}}
  • + *
  • {@code getTasks} → {@code GET /v3/tasks?app_guids={appGuid}} (paginated)
  • + *
  • {@code runTask} → {@code POST /v3/apps/{appGuid}/tasks}
  • + *
  • {@code cancelTask} → {@code POST /v3/tasks/{guid}/actions/cancel}
  • + *
+ * Application-name resolution mirrors the OSS impl's {@code getRequiredApplicationGuid}: it queries the target space and throws + * {@link CloudOperationException} with {@link HttpStatus#NOT_FOUND} when the application does not exist. + */ +public class TasksV3Operations { + + // CF v3 caps per_page at 5000; use a large page to minimise round-trips (the pagination walker still handles multiple pages). + private static final int DEFAULT_PAGE_SIZE = 5000; + + private final CloudControllerV3Client cc; + private final CloudSpace target; + + public TasksV3Operations(CloudControllerV3Client cc, CloudSpace target) { + this.cc = cc; + this.target = target; + } + + public CloudTask getTask(UUID taskGuid) { + V3Task task = cc.get("/v3/tasks/" + taskGuid, V3Task.class); + return task == null ? null : V3TaskMapper.toCloudTask(task); + } + + public List getTasks(String applicationName) { + UUID applicationGuid = getRequiredApplicationGuid(applicationName); + String query = "/v3/tasks?per_page=" + DEFAULT_PAGE_SIZE + "&app_guids=" + applicationGuid; + List tasks = cc.list(query, new ParameterizedTypeReference>() { + }); + return tasks.stream() + .map(V3TaskMapper::toCloudTask) + .toList(); + } + + public CloudTask runTask(String applicationName, CloudTask task) { + UUID applicationGuid = getRequiredApplicationGuid(applicationName); + V3Task created = cc.getRestClient() + .post() + .uri("/v3/apps/{guid}/tasks", applicationGuid) + .body(buildCreateTaskBody(task)) + .retrieve() + .body(V3Task.class); + return created == null ? null : V3TaskMapper.toCloudTask(created); + } + + public CloudTask cancelTask(UUID taskGuid) { + V3Task cancelled = cc.getRestClient() + .post() + .uri("/v3/tasks/{guid}/actions/cancel", taskGuid) + .retrieve() + .body(V3Task.class); + return cancelled == null ? null : V3TaskMapper.toCloudTask(cancelled); + } + + private Map buildCreateTaskBody(CloudTask task) { + Map body = new HashMap<>(); + body.put("command", task.getCommand()); + body.put("name", task.getName()); + CloudTask.Limits limits = task.getLimits(); + if (limits != null) { + if (limits.getMemory() != null) { + body.put("memory_in_mb", limits.getMemory()); + } + if (limits.getDisk() != null) { + body.put("disk_in_mb", limits.getDisk()); + } + } + return body; + } + + // Mirrors the OSS impl's getRequiredApplicationGuid: look up the app in the target space and 404 if it is absent. + private UUID getRequiredApplicationGuid(String applicationName) { + StringBuilder query = new StringBuilder("/v3/apps?per_page=" + DEFAULT_PAGE_SIZE); + if (target != null && target.getGuid() != null) { + query.append("&space_guids=") + .append(target.getGuid()); + } + query.append("&names=") + .append(applicationName); + List apps = cc.list(query.toString(), new ParameterizedTypeReference>() { + }); + if (apps.isEmpty()) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Application " + applicationName + " not found."); + } + return UUID.fromString(apps.get(0) + .guid()); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Application.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Application.java new file mode 100644 index 0000000000..2eab011a70 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Application.java @@ -0,0 +1,53 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of a CF v3 application resource ({@code GET/POST /v3/apps}). + * + *
+ * { "guid": "...", "name": "...", "state": "STARTED|STOPPED", "created_at": "...", "updated_at": "...",
+ *   "lifecycle": { "type": "buildpack|docker|cnb", "data": { "buildpacks": [...], "stack": "..." } },
+ *   "metadata": { "labels": {...}, "annotations": {...} },
+ *   "relationships": { "space": { "data": { "guid": "..." } } } }
+ * 
+ */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3Application(@JsonProperty("guid") String guid, @JsonProperty("name") String name, @JsonProperty("state") String state, + @JsonProperty("created_at") String createdAt, @JsonProperty("updated_at") String updatedAt, + @JsonProperty("lifecycle") V3Lifecycle lifecycle, @JsonProperty("metadata") V3Metadata metadata, + @JsonProperty("relationships") V3Relationships relationships) { + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Lifecycle(@JsonProperty("type") String type, @JsonProperty("data") V3LifecycleData data) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3LifecycleData(@JsonProperty("buildpacks") List buildpacks, @JsonProperty("stack") String stack) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Relationships(@JsonProperty("space") V3ToOneRelationship space) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3ToOneRelationship(@JsonProperty("data") V3RelationshipData data) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3RelationshipData(@JsonProperty("guid") String guid) { + } + + /** + * The CF v3 app environment variables response ({@code GET /v3/apps/{guid}/environment_variables}): + * {@code { "var": { "KEY": "VALUE", ... } }}. + */ + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3EnvironmentVariables(@JsonProperty("var") Map var) { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ApplicationMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ApplicationMapper.java new file mode 100644 index 0000000000..ae4de63eb3 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ApplicationMapper.java @@ -0,0 +1,59 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.Collections; +import java.util.Map; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudApplication; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableLifecycle; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Lifecycle; +import org.cloudfoundry.multiapps.controller.client.facade.domain.LifecycleType; + +/** + * Maps the {@link V3Application} wire model to the project's {@link CloudApplication} domain object. Mirrors the OSS + * {@code RawCloudApplication} adapter field-for-field, so both client implementations yield identical domain objects. + */ +public final class V3ApplicationMapper { + + private static final String BUILDPACKS = "buildpacks"; + private static final String STACK = "stack"; + + private V3ApplicationMapper() { + } + + public static CloudApplication toCloudApplication(V3Application app, CloudSpace space) { + return ImmutableCloudApplication.builder() + .metadata(V3ResourceMappers.parseMetadata(app.guid(), app.createdAt(), app.updatedAt())) + .v3Metadata(V3ResourceMappers.toV3Metadata(app.metadata())) + .name(app.name()) + .state(parseState(app.state())) + .lifecycle(parseLifecycle(app.lifecycle())) + .space(space) + .build(); + } + + private static CloudApplication.State parseState(String state) { + return state == null ? null : CloudApplication.State.valueOf(state); + } + + private static Lifecycle parseLifecycle(V3Application.V3Lifecycle lifecycle) { + if (lifecycle == null || lifecycle.type() == null) { + return null; + } + return ImmutableLifecycle.builder() + .type(LifecycleType.valueOf(lifecycle.type() + .toUpperCase())) + .data(extractLifecycleData(lifecycle.data())) + .build(); + } + + private static Map extractLifecycleData(V3Application.V3LifecycleData data) { + if (data == null) { + return Collections.emptyMap(); + } + return Map.of(BUILDPACKS, data.buildpacks() == null ? Collections.emptyList() : data.buildpacks(), + STACK, data.stack() == null ? "" : data.stack()); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AuditEvent.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AuditEvent.java new file mode 100644 index 0000000000..de24e0babc --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AuditEvent.java @@ -0,0 +1,30 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of a CF v3 audit event resource ({@code GET /v3/audit_events}). + * + *
+ * { "guid": "...", "created_at": "...", "updated_at": "...", "type": "audit.app.update",
+ *   "actor":  { "guid": "...", "type": "user",        "name": "..." },
+ *   "target": { "guid": "...", "type": "app",         "name": "..." },
+ *   "data": { ... }, "space": {...}, "organization": {...} }
+ * 
+ * + * Only the fields the domain {@code CloudEvent} needs are mapped; everything else is ignored so CF can evolve the payload without breaking + * us. Mirrors the CF v3 JSON consumed by the OSS {@code AuditEventResource} adapter (whose {@code actor}/{@code target} carry + * {@code guid}/{@code name}/{@code type}). + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3AuditEvent(@JsonProperty("guid") String guid, @JsonProperty("created_at") String createdAt, + @JsonProperty("updated_at") String updatedAt, @JsonProperty("type") String type, + @JsonProperty("actor") V3Participant actor, @JsonProperty("target") V3Participant target) { + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Participant(@JsonProperty("guid") String guid, @JsonProperty("type") String type, + @JsonProperty("name") String name) { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AuditEventMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AuditEventMapper.java new file mode 100644 index 0000000000..ede9fc9837 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AuditEventMapper.java @@ -0,0 +1,40 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEvent; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEvent.Participant; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudEvent; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudEvent.ImmutableParticipant; + +/** + * Maps the {@link V3AuditEvent} wire model to the project's {@link CloudEvent} domain object. Mirrors the OSS {@code RawCloudEvent} adapter + * field-for-field, so both client implementations yield identical domain objects. + *

+ * As in the OSS adapter, a {@code null} actor/target still yields an empty (non-{@code null}) {@link Participant}. + */ +public final class V3AuditEventMapper { + + private V3AuditEventMapper() { + } + + public static CloudEvent toCloudEvent(V3AuditEvent event) { + return ImmutableCloudEvent.builder() + .metadata(V3ResourceMappers.parseMetadata(event.guid(), event.createdAt(), event.updatedAt())) + .target(parseParticipant(event.target())) + .actor(parseParticipant(event.actor())) + .type(event.type()) + .build(); + } + + private static Participant parseParticipant(V3AuditEvent.V3Participant participant) { + if (participant == null) { + return ImmutableParticipant.builder() + .build(); + } + return ImmutableParticipant.builder() + .guid(V3ResourceMappers.parseNullableGuid(participant.guid())) + .name(participant.name()) + .type(participant.type()) + .build(); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Build.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Build.java new file mode 100644 index 0000000000..15fd5ec357 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Build.java @@ -0,0 +1,35 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of a CF v3 build resource ({@code GET/POST /v3/builds}, {@code GET /v3/apps/{guid}/builds}). + * + *

+ * { "guid": "...", "created_at": "...", "updated_at": "...",
+ *   "state": "STAGING|STAGED|FAILED", "error": "...",
+ *   "created_by": { "guid": "...", "name": "..." },
+ *   "package": { "guid": "..." },
+ *   "droplet": { "guid": "..." } }
+ * 
+ */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3Build(@JsonProperty("guid") String guid, @JsonProperty("created_at") String createdAt, + @JsonProperty("updated_at") String updatedAt, @JsonProperty("state") String state, + @JsonProperty("error") String error, @JsonProperty("created_by") V3CreatedBy createdBy, + @JsonProperty("package") V3PackageReference inputPackage, @JsonProperty("droplet") V3DropletReference droplet) { + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3CreatedBy(@JsonProperty("guid") String guid, @JsonProperty("name") String name) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3PackageReference(@JsonProperty("guid") String guid) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3DropletReference(@JsonProperty("guid") String guid) { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3BuildMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3BuildMapper.java new file mode 100644 index 0000000000..c40417b701 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3BuildMapper.java @@ -0,0 +1,60 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudBuild; +import org.cloudfoundry.multiapps.controller.client.facade.domain.DropletInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudBuild; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudBuild.ImmutableCreatedBy; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudBuild.ImmutablePackageInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableDropletInfo; + +/** + * Maps the {@link V3Build} wire model to the project's {@link CloudBuild} domain object. Mirrors the OSS {@code RawCloudBuild} adapter + * field-for-field, so both client implementations yield identical domain objects. + */ +public final class V3BuildMapper { + + private V3BuildMapper() { + } + + public static CloudBuild toCloudBuild(V3Build build) { + return ImmutableCloudBuild.builder() + .metadata(V3ResourceMappers.parseMetadata(build.guid(), build.createdAt(), build.updatedAt())) + .createdBy(parseCreatedBy(build.createdBy())) + .packageInfo(parsePackageInfo(build.inputPackage())) + .dropletInfo(parseDropletInfo(build.droplet())) + .state(parseState(build.state())) + .error(build.error()) + .build(); + } + + private static CloudBuild.CreatedBy parseCreatedBy(V3Build.V3CreatedBy createdBy) { + if (createdBy == null) { + return null; + } + return ImmutableCreatedBy.builder() + .guid(V3ResourceMappers.parseNullableGuid(createdBy.guid())) + .name(createdBy.name()) + .build(); + } + + private static CloudBuild.PackageInfo parsePackageInfo(V3Build.V3PackageReference inputPackage) { + if (inputPackage == null) { + return null; + } + return ImmutablePackageInfo.of(V3ResourceMappers.parseNullableGuid(inputPackage.guid())); + } + + private static DropletInfo parseDropletInfo(V3Build.V3DropletReference droplet) { + if (droplet == null || droplet.guid() == null) { + return null; + } + return ImmutableDropletInfo.builder() + .guid(V3ResourceMappers.parseNullableGuid(droplet.guid())) + .build(); + } + + private static CloudBuild.State parseState(String state) { + return state == null ? null : CloudBuild.State.valueOf(state); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Domain.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Domain.java new file mode 100644 index 0000000000..8bc9403d99 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Domain.java @@ -0,0 +1,45 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of a CF v3 domain resource ({@code GET/POST /v3/domains}, {@code GET /v3/organizations/{guid}/domains}, + * {@code GET /v3/organizations/{guid}/domains/default}). + * + *
+ * { "guid": "...", "name": "example.com", "created_at": "...", "updated_at": "...",
+ *   "metadata": { "labels": {...}, "annotations": {...} },
+ *   "relationships": { "organization": { "data": { "guid": "..." } | null } } }
+ * 
+ * + * The {@code relationships.organization.data} discriminates shared domains (data == null) from private domains (data != null); this + * mirrors the filtering the OSS impl applied on {@code DomainResource.getRelationships().getOrganization().getData()}. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3Domain(@JsonProperty("guid") String guid, @JsonProperty("name") String name, + @JsonProperty("created_at") String createdAt, @JsonProperty("updated_at") String updatedAt, + @JsonProperty("metadata") V3Metadata metadata, @JsonProperty("relationships") V3DomainRelationships relationships) { + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3DomainRelationships(@JsonProperty("organization") V3ToOneRelationship organization) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3ToOneRelationship(@JsonProperty("data") V3RelationshipData data) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3RelationshipData(@JsonProperty("guid") String guid) { + } + + /** + * True when this domain is scoped to an organization (a private domain), i.e. its {@code relationships.organization.data} is present. + * A shared domain has {@code data == null}. Mirrors the OSS {@code getPrivateDomainResources}/{@code getSharedDomainResources} filters. + */ + public boolean isPrivate() { + return relationships != null && relationships.organization() != null && relationships.organization() + .data() != null; + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3DomainMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3DomainMapper.java new file mode 100644 index 0000000000..98e1c96bb8 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3DomainMapper.java @@ -0,0 +1,22 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudDomain; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudDomain; + +/** + * Maps the {@link V3Domain} wire model to the project's {@link CloudDomain} domain object. Mirrors the OSS {@code RawCloudDomain} adapter + * field-for-field (metadata from the resource envelope + {@code name}), so both client implementations yield identical domain objects. + */ +public final class V3DomainMapper { + + private V3DomainMapper() { + } + + public static CloudDomain toCloudDomain(V3Domain domain) { + return ImmutableCloudDomain.builder() + .metadata(V3ResourceMappers.parseMetadata(domain.guid(), domain.createdAt(), domain.updatedAt())) + .name(domain.name()) + .build(); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Droplet.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Droplet.java new file mode 100644 index 0000000000..5281407ddd --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Droplet.java @@ -0,0 +1,24 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of the CF v3 current-droplet response ({@code GET /v3/apps/{guid}/droplets/current}): + * + *
+ * { "guid": "...", "links": { "package": { "href": "https://.../v3/packages/{guid}" }, ... } }
+ * 
+ * + * The client only needs the droplet {@code guid} and the {@code package} link, from which the package GUID is derived. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3Droplet(@JsonProperty("guid") String guid, @JsonProperty("links") Map links) { + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Link(@JsonProperty("href") String href) { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3InstancesInfoMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3InstancesInfoMapper.java new file mode 100644 index 0000000000..f111e8acc6 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3InstancesInfoMapper.java @@ -0,0 +1,49 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableInstanceInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableInstancesInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.InstanceInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.InstanceState; +import org.cloudfoundry.multiapps.controller.client.facade.domain.InstancesInfo; + +/** + * Maps the {@link V3Process.V3ProcessStats} wire model to the project's {@link InstancesInfo} domain object. Mirrors the OSS + * {@code RawInstancesInfo} adapter field-for-field, so both client implementations yield identical domain objects. + *

+ * The instance state string comes from CF ({@code GET /v3/apps/{guid}/processes/web/stats}) and is resolved via + * {@link InstanceState#valueOfWithDefault(String)} (which falls back to {@link InstanceState#UNKNOWN} for unrecognised values), so no OSS + * leaf type is involved. + */ +public final class V3InstancesInfoMapper { + + private V3InstancesInfoMapper() { + } + + public static InstancesInfo toInstancesInfo(V3Process.V3ProcessStats processStats) { + return ImmutableInstancesInfo.builder() + .instances(parseProcessStatistics(processStats.resources())) + .build(); + } + + private static List parseProcessStatistics(List stats) { + if (stats == null) { + return Collections.emptyList(); + } + return stats.stream() + .map(V3InstancesInfoMapper::parseProcessStatistic) + .collect(Collectors.toList()); + } + + private static InstanceInfo parseProcessStatistic(V3Process.V3ProcessStatsResource statsResource) { + return ImmutableInstanceInfo.builder() + .index(statsResource.index()) + .state(InstanceState.valueOfWithDefault(statsResource.state())) + .isRoutable(Boolean.parseBoolean(statsResource.routable())) + .build(); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Job.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Job.java new file mode 100644 index 0000000000..84c55b5be2 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Job.java @@ -0,0 +1,45 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of a Cloud Controller v3 job resource ({@code GET /v3/jobs/{guid}}). + * + *

+ * { "guid": "...", "created_at": "...", "updated_at": "...", "state": "PROCESSING|POLLING|COMPLETE|FAILED", "operation": "...",
+ *   "errors": [ { "detail": "...", "title": "...", "code": N } ], "warnings": [ { "detail": "..." } ] }
+ * 
+ * + * The async job poller in {@link org.cloudfoundry.multiapps.controller.client.facade.rest.CloudControllerV3Client} polls this until + * {@code state} reaches a terminal value (COMPLETE / FAILED), mirroring the OSS {@code JobV3Util} behaviour. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3Job(@JsonProperty("guid") String guid, @JsonProperty("created_at") String createdAt, + @JsonProperty("updated_at") String updatedAt, @JsonProperty("state") String state, + @JsonProperty("operation") String operation, @JsonProperty("errors") List errors, + @JsonProperty("warnings") List warnings) { + + public boolean isComplete() { + return "COMPLETE".equals(state); + } + + public boolean isFailed() { + return "FAILED".equals(state); + } + + public boolean isTerminal() { + return isComplete() || isFailed(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Error(@JsonProperty("detail") String detail, @JsonProperty("title") String title, @JsonProperty("code") Integer code) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Warning(@JsonProperty("detail") String detail) { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3JobMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3JobMapper.java new file mode 100644 index 0000000000..89a153a5e4 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3JobMapper.java @@ -0,0 +1,57 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.List; +import java.util.stream.Collectors; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudAsyncJob; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudAsyncJob; +import org.cloudfoundry.multiapps.controller.client.facade.domain.JobState; + +/** + * Maps the {@link V3Job} wire model to the project's {@link CloudAsyncJob} domain object. Mirrors the OSS {@code RawCloudAsyncJob} adapter + * field-for-field, so both client implementations yield identical domain objects. + */ +public final class V3JobMapper { + + private V3JobMapper() { + } + + public static CloudAsyncJob toCloudAsyncJob(V3Job job) { + return ImmutableCloudAsyncJob.builder() + .metadata(V3ResourceMappers.parseMetadata(job.guid(), job.createdAt(), job.updatedAt())) + .state(parseState(job.state())) + .operation(job.operation()) + .warnings(getWarnings(job)) + .errors(getErrors(job)) + .build(); + } + + private static JobState parseState(String state) { + return state == null ? null : JobState.valueOf(state.toUpperCase()); + } + + private static String getWarnings(V3Job job) { + List warnings = job.warnings(); + if (warnings == null) { + return ""; + } + return warnings.stream() + .map(V3Job.V3Warning::detail) + .collect(Collectors.joining(",")); + } + + private static String getErrors(V3Job job) { + List errors = job.errors(); + if (errors == null) { + return ""; + } + return errors.stream() + .map(V3JobMapper::joinErrorDetails) + .collect(Collectors.joining(",")); + } + + private static String joinErrorDetails(V3Job.V3Error error) { + return String.join(" ", String.valueOf(error.code()), error.title(), error.detail()); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ListResponse.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ListResponse.java new file mode 100644 index 0000000000..43e91ce3a5 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ListResponse.java @@ -0,0 +1,49 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.Collections; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of a Cloud Controller v3 list response envelope: + * + *
+ * {
+ *   "pagination": { "total_results": N, "next": { "href": "..." } | null, ... },
+ *   "resources":  [ ... ]
+ * }
+ * 
+ * + * Only the fields the client actually needs are mapped; everything else is ignored so CF can evolve the payload without breaking us. + * The {@link org.cloudfoundry.multiapps.controller.client.facade.rest.CloudControllerV3Client pagination walker} follows + * {@code pagination.next.href} until it is {@code null}, accumulating {@link #resources()} from every page. + * + * @param the wire type of a single resource in this list + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3ListResponse(@JsonProperty("pagination") Pagination pagination, @JsonProperty("resources") List resources) { + + public List resources() { + return resources == null ? Collections.emptyList() : resources; + } + + public String nextPageHref() { + if (pagination == null || pagination.next() == null) { + return null; + } + return pagination.next() + .href(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record Pagination(@JsonProperty("total_results") Integer totalResults, @JsonProperty("total_pages") Integer totalPages, + @JsonProperty("next") Link next, @JsonProperty("previous") Link previous) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record Link(@JsonProperty("href") String href) { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Metadata.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Metadata.java new file mode 100644 index 0000000000..ef50b44d5c --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Metadata.java @@ -0,0 +1,24 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.Collections; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of the CF v3 {@code metadata} object (labels + annotations) attached to most resources. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3Metadata(@JsonProperty("labels") Map labels, + @JsonProperty("annotations") Map annotations) { + + public Map labels() { + return labels == null ? Collections.emptyMap() : labels; + } + + public Map annotations() { + return annotations == null ? Collections.emptyMap() : annotations; + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Package.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Package.java new file mode 100644 index 0000000000..c67ff92e0b --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Package.java @@ -0,0 +1,36 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of a CF v3 package resource ({@code GET/POST /v3/packages}, {@code GET /v3/apps/{guid}/packages}). + * + *
+ * { "guid": "...", "type": "bits|docker", "state": "AWAITING_UPLOAD|PROCESSING_UPLOAD|READY|FAILED|COPYING|EXPIRED",
+ *   "created_at": "...", "updated_at": "...",
+ *   "data": {
+ *     // bits:
+ *     "checksum": { "type": "sha256", "value": "..." }, "error": "...",
+ *     // docker:
+ *     "image": "...", "username": "...", "password": "..."
+ *   },
+ *   "relationships": { "app": { "data": { "guid": "..." } } } }
+ * 
+ */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3Package(@JsonProperty("guid") String guid, @JsonProperty("type") String type, @JsonProperty("state") String state, + @JsonProperty("created_at") String createdAt, @JsonProperty("updated_at") String updatedAt, + @JsonProperty("data") V3PackageData data) { + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3PackageData(@JsonProperty("checksum") V3Checksum checksum, @JsonProperty("error") String error, + @JsonProperty("image") String image, @JsonProperty("username") String username, + @JsonProperty("password") String password) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Checksum(@JsonProperty("type") String type, @JsonProperty("value") String value) { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3PackageMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3PackageMapper.java new file mode 100644 index 0000000000..4f62f4b347 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3PackageMapper.java @@ -0,0 +1,76 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudPackage; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableBitsData; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudPackage; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableDockerData; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Status; + +/** + * Maps the {@link V3Package} wire model to the project's {@link CloudPackage} domain object. Mirrors the OSS {@code RawCloudPackage} + * adapter field-for-field, so both client implementations yield identical domain objects. + */ +public final class V3PackageMapper { + + private V3PackageMapper() { + } + + public static CloudPackage toCloudPackage(V3Package resource) { + return ImmutableCloudPackage.builder() + .metadata(V3ResourceMappers.parseMetadata(resource.guid(), resource.createdAt(), + resource.updatedAt())) + .status(parseStatus(resource)) + .data(parseData(resource)) + .type(parseType(resource)) + .build(); + } + + private static Status parseStatus(V3Package resource) { + return resource.state() == null ? null : Status.valueOf(resource.state()); + } + + private static CloudPackage.Type parseType(V3Package resource) { + return resource.type() == null ? null : CloudPackage.Type.from(resource.type()); + } + + private static CloudPackage.PackageData parseData(V3Package resource) { + if (resource.type() != null && CloudPackage.Type.from(resource.type()) == CloudPackage.Type.BITS) { + return parseBitsData(resource.data()); + } + return parseDockerData(resource.data()); + } + + private static CloudPackage.PackageData parseBitsData(V3Package.V3PackageData data) { + if (data == null) { + return ImmutableBitsData.builder() + .build(); + } + return ImmutableBitsData.builder() + .checksum(parseBitsChecksum(data.checksum())) + .error(data.error()) + .build(); + } + + private static org.cloudfoundry.multiapps.controller.client.facade.domain.BitsData.Checksum + parseBitsChecksum(V3Package.V3Checksum checksum) { + if (checksum == null) { + return null; + } + return ImmutableBitsData.ImmutableChecksum.builder() + .algorithm(checksum.type()) + .value(checksum.value()) + .build(); + } + + private static CloudPackage.PackageData parseDockerData(V3Package.V3PackageData data) { + if (data == null) { + return null; + } + return ImmutableDockerData.builder() + .image(data.image()) + .username(data.username()) + .password(data.password()) + .build(); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Process.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Process.java new file mode 100644 index 0000000000..263a4d29dc --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Process.java @@ -0,0 +1,71 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-models for the CF v3 process family of endpoints consumed by the client: + *
    + *
  • {@link V3Process} — {@code GET /v3/apps/{guid}/processes/web} (also {@code GET /v3/processes/{guid}});
  • + *
  • {@link V3ProcessStats} — {@code GET /v3/apps/{guid}/processes/web/stats};
  • + *
  • {@link V3AppFeatures} / {@link V3AppFeature} — {@code GET /v3/apps/{guid}/features};
  • + *
  • {@link V3SshEnabled} — {@code GET /v3/apps/{guid}/ssh_enabled}.
  • + *
+ * + *
+ * // process
+ * { "guid": "...", "command": "...", "instances": 1, "memory_in_mb": 256, "disk_in_mb": 1024,
+ *   "health_check": { "type": "port|http|process", "data": { "timeout": N, "invocation_timeout": N, "endpoint": "...", "interval": N } },
+ *   "readiness_health_check": { "type": "process|http|port", "data": { "invocation_timeout": N, "endpoint": "...", "interval": N } } }
+ * 
+ * + * Every record ignores unknown fields so CF can evolve the payload without breaking us. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3Process(@JsonProperty("guid") String guid, @JsonProperty("command") String command, + @JsonProperty("instances") Integer instances, @JsonProperty("memory_in_mb") Integer memoryInMb, + @JsonProperty("disk_in_mb") Integer diskInMb, @JsonProperty("health_check") V3HealthCheck healthCheck, + @JsonProperty("readiness_health_check") V3HealthCheck readinessHealthCheck) { + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3HealthCheck(@JsonProperty("type") String type, @JsonProperty("data") V3HealthCheckData data) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3HealthCheckData(@JsonProperty("timeout") Integer timeout, + @JsonProperty("invocation_timeout") Integer invocationTimeout, + @JsonProperty("endpoint") String endpoint, @JsonProperty("interval") Integer interval) { + } + + /** + * The CF v3 process statistics response ({@code GET /v3/apps/{guid}/processes/web/stats}): + * {@code { "resources": [ { "index": 0, "state": "RUNNING", "routable": true }, ... ] }}. + */ + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3ProcessStats(@JsonProperty("resources") List resources) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3ProcessStatsResource(@JsonProperty("index") Integer index, @JsonProperty("state") String state, + @JsonProperty("routable") String routable) { + } + + /** + * The CF v3 app features response ({@code GET /v3/apps/{guid}/features}): + * {@code { "resources": [ { "name": "ssh", "enabled": true }, ... ] }}. + */ + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3AppFeature(@JsonProperty("name") String name, @JsonProperty("enabled") Boolean enabled) { + } + + /** + * The CF v3 app SSH-enabled response ({@code GET /v3/apps/{guid}/ssh_enabled}): + * {@code { "enabled": true, "reason": "..." }}. + */ + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3SshEnabled(@JsonProperty("enabled") Boolean enabled, @JsonProperty("reason") String reason) { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ProcessMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ProcessMapper.java new file mode 100644 index 0000000000..94de49472a --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ProcessMapper.java @@ -0,0 +1,60 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudProcess; +import org.cloudfoundry.multiapps.controller.client.facade.domain.HealthCheckType; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudProcess; + +/** + * Maps the {@link V3Process} wire model to the project's {@link CloudProcess} domain object. Mirrors the OSS + * {@code RawCloudProcess} adapter field-for-field, so both client implementations yield identical domain objects. + */ +public final class V3ProcessMapper { + + private V3ProcessMapper() { + } + + public static CloudProcess toCloudProcess(V3Process process) { + V3Process.V3HealthCheck healthCheck = process.healthCheck(); + V3Process.V3HealthCheck readinessHealthCheck = process.readinessHealthCheck(); + + Integer healthCheckTimeout = null; + String healthCheckHttpEndpoint = null; + Integer healthCheckInvocationTimeout = null; + Integer healthCheckInterval = null; + if (healthCheck != null && healthCheck.data() != null) { + V3Process.V3HealthCheckData healthCheckData = healthCheck.data(); + healthCheckTimeout = healthCheckData.timeout(); + healthCheckInvocationTimeout = healthCheckData.invocationTimeout(); + healthCheckHttpEndpoint = healthCheckData.endpoint(); + healthCheckInterval = healthCheckData.interval(); + } + + Integer readinessHealthCheckInvocationTimeout = null; + String readinessHealthCheckHttpEndpoint = null; + Integer readinessHealthCheckInterval = null; + if (readinessHealthCheck != null && readinessHealthCheck.data() != null) { + V3Process.V3HealthCheckData readinessHealthCheckData = readinessHealthCheck.data(); + readinessHealthCheckInvocationTimeout = readinessHealthCheckData.invocationTimeout(); + readinessHealthCheckHttpEndpoint = readinessHealthCheckData.endpoint(); + readinessHealthCheckInterval = readinessHealthCheckData.interval(); + } + + return ImmutableCloudProcess.builder() + .command(process.command()) + .instances(process.instances()) + .memoryInMb(process.memoryInMb()) + .diskInMb(process.diskInMb()) + .healthCheckType(HealthCheckType.valueOf(healthCheck.type() + .toUpperCase())) + .healthCheckHttpEndpoint(healthCheckHttpEndpoint) + .healthCheckTimeout(healthCheckTimeout) + .healthCheckInvocationTimeout(healthCheckInvocationTimeout) + .healthCheckInterval(healthCheckInterval) + .readinessHealthCheckType(readinessHealthCheck == null ? null : readinessHealthCheck.type()) + .readinessHealthCheckHttpEndpoint(readinessHealthCheckHttpEndpoint) + .readinessHealthCheckInvocationTimeout(readinessHealthCheckInvocationTimeout) + .readinessHealthCheckInterval(readinessHealthCheckInterval) + .build(); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ResourceMappers.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ResourceMappers.java new file mode 100644 index 0000000000..e5fc872a2b --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ResourceMappers.java @@ -0,0 +1,73 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.time.LocalDateTime; +import java.time.format.DateTimeParseException; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Helpers shared by all CF v3 wire-model mappers: parse the resource envelope fields ({@code guid}, {@code created_at}, + * {@code updated_at}) into the project's {@link CloudMetadata}, and parse GUIDs/dates leniently — mirroring the behaviour of the OSS + * {@code RawCloudEntity} so the two client implementations produce identical domain objects. + */ +public final class V3ResourceMappers { + + private static final Logger LOGGER = LoggerFactory.getLogger(V3ResourceMappers.class); + + private V3ResourceMappers() { + } + + public static CloudMetadata parseMetadata(String guid, String createdAt, String updatedAt) { + return ImmutableCloudMetadata.builder() + .guid(parseNullableGuid(guid)) + .createdAt(parseNullableDate(createdAt)) + .updatedAt(parseNullableDate(updatedAt)) + .build(); + } + + /** + * Build the project's v3 {@link Metadata} (labels/annotations) from the wire representation. Kept as the OSS leaf type for now; it + * is re-homed to a project-owned type in the leaf-type migration step. + */ + public static Metadata toV3Metadata(V3Metadata metadata) { + if (metadata == null) { + return null; + } + return Metadata.builder() + .labels(metadata.labels()) + .annotations(metadata.annotations()) + .build(); + } + + public static UUID parseNullableGuid(String guid) { + if (guid == null) { + return null; + } + try { + return UUID.fromString(guid); + } catch (IllegalArgumentException e) { + LOGGER.warn("Could not parse GUID string: \"{}\"", guid, e); + return null; + } + } + + public static LocalDateTime parseNullableDate(String date) { + if (date == null) { + return null; + } + try { + // CF v3 timestamps are ISO-8601 with offset, e.g. 2026-08-04T10:15:30Z + return java.time.OffsetDateTime.parse(date) + .toLocalDateTime(); + } catch (DateTimeParseException e) { + LOGGER.warn("Could not parse date string: \"{}\"", date, e); + return null; + } + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Role.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Role.java new file mode 100644 index 0000000000..f280bc0267 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Role.java @@ -0,0 +1,22 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of a CF v3 role resource ({@code GET /v3/roles}). + * + *
+ * { "guid": "...", "created_at": "...", "updated_at": "...",
+ *   "type": "organization_auditor|organization_billing_manager|organization_manager|organization_user|space_auditor|space_developer|space_manager",
+ *   "relationships": { "user": { "data": { "guid": "..." } }, "space": { "data": { "guid": "..." } | null },
+ *                      "organization": { "data": { "guid": "..." } | null } } }
+ * 
+ * + * Only {@code type} is consumed by the mapper (mirroring the OSS {@code RawUserRole}); the remaining fields are documented for + * completeness and ignored so CF can evolve the payload without breaking us. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3Role(@JsonProperty("guid") String guid, @JsonProperty("created_at") String createdAt, + @JsonProperty("updated_at") String updatedAt, @JsonProperty("type") String type) { +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RoleMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RoleMapper.java new file mode 100644 index 0000000000..5b75520137 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RoleMapper.java @@ -0,0 +1,18 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.UserRole; + +/** + * Maps the {@link V3Role} wire model to the project's {@link UserRole} domain enum. Passes the raw CF v3 role {@code type} wire value + * (e.g. {@code space_developer}) straight to {@link UserRole#fromRoleType(String)}, so this mapper depends on no OSS types. + */ +public final class V3RoleMapper { + + private V3RoleMapper() { + } + + public static UserRole toUserRole(V3Role role) { + return UserRole.fromRoleType(role.type()); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Route.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Route.java new file mode 100644 index 0000000000..6cc6e7f991 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Route.java @@ -0,0 +1,57 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.Collections; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of a CF v3 route resource ({@code GET/POST /v3/routes}, {@code GET /v3/apps/{guid}/routes}). + * + *
+ * { "guid": "...", "host": "...", "path": "...", "port": 8080, "url": "host.domain/path",
+ *   "created_at": "...", "updated_at": "...",
+ *   "destinations": [ { "guid": "...", "app": { "guid": "..." }, "port": 8080, "weight": 1, "protocol": "http1" } ],
+ *   "metadata": { "labels": {...}, "annotations": {...} },
+ *   "relationships": { "space": { "data": { "guid": "..." } }, "domain": { "data": { "guid": "..." } } } }
+ * 
+ * + * Mirrors exactly the fields the OSS {@code RawCloudRoute} adapter consumes from {@code org.cloudfoundry.client.v3.routes.Route}, so + * both client implementations yield identical {@code CloudRoute} domain objects. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3Route(@JsonProperty("guid") String guid, @JsonProperty("host") String host, @JsonProperty("path") String path, + @JsonProperty("port") Integer port, @JsonProperty("url") String url, + @JsonProperty("created_at") String createdAt, @JsonProperty("updated_at") String updatedAt, + @JsonProperty("destinations") List destinations, @JsonProperty("metadata") V3Metadata metadata, + @JsonProperty("relationships") V3RouteRelationships relationships) { + + public List destinations() { + return destinations == null ? Collections.emptyList() : destinations; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Destination(@JsonProperty("guid") String guid, @JsonProperty("app") V3DestinationApp app, + @JsonProperty("port") Integer port, @JsonProperty("weight") Integer weight, + @JsonProperty("protocol") String protocol) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3DestinationApp(@JsonProperty("guid") String guid) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3RouteRelationships(@JsonProperty("space") V3ToOneRelationship space, + @JsonProperty("domain") V3ToOneRelationship domain) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3ToOneRelationship(@JsonProperty("data") V3RelationshipData data) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3RelationshipData(@JsonProperty("guid") String guid) { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RouteMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RouteMapper.java new file mode 100644 index 0000000000..b33b856d80 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RouteMapper.java @@ -0,0 +1,104 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import java.util.stream.Collectors; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudRoute; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudDomain; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudRoute; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableRouteDestination; +import org.cloudfoundry.multiapps.controller.client.facade.domain.RouteDestination; + +/** + * Maps the {@link V3Route} wire model to the project's {@link CloudRoute} domain object. Mirrors the OSS {@code RawCloudRoute} adapter + * field-for-field (including the domain-name derivation from the route URL and the requested-protocol lookup by application GUID), so + * both client implementations yield identical domain objects. + */ +public final class V3RouteMapper { + + private V3RouteMapper() { + } + + /** + * Maps a route without an application context (equivalent to OSS {@code ImmutableRawCloudRoute.of(route)}): the requested protocol is + * always {@code null}. + */ + public static CloudRoute toCloudRoute(V3Route route) { + return toCloudRoute(route, null); + } + + /** + * Maps a route in the context of a specific application (equivalent to OSS + * {@code ImmutableRawCloudRoute.builder().route(route).applicationGuid(applicationGuid).build()}): the requested protocol is taken + * from the destination that points at {@code applicationGuid}. + */ + public static CloudRoute toCloudRoute(V3Route route, UUID applicationGuid) { + List destinations = mapDestinations(route); + String domainGuid = route.relationships() + .domain() + .data() + .guid(); + return ImmutableCloudRoute.builder() + .metadata(V3ResourceMappers.parseMetadata(route.guid(), route.createdAt(), route.updatedAt())) + .appsUsingRoute(route.destinations() + .size()) + .host(route.host()) + .port(route.port()) + .domain(ImmutableCloudDomain.builder() + .name(computeDomain(route)) + .metadata(ImmutableCloudMetadata.of(UUID.fromString(domainGuid))) + .build()) + .path(route.path()) + .url(route.url()) + .destinations(destinations) + .requestedProtocol(computeRequestedProtocol(destinations, applicationGuid)) + .build(); + } + + private static String computeDomain(V3Route route) { + String domain = route.url(); + String host = route.host() == null ? "" : route.host(); + String path = route.path() == null ? "" : route.path(); + if (!host.isEmpty()) { + domain = domain.substring(host.length() + 1); + } + if (!path.isEmpty()) { + domain = domain.substring(0, domain.indexOf('/')); + } + if (route.port() != null) { + domain = domain.substring(0, domain.indexOf(':')); + } + return domain; + } + + private static List mapDestinations(V3Route route) { + return route.destinations() + .stream() + .map(destination -> ImmutableRouteDestination.builder() + .metadata(ImmutableCloudMetadata.builder() + .guid(UUID.fromString(destination.guid())) + .build()) + .applicationGuid(UUID.fromString(destination.app() + .guid())) + .port(destination.port()) + .weight(destination.weight()) + .protocol(destination.protocol()) + .build()) + .collect(Collectors.toList()); + } + + private static String computeRequestedProtocol(List destinations, UUID applicationGuid) { + if (applicationGuid == null) { + return null; + } + return destinations.stream() + .filter(destination -> Objects.equals(destination.getApplicationGuid(), applicationGuid)) + .findFirst() + .map(RouteDestination::getProtocol) + .orElse(null); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBinding.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBinding.java new file mode 100644 index 0000000000..9c24571233 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBinding.java @@ -0,0 +1,46 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of a CF v3 service credential binding resource ({@code GET/POST/PATCH/DELETE + * /v3/service_credential_bindings}). A binding is either of {@code type=app} (an application bound to a service instance) or + * {@code type=key} (a service key); the {@code relationships.app} is absent for keys. + * + *
+ * { "guid": "...", "name": "...", "type": "app|key", "created_at": "...", "updated_at": "...",
+ *   "last_operation": { "type": "create|delete", "state": "initial|in progress|succeeded|failed",
+ *                       "description": "...", "created_at": "...", "updated_at": "..." },
+ *   "metadata": { "labels": {...}, "annotations": {...} },
+ *   "relationships": { "app": { "data": { "guid": "..." } | null },
+ *                      "service_instance": { "data": { "guid": "..." } } } }
+ * 
+ */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3ServiceBinding(@JsonProperty("guid") String guid, @JsonProperty("name") String name, @JsonProperty("type") String type, + @JsonProperty("created_at") String createdAt, @JsonProperty("updated_at") String updatedAt, + @JsonProperty("last_operation") V3LastOperation lastOperation, + @JsonProperty("metadata") V3Metadata metadata, + @JsonProperty("relationships") V3Relationships relationships) { + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3LastOperation(@JsonProperty("type") String type, @JsonProperty("state") String state, + @JsonProperty("description") String description, @JsonProperty("created_at") String createdAt, + @JsonProperty("updated_at") String updatedAt) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Relationships(@JsonProperty("app") V3ToOneRelationship application, + @JsonProperty("service_instance") V3ToOneRelationship serviceInstance) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3ToOneRelationship(@JsonProperty("data") V3RelationshipData data) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3RelationshipData(@JsonProperty("guid") String guid) { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBindingMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBindingMapper.java new file mode 100644 index 0000000000..8a06576d65 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBindingMapper.java @@ -0,0 +1,71 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBinding; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceBinding; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableServiceCredentialBindingOperation; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceCredentialBindingOperation; + +/** + * Maps the {@link V3ServiceBinding} wire model to the project's {@link CloudServiceBinding} domain object. Mirrors the OSS + * {@code RawCloudServiceBinding} adapter field-for-field, so both client implementations yield identical domain objects: + *
    + *
  • {@code applicationGuid} — nullable; from {@code relationships.app.data.guid} (absent for {@code type=key} bindings);
  • + *
  • {@code serviceInstanceGuid} — required; from {@code relationships.service_instance.data.guid};
  • + *
  • {@code serviceBindingOperation} — from {@code last_operation}, mirroring {@link ServiceCredentialBindingOperation#from}.
  • + *
+ */ +public final class V3ServiceBindingMapper { + + private V3ServiceBindingMapper() { + } + + public static CloudServiceBinding toCloudServiceBinding(V3ServiceBinding binding) { + return ImmutableCloudServiceBinding.builder() + .metadata(V3ResourceMappers.parseMetadata(binding.guid(), binding.createdAt(), + binding.updatedAt())) + .applicationGuid(parseApplicationGuid(binding)) + .serviceInstanceGuid(parseServiceInstanceGuid(binding)) + .serviceBindingOperation(parseServiceBindingOperation(binding.lastOperation())) + .build(); + } + + private static UUID parseApplicationGuid(V3ServiceBinding binding) { + V3ServiceBinding.V3ToOneRelationship application = binding.relationships() == null ? null + : binding.relationships() + .application(); + if (application == null || application.data() == null) { + return null; + } + return V3ResourceMappers.parseNullableGuid(application.data() + .guid()); + } + + private static UUID parseServiceInstanceGuid(V3ServiceBinding binding) { + return UUID.fromString(binding.relationships() + .serviceInstance() + .data() + .guid()); + } + + private static ServiceCredentialBindingOperation parseServiceBindingOperation(V3ServiceBinding.V3LastOperation lastOperation) { + if (lastOperation == null) { + return null; + } + return ImmutableServiceCredentialBindingOperation.builder() + .type(ServiceCredentialBindingOperation.Type.fromString(lastOperation.type())) + .state(ServiceCredentialBindingOperation.State.fromString(lastOperation.state())) + .description(lastOperation.description()) + .createdAt(parseDate(lastOperation.createdAt())) + .updatedAt(parseDate(lastOperation.updatedAt())) + .build(); + } + + private static LocalDateTime parseDate(String date) { + return date == null ? null : LocalDateTime.parse(date, DateTimeFormatter.ISO_DATE_TIME); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBroker.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBroker.java new file mode 100644 index 0000000000..4be2e36a9b --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBroker.java @@ -0,0 +1,35 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of a CF v3 service broker resource ({@code GET/POST/PATCH /v3/service_brokers}). + * + *
+ * { "guid": "...", "name": "...", "url": "...", "created_at": "...", "updated_at": "...",
+ *   "metadata": { "labels": {...}, "annotations": {...} },
+ *   "relationships": { "space": { "data": { "guid": "..." } | null } } }
+ * 
+ * + * Only the fields the client maps into {@code CloudServiceBroker} are declared; everything else is ignored. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3ServiceBroker(@JsonProperty("guid") String guid, @JsonProperty("name") String name, @JsonProperty("url") String url, + @JsonProperty("created_at") String createdAt, @JsonProperty("updated_at") String updatedAt, + @JsonProperty("metadata") V3Metadata metadata, + @JsonProperty("relationships") V3Relationships relationships) { + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Relationships(@JsonProperty("space") V3ToOneRelationship space) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3ToOneRelationship(@JsonProperty("data") V3RelationshipData data) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3RelationshipData(@JsonProperty("guid") String guid) { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBrokerMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBrokerMapper.java new file mode 100644 index 0000000000..34d1bab18d --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBrokerMapper.java @@ -0,0 +1,40 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.Optional; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBroker; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceBroker; + +/** + * Maps the {@link V3ServiceBroker} wire model to the project's {@link CloudServiceBroker} domain object. Mirrors the OSS + * {@code RawCloudServiceBroker} adapter field-for-field, so both client implementations yield identical domain objects. + * + *

+ * As in the OSS adapter, {@code username}/{@code password} are write-only credentials that CF never returns, so they are not mapped; + * only {@code name}, {@code url} and the (optional) space GUID from {@code relationships.space.data.guid} are populated. + *

+ */ +public final class V3ServiceBrokerMapper { + + private V3ServiceBrokerMapper() { + } + + public static CloudServiceBroker toCloudServiceBroker(V3ServiceBroker serviceBroker) { + return ImmutableCloudServiceBroker.builder() + .metadata(V3ResourceMappers.parseMetadata(serviceBroker.guid(), serviceBroker.createdAt(), + serviceBroker.updatedAt())) + .name(serviceBroker.name()) + .url(serviceBroker.url()) + .spaceGuid(getSpaceGuid(serviceBroker)) + .build(); + } + + private static String getSpaceGuid(V3ServiceBroker serviceBroker) { + return Optional.ofNullable(serviceBroker.relationships()) + .map(V3ServiceBroker.V3Relationships::space) + .map(V3ServiceBroker.V3ToOneRelationship::data) + .map(V3ServiceBroker.V3RelationshipData::guid) + .orElse(null); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstance.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstance.java new file mode 100644 index 0000000000..a220b724b3 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstance.java @@ -0,0 +1,47 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of a CF v3 service instance resource ({@code GET/POST/PATCH /v3/service_instances}). + * + *
+ * { "guid": "...", "name": "...", "type": "managed|user-provided",
+ *   "created_at": "...", "updated_at": "...",
+ *   "tags": [...], "syslog_drain_url": "...",
+ *   "last_operation": { "type": "create|update|delete", "state": "succeeded|failed|in progress|initial", "description": "..." },
+ *   "metadata": { "labels": {...}, "annotations": {...} },
+ *   "relationships": { "space": { "data": { "guid": "..." } }, "service_plan": { "data": { "guid": "..." } } } }
+ * 
+ * + * Only the fields the client needs are mapped; everything else is ignored so CF can evolve the payload without breaking us. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3ServiceInstance(@JsonProperty("guid") String guid, @JsonProperty("name") String name, @JsonProperty("type") String type, + @JsonProperty("created_at") String createdAt, @JsonProperty("updated_at") String updatedAt, + @JsonProperty("tags") List tags, @JsonProperty("syslog_drain_url") String syslogDrainUrl, + @JsonProperty("last_operation") V3LastOperation lastOperation, @JsonProperty("metadata") V3Metadata metadata, + @JsonProperty("relationships") V3Relationships relationships) { + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3LastOperation(@JsonProperty("type") String type, @JsonProperty("state") String state, + @JsonProperty("description") String description) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Relationships(@JsonProperty("space") V3ToOneRelationship space, + @JsonProperty("service_plan") V3ToOneRelationship servicePlan) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3ToOneRelationship(@JsonProperty("data") V3RelationshipData data) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3RelationshipData(@JsonProperty("guid") String guid) { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstanceMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstanceMapper.java new file mode 100644 index 0000000000..45b6e73b51 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstanceMapper.java @@ -0,0 +1,88 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceOperation; + +/** + * Maps the {@link V3ServiceInstance} wire model to the project's {@link CloudServiceInstance} domain object. Mirrors the OSS + * {@code RawCloudServiceInstance} and {@code RawV3CloudServiceInstance} adapters field-for-field, so both client implementations yield + * identical domain objects. + * + *

+ * The OSS {@code RawCloudServiceInstance} derives {@code plan} and {@code label} from auxiliary {@code ServicePlan} / + * {@code ServiceOffering} resources it is zipped with; those names are resolved by the operations helper and passed in here. + * {@code RawV3CloudServiceInstance} (used for the {@code byNames} batch lookups) maps only the base fields with no auxiliary content. + *

+ */ +public final class V3ServiceInstanceMapper { + + private V3ServiceInstanceMapper() { + } + + /** + * Full mapping mirroring the OSS {@code RawCloudServiceInstance.derive()}: base fields plus the resolved plan/offering names. + */ + public static CloudServiceInstance toCloudServiceInstance(V3ServiceInstance serviceInstance, String planName, String labelName) { + return ImmutableCloudServiceInstance.builder() + .metadata(V3ResourceMappers.parseMetadata(serviceInstance.guid(), + serviceInstance.createdAt(), + serviceInstance.updatedAt())) + .v3Metadata(V3ResourceMappers.toV3Metadata(serviceInstance.metadata())) + .name(serviceInstance.name()) + .plan(planName) + .label(labelName) + .type(parseType(serviceInstance.type())) + .tags(serviceInstance.tags() == null ? java.util.Collections.emptyList() + : serviceInstance.tags()) + .lastOperation(parseLastOperation(serviceInstance.lastOperation())) + .syslogDrainUrl(serviceInstance.syslogDrainUrl()) + .build(); + } + + /** + * Base-only mapping mirroring the OSS {@code RawV3CloudServiceInstance.derive()}: metadata, v3Metadata, name and tags (used by the + * {@code byNames} batch lookups, which never resolve plan/offering and leave {@code type} unset). + */ + public static CloudServiceInstance toCloudServiceInstanceWithoutAuxiliaryContent(V3ServiceInstance serviceInstance) { + return ImmutableCloudServiceInstance.builder() + .metadata(V3ResourceMappers.parseMetadata(serviceInstance.guid(), + serviceInstance.createdAt(), + serviceInstance.updatedAt())) + .v3Metadata(V3ResourceMappers.toV3Metadata(serviceInstance.metadata())) + .name(serviceInstance.name()) + .tags(serviceInstance.tags() == null ? java.util.Collections.emptyList() + : serviceInstance.tags()) + .build(); + } + + /** + * Map the CF v3 {@code type} JSON value ({@code managed} / {@code user-provided}) to the project's {@link ServiceInstanceType}. + */ + private static ServiceInstanceType parseType(String type) { + if (type == null) { + return null; + } + for (ServiceInstanceType serviceInstanceType : ServiceInstanceType.values()) { + if (serviceInstanceType.getValue() + .equals(type)) { + return serviceInstanceType; + } + } + return null; + } + + /** + * Mirrors {@code ServiceOperation.fromLastOperation(LastOperation)}: a {@code null} operation, type or state yields {@code null}. + */ + private static ServiceOperation parseLastOperation(V3ServiceInstance.V3LastOperation lastOperation) { + if (lastOperation == null || lastOperation.type() == null || lastOperation.state() == null) { + return null; + } + ServiceOperation.Type type = ServiceOperation.Type.fromString(lastOperation.type()); + ServiceOperation.State state = ServiceOperation.State.fromString(lastOperation.state()); + return new ServiceOperation(type, lastOperation.description(), state); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceKeyDetails.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceKeyDetails.java new file mode 100644 index 0000000000..d844dfaa51 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceKeyDetails.java @@ -0,0 +1,28 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.Collections; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of the CF v3 service credential binding details response + * ({@code GET /v3/service_credential_bindings/{guid}/details}), which is where the actual {@code credentials} of a service key live. + * + *
+ * { "credentials": { ... }, "syslog_drain_url": "...", "volume_mounts": [ ... ] }
+ * 
+ * + * Mirrors the OSS impl's {@code serviceBindingsV3().getDetails(...)} call. The CF v3 API returns a 404 when fetching the details of a + * service key whose creation failed; callers translate that into empty credentials (see {@code ServiceKeysV3Operations}). + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3ServiceKeyDetails(@JsonProperty("credentials") Map credentials, + @JsonProperty("syslog_drain_url") String syslogDrainUrl) { + + public Map credentials() { + return credentials == null ? Collections.emptyMap() : credentials; + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceKeyMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceKeyMapper.java new file mode 100644 index 0000000000..bc617456c8 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceKeyMapper.java @@ -0,0 +1,59 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.Map; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceKey; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableServiceCredentialBindingOperation; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceCredentialBindingOperation; + +/** + * Maps a CF v3 service credential binding of {@code type=key} ({@link V3ServiceBinding}) to the project's {@link CloudServiceKey} domain + * object. Mirrors the OSS {@code RawCloudServiceKey#derive()} adapter field-for-field, so both client implementations yield identical + * domain objects: + *
    + *
  • {@code metadata} — from the binding resource envelope ({@code guid}, {@code created_at}, {@code updated_at});
  • + *
  • {@code v3Metadata} — labels/annotations from {@code metadata};
  • + *
  • {@code name} — the service key name;
  • + *
  • {@code credentials} — nullable; fetched separately from the binding details endpoint (absent when listing without + * credentials);
  • + *
  • {@code serviceInstance} — the owning (already-derived) {@link CloudServiceInstance};
  • + *
  • {@code serviceKeyOperation} — from {@code last_operation}, mirroring {@link ServiceCredentialBindingOperation#from}.
  • + *
+ */ +public final class V3ServiceKeyMapper { + + private V3ServiceKeyMapper() { + } + + public static CloudServiceKey toCloudServiceKey(V3ServiceBinding key, CloudServiceInstance serviceInstance) { + return toCloudServiceKey(key, serviceInstance, null); + } + + public static CloudServiceKey toCloudServiceKey(V3ServiceBinding key, CloudServiceInstance serviceInstance, + Map credentials) { + return ImmutableCloudServiceKey.builder() + .metadata(V3ResourceMappers.parseMetadata(key.guid(), key.createdAt(), key.updatedAt())) + .v3Metadata(V3ResourceMappers.toV3Metadata(key.metadata())) + .name(key.name()) + .credentials(credentials) + .serviceInstance(serviceInstance) + .serviceKeyOperation(parseServiceKeyOperation(key.lastOperation())) + .build(); + } + + private static ServiceCredentialBindingOperation parseServiceKeyOperation(V3ServiceBinding.V3LastOperation lastOperation) { + if (lastOperation == null) { + return null; + } + return ImmutableServiceCredentialBindingOperation.builder() + .type(ServiceCredentialBindingOperation.Type.fromString(lastOperation.type())) + .state(ServiceCredentialBindingOperation.State.fromString(lastOperation.state())) + .description(lastOperation.description()) + .createdAt(V3ResourceMappers.parseNullableDate(lastOperation.createdAt())) + .updatedAt(V3ResourceMappers.parseNullableDate(lastOperation.updatedAt())) + .build(); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceOffering.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceOffering.java new file mode 100644 index 0000000000..f0e64df11f --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceOffering.java @@ -0,0 +1,54 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of a CF v3 service offering resource ({@code GET /v3/service_offerings}). + * + *
+ * { "guid": "...", "name": "...", "description": "...", "available": true, "shareable": true,
+ *   "documentation_url": "...", "created_at": "...", "updated_at": "...",
+ *   "broker_catalog": { "id": "...", "metadata": {...}, "features": { "bindable": true, ... } },
+ *   "relationships": { "service_broker": { "data": { "guid": "..." } } },
+ *   "metadata": { "labels": {...}, "annotations": {...} } }
+ * 
+ */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3ServiceOffering(@JsonProperty("guid") String guid, @JsonProperty("name") String name, + @JsonProperty("description") String description, @JsonProperty("available") Boolean available, + @JsonProperty("shareable") Boolean shareable, + @JsonProperty("documentation_url") String documentationUrl, + @JsonProperty("created_at") String createdAt, @JsonProperty("updated_at") String updatedAt, + @JsonProperty("broker_catalog") V3BrokerCatalog brokerCatalog, + @JsonProperty("relationships") V3Relationships relationships, + @JsonProperty("metadata") V3Metadata metadata) { + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3BrokerCatalog(@JsonProperty("id") String id, @JsonProperty("metadata") Map metadata, + @JsonProperty("features") V3Features features) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Features(@JsonProperty("bindable") Boolean bindable, + @JsonProperty("allow_context_updates") Boolean allowContextUpdates, + @JsonProperty("bindings_retrievable") Boolean bindingsRetrievable, + @JsonProperty("instances_retrievable") Boolean instancesRetrievable, + @JsonProperty("plan_updateable") Boolean planUpdateable) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Relationships(@JsonProperty("service_broker") V3ToOneRelationship serviceBroker) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3ToOneRelationship(@JsonProperty("data") V3RelationshipData data) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3RelationshipData(@JsonProperty("guid") String guid) { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceOfferingMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceOfferingMapper.java new file mode 100644 index 0000000000..7393d9a97e --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceOfferingMapper.java @@ -0,0 +1,64 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.List; +import java.util.Map; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceOffering; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServicePlan; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceOffering; + +/** + * Maps the {@link V3ServiceOffering} wire model (plus the service plans fetched separately for it) to the project's + * {@link CloudServiceOffering} domain object. Mirrors the OSS {@code RawCloudServiceOffering} adapter field-for-field, so both client + * implementations yield identical domain objects. + */ +public final class V3ServiceOfferingMapper { + + private V3ServiceOfferingMapper() { + } + + public static CloudServiceOffering toCloudServiceOffering(V3ServiceOffering serviceOffering, List servicePlans) { + return ImmutableCloudServiceOffering.builder() + .metadata(V3ResourceMappers.parseMetadata(serviceOffering.guid(), + serviceOffering.createdAt(), + serviceOffering.updatedAt())) + .name(serviceOffering.name()) + .isAvailable(serviceOffering.available()) + .isBindable(extractBindable(serviceOffering.brokerCatalog())) + .description(serviceOffering.description()) + .isShareable(serviceOffering.shareable()) + .extra(extractBrokerCatalogMetadata(serviceOffering.brokerCatalog())) + .docUrl(serviceOffering.documentationUrl()) + .brokerId(extractBrokerId(serviceOffering.relationships())) + .uniqueId(extractBrokerCatalogId(serviceOffering.brokerCatalog())) + .servicePlans(servicePlans) + .build(); + } + + private static Boolean extractBindable(V3ServiceOffering.V3BrokerCatalog brokerCatalog) { + if (brokerCatalog == null || brokerCatalog.features() == null) { + return null; + } + return brokerCatalog.features() + .bindable(); + } + + private static Map extractBrokerCatalogMetadata(V3ServiceOffering.V3BrokerCatalog brokerCatalog) { + return brokerCatalog == null ? null : brokerCatalog.metadata(); + } + + private static String extractBrokerCatalogId(V3ServiceOffering.V3BrokerCatalog brokerCatalog) { + return brokerCatalog == null ? null : brokerCatalog.id(); + } + + private static String extractBrokerId(V3ServiceOffering.V3Relationships relationships) { + if (relationships == null || relationships.serviceBroker() == null || relationships.serviceBroker() + .data() == null) { + return null; + } + return relationships.serviceBroker() + .data() + .guid(); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServicePlan.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServicePlan.java new file mode 100644 index 0000000000..f7da724b1c --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServicePlan.java @@ -0,0 +1,43 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of a CF v3 service plan resource ({@code GET /v3/service_plans}). + * + *
+ * { "guid": "...", "name": "...", "description": "...", "free": true|false,
+ *   "visibility_type": "public|admin|organization|space",
+ *   "created_at": "...", "updated_at": "...",
+ *   "broker_catalog": { "id": "...", "metadata": { ... } },
+ *   "relationships": { "service_offering": { "data": { "guid": "..." } } },
+ *   "metadata": { "labels": {...}, "annotations": {...} } }
+ * 
+ */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3ServicePlan(@JsonProperty("guid") String guid, @JsonProperty("name") String name, + @JsonProperty("description") String description, @JsonProperty("free") Boolean free, + @JsonProperty("visibility_type") String visibilityType, @JsonProperty("created_at") String createdAt, + @JsonProperty("updated_at") String updatedAt, @JsonProperty("broker_catalog") V3BrokerCatalog brokerCatalog, + @JsonProperty("relationships") V3Relationships relationships, @JsonProperty("metadata") V3Metadata metadata) { + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3BrokerCatalog(@JsonProperty("id") String id, @JsonProperty("metadata") Map metadata) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Relationships(@JsonProperty("service_offering") V3ToOneRelationship serviceOffering) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3ToOneRelationship(@JsonProperty("data") V3RelationshipData data) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3RelationshipData(@JsonProperty("guid") String guid) { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServicePlanMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServicePlanMapper.java new file mode 100644 index 0000000000..4a7c197e67 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServicePlanMapper.java @@ -0,0 +1,55 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServicePlan; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServicePlan; + +/** + * Maps the {@link V3ServicePlan} wire model to the project's {@link CloudServicePlan} domain object. Mirrors the OSS + * {@code RawCloudServicePlan} adapter field-for-field, so both client implementations yield identical domain objects. + */ +public final class V3ServicePlanMapper { + + // CF v3 visibility_type value that maps to the domain's isPublic flag (see RawCloudServicePlan#derive). + private static final String PUBLIC_VISIBILITY = "public"; + + private V3ServicePlanMapper() { + } + + public static CloudServicePlan toCloudServicePlan(V3ServicePlan servicePlan) { + return ImmutableCloudServicePlan.builder() + .metadata(V3ResourceMappers.parseMetadata(servicePlan.guid(), servicePlan.createdAt(), + servicePlan.updatedAt())) + .v3Metadata(V3ResourceMappers.toV3Metadata(servicePlan.metadata())) + .name(servicePlan.name()) + .description(servicePlan.description()) + .extra(extractBrokerCatalogMetadata(servicePlan.brokerCatalog())) + .uniqueId(extractBrokerCatalogId(servicePlan.brokerCatalog())) + .serviceOfferingId(extractServiceOfferingId(servicePlan.relationships())) + .isPublic(isPublic(servicePlan.visibilityType())) + .isFree(servicePlan.free()) + .build(); + } + + private static java.util.Map extractBrokerCatalogMetadata(V3ServicePlan.V3BrokerCatalog brokerCatalog) { + return brokerCatalog == null ? null : brokerCatalog.metadata(); + } + + private static String extractBrokerCatalogId(V3ServicePlan.V3BrokerCatalog brokerCatalog) { + return brokerCatalog == null ? null : brokerCatalog.id(); + } + + private static String extractServiceOfferingId(V3ServicePlan.V3Relationships relationships) { + if (relationships == null || relationships.serviceOffering() == null || relationships.serviceOffering() + .data() == null) { + return null; + } + return relationships.serviceOffering() + .data() + .guid(); + } + + private static Boolean isPublic(String visibilityType) { + return visibilityType == null ? null : PUBLIC_VISIBILITY.equals(visibilityType); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Stack.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Stack.java new file mode 100644 index 0000000000..d1ba56ffe3 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Stack.java @@ -0,0 +1,20 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of a CF v3 stack resource ({@code GET /v3/stacks}). + * + *
+ * { "guid": "...", "name": "cflinuxfs4", "description": "...",
+ *   "created_at": "...", "updated_at": "...",
+ *   "metadata": { "labels": {...}, "annotations": {...} } }
+ * 
+ */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3Stack(@JsonProperty("guid") String guid, @JsonProperty("name") String name, + @JsonProperty("description") String description, @JsonProperty("created_at") String createdAt, + @JsonProperty("updated_at") String updatedAt, @JsonProperty("metadata") V3Metadata metadata) { + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3StackMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3StackMapper.java new file mode 100644 index 0000000000..9d4c7c84d9 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3StackMapper.java @@ -0,0 +1,23 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudStack; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudStack; + +/** + * Maps the {@link V3Stack} wire model to the project's {@link CloudStack} domain object. Mirrors the OSS + * {@code RawCloudStack} adapter field-for-field, so both client implementations yield identical domain objects. + */ +public final class V3StackMapper { + + private V3StackMapper() { + } + + public static CloudStack toCloudStack(V3Stack stack) { + return ImmutableCloudStack.builder() + .metadata(V3ResourceMappers.parseMetadata(stack.guid(), stack.createdAt(), stack.updatedAt())) + .name(stack.name()) + .description(stack.description()) + .build(); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Task.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Task.java new file mode 100644 index 0000000000..bddbd82ec6 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Task.java @@ -0,0 +1,28 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of a CF v3 task resource ({@code GET/POST /v3/tasks}, {@code POST /v3/apps/{guid}/tasks}). + * + *
+ * { "guid": "...", "name": "...", "command": "...", "state": "PENDING|RUNNING|SUCCEEDED|CANCELING|FAILED",
+ *   "memory_in_mb": 256, "disk_in_mb": 1024,
+ *   "result": { "failure_reason": "..." | null },
+ *   "created_at": "...", "updated_at": "...",
+ *   "metadata": { "labels": {...}, "annotations": {...} } }
+ * 
+ */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3Task(@JsonProperty("guid") String guid, @JsonProperty("name") String name, @JsonProperty("command") String command, + @JsonProperty("state") String state, @JsonProperty("memory_in_mb") Integer memoryInMb, + @JsonProperty("disk_in_mb") Integer diskInMb, @JsonProperty("result") V3Result result, + @JsonProperty("created_at") String createdAt, @JsonProperty("updated_at") String updatedAt, + @JsonProperty("metadata") V3Metadata metadata) { + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Result(@JsonProperty("failure_reason") String failureReason) { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3TaskMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3TaskMapper.java new file mode 100644 index 0000000000..1341ea722e --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3TaskMapper.java @@ -0,0 +1,45 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudTask; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudTask; + +/** + * Maps the {@link V3Task} wire model to the project's {@link CloudTask} domain object. Mirrors the OSS {@code RawCloudTask} adapter + * field-for-field, so both client implementations yield identical domain objects. + */ +public final class V3TaskMapper { + + private V3TaskMapper() { + } + + public static CloudTask toCloudTask(V3Task task) { + return ImmutableCloudTask.builder() + .metadata(V3ResourceMappers.parseMetadata(task.guid(), task.createdAt(), task.updatedAt())) + .name(task.name()) + .command(task.command()) + .limits(parseLimits(task)) + .result(parseResult(task)) + .state(parseState(task.state())) + .build(); + } + + private static CloudTask.Result parseResult(V3Task task) { + if (task.result() == null) { + return null; + } + return ImmutableCloudTask.ImmutableResult.of(task.result() + .failureReason()); + } + + private static CloudTask.Limits parseLimits(V3Task task) { + return ImmutableCloudTask.ImmutableLimits.builder() + .disk(task.diskInMb()) + .memory(task.memoryInMb()) + .build(); + } + + private static CloudTask.State parseState(String state) { + return state == null ? null : CloudTask.State.valueOf(state); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/ApplicationsCloudControllerClientIntegrationTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/ApplicationsCloudControllerClientIntegrationTest.java index 4cc72cbed1..70cc2867b7 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/ApplicationsCloudControllerClientIntegrationTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/ApplicationsCloudControllerClientIntegrationTest.java @@ -1,7 +1,7 @@ package org.cloudfoundry.multiapps.controller.client.facade; -import org.cloudfoundry.client.v3.Metadata; -import org.cloudfoundry.client.v3.processes.HealthCheckType; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.HealthCheckType; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudBuild; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudMetadata; @@ -171,7 +171,7 @@ void updateApplicationHealthcheckType() { .healthCheck( org.cloudfoundry.client.v3.processes.HealthCheck.builder() .type( - HealthCheckType.PROCESS) + org.cloudfoundry.client.v3.processes.HealthCheckType.PROCESS) .build()) .build()) .block(); diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/ServicesCloudControllerClientIntegrationTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/ServicesCloudControllerClientIntegrationTest.java index c0de6f9023..0cc22fe179 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/ServicesCloudControllerClientIntegrationTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/ServicesCloudControllerClientIntegrationTest.java @@ -23,8 +23,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import org.cloudfoundry.client.v3.jobs.JobState; -import org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceType; +import org.cloudfoundry.multiapps.controller.client.facade.domain.JobState; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudApplicationTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudApplicationTest.java index 514fe959c6..e864ff93a9 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudApplicationTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudApplicationTest.java @@ -49,7 +49,7 @@ void testDeriveForCnbApp() { private static CloudApplication buildApplication(org.cloudfoundry.multiapps.controller.client.facade.domain.Lifecycle lifecycle) { return ImmutableCloudApplication.builder() .metadata(RawCloudEntityTest.EXPECTED_METADATA_V3) - .v3Metadata(RawCloudEntityTest.V3_METADATA) + .v3Metadata(RawCloudEntityTest.V3_METADATA_DOMAIN) .name(RawCloudEntityTest.NAME) .state(EXPECTED_STATE) .lifecycle(lifecycle) diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudAsyncJobTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudAsyncJobTest.java index 1340e590ff..41407fb6b6 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudAsyncJobTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudAsyncJobTest.java @@ -34,7 +34,7 @@ void testDerive() { private CloudAsyncJob buildExpectedTask() { return ImmutableCloudAsyncJob.builder() .metadata(RawCloudEntityTest.EXPECTED_METADATA_PARSED_FROM_V3_RESOURCE) - .state(JobState.FAILED) + .state(org.cloudfoundry.multiapps.controller.client.facade.domain.JobState.FAILED) .operation(OPERATION) .warnings(WARNINGS) .errors(ERRORS) diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudEntityTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudEntityTest.java index 51b1fb5007..8e0e74876d 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudEntityTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudEntityTest.java @@ -44,6 +44,13 @@ class RawCloudEntityTest { .labels(V3_LABELS) .build(); + // Domain-typed equivalent of V3_METADATA, for building expected domain objects (CloudEntity.getV3Metadata() is domain.Metadata). + static final org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata V3_METADATA_DOMAIN = + org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata.builder() + .annotations(V3_ANNOTATIONS) + .labels(V3_LABELS) + .build(); + static final CloudMetadata EXPECTED_METADATA_PARSED_FROM_V3_RESOURCE = ImmutableCloudMetadata.builder() .guid(GUID) .createdAt(CREATED_AT) diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceBindingTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceBindingTest.java index 41d468060a..b2d1600aa8 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceBindingTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceBindingTest.java @@ -31,7 +31,10 @@ private static CloudServiceBinding buildExpectedServiceBinding() { .metadata(RawCloudEntityTest.EXPECTED_METADATA_V3) .applicationGuid(APPLICATION_GUID) .serviceInstanceGuid(SERVICE_INSTANCE_GUID) - .serviceBindingOperation(ServiceCredentialBindingOperation.from(LAST_OPERATION)) + .serviceBindingOperation(ServiceCredentialBindingOperation.from( + new org.cloudfoundry.multiapps.controller.client.facade.domain.LastOperation( + LAST_OPERATION.getType(), LAST_OPERATION.getState(), LAST_OPERATION.getDescription(), + LAST_OPERATION.getCreatedAt(), LAST_OPERATION.getUpdatedAt()))) .build(); } diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceInstanceTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceInstanceTest.java index 9645cca49a..1c41c4f866 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceInstanceTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceInstanceTest.java @@ -8,7 +8,7 @@ import org.cloudfoundry.client.v3.LastOperation; import org.cloudfoundry.client.v3.MaintenanceInfo; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.client.v3.Relationship; import org.cloudfoundry.client.v3.ToOneRelationship; import org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceRelationships; @@ -58,7 +58,7 @@ private CloudServiceInstance buildExpectedServiceInstance() { .guid(UUID.fromString(SERVICE_INSTANCE_ID)) .build()) .name(SERVICE_INSTANCE_NAME) - .v3Metadata(Metadata.builder() + .v3Metadata(org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata.builder() .annotations(METADATA_ANNOTATIONS) .labels(METADATA_LABELS) .build()) @@ -90,7 +90,7 @@ private ServiceInstanceResource buildServiceInstanceResource() { .description(MAINTENANCE_INFO_DESCRIPTION) .version(MAINTENANCE_INFO_VERSION) .build()) - .metadata(Metadata.builder() + .metadata(org.cloudfoundry.client.v3.Metadata.builder() .annotations(METADATA_ANNOTATIONS) .labels(METADATA_LABELS) .build()) diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceKeyTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceKeyTest.java index e8c49dc920..4a8a69f161 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceKeyTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceKeyTest.java @@ -34,11 +34,14 @@ void testDerive() { private static CloudServiceKey buildExpectedServiceKey() { return ImmutableCloudServiceKey.builder() .metadata(RawCloudEntityTest.EXPECTED_METADATA_V3) - .v3Metadata(RawCloudEntityTest.V3_METADATA) + .v3Metadata(RawCloudEntityTest.V3_METADATA_DOMAIN) .name(NAME) .credentials(CREDENTIALS) .serviceInstance(SERVICE_INSTANCE) - .serviceKeyOperation(ServiceCredentialBindingOperation.from(LAST_OPERATION)) + .serviceKeyOperation(ServiceCredentialBindingOperation.from( + new org.cloudfoundry.multiapps.controller.client.facade.domain.LastOperation( + LAST_OPERATION.getType(), LAST_OPERATION.getState(), LAST_OPERATION.getDescription(), + LAST_OPERATION.getCreatedAt(), LAST_OPERATION.getUpdatedAt()))) .build(); } diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceTest.java index 8a4fec8ef0..1eaf5f98e8 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceTest.java @@ -46,7 +46,7 @@ private static CloudServiceInstance buildExpectedService() { .name(NAME) .plan(PLAN_NAME) .label(OFFERING_NAME) - .type(ServiceInstanceType.MANAGED) + .type(org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType.MANAGED) .tags(TAGS) .lastOperation(new ServiceOperation(Type.CREATE, "", State.IN_PROGRESS)) .build(); @@ -56,7 +56,7 @@ private static CloudServiceInstance buildExpectedUserProvidedService() { return ImmutableCloudServiceInstance.builder() .metadata(RawCloudEntityTest.EXPECTED_METADATA_PARSED_FROM_V3_RESOURCE) .name(NAME) - .type(ServiceInstanceType.USER_PROVIDED) + .type(org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType.USER_PROVIDED) .tags(TAGS) .lastOperation(new ServiceOperation(Type.CREATE, "", State.SUCCEEDED)) .build(); diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/clients/CloudEntityResourceMapper.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/clients/CloudEntityResourceMapper.java index 8d01fe7eca..228cfaca71 100644 --- a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/clients/CloudEntityResourceMapper.java +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/clients/CloudEntityResourceMapper.java @@ -11,8 +11,8 @@ import java.util.Optional; import java.util.UUID; -import org.cloudfoundry.client.v3.Metadata; -import org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceType; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; 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 977ddae39b..d9e6a303f3 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 @@ -7,7 +7,7 @@ import java.util.UUID; import java.util.stream.Collectors; -import org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceType; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType; import org.cloudfoundry.multiapps.controller.client.facade.CloudCredentials; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataLabels; diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/metadata/processor/MtaMetadataParser.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/metadata/processor/MtaMetadataParser.java index f9503d54ee..290ed3b8ae 100644 --- a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/metadata/processor/MtaMetadataParser.java +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/metadata/processor/MtaMetadataParser.java @@ -7,7 +7,7 @@ import jakarta.inject.Inject; import jakarta.inject.Named; import org.apache.commons.lang3.StringUtils; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEntity; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/metadata/util/MtaMetadataUtil.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/metadata/util/MtaMetadataUtil.java index 59b674ab9e..4d6d69572c 100644 --- a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/metadata/util/MtaMetadataUtil.java +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/metadata/util/MtaMetadataUtil.java @@ -4,7 +4,7 @@ import java.util.Set; import org.apache.commons.lang3.StringUtils; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEntity; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataAnnotations; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataLabels; diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ApplicationMetadataBuilder.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ApplicationMetadataBuilder.java index 44f9dee90b..aea8248cab 100644 --- a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ApplicationMetadataBuilder.java +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ApplicationMetadataBuilder.java @@ -5,7 +5,7 @@ import java.util.TreeMap; import java.util.stream.Collectors; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.common.util.JsonUtil; import org.cloudfoundry.multiapps.common.util.MapUtil; import org.cloudfoundry.multiapps.controller.core.Constants; diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/MtaMetadataBuilder.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/MtaMetadataBuilder.java index 9b9a0d18db..a392eafb31 100644 --- a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/MtaMetadataBuilder.java +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/MtaMetadataBuilder.java @@ -1,8 +1,8 @@ package org.cloudfoundry.multiapps.controller.core.cf.v2; import org.apache.commons.lang3.StringUtils; -import org.cloudfoundry.client.v3.Metadata; -import org.cloudfoundry.client.v3.Metadata.Builder; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata.Builder; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataAnnotations; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataLabels; import org.cloudfoundry.multiapps.controller.core.cf.metadata.util.MtaMetadataUtil; diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServiceKeyMetadataBuilder.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServiceKeyMetadataBuilder.java index ae13c007af..e9ccf2fcc0 100644 --- a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServiceKeyMetadataBuilder.java +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServiceKeyMetadataBuilder.java @@ -1,7 +1,7 @@ package org.cloudfoundry.multiapps.controller.core.cf.v2; -import org.cloudfoundry.client.v3.Metadata; -import org.cloudfoundry.client.v3.Metadata.Builder; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata.Builder; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataLabels; import org.cloudfoundry.multiapps.mta.model.DeploymentDescriptor; diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServiceKeysCloudModelBuilder.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServiceKeysCloudModelBuilder.java index ef5cb4a984..1ad059b028 100644 --- a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServiceKeysCloudModelBuilder.java +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServiceKeysCloudModelBuilder.java @@ -6,8 +6,8 @@ import java.util.Map; import java.util.stream.Collectors; -import org.cloudfoundry.client.v3.Metadata; import org.cloudfoundry.multiapps.common.ContentException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceKey; import org.cloudfoundry.multiapps.controller.client.lib.domain.ImmutableCloudServiceInstanceExtended; diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServiceMetadataBuilder.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServiceMetadataBuilder.java index 2bbc6befb9..f80768cd03 100644 --- a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServiceMetadataBuilder.java +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServiceMetadataBuilder.java @@ -3,8 +3,8 @@ import java.util.Map; import java.util.TreeMap; -import org.cloudfoundry.client.v3.Metadata; -import org.cloudfoundry.client.v3.Metadata.Builder; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata.Builder; import org.cloudfoundry.multiapps.common.util.JsonUtil; import org.cloudfoundry.multiapps.common.util.MapUtil; import org.cloudfoundry.multiapps.controller.core.Constants; diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServicesCloudModelBuilder.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServicesCloudModelBuilder.java index 90abef06be..70e33a2be6 100644 --- a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServicesCloudModelBuilder.java +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServicesCloudModelBuilder.java @@ -7,7 +7,7 @@ import java.util.Objects; import java.util.TreeMap; import java.util.stream.Collectors; -import org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceType; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType; import org.cloudfoundry.multiapps.common.ContentException; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudServiceInstanceExtended; import org.cloudfoundry.multiapps.controller.client.lib.domain.ImmutableCloudServiceInstanceExtended; diff --git a/multiapps-controller-core/src/test/java/org/cloudfoundry/multiapps/controller/core/cf/clients/CustomServiceKeysClientTest.java b/multiapps-controller-core/src/test/java/org/cloudfoundry/multiapps/controller/core/cf/clients/CustomServiceKeysClientTest.java index 670dcfaec3..ffddd0f832 100644 --- a/multiapps-controller-core/src/test/java/org/cloudfoundry/multiapps/controller/core/cf/clients/CustomServiceKeysClientTest.java +++ b/multiapps-controller-core/src/test/java/org/cloudfoundry/multiapps/controller/core/cf/clients/CustomServiceKeysClientTest.java @@ -6,8 +6,8 @@ import java.util.List; import java.util.UUID; -import org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceType; import org.cloudfoundry.multiapps.controller.client.facade.CloudCredentials; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; import org.cloudfoundry.multiapps.controller.core.model.DeployedMtaService; import org.cloudfoundry.multiapps.controller.core.model.DeployedMtaServiceKey; diff --git a/multiapps-controller-core/src/test/java/org/cloudfoundry/multiapps/controller/core/cf/detect/DeployedMtaDetectorTest.java b/multiapps-controller-core/src/test/java/org/cloudfoundry/multiapps/controller/core/cf/detect/DeployedMtaDetectorTest.java index e61ff34873..8d5230fb38 100644 --- a/multiapps-controller-core/src/test/java/org/cloudfoundry/multiapps/controller/core/cf/detect/DeployedMtaDetectorTest.java +++ b/multiapps-controller-core/src/test/java/org/cloudfoundry/multiapps/controller/core/cf/detect/DeployedMtaDetectorTest.java @@ -9,7 +9,7 @@ import java.util.stream.Stream; import com.fasterxml.jackson.core.type.TypeReference; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.common.test.TestUtil; import org.cloudfoundry.multiapps.common.test.Tester; import org.cloudfoundry.multiapps.common.test.Tester.Expectation; diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/client/LoggingCloudControllerClient.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/client/LoggingCloudControllerClient.java index afb3fbdce1..60f0909f3c 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/client/LoggingCloudControllerClient.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/client/LoggingCloudControllerClient.java @@ -8,7 +8,6 @@ import java.util.Set; import java.util.UUID; -import org.cloudfoundry.client.v3.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.ApplicationServicesUpdateCallback; import org.cloudfoundry.multiapps.controller.client.facade.CloudControllerClient; import org.cloudfoundry.multiapps.controller.client.facade.ServiceBindingOperationCallback; @@ -32,6 +31,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.DockerInfo; import org.cloudfoundry.multiapps.controller.client.facade.domain.DropletInfo; import org.cloudfoundry.multiapps.controller.client.facade.domain.InstancesInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ServicePlanVisibility; import org.cloudfoundry.multiapps.controller.client.facade.domain.Staging; import org.cloudfoundry.multiapps.controller.client.facade.domain.Upload; diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/BuildApplicationDeployModelStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/BuildApplicationDeployModelStep.java index dd6569a455..1bcfd82f39 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/BuildApplicationDeployModelStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/BuildApplicationDeployModelStep.java @@ -8,7 +8,7 @@ import jakarta.inject.Inject; import jakarta.inject.Named; import org.apache.commons.lang3.StringUtils; -import org.cloudfoundry.client.v3.processes.HealthCheckType; +import org.cloudfoundry.multiapps.controller.client.facade.domain.HealthCheckType; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudRoute; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableStaging; import org.cloudfoundry.multiapps.controller.client.facade.domain.Staging; diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ClearMtaMetadataBaseStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ClearMtaMetadataBaseStep.java index 11a92109fd..b4cf434a2c 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ClearMtaMetadataBaseStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ClearMtaMetadataBaseStep.java @@ -4,7 +4,7 @@ import java.util.UUID; import org.apache.commons.collections4.MapUtils; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.CloudControllerClient; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceCreateUpdateServiceActionsStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceCreateUpdateServiceActionsStep.java index da5a65dc5e..818e6de5fc 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceCreateUpdateServiceActionsStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceCreateUpdateServiceActionsStep.java @@ -16,7 +16,7 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.MapUtils; import org.apache.commons.lang3.ObjectUtils; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.common.SLException; import org.cloudfoundry.multiapps.common.util.JsonUtil; import org.cloudfoundry.multiapps.controller.client.facade.CloudControllerClient; diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceKeysToRecreateStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceKeysToRecreateStep.java index 4a0dfe41ad..d0fe2541e3 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceKeysToRecreateStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceKeysToRecreateStep.java @@ -8,7 +8,7 @@ import jakarta.inject.Named; import org.apache.commons.collections4.ListUtils; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.common.util.JsonUtil; import org.cloudfoundry.multiapps.controller.client.facade.CloudControllerClient; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/PrepareApplicationForBackupStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/PrepareApplicationForBackupStep.java index ec99aa294e..3fcf4a2856 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/PrepareApplicationForBackupStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/PrepareApplicationForBackupStep.java @@ -3,7 +3,7 @@ import java.text.MessageFormat; import jakarta.inject.Named; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.CloudControllerClient; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataAnnotations; diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/RemoveMtaBackupMetadataStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/RemoveMtaBackupMetadataStep.java index 78bff75200..7d93f193a0 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/RemoveMtaBackupMetadataStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/RemoveMtaBackupMetadataStep.java @@ -1,7 +1,7 @@ package org.cloudfoundry.multiapps.controller.process.steps; import jakarta.inject.Named; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.CloudControllerClient; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataAnnotations; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataLabels; diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/StepsUtil.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/StepsUtil.java index 8a1036276a..d5058281c8 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/StepsUtil.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/StepsUtil.java @@ -14,7 +14,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import org.apache.commons.lang3.StringUtils; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.common.SLException; import org.cloudfoundry.multiapps.common.util.JsonUtil; import org.cloudfoundry.multiapps.controller.client.facade.CloudControllerClient; diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/AsyncJobToAsyncExecutionStateAdapter.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/AsyncJobToAsyncExecutionStateAdapter.java index bad6812d53..46e6bc95ed 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/AsyncJobToAsyncExecutionStateAdapter.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/AsyncJobToAsyncExecutionStateAdapter.java @@ -3,8 +3,8 @@ import java.text.MessageFormat; import java.util.function.Consumer; -import org.cloudfoundry.client.v3.jobs.JobState; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudAsyncJob; +import org.cloudfoundry.multiapps.controller.client.facade.domain.JobState; import org.cloudfoundry.multiapps.controller.process.Messages; import org.cloudfoundry.multiapps.controller.process.steps.AsyncExecutionState; import org.immutables.value.Value; diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/OperationInFinalStateHandler.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/OperationInFinalStateHandler.java index 9fb4a67889..3127360fa3 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/OperationInFinalStateHandler.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/OperationInFinalStateHandler.java @@ -9,15 +9,16 @@ import jakarta.inject.Inject; import jakarta.inject.Named; -import org.cloudfoundry.client.v3.Metadata; import org.cloudfoundry.multiapps.controller.api.model.ImmutableOperation; import org.cloudfoundry.multiapps.controller.api.model.Operation; import org.cloudfoundry.multiapps.controller.api.model.Operation.State; import org.cloudfoundry.multiapps.controller.api.model.ProcessType; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.core.auditlogging.CloudLoggingServiceConfigurationAuditLog; import org.cloudfoundry.multiapps.controller.core.cf.CloudControllerClientProvider; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataAnnotations; +import org.cloudfoundry.multiapps.controller.core.cloudlogging.CloudLoggingServiceClient; import org.cloudfoundry.multiapps.controller.core.model.DeployedMta; import org.cloudfoundry.multiapps.controller.core.model.DeployedMtaApplication; import org.cloudfoundry.multiapps.controller.core.util.LoggingUtil; @@ -25,13 +26,12 @@ import org.cloudfoundry.multiapps.controller.persistence.model.HistoricOperationEvent; import org.cloudfoundry.multiapps.controller.persistence.model.ImmutableHistoricOperationEvent; import org.cloudfoundry.multiapps.controller.persistence.model.LoggingConfiguration; -import org.cloudfoundry.multiapps.controller.persistence.services.cloudlogging.CloudLoggingServiceConfigurationService; import org.cloudfoundry.multiapps.controller.persistence.services.DescriptorBackupService; import org.cloudfoundry.multiapps.controller.persistence.services.FileService; import org.cloudfoundry.multiapps.controller.persistence.services.FileStorageException; import org.cloudfoundry.multiapps.controller.persistence.services.HistoricOperationEventService; -import org.cloudfoundry.multiapps.controller.core.cloudlogging.CloudLoggingServiceClient; import org.cloudfoundry.multiapps.controller.persistence.services.OperationService; +import org.cloudfoundry.multiapps.controller.persistence.services.cloudlogging.CloudLoggingServiceConfigurationService; import org.cloudfoundry.multiapps.controller.process.Messages; import org.cloudfoundry.multiapps.controller.process.dynatrace.DynatraceProcessDuration; import org.cloudfoundry.multiapps.controller.process.dynatrace.DynatracePublisher; @@ -213,7 +213,9 @@ private void deleteCloudLoggingServiceConfiguration(DelegateExecution execution) ProcessType processType = processTypeParser.getProcessType(execution); if (processType.equals(ProcessType.UNDEPLOY)) { - cloudLoggingServiceConfigurationService.createQuery().id(loggingConfiguration.getId()).delete(); + cloudLoggingServiceConfigurationService.createQuery() + .id(loggingConfiguration.getId()) + .delete(); cloudLoggingServiceConfigurationAuditLog.logDeleteLoggingConfiguration(VariableHandling.get(execution, Variables.USER), VariableHandling.get(execution, Variables.SPACE_GUID), loggingConfiguration); diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/client/LoggingCloudControllerClientTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/client/LoggingCloudControllerClientTest.java index 27fd087ce1..8f9d46421c 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/client/LoggingCloudControllerClientTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/client/LoggingCloudControllerClientTest.java @@ -4,7 +4,7 @@ import java.util.Optional; import java.util.UUID; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.ApplicationServicesUpdateCallback; import org.cloudfoundry.multiapps.controller.client.facade.CloudControllerClient; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/CreateServiceStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/CreateServiceStepTest.java index 51fe4a72c7..cfa0657540 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/CreateServiceStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/CreateServiceStepTest.java @@ -6,13 +6,13 @@ import java.util.UUID; import java.util.stream.Stream; -import org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceType; import org.cloudfoundry.multiapps.common.SLException; import org.cloudfoundry.multiapps.common.util.JsonUtil; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType; import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceOperation; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudServiceInstanceExtended; import org.cloudfoundry.multiapps.controller.client.lib.domain.ImmutableCloudServiceInstanceExtended; diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DeleteServiceMtaMetadataStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DeleteServiceMtaMetadataStepTest.java index 96a150b2fc..650b4faa3c 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DeleteServiceMtaMetadataStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DeleteServiceMtaMetadataStepTest.java @@ -2,7 +2,7 @@ import java.util.UUID; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudServiceInstanceExtended; import org.cloudfoundry.multiapps.controller.client.lib.domain.ImmutableCloudServiceInstanceExtended; diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetachServiceKeysFromMtaStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetachServiceKeysFromMtaStepTest.java index 6dc70e3ff9..b3f581c732 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetachServiceKeysFromMtaStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetachServiceKeysFromMtaStepTest.java @@ -5,7 +5,7 @@ import java.util.UUID; import java.util.stream.Stream; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetachServicesFromMtaStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetachServicesFromMtaStepTest.java index 40b15d83d3..d3b8e2005e 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetachServicesFromMtaStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetachServicesFromMtaStepTest.java @@ -6,7 +6,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; 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 9d9c09c8db..418341ae0d 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 @@ -4,7 +4,7 @@ import java.util.List; import java.util.Optional; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.common.test.TestUtil; import org.cloudfoundry.multiapps.common.test.Tester.Expectation; import org.cloudfoundry.multiapps.common.util.JsonUtil; diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceCreateUpdateServiceActionsStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceCreateUpdateServiceActionsStepTest.java index 069619f4e8..62a2cad73e 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceCreateUpdateServiceActionsStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceCreateUpdateServiceActionsStepTest.java @@ -8,13 +8,13 @@ import java.util.UUID; import java.util.stream.Stream; -import org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceType; import org.cloudfoundry.multiapps.common.test.TestUtil; import org.cloudfoundry.multiapps.common.util.JsonUtil; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType; import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceOperation; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudServiceInstanceExtended; import org.cloudfoundry.multiapps.controller.client.lib.domain.ImmutableCloudServiceInstanceExtended; diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceDeleteActionsToExecuteStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceDeleteActionsToExecuteStepTest.java index f04e7ca7b6..41a47a3cd8 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceDeleteActionsToExecuteStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceDeleteActionsToExecuteStepTest.java @@ -5,7 +5,7 @@ import java.util.Map; import java.util.UUID; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBinding; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceKeysToRecreateStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceKeysToRecreateStepTest.java index 82da244ea5..be639d0bd6 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceKeysToRecreateStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceKeysToRecreateStepTest.java @@ -5,7 +5,7 @@ import java.util.Map; import java.util.UUID; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.common.SLException; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/ExtractBatchedServicesWithResolvedDynamicParametersStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/ExtractBatchedServicesWithResolvedDynamicParametersStepTest.java index 1a1384edb2..4f8d239545 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/ExtractBatchedServicesWithResolvedDynamicParametersStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/ExtractBatchedServicesWithResolvedDynamicParametersStepTest.java @@ -9,8 +9,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceType; import org.cloudfoundry.multiapps.common.ContentException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudServiceInstanceExtended; import org.cloudfoundry.multiapps.controller.client.lib.domain.ImmutableCloudServiceInstanceExtended; import org.cloudfoundry.multiapps.controller.core.cf.v2.ResourceType; diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceBindingOperationExecutionTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceBindingOperationExecutionTest.java index c4c30ded04..c4304b6b11 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceBindingOperationExecutionTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceBindingOperationExecutionTest.java @@ -3,10 +3,10 @@ import java.util.List; import java.util.UUID; -import org.cloudfoundry.client.v3.jobs.JobState; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudAsyncJob; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudAsyncJob; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.JobState; import org.cloudfoundry.multiapps.controller.process.variables.Variables; import org.junit.jupiter.api.Test; diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceBrokerOperationsExecutionTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceBrokerOperationsExecutionTest.java index fb82e84353..cafb3e24bd 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceBrokerOperationsExecutionTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceBrokerOperationsExecutionTest.java @@ -3,8 +3,8 @@ import java.util.List; import java.util.stream.Stream; -import org.cloudfoundry.client.v3.jobs.JobState; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudAsyncJob; +import org.cloudfoundry.multiapps.controller.client.facade.domain.JobState; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudAsyncJob; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceBroker; import org.cloudfoundry.multiapps.controller.process.variables.Variables; diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceBrokersOperationsExecutionTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceBrokersOperationsExecutionTest.java index 94760131fd..c00b848696 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceBrokersOperationsExecutionTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceBrokersOperationsExecutionTest.java @@ -5,8 +5,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import org.cloudfoundry.client.v3.jobs.JobState; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudAsyncJob; +import org.cloudfoundry.multiapps.controller.client.facade.domain.JobState; import org.cloudfoundry.multiapps.controller.process.variables.Variables; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceKeyCreationOperationExecutionTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceKeyCreationOperationExecutionTest.java index a3b563ae2c..0836b1c072 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceKeyCreationOperationExecutionTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceKeyCreationOperationExecutionTest.java @@ -4,8 +4,8 @@ import java.util.UUID; import java.util.stream.Stream; -import org.cloudfoundry.client.v3.jobs.JobState; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudAsyncJob; +import org.cloudfoundry.multiapps.controller.client.facade.domain.JobState; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudAsyncJob; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudServiceInstanceExtended; diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceUnbindingsOperationExecutionTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceUnbindingsOperationExecutionTest.java index 671c01c888..e1ad3e6065 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceUnbindingsOperationExecutionTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceUnbindingsOperationExecutionTest.java @@ -2,13 +2,13 @@ import java.util.List; import java.util.UUID; -import org.cloudfoundry.client.v3.jobs.JobState; -import org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceType; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudAsyncJob; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudAsyncJob; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.domain.JobState; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudServiceInstanceExtended; import org.cloudfoundry.multiapps.controller.client.lib.domain.ImmutableCloudApplicationExtended; import org.cloudfoundry.multiapps.controller.client.lib.domain.ImmutableCloudServiceInstanceExtended; diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PrepareApplicationForBackupStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PrepareApplicationForBackupStepTest.java index ee1671799e..c3c511a19d 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PrepareApplicationForBackupStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PrepareApplicationForBackupStepTest.java @@ -3,7 +3,7 @@ import java.util.UUID; import java.util.stream.Stream; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudApplicationExtended; import org.cloudfoundry.multiapps.controller.client.lib.domain.ImmutableCloudApplicationExtended; diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PrepareBackupMtaForDeploymentStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PrepareBackupMtaForDeploymentStepTest.java index 861a639057..4d9262cec4 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PrepareBackupMtaForDeploymentStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PrepareBackupMtaForDeploymentStepTest.java @@ -13,7 +13,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.common.ContentException; import org.cloudfoundry.multiapps.controller.core.cf.detect.DeployedMtaDetector; import org.cloudfoundry.multiapps.controller.core.cf.metadata.ImmutableMtaMetadata; diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/RemoveMtaBackupMetadataStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/RemoveMtaBackupMetadataStepTest.java index 3d67e6d51f..a3e0442a32 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/RemoveMtaBackupMetadataStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/RemoveMtaBackupMetadataStepTest.java @@ -2,7 +2,7 @@ import java.util.UUID; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; import org.cloudfoundry.multiapps.controller.core.cf.metadata.ImmutableMtaMetadata; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataAnnotations; diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/StepsTestUtil.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/StepsTestUtil.java index 53e1e33fb0..daf246a99c 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/StepsTestUtil.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/StepsTestUtil.java @@ -3,7 +3,7 @@ import java.util.List; import java.util.UUID; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.common.util.JsonUtil; import org.cloudfoundry.multiapps.controller.client.facade.CloudControllerClient; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/SyncFlowableStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/SyncFlowableStepTest.java index 59ceec125e..5a3065be72 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/SyncFlowableStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/SyncFlowableStepTest.java @@ -8,7 +8,7 @@ import java.util.UUID; import java.util.stream.Stream; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.common.test.Tester; import org.cloudfoundry.multiapps.controller.client.facade.CloudControllerClient; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/UpdateServiceKeysMetadataStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/UpdateServiceKeysMetadataStepTest.java index 1533a3af32..7f9d40033c 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/UpdateServiceKeysMetadataStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/UpdateServiceKeysMetadataStepTest.java @@ -5,7 +5,7 @@ import java.util.List; import java.util.UUID; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/ExistingAppsToBackupCalculatorTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/ExistingAppsToBackupCalculatorTest.java index 1147c87e67..a4d2c66be7 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/ExistingAppsToBackupCalculatorTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/ExistingAppsToBackupCalculatorTest.java @@ -7,7 +7,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudApplication; import org.cloudfoundry.multiapps.controller.core.cf.metadata.ImmutableMtaMetadata; diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/ModuleDeterminerTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/ModuleDeterminerTest.java index 3b9cb47816..dfa90198c3 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/ModuleDeterminerTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/ModuleDeterminerTest.java @@ -3,7 +3,7 @@ import java.util.List; import java.util.Map; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudApplicationExtended; import org.cloudfoundry.multiapps.controller.client.lib.domain.ImmutableCloudApplicationExtended; import org.cloudfoundry.multiapps.controller.core.cf.CloudControllerClientProvider; From 909aab32a2330f8e6e7097140937eaac8c47ccef Mon Sep 17 00:00:00 2001 From: Krasimir Kargov Date: Fri, 7 Aug 2026 14:13:26 +0300 Subject: [PATCH 02/15] Removing more leftovers, changing logging and removing cf-java-client from poms LMCROSSITXSADEPLOY-3054 --- multiapps-controller-client/pom.xml | 38 +- .../src/main/java/module-info.java | 3 - .../controller/client/facade/AllowNulls.java | 16 + .../facade/CloudControllerClientImpl.java | 15 +- .../adapters/CloudFoundryClientFactory.java | 155 - .../facade/adapters/LogCacheClient.java | 110 +- .../facade/adapters/OAuthTokenProvider.java | 27 - .../facade/adapters/RawCloudApplication.java | 72 - .../facade/adapters/RawCloudAsyncJob.java | 51 - .../client/facade/adapters/RawCloudBuild.java | 67 - .../facade/adapters/RawCloudDomain.java | 24 - .../facade/adapters/RawCloudEntity.java | 110 - .../client/facade/adapters/RawCloudEvent.java | 56 - .../facade/adapters/RawCloudPackage.java | 74 - .../facade/adapters/RawCloudProcess.java | 62 - .../client/facade/adapters/RawCloudRoute.java | 99 - .../adapters/RawCloudServiceBinding.java | 37 - .../adapters/RawCloudServiceBroker.java | 40 - .../adapters/RawCloudServiceInstance.java | 56 - .../facade/adapters/RawCloudServiceKey.java | 41 - .../adapters/RawCloudServiceOffering.java | 45 - .../facade/adapters/RawCloudServicePlan.java | 37 - .../client/facade/adapters/RawCloudStack.java | 25 - .../client/facade/adapters/RawCloudTask.java | 50 - .../facade/adapters/RawInstancesInfo.java | 47 - .../client/facade/adapters/RawUserRole.java | 22 - .../adapters/RawV3CloudServiceInstance.java | 25 - .../facade/domain/CloudServiceBinding.java | 2 +- .../facade/domain/CloudServiceInstance.java | 2 +- .../client/facade/domain/CloudServiceKey.java | 2 +- .../client/facade/domain/Lifecycle.java | 2 +- .../client/facade/oauth2/OAuthClient.java | 6 - .../CloudControllerRestClientBuilder.java | 9 +- .../CloudControllerRestClientFactory.java | 149 +- .../rest/CloudControllerRestClientImpl.java | 2631 ----------------- .../facade/rest/CloudControllerV3Client.java | 5 - .../client/facade/rest/CloudSpaceClient.java | 127 +- .../rest/resources/LogCacheReadResponse.java | 45 + .../facade/rest/resources/V3Organization.java | 12 + .../client/facade/rest/resources/V3Space.java | 40 + .../client/facade/util/JobV3Util.java | 48 - ...sCloudControllerClientIntegrationTest.java | 23 +- .../CloudControllerClientIntegrationTest.java | 10 +- .../adapters/OAuthTokenProviderTest.java | 79 - .../adapters/RawCloudApplicationTest.java | 132 - .../facade/adapters/RawCloudAsyncJobTest.java | 82 - .../facade/adapters/RawCloudBuildTest.java | 137 - .../facade/adapters/RawCloudDomainTest.java | 49 - .../facade/adapters/RawCloudEntityTest.java | 158 - .../facade/adapters/RawCloudEventTest.java | 126 - .../facade/adapters/RawCloudPackageTest.java | 113 - .../facade/adapters/RawCloudProcessTest.java | 146 - .../facade/adapters/RawCloudRouteTest.java | 107 - .../adapters/RawCloudServiceBindingTest.java | 74 - .../adapters/RawCloudServiceBrokerTest.java | 55 - .../adapters/RawCloudServiceInstanceTest.java | 117 - .../adapters/RawCloudServiceKeyTest.java | 91 - .../adapters/RawCloudServiceOfferingTest.java | 107 - .../adapters/RawCloudServicePlanTest.java | 92 - .../facade/adapters/RawCloudServiceTest.java | 130 - .../facade/adapters/RawCloudStackTest.java | 40 - .../facade/adapters/RawCloudTaskTest.java | 72 - .../facade/adapters/RawInstancesInfoTest.java | 57 - .../facade/adapters/RawUserRoleTest.java | 49 - .../CloudControllerRestClientImplTest.java | 309 -- multiapps-controller-core/pom.xml | 8 + .../src/main/java/module-info.java | 2 - .../core/cf/CloudControllerClientFactory.java | 9 +- .../src/main/java/module-info.java | 2 - .../process/jobs/OrphanedDataCleaner.java | 4 +- .../web/upload/UploadFromUrlContext.java | 2 +- pom.xml | 23 +- 72 files changed, 326 insertions(+), 6563 deletions(-) create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/AllowNulls.java delete mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/CloudFoundryClientFactory.java delete mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/OAuthTokenProvider.java delete mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudApplication.java delete mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudAsyncJob.java delete mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudBuild.java delete mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudDomain.java delete mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudEntity.java delete mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudEvent.java delete mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudPackage.java delete mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudProcess.java delete mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudRoute.java delete mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceBinding.java delete mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceBroker.java delete mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceInstance.java delete mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceKey.java delete mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceOffering.java delete mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServicePlan.java delete mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudStack.java delete mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudTask.java delete mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawInstancesInfo.java delete mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawUserRole.java delete mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawV3CloudServiceInstance.java delete mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientImpl.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/LogCacheReadResponse.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Organization.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Space.java delete mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/util/JobV3Util.java delete mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/OAuthTokenProviderTest.java delete mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudApplicationTest.java delete mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudAsyncJobTest.java delete mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudBuildTest.java delete mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudDomainTest.java delete mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudEntityTest.java delete mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudEventTest.java delete mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudPackageTest.java delete mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudProcessTest.java delete mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudRouteTest.java delete mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceBindingTest.java delete mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceBrokerTest.java delete mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceInstanceTest.java delete mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceKeyTest.java delete mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceOfferingTest.java delete mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServicePlanTest.java delete mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceTest.java delete mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudStackTest.java delete mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudTaskTest.java delete mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawInstancesInfoTest.java delete mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawUserRoleTest.java delete mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientImplTest.java diff --git a/multiapps-controller-client/pom.xml b/multiapps-controller-client/pom.xml index ce3ba16217..1cb1ba3888 100644 --- a/multiapps-controller-client/pom.xml +++ b/multiapps-controller-client/pom.xml @@ -85,17 +85,19 @@ value provided - - - org.cloudfoundry - cloudfoundry-client-reactor - - + io.projectreactor.netty reactor-netty + + + + commons-logging + commons-logging + ${commons-logging.version} + @@ -140,30 +142,6 @@ - - org.cloudfoundry - cloudfoundry-client-reactor - ${cloudfoundry-client.version} - - - - org.slf4j - jcl-over-slf4j - - - - com.fasterxml.jackson.core - jackson-databind - - - commons-logging diff --git a/multiapps-controller-client/src/main/java/module-info.java b/multiapps-controller-client/src/main/java/module-info.java index 4201bc0a0b..111c91e054 100644 --- a/multiapps-controller-client/src/main/java/module-info.java +++ b/multiapps-controller-client/src/main/java/module-info.java @@ -12,7 +12,6 @@ exports org.cloudfoundry.multiapps.controller.client.facade.util; exports org.cloudfoundry.multiapps.controller.client.facade.dto; - requires transitive org.cloudfoundry.client; requires spring.security.oauth2.core; requires transitive spring.web; @@ -20,9 +19,7 @@ requires org.apache.commons.collections4; requires org.apache.commons.io; requires org.apache.commons.logging; - requires org.cloudfoundry.client.reactor; requires org.cloudfoundry.multiapps.common; - requires org.cloudfoundry.util; requires org.slf4j; requires java.net.http; requires spring.core; diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/AllowNulls.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/AllowNulls.java new file mode 100644 index 0000000000..680efaa956 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/AllowNulls.java @@ -0,0 +1,16 @@ +package org.cloudfoundry.multiapps.controller.client.facade; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks an Immutables collection attribute (typically a {@code Map}) as permitting {@code null} values. Project-owned replacement for + * {@code org.cloudfoundry.AllowNulls} (from cf-java-client) so the domain model no longer depends on the OSS client. Immutables + * recognises this marker by its simple name {@code AllowNulls}. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface AllowNulls { +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/CloudControllerClientImpl.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/CloudControllerClientImpl.java index bdb1855440..e016da176c 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/CloudControllerClientImpl.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/CloudControllerClientImpl.java @@ -11,7 +11,6 @@ import java.util.UUID; import java.util.function.Supplier; -import org.cloudfoundry.AbstractCloudFoundryException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudAsyncJob; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudBuild; @@ -40,7 +39,6 @@ import org.cloudfoundry.multiapps.controller.client.facade.rest.CloudControllerRestClient; import org.cloudfoundry.multiapps.controller.client.facade.rest.CloudControllerRestClientFactory; import org.cloudfoundry.multiapps.controller.client.facade.rest.ImmutableCloudControllerRestClientFactory; -import org.springframework.http.HttpStatus; import org.springframework.util.Assert; /** @@ -659,16 +657,9 @@ private void handleExceptions(Runnable runnable) { } private T handleExceptions(Supplier runnable) { - try { - return runnable.get(); - } catch (AbstractCloudFoundryException e) { - throw convertV3ClientException(e); - } - } - - private CloudOperationException convertV3ClientException(AbstractCloudFoundryException e) { - HttpStatus httpStatus = HttpStatus.valueOf(e.getStatusCode()); - return new CloudOperationException(httpStatus, httpStatus.getReasonPhrase(), e.getMessage(), e); + // The underlying CloudControllerRestClient already surfaces failures as CloudOperationException + // (via CloudControllerResponseErrorHandler), so no exception translation is needed here. + return runnable.get(); } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/CloudFoundryClientFactory.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/CloudFoundryClientFactory.java deleted file mode 100644 index 5461f765a8..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/CloudFoundryClientFactory.java +++ /dev/null @@ -1,155 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.io.IOException; -import java.net.URISyntaxException; -import java.net.URL; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.text.MessageFormat; -import java.time.Duration; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Executors; - -import org.cloudfoundry.client.CloudFoundryClient; -import org.cloudfoundry.client.v3.organizations.OrganizationsV3; -import org.cloudfoundry.client.v3.spaces.SpacesV3; -import org.cloudfoundry.reactor.ConnectionContext; -import org.cloudfoundry.reactor.DefaultConnectionContext; -import org.cloudfoundry.reactor.client.ReactorCloudFoundryClient; -import org.cloudfoundry.reactor.client.v3.organizations.ReactorOrganizationsV3; -import org.cloudfoundry.reactor.client.v3.spaces.ReactorSpacesV3; -import org.immutables.value.Value; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.http.HttpStatus; - -import org.cloudfoundry.multiapps.controller.client.facade.CloudException; -import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; -import org.cloudfoundry.multiapps.controller.client.facade.Messages; -import org.cloudfoundry.multiapps.controller.client.facade.oauth2.OAuthClient; -import org.cloudfoundry.multiapps.controller.client.facade.rest.CloudSpaceClient; -import org.cloudfoundry.multiapps.controller.client.facade.util.CloudUtil; -import org.cloudfoundry.multiapps.controller.client.facade.util.JsonUtil; - -import reactor.core.publisher.Mono; - -@Value.Immutable -public abstract class CloudFoundryClientFactory { - - private static final Logger LOGGER = LoggerFactory.getLogger(CloudFoundryClientFactory.class); - - static final HttpClient HTTP_CLIENT = HttpClient.newBuilder() - .executor(Executors.newSingleThreadExecutor()) - .followRedirects(HttpClient.Redirect.NORMAL) - .connectTimeout(Duration.ofMinutes(10)) - .build(); - - private final Map connectionContextCache = new ConcurrentHashMap<>(); - - public abstract Optional getSslHandshakeTimeout(); - - public abstract Optional getConnectTimeout(); - - public abstract Optional getConnectionPoolSize(); - - public abstract Optional getThreadPoolSize(); - - public abstract Optional getResponseTimeout(); - - public CloudFoundryClient createClient(URL controllerUrl, OAuthClient oAuthClient, Map requestTags) { - return ReactorCloudFoundryClient.builder() - .connectionContext(getOrCreateConnectionContext(controllerUrl.getHost())) - .tokenProvider(oAuthClient.getTokenProvider()) - .requestTags(requestTags) - .build(); - } - - public LogCacheClient createLogCacheClient(URL controllerUrl, OAuthClient oAuthClient, Map requestTags) { - return new LogCacheClient(oAuthClient, requestTags, getOrCreateConnectionContext(controllerUrl.getHost())); - } - - @SuppressWarnings("unchecked") - private Map callCfRoot(URL controllerUrl, Map requestTags) { - HttpResponse response; - try { - HttpRequest request = buildCfRootRequest(controllerUrl, requestTags); - LOGGER.info(MessageFormat.format(Messages.CALLING_CF_ROOT_0_TO_ACCESS_LOG_CACHE_URL, controllerUrl)); - response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); - if (response.statusCode() / 100 != 2) { - var status = HttpStatus.valueOf(response.statusCode()); - throw new CloudOperationException(status, status.getReasonPhrase(), response.body()); - } - LOGGER.info(Messages.CF_ROOT_REQUEST_FINISHED); - } catch (InterruptedException | URISyntaxException | IOException e) { - throw new CloudException(e.getMessage(), e); - } - var map = JsonUtil.convertJsonToMap(response.body()); - return (Map) map.get("links"); - } - - private HttpRequest buildCfRootRequest(URL controllerUrl, Map requestTags) throws URISyntaxException { - var requestBuilder = HttpRequest.newBuilder() - .GET() - .uri(controllerUrl.toURI()) - .timeout(Duration.ofMinutes(5)); - requestTags.forEach(requestBuilder::header); - return requestBuilder.build(); - } - - public CloudSpaceClient createSpaceClient(URL controllerUrl, OAuthClient oAuthClient, Map requestTags) { - String v3Api; - try { - var links = CloudUtil.executeWithRetry(() -> callCfRoot(controllerUrl, requestTags)); - @SuppressWarnings("unchecked") - var ccv3 = (Map) links.get("cloud_controller_v3"); - v3Api = (String) ccv3.get("href"); - } catch (CloudException e) { - LOGGER.warn(MessageFormat.format(Messages.CALL_TO_0_FAILED_WITH_1, controllerUrl.toString(), e.getMessage()), e); - v3Api = controllerUrl + "/v3"; - } - var spacesV3 = createV3SpacesClient(controllerUrl, v3Api, oAuthClient, requestTags); - var orgsV3 = createV3OrgsClient(controllerUrl, v3Api, oAuthClient, requestTags); - return new CloudSpaceClient(spacesV3, orgsV3); - } - - private SpacesV3 createV3SpacesClient(URL controllerUrl, String v3Api, OAuthClient oAuthClient, Map requestTags) { - return new ReactorSpacesV3(getOrCreateConnectionContext(controllerUrl.getHost()), - Mono.just(v3Api), - oAuthClient.getTokenProvider(), - requestTags); - } - - private OrganizationsV3 createV3OrgsClient(URL controllerUrl, String v3Api, OAuthClient oAuthClient, Map requestTags) { - return new ReactorOrganizationsV3(getOrCreateConnectionContext(controllerUrl.getHost()), - Mono.just(v3Api), - oAuthClient.getTokenProvider(), - requestTags); - } - - public ConnectionContext getOrCreateConnectionContext(String controllerApiHost) { - return connectionContextCache.computeIfAbsent(controllerApiHost, this::createConnectionContext); - } - - private ConnectionContext createConnectionContext(String controllerApiHost) { - DefaultConnectionContext.Builder builder = DefaultConnectionContext.builder() - .apiHost(controllerApiHost); - getSslHandshakeTimeout().ifPresent(builder::sslHandshakeTimeout); - getConnectTimeout().ifPresent(builder::connectTimeout); - getConnectionPoolSize().ifPresent(builder::connectionPoolSize); - getThreadPoolSize().ifPresent(builder::threadPoolSize); - builder.additionalHttpClientConfiguration(this::getAdditionalHttpClientConfiguration); - return builder.build(); - } - - private reactor.netty.http.client.HttpClient getAdditionalHttpClientConfiguration(reactor.netty.http.client.HttpClient client) { - var clientWithOptions = client; - if (getResponseTimeout().isPresent()) { - clientWithOptions = clientWithOptions.responseTimeout(getResponseTimeout().get()); - } - - return clientWithOptions; - } -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/LogCacheClient.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/LogCacheClient.java index ea8d519b3f..67d4bba810 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/LogCacheClient.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/LogCacheClient.java @@ -1,19 +1,5 @@ package org.cloudfoundry.multiapps.controller.client.facade.adapters; -import org.cloudfoundry.multiapps.controller.client.facade.CloudException; -import org.cloudfoundry.multiapps.controller.client.facade.Messages; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ApplicationLog; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableApplicationLog; -import org.cloudfoundry.multiapps.controller.client.facade.oauth2.OAuthClient; -import org.cloudfoundry.logcache.v1.Envelope; -import org.cloudfoundry.logcache.v1.EnvelopeType; -import org.cloudfoundry.logcache.v1.ReadRequest; -import org.cloudfoundry.logcache.v1.ReadResponse; -import org.cloudfoundry.reactor.ConnectionContext; -import org.cloudfoundry.reactor.logcache.v1.ReactorLogCacheClient; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.nio.charset.StandardCharsets; import java.text.MessageFormat; import java.time.Duration; @@ -24,77 +10,77 @@ import java.util.Base64; import java.util.LinkedList; import java.util.List; -import java.util.Map; import java.util.UUID; +import org.cloudfoundry.multiapps.controller.client.facade.CloudException; +import org.cloudfoundry.multiapps.controller.client.facade.Messages; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ApplicationLog; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableApplicationLog; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.LogCacheReadResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.client.RestClient; + +/** + * Fetches recent application logs from Log-Cache. Reimplemented on a plain synchronous Spring {@link RestClient} against Log-Cache's + * JSON {@code read} REST API ({@code GET /api/v1/read/{source_id}}), removing the previous dependency on the OSS cf-java-client + * ({@code org.cloudfoundry.logcache.v1.*} / {@code org.cloudfoundry.reactor.*}). The {@link RestClient} is pre-configured with the + * log-cache base URL, bearer-token auth and request tags by the factory; this class only builds the request and maps the response to + * {@link ApplicationLog}, preserving the previous behaviour exactly. + */ public class LogCacheClient { private static final Logger LOGGER = LoggerFactory.getLogger(LogCacheClient.class); private static final String SOURCE_TYPE_KEY_NAME = "source_type"; private static final int MAX_LOG_COUNT = 1000; - private final OAuthClient oAuthClient; - private final Map requestTags; - private final ConnectionContext connectionContext; - public LogCacheClient(OAuthClient oAuthClient, Map requestTags, ConnectionContext connectionContext) { - this.oAuthClient = oAuthClient; - this.requestTags = requestTags; - this.connectionContext = connectionContext; + private final RestClient restClient; + + public LogCacheClient(RestClient restClient) { + this.restClient = restClient; } public List getRecentLogs(UUID applicationGuid, LocalDateTime offset) { LOGGER.info(Messages.TRYING_TO_GET_APP_LOGS); - org.cloudfoundry.logcache.v1.LogCacheClient logCacheClient = createReactorLogCacheClient(); - ReadResponse applicationLogsResponse = readApplicationLogs(logCacheClient, applicationGuid, offset); + LogCacheReadResponse response = readApplicationLogs(applicationGuid, offset); - if (applicationLogsResponse != null) { - LOGGER.info(Messages.APP_LOGS_WERE_FETCHED_SUCCESSFULLY); - return applicationLogsResponse.getEnvelopes() - .getBatch() - .stream() - .map(this::mapToAppLog) - // we use a linked list so that the log messages can be a LIFO sequence - // that way, we avoid unnecessary sorting and copying to and from another collection/array - .collect(LinkedList::new, LinkedList::addFirst, LinkedList::addAll); - } else { + if (response == null) { throw new CloudException(MessageFormat.format(Messages.FAILED_TO_FETCH_APP_LOGS_FOR_APP, applicationGuid)); } - - } - - private ReactorLogCacheClient createReactorLogCacheClient() { - return ReactorLogCacheClient.builder() - .requestTags(requestTags) - .connectionContext(connectionContext) - .tokenProvider(oAuthClient.getTokenProvider()) - .build(); + LOGGER.info(Messages.APP_LOGS_WERE_FETCHED_SUCCESSFULLY); + return response.batch() + .stream() + .map(this::mapToAppLog) + // we use a linked list so that the log messages can be a LIFO sequence + // that way, we avoid unnecessary sorting and copying to and from another collection/array + .collect(LinkedList::new, LinkedList::addFirst, LinkedList::addAll); } - private ReadResponse readApplicationLogs(org.cloudfoundry.logcache.v1.LogCacheClient logCacheClient, UUID applicationGuid, - LocalDateTime offset) { + private LogCacheReadResponse readApplicationLogs(UUID applicationGuid, LocalDateTime offset) { var instant = offset.toInstant(ZoneOffset.UTC); var secondsInNanos = Duration.ofSeconds(instant.getEpochSecond()) .toNanos(); - return logCacheClient.read(ReadRequest.builder() - .envelopeType(EnvelopeType.LOG) - .sourceId(applicationGuid.toString()) - .descending(Boolean.TRUE) - .limit(MAX_LOG_COUNT) - .startTime(secondsInNanos + instant.getNano() + 1) - .build()) - .block(); + long startTime = secondsInNanos + instant.getNano() + 1; + return restClient.get() + .uri(uriBuilder -> uriBuilder.path("/api/v1/read/{sourceId}") + .queryParam("envelope_types", "LOG") + .queryParam("descending", Boolean.TRUE) + .queryParam("limit", MAX_LOG_COUNT) + .queryParam("start_time", startTime) + .build(applicationGuid.toString())) + .retrieve() + .body(LogCacheReadResponse.class); } - private ApplicationLog mapToAppLog(Envelope envelope) { + private ApplicationLog mapToAppLog(LogCacheReadResponse.Envelope envelope) { return ImmutableApplicationLog.builder() - .applicationGuid(envelope.getSourceId()) - .message(decodeLogPayload(envelope.getLog() - .getPayload())) - .timestamp(fromLogTimestamp(envelope.getTimestamp())) - .messageType(fromLogMessageType(envelope.getLog() - .getType() - .getValue())) - .sourceName(envelope.getTags() + .applicationGuid(envelope.sourceId()) + .message(decodeLogPayload(envelope.log() + .payload())) + .timestamp(fromLogTimestamp(envelope.timestamp())) + .messageType(fromLogMessageType(envelope.log() + .type())) + .sourceName(envelope.tags() .get(SOURCE_TYPE_KEY_NAME)) .build(); } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/OAuthTokenProvider.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/OAuthTokenProvider.java deleted file mode 100644 index ff5def4c97..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/OAuthTokenProvider.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import org.cloudfoundry.reactor.ConnectionContext; -import org.cloudfoundry.reactor.TokenProvider; - -import org.cloudfoundry.multiapps.controller.client.facade.oauth2.OAuth2AccessTokenWithAdditionalInfo; -import org.cloudfoundry.multiapps.controller.client.facade.oauth2.OAuthClient; - -import reactor.core.publisher.Mono; - -public class OAuthTokenProvider implements TokenProvider { - - private final OAuthClient oAuthClient; - - public OAuthTokenProvider(OAuthClient oAuthClient) { - this.oAuthClient = oAuthClient; - } - - @Override - public Mono getToken(ConnectionContext connectionContext) { - return Mono.fromSupplier(() -> { - OAuth2AccessTokenWithAdditionalInfo token = oAuthClient.getToken(); - return token.getAuthorizationHeaderValue(); - }); - } - -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudApplication.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudApplication.java deleted file mode 100644 index 63489ddd70..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudApplication.java +++ /dev/null @@ -1,72 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.util.Collections; -import java.util.Map; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Derivable; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudApplication; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableLifecycle; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Lifecycle; -import org.cloudfoundry.multiapps.controller.client.facade.domain.LifecycleType; -import org.cloudfoundry.client.v3.BuildpackData; -import org.cloudfoundry.client.v3.CnbData; -import org.cloudfoundry.client.v3.LifecycleData; -import org.cloudfoundry.client.v3.applications.Application; -import org.cloudfoundry.client.v3.applications.ApplicationState; -import org.immutables.value.Value; - -@Value.Immutable -public abstract class RawCloudApplication extends RawCloudEntity { - - public static final String BUILDPACKS = "buildpacks"; - public static final String STACK = "stack"; - - public abstract Application getApplication(); - - public abstract Derivable getSpace(); - - @Override - public CloudApplication derive() { - Application app = getApplication(); - return ImmutableCloudApplication.builder() - .metadata(parseResourceMetadata(app)) - .v3Metadata(toDomainMetadata(app.getMetadata())) - .name(app.getName()) - .state(parseState(app.getState())) - .lifecycle(parseLifecycle(app.getLifecycle())) - .space(getSpace().derive()) - .build(); - } - - private static CloudApplication.State parseState(ApplicationState state) { - return CloudApplication.State.valueOf(state.getValue()); - } - - private static Lifecycle parseLifecycle(org.cloudfoundry.client.v3.Lifecycle lifecycle) { - Map data = extractLifecycleData(lifecycle.getData()); - - return ImmutableLifecycle.builder() - .type(LifecycleType.valueOf(lifecycle.getType() - .toString() - .toUpperCase())) - .data(data) - .build(); - } - - private static Map extractLifecycleData(LifecycleData lifecycleData) { - if (lifecycleData instanceof BuildpackData buildpackData) { - return Map.of( - BUILDPACKS, buildpackData.getBuildpacks(), - STACK, buildpackData.getStack()); - } else if (lifecycleData instanceof CnbData cnbData) { - return Map.of( - BUILDPACKS, cnbData.getBuildpacks(), - STACK, cnbData.getStack()); - } else { - return Collections.emptyMap(); - } - } - -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudAsyncJob.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudAsyncJob.java deleted file mode 100644 index 3650a8de77..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudAsyncJob.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.util.stream.Collectors; - -import org.cloudfoundry.client.v3.jobs.Job; -import org.cloudfoundry.client.v3.jobs.Warning; -import org.immutables.value.Value; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudAsyncJob; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudAsyncJob; - -@Value.Immutable -public abstract class RawCloudAsyncJob extends RawCloudEntity { - - @Value.Parameter - public abstract Job getJob(); - - @Override - public CloudAsyncJob derive() { - Job job = getJob(); - return ImmutableCloudAsyncJob.builder() - .metadata(parseResourceMetadata(job)) - .state(org.cloudfoundry.multiapps.controller.client.facade.domain.JobState.valueOf(job.getState() - .name())) - .operation(job.getOperation()) - .warnings(getWarnings(job)) - .errors(getErrors(job)) - .build(); - } - - private String getWarnings(Job job) { - return job.getWarnings() - .stream() - .map(Warning::getDetail) - .collect(Collectors.joining(",")); - } - - private String getErrors(Job job) { - return job.getErrors() - .stream() - .map(this::joinErrorDetails) - .collect(Collectors.joining(",")); - } - - private String joinErrorDetails(org.cloudfoundry.client.v3.Error error) { - return String.join(" ", error.getCode() - .toString(), - error.getTitle(), error.getDetail()); - } - -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudBuild.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudBuild.java deleted file mode 100644 index 2cdbc39eaa..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudBuild.java +++ /dev/null @@ -1,67 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.util.Optional; - -import org.cloudfoundry.client.v3.Relationship; -import org.cloudfoundry.client.v3.builds.Build; -import org.cloudfoundry.client.v3.builds.BuildState; -import org.cloudfoundry.client.v3.builds.CreatedBy; -import org.cloudfoundry.client.v3.builds.Droplet; -import org.immutables.value.Value; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudBuild; -import org.cloudfoundry.multiapps.controller.client.facade.domain.DropletInfo; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudBuild; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudBuild.ImmutableCreatedBy; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudBuild.ImmutablePackageInfo; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableDropletInfo; - -@Value.Immutable -public abstract class RawCloudBuild extends RawCloudEntity { - - private static CloudBuild.CreatedBy parseCreatedBy(Build buildResource) { - CreatedBy createdBy = buildResource.getCreatedBy(); - return ImmutableCreatedBy.builder() - .guid(parseNullableGuid(createdBy.getId())) - .name(createdBy.getName()) - .build(); - } - - private static CloudBuild.PackageInfo parsePackageInfo(Build buildResource) { - Relationship packageRelationship = buildResource.getInputPackage(); - String packageId = packageRelationship.getId(); - return ImmutablePackageInfo.of(parseNullableGuid(packageId)); - } - - private static DropletInfo parseDropletInfo(Build buildResource) { - Droplet droplet = buildResource.getDroplet(); - return Optional.ofNullable(droplet) - .map(Droplet::getId) - .map(RawCloudEntity::parseNullableGuid) - .map(dropletGuid -> ImmutableDropletInfo.builder() - .guid(dropletGuid) - .build()) - .orElse(null); - } - - private static CloudBuild.State parseState(BuildState state) { - return parseEnum(state, CloudBuild.State.class); - } - - @Value.Parameter - public abstract Build getResource(); - - @Override - public CloudBuild derive() { - Build resource = getResource(); - return ImmutableCloudBuild.builder() - .metadata(parseResourceMetadata(resource)) - .createdBy(parseCreatedBy(resource)) - .packageInfo(parsePackageInfo(resource)) - .dropletInfo(parseDropletInfo(resource)) - .state(parseState(resource.getState())) - .error(resource.getError()) - .build(); - } - -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudDomain.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudDomain.java deleted file mode 100644 index f03981dbaa..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudDomain.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import org.cloudfoundry.client.v3.domains.Domain; -import org.immutables.value.Value; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudDomain; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudDomain; - -@Value.Immutable -public abstract class RawCloudDomain extends RawCloudEntity { - - @Value.Parameter - public abstract Domain getResource(); - - @Override - public CloudDomain derive() { - Domain resource = getResource(); - return ImmutableCloudDomain.builder() - .metadata(parseResourceMetadata(resource)) - .name(resource.getName()) - .build(); - } - -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudEntity.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudEntity.java deleted file mode 100644 index e5da78b0d5..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudEntity.java +++ /dev/null @@ -1,110 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.text.MessageFormat; -import java.time.LocalDateTime; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeParseException; -import java.util.Collection; -import java.util.UUID; -import java.util.stream.Collectors; - -import org.cloudfoundry.client.v3.Resource; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudMetadata; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Derivable; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; - -public abstract class RawCloudEntity implements Derivable { - - private static final Logger LOGGER = LoggerFactory.getLogger(RawCloudEntity.class); - - protected RawCloudEntity() { - // Recommended by Sonar. - } - - public static CloudMetadata parseResourceMetadata(Resource resource) { - return ImmutableCloudMetadata.builder() - .guid(parseNullableGuid(resource.getId())) - .createdAt(parseNullableDate(resource.getCreatedAt())) - .updatedAt(parseNullableDate(resource.getUpdatedAt())) - .build(); - } - - /** - * Convert the OSS {@code LastOperation} to the project-owned {@link org.cloudfoundry.multiapps.controller.client.facade.domain.LastOperation}. - * Shared by the service instance / binding / key adapters. (Part of the OSS-backed path; removed with the OSS client.) - */ - protected static org.cloudfoundry.multiapps.controller.client.facade.domain.LastOperation toDomainLastOperation( - org.cloudfoundry.client.v3.LastOperation lastOperation) { - if (lastOperation == null) { - return null; - } - return new org.cloudfoundry.multiapps.controller.client.facade.domain.LastOperation(lastOperation.getType(), - lastOperation.getState(), - lastOperation.getDescription(), - lastOperation.getCreatedAt(), - lastOperation.getUpdatedAt()); - } - - /** - * Convert the OSS v3 {@code Metadata} to the project-owned - * {@link org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata}. (OSS-backed path; removed with the OSS client.) - */ - protected static org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata toDomainMetadata( - org.cloudfoundry.client.v3.Metadata metadata) { - if (metadata == null) { - return null; - } - return org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata.builder() - .labels(metadata.getLabels()) - .annotations(metadata.getAnnotations()) - .build(); - } - - protected static UUID parseNullableGuid(String guid) { - return guid == null ? null : parseGuid(guid); - } - - protected static UUID parseGuid(String guid) { - try { - return UUID.fromString(guid); - } catch (IllegalArgumentException e) { - LOGGER.warn(MessageFormat.format("Could not parse GUID string: \"{0}\"", guid), e); - return null; - } - } - - protected static LocalDateTime parseNullableDate(String date) { - return date == null ? null : parseDate(date); - } - - protected static LocalDateTime parseDate(String dateString) { - try { - return ZonedDateTime.parse(dateString, DateTimeFormatter.ISO_DATE_TIME) - .toLocalDateTime(); - } catch (DateTimeParseException e) { - LOGGER.warn(MessageFormat.format("Could not parse date string: \"{0}\"", dateString), e); - return null; - } - } - - protected static > E parseEnum(Enum value, Class targetEnum) { - String name = value.name() - .toUpperCase(); - return Enum.valueOf(targetEnum, name); - } - - protected static D deriveFromNullable(Derivable derivable) { - return derivable == null ? null : derivable.derive(); - } - - protected static Collection derive(Collection> derivables) { - return derivables.stream() - .map(Derivable::derive) - .collect(Collectors.toList()); - } - -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudEvent.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudEvent.java deleted file mode 100644 index 7a0937f970..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudEvent.java +++ /dev/null @@ -1,56 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import org.cloudfoundry.client.v3.auditevents.AuditEventActor; -import org.cloudfoundry.client.v3.auditevents.AuditEventResource; -import org.cloudfoundry.client.v3.auditevents.AuditEventTarget; -import org.immutables.value.Value; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEvent; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEvent.Participant; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudEvent; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudEvent.ImmutableParticipant; - -@Value.Immutable -public abstract class RawCloudEvent extends RawCloudEntity { - - @Value.Parameter - public abstract AuditEventResource getResource(); - - @Override - public CloudEvent derive() { - AuditEventResource resource = getResource(); - return ImmutableCloudEvent.builder() - .metadata(parseResourceMetadata(resource)) - .target(parseTarget(resource)) - .actor(parseActor(resource)) - .type(resource.getType()) - .build(); - } - - private static Participant parseTarget(AuditEventResource resource) { - AuditEventTarget target = resource.getAuditEventTarget(); - if (target == null) { - return ImmutableParticipant.builder() - .build(); - } - return ImmutableParticipant.builder() - .guid(parseNullableGuid(target.getId())) - .name(target.getName()) - .type(target.getType()) - .build(); - } - - private static Participant parseActor(AuditEventResource resource) { - AuditEventActor actor = resource.getAuditEventActor(); - if (actor == null) { - return ImmutableParticipant.builder() - .build(); - } - return ImmutableParticipant.builder() - .guid(parseNullableGuid(actor.getId())) - .name(actor.getName()) - .type(actor.getType()) - .build(); - } - -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudPackage.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudPackage.java deleted file mode 100644 index 73933bf781..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudPackage.java +++ /dev/null @@ -1,74 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import org.cloudfoundry.client.v3.Checksum; -import org.cloudfoundry.client.v3.packages.BitsData; -import org.cloudfoundry.client.v3.packages.DockerData; -import org.cloudfoundry.client.v3.packages.Package; -import org.cloudfoundry.client.v3.packages.PackageType; -import org.immutables.value.Value; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudPackage; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableBitsData; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudPackage; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableDockerData; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Status; - -@Value.Immutable -public abstract class RawCloudPackage extends RawCloudEntity { - - @Value.Parameter - public abstract Package getResource(); - - @Override - public CloudPackage derive() { - Package resource = getResource(); - return ImmutableCloudPackage.builder() - .metadata(parseResourceMetadata(resource)) - .status(parseStatus(resource)) - .data(parseData(resource)) - .type(parseType(resource)) - .build(); - } - - private static Status parseStatus(Package resource) { - return parseEnum(resource.getState(), Status.class); - } - - private static CloudPackage.PackageData parseData(Package resource) { - if (resource.getType() == PackageType.BITS) { - return parseBitsData((BitsData) resource.getData()); - } - return parseDockerData((DockerData) resource.getData()); - } - - private static CloudPackage.PackageData parseBitsData(BitsData data) { - return ImmutableBitsData.builder() - .checksum(parseBitsChecksum(data.getChecksum())) - .error(data.getError()) - .build(); - } - - private static org.cloudfoundry.multiapps.controller.client.facade.domain.BitsData.Checksum parseBitsChecksum(Checksum checksum) { - if (checksum == null) { - return null; - } - return ImmutableBitsData.ImmutableChecksum.builder() - .algorithm(checksum.getType() - .toString()) - .value(checksum.getValue()) - .build(); - } - - private static CloudPackage.PackageData parseDockerData(DockerData data) { - return ImmutableDockerData.builder() - .image(data.getImage()) - .username(data.getUsername()) - .password(data.getPassword()) - .build(); - } - - private static CloudPackage.Type parseType(Package resource) { - return parseEnum(resource.getType(), CloudPackage.Type.class); - } - -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudProcess.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudProcess.java deleted file mode 100644 index 931f528b75..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudProcess.java +++ /dev/null @@ -1,62 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import org.cloudfoundry.client.v3.processes.Data; -import org.cloudfoundry.client.v3.processes.HealthCheck; -import org.cloudfoundry.client.v3.processes.Process; -import org.cloudfoundry.client.v3.processes.ReadinessHealthCheck; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudProcess; -import org.cloudfoundry.multiapps.controller.client.facade.domain.HealthCheckType; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudProcess; -import org.immutables.value.Value; - -@Value.Immutable -public abstract class RawCloudProcess extends RawCloudEntity { - - @Value.Parameter - public abstract Process getProcess(); - - @Override - public CloudProcess derive() { - Process process = getProcess(); - HealthCheck healthCheck = process.getHealthCheck(); - ReadinessHealthCheck readinessHealthCheckType = process.getReadinessHealthCheck(); - Integer healthCheckTimeout = null; - String healthCheckHttpEndpoint = null; - Integer healthCheckInvocationTimeout = null; - Integer healthCheckInterval = null; - if (healthCheck.getData() != null) { - Data healthCheckData = healthCheck.getData(); - healthCheckTimeout = healthCheckData.getTimeout(); - healthCheckInvocationTimeout = healthCheckData.getInvocationTimeout(); - healthCheckHttpEndpoint = healthCheckData.getEndpoint(); - healthCheckInterval = healthCheckData.getInterval(); - } - Integer readinessHealthCheckInvocationTimeout = null; - String readinessHealthCheckHttpEndpoint = null; - Integer readinessHealthCheckInterval = null; - if (readinessHealthCheckType.getData() != null) { - Data readinessHealthCheckData = readinessHealthCheckType.getData(); - readinessHealthCheckInvocationTimeout = readinessHealthCheckData.getInvocationTimeout(); - readinessHealthCheckHttpEndpoint = readinessHealthCheckData.getEndpoint(); - readinessHealthCheckInterval = readinessHealthCheckData.getInterval(); - } - return ImmutableCloudProcess.builder() - .command(process.getCommand()) - .instances(process.getInstances()) - .memoryInMb(process.getMemoryInMb()) - .diskInMb(process.getDiskInMb()) - .healthCheckType(HealthCheckType.valueOf(healthCheck.getType() - .getValue() - .toUpperCase())) - .healthCheckHttpEndpoint(healthCheckHttpEndpoint) - .healthCheckTimeout(healthCheckTimeout) - .healthCheckInvocationTimeout(healthCheckInvocationTimeout) - .healthCheckInterval(healthCheckInterval) - .readinessHealthCheckType(readinessHealthCheckType.getType() - .getValue()) - .readinessHealthCheckHttpEndpoint(readinessHealthCheckHttpEndpoint) - .readinessHealthCheckInvocationTimeout(readinessHealthCheckInvocationTimeout) - .readinessHealthCheckInterval(readinessHealthCheckInterval) - .build(); - } -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudRoute.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudRoute.java deleted file mode 100644 index e2ae758814..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudRoute.java +++ /dev/null @@ -1,99 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.util.List; -import java.util.Objects; -import java.util.UUID; -import java.util.stream.Collectors; - -import org.cloudfoundry.client.v3.routes.Route; -import org.immutables.value.Value; - -import org.cloudfoundry.multiapps.controller.client.facade.Nullable; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudRoute; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudDomain; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudRoute; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableRouteDestination; -import org.cloudfoundry.multiapps.controller.client.facade.domain.RouteDestination; - -@Value.Immutable -public abstract class RawCloudRoute extends RawCloudEntity { - - @Value.Parameter - public abstract Route getRoute(); - - @Nullable - public abstract UUID getApplicationGuid(); - - @Override - public CloudRoute derive() { - Route route = getRoute(); - List destinations = mapDestinations(); - String domainGuid = route.getRelationships() - .getDomain() - .getData() - .getId(); - return ImmutableCloudRoute.builder() - .metadata(parseResourceMetadata(route)) - .appsUsingRoute(route.getDestinations() - .size()) - .host(route.getHost()) - .port(route.getPort()) - .domain(ImmutableCloudDomain.builder() - .name(computeDomain(route)) - .metadata(ImmutableCloudMetadata.of(UUID.fromString(domainGuid))) - .build()) - .path(route.getPath()) - .url(route.getUrl()) - .destinations(destinations) - .requestedProtocol(computeRequestedProtocol(destinations)) - .build(); - } - - private static String computeDomain(Route route) { - String domain = route.getUrl(); - if (!route.getHost() - .isEmpty()) { - domain = domain.substring(route.getHost() - .length() - + 1); - } - if (!route.getPath() - .isEmpty()) { - domain = domain.substring(0, domain.indexOf('/')); - } - if (route.getPort() != null) { - domain = domain.substring(0, domain.indexOf(':')); - } - return domain; - } - - private List mapDestinations() { - return getRoute().getDestinations() - .stream() - .map(destination -> ImmutableRouteDestination.builder() - .metadata(ImmutableCloudMetadata.builder() - .guid(UUID.fromString(destination.getDestinationId())) - .build()) - .applicationGuid(UUID.fromString(destination.getApplication() - .getApplicationId())) - .port(destination.getPort()) - .weight(destination.getWeight()) - .protocol(destination.getProtocol()) - .build()) - .collect(Collectors.toList()); - } - - private String computeRequestedProtocol(List destinations) { - UUID applicationGuid = getApplicationGuid(); - if (applicationGuid == null) { - return null; - } - return destinations.stream() - .filter(destination -> Objects.equals(destination.getApplicationGuid(), applicationGuid)) - .findFirst() - .map(RouteDestination::getProtocol) - .orElse(null); - } - -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceBinding.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceBinding.java deleted file mode 100644 index b91a44dac7..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceBinding.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.util.UUID; - -import org.cloudfoundry.client.v3.servicebindings.ServiceBinding; -import org.cloudfoundry.client.v3.servicebindings.ServiceBindingResource; -import org.immutables.value.Value; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBinding; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceBinding; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceCredentialBindingOperation; - -@Value.Immutable -public abstract class RawCloudServiceBinding extends RawCloudEntity { - - @Value.Parameter - public abstract ServiceBindingResource getServiceBinding(); - - @Override - public CloudServiceBinding derive() { - ServiceBinding serviceBinding = getServiceBinding(); - var appRelationship = serviceBinding.getRelationships() - .getApplication(); - return ImmutableCloudServiceBinding.builder() - .metadata(parseResourceMetadata(serviceBinding)) - .applicationGuid(parseNullableGuid(appRelationship == null ? null - : appRelationship.getData() - .getId())) - .serviceInstanceGuid(UUID.fromString(serviceBinding.getRelationships() - .getServiceInstance() - .getData() - .getId())) - .serviceBindingOperation(ServiceCredentialBindingOperation.from(toDomainLastOperation(getServiceBinding().getLastOperation()))) - .build(); - } - -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceBroker.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceBroker.java deleted file mode 100644 index d76fcc5cdd..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceBroker.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.util.Optional; - -import org.cloudfoundry.client.v3.Relationship; -import org.cloudfoundry.client.v3.ToOneRelationship; -import org.cloudfoundry.client.v3.servicebrokers.ServiceBroker; -import org.cloudfoundry.client.v3.servicebrokers.ServiceBrokerRelationships; -import org.immutables.value.Value; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBroker; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceBroker; - -@Value.Immutable -public abstract class RawCloudServiceBroker extends RawCloudEntity { - - @Value.Parameter - public abstract ServiceBroker getServiceBroker(); - - @Override - public CloudServiceBroker derive() { - ServiceBroker serviceBroker = getServiceBroker(); - String spaceGuid = getSpaceGuid(serviceBroker); - return ImmutableCloudServiceBroker.builder() - .metadata(parseResourceMetadata(serviceBroker)) - .name(serviceBroker.getName()) - .url(serviceBroker.getUrl()) - .spaceGuid(spaceGuid) - .build(); - } - - private String getSpaceGuid(ServiceBroker serviceBroker) { - return Optional.ofNullable(serviceBroker.getRelationships()) - .map(ServiceBrokerRelationships::getSpace) - .map(ToOneRelationship::getData) - .map(Relationship::getId) - .orElse(null); - } - -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceInstance.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceInstance.java deleted file mode 100644 index f66b1580aa..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceInstance.java +++ /dev/null @@ -1,56 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.util.Optional; - -import org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceResource; -import org.cloudfoundry.client.v3.serviceofferings.ServiceOffering; -import org.cloudfoundry.client.v3.serviceplans.ServicePlan; -import org.immutables.value.Value; - -import org.cloudfoundry.multiapps.controller.client.facade.Nullable; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceInstance; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceOperation; - -@Value.Immutable -public abstract class RawCloudServiceInstance extends RawCloudEntity { - - @Value.Parameter - public abstract ServiceInstanceResource getResource(); - - @Nullable - public abstract ServicePlan getServicePlan(); - - @Nullable - public abstract ServiceOffering getServiceOffering(); - - @Override - public CloudServiceInstance derive() { - ServiceInstanceResource resource = getResource(); - return ImmutableCloudServiceInstance.builder() - .metadata(parseResourceMetadata(resource)) - .v3Metadata(toDomainMetadata(resource.getMetadata())) - .name(resource.getName()) - .plan(getServicePlanName()) - .label(getLabelName()) - .type(org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType.valueOf(resource.getType() - .name())) - .tags(resource.getTags()) - .lastOperation(ServiceOperation.fromLastOperation(toDomainLastOperation(resource.getLastOperation()))) - .syslogDrainUrl(resource.getSyslogDrainUrl()) - .build(); - } - - private String getServicePlanName() { - return Optional.ofNullable(getServicePlan()) - .map(ServicePlan::getName) - .orElse(null); - } - - private String getLabelName() { - return Optional.ofNullable(getServiceOffering()) - .map(ServiceOffering::getName) - .orElse(null); - } - -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceKey.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceKey.java deleted file mode 100644 index 87afdbe471..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceKey.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.util.Map; - -import org.cloudfoundry.AllowNulls; -import org.cloudfoundry.client.v3.servicebindings.ServiceBindingResource; -import org.immutables.value.Value; - -import org.cloudfoundry.multiapps.controller.client.facade.Nullable; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Derivable; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceKey; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceCredentialBindingOperation; - -@Value.Immutable -public abstract class RawCloudServiceKey extends RawCloudEntity { - - public abstract ServiceBindingResource getServiceBindingResource(); - - @Nullable - @AllowNulls - public abstract Map getCredentials(); - - public abstract Derivable getServiceInstance(); - - @Override - public CloudServiceKey derive() { - ServiceBindingResource serviceBindingResource = getServiceBindingResource(); - Derivable serviceInstance = getServiceInstance(); - return ImmutableCloudServiceKey.builder() - .metadata(parseResourceMetadata(serviceBindingResource)) - .v3Metadata(toDomainMetadata(serviceBindingResource.getMetadata())) - .name(serviceBindingResource.getName()) - .credentials(getCredentials()) - .serviceInstance(serviceInstance.derive()) - .serviceKeyOperation(ServiceCredentialBindingOperation.from(toDomainLastOperation(serviceBindingResource.getLastOperation()))) - .build(); - } - -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceOffering.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceOffering.java deleted file mode 100644 index c252aa50bd..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceOffering.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.util.List; - -import org.cloudfoundry.client.v3.serviceofferings.ServiceOfferingResource; -import org.immutables.value.Value; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceOffering; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServicePlan; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Derivable; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceOffering; - -@Value.Immutable -public abstract class RawCloudServiceOffering extends RawCloudEntity { - - public abstract ServiceOfferingResource getServiceOffering(); - - public abstract List> getServicePlans(); - - @Override - public CloudServiceOffering derive() { - ServiceOfferingResource serviceOffering = getServiceOffering(); - return ImmutableCloudServiceOffering.builder() - .metadata(parseResourceMetadata(serviceOffering)) - .name(serviceOffering.getName()) - .isAvailable(serviceOffering.getAvailable()) - .isBindable(serviceOffering.getBrokerCatalog() - .getFeatures() - .getBindable()) - .description(serviceOffering.getDescription()) - .isShareable(serviceOffering.getShareable()) - .extra(serviceOffering.getBrokerCatalog() - .getMetadata()) - .docUrl(serviceOffering.getDocumentationUrl()) - .brokerId(serviceOffering.getRelationships() - .getServiceBroker() - .getData() - .getId()) - .uniqueId(serviceOffering.getBrokerCatalog() - .getBrokerCatalogId()) - .servicePlans(derive(getServicePlans())) - .build(); - } - -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServicePlan.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServicePlan.java deleted file mode 100644 index a79527037e..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServicePlan.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import org.cloudfoundry.client.v3.serviceplans.ServicePlan; -import org.cloudfoundry.client.v3.serviceplans.Visibility; -import org.immutables.value.Value; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServicePlan; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServicePlan; - -@Value.Immutable -public abstract class RawCloudServicePlan extends RawCloudEntity { - - @Value.Parameter - public abstract ServicePlan getResource(); - - @Override - public CloudServicePlan derive() { - ServicePlan resource = getResource(); - return ImmutableCloudServicePlan.builder() - .metadata(parseResourceMetadata(resource)) - .name(resource.getName()) - .description(resource.getDescription()) - .extra(resource.getBrokerCatalog() - .getMetadata()) - .uniqueId(resource.getBrokerCatalog() - .getBrokerCatalogId()) - .serviceOfferingId(resource.getRelationships() - .getServiceOffering() - .getData() - .getId()) - .isPublic(resource.getVisibilityType() - .equals(Visibility.PUBLIC)) - .isFree(resource.getFree()) - .build(); - } - -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudStack.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudStack.java deleted file mode 100644 index 6c2a8ce152..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudStack.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import org.cloudfoundry.client.v3.stacks.Stack; -import org.immutables.value.Value; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudStack; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudStack; - -@Value.Immutable -public abstract class RawCloudStack extends RawCloudEntity { - - @Value.Parameter - public abstract Stack getStack(); - - @Override - public CloudStack derive() { - Stack stack = getStack(); - return ImmutableCloudStack.builder() - .metadata(parseResourceMetadata(stack)) - .name(stack.getName()) - .description(stack.getDescription()) - .build(); - } - -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudTask.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudTask.java deleted file mode 100644 index b963d4457a..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudTask.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.util.Optional; - -import org.cloudfoundry.client.v3.tasks.Result; -import org.cloudfoundry.client.v3.tasks.Task; -import org.cloudfoundry.client.v3.tasks.TaskState; -import org.immutables.value.Value; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudTask; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudTask; - -@Value.Immutable -public abstract class RawCloudTask extends RawCloudEntity { - - @Value.Parameter - public abstract Task getResource(); - - @Override - public CloudTask derive() { - Task resource = getResource(); - return ImmutableCloudTask.builder() - .metadata(parseResourceMetadata(resource)) - .name(resource.getName()) - .command(resource.getCommand()) - .limits(parseLimits(resource)) - .result(parseResult(resource)) - .state(parseState(resource.getState())) - .build(); - } - - private static CloudTask.Result parseResult(Task resource) { - return Optional.ofNullable(resource.getResult()) - .map(Result::getFailureReason) - .map(ImmutableCloudTask.ImmutableResult::of) - .orElse(null); - } - - private static CloudTask.Limits parseLimits(Task resource) { - return ImmutableCloudTask.ImmutableLimits.builder() - .disk(resource.getDiskInMb()) - .memory(resource.getMemoryInMb()) - .build(); - } - - private static CloudTask.State parseState(TaskState state) { - return parseEnum(state, CloudTask.State.class); - } - -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawInstancesInfo.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawInstancesInfo.java deleted file mode 100644 index 1b55721485..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawInstancesInfo.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - -import org.cloudfoundry.client.v3.applications.GetApplicationProcessStatisticsResponse; -import org.cloudfoundry.client.v3.processes.ProcessStatisticsResource; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableInstanceInfo; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableInstancesInfo; -import org.cloudfoundry.multiapps.controller.client.facade.domain.InstanceInfo; -import org.cloudfoundry.multiapps.controller.client.facade.domain.InstanceState; -import org.cloudfoundry.multiapps.controller.client.facade.domain.InstancesInfo; -import org.immutables.value.Value; - -@Value.Immutable -public abstract class RawInstancesInfo extends RawCloudEntity { - - @Value.Parameter - public abstract GetApplicationProcessStatisticsResponse getProcessStatisticsResponse(); - - @Override - public InstancesInfo derive() { - var processStats = getProcessStatisticsResponse(); - return ImmutableInstancesInfo.builder() - .instances(parseProcessStatistics(processStats.getResources())) - .build(); - } - - private static List parseProcessStatistics(List stats) { - if (stats == null) { - return Collections.emptyList(); - } - return stats.stream() - .map(RawInstancesInfo::parseProcessStatistic) - .collect(Collectors.toList()); - } - - private static InstanceInfo parseProcessStatistic(ProcessStatisticsResource statsResource) { - return ImmutableInstanceInfo.builder() - .index(statsResource.getIndex()) - .state(InstanceState.valueOfWithDefault(statsResource.getState() - .getValue())) - .isRoutable(Boolean.parseBoolean(statsResource.getRoutable())) - .build(); - } -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawUserRole.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawUserRole.java deleted file mode 100644 index 18aee09f06..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawUserRole.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.Derivable; -import org.cloudfoundry.client.v3.roles.RoleResource; -import org.immutables.value.Value; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.UserRole; - -@Value.Immutable -public abstract class RawUserRole implements Derivable { - - @Value.Parameter - public abstract RoleResource getRoleResource(); - - @Override - public UserRole derive() { - RoleResource role = getRoleResource(); - return UserRole.fromRoleType(role.getType() - .getValue()); - } - -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawV3CloudServiceInstance.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawV3CloudServiceInstance.java deleted file mode 100644 index db610973ef..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawV3CloudServiceInstance.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceInstance; -import org.cloudfoundry.client.v3.serviceinstances.ServiceInstance; -import org.immutables.value.Value; - -@Value.Immutable -public abstract class RawV3CloudServiceInstance extends RawCloudEntity { - - @Value.Parameter - public abstract ServiceInstance getServiceInstance(); - - @Override - public CloudServiceInstance derive() { - ServiceInstance serviceInstance = getServiceInstance(); - return ImmutableCloudServiceInstance.builder() - .metadata(parseResourceMetadata(serviceInstance)) - .v3Metadata(toDomainMetadata(serviceInstance.getMetadata())) - .name(serviceInstance.getName()) - .tags(serviceInstance.getTags()) - .build(); - } - -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudServiceBinding.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudServiceBinding.java index bceb94223b..e3fb8544fe 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudServiceBinding.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudServiceBinding.java @@ -2,7 +2,7 @@ import java.util.UUID; -import org.cloudfoundry.Nullable; +import org.cloudfoundry.multiapps.controller.client.facade.Nullable; import org.immutables.value.Value; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudServiceInstance.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudServiceInstance.java index 06f546412c..2cf5216efa 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudServiceInstance.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudServiceInstance.java @@ -3,7 +3,7 @@ import java.util.List; import java.util.Map; -import org.cloudfoundry.AllowNulls; +import org.cloudfoundry.multiapps.controller.client.facade.AllowNulls; import org.immutables.value.Value; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudServiceKey.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudServiceKey.java index a77cad1967..76c211eeda 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudServiceKey.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudServiceKey.java @@ -2,7 +2,7 @@ import java.util.Map; -import org.cloudfoundry.AllowNulls; +import org.cloudfoundry.multiapps.controller.client.facade.AllowNulls; import org.immutables.value.Value; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/Lifecycle.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/Lifecycle.java index f448f2c668..d6453dd89e 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/Lifecycle.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/Lifecycle.java @@ -3,7 +3,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import org.cloudfoundry.multiapps.controller.client.facade.Nullable; -import org.cloudfoundry.AllowNulls; +import org.cloudfoundry.multiapps.controller.client.facade.AllowNulls; import org.immutables.value.Value; import java.util.Map; diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/oauth2/OAuthClient.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/oauth2/OAuthClient.java index 414fc822b8..03ae12365f 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/oauth2/OAuthClient.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/oauth2/OAuthClient.java @@ -8,9 +8,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.CloudCredentials; import org.cloudfoundry.multiapps.controller.client.facade.Constants; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.OAuthTokenProvider; import org.cloudfoundry.multiapps.controller.client.facade.util.JsonUtil; -import org.cloudfoundry.reactor.TokenProvider; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.util.LinkedMultiValueMap; @@ -78,10 +76,6 @@ public String getAuthorizationHeaderValue() { return null; } - public TokenProvider getTokenProvider() { - return new OAuthTokenProvider(this); - } - private boolean shouldRefreshToken() { return credentials.isRefreshable() && token.getOAuth2AccessToken() .getExpiresAt() diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientBuilder.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientBuilder.java index 4c0d158f1d..bcf7f5e322 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientBuilder.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientBuilder.java @@ -18,6 +18,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.client.JdkClientHttpRequestFactory; import org.springframework.http.converter.ByteArrayHttpMessageConverter; +import org.springframework.http.converter.FormHttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.web.client.RestClient; @@ -56,10 +57,14 @@ public static RestClient build(URL v3ApiUrl, OAuthClient oAuthClient, Map // MappingJackson2YamlHttpMessageConverter -> Jackson2ObjectMapperBuilder.yaml(), which loads a YAMLFactory that is // incompatible with this codebase's jackson-core (mixed Jackson 2/3 classpath) and throws VerifyError at load time - // — so clearing the list afterwards is too late. The List overload skips initMessageConverters() entirely, so the - // YAML converter is never built. CF v3 is JSON-only; JSON + String + byte[] (from an ObjectMapper we control) suffice. + // — so clearing the list afterwards is too late. The List overload skips initMessageConverters() entirely. + // Converters: byte[] + String + JSON (from an ObjectMapper we control) for the v3 JSON APIs, plus a plain + // FormHttpMessageConverter for multipart/form-data (the POST /v3/packages/{guid}/upload app-bits upload). We use + // FormHttpMessageConverter, NOT AllEncompassingFormHttpMessageConverter, precisely because the latter is what + // triggers the YAML VerifyError above. .messageConverters(List.of(new ByteArrayHttpMessageConverter(), new StringHttpMessageConverter(), + new FormHttpMessageConverter(), new MappingJackson2HttpMessageConverter(new ObjectMapper()))) .requestInterceptor((request, body, execution) -> { String authorization = oAuthClient.getAuthorizationHeaderValue(); diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientFactory.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientFactory.java index 47a209a587..fa394c5925 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientFactory.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientFactory.java @@ -1,19 +1,21 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest; import org.cloudfoundry.multiapps.controller.client.facade.CloudCredentials; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.CloudFoundryClientFactory; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableCloudFoundryClientFactory; +import org.cloudfoundry.multiapps.controller.client.facade.CloudException; +import org.cloudfoundry.multiapps.controller.client.facade.adapters.LogCacheClient; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; import org.cloudfoundry.multiapps.controller.client.facade.oauth2.OAuthClient; import org.cloudfoundry.multiapps.controller.client.facade.util.JsonUtil; import org.cloudfoundry.multiapps.controller.client.facade.util.RestUtil; -import org.cloudfoundry.client.CloudFoundryClient; import org.immutables.value.Value; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.client.RestClient; +import java.net.MalformedURLException; +import java.net.URI; import java.net.URL; +import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; @@ -21,9 +23,15 @@ import java.util.Map; import java.util.Optional; +/** + * Builds the in-house {@link CloudControllerRestClient} (and the auxiliary {@link CloudSpaceClient} / {@link LogCacheClient}) that talk to + * the Cloud Controller v3 REST API directly through a blocking Spring {@link RestClient}, with no dependency on the OSS cf-java-client. + */ @Value.Immutable public abstract class CloudControllerRestClientFactory { + private static final Logger LOGGER = LoggerFactory.getLogger(CloudControllerRestClientFactory.class); + private final RestUtil restUtil = new RestUtil(); public abstract Optional getSslHandshakeTimeout(); @@ -41,35 +49,10 @@ public boolean shouldTrustSelfSignedCertificates() { return false; } - /** - * Feature flag for the cf-java-client migration PoC. When {@code true}, {@link #createClient} builds the in-house - * {@link CloudControllerRestClientV3Impl} (direct CF v3 REST, no cf-java-client); when {@code false} (default), it builds the - * OSS-backed {@link CloudControllerRestClientImpl}. Kept default-off so existing behaviour is unchanged and the two can be A/B - * compared against the same live Cloud Foundry. - */ - @Value.Default - public boolean useV3RestClient() { - // cf_java_client_poc branch: default ON so the in-house CloudControllerRestClientV3Impl is always chosen for OQ verification. - // Do NOT merge this default to main — the switch belongs off there until the client is validated. - return true; - } - - @Value.Derived - public CloudFoundryClientFactory getCloudFoundryClientFactory() { - ImmutableCloudFoundryClientFactory.Builder builder = ImmutableCloudFoundryClientFactory.builder(); - getSslHandshakeTimeout().ifPresent(builder::sslHandshakeTimeout); - getConnectTimeout().ifPresent(builder::connectTimeout); - getConnectionPoolSize().ifPresent(builder::connectionPoolSize); - getThreadPoolSize().ifPresent(builder::threadPoolSize); - getResponseTimeout().ifPresent(builder::responseTimeout); - return builder.build(); - } - public CloudControllerRestClient createClient(URL controllerUrl, CloudCredentials credentials, String organizationName, String spaceName, OAuthClient oAuthClient, Map requestTags) { oAuthClient.init(credentials); - CloudSpaceClient spaceGetter = getCloudFoundryClientFactory().createSpaceClient(controllerUrl, oAuthClient, requestTags); - CloudSpace target = spaceGetter.getSpace(organizationName, spaceName); + CloudSpace target = createSpaceClient(controllerUrl, oAuthClient, requestTags).getSpace(organizationName, spaceName); return createClient(controllerUrl, credentials, target, oAuthClient, requestTags); } @@ -81,45 +64,78 @@ public CloudControllerRestClient createClient(URL controllerUrl, CloudCredential public CloudControllerRestClient createClient(URL controllerUrl, CloudCredentials credentials, CloudSpace target, OAuthClient oAuthClient, Map requestTags) { oAuthClient.init(credentials); - if (useV3RestClient()) { - return createV3RestClient(controllerUrl, target, oAuthClient, requestTags); - } - CloudFoundryClient delegate = getCloudFoundryClientFactory().createClient(controllerUrl, oAuthClient, requestTags); - return new CloudControllerRestClientImpl(delegate, target); + URL v3ApiUrl = deriveV3ApiUrl(controllerUrl, requestTags); + RestClient restClient = buildRestClient(toOrigin(v3ApiUrl), oAuthClient, requestTags); + return new CloudControllerRestClientV3Impl(v3ApiUrl, oAuthClient, target, restClient); } - private CloudControllerRestClient createV3RestClient(URL controllerUrl, CloudSpace target, OAuthClient oAuthClient, - Map requestTags) { + public CloudSpaceClient createSpaceClient(URL controllerUrl, OAuthClient oAuthClient, Map requestTags) { URL v3ApiUrl = deriveV3ApiUrl(controllerUrl, requestTags); - // The RestClient baseUrl must be the API ORIGIN (scheme://host[:port]) only — every operation path already carries the - // "/v3/..." prefix, so using the ".../v3" href as the base would produce a duplicated "/v3/v3/apps" path (CF -> 404 Unknown - // request). We keep v3ApiUrl (which includes /v3) for reference/root-link semantics but strip it to the origin for the base. - URL baseUrl = toOrigin(v3ApiUrl); - LOGGER.warn("[cf-v3-poc] controllerUrl={} resolved v3ApiUrl={} restClientBaseUrl={}", controllerUrl, v3ApiUrl, baseUrl); + RestClient restClient = buildRestClient(toOrigin(v3ApiUrl), oAuthClient, requestTags); + return new CloudSpaceClient(new CloudControllerV3Client(restClient)); + } + + public LogCacheClient createLogCacheClient(URL controllerUrl, OAuthClient oAuthClient, Map requestTags) { + URL logCacheUrl = deriveLogCacheUrl(controllerUrl, requestTags); + RestClient restClient = buildRestClient(logCacheUrl, oAuthClient, requestTags); + return new LogCacheClient(restClient); + } + + private RestClient buildRestClient(URL baseUrl, OAuthClient oAuthClient, Map requestTags) { var transportOptions = new CloudControllerRestClientBuilder.TransportOptions(getConnectTimeout(), getResponseTimeout(), getConnectionPoolSize(), shouldTrustSelfSignedCertificates()); - RestClient restClient = CloudControllerRestClientBuilder.build(baseUrl, oAuthClient, requestTags, transportOptions); - return new CloudControllerRestClientV3Impl(v3ApiUrl, oAuthClient, target, restClient); + return CloudControllerRestClientBuilder.build(baseUrl, oAuthClient, requestTags, transportOptions); } - // scheme://host[:port] of the given URL, dropping any path (e.g. the trailing /v3). + // scheme://host[:port] of the given URL, dropping any path (e.g. the trailing /v3). The RestClient baseUrl must be the origin only, + // because every operation path already carries the "/v3/..." prefix. private URL toOrigin(URL url) { try { int port = url.getPort(); String origin = url.getProtocol() + "://" + url.getHost() + (port == -1 ? "" : ":" + port); - return java.net.URI.create(origin) - .toURL(); - } catch (java.net.MalformedURLException e) { + return URI.create(origin) + .toURL(); + } catch (MalformedURLException e) { throw new IllegalArgumentException("Could not derive the API origin from " + url, e); } } - // Resolve the CF v3 API base URL the same way the OSS factory does: GET the CF root and read links.cloud_controller_v3.href. - // The conventional controllerUrl + "/v3" is only a fallback — on some landscapes the real v3 href differs, and using the naive - // path yields "404 Not Found: Unknown request" from the Cloud Controller. + // Resolve the CF v3 API base URL from the CF root "cloud_controller_v3" link; fall back to controllerUrl + "/v3". @SuppressWarnings("unchecked") private URL deriveV3ApiUrl(URL controllerUrl, Map requestTags) { + try { + Map links = fetchRootLinks(controllerUrl, requestTags); + Map ccv3 = (Map) links.get("cloud_controller_v3"); + return URI.create((String) ccv3.get("href")) + .toURL(); + } catch (Exception e) { + return fallbackUrl(controllerUrl, "/v3"); + } + } + + // Log-Cache base URL from the CF root "log_cache" link; fall back to the "log-cache" subdomain of the controller host. + @SuppressWarnings("unchecked") + private URL deriveLogCacheUrl(URL controllerUrl, Map requestTags) { + try { + Map links = fetchRootLinks(controllerUrl, requestTags); + Map logCache = (Map) links.get("log_cache"); + return URI.create((String) logCache.get("href")) + .toURL(); + } catch (Exception e) { + String host = controllerUrl.getHost(); + String logCacheHost = host.startsWith("api.") ? "log-cache." + host.substring("api.".length()) : "log-cache." + host; + try { + return URI.create(controllerUrl.getProtocol() + "://" + logCacheHost) + .toURL(); + } catch (MalformedURLException me) { + throw new CloudException("Could not derive the log-cache URL from " + controllerUrl, me); + } + } + } + + @SuppressWarnings("unchecked") + private Map fetchRootLinks(URL controllerUrl, Map requestTags) { try { HttpRequest.Builder requestBuilder = HttpRequest.newBuilder() .GET() @@ -128,39 +144,34 @@ private URL deriveV3ApiUrl(URL controllerUrl, Map requestTags) { requestTags.forEach(requestBuilder::header); HttpResponse response = rootHttpClient().send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString()); if (response.statusCode() / 100 != 2) { - return fallbackV3ApiUrl(controllerUrl); + throw new CloudException("CF root request to " + controllerUrl + " returned " + response.statusCode()); } - Map links = (Map) JsonUtil.convertJsonToMap(response.body()) - .get("links"); - Map ccv3 = (Map) links.get("cloud_controller_v3"); - String href = (String) ccv3.get("href"); - return java.net.URI.create(href) - .toURL(); + return (Map) JsonUtil.convertJsonToMap(response.body()) + .get("links"); } catch (InterruptedException e) { Thread.currentThread() .interrupt(); - return fallbackV3ApiUrl(controllerUrl); + throw new CloudException("Interrupted while calling the CF root at " + controllerUrl, e); } catch (Exception e) { - return fallbackV3ApiUrl(controllerUrl); + throw new CloudException("Failed to call the CF root at " + controllerUrl + ": " + e.getMessage(), e); } } - private static java.net.http.HttpClient rootHttpClient() { - return java.net.http.HttpClient.newBuilder() - .connectTimeout(Duration.ofMinutes(1)) - .build(); + private static HttpClient rootHttpClient() { + return HttpClient.newBuilder() + .connectTimeout(Duration.ofMinutes(1)) + .build(); } - private URL fallbackV3ApiUrl(URL controllerUrl) { + private URL fallbackUrl(URL controllerUrl, String pathSuffix) { try { - return java.net.URI.create(controllerUrl.toString() + "/v3") - .toURL(); - } catch (java.net.MalformedURLException e) { - throw new IllegalArgumentException("Could not derive the CF v3 API URL from " + controllerUrl, e); + return URI.create(controllerUrl.toString() + pathSuffix) + .toURL(); + } catch (MalformedURLException e) { + throw new IllegalArgumentException("Could not derive URL from " + controllerUrl + pathSuffix, e); } } - private OAuthClient createOAuthClient(URL controllerUrl, String origin) { return restUtil.createOAuthClientByControllerUrl(controllerUrl, shouldTrustSelfSignedCertificates()); } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientImpl.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientImpl.java deleted file mode 100644 index 95172fea6c..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientImpl.java +++ /dev/null @@ -1,2631 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.rest; - -import java.nio.file.Path; -import java.text.MessageFormat; -import java.time.Duration; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.EnumSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.TimeUnit; -import java.util.function.Function; -import java.util.function.IntFunction; -import java.util.function.Supplier; -import java.util.stream.Collectors; - -import org.cloudfoundry.AbstractCloudFoundryException; -import org.cloudfoundry.client.CloudFoundryClient; -import org.cloudfoundry.client.v3.BuildpackData; -import org.cloudfoundry.client.v3.DockerData; -import org.cloudfoundry.client.v3.Lifecycle; -import org.cloudfoundry.client.v3.LifecycleType; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; -import org.cloudfoundry.client.v3.Relationship; -import org.cloudfoundry.client.v3.Resource; -import org.cloudfoundry.client.v3.ToOneRelationship; -import org.cloudfoundry.client.v3.applications.Application; -import org.cloudfoundry.client.v3.applications.ApplicationFeature; -import org.cloudfoundry.client.v3.applications.ApplicationRelationships; -import org.cloudfoundry.client.v3.applications.ApplicationState; -import org.cloudfoundry.client.v3.applications.CreateApplicationRequest; -import org.cloudfoundry.client.v3.applications.CreateApplicationResponse; -import org.cloudfoundry.client.v3.applications.DeleteApplicationRequest; -import org.cloudfoundry.client.v3.applications.GetApplicationCurrentDropletRequest; -import org.cloudfoundry.client.v3.applications.GetApplicationCurrentDropletResponse; -import org.cloudfoundry.client.v3.applications.GetApplicationEnvironmentVariablesRequest; -import org.cloudfoundry.client.v3.applications.GetApplicationEnvironmentVariablesResponse; -import org.cloudfoundry.client.v3.applications.GetApplicationProcessRequest; -import org.cloudfoundry.client.v3.applications.GetApplicationProcessResponse; -import org.cloudfoundry.client.v3.applications.GetApplicationProcessStatisticsRequest; -import org.cloudfoundry.client.v3.applications.GetApplicationProcessStatisticsResponse; -import org.cloudfoundry.client.v3.applications.GetApplicationRequest; -import org.cloudfoundry.client.v3.applications.GetApplicationSshEnabledRequest; -import org.cloudfoundry.client.v3.applications.GetApplicationSshEnabledResponse; -import org.cloudfoundry.client.v3.applications.ListApplicationBuildsRequest; -import org.cloudfoundry.client.v3.applications.ListApplicationFeaturesRequest; -import org.cloudfoundry.client.v3.applications.ListApplicationPackagesRequest; -import org.cloudfoundry.client.v3.applications.ListApplicationRoutesRequest; -import org.cloudfoundry.client.v3.applications.ListApplicationsRequest; -import org.cloudfoundry.client.v3.applications.ScaleApplicationRequest; -import org.cloudfoundry.client.v3.applications.SetApplicationCurrentDropletRequest; -import org.cloudfoundry.client.v3.applications.StartApplicationRequest; -import org.cloudfoundry.client.v3.applications.StopApplicationRequest; -import org.cloudfoundry.client.v3.applications.UpdateApplicationEnvironmentVariablesRequest; -import org.cloudfoundry.client.v3.applications.UpdateApplicationFeatureRequest; -import org.cloudfoundry.client.v3.applications.UpdateApplicationRequest; -import org.cloudfoundry.client.v3.applications.UpdateApplicationResponse; -import org.cloudfoundry.client.v3.auditevents.AuditEventResource; -import org.cloudfoundry.client.v3.auditevents.ListAuditEventsRequest; -import org.cloudfoundry.client.v3.builds.Build; -import org.cloudfoundry.client.v3.builds.CreateBuildRequest; -import org.cloudfoundry.client.v3.builds.GetBuildRequest; -import org.cloudfoundry.client.v3.builds.ListBuildsRequest; -import org.cloudfoundry.client.v3.domains.CreateDomainRequest; -import org.cloudfoundry.client.v3.domains.DeleteDomainRequest; -import org.cloudfoundry.client.v3.domains.Domain; -import org.cloudfoundry.client.v3.domains.DomainRelationships; -import org.cloudfoundry.client.v3.domains.DomainResource; -import org.cloudfoundry.client.v3.domains.ListDomainsRequest; -import org.cloudfoundry.client.v3.jobs.GetJobRequest; -import org.cloudfoundry.client.v3.organizations.GetOrganizationDefaultDomainRequest; -import org.cloudfoundry.client.v3.organizations.ListOrganizationDomainsRequest; -import org.cloudfoundry.client.v3.packages.CreatePackageRequest; -import org.cloudfoundry.client.v3.packages.CreatePackageResponse; -import org.cloudfoundry.client.v3.packages.GetPackageRequest; -import org.cloudfoundry.client.v3.packages.Package; -import org.cloudfoundry.client.v3.packages.PackageRelationships; -import org.cloudfoundry.client.v3.packages.PackageResource; -import org.cloudfoundry.client.v3.packages.PackageType; -import org.cloudfoundry.client.v3.packages.UploadPackageRequest; -import org.cloudfoundry.client.v3.processes.Data; -import org.cloudfoundry.client.v3.processes.HealthCheck; -import org.cloudfoundry.client.v3.processes.HealthCheckType; -import org.cloudfoundry.client.v3.processes.ReadinessHealthCheck; -import org.cloudfoundry.client.v3.processes.ReadinessHealthCheckType; -import org.cloudfoundry.client.v3.processes.UpdateProcessRequest; -import org.cloudfoundry.client.v3.roles.ListRolesRequest; -import org.cloudfoundry.client.v3.roles.RoleResource; -import org.cloudfoundry.client.v3.roles.RoleType; -import org.cloudfoundry.client.v3.routes.CreateRouteRequest; -import org.cloudfoundry.client.v3.routes.CreateRouteResponse; -import org.cloudfoundry.client.v3.routes.DeleteRouteRequest; -import org.cloudfoundry.client.v3.routes.Destination; -import org.cloudfoundry.client.v3.routes.InsertRouteDestinationsRequest; -import org.cloudfoundry.client.v3.routes.ListRoutesRequest; -import org.cloudfoundry.client.v3.routes.RemoveRouteDestinationsRequest; -import org.cloudfoundry.client.v3.routes.RouteRelationships; -import org.cloudfoundry.client.v3.routes.RouteResource; -import org.cloudfoundry.client.v3.servicebindings.CreateServiceBindingRequest; -import org.cloudfoundry.client.v3.servicebindings.CreateServiceBindingResponse; -import org.cloudfoundry.client.v3.servicebindings.DeleteServiceBindingRequest; -import org.cloudfoundry.client.v3.servicebindings.GetServiceBindingDetailsRequest; -import org.cloudfoundry.client.v3.servicebindings.GetServiceBindingDetailsResponse; -import org.cloudfoundry.client.v3.servicebindings.GetServiceBindingParametersRequest; -import org.cloudfoundry.client.v3.servicebindings.GetServiceBindingParametersResponse; -import org.cloudfoundry.client.v3.servicebindings.ListServiceBindingsRequest; -import org.cloudfoundry.client.v3.servicebindings.ServiceBindingRelationships; -import org.cloudfoundry.client.v3.servicebindings.ServiceBindingResource; -import org.cloudfoundry.client.v3.servicebindings.ServiceBindingType; -import org.cloudfoundry.client.v3.servicebindings.UpdateServiceBindingRequest; -import org.cloudfoundry.client.v3.servicebrokers.BasicAuthentication; -import org.cloudfoundry.client.v3.servicebrokers.CreateServiceBrokerRequest; -import org.cloudfoundry.client.v3.servicebrokers.DeleteServiceBrokerRequest; -import org.cloudfoundry.client.v3.servicebrokers.ListServiceBrokersRequest; -import org.cloudfoundry.client.v3.servicebrokers.ServiceBrokerRelationships; -import org.cloudfoundry.client.v3.servicebrokers.ServiceBrokerResource; -import org.cloudfoundry.client.v3.servicebrokers.UpdateServiceBrokerRequest; -import org.cloudfoundry.client.v3.serviceinstances.CreateServiceInstanceRequest; -import org.cloudfoundry.client.v3.serviceinstances.DeleteServiceInstanceRequest; -import org.cloudfoundry.client.v3.serviceinstances.GetManagedServiceParametersRequest; -import org.cloudfoundry.client.v3.serviceinstances.GetManagedServiceParametersResponse; -import org.cloudfoundry.client.v3.serviceinstances.GetServiceInstanceRequest; -import org.cloudfoundry.client.v3.serviceinstances.GetUserProvidedCredentialsRequest; -import org.cloudfoundry.client.v3.serviceinstances.GetUserProvidedCredentialsResponse; -import org.cloudfoundry.client.v3.serviceinstances.ListServiceInstancesRequest; -import org.cloudfoundry.client.v3.serviceinstances.ServiceInstance; -import org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceRelationships; -import org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceResource; -import org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceType; -import org.cloudfoundry.client.v3.serviceinstances.UpdateServiceInstanceRequest; -import org.cloudfoundry.client.v3.serviceofferings.GetServiceOfferingRequest; -import org.cloudfoundry.client.v3.serviceofferings.ListServiceOfferingsRequest; -import org.cloudfoundry.client.v3.serviceofferings.ServiceOffering; -import org.cloudfoundry.client.v3.serviceofferings.ServiceOfferingResource; -import org.cloudfoundry.client.v3.serviceplans.GetServicePlanRequest; -import org.cloudfoundry.client.v3.serviceplans.ListServicePlansRequest; -import org.cloudfoundry.client.v3.serviceplans.ServicePlan; -import org.cloudfoundry.client.v3.serviceplans.ServicePlanResource; -import org.cloudfoundry.client.v3.serviceplans.UpdateServicePlanVisibilityRequest; -import org.cloudfoundry.client.v3.serviceplans.Visibility; -import org.cloudfoundry.client.v3.spaces.DeleteUnmappedRoutesRequest; -import org.cloudfoundry.client.v3.stacks.ListStacksRequest; -import org.cloudfoundry.client.v3.stacks.Stack; -import org.cloudfoundry.client.v3.tasks.CancelTaskRequest; -import org.cloudfoundry.client.v3.tasks.CreateTaskRequest; -import org.cloudfoundry.client.v3.tasks.GetTaskRequest; -import org.cloudfoundry.client.v3.tasks.ListTasksRequest; -import org.cloudfoundry.client.v3.tasks.Task; -import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; -import org.cloudfoundry.multiapps.controller.client.facade.Constants; -import org.cloudfoundry.multiapps.controller.client.facade.Messages; -import org.cloudfoundry.multiapps.controller.client.facade.UploadStatusCallback; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableRawCloudApplication; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableRawCloudAsyncJob; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableRawCloudBuild; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableRawCloudDomain; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableRawCloudEvent; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableRawCloudPackage; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableRawCloudProcess; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableRawCloudRoute; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableRawCloudServiceBinding; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableRawCloudServiceBroker; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableRawCloudServiceInstance; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableRawCloudServiceKey; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableRawCloudServiceOffering; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableRawCloudServicePlan; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableRawCloudStack; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableRawCloudTask; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableRawInstancesInfo; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableRawUserRole; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableRawV3CloudServiceInstance; -import org.cloudfoundry.multiapps.controller.client.facade.domain.BitsData; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudAsyncJob; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudBuild; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudDomain; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEntity; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEvent; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudMetadata; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudPackage; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudProcess; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudRoute; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBinding; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBroker; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceOffering; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServicePlan; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudStack; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudTask; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Derivable; -import org.cloudfoundry.multiapps.controller.client.facade.domain.DockerCredentials; -import org.cloudfoundry.multiapps.controller.client.facade.domain.DockerInfo; -import org.cloudfoundry.multiapps.controller.client.facade.domain.DropletInfo; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ErrorDetails; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableDropletInfo; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableErrorDetails; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableInstancesInfo; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableUpload; -import org.cloudfoundry.multiapps.controller.client.facade.domain.InstancesInfo; -import org.cloudfoundry.multiapps.controller.client.facade.domain.RouteDestination; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ServicePlanVisibility; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Staging; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Status; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Upload; -import org.cloudfoundry.multiapps.controller.client.facade.domain.UserRole; -import org.cloudfoundry.multiapps.controller.client.facade.dto.ApplicationToCreateDto; -import org.cloudfoundry.multiapps.controller.client.facade.util.JobV3Util; -import org.cloudfoundry.util.PaginationUtils; -import org.springframework.http.HttpStatus; -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -/** - * Abstract implementation of the CloudControllerClient intended to serve as the base. - */ -public class CloudControllerRestClientImpl implements CloudControllerRestClient { - - private static final long PACKAGE_UPLOAD_JOB_POLLING_PERIOD = TimeUnit.SECONDS.toMillis(5); - private static final Duration DELETE_JOB_TIMEOUT = Duration.ofMinutes(5); - private static final Duration BINDING_OPERATIONS_TIMEOUT = Duration.ofMinutes(10); - private static final int MAX_CHAR_LENGTH_FOR_PARAMS_IN_REQUEST = 4000; - - private CloudSpace target; // optional, as some operations do not require a targeted space - private CloudFoundryClient delegate; - - /** - * Only for unit tests. This works around the fact that the initialize method is called within the constructor and hence can not be - * overloaded, making it impossible to write unit tests that don't trigger network calls. - */ - protected CloudControllerRestClientImpl() { - } - - public CloudControllerRestClientImpl(CloudFoundryClient delegate) { - this(delegate, null); - } - - public CloudControllerRestClientImpl(CloudFoundryClient delegate, CloudSpace target) { - this.target = target; - this.delegate = delegate; - } - - @Override - public CloudSpace getTarget() { - return target; - } - - @Override - public void addDomain(String domainName) { - assertSpaceProvided("add domain"); - CloudDomain domain = findDomainByName(domainName); - if (domain == null) { - doCreateDomain(domainName); - } - } - - @Override - public void addRoute(String host, String domainName, String path) { - assertSpaceProvided("add route for domain"); - UUID domainGuid = getRequiredDomainGuid(domainName); - doAddRoute(domainGuid, host, path); - } - - @Override - public Optional bindServiceInstance(String bindingName, String applicationName, String serviceInstanceName) { - return bindServiceInstance(bindingName, applicationName, serviceInstanceName, null); - } - - @Override - public Optional bindServiceInstance(String bindingName, String applicationName, String serviceInstanceName, - Map parameters) { - UUID applicationGuid = getRequiredApplicationGuid(applicationName); - UUID serviceInstanceGuid = getRequiredServiceInstanceGuid(serviceInstanceName); - - var createBindingRequest = CreateServiceBindingRequest.builder() - .name(bindingName) - .type(ServiceBindingType.APPLICATION) - .relationships(ServiceBindingRelationships.builder() - .application(buildToOneRelationship( - applicationGuid)) - .serviceInstance( - buildToOneRelationship( - serviceInstanceGuid)) - .build()); - if (!CollectionUtils.isEmpty(parameters)) { - createBindingRequest.parameters(parameters); - } - return delegate.serviceBindingsV3() - .create(createBindingRequest.build()) - .map(CreateServiceBindingResponse::getJobId) - .block(); - } - - @Override - public void createApplication(ApplicationToCreateDto applicationToCreateDto) { - assertSpaceProvided("create application"); - CreateApplicationRequest createApplicationRequest = CreateApplicationRequest.builder() - .name(applicationToCreateDto.getName()) - .metadata(toOssMetadata(applicationToCreateDto.getMetadata())) - .lifecycle(buildApplicationLifecycle( - applicationToCreateDto.getStaging())) - .relationships(buildApplicationRelationships()) - .environmentVariables(applicationToCreateDto.getEnv()) - .build(); - CreateApplicationResponse createApplicationResponse = delegate.applicationsV3() - .create(createApplicationRequest) - .block(); - updateApplicationAttributes(applicationToCreateDto, createApplicationResponse.getId()); - } - - private Lifecycle buildApplicationLifecycle(Staging staging) { - // Prioritize Docker lifecycle if DockerInfo is provided - if (staging.getDockerInfo() != null) { - return createDockerLifecycle(); - } - - // Determine lifecycle type, defaulting to BUILDPACK if lifecycleType is null - LifecycleType lifecycleType = staging.getLifecycleType() != null ? LifecycleType.valueOf(staging.getLifecycleType() - .name()) : LifecycleType.BUILDPACK; - - return createLifecycleByType(staging, lifecycleType); - } - - private Lifecycle createLifecycleByType(Staging staging, LifecycleType lifecycleType) { - validateLifecycleConfiguration(staging, lifecycleType); - BuildpackData buildpackData = createBuildpackData(staging); - return Lifecycle.builder() - .type(lifecycleType) - .data(buildpackData) - .build(); - } - - private Lifecycle createDockerLifecycle() { - return Lifecycle.builder() - .type(LifecycleType.DOCKER) - .data(DockerData.builder() - .build()) - .build(); - } - - private void validateLifecycleConfiguration(Staging staging, LifecycleType lifecycleType) { - if (lifecycleType == LifecycleType.CNB && CollectionUtils.isEmpty(staging.getBuildpacks())) { - throw new IllegalArgumentException(Messages.BUILDPACKS_ARE_REQUIRED_FOR_CNB_LIFECYCLE_TYPE); - } - } - - private BuildpackData createBuildpackData(Staging staging) { - BuildpackData.Builder buildpackDataBuilder = BuildpackData.builder() - .stack(staging.getStackName()); - if (staging.getBuildpacks() != null) { - buildpackDataBuilder.addAllBuildpacks(staging.getBuildpacks()); - } - return buildpackDataBuilder.build(); - } - - private ApplicationRelationships buildApplicationRelationships() { - return ApplicationRelationships.builder() - .space(buildToOneRelationship(getTargetSpaceGuid())) - .build(); - } - - private void updateApplicationAttributes(ApplicationToCreateDto applicationToCreateDto, String applicationGuid) { - UUID createdApplicationGuid = UUID.fromString(applicationGuid); - updateApplicationProcess(createdApplicationGuid, applicationToCreateDto.getStaging()); - delegate.applicationsV3() - .scale(ScaleApplicationRequest.builder() - .applicationId(createdApplicationGuid.toString()) - .type("web") - .memoryInMb(applicationToCreateDto.getMemoryInMb()) - .diskInMb(applicationToCreateDto.getDiskQuotaInMb()) - .build()) - .block(); - if (!CollectionUtils.isEmpty(applicationToCreateDto.getRoutes())) { - addRoutes(applicationToCreateDto.getRoutes(), createdApplicationGuid); - } - } - - private void updateApplicationProcess(UUID applicationGuid, Staging staging) { - staging.getAppFeatures() - .entrySet() - .forEach(entry -> updateAppFeature(applicationGuid, entry.getKey(), entry.getValue())); - GetApplicationProcessResponse getApplicationProcessResponse = getApplicationProcessResource(applicationGuid); - UpdateProcessRequest.Builder updateProcessRequestBuilder = UpdateProcessRequest.builder() - .processId(getApplicationProcessResponse.getId()) - .command(staging.getCommand()); - if (staging.getHealthCheckType() != null) { - updateProcessRequestBuilder.healthCheck(buildHealthCheck(staging)); - } - if (staging.getReadinessHealthCheckType() != null) { - updateProcessRequestBuilder.readinessHealthCheck(buildReadinessHealthCheck(staging)); - } - delegate.processes() - .update(updateProcessRequestBuilder.build()) - .block(); - } - - private ReadinessHealthCheck buildReadinessHealthCheck(Staging staging) { - return ReadinessHealthCheck.builder() - .type(ReadinessHealthCheckType.from(staging.getReadinessHealthCheckType())) - .data(buildReadinessHealthCheckData(staging)) - .build(); - } - - private Data buildReadinessHealthCheckData(Staging staging) { - return Data.builder() - .invocationTimeout(staging.getReadinessHealthCheckInvocationTimeout()) - .endpoint(staging.getReadinessHealthCheckHttpEndpoint()) - .interval(staging.getReadinessHealthCheckInterval()) - .build(); - } - - private void updateAppFeature(UUID applicationGuid, String featureName, boolean enabled) { - delegate.applicationsV3() - .updateFeature(UpdateApplicationFeatureRequest.builder() - .featureName(featureName) - .enabled(enabled) - .applicationId(applicationGuid.toString()) - .build()) - .block(); - } - - private GetApplicationProcessResponse getApplicationProcessResource(UUID applicationGuid) { - return delegate.applicationsV3() - .getProcess(GetApplicationProcessRequest.builder() - .type("web") - .applicationId(applicationGuid.toString()) - .build()) - .block(); - } - - private HealthCheck buildHealthCheck(Staging staging) { - HealthCheckType healthCheckType = HealthCheckType.from(staging.getHealthCheckType()); - return HealthCheck.builder() - .type(healthCheckType) - .data(Data.builder() - .endpoint(staging.getHealthCheckHttpEndpoint()) - .timeout(staging.getHealthCheckTimeout()) - .invocationTimeout(staging.getInvocationTimeout()) - .interval(staging.getHealthCheckInterval()) - .build()) - .build(); - } - - private void addRoutes(Set routes, UUID applicationGuid) { - Map domains = getDomainsFromRoutes(routes); - for (CloudRoute route : routes) { - validateDomainForRoute(route, domains); - UUID domainGuid = domains.get(route.getDomain() - .getName()); - UUID routeGuid = getOrAddRoute(domainGuid, route.getHost(), route.getPath()); - bindRoute(routeGuid, applicationGuid, route.getRequestedProtocol()); - } - } - - @Override - public void createServiceInstance(CloudServiceInstance serviceInstance) { - assertSpaceProvided("create service instance"); - Assert.notNull(serviceInstance, "Service instance must not be null."); - CloudServicePlan servicePlan = findPlanForService(serviceInstance, serviceInstance.getPlan()); - UUID servicePlanGuid = servicePlan.getGuid(); - - delegate.serviceInstancesV3() - .create(CreateServiceInstanceRequest.builder() - .type(ServiceInstanceType.MANAGED) - .name(serviceInstance.getName()) - .metadata(toOssMetadata(serviceInstance.getV3Metadata())) - .relationships(ServiceInstanceRelationships.builder() - .servicePlan(buildToOneRelationship( - servicePlanGuid.toString())) - .space(buildToOneRelationship( - getTargetSpaceGuid().toString())) - .build()) - .tags(serviceInstance.getTags()) - .parameters(serviceInstance.getCredentials()) - .build()) - .block(); - } - - @Override - public String createServiceBroker(CloudServiceBroker serviceBroker) { - Assert.notNull(serviceBroker, "Service broker must not be null."); - - ServiceBrokerRelationships serviceBrokerRelationship = Optional.ofNullable(serviceBroker.getSpaceGuid()) - .map(UUID::fromString) - .map(this::buildToOneRelationship) - .map(spaceRelationship -> ServiceBrokerRelationships.builder() - .space( - spaceRelationship) - .build()) - .orElse(null); - - return delegate.serviceBrokersV3() - .create(CreateServiceBrokerRequest.builder() - .name(serviceBroker.getName()) - .url(serviceBroker.getUrl()) - .authentication(BasicAuthentication.builder() - .username(serviceBroker.getUsername()) - .password(serviceBroker.getPassword()) - .build()) - .relationships(serviceBrokerRelationship) - .build()) - .block(); - } - - @Override - public CloudServiceKey createAndFetchServiceKey(CloudServiceKey keyModel, String serviceInstanceName) { - CloudServiceInstance serviceInstance = getServiceInstance(serviceInstanceName); - doCreateServiceKeySync(keyModel.getName(), keyModel.getCredentials(), keyModel.getV3Metadata(), serviceInstance); - - return fetchWithAuxiliaryContent( - () -> getServiceKeyResourceByNameAndServiceInstanceGuid(keyModel.getName(), getGuid(serviceInstance)), - fetchedKey -> zipWithAuxiliaryServiceKeyContent(fetchedKey, serviceInstance)); - } - - @Override - public Optional createServiceKey(CloudServiceKey keyModel, String serviceInstanceName) { - CloudServiceInstance serviceInstance = getServiceInstance(serviceInstanceName); - return doCreateServiceKey(keyModel.getName(), keyModel.getCredentials(), keyModel.getV3Metadata(), serviceInstance); - } - - @Override - public Optional createServiceKey(String serviceInstanceName, String serviceKeyName, Map parameters) { - CloudServiceInstance serviceInstance = getServiceInstance(serviceInstanceName); - return doCreateServiceKey(serviceKeyName, parameters, null, serviceInstance); - } - - private void doCreateServiceKeySync(String name, Map parameters, Metadata metadata, - CloudServiceInstance serviceInstance) { - CreateServiceBindingRequest createServiceBindingRequest = buildServiceCredentialBindingRequest(name, parameters, metadata, - serviceInstance); - delegate.serviceBindingsV3() - .create(createServiceBindingRequest) - .map(response -> response.getJobId() - .get()) - .flatMap(jobId -> JobV3Util.waitForCompletion(delegate, BINDING_OPERATIONS_TIMEOUT, jobId)) - .block(); - } - - private Optional doCreateServiceKey(String name, Map parameters, Metadata metadata, - CloudServiceInstance serviceInstance) { - CreateServiceBindingRequest createServiceBindingRequest = buildServiceCredentialBindingRequest(name, parameters, metadata, - serviceInstance); - return delegate.serviceBindingsV3() - .create(createServiceBindingRequest) - .map(CreateServiceBindingResponse::getJobId) - .block(); - } - - private CreateServiceBindingRequest buildServiceCredentialBindingRequest(String name, Map parameters, Metadata metadata, - CloudServiceInstance serviceInstance) { - if (serviceInstance.getType() != org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType.MANAGED) { - throw new IllegalArgumentException( - String.format(Messages.CANT_CREATE_SERVICE_KEY_FOR_USER_PROVIDED_SERVICE, serviceInstance.getName())); - } - UUID serviceInstanceGuid = getGuid(serviceInstance); - - var createBindingRequest = CreateServiceBindingRequest.builder() - .type(ServiceBindingType.KEY) - .name(name) - .metadata(toOssMetadata(metadata)) - .relationships(ServiceBindingRelationships.builder() - .serviceInstance( - buildToOneRelationship( - serviceInstanceGuid)) - .build()); - if (!CollectionUtils.isEmpty(parameters)) { - createBindingRequest.parameters(parameters); - } - return createBindingRequest.build(); - } - - private static org.cloudfoundry.client.v3.Metadata toOssMetadata(Metadata metadata) { - if (metadata == null) { - return null; - } - return org.cloudfoundry.client.v3.Metadata.builder() - .labels(metadata.getLabels()) - .annotations(metadata.getAnnotations()) - .build(); - } - - @Override - public void createUserProvidedServiceInstance(CloudServiceInstance serviceInstance) { - assertSpaceProvided("create service instance"); - Assert.notNull(serviceInstance, "Service instance must not be null."); - String syslogDrainUrl = StringUtils.hasText(serviceInstance.getSyslogDrainUrl()) ? serviceInstance.getSyslogDrainUrl() : ""; - delegate.serviceInstancesV3() - .create(CreateServiceInstanceRequest.builder() - .name(serviceInstance.getName()) - .metadata(toOssMetadata(serviceInstance.getV3Metadata())) - .type(ServiceInstanceType.USER_PROVIDED) - .credentials(serviceInstance.getCredentials()) - .syslogDrainUrl(syslogDrainUrl) - .tags(serviceInstance.getTags()) - .relationships(ServiceInstanceRelationships.builder() - .space(buildToOneRelationship( - getTargetSpaceGuid().toString())) - .build()) - .build()) - .block(); - } - - @Override - public void deleteApplication(String applicationName) { - UUID applicationGuid = getRequiredApplicationGuid(applicationName); - deleteApplication(applicationGuid); - } - - private void deleteApplication(UUID applicationGuid) { - delegate.applicationsV3() - .delete(DeleteApplicationRequest.builder() - .applicationId(applicationGuid.toString()) - .build()) - .flatMap(jobId -> JobV3Util.waitForCompletion(delegate, DELETE_JOB_TIMEOUT, jobId)) - .block(); - } - - @Override - public void deleteDomain(String domainName) { - assertSpaceProvided("delete domain"); - CloudDomain domain = findDomainByName(domainName, true); - List routes = findRoutes(domain); - if (!routes.isEmpty()) { - throw new IllegalStateException("Unable to remove domain that is in use --" + " it has " + routes.size() + " routes."); - } - doDeleteDomain(getGuid(domain)); - } - - /** - * Delete routes that do not have any application which is assigned to them. - */ - @Override - public void deleteOrphanedRoutes() { - delegate.spacesV3() - .deleteUnmappedRoutes(DeleteUnmappedRoutesRequest.builder() - .spaceId(getTargetSpaceGuid().toString()) - .build()) - .flatMap(jobId -> JobV3Util.waitForCompletion(delegate, DELETE_JOB_TIMEOUT, jobId)) - .block(); - } - - @Override - public void deleteRoute(String host, String domainName, String path) { - assertSpaceProvided("delete route for domain"); - UUID routeGuid = getRouteGuid(getRequiredDomainGuid(domainName), host, path); - if (routeGuid == null) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", - "Host " + host + " not found for domain " + domainName + "."); - } - doDeleteRoute(routeGuid); - } - - @Override - public void deleteServiceInstance(String serviceInstanceName) { - CloudServiceInstance serviceInstance = getServiceInstanceWithoutAuxiliaryContent(serviceInstanceName); - doDeleteServiceInstance(serviceInstance.getGuid()); - } - - @Override - public void deleteServiceInstance(CloudServiceInstance serviceInstance) { - doDeleteServiceInstance(serviceInstance.getGuid()); - } - - @Override - public String deleteServiceBroker(String name) { - CloudServiceBroker broker = getServiceBroker(name); - UUID guid = broker.getMetadata() - .getGuid(); - return delegate.serviceBrokersV3() - .delete(DeleteServiceBrokerRequest.builder() - .serviceBrokerId(guid.toString()) - .build()) - .block(); - } - - @Override - public Optional deleteServiceBinding(String serviceInstanceName, String serviceKeyName) { - CloudServiceKey serviceKey = getServiceKey(serviceInstanceName, serviceKeyName); - if (serviceKey == null) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Service key " + serviceKeyName + " not found."); - } - return doDeleteServiceBindings(List.of(serviceKey.getGuid())).stream() - .findFirst(); - } - - @Override - public CloudApplication getApplication(String applicationName) { - return getApplication(applicationName, true); - } - - @Override - public CloudApplication getApplication(String applicationName, boolean required) { - return findApplicationByName(applicationName, required); - } - - @Override - public UUID getApplicationGuid(String applicationName) { - return getRequiredApplicationGuid(applicationName); - } - - @Override - public String getApplicationName(UUID applicationGuid) { - // This will throw a CloudOperationException with a 404 if no app with given GUID is present - return getApplicationByGuid(applicationGuid).block() - .getName(); - } - - @Override - public Map getApplicationEnvironment(UUID applicationGuid) { - return delegate.applicationsV3() - .getEnvironmentVariables(GetApplicationEnvironmentVariablesRequest.builder() - .applicationId(applicationGuid.toString()) - .build()) - .map(GetApplicationEnvironmentVariablesResponse::getVars) - .block(); - } - - @Override - public Map getApplicationEnvironment(String applicationName) { - UUID applicationGuid = getRequiredApplicationGuid(applicationName); - return getApplicationEnvironment(applicationGuid); - } - - @Override - public List getApplicationEvents(String applicationName) { - UUID applicationGuid = getRequiredApplicationGuid(applicationName); - return getEventsByTarget(applicationGuid); - } - - @Override - public List getEventsByTarget(UUID uuid) { - return findEventsByTarget(uuid.toString()); - } - - @Override - public InstancesInfo getApplicationInstances(CloudApplication application) { - if (application.getState() - .equals(CloudApplication.State.STARTED)) { - return findApplicationInstances(getGuid(application)); - } - return ImmutableInstancesInfo.builder() - .instances(Collections.emptyList()) - .build(); - } - - @Override - public InstancesInfo getApplicationInstances(UUID applicationGuid) { - return findApplicationInstances(applicationGuid); - } - - @Override - public CloudProcess getApplicationProcess(UUID applicationGuid) { - return fetch(() -> Mono.just(getApplicationProcessResource(applicationGuid)), ImmutableRawCloudProcess::of); - } - - @Override - public List getApplicationRoutes(UUID applicationGuid) { - return fetchList(() -> getRouteResourcesByAppGuid(applicationGuid), routeResource -> ImmutableRawCloudRoute.builder() - .route(routeResource) - .applicationGuid( - applicationGuid) - .build()); - } - - @Override - public boolean getApplicationSshEnabled(UUID applicationGuid) { - return delegate.applicationsV3() - .getSshEnabled(GetApplicationSshEnabledRequest.builder() - .applicationId(applicationGuid.toString()) - .build()) - .map(GetApplicationSshEnabledResponse::getEnabled) - .defaultIfEmpty(false) - .block(); - } - - @Override - public Map getApplicationFeatures(UUID applicationGuid) { - IntFunction pageRequestSupplier = page -> ListApplicationFeaturesRequest.builder() - .applicationId( - applicationGuid.toString()) - .page(page) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.applicationsV3() - .listFeatures(pageRequestSupplier.apply(page))) - .collect(Collectors.toMap(ApplicationFeature::getName, ApplicationFeature::getEnabled)) - .block(); - } - - @Override - public List getApplications() { - return fetchList(this::getApplicationResources, application -> ImmutableRawCloudApplication.builder() - .application(application) - .space(target) - .build()); - } - - @Override - public List getApplicationsByMetadataLabelSelector(String labelSelector) { - assertSpaceProvided("get applications"); - return fetchList(() -> getApplicationsByLabelSelector(labelSelector), application -> ImmutableRawCloudApplication.builder() - .application( - application) - .space(target) - .build()); - } - - private Flux getApplicationsByLabelSelector(String labelSelector) { - IntFunction pageRequestSupplier = page -> ListApplicationsRequest.builder() - .spaceId(getTargetSpaceGuid().toString()) - .labelSelector(labelSelector) - .page(page) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.applicationsV3() - .list(pageRequestSupplier.apply(page))); - } - - @Override - public CloudDomain getDefaultDomain() { - return fetch(() -> getDefaultDomainResource(getTargetOrganizationGuid().toString()), ImmutableRawCloudDomain::of); - } - - private Mono getDefaultDomainResource(String guid) { - return delegate.organizationsV3() - .getDefaultDomain(GetOrganizationDefaultDomainRequest.builder() - .organizationId(guid) - .build()); - } - - @Override - public List getSharedDomains() { - return fetchList(this::getSharedDomainResources, ImmutableRawCloudDomain::of); - } - - @Override - public List getDomains() { - return fetchList(this::getDomainResources, ImmutableRawCloudDomain::of); - } - - @Override - public List getDomainsForOrganization() { - assertSpaceProvided("access organization domains"); - return findDomainsByOrganizationGuid(getTargetOrganizationGuid()); - } - - @Override - public List getPrivateDomains() { - return fetchList(this::getPrivateDomainResources, ImmutableRawCloudDomain::of); - } - - @Override - public List getEvents() { - return fetchList(this::getEventResources, ImmutableRawCloudEvent::of); - } - - @Override - public List getRoutes(String domainName) { - assertSpaceProvided("get routes for domain"); - CloudDomain domain = findDomainByName(domainName, true); - return findRoutes(domain); - } - - @Override - public UUID getRequiredServiceInstanceGuid(String name) { - Resource serviceInstanceResource = getServiceInstanceByName(name).block(); - if (serviceInstanceResource == null) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Service instance " + name + " not found."); - } - return UUID.fromString(serviceInstanceResource.getId()); - } - - @Override - public CloudServiceInstance getServiceInstance(String serviceInstanceName) { - return getServiceInstance(serviceInstanceName, true); - } - - @Override - public CloudServiceInstance getServiceInstance(String serviceInstanceName, boolean required) { - CloudServiceInstance serviceInstance = findServiceInstanceByName(serviceInstanceName); - return getServiceInstanceIfRequired(serviceInstanceName, serviceInstance, required); - } - - private CloudServiceInstance getServiceInstanceIfRequired(String serviceInstanceName, CloudServiceInstance serviceInstance, - boolean required) { - if (serviceInstance == null && required) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Service instance " + serviceInstanceName + " not found."); - } - return serviceInstance; - } - - @Override - public CloudServiceBinding getServiceBinding(UUID serviceBindingGuid) { - // TODO: Refactor and use serviceBindingsV3.get() when https://github.com/cloudfoundry/cf-java-client/pull/1171 is merged and - // released - return fetch(() -> getServiceBindingResource(serviceBindingGuid), ImmutableRawCloudServiceBinding::of); - } - - private Mono getServiceBindingResource(UUID serviceBindingGuid) { - IntFunction pageRequestSupplier = page -> ListServiceBindingsRequest.builder() - .page(page) - .id(serviceBindingGuid.toString()) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.serviceBindingsV3() - .list(pageRequestSupplier.apply(page))) - .singleOrEmpty(); - } - - @Override - public List getServiceAppBindings(UUID serviceInstanceGuid) { - return fetchList(() -> getServiceBindingResourcesByServiceInstanceGuid(serviceInstanceGuid), ImmutableRawCloudServiceBinding::of); - } - - @Override - public List getAppBindings(UUID applicationGuid) { - return fetchList(() -> getServiceBindingResourcesByApplicationGuid(applicationGuid), ImmutableRawCloudServiceBinding::of); - } - - @Override - public List getServiceBindingsForApplication(UUID applicationId, UUID serviceInstanceGuid) { - return fetchList(() -> getServiceBindingResourcesByApplicationGuidAndServiceInstanceGuid(applicationId, serviceInstanceGuid), - ImmutableRawCloudServiceBinding::of); - } - - @Override - public CloudServiceBroker getServiceBroker(String name) { - return getServiceBroker(name, true); - } - - @Override - public CloudServiceBroker getServiceBroker(String name, boolean required) { - CloudServiceBroker serviceBroker = findServiceBrokerByName(name); - if (serviceBroker == null && required) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Service broker " + name + " not found."); - } - return serviceBroker; - } - - @Override - public List getServiceBrokers() { - return fetchList(this::getServiceBrokerResources, ImmutableRawCloudServiceBroker::of); - } - - @Override - public CloudServiceKey getServiceKey(String serviceInstanceName, String serviceKeyName) { - CloudServiceInstance serviceInstance = getServiceInstance(serviceInstanceName); - return fetchWithAuxiliaryContent(() -> getServiceKeyResourceByNameAndServiceInstanceGuid(serviceKeyName, serviceInstance.getGuid()), - serviceKey -> zipWithAuxiliaryServiceKeyContent(serviceKey, serviceInstance)); - } - - @Override - public List getServiceKeys(String serviceInstanceName) { - CloudServiceInstance serviceInstance = getServiceInstance(serviceInstanceName); - return getServiceKeys(serviceInstance); - } - - @Override - public List getServiceKeysWithCredentials(String serviceInstanceName) { - CloudServiceInstance serviceInstance = getServiceInstance(serviceInstanceName); - return getServiceKeysWithCredentials(serviceInstance); - } - - @Override - public List getServiceKeys(CloudServiceInstance serviceInstance) { - return fetchList(() -> getServiceKeyResource(serviceInstance), serviceKey -> ImmutableRawCloudServiceKey.builder() - .serviceInstance( - serviceInstance) - .serviceBindingResource( - serviceKey) - .build()); - } - - @Override - public List getServiceKeysWithCredentials(CloudServiceInstance serviceInstance) { - return fetchListWithAuxiliaryContent(() -> getServiceKeyResource(serviceInstance), - serviceKey -> zipWithAuxiliaryServiceKeyContent(serviceKey, serviceInstance)); - } - - private Mono> zipWithAuxiliaryServiceKeyContent(ServiceBindingResource key, - CloudServiceInstance serviceInstance) { - return getServiceKeyCredentials(key.getId()).map(credentials -> ImmutableRawCloudServiceKey.builder() - .serviceBindingResource(key) - .credentials(credentials) - .serviceInstance(serviceInstance) - .build()); - } - - private Mono> getServiceKeyCredentials(String keyGuid) { - return delegate.serviceBindingsV3() - .getDetails(GetServiceBindingDetailsRequest.builder() - .serviceBindingId(keyGuid) - .build()) - // CF V3 API returns 404 when fetching credentials of a service key which creation failed - .onErrorResume(t -> doesErrorMatchStatusCode(t, HttpStatus.NOT_FOUND), t -> Mono.just( - GetServiceBindingDetailsResponse.builder() - .volumeMounts(Collections.emptyList()) - .credentials(Collections.emptyMap()) - .build())) - .map(GetServiceBindingDetailsResponse::getCredentials); - } - - @Override - public Map getServiceInstanceParameters(UUID guid) { - return delegate.serviceInstancesV3() - .getManagedServiceParameters(GetManagedServiceParametersRequest.builder() - .serviceInstanceId(guid.toString()) - .build()) - .map(GetManagedServiceParametersResponse::getParameters) - .block(); - } - - @Override - public Map getUserProvidedServiceInstanceParameters(UUID guid) { - return delegate.serviceInstancesV3() - .getUserProvidedCredentials(GetUserProvidedCredentialsRequest.builder() - .serviceInstanceId(guid.toString()) - .build()) - .map(GetUserProvidedCredentialsResponse::getCredentials) - .block(); - } - - @Override - public Map getServiceBindingParameters(UUID guid) { - return delegate.serviceBindingsV3() - .getParameters(GetServiceBindingParametersRequest.builder() - .serviceBindingId(guid.toString()) - .build()) - .map(GetServiceBindingParametersResponse::getParameters) - .block(); - } - - @Override - public List getServiceOfferings() { - return fetchListWithAuxiliaryContent(this::getServiceResources, this::zipWithAuxiliaryServiceOfferingContent); - } - - @Override - public void updateServicePlan(String serviceName, String planName) { - CloudServiceInstance service = getServiceInstance(serviceName); - if (service.isUserProvided()) { - return; - } - CloudServicePlan plan = findPlanForService(service, planName); - delegate.serviceInstancesV3() - .update(UpdateServiceInstanceRequest.builder() - .serviceInstanceId(service.getGuid() - .toString()) - .relationships(ServiceInstanceRelationships.builder() - .servicePlan( - buildToOneRelationship(plan.getGuid())) - .build()) - .build()) - .block(); - } - - @Override - public String getServiceInstanceName(UUID serviceInstanceGuid) { - return getServiceInstanceByGuid(serviceInstanceGuid).block() - .getName(); - } - - @Override - public CloudServiceInstance getServiceInstanceWithoutAuxiliaryContent(String serviceInstanceName) { - return getServiceInstanceWithoutAuxiliaryContent(serviceInstanceName, true); - } - - @Override - public CloudServiceInstance getServiceInstanceWithoutAuxiliaryContent(String serviceInstanceName, boolean required) { - CloudServiceInstance serviceInstance = fetch(() -> getServiceInstanceResourceByName(serviceInstanceName), - ImmutableRawCloudServiceInstance::of); - return getServiceInstanceIfRequired(serviceInstanceName, serviceInstance, required); - } - - @Override - public void updateServiceParameters(String serviceName, Map parameters) { - CloudServiceInstance service = getServiceInstanceWithoutAuxiliaryContent(serviceName); - var updateServiceInstanceRequest = UpdateServiceInstanceRequest.builder() - .serviceInstanceId(service.getGuid() - .toString()); - if (service.isUserProvided()) { - updateServiceInstanceRequest.credentials(parameters); - } else { - updateServiceInstanceRequest.parameters(parameters); - } - delegate.serviceInstancesV3() - .update(updateServiceInstanceRequest.build()) - .block(); - } - - @Override - public void updateServiceTags(String serviceName, List tags) { - UUID serviceInstanceGuid = getRequiredServiceInstanceGuid(serviceName); - delegate.serviceInstancesV3() - .update(UpdateServiceInstanceRequest.builder() - .serviceInstanceId(serviceInstanceGuid.toString()) - .tags(tags) - .build()) - .block(); - } - - @Override - public void updateServiceSyslogDrainUrl(String serviceName, String syslogDrainUrl) { - CloudServiceInstance service = getServiceInstanceWithoutAuxiliaryContent(serviceName); - if (!service.isUserProvided()) { - return; - } - String updatedSyslogDrain = StringUtils.hasText(syslogDrainUrl) ? syslogDrainUrl : ""; - delegate.serviceInstancesV3() - .update(UpdateServiceInstanceRequest.builder() - .serviceInstanceId(service.getGuid() - .toString()) - .syslogDrainUrl(updatedSyslogDrain) - .build()) - .block(); - } - - private List> toBatches(Collection largeList, int maxCharLength) { - if (largeList.isEmpty()) { - return Collections.emptyList(); - } - - List> batches = new ArrayList<>(); - int currentBatchLength = 0, currentBatchIndex = 0; - batches.add(new ArrayList<>()); - - for (T element : largeList) { - int elementLength = element.toString() - .length(); - if (elementLength + currentBatchLength >= maxCharLength) { - batches.add(new ArrayList<>()); - currentBatchIndex++; - currentBatchLength = 0; - } - batches.get(currentBatchIndex) - .add(element); - currentBatchLength += elementLength; - } - return batches; - } - - private Flux getServiceInstancesByNames(List serviceInstanceNames) { - String spaceGuid = getTargetSpaceGuid().toString(); - IntFunction pageRequestSupplier = page -> ListServiceInstancesRequest.builder() - .spaceId(spaceGuid) - .addAllServiceInstanceNames( - serviceInstanceNames) - .page(page) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.serviceInstancesV3() - .list(pageRequestSupplier.apply(page))); - } - - @Override - public List getServiceInstancesWithoutAuxiliaryContentByNames(List names) { - List allServiceInstances = new ArrayList<>(); - for (List batchOfServiceInstanceNames : toBatches(names, MAX_CHAR_LENGTH_FOR_PARAMS_IN_REQUEST)) { - List serviceInstances = fetchList(() -> getServiceInstancesByNames(batchOfServiceInstanceNames), - ImmutableRawV3CloudServiceInstance::of); - allServiceInstances.addAll(serviceInstances); - } - return allServiceInstances; - } - - @Override - public List getServiceInstancesByMetadataLabelSelector(String labelSelector) { - IntFunction pageRequestSupplier = page -> ListServiceInstancesRequest.builder() - .labelSelector(labelSelector) - .spaceId( - getTargetSpaceGuid().toString()) - .page(page) - .build(); - - return fetchListWithAuxiliaryContent(() -> getServiceInstanceResources(pageRequestSupplier), - this::zipWithAuxiliaryServiceInstanceContent); - } - - @Override - public List getServiceInstancesWithoutAuxiliaryContentByMetadataLabelSelector(String labelSelector) { - IntFunction pageRequestSupplier = page -> ListServiceInstancesRequest.builder() - .labelSelector(labelSelector) - .spaceId( - getTargetSpaceGuid().toString()) - .page(page) - .build(); - - return fetchList(() -> getServiceInstanceResources(pageRequestSupplier), ImmutableRawCloudServiceInstance::of); - } - - @Override - public CloudStack getStack(String name) { - return getStack(name, true); - } - - @Override - public CloudStack getStack(String name, boolean required) { - CloudStack stack = findStackResource(name); - if (stack == null && required) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Stack " + name + " not found."); - } - return stack; - } - - @Override - public List getStacks() { - return fetchList(this::getStackResources, ImmutableRawCloudStack::of); - } - - @Override - public void rename(String applicationName, String newName) { - UUID applicationGuid = getRequiredApplicationGuid(applicationName); - delegate.applicationsV3() - .update(UpdateApplicationRequest.builder() - .applicationId(applicationGuid.toString()) - .name(newName) - .build()) - .onErrorResume(t -> doesErrorMatchStatusCode(t, HttpStatus.SERVICE_UNAVAILABLE), - exception -> fallbackApplicationRename(newName, applicationGuid, exception)) - .block(); - } - - private Mono fallbackApplicationRename(String newName, UUID applicationGuid, Throwable exception) { - String existingApplicationName = getApplicationByGuid(applicationGuid).block() - .getName(); - if (newName.equals(existingApplicationName)) { - return Mono.empty(); - } - return Mono.error(exception); - } - - @Override - public void restartApplication(String applicationName) { - stopApplication(applicationName); - startApplication(applicationName); - } - - @Override - public void startApplication(String applicationName) { - Application application = getApplicationByName(applicationName).block(); - if (application.getState() == ApplicationState.STARTED) { - return; - } - UUID applicationGuid = UUID.fromString(application.getId()); - delegate.applicationsV3() - .start(StartApplicationRequest.builder() - .applicationId(applicationGuid.toString()) - .build()) - .block(); - } - - @Override - public void stopApplication(String applicationName) { - Application application = getApplicationByName(applicationName).block(); - if (application.getState() == ApplicationState.STOPPED) { - return; - } - UUID applicationGuid = UUID.fromString(application.getId()); - delegate.applicationsV3() - .stop(StopApplicationRequest.builder() - .applicationId(applicationGuid.toString()) - .build()) - .block(); - } - - @Override - public List unbindServiceInstance(String applicationName, String serviceInstanceName) { - UUID applicationGuid = getRequiredApplicationGuid(applicationName); - UUID serviceInstanceGuid = getRequiredServiceInstanceGuid(serviceInstanceName); - - return doUnbindServiceInstance(applicationGuid, serviceInstanceGuid); - } - - @Override - public Optional deleteServiceBinding(UUID serviceBindingGuid) { - return doDeleteServiceBindings(List.of(serviceBindingGuid)).stream() - .findFirst(); - } - - @Override - public List unbindServiceInstance(UUID applicationGuid, UUID serviceInstanceGuid) { - return doUnbindServiceInstance(applicationGuid, serviceInstanceGuid); - } - - @Override - public void updateApplicationDiskQuota(String applicationName, int diskQuota) { - UUID applicationGuid = getRequiredApplicationGuid(applicationName); - delegate.applicationsV3() - .scale(ScaleApplicationRequest.builder() - .applicationId(applicationGuid.toString()) - .type("web") - .diskInMb(diskQuota) - .build()) - .block(); - } - - @Override - public void updateApplicationEnv(String applicationName, Map env) { - UUID applicationGuid = getRequiredApplicationGuid(applicationName); - delegate.applicationsV3() - .updateEnvironmentVariables(UpdateApplicationEnvironmentVariablesRequest.builder() - .applicationId(applicationGuid.toString()) - .vars(env) - .build()) - .block(); - } - - @Override - public void updateApplicationInstances(String applicationName, int instances) { - UUID applicationGuid = getRequiredApplicationGuid(applicationName); - delegate.applicationsV3() - .scale(ScaleApplicationRequest.builder() - .applicationId(applicationGuid.toString()) - .type("web") - .instances(instances) - .build()) - .block(); - } - - @Override - public void updateApplicationMemory(String applicationName, int memory) { - UUID applicationGuid = getRequiredApplicationGuid(applicationName); - delegate.applicationsV3() - .scale(ScaleApplicationRequest.builder() - .applicationId(applicationGuid.toString()) - .type("web") - .memoryInMb(memory) - .build()) - .block(); - } - - @Override - public void updateApplicationMetadata(UUID guid, Metadata metadata) { - delegate.applicationsV3() - .update(org.cloudfoundry.client.v3.applications.UpdateApplicationRequest.builder() - .applicationId(guid.toString()) - .metadata(toOssMetadata(metadata)) - .build()) - .block(); - } - - @Override - public void updateApplicationStaging(String applicationName, Staging staging) { - UUID applicationGuid = getRequiredApplicationGuid(applicationName); - delegate.applicationsV3() - .update(UpdateApplicationRequest.builder() - .applicationId(applicationGuid.toString()) - .lifecycle(buildApplicationLifecycle(staging)) - .build()) - .block(); - updateApplicationProcess(applicationGuid, staging); - } - - @Override - public void updateApplicationRoutes(String applicationName, Set updatedRoutes) { - UUID applicationGuid = getApplicationGuid(applicationName); - List appRoutes = fetchList(() -> getRouteResourcesByAppGuid(applicationGuid), ImmutableRawCloudRoute::of); - Set outdatedRoutes = getOutdatedRoutes(applicationGuid, appRoutes, updatedRoutes); - Set newRoutes = getNewRoutes(applicationGuid, appRoutes, updatedRoutes); - removeRoutes(outdatedRoutes, applicationGuid); - addRoutes(newRoutes, applicationGuid); - } - - private Set getOutdatedRoutes(UUID applicationGuid, List currentRoutes, Set updatedRoutes) { - return currentRoutes.stream() - .filter(currentRoute -> isRouteOutdated(applicationGuid, currentRoute, updatedRoutes)) - .collect(Collectors.toSet()); - } - - private boolean isRouteOutdated(UUID applicationGuid, CloudRoute currentRoute, Set updatedRoutes) { - Optional updatedRoute = findRoute(currentRoute.getUrl(), updatedRoutes); - if (updatedRoute.isEmpty()) { - return true; - } - return isProtocolChanged(applicationGuid, currentRoute, updatedRoute.get()); - } - - private Optional findRoute(String url, Collection routes) { - return routes.stream() - .filter(route -> Objects.equals(url, route.getUrl())) - .findFirst(); - } - - private boolean isProtocolChanged(UUID applicationGuid, CloudRoute currentRoute, CloudRoute updatedRoute) { - if (updatedRoute.getRequestedProtocol() == null) { - return false; - } - return currentRoute.getDestinations() - .stream() - .filter(routeDestination -> Objects.equals(routeDestination.getApplicationGuid(), applicationGuid)) - .noneMatch( - routeDestination -> Objects.equals(routeDestination.getProtocol(), updatedRoute.getRequestedProtocol())); - } - - private Set getNewRoutes(UUID applicationGuid, List currentRoutes, Set updatedRoutes) { - return updatedRoutes.stream() - .filter(updatedRoute -> isRouteUpdated(applicationGuid, updatedRoute, currentRoutes)) - .collect(Collectors.toSet()); - } - - private boolean isRouteUpdated(UUID applicationGuid, CloudRoute updatedRoute, List currentRoutes) { - Optional currentRoute = findRoute(updatedRoute.getUrl(), currentRoutes); - if (currentRoute.isEmpty()) { - return true; - } - return isProtocolChanged(applicationGuid, currentRoute.get(), updatedRoute); - } - - @Override - public String updateServiceBroker(CloudServiceBroker serviceBroker) { - Assert.notNull(serviceBroker, "Service broker must not be null."); - - CloudServiceBroker existingBroker = getServiceBroker(serviceBroker.getName()); - UUID brokerGuid = existingBroker.getMetadata() - .getGuid(); - - return delegate.serviceBrokersV3() - .update(UpdateServiceBrokerRequest.builder() - .serviceBrokerId(brokerGuid.toString()) - .name(serviceBroker.getName()) - .authentication(BasicAuthentication.builder() - .username(serviceBroker.getUsername()) - .password(serviceBroker.getPassword()) - .build()) - .url(serviceBroker.getUrl()) - .build()) - .flatMap(response -> Mono.justOrEmpty(response.jobId())) - .block(); - } - - @Override - public void updateServiceInstanceMetadata(UUID guid, Metadata metadata) { - delegate.serviceInstancesV3() - .update(UpdateServiceInstanceRequest.builder() - .serviceInstanceId(guid.toString()) - .metadata(toOssMetadata(metadata)) - .build()) - .block(); - } - - @Override - public void updateServiceBindingMetadata(UUID guid, Metadata metadata) { - delegate.serviceBindingsV3() - .update(UpdateServiceBindingRequest.builder() - .serviceBindingId(guid.toString()) - .metadata(toOssMetadata(metadata)) - .build()) - .block(); - } - - @Override - public void updateServicePlanVisibilityForBroker(String name, ServicePlanVisibility visibility) { - CloudServiceBroker broker = getServiceBroker(name); - List servicePlans = findServicePlansByBrokerGuid(getGuid(broker)); - for (CloudServicePlan servicePlan : servicePlans) { - updateServicePlanVisibility(servicePlan, visibility); - } - } - - @Override - public CloudTask getTask(UUID taskGuid) { - return fetch(() -> getTaskResource(taskGuid), ImmutableRawCloudTask::of); - } - - @Override - public List getTasks(String applicationName) { - UUID applicationGuid = getRequiredApplicationGuid(applicationName); - return fetchList(() -> getTaskResourcesByApplicationGuid(applicationGuid), ImmutableRawCloudTask::of); - } - - @Override - public CloudTask runTask(String applicationName, CloudTask task) { - UUID applicationGuid = getRequiredApplicationGuid(applicationName); - return createTask(applicationGuid, task); - } - - @Override - public CloudTask cancelTask(UUID taskGuid) { - return fetch(() -> cancelTaskResource(taskGuid), ImmutableRawCloudTask::of); - } - - @Override - public CloudPackage asyncUploadApplication(String applicationName, Path file, UploadStatusCallback callback, Duration uploadTimeout) { - CloudPackage cloudPackage = startUpload(applicationName, file, uploadTimeout); - processAsyncUploadInBackground(cloudPackage, callback); - return cloudPackage; - } - - @Override - public Upload getUploadStatus(UUID packageGuid) { - CloudPackage cloudPackage = getPackage(packageGuid); - ErrorDetails errorDetails = null; - if (cloudPackage.getType() == CloudPackage.Type.BITS) { - errorDetails = ImmutableErrorDetails.builder() - .description(((BitsData) cloudPackage.getData()).getError()) - .build(); - } - return ImmutableUpload.builder() - .status(cloudPackage.getStatus()) - .errorDetails(errorDetails) - .build(); - } - - @Override - public CloudBuild getBuild(UUID buildGuid) { - return fetch(() -> getBuildResource(buildGuid), ImmutableRawCloudBuild::of); - } - - @Override - public List getBuildsForApplication(UUID applicationGuid) { - return fetchList(() -> getBuildResourcesByApplicationGuid(applicationGuid), ImmutableRawCloudBuild::of); - } - - @Override - public List getBuildsForPackage(UUID packageGuid) { - return fetchList(() -> getBuildResourcesByPackageGuid(packageGuid), ImmutableRawCloudBuild::of); - } - - @Override - public CloudBuild createBuild(UUID packageGuid) { - return fetch(() -> createBuildResource(packageGuid), ImmutableRawCloudBuild::of); - } - - @Override - public void bindDropletToApp(UUID dropletGuid, UUID applicationGuid) { - delegate.applicationsV3() - .setCurrentDroplet(SetApplicationCurrentDropletRequest.builder() - .applicationId(applicationGuid.toString()) - .data(Relationship.builder() - .id(dropletGuid.toString()) - .build()) - .build()) - .block(); - } - - @Override - public DropletInfo getCurrentDropletForApplication(UUID applicationGuid) { - GetApplicationCurrentDropletResponse dropletResponse = delegate.applicationsV3() - .getCurrentDroplet(GetApplicationCurrentDropletRequest.builder() - .applicationId( - applicationGuid.toString()) - .build()) - .block(); - if (dropletResponse == null) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not found", - "Application with guid " + applicationGuid + " does not have a droplet"); - } - return parseDropletInfo(dropletResponse); - } - - private DropletInfo parseDropletInfo(GetApplicationCurrentDropletResponse getApplicationCurrentDropletResponse) { - String packageUrl = getApplicationCurrentDropletResponse.getLinks() - .get(Constants.PACKAGE) - .getHref(); - if (packageUrl.endsWith("/")) { - packageUrl = packageUrl.substring(0, packageUrl.lastIndexOf("/")); - } - String packageGuid = packageUrl.substring(packageUrl.lastIndexOf("/") + 1); - return ImmutableDropletInfo.builder() - .guid(UUID.fromString(getApplicationCurrentDropletResponse.getId())) - .packageGuid(UUID.fromString(packageGuid)) - .build(); - } - - @Override - public List getPackagesForApplication(UUID applicationGuid) { - return fetchList(() -> getPackages(applicationGuid.toString()), ImmutableRawCloudPackage::of); - } - - private Flux getPackages(String applicationGuid) { - IntFunction pageRequestSupplier = page -> ListApplicationPackagesRequest.builder() - .page(page) - .applicationId( - applicationGuid) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.applicationsV3() - .listPackages(pageRequestSupplier.apply(page))); - } - - @Override - public Set getUserRolesBySpaceAndUser(UUID spaceGuid, UUID userGuid) { - Supplier> setFactory = () -> EnumSet.noneOf(UserRole.class); - return fetchFlux(() -> getRoles(spaceGuid, userGuid), ImmutableRawUserRole::of).collect(Collectors.toCollection(setFactory)) - .block(); - } - - public CloudPackage createDockerPackage(UUID applicationGuid, DockerInfo dockerInfo) { - org.cloudfoundry.client.v3.packages.DockerData.Builder dockerDataBuilder = org.cloudfoundry.client.v3.packages.DockerData.builder() - .image( - dockerInfo.getImage()); - if (dockerInfo.getCredentials() != null) { - addNonNullDockerCredentials(dockerInfo.getCredentials(), dockerDataBuilder); - } - CreatePackageRequest packageRequest = CreatePackageRequest.builder() - .type(PackageType.DOCKER) - .data(dockerDataBuilder.build()) - .relationships(buildPackageRelationships(applicationGuid)) - .build(); - CreatePackageResponse packageResponse = delegate.packages() - .create(packageRequest) - .block(); - return getPackage(UUID.fromString(packageResponse.getId())); - } - - @Override - public CloudAsyncJob getAsyncJob(String jobId) { - return fetch(() -> delegate.jobsV3() - .get(GetJobRequest.builder() - .jobId(jobId) - .build()), ImmutableRawCloudAsyncJob::of); - } - - private void addNonNullDockerCredentials(DockerCredentials dockerCredentials, - org.cloudfoundry.client.v3.packages.DockerData.Builder dockerDataBuilder) { - String username = dockerCredentials.getUsername(); - if (username != null) { - dockerDataBuilder.username(username); - } - String password = dockerCredentials.getPassword(); - if (password != null) { - dockerDataBuilder.password(password); - } - } - - private Flux getRoles(UUID spaceGuid, UUID userGuid) { - IntFunction pageRequestSupplier = page -> ListRolesRequest.builder() - .page(page) - .spaceId(spaceGuid.toString()) - .userId(userGuid.toString()) - .types(RoleType.values()) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.rolesV3() - .list(pageRequestSupplier.apply(page))); - } - - private CloudApplication findApplicationByName(String name, boolean required) { - CloudApplication application = findApplicationByName(name); - if (application == null && required) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Application " + name + " not found."); - } - return application; - } - - private CloudApplication findApplicationByName(String name) { - return fetch(() -> getApplicationByName(name), application -> ImmutableRawCloudApplication.builder() - .application(application) - .space(target) - .build()); - } - - private Flux getApplicationResources() { - assertSpaceProvided("get application"); - IntFunction pageRequestSupplier = page -> ListApplicationsRequest.builder() - .spaceId(getTargetSpaceGuid().toString()) - .page(page) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.applicationsV3() - .list(pageRequestSupplier.apply(page))); - } - - private Mono getApplicationByGuid(UUID guid) { - return delegate.applicationsV3() - .get(GetApplicationRequest.builder() - .applicationId(guid.toString()) - .build()); - } - - private Mono getApplicationByName(String name) { - assertSpaceProvided("get application"); - IntFunction pageRequestSupplier = page -> ListApplicationsRequest.builder() - .spaceId(getTargetSpaceGuid().toString()) - .name(name) - .page(page) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.applicationsV3() - .list(pageRequestSupplier.apply(page))) - .singleOrEmpty(); - } - - private CloudServiceInstance findServiceInstanceByName(String name) { - return fetchWithAuxiliaryContent(() -> getServiceInstanceResourceByName(name), this::zipWithAuxiliaryServiceInstanceContent); - } - - private Mono getServiceInstanceByGuid(UUID serviceInstanceGuid) { - return delegate.serviceInstancesV3() - .get(GetServiceInstanceRequest.builder() - .serviceInstanceId(serviceInstanceGuid.toString()) - .build()); - } - - private Mono getServiceInstanceResourceByName(String name) { - IntFunction pageRequestSupplier = page -> ListServiceInstancesRequest.builder() - .spaceId( - getTargetSpaceGuid().toString()) - .serviceInstanceName(name) - .page(page) - .build(); - return getServiceInstanceResources(pageRequestSupplier).singleOrEmpty(); - } - - private Flux getServiceInstanceResources(IntFunction pageRequestSupplier) { - return PaginationUtils.requestClientV3Resources(page -> delegate.serviceInstancesV3() - .list(pageRequestSupplier.apply(page))); - } - - private Mono> zipWithAuxiliaryServiceInstanceContent(ServiceInstanceResource serviceInstanceResource) { - if (isUserProvided(serviceInstanceResource)) { - return Mono.just(ImmutableRawCloudServiceInstance.of(serviceInstanceResource)); - } - String servicePlanGuid = serviceInstanceResource.getRelationships() - .getServicePlan() - .getData() - .getId(); - - return getServicePlanResource(servicePlanGuid, serviceInstanceResource.getName()).zipWhen(servicePlan -> getServiceOffering( - servicePlan.getRelationships() - .getServiceOffering() - .getData() - .getId())) - .map( - tuple -> ImmutableRawCloudServiceInstance.builder() - .resource( - serviceInstanceResource) - .servicePlan( - tuple.getT1()) - .serviceOffering( - tuple.getT2()) - .build()); - } - - private boolean isUserProvided(ServiceInstanceResource serviceInstanceResource) { - return ServiceInstanceType.USER_PROVIDED.equals(serviceInstanceResource.getType()); - } - - private Flux getServiceBindingResourcesByServiceInstanceGuid(UUID serviceInstanceGuid) { - IntFunction pageRequestSupplier = page -> ListServiceBindingsRequest.builder() - .serviceInstanceId( - serviceInstanceGuid.toString()) - .type( - ServiceBindingType.APPLICATION) - .page(page) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.serviceBindingsV3() - .list(pageRequestSupplier.apply(page))); - } - - private Flux getServiceBindingResourcesByApplicationGuidAndServiceInstanceGuid(UUID applicationGuid, - UUID serviceInstanceGuid) { - IntFunction pageRequestSupplier = page -> ListServiceBindingsRequest.builder() - .applicationId( - applicationGuid.toString()) - .serviceInstanceId( - serviceInstanceGuid.toString()) - .page(page) - .build(); - return getApplicationServiceBindingResources(pageRequestSupplier); - } - - private Flux getServiceBindingResourcesByApplicationGuid(UUID applicationGuid) { - IntFunction pageRequestSupplier = page -> ListServiceBindingsRequest.builder() - .applicationId( - applicationGuid.toString()) - .page(page) - .build(); - return getApplicationServiceBindingResources(pageRequestSupplier); - } - - private Flux getApplicationServiceBindingResources( - IntFunction pageRequestSupplier) { - return PaginationUtils.requestClientV3Resources(page -> delegate.serviceBindingsV3() - .list(pageRequestSupplier.apply(page))); - } - - private List findServicePlansByBrokerGuid(UUID brokerGuid) { - List offerings = findServiceOfferingsByBrokerGuid(brokerGuid); - return getPlans(offerings); - } - - private List getPlans(List offerings) { - return offerings.stream() - .map(CloudServiceOffering::getServicePlans) - .flatMap(Collection::stream) - .collect(Collectors.toList()); - } - - private void updateServicePlanVisibility(CloudServicePlan servicePlan, ServicePlanVisibility visibility) { - updateServicePlanVisibility(getGuid(servicePlan), visibility); - } - - private void updateServicePlanVisibility(UUID servicePlanGuid, ServicePlanVisibility visibility) { - delegate.servicePlansV3() - .updateVisibility(UpdateServicePlanVisibilityRequest.builder() - .servicePlanId(servicePlanGuid.toString()) - .type(Visibility.from(visibility.toString())) - .build()) - .block(); - } - - private Mono getTaskResource(UUID guid) { - GetTaskRequest request = GetTaskRequest.builder() - .taskId(guid.toString()) - .build(); - return delegate.tasks() - .get(request); - } - - private Flux getTaskResourcesByApplicationGuid(UUID applicationGuid) { - IntFunction pageRequestSupplier = page -> ListTasksRequest.builder() - .applicationId(applicationGuid.toString()) - .page(page) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.tasks() - .list(pageRequestSupplier.apply(page))); - } - - private CloudTask createTask(UUID applicationGuid, CloudTask task) { - return fetch(() -> createTaskResource(applicationGuid, task), ImmutableRawCloudTask::of); - } - - private Mono createTaskResource(UUID applicationGuid, CloudTask task) { - CreateTaskRequest request = CreateTaskRequest.builder() - .applicationId(applicationGuid.toString()) - .command(task.getCommand()) - .name(task.getName()) - .memoryInMb(task.getLimits() - .getMemory()) - .diskInMb(task.getLimits() - .getDisk()) - .build(); - return delegate.tasks() - .create(request); - } - - private Mono cancelTaskResource(UUID taskGuid) { - CancelTaskRequest request = CancelTaskRequest.builder() - .taskId(taskGuid.toString()) - .build(); - return delegate.tasks() - .cancel(request); - } - - private CloudPackage startUpload(String applicationName, Path file, Duration uploadTimeout) { - Assert.notNull(applicationName, "AppName must not be null"); - Assert.notNull(file, "File must not be null"); - - UUID applicationGuid = getRequiredApplicationGuid(applicationName); - UUID packageGuid = getGuid(createPackageForApplication(applicationGuid)); - - delegate.packages() - .upload(UploadPackageRequest.builder() - .bits(file) - .packageId(packageGuid.toString()) - .build()) - .timeout(uploadTimeout) - .block(); - - return getPackage(packageGuid); - } - - private CloudPackage createPackageForApplication(UUID applicationGuid) { - return fetch(() -> createPackageResource(applicationGuid), ImmutableRawCloudPackage::of); - } - - private Mono createPackageResource(UUID applicationGuid) { - CreatePackageRequest request = CreatePackageRequest.builder() - .type(PackageType.BITS) - .relationships(buildPackageRelationships(applicationGuid)) - .build(); - return delegate.packages() - .create(request); - } - - private PackageRelationships buildPackageRelationships(UUID applicationGuid) { - return PackageRelationships.builder() - .application(buildToOneRelationship(applicationGuid)) - .build(); - } - - private ToOneRelationship buildToOneRelationship(UUID guid) { - return ToOneRelationship.builder() - .data(buildRelationship(guid.toString())) - .build(); - } - - private ToOneRelationship buildToOneRelationship(String guid) { - return ToOneRelationship.builder() - .data(buildRelationship(guid)) - .build(); - } - - private Relationship buildRelationship(String guid) { - return Relationship.builder() - .id(guid) - .build(); - } - - private Mono createBuildResource(UUID packageGuid) { - CreateBuildRequest request = CreateBuildRequest.builder() - .getPackage(buildRelationship(packageGuid.toString())) - .build(); - return delegate.builds() - .create(request); - } - - private Mono getBuildResource(UUID buildGuid) { - GetBuildRequest request = GetBuildRequest.builder() - .buildId(buildGuid.toString()) - .build(); - return delegate.builds() - .get(request); - } - - private Flux getBuildResourcesByApplicationGuid(UUID applicationGuid) { - IntFunction pageRequestSupplier = page -> ListApplicationBuildsRequest.builder() - .applicationId( - applicationGuid.toString()) - .page(page) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.applicationsV3() - .listBuilds(pageRequestSupplier.apply(page))); - } - - private Flux getBuildResourcesByPackageGuid(UUID packageGuid) { - IntFunction pageRequestSupplier = page -> ListBuildsRequest.builder() - .packageId(packageGuid.toString()) - .page(page) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.builds() - .list(pageRequestSupplier.apply(page))); - } - - private void assertSpaceProvided(String operation) { - Assert.notNull(target, "Unable to " + operation + " without specifying organization and space to use."); - } - - private void removeRoutes(Set routes, UUID applicationGuid) { - for (CloudRoute route : routes) { - for (RouteDestination destination : route.getDestinations()) { - if (destination.getApplicationGuid() - .equals(applicationGuid)) { - unbindRoute(route.getGuid(), destination.getGuid()); - } - } - } - } - - private void validateDomainForRoute(CloudRoute route, Map existingDomains) { - String domain = route.getDomain() - .getName(); - if (!StringUtils.hasLength(domain) || !existingDomains.containsKey(domain)) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), - "Domain '" + domain + "' not found for URI " + route.getUrl()); - } - } - - private void unbindRoute(UUID routeGuid, UUID destinationGuid) { - delegate.routesV3() - .removeDestinations(RemoveRouteDestinationsRequest.builder() - .routeId(routeGuid.toString()) - .destinationId(destinationGuid.toString()) - .build()) - .block(); - } - - private void bindRoute(UUID routeGuid, UUID applicationGuid, String protocol) { - delegate.routesV3() - .insertDestinations(InsertRouteDestinationsRequest.builder() - .routeId(routeGuid.toString()) - .destination(createDestination(applicationGuid, protocol)) - .build()) - .block(); - } - - private Destination createDestination(UUID applicationGuid, String protocol) { - return Destination.builder() - .application(org.cloudfoundry.client.v3.routes.Application.builder() - .applicationId(applicationGuid.toString()) - .build()) - .protocol(protocol) - .build(); - } - - private UUID getOrAddRoute(UUID domainGuid, String host, String path) { - UUID routeGuid = getRouteGuid(domainGuid, host, path); - if (routeGuid == null) { - routeGuid = doAddRoute(domainGuid, host, path); - } - return routeGuid; - } - - private UUID doAddRoute(UUID domainGuid, String host, String path) { - assertSpaceProvided("add route"); - CreateRouteResponse response = delegate.routesV3() - .create(CreateRouteRequest.builder() - .host(host) - .path(path) - .relationships(RouteRelationships.builder() - .domain(buildToOneRelationship( - domainGuid)) - .space(buildToOneRelationship( - getTargetSpaceGuid())) - .build()) - .build()) - .block(); - return getGuid(response); - } - - private void doCreateDomain(String name) { - delegate.domainsV3() - .create(CreateDomainRequest.builder() - .name(name) - .relationships(DomainRelationships.builder() - .organization( - buildToOneRelationship(getTargetOrganizationGuid())) - .build()) - .build()) - .block(); - } - - private void doDeleteDomain(UUID guid) { - delegate.domainsV3() - .delete(DeleteDomainRequest.builder() - .domainId(guid.toString()) - .build()) - .flatMap(jobId -> JobV3Util.waitForCompletion(delegate, DELETE_JOB_TIMEOUT, jobId)) - .block(); - } - - private void doDeleteRoute(UUID guid) { - delegate.routesV3() - .delete(DeleteRouteRequest.builder() - .routeId(guid.toString()) - .build()) - .flatMap(jobId -> JobV3Util.waitForCompletion(delegate, DELETE_JOB_TIMEOUT, jobId)) - .block(); - } - - private void doDeleteServiceInstance(UUID serviceInstanceGuid) { - delegate.serviceInstancesV3() - .delete(DeleteServiceInstanceRequest.builder() - .serviceInstanceId(serviceInstanceGuid.toString()) - .build()) - .block(); - } - - private List doUnbindServiceInstance(UUID applicationGuid, UUID serviceInstanceGuid) { - List serviceBindingGuids = getServiceBindingGuids(applicationGuid, serviceInstanceGuid); - if (serviceBindingGuids.isEmpty()) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", - "Service binding between service with GUID " + serviceInstanceGuid - + " and application with GUID " + applicationGuid + " not found."); - } - return doDeleteServiceBindings(serviceBindingGuids); - } - - private List doDeleteServiceBindings(List guids) { - List jobIds = new ArrayList<>(); - List errors = new ArrayList<>(); - for (UUID guid : guids) { - try { - Optional.ofNullable(deleteSingleServiceBinding(guid)) - .ifPresent(jobIds::add); - } catch (AbstractCloudFoundryException e) { - errors.add(e); - } - } - throwOnErrors(errors); - return jobIds; - } - - private String deleteSingleServiceBinding(UUID guid) { - return delegate.serviceBindingsV3() - .delete(DeleteServiceBindingRequest.builder() - .serviceBindingId(guid.toString()) - .build()) - .block(); - } - - private void throwOnErrors(List errors) { - if (errors.isEmpty()) { - return; - } - RuntimeException first = errors.get(0); - errors.subList(1, errors.size()) - .forEach(first::addSuppressed); - throw first; - } - - private CloudDomain findDomainByName(String name, boolean required) { - CloudDomain domain = findDomainByName(name); - if (domain == null && required) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Domain " + name + " not found."); - } - return domain; - } - - private CloudDomain findDomainByName(String name) { - return fetch(() -> getDomainResourceByName(name), ImmutableRawCloudDomain::of); - } - - private List findDomainsByOrganizationGuid(UUID organizationGuid) { - return fetchList(() -> getDomainResourcesByOrganizationGuid(organizationGuid), ImmutableRawCloudDomain::of); - } - - private Mono getDomainResourceByName(String name) { - IntFunction pageRequestSupplier = page -> ListDomainsRequest.builder() - .name(name) - .page(page) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.domainsV3() - .list(pageRequestSupplier.apply(page))) - .singleOrEmpty(); - } - - private Flux getDomainResources() { - IntFunction pageRequestSupplier = page -> ListDomainsRequest.builder() - .page(page) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.domainsV3() - .list(pageRequestSupplier.apply(page))); - } - - private Flux getSharedDomainResources() { - return getDomainResources().filter(domain -> domain.getRelationships() - .getOrganization() - .getData() == null); - } - - private Flux getPrivateDomainResources() { - return getDomainResources().filter(domain -> domain.getRelationships() - .getOrganization() - .getData() != null); - } - - private Flux getDomainResourcesByOrganizationGuid(UUID organizationGuid) { - IntFunction pageRequestSupplier = page -> ListOrganizationDomainsRequest.builder() - .organizationId( - organizationGuid.toString()) - .page(page) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.organizationsV3() - .listDomains(pageRequestSupplier.apply(page))); - } - - private Flux getDomainResourcesByNamesInBatches(Set names) { - return Flux.fromIterable(toBatches(names, MAX_CHAR_LENGTH_FOR_PARAMS_IN_REQUEST)) - .flatMap(this::getDomainResourcesByNames); - } - - private Flux getDomainResourcesByNames(Collection names) { - if (names.isEmpty()) { - return Flux.empty(); - } - IntFunction pageRequestSupplier = page -> ListDomainsRequest.builder() - .names(names) - .page(page) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.domainsV3() - .list(pageRequestSupplier.apply(page))); - } - - private List findRoutes(CloudDomain domain) { - return fetchList(() -> getRouteResourcesByDomainGuidAndSpaceGuid(domain.getGuid(), getTargetSpaceGuid()), - ImmutableRawCloudRoute::of); - } - - private Flux getRouteResourcesByDomainGuidAndSpaceGuid(UUID domainGuid, UUID spaceGuid) { - IntFunction pageRequestSupplier = page -> ListRoutesRequest.builder() - .domainId(domainGuid.toString()) - .spaceId(spaceGuid.toString()) - .page(page) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.routesV3() - .list(pageRequestSupplier.apply(page))); - } - - private Flux getRouteResourcesByDomainGuidHostAndPath(UUID domainGuid, String host, String path) { - ListRoutesRequest.Builder requestBuilder = ListRoutesRequest.builder(); - if (host != null) { - requestBuilder.host(host); - } - if (path != null) { - requestBuilder.path(path); - } - requestBuilder.spaceId(getTargetSpaceGuid().toString()) - .domainId(domainGuid.toString()); - - return PaginationUtils.requestClientV3Resources(page -> delegate.routesV3() - .list(requestBuilder.page(page) - .build())); - } - - private Flux getRouteResourcesByAppGuid(UUID applicationGuid) { - IntFunction pageSupplier = page -> ListApplicationRoutesRequest.builder() - .applicationId( - applicationGuid.toString()) - .page(page) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.applicationsV3() - .listRoutes(pageSupplier.apply(page))); - } - - private List findServiceOfferingsByBrokerGuid(UUID brokerGuid) { - return fetchListWithAuxiliaryContent(() -> getServiceResourcesByBrokerGuid(brokerGuid), - this::zipWithAuxiliaryServiceOfferingContent); - } - - private List findServiceOfferingsByLabel(String label) { - Assert.notNull(label, "Service label must not be null"); - return fetchListWithAuxiliaryContent(() -> getServiceResourcesByLabel(label), this::zipWithAuxiliaryServiceOfferingContent); - } - - private List findServiceOfferingsByLabelAndBrokerName(String label, String brokerName) { - Assert.notNull(label, "Service label must not be null"); - Assert.notNull(brokerName, "Service broker must not be null"); - return fetchListWithAuxiliaryContent(() -> getServiceResourcesByLabelAndBrokerName(label, brokerName), - this::zipWithAuxiliaryServiceOfferingContent); - } - - private Flux getServiceResources() { - IntFunction pageRequestSupplier = page -> ListServiceOfferingsRequest.builder() - .spaceId( - getTargetSpaceGuid().toString()) - .page(page) - .build(); - return getServiceResources(pageRequestSupplier); - } - - protected Mono getServiceOffering(String offeringId) { - GetServiceOfferingRequest request = GetServiceOfferingRequest.builder() - .serviceOfferingId(offeringId) - .build(); - return delegate.serviceOfferingsV3() - .get(request) - .onErrorMap(t -> doesErrorMatchStatusCode(t, HttpStatus.FORBIDDEN), - t -> new CloudOperationException(HttpStatus.FORBIDDEN, HttpStatus.FORBIDDEN.getReasonPhrase(), - MessageFormat.format( - Messages.SERVICE_OFFERING_WITH_GUID_0_IS_NOT_AVAILABLE, offeringId), - t)) - .onErrorMap(t -> doesErrorMatchStatusCode(t, HttpStatus.NOT_FOUND), - t -> new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), - MessageFormat.format(Messages.SERVICE_OFFERING_WITH_GUID_0_NOT_FOUND, - offeringId), t)); - } - - private Flux getServiceResourcesByBrokerGuid(UUID brokerGuid) { - IntFunction pageRequestSupplier = page -> ListServiceOfferingsRequest.builder() - .serviceBrokerId( - brokerGuid.toString()) - .spaceId( - getTargetSpaceGuid().toString()) - .page(page) - .build(); - return getServiceResources(pageRequestSupplier); - } - - private Flux getServiceResourcesByLabel(String label) { - IntFunction pageRequestSupplier = page -> ListServiceOfferingsRequest.builder() - .name(label) - .spaceId( - getTargetSpaceGuid().toString()) - .page(page) - .build(); - return getServiceResources(pageRequestSupplier); - } - - private Flux getServiceResourcesByLabelAndBrokerName(String label, String brokerName) { - IntFunction pageRequestSupplier = page -> ListServiceOfferingsRequest.builder() - .name(label) - .serviceBrokerName(brokerName) - .spaceId( - getTargetSpaceGuid().toString()) - .page(page) - .build(); - return getServiceResources(pageRequestSupplier); - } - - private Flux getServiceResources(IntFunction pageRequestSupplier) { - return PaginationUtils.requestClientV3Resources(page -> delegate.serviceOfferingsV3() - .list(pageRequestSupplier.apply(page))); - } - - private Mono> zipWithAuxiliaryServiceOfferingContent(ServiceOfferingResource serviceOffering) { - UUID serviceOfferingGuid = getGuid(serviceOffering); - return getServicePlansFlux(serviceOfferingGuid).collectList() - .map(servicePlans -> ImmutableRawCloudServiceOffering.builder() - .serviceOffering( - serviceOffering) - .servicePlans(servicePlans) - .build()); - } - - private Flux getServicePlansFlux(UUID serviceOfferingGuid) { - return fetchFlux(() -> getServicePlanResourcesByServiceOfferingGuid(serviceOfferingGuid), ImmutableRawCloudServicePlan::of); - } - - protected Mono getServicePlanResource(String servicePlanGuid, String serviceInstanceName) { - GetServicePlanRequest request = GetServicePlanRequest.builder() - .servicePlanId(servicePlanGuid) - .build(); - - return delegate.servicePlansV3() - .get(request) - .onErrorMap(t -> doesErrorMatchStatusCode(t, HttpStatus.FORBIDDEN), - t -> new CloudOperationException(HttpStatus.FORBIDDEN, HttpStatus.FORBIDDEN.getReasonPhrase(), - MessageFormat.format( - Messages.SERVICE_PLAN_WITH_GUID_0_NOT_AVAILABLE_FOR_SERVICE_INSTANCE_1, - servicePlanGuid, serviceInstanceName), t)) - .onErrorMap(t -> doesErrorMatchStatusCode(t, HttpStatus.NOT_FOUND), - t -> new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), - MessageFormat.format(Messages.NO_SERVICE_PLAN_FOUND, servicePlanGuid, - serviceInstanceName), t)); - } - - private Flux getServicePlanResourcesByServiceOfferingGuid(UUID serviceOfferingGuid) { - IntFunction pageRequestSupplier = page -> ListServicePlansRequest.builder() - .serviceOfferingId( - serviceOfferingGuid.toString()) - .page(page) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.servicePlansV3() - .list(pageRequestSupplier.apply(page))); - } - - private Flux getServiceKeyResource(CloudServiceInstance serviceInstance) { - UUID serviceInstanceGuid = getGuid(serviceInstance); - return getServiceKeyResourcesByServiceInstanceGuid(serviceInstanceGuid); - } - - private Flux getServiceKeyResourcesByServiceInstanceGuid(UUID serviceInstanceGuid) { - IntFunction pageRequestSupplier = page -> ListServiceBindingsRequest.builder() - .serviceInstanceId( - serviceInstanceGuid.toString()) - .type(ServiceBindingType.KEY) - .page(page) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.serviceBindingsV3() - .list(pageRequestSupplier.apply(page))); - } - - private Mono getServiceKeyResourceByNameAndServiceInstanceGuid(String name, UUID guid) { - IntFunction pageRequestSupplier = page -> ListServiceBindingsRequest.builder() - .serviceInstanceId(guid.toString()) - .type(ServiceBindingType.KEY) - .name(name) - .page(page) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.serviceBindingsV3() - .list(pageRequestSupplier.apply(page))) - .singleOrEmpty(); - } - - private CloudStack findStackResource(String name) { - return fetch(() -> getStackResourceByName(name), ImmutableRawCloudStack::of); - } - - private Flux getStackResources() { - IntFunction pageRequestSupplier = page -> ListStacksRequest.builder() - .page(page) - .build(); - return getStackResources(pageRequestSupplier); - } - - private Mono getStackResourceByName(String name) { - IntFunction pageRequestSupplier = page -> ListStacksRequest.builder() - .name(name) - .page(page) - .build(); - return getStackResources(pageRequestSupplier).singleOrEmpty(); - } - - private Flux getStackResources(IntFunction requestForPage) { - return PaginationUtils.requestClientV3Resources(page -> delegate.stacksV3() - .list(requestForPage.apply(page))); - } - - private List findEventsByTarget(String target) { - return fetchList(() -> getEventResourcesByTarget(target), ImmutableRawCloudEvent::of); - } - - private Flux getEventResources() { - IntFunction pageRequestSupplier = page -> ListAuditEventsRequest.builder() - .page(page) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.auditEventsV3() - .list(pageRequestSupplier.apply(page))); - } - - private Flux getEventResourcesByTarget(String target) { - IntFunction pageRequestSupplier = page -> ListAuditEventsRequest.builder() - .targetId(target) - .page(page) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.auditEventsV3() - .list(pageRequestSupplier.apply(page))); - } - - private InstancesInfo findApplicationInstances(UUID applicationGuid) { - return fetch(() -> getApplicationProcessStatsResource(applicationGuid), ImmutableRawInstancesInfo::of); - } - - private Mono getApplicationProcessStatsResource(UUID applicationGuid) { - return delegate.applicationsV3() - .getProcessStatistics(GetApplicationProcessStatisticsRequest.builder() - .applicationId(applicationGuid.toString()) - .type("web") - .build()); - } - - private CloudServiceBroker findServiceBrokerByName(String name) { - return fetch(() -> getServiceBrokerResourceByName(name), ImmutableRawCloudServiceBroker::of); - } - - private Flux getServiceBrokerResources() { - IntFunction pageRequestSupplier = page -> ListServiceBrokersRequest.builder() - .page(page) - .build(); - return getServiceBrokerResources(pageRequestSupplier); - } - - private Mono getServiceBrokerResourceByName(String name) { - IntFunction pageRequestSupplier = page -> ListServiceBrokersRequest.builder() - .page(page) - .name(name) - .build(); - return getServiceBrokerResources(pageRequestSupplier).singleOrEmpty(); - } - - private Flux getServiceBrokerResources(IntFunction pageRequestSupplier) { - return PaginationUtils.requestClientV3Resources(page -> delegate.serviceBrokersV3() - .list(pageRequestSupplier.apply(page))); - } - - private CloudServicePlan findPlanForService(CloudServiceInstance service, String planName) { - List offerings = getServiceOfferings(service); - for (var offering : offerings) { - for (var plan : offering.getServicePlans()) { - if (planName.equals(plan.getName())) { - return plan; - } - } - } - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Service plan " + planName + " not found."); - } - - private List getServiceOfferings(CloudServiceInstance service) { - if (service.getBroker() == null) { - return findServiceOfferingsByLabel(service.getLabel()); - } - return findServiceOfferingsByLabelAndBrokerName(service.getLabel(), service.getBroker()); - } - - private UUID getRequiredApplicationGuid(String name) { - Resource applicationResource = getApplicationByName(name).block(); - if (applicationResource == null) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Application " + name + " not found."); - } - return UUID.fromString(applicationResource.getId()); - } - - private Mono getServiceInstanceByName(String name) { - IntFunction pageRequestSupplier = page -> ListServiceInstancesRequest.builder() - .spaceId( - getTargetSpaceGuid().toString()) - .serviceInstanceName(name) - .page(page) - .build(); - return PaginationUtils.requestClientV3Resources(page -> delegate.serviceInstancesV3() - .list(pageRequestSupplier.apply(page))) - .singleOrEmpty(); - } - - private UUID getRequiredDomainGuid(String name) { - return getGuid(findDomainByName(name, true)); - } - - private Map getDomainsFromRoutes(Set routes) { - Set domainNames = routes.stream() - .map(route -> route.getDomain() - .getName()) - .filter(StringUtils::hasLength) - .collect(Collectors.toSet()); - return getDomainResourcesByNamesInBatches(domainNames).collectMap(DomainResource::getName, - domain -> UUID.fromString(domain.getId())) - .block(); - } - - private UUID getRouteGuid(UUID domainGuid, String host, String path) { - List routeEntitiesResource = getRouteResourcesByDomainGuidHostAndPath(domainGuid, host, path).collect( - Collectors.toList()) - .block(); - if (CollectionUtils.isEmpty(routeEntitiesResource)) { - return null; - } - return getGuid(routeEntitiesResource.get(0)); - } - - private List getServiceBindingGuids(UUID applicationGuid, UUID serviceInstanceGuid) { - return getServiceBindingResourcesByApplicationGuidAndServiceInstanceGuid(applicationGuid, serviceInstanceGuid).map(this::getGuid) - .collectList() - .block(); - } - - private void processAsyncUploadInBackground(CloudPackage cloudPackage, UploadStatusCallback callback) { - String threadName = String.format("App upload monitor: %s", cloudPackage.getGuid()); - new Thread(() -> processAsyncUpload(cloudPackage, callback), threadName).start(); - } - - private void processAsyncUpload(CloudPackage cloudPackage, UploadStatusCallback callback) { - while (true) { - Upload upload = getUploadStatus(cloudPackage.getGuid()); - Status uploadStatus = upload.getStatus(); - boolean unsubscribe = callback.onProgress(uploadStatus.toString()); - if (unsubscribe || isUploadReady(uploadStatus)) { - return; - } - if (hasUploadFailed(uploadStatus)) { - callback.onError(upload.getErrorDetails() - .getDescription()); - return; - } - try { - Thread.sleep(PACKAGE_UPLOAD_JOB_POLLING_PERIOD); - } catch (InterruptedException e) { - return; - } - } - } - - private boolean isUploadReady(Status status) { - return status == Status.READY; - } - - private boolean hasUploadFailed(Status status) { - return status == Status.EXPIRED || status == Status.FAILED; - } - - @Override - public CloudPackage getPackage(UUID packageGuid) { - return fetch(() -> getPackageResource(packageGuid), ImmutableRawCloudPackage::of); - } - - private Mono getPackageResource(UUID guid) { - GetPackageRequest request = GetPackageRequest.builder() - .packageId(guid.toString()) - .build(); - return delegate.packages() - .get(request); - } - - private UUID getTargetOrganizationGuid() { - return getGuid(target.getOrganization()); - } - - private UUID getTargetSpaceGuid() { - return getGuid(target); - } - - private UUID getGuid(CloudEntity entity) { - return Optional.ofNullable(entity) - .map(CloudEntity::getMetadata) - .map(CloudMetadata::getGuid) - .orElse(null); - } - - private UUID getGuid(Resource resource) { - return Optional.ofNullable(resource) - .map(Resource::getId) - .map(UUID::fromString) - .orElse(null); - } - - private boolean doesErrorMatchStatusCode(Throwable t, HttpStatus status) { - if (t instanceof AbstractCloudFoundryException e) { - return e.getStatusCode() == status.value(); - } - return false; - } - - private > Flux fetchFluxWithAuxiliaryContent(Supplier> resourceSupplier, - Function> resourceMapper) { - return resourceSupplier.get() - .flatMap(resourceMapper) - .map(Derivable::derive); - } - - private > List fetchListWithAuxiliaryContent(Supplier> resourceSupplier, - Function> resourceMapper) { - return fetchFluxWithAuxiliaryContent(resourceSupplier, resourceMapper).collectList() - .block(); - } - - private > List fetchList(Supplier> resourceSupplier, Function resourceMapper) { - return fetchFlux(resourceSupplier, resourceMapper).collectList() - .block(); - } - - private > Flux fetchFlux(Supplier> resourceSupplier, Function resourceMapper) { - return resourceSupplier.get() - .map(resourceMapper) - .map(Derivable::derive); - } - - private > T fetchWithAuxiliaryContent(Supplier> resourceSupplier, - Function> resourceMapper) { - return fetchMonoWithAuxiliaryContent(resourceSupplier, resourceMapper).block(); - } - - private > T fetch(Supplier> resourceSupplier, Function resourceMapper) { - return fetchMono(resourceSupplier, resourceMapper).block(); - } - - private > Mono fetchMonoWithAuxiliaryContent(Supplier> resourceSupplier, - Function> resourceMapper) { - return resourceSupplier.get() - .flatMap(resourceMapper) - .map(Derivable::derive); - } - - private > Mono fetchMono(Supplier> resourceSupplier, Function resourceMapper) { - return resourceSupplier.get() - .map(resourceMapper) - .map(Derivable::derive); - } - -} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Client.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Client.java index de312d1a0b..2adab4c566 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Client.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Client.java @@ -10,8 +10,6 @@ import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Job; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; @@ -33,7 +31,6 @@ */ public class CloudControllerV3Client { - private static final Logger LOGGER = LoggerFactory.getLogger(CloudControllerV3Client.class); // Mirrors the OSS JobV3Util backoff so async behaviour is unchanged. private static final Duration JOB_POLL_MIN_INTERVAL = Duration.ofSeconds(1); @@ -47,7 +44,6 @@ public CloudControllerV3Client(RestClient restClient) { } public T get(String uri, Class responseType) { - LOGGER.warn("[cf-v3-poc] GET {}", uri); return restClient.get() .uri(uri) .retrieve() @@ -77,7 +73,6 @@ public List list(String firstPageUri, ParameterizedTypeReference all = new ArrayList<>(); String nextUri = firstPageUri; while (nextUri != null) { - LOGGER.warn("[cf-v3-poc] LIST {}", nextUri); V3ListResponse page = restClient.get() .uri(nextUri) .retrieve() diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudSpaceClient.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudSpaceClient.java index 0eb52d8e0d..2f860840b0 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudSpaceClient.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudSpaceClient.java @@ -1,127 +1,80 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest; +import java.util.List; +import java.util.UUID; + import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.RawCloudEntity; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudOrganization; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Organization; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ResourceMappers; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Space; import org.cloudfoundry.multiapps.controller.client.facade.util.UriUtil; - -import org.cloudfoundry.AbstractCloudFoundryException; -import org.cloudfoundry.client.v3.ClientV3Exception; -import org.cloudfoundry.client.v3.organizations.GetOrganizationRequest; -import org.cloudfoundry.client.v3.organizations.ListOrganizationsRequest; -import org.cloudfoundry.client.v3.organizations.Organization; -import org.cloudfoundry.client.v3.organizations.OrganizationsV3; -import org.cloudfoundry.client.v3.spaces.GetSpaceRequest; -import org.cloudfoundry.client.v3.spaces.ListSpacesRequest; -import org.cloudfoundry.client.v3.spaces.Space; -import org.cloudfoundry.client.v3.spaces.SpacesV3; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpStatus; -import reactor.util.retry.Retry; -import reactor.util.retry.RetryBackoffSpec; - -import java.time.Duration; -import java.util.List; -import java.util.UUID; +/** + * Resolves a target {@link CloudSpace} (space + its organization) by GUID or by org-name/space-name, talking directly to the CF v3 REST + * API through {@link CloudControllerV3Client}. Replaces the OSS cf-java-client-based implementation (which used {@code SpacesV3} / + * {@code OrganizationsV3}); behaviour and 404 semantics are preserved. + */ public class CloudSpaceClient { private static final List CHARS_TO_ENCODE = List.of(","); - private static final long RETRIES = 3; - private static final Duration RETRY_INTERVAL = Duration.ofSeconds(3); - private final SpacesV3 spacesClient; - private final OrganizationsV3 orgsClient; + private final CloudControllerV3Client cc; - public CloudSpaceClient(SpacesV3 spacesClient, OrganizationsV3 orgsClient) { - this.spacesClient = spacesClient; - this.orgsClient = orgsClient; + public CloudSpaceClient(CloudControllerV3Client cc) { + this.cc = cc; } public CloudSpace getSpace(UUID spaceGuid) { - Space space = spacesClient.get(GetSpaceRequest.builder() - .spaceId(spaceGuid.toString()) - .build()) - .retryWhen(Retry.fixedDelay(RETRIES, RETRY_INTERVAL) - .onRetryExhaustedThrow(this::throwOriginalError)) - .onErrorMap(ClientV3Exception.class, this::convertV3ClientException) - .block(); - if (space == null) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Space with GUID " + spaceGuid + " not found."); - } - - String orgGuid = space.getRelationships() - .getOrganization() - .getData() - .getId(); - Organization org = orgsClient.get(GetOrganizationRequest.builder() - .organizationId(orgGuid) - .build()) - .retryWhen(Retry.fixedDelay(RETRIES, RETRY_INTERVAL) - .onRetryExhaustedThrow(this::throwOriginalError)) - .onErrorMap(ClientV3Exception.class, this::convertV3ClientException) - .block(); - if (org == null) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Organization with GUID " + orgGuid + " not found."); - } + V3Space space = cc.getOptional("/v3/spaces/" + spaceGuid, V3Space.class) + .orElseThrow(() -> new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", + "Space with GUID " + spaceGuid + " not found.")); + String orgGuid = space.organizationGuid(); + V3Organization org = cc.getOptional("/v3/organizations/" + orgGuid, V3Organization.class) + .orElseThrow(() -> new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", + "Organization with GUID " + orgGuid + " not found.")); return mapToCloudSpace(space, org); } public CloudSpace getSpace(String organizationName, String spaceName) { - var orgsResponse = orgsClient.list(ListOrganizationsRequest.builder() - .name(encodeAsQueryParam(organizationName)) - .build()) - .retryWhen(Retry.fixedDelay(RETRIES, RETRY_INTERVAL) - .onRetryExhaustedThrow(this::throwOriginalError)) - .onErrorMap(ClientV3Exception.class, this::convertV3ClientException) - .block(); - List orgs = orgsResponse.getResources(); + List orgs = cc.list("/v3/organizations?names=" + encodeAsQueryParam(organizationName), + new ParameterizedTypeReference>() { + }); if (orgs.isEmpty()) { throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Organization " + organizationName + " not found."); } + V3Organization org = orgs.get(0); - Organization org = orgs.get(0); - - var spacesResponse = spacesClient.list(ListSpacesRequest.builder() - .organizationId(org.getId()) - .name(encodeAsQueryParam(spaceName)) - .build()) - .retryWhen(Retry.fixedDelay(RETRIES, RETRY_INTERVAL) - .onRetryExhaustedThrow(this::throwOriginalError)) - .onErrorMap(ClientV3Exception.class, this::convertV3ClientException) - .block(); - List spaces = spacesResponse.getResources(); + List spaces = cc.list("/v3/spaces?organization_guids=" + org.guid() + "&names=" + encodeAsQueryParam(spaceName), + new ParameterizedTypeReference>() { + }); if (spaces.isEmpty()) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Space " + spaceName + " not found in organization " + organizationName); + throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", + "Space " + spaceName + " not found in organization " + organizationName); } - - Space space = spaces.get(0); - return mapToCloudSpace(space, org); + return mapToCloudSpace(spaces.get(0), org); } private String encodeAsQueryParam(String param) { return UriUtil.encodeChars(param, CHARS_TO_ENCODE); } - private CloudSpace mapToCloudSpace(Space space, Organization org) { + private CloudSpace mapToCloudSpace(V3Space space, V3Organization org) { return ImmutableCloudSpace.builder() - .metadata(RawCloudEntity.parseResourceMetadata(space)) - .name(space.getName()) + .metadata(V3ResourceMappers.parseMetadata(space.guid(), space.createdAt(), space.updatedAt())) + .name(space.name()) .organization(ImmutableCloudOrganization.builder() - .metadata(RawCloudEntity.parseResourceMetadata(org)) - .name(org.getName()) + .metadata(V3ResourceMappers.parseMetadata(org.guid(), + org.createdAt(), + org.updatedAt())) + .name(org.name()) .build()) .build(); } - private Throwable throwOriginalError(RetryBackoffSpec retrySpec, Retry.RetrySignal signal) { - return signal.failure(); - } - - private CloudOperationException convertV3ClientException(AbstractCloudFoundryException e) { - HttpStatus httpStatus = HttpStatus.valueOf(e.getStatusCode()); - return new CloudOperationException(httpStatus, httpStatus.getReasonPhrase(), e.getMessage(), e); - } - } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/LogCacheReadResponse.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/LogCacheReadResponse.java new file mode 100644 index 0000000000..3965703cdb --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/LogCacheReadResponse.java @@ -0,0 +1,45 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of a Log-Cache {@code read} response ({@code GET /api/v1/read/{source_id}}), which is a plain JSON/REST API: + * + *
+ * { "envelopes": { "batch": [
+ *     { "source_id": "...", "timestamp": "1699999999000000000", "tags": { "source_type": "APP/PROC/WEB" },
+ *       "log": { "payload": "<base64>", "type": "OUT" | "ERR" } } ] } }
+ * 
+ * + * Replaces the OSS {@code org.cloudfoundry.logcache.v1.*} types so the log-cache client no longer depends on cf-java-client. Only the + * fields the mapper needs are declared. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record LogCacheReadResponse(@JsonProperty("envelopes") EnvelopeBatch envelopes) { + + public List batch() { + if (envelopes == null || envelopes.batch() == null) { + return Collections.emptyList(); + } + return envelopes.batch(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record EnvelopeBatch(@JsonProperty("batch") List batch) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record Envelope(@JsonProperty("source_id") String sourceId, @JsonProperty("timestamp") Long timestamp, + @JsonProperty("tags") Map tags, @JsonProperty("log") Log log) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record Log(@JsonProperty("payload") String payload, @JsonProperty("type") String type) { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Organization.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Organization.java new file mode 100644 index 0000000000..e7b9a14729 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Organization.java @@ -0,0 +1,12 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of a CF v3 organization resource ({@code GET /v3/organizations/{guid}} / {@code GET /v3/organizations?...}). + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3Organization(@JsonProperty("guid") String guid, @JsonProperty("name") String name, + @JsonProperty("created_at") String createdAt, @JsonProperty("updated_at") String updatedAt) { +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Space.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Space.java new file mode 100644 index 0000000000..4e7c0d28c8 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Space.java @@ -0,0 +1,40 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of a CF v3 space resource ({@code GET /v3/spaces/{guid}} / {@code GET /v3/spaces?...}). + * + *
+ * { "guid": "...", "name": "...", "created_at": "...", "updated_at": "...",
+ *   "relationships": { "organization": { "data": { "guid": "..." } } } }
+ * 
+ */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3Space(@JsonProperty("guid") String guid, @JsonProperty("name") String name, @JsonProperty("created_at") String createdAt, + @JsonProperty("updated_at") String updatedAt, @JsonProperty("relationships") V3SpaceRelationships relationships) { + + public String organizationGuid() { + if (relationships == null || relationships.organization() == null || relationships.organization() + .data() == null) { + return null; + } + return relationships.organization() + .data() + .guid(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3SpaceRelationships(@JsonProperty("organization") V3ToOne organization) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3ToOne(@JsonProperty("data") V3Data data) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Data(@JsonProperty("guid") String guid) { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/util/JobV3Util.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/util/JobV3Util.java deleted file mode 100644 index e7e293c426..0000000000 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/util/JobV3Util.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.util; - -import java.time.Duration; -import java.util.EnumSet; -import java.util.List; -import java.util.Set; - -import org.cloudfoundry.client.CloudFoundryClient; -import org.cloudfoundry.client.v3.ClientV3Exception; -import org.cloudfoundry.client.v3.Error; -import org.cloudfoundry.client.v3.jobs.GetJobRequest; -import org.cloudfoundry.client.v3.jobs.GetJobResponse; -import org.cloudfoundry.client.v3.jobs.Job; -import org.cloudfoundry.client.v3.jobs.JobState; -import org.cloudfoundry.util.DelayUtils; - -import reactor.core.publisher.Mono; - -public class JobV3Util { - - private JobV3Util() { - } - - private static final Set FINAL_STATES = EnumSet.of(JobState.COMPLETE, JobState.FAILED); - - public static Mono waitForCompletion(CloudFoundryClient cloudFoundryClient, Duration completionTimeout, String jobId) { - return requestJobV3(cloudFoundryClient, jobId).filter(job -> FINAL_STATES.contains(job.getState())) - .repeatWhenEmpty(DelayUtils.exponentialBackOff(Duration.ofSeconds(1), - Duration.ofSeconds(15), - completionTimeout)) - .filter(job -> JobState.FAILED == job.getState()) - .flatMap(JobV3Util::getError); - } - - private static Mono requestJobV3(CloudFoundryClient cloudFoundryClient, String jobId) { - return cloudFoundryClient.jobsV3() - .get(GetJobRequest.builder() - .jobId(jobId) - .build()); - } - - private static Mono getError(Job job) { - List errors = job.getErrors(); - // Status code must be set, otherwise it will throw NPE during getStatusCode() invocation - return Mono.error(new ClientV3Exception(200, errors)); - } - -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/ApplicationsCloudControllerClientIntegrationTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/ApplicationsCloudControllerClientIntegrationTest.java index 70cc2867b7..95ca5e50bd 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/ApplicationsCloudControllerClientIntegrationTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/ApplicationsCloudControllerClientIntegrationTest.java @@ -157,27 +157,12 @@ void updateApplicationHealthcheckType() { String applicationName = "test-application-16"; try { createAndVerifyDefaultApplication(applicationName); + client.updateApplicationStaging(applicationName, ImmutableStaging.builder() + .healthCheckType(HealthCheckType.PROCESS.getValue()) + .build()); UUID applicationGuid = client.getApplicationGuid(applicationName); - var processResponse = delegate.applicationsV3() - .getProcess(org.cloudfoundry.client.v3.applications.GetApplicationProcessRequest.builder() - .applicationId( - applicationGuid.toString()) - .type("web") - .build()) - .block(); - delegate.processes() - .update(org.cloudfoundry.client.v3.processes.UpdateProcessRequest.builder() - .processId(processResponse.getId()) - .healthCheck( - org.cloudfoundry.client.v3.processes.HealthCheck.builder() - .type( - org.cloudfoundry.client.v3.processes.HealthCheckType.PROCESS) - .build()) - .build()) - .block(); CloudProcess cloudProcess = client.getApplicationProcess(applicationGuid); - assertEquals(org.cloudfoundry.multiapps.controller.client.facade.domain.HealthCheckType.PROCESS, - cloudProcess.getHealthCheckType()); + assertEquals(HealthCheckType.PROCESS, cloudProcess.getHealthCheckType()); } catch (Exception e) { fail(e); } finally { diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/CloudControllerClientIntegrationTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/CloudControllerClientIntegrationTest.java index b394252229..3b0e958345 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/CloudControllerClientIntegrationTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/CloudControllerClientIntegrationTest.java @@ -7,15 +7,14 @@ import java.util.HashMap; import java.util.Map; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableCloudFoundryClientFactory; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableLifecycle; import org.cloudfoundry.multiapps.controller.client.facade.domain.Lifecycle; import org.cloudfoundry.multiapps.controller.client.facade.domain.LifecycleType; import org.cloudfoundry.multiapps.controller.client.facade.domain.Staging; import org.cloudfoundry.multiapps.controller.client.facade.rest.CloudSpaceClient; +import org.cloudfoundry.multiapps.controller.client.facade.rest.ImmutableCloudControllerRestClientFactory; import org.cloudfoundry.multiapps.controller.client.facade.util.RestUtil; -import org.cloudfoundry.client.CloudFoundryClient; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -30,7 +29,6 @@ abstract class CloudControllerClientIntegrationTest { protected static final String DEFAULT_STACK = "cflinuxfs4"; protected static CloudControllerClient client; - protected static CloudFoundryClient delegate; protected static CloudSpace target; @BeforeAll @@ -39,14 +37,14 @@ static void login() throws MalformedURLException { CloudCredentials credentials = getCloudCredentials(); URL apiUrl = URI.create(ITVariable.CF_API.getValue()) .toURL(); - var clientFactory = ImmutableCloudFoundryClientFactory.builder() - .build(); + var clientFactory = ImmutableCloudControllerRestClientFactory.builder() + .shouldTrustSelfSignedCertificates(true) + .build(); var oauthClient = new RestUtil().createOAuthClientByControllerUrl(apiUrl, true); oauthClient.init(credentials); CloudSpaceClient spaceClient = clientFactory.createSpaceClient(apiUrl, oauthClient, Collections.emptyMap()); target = spaceClient.getSpace(ITVariable.ORG.getValue(), ITVariable.SPACE.getValue()); client = new CloudControllerClientImpl(apiUrl, credentials, target, true); - delegate = clientFactory.createClient(apiUrl, oauthClient, Collections.emptyMap()); } @BeforeEach diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/OAuthTokenProviderTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/OAuthTokenProviderTest.java deleted file mode 100644 index f6131944b1..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/OAuthTokenProviderTest.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import org.cloudfoundry.multiapps.controller.client.facade.oauth2.OAuth2AccessTokenWithAdditionalInfo; -import org.cloudfoundry.multiapps.controller.client.facade.oauth2.OAuthClient; -import org.cloudfoundry.reactor.ConnectionContext; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; - -import reactor.core.publisher.Mono; - -class OAuthTokenProviderTest { - - @Mock - private OAuthClient oAuthClient; - @Mock - private OAuth2AccessTokenWithAdditionalInfo token; - @Mock - private ConnectionContext connectionContext; - - private OAuthTokenProvider provider; - - @BeforeEach - void setUp() throws Exception { - MockitoAnnotations.openMocks(this) - .close(); - provider = new OAuthTokenProvider(oAuthClient); - } - - @Test - void testGetTokenIsLazyAndDoesNotInvokeOAuthClientUntilSubscribed() { - Mockito.when(oAuthClient.getToken()) - .thenReturn(token); - Mockito.when(token.getAuthorizationHeaderValue()) - .thenReturn("Bearer abc123"); - - Mono mono = provider.getToken(connectionContext); - - Mockito.verify(oAuthClient, Mockito.never()) - .getToken(); - - String result = mono.block(); - - Assertions.assertEquals("Bearer abc123", result); - Mockito.verify(oAuthClient) - .getToken(); - } - - @Test - void testGetTokenInvokesOAuthClientOncePerSubscription() { - Mockito.when(oAuthClient.getToken()) - .thenReturn(token); - Mockito.when(token.getAuthorizationHeaderValue()) - .thenReturn("Bearer abc123"); - - Mono mono = provider.getToken(connectionContext); - - mono.block(); - mono.block(); - - Mockito.verify(oAuthClient, Mockito.times(2)) - .getToken(); - } - - @Test - void testGetTokenPropagatesOAuthClientFailure() { - RuntimeException failure = new RuntimeException("token retrieval failed"); - Mockito.when(oAuthClient.getToken()) - .thenThrow(failure); - - Mono mono = provider.getToken(connectionContext); - - RuntimeException thrown = Assertions.assertThrows(RuntimeException.class, mono::block); - Assertions.assertSame(failure, thrown); - } -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudApplicationTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudApplicationTest.java deleted file mode 100644 index e864ff93a9..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudApplicationTest.java +++ /dev/null @@ -1,132 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.util.List; -import java.util.Map; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudApplication; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudSpace; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableLifecycle; -import org.cloudfoundry.client.v3.BuildpackData; -import org.cloudfoundry.client.v3.DockerData; -import org.cloudfoundry.client.v3.Lifecycle; -import org.cloudfoundry.client.v3.LifecycleType; -import org.cloudfoundry.client.v3.applications.ApplicationResource; -import org.cloudfoundry.client.v3.applications.ApplicationState; -import org.junit.jupiter.api.Test; - -class RawCloudApplicationTest { - - private static final String STATE = "STARTED"; - private static final String BUILDPACK = "ruby_buildpack"; - private static final String STACK_NAME = "cflinuxfs3"; - private static final String SPACE_NAME = "test"; - private static final CloudSpace SPACE = ImmutableCloudSpace.builder() - .name(SPACE_NAME) - .build(); - private static final String EXPECTED_BUILDPACK = "ruby_buildpack"; - private static final String BUILDPACK_URL = "custom-buildpack-url"; - - private static final String EXPECTED_STACK = "cflinuxfs3"; - private static final CloudApplication.State EXPECTED_STATE = CloudApplication.State.STARTED; - - @Test - void testDeriveForBuildpackApp() { - RawCloudEntityTest.testDerive(buildApplication(buildBuildpackLifecycle()), buildRawApplication(buildBuildpackLifecycleResource())); - } - - @Test - void testDeriveForDockerApp() { - RawCloudEntityTest.testDerive(buildApplication(buildDockerLifecycle()), buildRawApplication(buildDockerLifecycleResource())); - } - - @Test - void testDeriveForCnbApp() { - RawCloudEntityTest.testDerive(buildApplication(buildCnbLifecycle()), buildRawApplication(buildCnbLifecycleResource())); - } - - private static CloudApplication buildApplication(org.cloudfoundry.multiapps.controller.client.facade.domain.Lifecycle lifecycle) { - return ImmutableCloudApplication.builder() - .metadata(RawCloudEntityTest.EXPECTED_METADATA_V3) - .v3Metadata(RawCloudEntityTest.V3_METADATA_DOMAIN) - .name(RawCloudEntityTest.NAME) - .state(EXPECTED_STATE) - .lifecycle(lifecycle) - .space(SPACE) - .build(); - } - - private static org.cloudfoundry.multiapps.controller.client.facade.domain.Lifecycle buildBuildpackLifecycle() { - return ImmutableLifecycle.builder() - .type(org.cloudfoundry.multiapps.controller.client.facade.domain.LifecycleType.BUILDPACK) - .data(buildLifecycleData(EXPECTED_BUILDPACK)) - .build(); - } - - private org.cloudfoundry.multiapps.controller.client.facade.domain.Lifecycle buildCnbLifecycle() { - return ImmutableLifecycle.builder() - .type(org.cloudfoundry.multiapps.controller.client.facade.domain.LifecycleType.CNB) - .data(buildLifecycleData(BUILDPACK_URL)) - .build(); - } - - private static org.cloudfoundry.multiapps.controller.client.facade.domain.Lifecycle buildDockerLifecycle() { - return ImmutableLifecycle.builder() - .type(org.cloudfoundry.multiapps.controller.client.facade.domain.LifecycleType.DOCKER) - .data(Map.of()) - .build(); - } - - private static Map buildLifecycleData(String buildpack) { - return Map.of("buildpacks", List.of(buildpack), "stack", EXPECTED_STACK); - } - - private static RawCloudApplication buildRawApplication(Lifecycle lifecycle) { - return ImmutableRawCloudApplication.builder() - .application(buildApplicationResource(lifecycle)) - .space(SPACE) - .build(); - } - - private static ApplicationResource buildApplicationResource(Lifecycle lifecycle) { - return ApplicationResource.builder() - .metadata(RawCloudEntityTest.V3_METADATA) - .createdAt(RawCloudEntityTest.CREATED_AT_STRING) - .updatedAt(RawCloudEntityTest.UPDATED_AT_STRING) - .state(ApplicationState.valueOf(STATE)) - .id(RawCloudEntityTest.GUID.toString()) - .lifecycle(lifecycle) - .name("foo") - .build(); - } - - private static Lifecycle buildBuildpackLifecycleResource() { - return Lifecycle.builder() - .type(LifecycleType.BUILDPACK) - .data(BuildpackData.builder() - .buildpack(BUILDPACK) - .stack(STACK_NAME) - .build()) - .build(); - } - - private Lifecycle buildCnbLifecycleResource() { - return Lifecycle.builder() - .type(LifecycleType.CNB) - .data(BuildpackData.builder() - .buildpack(BUILDPACK_URL) - .stack(STACK_NAME) - .build()) - .build(); - } - - private static Lifecycle buildDockerLifecycleResource() { - return Lifecycle.builder() - .type(LifecycleType.DOCKER) - .data(DockerData.builder() - .build()) - .build(); - } - -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudAsyncJobTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudAsyncJobTest.java deleted file mode 100644 index 41407fb6b6..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudAsyncJobTest.java +++ /dev/null @@ -1,82 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.util.List; - -import org.cloudfoundry.client.v3.Error; -import org.cloudfoundry.client.v3.jobs.GetJobResponse; -import org.cloudfoundry.client.v3.jobs.Job; -import org.cloudfoundry.client.v3.jobs.JobState; -import org.cloudfoundry.client.v3.jobs.Warning; -import org.junit.jupiter.api.Test; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudAsyncJob; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudAsyncJob; - -class RawCloudAsyncJobTest { - - private static final String OPERATION = "service_broker.delete"; - private static final String WARNING_1 = "warning1"; - private static final String WARNING_2 = "warning2"; - private static final String WARNINGS = "warning1,warning2"; - private static final int ERROR_CODE_1 = 10008; - private static final int ERROR_CODE_2 = 1000; - private static final String ERROR_TITLE_1 = "CF-UnprocessableEntity"; - private static final String ERROR_TITLE_2 = "CF-InvalidAuthToken"; - private static final String ERROR_DETAIL_1 = "something went wrong"; - private static final String ERROR_DETAIL_2 = "not valid token"; - private static final String ERRORS = "10008 CF-UnprocessableEntity something went wrong,1000 CF-InvalidAuthToken not valid token"; - - @Test - void testDerive() { - RawCloudEntityTest.testDerive(buildExpectedTask(), buildRawTask()); - } - - private CloudAsyncJob buildExpectedTask() { - return ImmutableCloudAsyncJob.builder() - .metadata(RawCloudEntityTest.EXPECTED_METADATA_PARSED_FROM_V3_RESOURCE) - .state(org.cloudfoundry.multiapps.controller.client.facade.domain.JobState.FAILED) - .operation(OPERATION) - .warnings(WARNINGS) - .errors(ERRORS) - .build(); - } - - private RawCloudAsyncJob buildRawTask() { - return ImmutableRawCloudAsyncJob.of(buildTestResource()); - } - - private Job buildTestResource() { - return GetJobResponse.builder() - .id(RawCloudEntityTest.GUID_STRING) - .createdAt(RawCloudEntityTest.CREATED_AT_STRING) - .updatedAt(RawCloudEntityTest.UPDATED_AT_STRING) - .state(JobState.FAILED) - .operation(OPERATION) - .warnings(buildWarnings()) - .errors(buildErrors()) - .build(); - } - - private List buildWarnings() { - return List.of(Warning.builder() - .detail(WARNING_1) - .build(), - Warning.builder() - .detail(WARNING_2) - .build()); - } - - private List buildErrors() { - return List.of(Error.builder() - .code(ERROR_CODE_1) - .title(ERROR_TITLE_1) - .detail(ERROR_DETAIL_1) - .build(), - Error.builder() - .code(ERROR_CODE_2) - .title(ERROR_TITLE_2) - .detail(ERROR_DETAIL_2) - .build()); - } - -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudBuildTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudBuildTest.java deleted file mode 100644 index c35ec79de0..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudBuildTest.java +++ /dev/null @@ -1,137 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.util.UUID; - -import org.cloudfoundry.client.v3.BuildpackData; -import org.cloudfoundry.client.v3.Lifecycle; -import org.cloudfoundry.client.v3.LifecycleType; -import org.cloudfoundry.client.v3.Relationship; -import org.cloudfoundry.client.v3.builds.Build; -import org.cloudfoundry.client.v3.builds.BuildResource; -import org.cloudfoundry.client.v3.builds.BuildState; -import org.cloudfoundry.client.v3.builds.CreatedBy; -import org.cloudfoundry.client.v3.builds.Droplet; -import org.junit.jupiter.api.Test; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudBuild; -import org.cloudfoundry.multiapps.controller.client.facade.domain.DropletInfo; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudBuild; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableDropletInfo; - -class RawCloudBuildTest { - - private static final String PACKAGE_GUID_STRING = "d0bd1437-f112-48cd-b777-6b160cf2fff0"; - private static final String DROPLET_GUID_STRING = "47e172c9-bfb2-41df-bf3f-850108c241ef"; - private static final Droplet DROPLET = buildTestDroplet(); - private static final String CREATED_BY_GUID_STRING = "37964fd8-a234-4c04-af31-c3fe5e477bed"; - private static final String CREATED_BY_NAME = "admin"; - private static final LifecycleType LIFECYCLE_TYPE = LifecycleType.BUILDPACK; - private static final BuildState STATE = BuildState.FAILED; - private static final String ERROR = "blabla"; - - private static final UUID PACKAGE_GUID = UUID.fromString(PACKAGE_GUID_STRING); - private static final UUID DROPLET_GUID = UUID.fromString(DROPLET_GUID_STRING); - private static final UUID CREATED_BY_GUID = UUID.fromString(CREATED_BY_GUID_STRING); - - private static final CloudBuild.State EXPECTED_STATE = CloudBuild.State.FAILED; - private static final DropletInfo EXPECTED_DROPLET_INFO = buildExpectedDropletInfo(); - - private static CloudBuild buildExpectedBuild() { - return buildExpectedBuild(null); - } - - private static CloudBuild buildExpectedBuild(DropletInfo dropletInfo) { - return ImmutableCloudBuild.builder() - .metadata(RawCloudEntityTest.EXPECTED_METADATA_PARSED_FROM_V3_RESOURCE) - .createdBy(buildExpectedCreatedBy()) - .dropletInfo(dropletInfo) - .packageInfo(buildExpectedPackageInfo()) - .state(EXPECTED_STATE) - .error(ERROR) - .build(); - } - - private static CloudBuild.CreatedBy buildExpectedCreatedBy() { - return ImmutableCloudBuild.ImmutableCreatedBy.builder() - .guid(CREATED_BY_GUID) - .name(CREATED_BY_NAME) - .build(); - } - - private static DropletInfo buildExpectedDropletInfo() { - return ImmutableDropletInfo.builder() - .guid(DROPLET_GUID) - .build(); - } - - private static CloudBuild.PackageInfo buildExpectedPackageInfo() { - return ImmutableCloudBuild.ImmutablePackageInfo.builder() - .guid(PACKAGE_GUID) - .build(); - } - - private static Build buildTestResource(Droplet droplet) { - return BuildResource.builder() - .id(RawCloudEntityTest.GUID_STRING) - .createdAt(RawCloudEntityTest.CREATED_AT_STRING) - .updatedAt(RawCloudEntityTest.UPDATED_AT_STRING) - .inputPackage(buildTestPackage()) - .createdBy(buildTestCreatedBy()) - .lifecycle(buildTestLifecycle()) - .droplet(droplet) - .state(STATE) - .error(ERROR) - .build(); - } - - private static Relationship buildTestPackage() { - return Relationship.builder() - .id(PACKAGE_GUID_STRING) - .build(); - } - - private static Droplet buildTestDroplet() { - return Droplet.builder() - .id(DROPLET_GUID_STRING) - .build(); - } - - private static CreatedBy buildTestCreatedBy() { - return CreatedBy.builder() - .id(CREATED_BY_GUID_STRING) - .name(CREATED_BY_NAME) - .email(CREATED_BY_NAME) - .build(); - } - - private static Lifecycle buildTestLifecycle() { - return Lifecycle.builder() - .data(buildTestLifecycleData()) - .type(LIFECYCLE_TYPE) - .build(); - } - - private static BuildpackData buildTestLifecycleData() { - return BuildpackData.builder() - .build(); - } - - @Test - void testDeriveWithoutDroplet() { - RawCloudEntityTest.testDerive(buildExpectedBuild(), buildRawBuild()); - } - - @Test - void testDerive() { - RawCloudEntityTest.testDerive(buildExpectedBuild(EXPECTED_DROPLET_INFO), buildRawBuild(DROPLET)); - } - - private RawCloudBuild buildRawBuild() { - return buildRawBuild(null); - } - - private RawCloudBuild buildRawBuild(Droplet droplet) { - return ImmutableRawCloudBuild.of(buildTestResource(droplet)); - } - -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudDomainTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudDomainTest.java deleted file mode 100644 index 65fd5a6642..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudDomainTest.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import org.cloudfoundry.client.v3.ToManyRelationship; -import org.cloudfoundry.client.v3.ToOneRelationship; -import org.cloudfoundry.client.v3.domains.Domain; -import org.cloudfoundry.client.v3.domains.DomainRelationships; -import org.cloudfoundry.client.v3.organizations.GetOrganizationDefaultDomainResponse; -import org.junit.jupiter.api.Test; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudDomain; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudDomain; - -class RawCloudDomainTest { - - private static final String DOMAIN_NAME = "example.com"; - - @Test - void testDerive() { - RawCloudEntityTest.testDerive(buildExpectedDomain(), buildRawDomain()); - } - - private static CloudDomain buildExpectedDomain() { - return ImmutableCloudDomain.builder() - .metadata(RawCloudEntityTest.EXPECTED_METADATA_PARSED_FROM_V3_RESOURCE) - .name(DOMAIN_NAME) - .build(); - } - - private static RawCloudDomain buildRawDomain() { - return ImmutableRawCloudDomain.of(buildTestResource()); - } - - private static Domain buildTestResource() { - return GetOrganizationDefaultDomainResponse.builder() - .id(RawCloudEntityTest.GUID_STRING) - .createdAt(RawCloudEntityTest.CREATED_AT_STRING) - .updatedAt(RawCloudEntityTest.UPDATED_AT_STRING) - .name(DOMAIN_NAME) - .isInternal(false) - .relationships(DomainRelationships.builder() - .organization(ToOneRelationship.builder() - .build()) - .sharedOrganizations(ToManyRelationship.builder() - .build()) - .build()) - .build(); - } - -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudEntityTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudEntityTest.java deleted file mode 100644 index 8e0e74876d..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudEntityTest.java +++ /dev/null @@ -1,158 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; - -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -import org.cloudfoundry.client.v3.Metadata; -import org.cloudfoundry.client.v3.Relationship; -import org.cloudfoundry.client.v3.ToOneRelationship; -import org.cloudfoundry.client.v3.applications.ApplicationState; -import org.cloudfoundry.client.v3.organizations.Organization; -import org.cloudfoundry.client.v3.organizations.OrganizationResource; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudBuild; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudMetadata; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Derivable; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; - -class RawCloudEntityTest { - - static final String GUID_STRING = "3725650a-8725-4401-a949-c68f83d54a86"; - static final String CREATED_AT_STRING = "2017-06-22T13:38:41Z"; - static final String UPDATED_AT_STRING = "2019-03-21T12:29:24Z"; - - static final String NAME = "foo"; - static final UUID GUID = UUID.fromString(GUID_STRING); - static final LocalDateTime CREATED_AT = fromZonedDateTime(ZonedDateTime.of(2017, 6, 22, 13, 38, 41, 0, ZoneId.of("Z"))); - static final LocalDateTime UPDATED_AT = fromZonedDateTime(ZonedDateTime.of(2019, 3, 21, 12, 29, 24, 0, ZoneId.of("Z"))); - static final Map V3_ANNOTATIONS = Map.of("annotation1", "value1", "annotation2", "value2"); - static final Map V3_LABELS = Map.of("label1", "value1", "label2", "value2"); - - static final Metadata V3_METADATA = Metadata.builder() - .annotations(V3_ANNOTATIONS) - .labels(V3_LABELS) - .build(); - - // Domain-typed equivalent of V3_METADATA, for building expected domain objects (CloudEntity.getV3Metadata() is domain.Metadata). - static final org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata V3_METADATA_DOMAIN = - org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata.builder() - .annotations(V3_ANNOTATIONS) - .labels(V3_LABELS) - .build(); - - static final CloudMetadata EXPECTED_METADATA_PARSED_FROM_V3_RESOURCE = ImmutableCloudMetadata.builder() - .guid(GUID) - .createdAt(CREATED_AT) - .updatedAt(UPDATED_AT) - .build(); - static final CloudMetadata EXPECTED_METADATA_V3 = ImmutableCloudMetadata.builder() - .guid(GUID) - .createdAt(CREATED_AT) - .updatedAt(UPDATED_AT) - .build(); - - static LocalDateTime fromZonedDateTime(ZonedDateTime dateTime) { - return dateTime.toLocalDateTime(); - } - - @Test - void testParseV3ResourceMetadata() { - Organization resource = OrganizationResource.builder() - .id(GUID_STRING) - .createdAt(CREATED_AT_STRING) - .updatedAt(UPDATED_AT_STRING) - .name(NAME) - .metadata(V3_METADATA) - .build(); - - CloudMetadata metadata = RawCloudEntity.parseResourceMetadata(resource); - assertEquals(EXPECTED_METADATA_PARSED_FROM_V3_RESOURCE, metadata); - } - - @Test - void testParseNullableGuid() { - assertNull(RawCloudEntity.parseNullableGuid(null)); - } - - @Test - void testParseNullableDate() { - assertNull(RawCloudEntity.parseNullableDate(null)); - } - - @Test - void testParseGuid() { - assertEquals(GUID, RawCloudEntity.parseGuid(GUID_STRING)); - } - - @Test - void testParseGuidWithInvalidGuid() { - assertNull(RawCloudEntity.parseGuid("foo")); - } - - @Test - void testParseDateWithInvalidDate() { - assertNull(RawCloudEntity.parseDate("foo")); - } - - @Test - void testParseDateWithInvalidFormat() { - assertNull(RawCloudEntity.parseDate("16.07.2019 15:30:25")); - } - - @Test - void testParseDate() { - assertEquals(CREATED_AT, RawCloudEntity.parseDate(CREATED_AT_STRING)); - } - - @Test - void testParseEnum() { - CloudApplication.State state = RawCloudEntity.parseEnum(ApplicationState.STARTED, CloudApplication.State.class); - assertEquals(CloudApplication.State.STARTED, state); - } - - @Test - void testParseEnumWithIncompatibleEnumTypes() { - Assertions.assertThrows(IllegalArgumentException.class, - () -> RawCloudEntity.parseEnum(ApplicationState.STARTED, CloudBuild.State.class)); - } - - @Test - void testDeriveFromNullable() { - assertEquals(NAME, RawCloudEntity.deriveFromNullable(() -> NAME)); - } - - @Test - void testDeriveFromNullableWithNull() { - assertNull(RawCloudEntity.deriveFromNullable(null)); - } - - @Test - void testDerive() { - List> derivables = Arrays.asList(() -> NAME, () -> GUID_STRING); - assertEquals(Arrays.asList(NAME, GUID_STRING), RawCloudEntity.derive(derivables)); - } - - static void testDerive(T expected, Derivable derivable) { - assertEquals(expected, derivable.derive()); - } - - static ToOneRelationship buildToOneRelationship(String id) { - return ToOneRelationship.builder() - .data(Relationship.builder() - .id(id) - .build()) - .build(); - } - -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudEventTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudEventTest.java deleted file mode 100644 index 2a826d312a..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudEventTest.java +++ /dev/null @@ -1,126 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.UUID; - -import org.cloudfoundry.client.v3.auditevents.AuditEventActor; -import org.cloudfoundry.client.v3.auditevents.AuditEventResource; -import org.cloudfoundry.client.v3.auditevents.AuditEventTarget; -import org.junit.jupiter.api.Test; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEvent; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEvent.Participant; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudEvent; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudEvent.ImmutableParticipant; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; - -class RawCloudEventTest { - - private static final String TARGET_GUID_STRING = "b7c57058-afc0-43c9-afee-64019e850bef"; - private static final String TARGET_NAME = "foo"; - private static final String TARGET_TYPE = "app"; - private static final String ACTOR_GUID_STRING = "72c1e48a-9629-4cfe-a64c-f53851d81f61"; - private static final String ACTOR_NAME = "john"; - private static final String ACTOR_TYPE = "user"; - private static final String TIMESTAMP_STRING = "2019-07-03T20:00:46Z"; - private static final String TYPE = "audit.app.create"; - - private static final UUID TARGET_GUID = UUID.fromString(TARGET_GUID_STRING); - private static final UUID ACTOR_GUID = UUID.fromString(ACTOR_GUID_STRING); - private static final LocalDateTime TIMESTAMP = RawCloudEntityTest.fromZonedDateTime(ZonedDateTime.of(2019, 7, 3, 20, 0, 46, 0, - ZoneId.of("Z"))); - - @Test - void testDerive() { - RawCloudEntityTest.testDerive(buildExpectedEvent(), buildRawEvent()); - } - - @Test - void testDeriveWithEmptyResponse() { - RawCloudEntityTest.testDerive(buildEmptyExpectedEvent(), buildEmptyRawEvent()); - } - - private CloudEvent buildExpectedEvent() { - return ImmutableCloudEvent.builder() - .metadata(ImmutableCloudMetadata.builder() - .guid(RawCloudEntityTest.GUID) - .createdAt(TIMESTAMP) - .updatedAt(RawCloudEntityTest.UPDATED_AT) - .build()) - .target(buildExpectedTarget()) - .actor(buildExpectedActor()) - .type(TYPE) - .build(); - } - - private Participant buildExpectedTarget() { - return ImmutableParticipant.builder() - .guid(TARGET_GUID) - .name(TARGET_NAME) - .type(TARGET_TYPE) - .build(); - } - - private Participant buildExpectedActor() { - return ImmutableParticipant.builder() - .guid(ACTOR_GUID) - .name(ACTOR_NAME) - .type(ACTOR_TYPE) - .build(); - } - - private CloudEvent buildEmptyExpectedEvent() { - return ImmutableCloudEvent.builder() - .metadata(ImmutableCloudMetadata.builder() - .guid(RawCloudEntityTest.GUID) - .createdAt(TIMESTAMP) - .updatedAt(RawCloudEntityTest.UPDATED_AT) - .build()) - .target(buildEmptyParticipant()) - .actor(buildEmptyParticipant()) - .build(); - } - - private Participant buildEmptyParticipant() { - return ImmutableParticipant.builder() - .build(); - } - - private RawCloudEvent buildRawEvent() { - return ImmutableRawCloudEvent.of(buildTestResource()); - } - - private AuditEventResource buildTestResource() { - return AuditEventResource.builder() - .id(RawCloudEntityTest.GUID_STRING) - .createdAt(TIMESTAMP_STRING) - .updatedAt(RawCloudEntityTest.UPDATED_AT_STRING) - .type(TYPE) - .auditEventActor(AuditEventActor.builder() - .id(ACTOR_GUID_STRING) - .name(ACTOR_NAME) - .type(ACTOR_TYPE) - .build()) - .auditEventTarget(AuditEventTarget.builder() - .id(TARGET_GUID_STRING) - .type(TARGET_TYPE) - .name(TARGET_NAME) - .build()) - .build(); - } - - private RawCloudEvent buildEmptyRawEvent() { - return ImmutableRawCloudEvent.of(buildEmptyTestResource()); - } - - private AuditEventResource buildEmptyTestResource() { - return AuditEventResource.builder() - .id(RawCloudEntityTest.GUID_STRING) - .createdAt(TIMESTAMP_STRING) - .updatedAt(RawCloudEntityTest.UPDATED_AT_STRING) - .build(); - } - -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudPackageTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudPackageTest.java deleted file mode 100644 index 2f636ea79b..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudPackageTest.java +++ /dev/null @@ -1,113 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import org.cloudfoundry.client.v3.Checksum; -import org.cloudfoundry.client.v3.ChecksumType; -import org.cloudfoundry.client.v3.packages.BitsData; -import org.cloudfoundry.client.v3.packages.DockerData; -import org.cloudfoundry.client.v3.packages.Package; -import org.cloudfoundry.client.v3.packages.PackageData; -import org.cloudfoundry.client.v3.packages.PackageResource; -import org.cloudfoundry.client.v3.packages.PackageState; -import org.cloudfoundry.client.v3.packages.PackageType; -import org.junit.jupiter.api.Test; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudPackage; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableBitsData; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudPackage; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableDockerData; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Status; - -class RawCloudPackageTest { - - private static final PackageState STATE = PackageState.EXPIRED; - private static final String ERROR = "blabla"; - private static final String DOCKER_IMAGE = "cloudfoundry/test"; - private static final ChecksumType CHECKSUM_TYPE = ChecksumType.SHA256; - private static final String CHECKSUM_VALUE = "7251a608605c0f45710f8415bba0d117c90598824697a2a5d00850ea9b179112"; - - private static final Status EXPECTED_STATUS = Status.EXPIRED; - private static final String EXPECTED_CHECKSUM_ALGORITHM = CHECKSUM_TYPE.toString(); - - @Test - void testDeriveWithBitsData() { - RawCloudEntityTest.testDerive(buildExpectedPackage(CloudPackage.Type.BITS), buildRawPackage(PackageType.BITS)); - } - - @Test - void testDeriveWithDockerData() { - RawCloudEntityTest.testDerive(buildExpectedPackage(CloudPackage.Type.DOCKER), buildRawPackage(PackageType.DOCKER)); - } - - private static CloudPackage buildExpectedPackage(CloudPackage.Type type) { - return ImmutableCloudPackage.builder() - .metadata(RawCloudEntityTest.EXPECTED_METADATA_PARSED_FROM_V3_RESOURCE) - .data(buildExpectedData(type)) - .type(type) - .status(EXPECTED_STATUS) - .build(); - } - - private static CloudPackage.PackageData buildExpectedData(CloudPackage.Type type) { - if (type == CloudPackage.Type.DOCKER) { - return ImmutableDockerData.builder() - .image(DOCKER_IMAGE) - .build(); - } - return ImmutableBitsData.builder() - .checksum(buildExpectedChecksum()) - .error(ERROR) - .build(); - } - - private static org.cloudfoundry.multiapps.controller.client.facade.domain.BitsData.Checksum buildExpectedChecksum() { - return ImmutableBitsData.ImmutableChecksum.builder() - .algorithm(EXPECTED_CHECKSUM_ALGORITHM) - .value(CHECKSUM_VALUE) - .build(); - } - - private static RawCloudPackage buildRawPackage(PackageType type) { - return ImmutableRawCloudPackage.builder() - .resource(buildTestResource(type)) - .build(); - } - - private static Package buildTestResource(PackageType type) { - return PackageResource.builder() - .id(RawCloudEntityTest.GUID_STRING) - .createdAt(RawCloudEntityTest.CREATED_AT_STRING) - .updatedAt(RawCloudEntityTest.UPDATED_AT_STRING) - .type(type) - .data(buildTestData(type)) - .state(STATE) - .build(); - } - - private static PackageData buildTestData(PackageType type) { - if (type == PackageType.DOCKER) { - return buildDockerData(); - } - return buildBitsData(); - } - - private static DockerData buildDockerData() { - return DockerData.builder() - .image(DOCKER_IMAGE) - .build(); - } - - private static BitsData buildBitsData() { - return BitsData.builder() - .checksum(buildTestChecksum()) - .error(ERROR) - .build(); - } - - private static Checksum buildTestChecksum() { - return Checksum.builder() - .type(CHECKSUM_TYPE) - .value(CHECKSUM_VALUE) - .build(); - } - -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudProcessTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudProcessTest.java deleted file mode 100644 index b57652433c..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudProcessTest.java +++ /dev/null @@ -1,146 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import org.cloudfoundry.client.v3.Metadata; -import org.cloudfoundry.client.v3.processes.Data; -import org.cloudfoundry.client.v3.processes.HealthCheck; -import org.cloudfoundry.client.v3.processes.HealthCheckType; -import org.cloudfoundry.client.v3.processes.ProcessRelationships; -import org.cloudfoundry.client.v3.processes.ProcessResource; -import org.cloudfoundry.client.v3.processes.ReadinessHealthCheck; -import org.cloudfoundry.client.v3.processes.ReadinessHealthCheckType; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudProcess; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudProcess; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; - -class RawCloudProcessTest { - - private static final String COMMAND = "bundle exec rackup"; - private static final Integer DISK_IN_MB = 1024; - private static final Integer INSTANCES = 2; - private static final Integer MEMORY_IN_MB = 512; - private static final String HTTP_ENDPOINT = "/health"; - private static final Integer TIMEOUT = 30; - private static final Integer INVOCATION_TIMEOUT = 5; - private static final Integer INTERVAL = 10; - private static final String READINESS_HTTP_ENDPOINT = "/ready"; - private static final Integer READINESS_INVOCATION_TIMEOUT = 4; - private static final Integer READINESS_INTERVAL = 8; - - @Test - void testDeriveWithFullHealthCheckDataIncludingInterval() { - RawCloudProcess raw = ImmutableRawCloudProcess.of(buildProcessResource(HealthCheckType.HTTP, buildHealthCheckData(), - ReadinessHealthCheckType.HTTP, - buildReadinessHealthCheckData())); - - CloudProcess derived = raw.derive(); - - assertEquals(buildExpected(org.cloudfoundry.multiapps.controller.client.facade.domain.HealthCheckType.HTTP, INTERVAL, - ReadinessHealthCheckType.HTTP.getValue()), - derived); - } - - @Test - void testDeriveWithMissingHealthCheckDataLeavesIntervalNull() { - // healthCheck.getData() returns null -> all four health-check Integer/String fields stay null - RawCloudProcess raw = ImmutableRawCloudProcess.of(buildProcessResource(HealthCheckType.PORT, null, - ReadinessHealthCheckType.PORT, null)); - - CloudProcess derived = raw.derive(); - - assertEquals(org.cloudfoundry.multiapps.controller.client.facade.domain.HealthCheckType.PORT, derived.getHealthCheckType()); - assertNull(derived.getHealthCheckTimeout()); - assertNull(derived.getHealthCheckInvocationTimeout()); - assertNull(derived.getHealthCheckHttpEndpoint()); - assertNull(derived.getHealthCheckInterval()); - assertNull(derived.getReadinessHealthCheckInvocationTimeout()); - assertNull(derived.getReadinessHealthCheckHttpEndpoint()); - assertNull(derived.getReadinessHealthCheckInterval()); - } - - @Test - void testDeriveWithHealthCheckDataPresentButIntervalNull() { - Data dataWithoutInterval = Data.builder() - .endpoint(HTTP_ENDPOINT) - .timeout(TIMEOUT) - .invocationTimeout(INVOCATION_TIMEOUT) - .build(); - RawCloudProcess raw = ImmutableRawCloudProcess.of(buildProcessResource(HealthCheckType.HTTP, dataWithoutInterval, - ReadinessHealthCheckType.PORT, null)); - - CloudProcess derived = raw.derive(); - - assertEquals(TIMEOUT, derived.getHealthCheckTimeout()); - assertEquals(INVOCATION_TIMEOUT, derived.getHealthCheckInvocationTimeout()); - assertEquals(HTTP_ENDPOINT, derived.getHealthCheckHttpEndpoint()); - assertNull(derived.getHealthCheckInterval()); - } - - private static ProcessResource buildProcessResource(HealthCheckType healthCheckType, Data healthCheckData, - ReadinessHealthCheckType readinessHealthCheckType, Data readinessHealthCheckData) { - HealthCheck.Builder healthCheckBuilder = HealthCheck.builder() - .type(healthCheckType); - if (healthCheckData != null) { - healthCheckBuilder.data(healthCheckData); - } - ReadinessHealthCheck.Builder readinessBuilder = ReadinessHealthCheck.builder() - .type(readinessHealthCheckType); - if (readinessHealthCheckData != null) { - readinessBuilder.data(readinessHealthCheckData); - } - return ProcessResource.builder() - .id(RawCloudEntityTest.GUID_STRING) - .createdAt(RawCloudEntityTest.CREATED_AT_STRING) - .updatedAt(RawCloudEntityTest.UPDATED_AT_STRING) - .command(COMMAND) - .diskInMb(DISK_IN_MB) - .instances(INSTANCES) - .memoryInMb(MEMORY_IN_MB) - .type("web") - .metadata(Metadata.builder() - .build()) - .relationships(ProcessRelationships.builder() - .build()) - .healthCheck(healthCheckBuilder.build()) - .readinessHealthCheck(readinessBuilder.build()) - .build(); - } - - private static Data buildHealthCheckData() { - return Data.builder() - .endpoint(HTTP_ENDPOINT) - .timeout(TIMEOUT) - .invocationTimeout(INVOCATION_TIMEOUT) - .interval(INTERVAL) - .build(); - } - - private static Data buildReadinessHealthCheckData() { - return Data.builder() - .endpoint(READINESS_HTTP_ENDPOINT) - .invocationTimeout(READINESS_INVOCATION_TIMEOUT) - .interval(READINESS_INTERVAL) - .build(); - } - - private static CloudProcess buildExpected(org.cloudfoundry.multiapps.controller.client.facade.domain.HealthCheckType type, - Integer interval, String readinessHealthCheckType) { - return ImmutableCloudProcess.builder() - .command(COMMAND) - .instances(INSTANCES) - .memoryInMb(MEMORY_IN_MB) - .diskInMb(DISK_IN_MB) - .healthCheckType(type) - .healthCheckHttpEndpoint(HTTP_ENDPOINT) - .healthCheckTimeout(TIMEOUT) - .healthCheckInvocationTimeout(INVOCATION_TIMEOUT) - .healthCheckInterval(interval) - .readinessHealthCheckType(readinessHealthCheckType) - .readinessHealthCheckHttpEndpoint(READINESS_HTTP_ENDPOINT) - .readinessHealthCheckInvocationTimeout(READINESS_INVOCATION_TIMEOUT) - .readinessHealthCheckInterval(READINESS_INTERVAL) - .build(); - } -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudRouteTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudRouteTest.java deleted file mode 100644 index 94f19d18d3..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudRouteTest.java +++ /dev/null @@ -1,107 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.util.List; -import java.util.UUID; - -import org.cloudfoundry.client.v3.Relationship; -import org.cloudfoundry.client.v3.ToOneRelationship; -import org.cloudfoundry.client.v3.routes.Application; -import org.cloudfoundry.client.v3.routes.Destination; -import org.cloudfoundry.client.v3.routes.Route; -import org.cloudfoundry.client.v3.routes.RouteRelationships; -import org.cloudfoundry.client.v3.routes.RouteResource; -import org.junit.jupiter.api.Test; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudDomain; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudRoute; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudDomain; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudRoute; - -class RawCloudRouteTest { - - private static final String HOST = "foo"; - private static final String DOMAIN_NAME = "example.com"; - private static final CloudDomain DOMAIN = buildTestDomain(); - private static final Integer APPS_USING_ROUTE = 2; - private static final UUID DESTINATION_1_GUID = UUID.randomUUID(); - private static final UUID APPLICATION_1_GUID = UUID.randomUUID(); - private static final String DESTINATION_1_PROTOCOL = "http2"; - private static final UUID DESTINATION_2_GUID = UUID.randomUUID(); - private static final UUID APPLICATION_2_GUID = UUID.randomUUID(); - private static final String DESTINATION_2_PROTOCOL = "http1"; - private static final List DESTINATIONS = buildTestDestinations(); - - @Test - void testDerive() { - RawCloudEntityTest.testDerive(buildExpectedRoute(), buildRawRoute()); - } - - private static CloudRoute buildExpectedRoute() { - return ImmutableCloudRoute.builder() - .metadata(RawCloudEntityTest.EXPECTED_METADATA_V3) - .host(HOST) - .domain(DOMAIN) - .path("") - .appsUsingRoute(APPS_USING_ROUTE) - .url(HOST + "." + DOMAIN_NAME) - .build(); - } - - private static RawCloudRoute buildRawRoute() { - return ImmutableRawCloudRoute.builder() - .route(buildTestRoute()) - .build(); - } - - private static Route buildTestRoute() { - return RouteResource.builder() - .id(RawCloudEntityTest.GUID_STRING) - .createdAt(RawCloudEntityTest.CREATED_AT_STRING) - .updatedAt(RawCloudEntityTest.UPDATED_AT_STRING) - .relationships(RouteRelationships.builder() - .space(buildToOneRelationship(RawCloudEntityTest.GUID)) - .domain(buildToOneRelationship(RawCloudEntityTest.GUID)) - .build()) - .metadata(RawCloudEntityTest.V3_METADATA) - .host(HOST) - .path("") - .url(HOST + "." + DOMAIN_NAME) - .addAllDestinations(DESTINATIONS) - .build(); - } - - private static CloudDomain buildTestDomain() { - return ImmutableCloudDomain.builder() - .metadata(RawCloudEntityTest.EXPECTED_METADATA_V3) - .name(DOMAIN_NAME) - .build(); - } - - private static List buildTestDestinations() { - return List.of(buildDestination(DESTINATION_1_GUID.toString(), APPLICATION_1_GUID.toString(), DESTINATION_1_PROTOCOL), - buildDestination(DESTINATION_2_GUID.toString(), APPLICATION_2_GUID.toString(), DESTINATION_2_PROTOCOL)); - } - - private static Destination buildDestination(String destinationGuid, String applicationGuid, String protocol) { - return Destination.builder() - .destinationId(destinationGuid) - .application(Application.builder() - .applicationId(applicationGuid) - .build()) - .protocol(protocol) - .build(); - } - - private static ToOneRelationship buildToOneRelationship(UUID guid) { - return ToOneRelationship.builder() - .data(buildRelationship(guid)) - .build(); - } - - private static Relationship buildRelationship(UUID guid) { - return Relationship.builder() - .id(guid.toString()) - .build(); - } - -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceBindingTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceBindingTest.java deleted file mode 100644 index b2d1600aa8..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceBindingTest.java +++ /dev/null @@ -1,74 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.time.LocalDateTime; -import java.util.UUID; - -import org.cloudfoundry.client.v3.LastOperation; -import org.cloudfoundry.client.v3.servicebindings.ServiceBindingRelationships; -import org.cloudfoundry.client.v3.servicebindings.ServiceBindingResource; -import org.cloudfoundry.client.v3.servicebindings.ServiceBindingType; -import org.junit.jupiter.api.Test; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBinding; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceBinding; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceCredentialBindingOperation; - -class RawCloudServiceBindingTest { - - private static final String APPLICATION_GUID_STRING = "3725650a-8725-4401-a949-c68f83d54a86"; - private static final String SERVICE_INSTANCE_GUID_STRING = "3725650a-8725-4401-a949-c68f83d54a86"; - private static final UUID APPLICATION_GUID = UUID.fromString(APPLICATION_GUID_STRING); - private static final UUID SERVICE_INSTANCE_GUID = UUID.fromString(SERVICE_INSTANCE_GUID_STRING); - private static final LastOperation LAST_OPERATION = buildLastOperation(); - - @Test - void testDerive() { - RawCloudEntityTest.testDerive(buildExpectedServiceBinding(), buildRawServiceBinding()); - } - - private static CloudServiceBinding buildExpectedServiceBinding() { - return ImmutableCloudServiceBinding.builder() - .metadata(RawCloudEntityTest.EXPECTED_METADATA_V3) - .applicationGuid(APPLICATION_GUID) - .serviceInstanceGuid(SERVICE_INSTANCE_GUID) - .serviceBindingOperation(ServiceCredentialBindingOperation.from( - new org.cloudfoundry.multiapps.controller.client.facade.domain.LastOperation( - LAST_OPERATION.getType(), LAST_OPERATION.getState(), LAST_OPERATION.getDescription(), - LAST_OPERATION.getCreatedAt(), LAST_OPERATION.getUpdatedAt()))) - .build(); - } - - private static RawCloudServiceBinding buildRawServiceBinding() { - return ImmutableRawCloudServiceBinding.builder() - .serviceBinding(buildTestResource()) - - .build(); - } - - private static ServiceBindingResource buildTestResource() { - return ServiceBindingResource.builder() - .id(RawCloudEntityTest.GUID_STRING) - .createdAt(RawCloudEntityTest.CREATED_AT_STRING) - .updatedAt(RawCloudEntityTest.UPDATED_AT_STRING) - .type(ServiceBindingType.APPLICATION) - .lastOperation(LAST_OPERATION) - .relationships(ServiceBindingRelationships.builder() - .serviceInstance(RawCloudEntityTest.buildToOneRelationship(SERVICE_INSTANCE_GUID_STRING)) - .application(RawCloudEntityTest.buildToOneRelationship(APPLICATION_GUID_STRING)) - .build()) - .build(); - } - - private static LastOperation buildLastOperation() { - return LastOperation.builder() - .state(ServiceCredentialBindingOperation.State.SUCCEEDED.toString()) - .type(ServiceCredentialBindingOperation.Type.CREATE.toString()) - .createdAt(LocalDateTime.now() - .toString()) - .updatedAt(LocalDateTime.now() - .toString()) - .description("Service binding created") - .build(); - } - -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceBrokerTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceBrokerTest.java deleted file mode 100644 index c3c9dca252..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceBrokerTest.java +++ /dev/null @@ -1,55 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import org.cloudfoundry.client.v3.Relationship; -import org.cloudfoundry.client.v3.ToOneRelationship; -import org.cloudfoundry.client.v3.servicebrokers.ServiceBrokerRelationships; -import org.cloudfoundry.client.v3.servicebrokers.ServiceBrokerResource; -import org.junit.jupiter.api.Test; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBroker; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceBroker; - -class RawCloudServiceBrokerTest { - - private static final String NAME = "auditlog-broker"; - private static final String URL = "https://auditlog-broker.example.com"; - private static final String SPACE_GUID_STRING = "ef93547f-74c3-4bad-ba69-b7dc4f212622"; - - @Test - void testDerive() { - RawCloudEntityTest.testDerive(buildExpectedServiceBroker(), buildRawServiceBroker()); - } - - private static CloudServiceBroker buildExpectedServiceBroker() { - return ImmutableCloudServiceBroker.builder() - .metadata(RawCloudEntityTest.EXPECTED_METADATA_PARSED_FROM_V3_RESOURCE) - .name(NAME) - .spaceGuid(SPACE_GUID_STRING) - .url(URL) - .build(); - } - - private static RawCloudServiceBroker buildRawServiceBroker() { - return ImmutableRawCloudServiceBroker.builder() - .serviceBroker(buildTestServiceBroker()) - .build(); - } - - private static ServiceBrokerResource buildTestServiceBroker() { - return ServiceBrokerResource.builder() - .id(RawCloudEntityTest.GUID_STRING) - .createdAt(RawCloudEntityTest.CREATED_AT_STRING) - .updatedAt(RawCloudEntityTest.UPDATED_AT_STRING) - .name(NAME) - .url(URL) - .relationships(ServiceBrokerRelationships.builder() - .space(ToOneRelationship.builder() - .data(Relationship.builder() - .id(SPACE_GUID_STRING) - .build()) - .build()) - .build()) - .build(); - } - -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceInstanceTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceInstanceTest.java deleted file mode 100644 index 1c41c4f866..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceInstanceTest.java +++ /dev/null @@ -1,117 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -import org.cloudfoundry.client.v3.LastOperation; -import org.cloudfoundry.client.v3.MaintenanceInfo; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; -import org.cloudfoundry.client.v3.Relationship; -import org.cloudfoundry.client.v3.ToOneRelationship; -import org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceRelationships; -import org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceResource; -import org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceType; -import org.junit.jupiter.api.Test; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceInstance; - -class RawCloudServiceInstanceTest { - - private static final String SERVICE_INSTANCE_ID = "7cb26016-342d-11ed-a261-0242ac120002"; - private static final String SERVICE_INSTANCE_NAME = "v3-service-instance"; - private static final String DATE = "2022-01-01T13:35:11Z"; - private static final String DASHBOARD_URL = "dashboard-url"; - private static final String LAST_OPERATION_DESCRIPTION = "Last operation has succeeded"; - private static final String LAST_OPERATION_STATE = "succeeded"; - private static final String LAST_OPERATION_TYPE = "create"; - private static final String MAINTENANCE_INFO_DESCRIPTION = "A long time ago"; - private static final String MAINTENANCE_INFO_VERSION = "0.9.9"; - private static final Map METADATA_ANNOTATIONS = Map.of("foo", "bar"); - private static final Map METADATA_LABELS = Map.of("baz", "mop"); - private static final String SPACE_GUID = "5f5a8db4-342c-11ed-a261-0242ac120002"; - private static final String SERVICE_PLAN_GUID = "810a0322-342c-11ed-a261-0242ac120002"; - private static final String ROUTE_SERVICE_URL = "https://route-service-url"; - private static final String SYSLOG_DRAIN_URL = "https://syslog-drain"; - private static final List TAGS = List.of("one", "two", "three"); - private static final ServiceInstanceType SERVICE_INSTANCE_TYPE = ServiceInstanceType.MANAGED; - private static final boolean UPDATE_AVAILABLE = false; - - @Test - void testDerive() { - RawCloudEntityTest.testDerive(buildExpectedServiceInstance(), buildRawServiceInstance()); - } - - private CloudServiceInstance buildExpectedServiceInstance() { - return ImmutableCloudServiceInstance.builder() - .metadata(ImmutableCloudMetadata.builder() - .createdAt(ZonedDateTime.parse(DATE, - DateTimeFormatter.ISO_DATE_TIME) - .toLocalDateTime()) - .updatedAt(ZonedDateTime.parse(DATE, - DateTimeFormatter.ISO_DATE_TIME) - .toLocalDateTime()) - .guid(UUID.fromString(SERVICE_INSTANCE_ID)) - .build()) - .name(SERVICE_INSTANCE_NAME) - .v3Metadata(org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata.builder() - .annotations(METADATA_ANNOTATIONS) - .labels(METADATA_LABELS) - .build()) - .tags(TAGS) - .build(); - } - - private RawV3CloudServiceInstance buildRawServiceInstance() { - return ImmutableRawV3CloudServiceInstance.builder() - .serviceInstance(buildServiceInstanceResource()) - .build(); - } - - private ServiceInstanceResource buildServiceInstanceResource() { - return ServiceInstanceResource.builder() - .id(SERVICE_INSTANCE_ID) - .name(SERVICE_INSTANCE_NAME) - .createdAt(DATE) - .updatedAt(DATE) - .dashboardUrl(DASHBOARD_URL) - .lastOperation(LastOperation.builder() - .createdAt(DATE) - .description(LAST_OPERATION_DESCRIPTION) - .state(LAST_OPERATION_STATE) - .type(LAST_OPERATION_TYPE) - .updatedAt(DATE) - .build()) - .maintenanceInfo(MaintenanceInfo.builder() - .description(MAINTENANCE_INFO_DESCRIPTION) - .version(MAINTENANCE_INFO_VERSION) - .build()) - .metadata(org.cloudfoundry.client.v3.Metadata.builder() - .annotations(METADATA_ANNOTATIONS) - .labels(METADATA_LABELS) - .build()) - .relationships(ServiceInstanceRelationships.builder() - .space(ToOneRelationship.builder() - .data(Relationship.builder() - .id(SPACE_GUID) - .build()) - .build()) - .servicePlan(ToOneRelationship.builder() - .data(Relationship.builder() - .id(SERVICE_PLAN_GUID) - .build()) - .build()) - .build()) - .routeServiceUrl(ROUTE_SERVICE_URL) - .syslogDrainUrl(SYSLOG_DRAIN_URL) - .tags(TAGS) - .type(SERVICE_INSTANCE_TYPE) - .updateAvailable(UPDATE_AVAILABLE) - .build(); - } - -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceKeyTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceKeyTest.java deleted file mode 100644 index 4a8a69f161..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceKeyTest.java +++ /dev/null @@ -1,91 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.time.LocalDateTime; -import java.util.HashMap; -import java.util.Map; - -import org.cloudfoundry.client.v3.LastOperation; -import org.cloudfoundry.client.v3.servicebindings.ServiceBindingRelationships; -import org.cloudfoundry.client.v3.servicebindings.ServiceBindingResource; -import org.cloudfoundry.client.v3.servicebindings.ServiceBindingType; -import org.junit.jupiter.api.Test; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceInstance; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceKey; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceCredentialBindingOperation; - -class RawCloudServiceKeyTest { - - private static final String SERVICE_NAME = "foo"; - private static final String NAME = "bar"; - private static final Map CREDENTIALS = buildTestCredentials(); - private static final CloudServiceInstance SERVICE_INSTANCE = ImmutableCloudServiceInstance.builder() - .name(SERVICE_NAME) - .build(); - private static final LastOperation LAST_OPERATION = buildLastOperation(); - - @Test - void testDerive() { - RawCloudEntityTest.testDerive(buildExpectedServiceKey(), buildRawServiceKey()); - } - - private static CloudServiceKey buildExpectedServiceKey() { - return ImmutableCloudServiceKey.builder() - .metadata(RawCloudEntityTest.EXPECTED_METADATA_V3) - .v3Metadata(RawCloudEntityTest.V3_METADATA_DOMAIN) - .name(NAME) - .credentials(CREDENTIALS) - .serviceInstance(SERVICE_INSTANCE) - .serviceKeyOperation(ServiceCredentialBindingOperation.from( - new org.cloudfoundry.multiapps.controller.client.facade.domain.LastOperation( - LAST_OPERATION.getType(), LAST_OPERATION.getState(), LAST_OPERATION.getDescription(), - LAST_OPERATION.getCreatedAt(), LAST_OPERATION.getUpdatedAt()))) - .build(); - } - - private static RawCloudServiceKey buildRawServiceKey() { - return ImmutableRawCloudServiceKey.builder() - .serviceBindingResource(buildTestResource()) - .serviceInstance(SERVICE_INSTANCE) - .credentials(CREDENTIALS) - .build(); - } - - private static ServiceBindingResource buildTestResource() { - return ServiceBindingResource.builder() - .id(RawCloudEntityTest.GUID_STRING) - .createdAt(RawCloudEntityTest.CREATED_AT_STRING) - .updatedAt(RawCloudEntityTest.UPDATED_AT_STRING) - .metadata(RawCloudEntityTest.V3_METADATA) - .name(NAME) - .type(ServiceBindingType.KEY) - .lastOperation(LAST_OPERATION) - .relationships(ServiceBindingRelationships.builder() - .serviceInstance(RawCloudEntityTest.buildToOneRelationship("")) - .build()) - .build(); - } - - private static Map buildTestCredentials() { - Map parameters = new HashMap<>(); - parameters.put("foo", "bar"); - parameters.put("baz", false); - parameters.put("qux", 3.141); - return parameters; - } - - private static LastOperation buildLastOperation() { - return LastOperation.builder() - .state(ServiceCredentialBindingOperation.State.SUCCEEDED.toString()) - .type(ServiceCredentialBindingOperation.Type.CREATE.toString()) - .createdAt(LocalDateTime.now() - .toString()) - .updatedAt(LocalDateTime.now() - .toString()) - .description("Service key created") - .build(); - } - -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceOfferingTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceOfferingTest.java deleted file mode 100644 index b80fd6d986..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceOfferingTest.java +++ /dev/null @@ -1,107 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.util.List; -import java.util.Map; -import java.util.UUID; - -import org.cloudfoundry.client.v3.Relationship; -import org.cloudfoundry.client.v3.ToOneRelationship; -import org.cloudfoundry.client.v3.serviceofferings.BrokerCatalog; -import org.cloudfoundry.client.v3.serviceofferings.Features; -import org.cloudfoundry.client.v3.serviceofferings.ServiceOfferingRelationships; -import org.cloudfoundry.client.v3.serviceofferings.ServiceOfferingResource; -import org.junit.jupiter.api.Test; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceOffering; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServicePlan; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceOffering; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServicePlan; - -public class RawCloudServiceOfferingTest { - - private static final String NAME = "postgresql"; - static final boolean AVAILABLE = true; - static final boolean BINDABLE = true; - static final boolean ALLOW_CONTEXT_UPDATES = false; - static final boolean BINDINGS_RETRIEVABLE = true; - static final boolean INSTANCES_RETRIEVABLE = true; - static final boolean PLAN_UPDATEABLE = false; - static final boolean SHAREABLE = true; - static final String DESCRIPTION = "description"; - static final String DOCUMENTATION_URL = "/documentation"; - static final String UNIQUE_ID = "unique-id"; - static final String SERVICE_BROKER_GUID = UUID.randomUUID() - .toString(); - private static final Map EXTRA = Map.of("key-metadata", "value-metadata"); - private static final List PLANS = buildTestServiceBindings(); - - @Test - void testDerive() { - RawCloudEntityTest.testDerive(buildExpectedServiceOffering(), buildRawServiceOffering()); - } - - private static CloudServiceOffering buildExpectedServiceOffering() { - return ImmutableCloudServiceOffering.builder() - .metadata(RawCloudEntityTest.EXPECTED_METADATA_PARSED_FROM_V3_RESOURCE) - .brokerId(SERVICE_BROKER_GUID) - .name(NAME) - .isAvailable(AVAILABLE) - .isBindable(BINDABLE) - .isShareable(SHAREABLE) - .description(DESCRIPTION) - .extra(EXTRA) - .docUrl(DOCUMENTATION_URL) - .uniqueId(UNIQUE_ID) - .servicePlans(PLANS) - .build(); - } - - private static RawCloudServiceOffering buildRawServiceOffering() { - return ImmutableRawCloudServiceOffering.builder() - .serviceOffering(buildTestServiceOffering()) - .servicePlans(PLANS) - .build(); - } - - public static ServiceOfferingResource buildTestServiceOffering() { - return ServiceOfferingResource.builder() - .id(RawCloudEntityTest.GUID_STRING) - .createdAt(RawCloudEntityTest.CREATED_AT_STRING) - .updatedAt(RawCloudEntityTest.UPDATED_AT_STRING) - .available(AVAILABLE) - .name(NAME) - .brokerCatalog(BrokerCatalog.builder() - .brokerCatalogId(UNIQUE_ID) - .features(Features.builder() - .bindable(BINDABLE) - .allowContextUpdates(ALLOW_CONTEXT_UPDATES) - .bindingsRetrievable(BINDINGS_RETRIEVABLE) - .instancesRetrievable(INSTANCES_RETRIEVABLE) - .planUpdateable(PLAN_UPDATEABLE) - .build()) - .metadata(EXTRA) - .build()) - .relationships(ServiceOfferingRelationships.builder() - .serviceBroker(ToOneRelationship.builder() - .data(Relationship.builder() - .id(SERVICE_BROKER_GUID) - .build()) - .build()) - .build()) - .shareable(SHAREABLE) - .description(DESCRIPTION) - .documentationUrl(DOCUMENTATION_URL) - .build(); - } - - private static List buildTestServiceBindings() { - return List.of(buildTestServiceBinding()); - } - - private static CloudServicePlan buildTestServiceBinding() { - return ImmutableCloudServicePlan.builder() - .name("v9.4-small") - .build(); - } - -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServicePlanTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServicePlanTest.java deleted file mode 100644 index f43f5e6a6a..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServicePlanTest.java +++ /dev/null @@ -1,92 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.util.Map; -import java.util.UUID; - -import org.cloudfoundry.client.v3.Relationship; -import org.cloudfoundry.client.v3.ToOneRelationship; -import org.cloudfoundry.client.v3.serviceplans.BrokerCatalog; -import org.cloudfoundry.client.v3.serviceplans.Features; -import org.cloudfoundry.client.v3.serviceplans.Schema; -import org.cloudfoundry.client.v3.serviceplans.Schemas; -import org.cloudfoundry.client.v3.serviceplans.ServiceInstanceSchema; -import org.cloudfoundry.client.v3.serviceplans.ServicePlan; -import org.cloudfoundry.client.v3.serviceplans.ServicePlanRelationships; -import org.cloudfoundry.client.v3.serviceplans.ServicePlanResource; -import org.cloudfoundry.client.v3.serviceplans.Visibility; -import org.junit.jupiter.api.Test; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServicePlan; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServicePlan; - -public class RawCloudServicePlanTest { - - private static final String NAME = "v9.4-small"; - private static final String DESCRIPTION = "description"; - private static final Map EXTRA = Map.of("extra", "value"); - private static final String UNIQUE_ID = "unique-id"; - private static final boolean PUBLIC = true; - private static final boolean FREE = false; - private static final boolean PLAN_BINDABLE = true; - private static final boolean PLAN_UPDATABLE = true; - private static final boolean AVAILABLE = true; - private static final String SERVICE_OFFERING_ID = UUID.randomUUID() - .toString(); - - @Test - void testDerive() { - RawCloudEntityTest.testDerive(buildExpectedPlan(), buildRawServicePlan()); - } - - private static CloudServicePlan buildExpectedPlan() { - return ImmutableCloudServicePlan.builder() - .metadata(RawCloudEntityTest.EXPECTED_METADATA_PARSED_FROM_V3_RESOURCE) - .name(NAME) - .description(DESCRIPTION) - .extra(EXTRA) - .uniqueId(UNIQUE_ID) - .isPublic(PUBLIC) - .isFree(FREE) - .serviceOfferingId(SERVICE_OFFERING_ID) - .build(); - } - - private static RawCloudServicePlan buildRawServicePlan() { - return ImmutableRawCloudServicePlan.of(buildTestServicePlan(NAME)); - } - - public static ServicePlan buildTestServicePlan(String planName) { - return ServicePlanResource.builder() - .id(RawCloudEntityTest.GUID_STRING) - .createdAt(RawCloudEntityTest.CREATED_AT_STRING) - .updatedAt(RawCloudEntityTest.UPDATED_AT_STRING) - .available(AVAILABLE) - .name(planName) - .description(DESCRIPTION) - .brokerCatalog(BrokerCatalog.builder() - .metadata(EXTRA) - .brokerCatalogId(UNIQUE_ID) - .features(Features.builder() - .bindable(PLAN_BINDABLE) - .planUpdateable(PLAN_UPDATABLE) - .build()) - .build()) - .visibilityType(Visibility.PUBLIC) - .free(FREE) - .relationships(ServicePlanRelationships.builder() - .serviceOffering(ToOneRelationship.builder() - .data(Relationship.builder() - .id(SERVICE_OFFERING_ID) - .build()) - .build()) - .build()) - .schemas(Schemas.builder() - .serviceInstance(ServiceInstanceSchema.builder() - .create(Schema.builder() - .build()) - .build()) - .build()) - .build(); - } - -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceTest.java deleted file mode 100644 index 1eaf5f98e8..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudServiceTest.java +++ /dev/null @@ -1,130 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import java.util.Arrays; -import java.util.List; - -import org.cloudfoundry.client.v3.LastOperation; -import org.cloudfoundry.client.v3.Relationship; -import org.cloudfoundry.client.v3.ToOneRelationship; -import org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceResource; -import org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceType; -import org.cloudfoundry.client.v3.serviceofferings.BrokerCatalog; -import org.cloudfoundry.client.v3.serviceofferings.Features; -import org.cloudfoundry.client.v3.serviceofferings.ServiceOfferingRelationships; -import org.cloudfoundry.client.v3.serviceofferings.ServiceOfferingResource; -import org.junit.jupiter.api.Test; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceInstance; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceOperation; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceOperation.State; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceOperation.Type; - -class RawCloudServiceTest { - - private static final String NAME = "my-db"; - private static final String OFFERING_NAME = "postgresql"; - private static final String PLAN_NAME = "v9.4-small"; - private static final List TAGS = Arrays.asList("test-tag-1", "test-tag-2"); - private static final String LAST_OPERATION_TYPE_CREATE = "create"; - private static final String LAST_OPERATION_STATE_IN_PROGRESS = "in progress"; - private static final String LAST_OPERATION_STATE_SUCCEEDED = "succeeded"; - - @Test - void testDerive() { - RawCloudEntityTest.testDerive(buildExpectedService(), buildRawService()); - } - - @Test - void testDeriveWithUserProvidedService() { - RawCloudEntityTest.testDerive(buildExpectedUserProvidedService(), buildRawUserProvidedService()); - } - - private static CloudServiceInstance buildExpectedService() { - return ImmutableCloudServiceInstance.builder() - .metadata(RawCloudEntityTest.EXPECTED_METADATA_PARSED_FROM_V3_RESOURCE) - .name(NAME) - .plan(PLAN_NAME) - .label(OFFERING_NAME) - .type(org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType.MANAGED) - .tags(TAGS) - .lastOperation(new ServiceOperation(Type.CREATE, "", State.IN_PROGRESS)) - .build(); - } - - private static CloudServiceInstance buildExpectedUserProvidedService() { - return ImmutableCloudServiceInstance.builder() - .metadata(RawCloudEntityTest.EXPECTED_METADATA_PARSED_FROM_V3_RESOURCE) - .name(NAME) - .type(org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType.USER_PROVIDED) - .tags(TAGS) - .lastOperation(new ServiceOperation(Type.CREATE, "", State.SUCCEEDED)) - .build(); - } - - private static RawCloudServiceInstance buildRawService() { - return ImmutableRawCloudServiceInstance.builder() - .resource(buildTestResource(false, LastOperation.builder() - .type(LAST_OPERATION_TYPE_CREATE) - .state(LAST_OPERATION_STATE_IN_PROGRESS) - .build())) - .servicePlan(RawCloudServicePlanTest.buildTestServicePlan(PLAN_NAME)) - .serviceOffering(buildTestServiceOffering()) - .build(); - } - - private static RawCloudServiceInstance buildRawUserProvidedService() { - return ImmutableRawCloudServiceInstance.builder() - .resource(buildTestResource(true, LastOperation.builder() - .type(LAST_OPERATION_TYPE_CREATE) - .state(LAST_OPERATION_STATE_SUCCEEDED) - .build())) - .build(); - } - - private static ServiceInstanceResource buildTestResource(boolean isUserProvided, LastOperation lastOperation) { - ServiceInstanceResource.Builder serviceInstanceResourceBuilder = ServiceInstanceResource.builder() - .id(RawCloudEntityTest.GUID_STRING) - .createdAt(RawCloudEntityTest.CREATED_AT_STRING) - .updatedAt(RawCloudEntityTest.UPDATED_AT_STRING) - .name(NAME) - .type(ServiceInstanceType.MANAGED) - .addAllTags(TAGS) - .lastOperation(lastOperation); - if (isUserProvided) { - serviceInstanceResourceBuilder.type(ServiceInstanceType.USER_PROVIDED); - } - return serviceInstanceResourceBuilder.build(); - } - - private static ServiceOfferingResource buildTestServiceOffering() { - return ServiceOfferingResource.builder() - .id(RawCloudEntityTest.GUID_STRING) - .createdAt(RawCloudEntityTest.CREATED_AT_STRING) - .updatedAt(RawCloudEntityTest.UPDATED_AT_STRING) - .available(RawCloudServiceOfferingTest.AVAILABLE) - .name(OFFERING_NAME) - .brokerCatalog(BrokerCatalog.builder() - .brokerCatalogId(RawCloudServiceOfferingTest.UNIQUE_ID) - .features(Features.builder() - .bindable(RawCloudServiceOfferingTest.BINDABLE) - .allowContextUpdates(RawCloudServiceOfferingTest.ALLOW_CONTEXT_UPDATES) - .bindingsRetrievable(RawCloudServiceOfferingTest.BINDINGS_RETRIEVABLE) - .instancesRetrievable(RawCloudServiceOfferingTest.INSTANCES_RETRIEVABLE) - .planUpdateable(RawCloudServiceOfferingTest.PLAN_UPDATEABLE) - .build()) - .build()) - .relationships(ServiceOfferingRelationships.builder() - .serviceBroker(ToOneRelationship.builder() - .data(Relationship.builder() - .id(RawCloudServiceOfferingTest.SERVICE_BROKER_GUID) - .build()) - .build()) - .build()) - .shareable(RawCloudServiceOfferingTest.SHAREABLE) - .description(RawCloudServiceOfferingTest.DESCRIPTION) - .documentationUrl(RawCloudServiceOfferingTest.DOCUMENTATION_URL) - .build(); - } - -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudStackTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudStackTest.java deleted file mode 100644 index ac4da5f8b2..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudStackTest.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import org.cloudfoundry.client.v3.stacks.Stack; -import org.cloudfoundry.client.v3.stacks.StackResource; -import org.junit.jupiter.api.Test; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudStack; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudStack; - -class RawCloudStackTest { - - private static final String NAME = "cflinuxfs3"; - - @Test - void testDerive() { - RawCloudEntityTest.testDerive(buildExpectedStack(), buildRawStack()); - } - - private static CloudStack buildExpectedStack() { - return ImmutableCloudStack.builder() - .metadata(RawCloudEntityTest.EXPECTED_METADATA_V3) - .name(NAME) - .build(); - } - - private static RawCloudStack buildRawStack() { - return ImmutableRawCloudStack.of(buildTestResource()); - } - - private static Stack buildTestResource() { - return StackResource.builder() - .id(RawCloudEntityTest.GUID_STRING) - .createdAt(RawCloudEntityTest.CREATED_AT_STRING) - .updatedAt(RawCloudEntityTest.UPDATED_AT_STRING) - .metadata(RawCloudEntityTest.V3_METADATA) - .name(NAME) - .build(); - } - -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudTaskTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudTaskTest.java deleted file mode 100644 index 9d69a9da4c..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudTaskTest.java +++ /dev/null @@ -1,72 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import org.cloudfoundry.client.v3.tasks.Result; -import org.cloudfoundry.client.v3.tasks.Task; -import org.cloudfoundry.client.v3.tasks.TaskResource; -import org.cloudfoundry.client.v3.tasks.TaskState; -import org.junit.jupiter.api.Test; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudTask; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudTask; - -class RawCloudTaskTest { - - private static final String NAME = "echo"; - private static final String COMMAND = "echo \"Hello!\""; - private static final int DISK_IN_MB = 128; - private static final int MEMORY_IN_MB = 256; - private static final String FAILURE_REASON = "blabla"; - private static final String DROPLET_ID = "8ff24465-823b-4a55-85ee-a680f2c743cd"; - private static final int SEQUENCE_ID = 3; - private static final TaskState STATE = TaskState.FAILED; - - private static final CloudTask.State EXPECTED_STATE = CloudTask.State.FAILED; - - @Test - void testDerive() { - RawCloudEntityTest.testDerive(buildExpectedTask(), buildRawTask()); - } - - private static CloudTask buildExpectedTask() { - return ImmutableCloudTask.builder() - .metadata(RawCloudEntityTest.EXPECTED_METADATA_PARSED_FROM_V3_RESOURCE) - .name(NAME) - .command(COMMAND) - .limits(ImmutableCloudTask.ImmutableLimits.builder() - .disk(DISK_IN_MB) - .memory(MEMORY_IN_MB) - .build()) - .result(ImmutableCloudTask.ImmutableResult.builder() - .failureReason(FAILURE_REASON) - .build()) - .state(EXPECTED_STATE) - .build(); - } - - private static RawCloudTask buildRawTask() { - return ImmutableRawCloudTask.of(buildTestResource()); - } - - private static Task buildTestResource() { - return TaskResource.builder() - .id(RawCloudEntityTest.GUID_STRING) - .createdAt(RawCloudEntityTest.CREATED_AT_STRING) - .updatedAt(RawCloudEntityTest.UPDATED_AT_STRING) - .name(NAME) - .command(COMMAND) - .diskInMb(DISK_IN_MB) - .memoryInMb(MEMORY_IN_MB) - .state(STATE) - .result(buildTestResult()) - .dropletId(DROPLET_ID) - .sequenceId(SEQUENCE_ID) - .build(); - } - - private static Result buildTestResult() { - return Result.builder() - .failureReason(FAILURE_REASON) - .build(); - } - -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawInstancesInfoTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawInstancesInfoTest.java deleted file mode 100644 index 2051f7a207..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawInstancesInfoTest.java +++ /dev/null @@ -1,57 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import org.cloudfoundry.client.v3.applications.GetApplicationProcessStatisticsResponse; -import org.cloudfoundry.client.v3.processes.ProcessState; -import org.cloudfoundry.client.v3.processes.ProcessStatisticsResource; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableInstanceInfo; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableInstancesInfo; -import org.cloudfoundry.multiapps.controller.client.facade.domain.InstanceState; -import org.cloudfoundry.multiapps.controller.client.facade.domain.InstancesInfo; -import org.junit.jupiter.api.Test; - -class RawInstancesInfoTest { - - @Test - void testDeriveWithTrueRoutable() { - RawCloudEntityTest.testDerive(buildExpectedInstancesInfo(true), buildActualInstancesInfo("true")); - } - - @Test - void testDeriveWithFalseRoutable() { - RawCloudEntityTest.testDerive(buildExpectedInstancesInfo(false), buildActualInstancesInfo("false")); - } - - private InstancesInfo buildExpectedInstancesInfo(boolean expectedRoutable) { - return ImmutableInstancesInfo.builder() - .addInstance(ImmutableInstanceInfo.builder() - .index(0) - .state(InstanceState.RUNNING) - .isRoutable(expectedRoutable) - .build()) - .build(); - } - - private RawInstancesInfo buildActualInstancesInfo(String routable) { - return ImmutableRawInstancesInfo.builder() - .processStatisticsResponse(getApplicationProcessStatisticsResponse(routable)) - .build(); - } - - private GetApplicationProcessStatisticsResponse getApplicationProcessStatisticsResponse(String routable) { - return GetApplicationProcessStatisticsResponse.builder() - .resource(ProcessStatisticsResource.builder() - .index(0) - .details("Instance is in running state") - .diskQuota(1024L) - .state(ProcessState.RUNNING) - .memoryQuota(1024L) - .type("web") - .uptime(9042L) - .fileDescriptorQuota(1024L) - .routable(routable) - .host("10.244.16.10") - .build()) - .build(); - } - -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawUserRoleTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawUserRoleTest.java deleted file mode 100644 index bc650d4603..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawUserRoleTest.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.adapters; - -import org.cloudfoundry.client.v3.Relationship; -import org.cloudfoundry.client.v3.ToOneRelationship; -import org.cloudfoundry.client.v3.roles.RoleRelationships; -import org.cloudfoundry.client.v3.roles.RoleResource; -import org.cloudfoundry.client.v3.roles.RoleType; -import org.junit.jupiter.api.Test; - -import org.cloudfoundry.multiapps.controller.client.facade.domain.UserRole; - -class RawUserRoleTest { - - private static final String USER_ROLE_GUID = "c94b4588-3431-11ed-a261-0242ac120002"; - private static final String USER_GUID = "328eaf4e-3432-11ed-a261-0242ac120002"; - private static final String DATE = "2022-01-01T13:35:11Z"; - private static final RoleType USER_ROLE_TYPE = RoleType.SPACE_DEVELOPER; - - @Test - void testDerive() { - RawCloudEntityTest.testDerive(buildExpectedUserRole(), buildActualUserRole()); - } - - private UserRole buildExpectedUserRole() { - return UserRole.SPACE_DEVELOPER; - } - - private RawUserRole buildActualUserRole() { - return ImmutableRawUserRole.builder() - .roleResource(buildRoleResource()) - .build(); - } - - private RoleResource buildRoleResource() { - return RoleResource.builder() - .createdAt(DATE) - .id(USER_ROLE_GUID) - .type(USER_ROLE_TYPE) - .relationships(RoleRelationships.builder() - .user(ToOneRelationship.builder() - .data(Relationship.builder() - .id(USER_GUID) - .build()) - .build()) - .build()) - .build(); - } - -} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientImplTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientImplTest.java deleted file mode 100644 index 0519cbecce..0000000000 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientImplTest.java +++ /dev/null @@ -1,309 +0,0 @@ -package org.cloudfoundry.multiapps.controller.client.facade.rest; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.util.Collections; -import java.util.List; -import java.util.UUID; -import java.util.stream.Stream; - -import org.cloudfoundry.client.CloudFoundryClient; -import org.cloudfoundry.client.v3.BuildpackData; -import org.cloudfoundry.client.v3.ClientV3Exception; -import org.cloudfoundry.client.v3.Lifecycle; -import org.cloudfoundry.client.v3.LifecycleType; -import org.cloudfoundry.client.v3.Pagination; -import org.cloudfoundry.client.v3.applications.ApplicationResource; -import org.cloudfoundry.client.v3.applications.ApplicationState; -import org.cloudfoundry.client.v3.applications.ApplicationsV3; -import org.cloudfoundry.client.v3.applications.GetApplicationRequest; -import org.cloudfoundry.client.v3.applications.GetApplicationResponse; -import org.cloudfoundry.client.v3.applications.ListApplicationsRequest; -import org.cloudfoundry.client.v3.applications.ListApplicationsResponse; -import org.cloudfoundry.client.v3.applications.UpdateApplicationRequest; -import org.cloudfoundry.client.v3.applications.UpdateApplicationResponse; -import org.cloudfoundry.client.v3.serviceofferings.GetServiceOfferingRequest; -import org.cloudfoundry.client.v3.serviceofferings.GetServiceOfferingResponse; -import org.cloudfoundry.client.v3.serviceofferings.ServiceOffering; -import org.cloudfoundry.client.v3.serviceofferings.ServiceOfferingsV3; -import org.cloudfoundry.client.v3.serviceplans.GetServicePlanRequest; -import org.cloudfoundry.client.v3.serviceplans.GetServicePlanResponse; -import org.cloudfoundry.client.v3.serviceplans.ServicePlan; -import org.cloudfoundry.client.v3.serviceplans.ServicePlansV3; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; -import org.springframework.http.HttpStatus; - -import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.RawCloudServiceOfferingTest; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.RawCloudServicePlanTest; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudSpace; - -import reactor.core.publisher.Mono; - -class CloudControllerRestClientImplTest { - - private static final String SERVICE_PLAN_GUID = "1803e5a7-40c7-438e-b2be-e2045c9b7cda"; - private static final String SERVICE_INSTANCE_GUID = "26949ebb-a624-35c0-000-1110a01f1880"; - private static final String SERVICE_OFFERING_GUID = "1803e5a7-40c7-438e-b2be-e2045c9b7cda"; - private static final String PLAN_NAME = "test-plan"; - private static final String OLD_APPLICATION_NAME = "old-app-name"; - private static final String NEW_APPLICATION_NAME = "new-app-name"; - private static final String APPLICATION_CREATED_AT = "2024-01-26T10:00:00"; - private static final UUID APPLICATION_GUID = UUID.randomUUID(); - private static final UUID SPACE_GUID = UUID.randomUUID(); - - @Mock - private CloudFoundryClient delegate; - private CloudControllerRestClientImpl controllerClient; - - @BeforeEach - public void setUp() throws Exception { - MockitoAnnotations.openMocks(this) - .close(); - controllerClient = new CloudControllerRestClientImpl(delegate); - } - - @Test - void testGetServiceOffering() { - GetServiceOfferingRequest request = GetServiceOfferingRequest.builder() - .serviceOfferingId(SERVICE_PLAN_GUID) - .build(); - GetServiceOfferingResponse response = GetServiceOfferingResponse.builder() - .from(RawCloudServiceOfferingTest.buildTestServiceOffering()) - .build(); - - ServiceOfferingsV3 serviceOfferingsV3 = Mockito.mock(ServiceOfferingsV3.class); - Mockito.when(delegate.serviceOfferingsV3()) - .thenReturn(serviceOfferingsV3); - Mockito.when(serviceOfferingsV3.get(request)) - .thenReturn(Mono.just(response)); - - ServiceOffering serviceOffering = controllerClient.getServiceOffering(SERVICE_PLAN_GUID) - .block(); - - assertEquals(response, serviceOffering); - } - - public static Stream testGetServiceOfferingWithError() { - return Stream.of( -// @formatter:off - Arguments.of(HttpStatus.FORBIDDEN.value(), - "403 Forbidden: Service offering with guid \"1803e5a7-40c7-438e-b2be-e2045c9b7cda\" is not available."), - Arguments.of(HttpStatus.NOT_FOUND.value(), - "404 Not Found: Service offering with guid \"1803e5a7-40c7-438e-b2be-e2045c9b7cda\" not found.") -// @formatter:on - ); - } - - @ParameterizedTest - @MethodSource - void testGetServiceOfferingWithError(int errorCode, String expectedErrorMessage) { - GetServiceOfferingRequest request = GetServiceOfferingRequest.builder() - .serviceOfferingId(SERVICE_OFFERING_GUID) - .build(); - ServiceOfferingsV3 serviceOfferingsV3 = Mockito.mock(ServiceOfferingsV3.class); - Mockito.when(delegate.serviceOfferingsV3()) - .thenReturn(serviceOfferingsV3); - Mockito.when(serviceOfferingsV3.get(request)) - .thenReturn(Mono.error(clientV3Exception(errorCode))); - Exception cloudControllerException = assertThrows(CloudOperationException.class, - () -> controllerClient.getServiceOffering(SERVICE_OFFERING_GUID) - .block()); - assertEquals(expectedErrorMessage, cloudControllerException.getMessage()); - } - - public static Stream testGetServicePlanWithError() { - return Stream.of( -// @formatter:off - Arguments.of(HttpStatus.FORBIDDEN.value(), - "403 Forbidden: Service plan with guid \"1803e5a7-40c7-438e-b2be-e2045c9b7cda\" is not available for service instance \"26949ebb-a624-35c0-000-1110a01f1880\"."), - Arguments.of(HttpStatus.NOT_FOUND.value(), - "404 Not Found: Service plan with guid \"1803e5a7-40c7-438e-b2be-e2045c9b7cda\" for service instance with name \"26949ebb-a624-35c0-000-1110a01f1880\" was not found.") -// @formatter:on - ); - } - - @ParameterizedTest - @MethodSource - void testGetServicePlanWithError(int errorCode, String expectedErrorMessage) { - GetServicePlanRequest request = GetServicePlanRequest.builder() - .servicePlanId(SERVICE_PLAN_GUID) - .build(); - - ServicePlansV3 servicePlans = Mockito.mock(ServicePlansV3.class); - Mockito.when(delegate.servicePlansV3()) - .thenReturn(servicePlans); - Mockito.when(servicePlans.get(request)) - .thenReturn(Mono.error(clientV3Exception(errorCode))); - - Exception cloudControllerException = assertThrows(CloudOperationException.class, - () -> controllerClient.getServicePlanResource(SERVICE_PLAN_GUID, - SERVICE_INSTANCE_GUID) - .block()); - assertEquals(expectedErrorMessage, cloudControllerException.getMessage()); - } - - @Test - void testGetServicePlanResource() { - GetServicePlanRequest request = GetServicePlanRequest.builder() - .servicePlanId(SERVICE_PLAN_GUID) - .build(); - GetServicePlanResponse response = GetServicePlanResponse.builder() - .from(RawCloudServicePlanTest.buildTestServicePlan(PLAN_NAME)) - .build(); - - ServicePlansV3 servicePlans = Mockito.mock(ServicePlansV3.class); - Mockito.when(delegate.servicePlansV3()) - .thenReturn(servicePlans); - Mockito.when(servicePlans.get(request)) - .thenReturn(Mono.just(response)); - - ServicePlan servicePlanResource = controllerClient.getServicePlanResource(SERVICE_PLAN_GUID, SERVICE_INSTANCE_GUID) - .block(); - - assertEquals(response, servicePlanResource); - } - - private static Stream testRenameApplication() { - return Stream.of( - // (1) Successful application rename - Arguments.of(Mono.just(UpdateApplicationResponse.builder() - .name(NEW_APPLICATION_NAME) - .id(APPLICATION_GUID.toString()) - .createdAt(APPLICATION_CREATED_AT) - .state(ApplicationState.STARTED) - .lifecycle(getApplicationLifecycle()) - .build()), - null, false), - // (2) Controller's API returns 503 and rename was actually done - Arguments.of(Mono.error(new ClientV3Exception(503, - List.of(org.cloudfoundry.client.v3.Error.builder() - .code(170015) - .title("CF-RunnerUnavailable") - .detail("Runner is unavailable") - .build()))), - Mono.just(GetApplicationResponse.builder() - .name(NEW_APPLICATION_NAME) - .id(APPLICATION_GUID.toString()) - .createdAt(APPLICATION_CREATED_AT) - .state(ApplicationState.STARTED) - .lifecycle(getApplicationLifecycle()) - .build()), - false), - // (3) Controller's API fails with 500 and application wasn't renamed - Arguments.of(Mono.error(new ClientV3Exception(500, Collections.emptyList())), null, true), - // (4) Controller's API returns 503 and while make request to get current application name the request fails - Arguments.of(Mono.error(new ClientV3Exception(503, - List.of(org.cloudfoundry.client.v3.Error.builder() - .code(170015) - .title("CF-RunnerUnavailable") - .detail("Runner is unavailable") - .build()))), - Mono.error(new ClientV3Exception(500, Collections.emptyList())), true), - // (5) Controller's API returns 503 and application wasn't renamed - Arguments.of(Mono.error(new ClientV3Exception(503, - List.of(org.cloudfoundry.client.v3.Error.builder() - .code(170015) - .title("CF-RunnerUnavailable") - .detail("Runner is unavailable") - .build()))), - Mono.just(GetApplicationResponse.builder() - .name(OLD_APPLICATION_NAME) - .id(APPLICATION_GUID.toString()) - .createdAt(APPLICATION_CREATED_AT) - .state(ApplicationState.STARTED) - .lifecycle(getApplicationLifecycle()) - .build()), - true)); - } - - private static Lifecycle getApplicationLifecycle() { - return Lifecycle.builder() - .type(LifecycleType.BUILDPACK) - .data(BuildpackData.builder() - .build()) - .build(); - } - - @ParameterizedTest - @MethodSource - void testRenameApplication(Mono updateApplicationResponse, - Mono getApplicationResponse, boolean expectedFailure) { - ApplicationsV3 applicationsV3 = Mockito.mock(ApplicationsV3.class); - var updateAppRequest = UpdateApplicationRequest.builder() - .applicationId(APPLICATION_GUID.toString()) - .name(NEW_APPLICATION_NAME) - .build(); - prepareClientDelegate(applicationsV3, updateAppRequest, updateApplicationResponse, getApplicationResponse); - - if (expectedFailure) { - assertThrows(ClientV3Exception.class, () -> controllerClient.rename(OLD_APPLICATION_NAME, NEW_APPLICATION_NAME)); - return; - } - controllerClient.rename(OLD_APPLICATION_NAME, NEW_APPLICATION_NAME); - - Mockito.verify(applicationsV3, Mockito.atMostOnce()) - .update(updateAppRequest); - } - - private void prepareClientDelegate(ApplicationsV3 applicationsV3, UpdateApplicationRequest updateAppRequest, - Mono updateApplicationResponse, - Mono getApplicationResponse) { - initControllerClientWithTargetSpace(SPACE_GUID); - - Mockito.when(delegate.applicationsV3()) - .thenReturn(applicationsV3); - var listAppsRequest = ListApplicationsRequest.builder() - .spaceId(SPACE_GUID.toString()) - .name(OLD_APPLICATION_NAME) - .page(1) - .build(); - - var listAppsResponse = ListApplicationsResponse.builder() - .resource(ApplicationResource.builder() - .name(OLD_APPLICATION_NAME) - .id(APPLICATION_GUID.toString()) - .createdAt(APPLICATION_CREATED_AT) - .state(ApplicationState.STARTED) - .lifecycle(getApplicationLifecycle()) - .build()) - .pagination(Pagination.builder() - .totalResults(1) - .totalPages(1) - .build()) - .build(); - Mockito.when(applicationsV3.list(listAppsRequest)) - .thenReturn(Mono.just(listAppsResponse)); - Mockito.when(applicationsV3.update(updateAppRequest)) - .thenReturn(updateApplicationResponse); - var getAppRequest = GetApplicationRequest.builder() - .applicationId(APPLICATION_GUID.toString()) - .build(); - - Mockito.when(applicationsV3.get(getAppRequest)) - .thenReturn(getApplicationResponse); - } - - private void initControllerClientWithTargetSpace(UUID spaceGuid) { - CloudSpace target = ImmutableCloudSpace.builder() - .metadata(ImmutableCloudMetadata.builder() - .guid(spaceGuid) - .build()) - .build(); - this.controllerClient = new CloudControllerRestClientImpl(delegate, target); - } - - private ClientV3Exception clientV3Exception(int statusCode) { - return new ClientV3Exception(statusCode, Collections.emptyList()); - } -} diff --git a/multiapps-controller-core/pom.xml b/multiapps-controller-core/pom.xml index 797054b9d0..f07caae358 100644 --- a/multiapps-controller-core/pom.xml +++ b/multiapps-controller-core/pom.xml @@ -103,6 +103,14 @@ multiapps-controller-core-test test
+ + + io.jsonwebtoken + jjwt-api + ${jjwt.version} + test + org.cloudfoundry.multiapps multiapps-controller-client diff --git a/multiapps-controller-core/src/main/java/module-info.java b/multiapps-controller-core/src/main/java/module-info.java index b00243ee13..d24aaceea8 100644 --- a/multiapps-controller-core/src/main/java/module-info.java +++ b/multiapps-controller-core/src/main/java/module-info.java @@ -45,8 +45,6 @@ requires transitive org.cloudfoundry.multiapps.controller.client; requires transitive org.cloudfoundry.multiapps.controller.persistence; requires transitive org.cloudfoundry.multiapps.mta; - - requires org.cloudfoundry.client; requires com.fasterxml.jackson.annotation; requires com.fasterxml.jackson.core; requires com.fasterxml.jackson.databind; diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/CloudControllerClientFactory.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/CloudControllerClientFactory.java index 91fd2fe929..07a0a572b6 100644 --- a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/CloudControllerClientFactory.java +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/CloudControllerClientFactory.java @@ -68,8 +68,7 @@ public CloudControllerClient createClient(OAuth2AccessTokenWithAdditionalInfo to CloudCredentials credentials = createCredentials(token); oAuthClient.init(credentials); - var spaceClient = clientFactory.getCloudFoundryClientFactory() - .createSpaceClient(configuration.getControllerUrl(), oAuthClient, requestTags); + var spaceClient = clientFactory.createSpaceClient(configuration.getControllerUrl(), oAuthClient, requestTags); CloudSpace target = spaceClient.getSpace(UUID.fromString(spaceId)); CloudControllerRestClient controllerClient = clientFactory.createClient(configuration.getControllerUrl(), credentials, target, @@ -82,8 +81,7 @@ public CloudSpaceClient createSpaceClient(OAuth2AccessTokenWithAdditionalInfo to CloudCredentials credentials = createCredentials(token); oAuthClient.init(credentials); var requestTags = buildRequestTags(StringUtils.EMPTY); - return clientFactory.getCloudFoundryClientFactory() - .createSpaceClient(configuration.getControllerUrl(), oAuthClient, requestTags); + return clientFactory.createSpaceClient(configuration.getControllerUrl(), oAuthClient, requestTags); } public LogCacheClient createLogCacheClient(OAuth2AccessTokenWithAdditionalInfo token, String correlationId) { @@ -91,8 +89,7 @@ public LogCacheClient createLogCacheClient(OAuth2AccessTokenWithAdditionalInfo t CloudCredentials credentials = createCredentials(token); oAuthClient.init(credentials); var requestTags = buildRequestTags(correlationId); - return clientFactory.getCloudFoundryClientFactory() - .createLogCacheClient(configuration.getControllerUrl(), oAuthClient, requestTags); + return clientFactory.createLogCacheClient(configuration.getControllerUrl(), oAuthClient, requestTags); } private CloudCredentials createCredentials(OAuth2AccessTokenWithAdditionalInfo token) { diff --git a/multiapps-controller-process/src/main/java/module-info.java b/multiapps-controller-process/src/main/java/module-info.java index 6a67ce6fab..e26da0ff1f 100644 --- a/multiapps-controller-process/src/main/java/module-info.java +++ b/multiapps-controller-process/src/main/java/module-info.java @@ -22,8 +22,6 @@ requires transitive flowable.engine; requires transitive org.cloudfoundry.multiapps.controller.api; requires transitive org.cloudfoundry.multiapps.controller.core; - - requires org.cloudfoundry.client; requires com.fasterxml.jackson.core; requires com.fasterxml.jackson.databind; requires com.google.common; diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/jobs/OrphanedDataCleaner.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/jobs/OrphanedDataCleaner.java index ef348f7181..73cd64bc99 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/jobs/OrphanedDataCleaner.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/jobs/OrphanedDataCleaner.java @@ -8,7 +8,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.CloudCredentials; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; -import org.cloudfoundry.multiapps.controller.client.facade.adapters.ImmutableCloudFoundryClientFactory; +import org.cloudfoundry.multiapps.controller.client.facade.rest.ImmutableCloudControllerRestClientFactory; import org.cloudfoundry.multiapps.controller.client.facade.rest.CloudSpaceClient; import org.cloudfoundry.multiapps.controller.core.auditlogging.MtaConfigurationPurgerAuditLog; import org.cloudfoundry.multiapps.controller.core.cf.OAuthClientFactory; @@ -97,7 +97,7 @@ protected void initSpaceClient() { CloudCredentials cloudCredentials = new CloudCredentials(configuration.getGlobalAuditorUser(), configuration.getGlobalAuditorPassword(), SecurityUtil.CLIENT_ID, SecurityUtil.CLIENT_SECRET, configuration.getGlobalAuditorOrigin()); - var clientFactory = ImmutableCloudFoundryClientFactory.builder() + var clientFactory = ImmutableCloudControllerRestClientFactory.builder() .connectTimeout(Duration.ofMinutes(5)) .responseTimeout(Duration.ofMinutes(5)) .connectionPoolSize(1) diff --git a/multiapps-controller-web/src/main/java/org/cloudfoundry/multiapps/controller/web/upload/UploadFromUrlContext.java b/multiapps-controller-web/src/main/java/org/cloudfoundry/multiapps/controller/web/upload/UploadFromUrlContext.java index c1ef04215d..18134dd785 100644 --- a/multiapps-controller-web/src/main/java/org/cloudfoundry/multiapps/controller/web/upload/UploadFromUrlContext.java +++ b/multiapps-controller-web/src/main/java/org/cloudfoundry/multiapps/controller/web/upload/UploadFromUrlContext.java @@ -4,7 +4,7 @@ import java.util.concurrent.atomic.AtomicLong; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import org.cloudfoundry.Nullable; +import org.cloudfoundry.multiapps.controller.client.facade.Nullable; import org.cloudfoundry.multiapps.controller.api.model.UserCredentials; import org.cloudfoundry.multiapps.controller.persistence.model.AsyncUploadJobEntry; import org.immutables.value.Value; diff --git a/pom.xml b/pom.xml index da9c54a4ea..2f6fcf00dd 100644 --- a/pom.xml +++ b/pom.xml @@ -35,7 +35,6 @@ 2.22.3 4.33.0 5.1.0 - 5.16.0.RELEASE 1.3.7 4.2.18.Final 1.6.16 @@ -59,13 +58,12 @@ 1.28.0 3.3.2 1.3.6 + 0.12.6 2.70.0 0.134.0 12.35.0 1.0.4 4.0.1 - 6.4.7 - 8.20.0 multiapps-controller-client @@ -826,19 +824,6 @@ resilience4j-ratelimiter ${resilience4j.version} - - - com.bucket4j - bucket4j_jdk17-postgresql - ${bucket4j.version} - - - - - com.squareup.wire - wire-runtime - ${wire-runtime.version} - org.bouncycastle @@ -857,12 +842,6 @@ java-cfenv-jdbc ${java-cfenv.version} - - - org.cloudfoundry - cloudfoundry-client-reactor - ${cloudfoundry-client.version} - From 1f6e56679f2c5f07862bb31a07a0719a2cc9da55 Mon Sep 17 00:00:00 2001 From: Krasimir Kargov Date: Mon, 10 Aug 2026 14:10:34 +0300 Subject: [PATCH 03/15] Adding reactor-netty back not completed LMCROSSITXSADEPLOY-3563 --- .../CloudControllerRestClientBuilder.java | 145 ++++++++++++++---- .../CloudControllerRestClientFactory.java | 74 ++++++--- .../rest/CloudControllerRestClientV3Impl.java | 15 +- .../facade/rest/CloudControllerV3Client.java | 82 +++++++++- .../facade/rest/PackagesV3Operations.java | 33 +++- .../client/facade/rest/ReactiveFanOut.java | 53 +++++++ .../rest/ServiceInstancesV3Operations.java | 7 +- .../facade/rest/ServiceKeysV3Operations.java | 10 +- .../CloudControllerV3ClientPageUriTest.java | 40 +++++ .../facade/rest/ReactiveFanOutTest.java | 108 +++++++++++++ 10 files changed, 494 insertions(+), 73 deletions(-) create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ReactiveFanOut.java create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3ClientPageUriTest.java create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ReactiveFanOutTest.java diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientBuilder.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientBuilder.java index bcf7f5e322..65d75efb9e 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientBuilder.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientBuilder.java @@ -1,57 +1,71 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest; import java.net.URL; -import java.net.http.HttpClient; -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; import java.security.cert.X509Certificate; import java.time.Duration; import java.util.List; import java.util.Map; import java.util.Optional; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import org.springframework.http.HttpHeaders; -import org.springframework.http.client.JdkClientHttpRequestFactory; +import org.springframework.http.client.ReactorClientHttpRequestFactory; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; import org.springframework.http.converter.ByteArrayHttpMessageConverter; import org.springframework.http.converter.FormHttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.web.client.RestClient; +import org.springframework.web.reactive.function.client.ExchangeFilterFunction; +import org.springframework.web.reactive.function.client.ClientRequest; +import org.springframework.web.reactive.function.client.WebClient; import com.fasterxml.jackson.databind.ObjectMapper; +import io.netty.channel.ChannelOption; +import io.netty.handler.ssl.SslContextBuilder; import org.cloudfoundry.multiapps.controller.client.facade.oauth2.OAuthClient; +import reactor.netty.http.client.HttpClient; +import reactor.netty.resources.ConnectionProvider; /** * Builds the blocking Spring {@link RestClient} used by {@link CloudControllerRestClientV3Impl} to talk to the Cloud Controller v3 * REST API directly, without the OSS cf-java-client. *

- * The transport is the JDK {@link java.net.http.HttpClient} (via {@link JdkClientHttpRequestFactory}). This is deliberately chosen over - * reactor-netty: it is already available through {@code spring-web} and the JDK, so it pulls no extra transitive dependencies - * (no reactor-netty, no spring-webflux) into the deploy-service WAR — which is exactly the dependency-shedding this migration is about. - * The JDK client provides connection reuse (HTTP/2 multiplexing + keep-alive) and connect/read timeouts; TLS trust is configurable for - * self-signed landscapes. Every request carries a bearer token from {@link OAuthClient} (refreshed as needed), and non-2xx responses are - * mapped to {@code CloudOperationException} by the existing {@link CloudControllerResponseErrorHandler}. + * The transport is Reactor-Netty (via Spring's {@link ReactorClientHttpRequestFactory}, which lives in {@code spring-web} — NOT + * {@code spring-webflux}). This is the same HTTP engine the deploy-service ran on under the OSS client, so its proven behavior + * (connection pooling, TLS, timeouts, proxy handling on the target landscapes) is preserved; only the cf-java-client library on top of it + * was removed. The tuning knobs the OSS {@code DefaultConnectionContext} honored are restored here: pool size (via {@link ConnectionProvider}), + * connect timeout, and response timeout. The caller still consumes everything synchronously through {@link RestClient} — Reactor-Netty is + * used purely as the transport, exactly as before. + *

+ * Every request carries a bearer token from {@link OAuthClient} (refreshed as needed), and non-2xx responses are mapped to + * {@code CloudOperationException} by the existing {@link CloudControllerResponseErrorHandler}. */ public final class CloudControllerRestClientBuilder { private static final Duration DEFAULT_CONNECT_TIMEOUT = Duration.ofMinutes(1); + private static final int DEFAULT_CONNECTION_POOL_SIZE = 192; + private static final String CONNECTION_POOL_NAME = "cf-controller-client"; private CloudControllerRestClientBuilder() { } public static RestClient build(URL v3ApiUrl, OAuthClient oAuthClient, Map requestTags, TransportOptions options) { - JdkClientHttpRequestFactory requestFactory = new JdkClientHttpRequestFactory(buildHttpClient(options)); - options.responseTimeout() - .ifPresent(requestFactory::setReadTimeout); + return buildRestClient(buildHttpClient(options), v3ApiUrl, oAuthClient, requestTags); + } + + /** + * Build a blocking {@link RestClient} on the given (already-configured) Reactor-Netty {@link HttpClient}. Sharing one + * {@code HttpClient} between the RestClient and the {@link #buildWebClient WebClient} means both ride the same connection pool, + * timeouts, TLS and proxy settings. + */ + public static RestClient buildRestClient(HttpClient httpClient, URL baseUrl, OAuthClient oAuthClient, + Map requestTags) { return RestClient.builder() - .baseUrl(v3ApiUrl.toString()) - .requestFactory(requestFactory) + .baseUrl(baseUrl.toString()) + .requestFactory(new ReactorClientHttpRequestFactory(httpClient)) // Pass a fully-formed converter list via the List overload — deliberately NOT the Consumer overload. // messageConverters(Consumer) first calls initMessageConverters() to build Spring's DEFAULT list and only then // hands it to the consumer; that default init eagerly constructs AllEncompassingFormHttpMessageConverter -> @@ -80,23 +94,86 @@ public static RestClient build(URL v3ApiUrl, OAuthClient oAuthClient, Map requestTags) { + return WebClient.builder() + .baseUrl(baseUrl.toString()) + .clientConnector(new ReactorClientHttpConnector(httpClient)) + .filter(authAndTagsFilter(oAuthClient, requestTags)) + .build(); + } + + private static ExchangeFilterFunction authAndTagsFilter(OAuthClient oAuthClient, Map requestTags) { + return (request, next) -> { + ClientRequest.Builder builder = ClientRequest.from(request); + String authorization = oAuthClient.getAuthorizationHeaderValue(); + if (authorization != null) { + builder.header(HttpHeaders.AUTHORIZATION, authorization); + } + requestTags.forEach(builder::header); + return next.exchange(builder.build()); + }; + } + + static HttpClient buildHttpClient(TransportOptions options) { + int poolSize = options.connectionPoolSize() + .orElse(DEFAULT_CONNECTION_POOL_SIZE); + ConnectionProvider connectionProvider = ConnectionProvider.builder(CONNECTION_POOL_NAME) + .maxConnections(poolSize) + .build(); + HttpClient httpClient = HttpClient.create(connectionProvider) + .followRedirect(true) + // Honor JVM standard proxy settings (-Dhttp(s).proxyHost/proxyPort/nonProxyHosts), matching the + // OSS DefaultConnectionContext's proxy support. No-op when no proxy is configured, so it is safe + // on direct-connect landscapes and required on proxied ones. + .proxyWithSystemProperties(); + int connectTimeoutMillis = (int) options.connectTimeout() + .orElse(DEFAULT_CONNECT_TIMEOUT) + .toMillis(); + httpClient = httpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeoutMillis); + Optional responseTimeout = options.responseTimeout(); + if (responseTimeout.isPresent()) { + httpClient = httpClient.responseTimeout(responseTimeout.get()); + } + Optional handshakeTimeout = options.sslHandshakeTimeout(); if (options.trustSelfSignedCertificates()) { - builder.sslContext(buildTrustAllSslContext()); + httpClient = httpClient.secure(spec -> { + var sslBuilder = spec.sslContext(buildTrustAllSslContext()); + handshakeTimeout.ifPresent(sslBuilder::handshakeTimeout); + }); + } else if (handshakeTimeout.isPresent()) { + httpClient = httpClient.secure(spec -> { + var sslBuilder = spec.sslContext(defaultClientSslContext()); + sslBuilder.handshakeTimeout(handshakeTimeout.get()); + }); + } else { + httpClient = httpClient.secure(); + } + return httpClient; + } + + private static io.netty.handler.ssl.SslContext defaultClientSslContext() { + try { + return SslContextBuilder.forClient() + .build(); + } catch (javax.net.ssl.SSLException e) { + throw new IllegalStateException("An error occurred setting up the default SSLContext", e); } - return builder.build(); } - private static SSLContext buildTrustAllSslContext() { + private static io.netty.handler.ssl.SslContext buildTrustAllSslContext() { try { - SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(null, new TrustManager[] { trustAllManager() }, new SecureRandom()); - return sslContext; - } catch (NoSuchAlgorithmException | KeyManagementException e) { + return SslContextBuilder.forClient() + .trustManager(trustAllManager()) + .build(); + } catch (javax.net.ssl.SSLException e) { throw new IllegalStateException("An error occurred setting up the SSLContext", e); } } @@ -123,11 +200,13 @@ public X509Certificate[] getAcceptedIssuers() { } /** - * The transport-tuning surface carried over from the OSS-backed factory, so operators keep the same knobs. {@code connectionPoolSize} - * is accepted for parity but the JDK client manages connection reuse internally; it is retained for a future backend swap. + * The transport-tuning surface, honored by the Reactor-Netty transport: {@code connectTimeout} and {@code responseTimeout} on the + * client, {@code sslHandshakeTimeout} on the TLS layer, {@code connectionPoolSize} on the {@link ConnectionProvider}. Mirrors the + * knobs the OSS {@code DefaultConnectionContext} exposed. */ public record TransportOptions(Optional connectTimeout, Optional responseTimeout, - Optional connectionPoolSize, boolean trustSelfSignedCertificates) { + Optional sslHandshakeTimeout, Optional connectionPoolSize, + boolean trustSelfSignedCertificates) { } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientFactory.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientFactory.java index fa394c5925..1a2607a32c 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientFactory.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientFactory.java @@ -11,17 +11,20 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.client.RestClient; +import org.springframework.web.reactive.function.client.WebClient; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; -import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; import java.util.Collections; import java.util.Map; import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +import reactor.netty.http.client.HttpClient; /** * Builds the in-house {@link CloudControllerRestClient} (and the auxiliary {@link CloudSpaceClient} / {@link LogCacheClient}) that talk to @@ -33,6 +36,10 @@ public abstract class CloudControllerRestClientFactory { private static final Logger LOGGER = LoggerFactory.getLogger(CloudControllerRestClientFactory.class); private final RestUtil restUtil = new RestUtil(); + // Per-host transport cache — mirrors the OSS connectionContextCache. One shared reactor-netty HttpClient (pool + TLS) and one root-link + // discovery per Cloud Controller host, reused across the CF / space / log-cache clients. The per-call OAuth token and request tags are + // NOT cached here; they are applied per RestClient/WebClient build (cheap), so caching the transport is safe. + private final Map hostTransports = new ConcurrentHashMap<>(); public abstract Optional getSslHandshakeTimeout(); @@ -64,28 +71,54 @@ public CloudControllerRestClient createClient(URL controllerUrl, CloudCredential public CloudControllerRestClient createClient(URL controllerUrl, CloudCredentials credentials, CloudSpace target, OAuthClient oAuthClient, Map requestTags) { oAuthClient.init(credentials); - URL v3ApiUrl = deriveV3ApiUrl(controllerUrl, requestTags); - RestClient restClient = buildRestClient(toOrigin(v3ApiUrl), oAuthClient, requestTags); - return new CloudControllerRestClientV3Impl(v3ApiUrl, oAuthClient, target, restClient); + HostTransport transport = hostTransport(controllerUrl, requestTags); + URL v3ApiUrl = transport.v3ApiUrl(); + URL origin = toOrigin(v3ApiUrl); + HttpClient httpClient = transport.httpClient(); + RestClient restClient = CloudControllerRestClientBuilder.buildRestClient(httpClient, origin, oAuthClient, requestTags); + WebClient webClient = CloudControllerRestClientBuilder.buildWebClient(httpClient, origin, oAuthClient, requestTags); + CloudControllerV3Client cc = new CloudControllerV3Client(restClient, webClient); + // Per-upload client factory: the app-bits upload (POST /v3/packages/{guid}/upload) must be bounded by the caller's + // per-MTA uploadTimeout, not the shared 900s response timeout. Build a dedicated RestClient whose response timeout is + // that uploadTimeout, mirroring the OSS startUpload's .timeout(uploadTimeout). Everything else (pool, connect, TLS) matches. + java.util.function.Function uploadRestClientFactory = uploadTimeout -> { + var uploadOptions = new CloudControllerRestClientBuilder.TransportOptions(getConnectTimeout(), Optional.of(uploadTimeout), + getSslHandshakeTimeout(), getConnectionPoolSize(), + shouldTrustSelfSignedCertificates()); + return CloudControllerRestClientBuilder.build(origin, oAuthClient, requestTags, uploadOptions); + }; + return new CloudControllerRestClientV3Impl(v3ApiUrl, oAuthClient, target, restClient, cc, uploadRestClientFactory); } public CloudSpaceClient createSpaceClient(URL controllerUrl, OAuthClient oAuthClient, Map requestTags) { - URL v3ApiUrl = deriveV3ApiUrl(controllerUrl, requestTags); - RestClient restClient = buildRestClient(toOrigin(v3ApiUrl), oAuthClient, requestTags); - return new CloudSpaceClient(new CloudControllerV3Client(restClient)); + HostTransport transport = hostTransport(controllerUrl, requestTags); + URL origin = toOrigin(transport.v3ApiUrl()); + RestClient restClient = CloudControllerRestClientBuilder.buildRestClient(transport.httpClient(), origin, oAuthClient, requestTags); + WebClient webClient = CloudControllerRestClientBuilder.buildWebClient(transport.httpClient(), origin, oAuthClient, requestTags); + return new CloudSpaceClient(new CloudControllerV3Client(restClient, webClient)); } public LogCacheClient createLogCacheClient(URL controllerUrl, OAuthClient oAuthClient, Map requestTags) { - URL logCacheUrl = deriveLogCacheUrl(controllerUrl, requestTags); - RestClient restClient = buildRestClient(logCacheUrl, oAuthClient, requestTags); + HostTransport transport = hostTransport(controllerUrl, requestTags); + RestClient restClient = CloudControllerRestClientBuilder.buildRestClient(transport.httpClient(), transport.logCacheUrl(), + oAuthClient, requestTags); return new LogCacheClient(restClient); } - private RestClient buildRestClient(URL baseUrl, OAuthClient oAuthClient, Map requestTags) { - var transportOptions = new CloudControllerRestClientBuilder.TransportOptions(getConnectTimeout(), getResponseTimeout(), - getConnectionPoolSize(), - shouldTrustSelfSignedCertificates()); - return CloudControllerRestClientBuilder.build(baseUrl, oAuthClient, requestTags, transportOptions); + // Get (or lazily build) the shared per-host transport: one reactor-netty HttpClient + the resolved v3 / log-cache URLs, discovered + // once per host. Keyed on the controller host so the CF / space / log-cache clients for the same landscape share the pool. + private HostTransport hostTransport(URL controllerUrl, Map requestTags) { + return hostTransports.computeIfAbsent(controllerUrl.getHost(), host -> { + HttpClient httpClient = CloudControllerRestClientBuilder.buildHttpClient(transportOptions()); + URL v3ApiUrl = deriveV3ApiUrl(controllerUrl, requestTags); + URL logCacheUrl = deriveLogCacheUrl(controllerUrl, requestTags); + return new HostTransport(httpClient, v3ApiUrl, logCacheUrl); + }); + } + + private CloudControllerRestClientBuilder.TransportOptions transportOptions() { + return new CloudControllerRestClientBuilder.TransportOptions(getConnectTimeout(), getResponseTimeout(), getSslHandshakeTimeout(), + getConnectionPoolSize(), shouldTrustSelfSignedCertificates()); } // scheme://host[:port] of the given URL, dropping any path (e.g. the trailing /v3). The RestClient baseUrl must be the origin only, @@ -157,10 +190,10 @@ private Map fetchRootLinks(URL controllerUrl, Map uploadRestClientFactory) { + this(v3ApiUrl, oAuthClient, target, restClient, new CloudControllerV3Client(restClient), uploadRestClientFactory); + } + + public CloudControllerRestClientV3Impl(URL v3ApiUrl, OAuthClient oAuthClient, CloudSpace target, RestClient restClient, + CloudControllerV3Client cc, + java.util.function.Function uploadRestClientFactory) { this.v3ApiUrl = v3ApiUrl; this.oAuthClient = oAuthClient; this.target = target; this.restClient = restClient; - this.cc = new CloudControllerV3Client(restClient); + this.cc = cc; this.buildsOps = new BuildsV3Operations(cc, target); this.domainsOps = new DomainsV3Operations(cc, target); this.eventsOps = new EventsV3Operations(cc, target); this.jobsOps = new JobsV3Operations(cc, target); - this.packagesOps = new PackagesV3Operations(cc, target); + this.packagesOps = new PackagesV3Operations(cc, target, uploadRestClientFactory); this.processesOps = new ProcessesV3Operations(cc, target); this.rolesOps = new RolesV3Operations(cc, target); this.routesOps = new RoutesV3Operations(cc, target); diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Client.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Client.java index 2adab4c566..3c8b53b46f 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Client.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Client.java @@ -15,6 +15,11 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.client.RestClient; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.reactive.function.client.WebClientResponseException; +import org.springframework.web.util.UriComponentsBuilder; + +import reactor.core.publisher.Flux; /** * Low-level Cloud Controller v3 access shared by every operation of {@link CloudControllerRestClientV3Impl}. This is the reusable @@ -37,10 +42,22 @@ public class CloudControllerV3Client { private static final Duration JOB_POLL_MAX_INTERVAL = Duration.ofSeconds(15); private static final Duration DEFAULT_JOB_TIMEOUT = Duration.ofMinutes(5); + // Reactor's default flatMap concurrency — the in-flight cap the OSS PaginationUtils effectively used when fetching pages 2..N. + private static final int MAX_CONCURRENT_PAGES = 256; + private final RestClient restClient; + // Optional reactive client on the SAME reactor-netty transport, used only to fetch pages 2..N concurrently (as the OSS client did via + // Reactor flatMap). When null (e.g. the auxiliary space/log-cache clients that never paginate large sets), list() falls back to the + // sequential next.href walk. + private final WebClient webClient; public CloudControllerV3Client(RestClient restClient) { + this(restClient, null); + } + + public CloudControllerV3Client(RestClient restClient, WebClient webClient) { this.restClient = restClient; + this.webClient = webClient; } public T get(String uri, Class responseType) { @@ -66,12 +83,38 @@ public Optional getOptional(String uri, Class responseType) { } /** - * Walk every page of a v3 list endpoint, following {@code pagination.next.href} until it is {@code null}. The caller supplies the - * fully-formed first-page URI (path + query) and a type reference for the paged {@link V3ListResponse}. + * List every resource of a v3 list endpoint across all pages. Mirrors the OSS {@code PaginationUtils.requestClientV3Resources}: + * fetch page 1, read {@code pagination.total_pages}, then request pages {@code 2..N} concurrently (bounded by + * {@value #MAX_CONCURRENT_PAGES}) via the reactive {@link WebClient} on the shared transport, and concatenate the results in page + * order. This restores the intra-operation concurrency the OSS client had on large, multi-page listings. + *

+ * Falls back to the sequential {@code pagination.next.href} walk when no {@link WebClient} is configured or the response does not + * report {@code total_pages} — preserving correctness on any endpoint that omits it. */ public List list(String firstPageUri, ParameterizedTypeReference> pageType) { - List all = new ArrayList<>(); - String nextUri = firstPageUri; + String firstPageRelativeUri = toRelativeIfAbsolute(firstPageUri); + V3ListResponse firstPage = restClient.get() + .uri(firstPageRelativeUri) + .retrieve() + .body(pageType); + if (firstPage == null) { + return new ArrayList<>(); + } + Integer totalPages = firstPage.pagination() == null ? null + : firstPage.pagination() + .totalPages(); + if (webClient == null || totalPages == null || totalPages <= 1) { + return listSequentially(firstPage, pageType); + } + List all = new ArrayList<>(firstPage.resources()); + all.addAll(fetchRemainingPagesConcurrently(firstPageRelativeUri, totalPages, pageType)); + return all; + } + + // Sequential pagination via pagination.next.href — the fallback path (and the whole behaviour when no WebClient is set). + private List listSequentially(V3ListResponse firstPage, ParameterizedTypeReference> pageType) { + List all = new ArrayList<>(firstPage.resources()); + String nextUri = toRelativeIfAbsolute(firstPage.nextPageHref()); while (nextUri != null) { V3ListResponse page = restClient.get() .uri(nextUri) @@ -86,6 +129,37 @@ public List list(String firstPageUri, ParameterizedTypeReference List fetchRemainingPagesConcurrently(String firstPageRelativeUri, int totalPages, + ParameterizedTypeReference> pageType) { + return Flux.range(2, totalPages - 1) + .flatMapSequential(page -> webClient.get() + .uri(pageUri(firstPageRelativeUri, page)) + .retrieve() + .bodyToMono(pageType) + .onErrorMap(WebClientResponseException.class, this::toCloudOperationException), + MAX_CONCURRENT_PAGES) + .flatMapIterable(V3ListResponse::resources) + .collectList() + .block(); + } + + // Set/replace the "page" query parameter on the first-page URI, keeping per_page and every filter intact. Package-visible for testing. + static String pageUri(String firstPageRelativeUri, int page) { + return UriComponentsBuilder.fromUriString(firstPageRelativeUri) + .replaceQueryParam("page", page) + .build() + .toUriString(); + } + + private CloudOperationException toCloudOperationException(WebClientResponseException e) { + HttpStatus status = HttpStatus.valueOf(e.getStatusCode() + .value()); + return new CloudOperationException(status, status.getReasonPhrase(), e.getResponseBodyAsString(), e); + } + /** * Follow the {@code Location} header of a 202-Accepted async response to its job, then poll to completion. */ diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3Operations.java index 689e2a9305..b336615131 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3Operations.java @@ -28,6 +28,7 @@ import org.springframework.util.Assert; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestClient; /** * CF v3 packages operations of the cf-java-client replacement. Reproduces the HTTP shape and domain mapping of the OSS @@ -53,10 +54,19 @@ public class PackagesV3Operations { private final CloudControllerV3Client cc; private final CloudSpace target; + // Builds a RestClient whose response timeout is the given per-upload timeout, so a stalled bits upload is bounded by the + // user-configured UPLOAD_TIMEOUT (mirrors the OSS .timeout(uploadTimeout)). May be null, in which case the shared client is used. + private final java.util.function.Function uploadRestClientFactory; public PackagesV3Operations(CloudControllerV3Client cc, CloudSpace target) { + this(cc, target, null); + } + + public PackagesV3Operations(CloudControllerV3Client cc, CloudSpace target, + java.util.function.Function uploadRestClientFactory) { this.cc = cc; this.target = target; + this.uploadRestClientFactory = uploadRestClientFactory; } public CloudPackage getPackage(UUID packageGuid) { @@ -122,17 +132,26 @@ private CloudPackage startUpload(String applicationName, Path file, Duration upl MultiValueMap body = new LinkedMultiValueMap<>(); body.add("bits", new FileSystemResource(file)); - cc.getRestClient() - .post() - .uri("/v3/packages/{guid}/upload", packageGuid) - .contentType(MediaType.MULTIPART_FORM_DATA) - .body(body) - .retrieve() - .toBodilessEntity(); + // Bound the bits upload by the caller's per-MTA uploadTimeout via a dedicated RestClient whose response timeout is that value + // (see CloudControllerRestClientFactory). Fall back to the shared client if no factory/timeout was supplied. + RestClient uploadClient = resolveUploadClient(uploadTimeout); + uploadClient.post() + .uri("/v3/packages/{guid}/upload", packageGuid) + .contentType(MediaType.MULTIPART_FORM_DATA) + .body(body) + .retrieve() + .toBodilessEntity(); return getPackage(packageGuid); } + private RestClient resolveUploadClient(Duration uploadTimeout) { + if (uploadRestClientFactory != null && uploadTimeout != null && !uploadTimeout.isZero() && !uploadTimeout.isNegative()) { + return uploadRestClientFactory.apply(uploadTimeout); + } + return cc.getRestClient(); + } + private CloudPackage createBitsPackage(UUID applicationGuid) { V3Package created = cc.getRestClient() .post() diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ReactiveFanOut.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ReactiveFanOut.java new file mode 100644 index 0000000000..04a36caa1a --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ReactiveFanOut.java @@ -0,0 +1,53 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.List; +import java.util.function.Function; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; + +/** + * Restores the intra-operation concurrency the OSS cf-java-client had for free. The OSS client composed per-item work + * (e.g. fetching credentials for N service keys, or resolving the plan/offering of N service instances) as a reactive graph and let + * Reactor's {@code flatMap} run those calls concurrently (default concurrency 256) before a single {@code .block()} at the boundary. + * The synchronous {@link RestClient}-based rewrite would otherwise do the same work strictly sequentially ({@code stream().map(fetch)}). + *

+ * This helper reproduces exactly that pattern — {@code Flux.fromIterable(...).flatMap(...).block()} — reusing the existing + * blocking per-item mappers unchanged. Each blocking mapper is offloaded onto {@link Schedulers#boundedElastic()} so the calls + * run concurrently without pinning the caller's thread; {@code flatMap} bounds how many are in flight (Reactor's default 256, matching + * the OSS cap). Results are returned in input order ({@link Flux#flatMapSequential} preserves order while still subscribing + * eagerly), and the first error aborts the rest and propagates unwrapped — the same fail-fast semantics a {@code .block()} over a failed + * {@code flatMap} had in the OSS path. + *

+ * This uses only Reactor (a Spring library already on the classpath as the transport); it does not reintroduce cf-java-client. + */ +final class ReactiveFanOut { + + // Reactor's default flatMap concurrency — the in-flight cap the OSS client effectively used for per-item fan-out. + private static final int MAX_CONCURRENCY = 256; + + private ReactiveFanOut() { + } + + /** + * Apply {@code mapper} (a blocking function) to every item concurrently, bounded by {@value #MAX_CONCURRENCY} in-flight calls, and + * return the results in the same order as {@code items}. The single-element and empty cases short-circuit without touching the + * reactive machinery (the overwhelmingly common case for typical MTAs). + */ + static List mapConcurrently(List items, Function mapper) { + if (items.isEmpty()) { + return List.of(); + } + if (items.size() == 1) { + return List.of(mapper.apply(items.get(0))); + } + return Flux.fromIterable(items) + .flatMapSequential(item -> Mono.fromCallable(() -> mapper.apply(item)) + .subscribeOn(Schedulers.boundedElastic()), + MAX_CONCURRENCY) + .collectList() + .block(); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3Operations.java index 1361ba9fb4..87d43c10f4 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3Operations.java @@ -179,10 +179,9 @@ public List getServiceInstancesWithoutAuxiliaryContentByNa public List getServiceInstancesByMetadataLabelSelector(String labelSelector) { String uri = "/v3/service_instances?per_page=" + PER_PAGE + "&space_guids=" + getTargetSpaceGuid() + "&label_selector=" + labelSelector; - return cc.list(uri, SERVICE_INSTANCE_LIST_TYPE) - .stream() - .map(this::mapWithAuxiliaryContent) - .collect(Collectors.toList()); + // Each managed instance needs its plan + offering resolved via extra GETs (mapWithAuxiliaryContent). The OSS client ran this + // enrichment concurrently across the list; preserve that so a large label-selector result doesn't serialize the per-instance calls. + return ReactiveFanOut.mapConcurrently(cc.list(uri, SERVICE_INSTANCE_LIST_TYPE), this::mapWithAuxiliaryContent); } public List getServiceInstancesWithoutAuxiliaryContentByMetadataLabelSelector(String labelSelector) { diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3Operations.java index 720db7bc3f..e6e422acfe 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3Operations.java @@ -97,11 +97,11 @@ public List getServiceKeys(CloudServiceInstance serviceInstance } public List getServiceKeysWithCredentials(CloudServiceInstance serviceInstance) { - return listServiceKeyResources(serviceInstance.getGuid()).stream() - .map(key -> V3ServiceKeyMapper.toCloudServiceKey(key, serviceInstance, - getServiceKeyCredentials( - key.guid()))) - .toList(); + // Each key needs a separate GET /details for its credentials. The OSS client fetched these concurrently (reactive flatMap); do the + // same here so a service with many keys doesn't serialize N credential round-trips. + return ReactiveFanOut.mapConcurrently(listServiceKeyResources(serviceInstance.getGuid()), + key -> V3ServiceKeyMapper.toCloudServiceKey(key, serviceInstance, + getServiceKeyCredentials(key.guid()))); } // --- helpers ---------------------------------------------------------------------------------------------------------------- diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3ClientPageUriTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3ClientPageUriTest.java new file mode 100644 index 0000000000..c5e4eb67a8 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3ClientPageUriTest.java @@ -0,0 +1,40 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class CloudControllerV3ClientPageUriTest { + + @Test + void testAddsPageWhenAbsentAndKeepsExistingQuery() { + String uri = CloudControllerV3Client.pageUri("/v3/apps?per_page=5000&space_guids=abc", 3); + + Assertions.assertTrue(uri.contains("page=3"), uri); + Assertions.assertTrue(uri.contains("per_page=5000"), uri); + Assertions.assertTrue(uri.contains("space_guids=abc"), uri); + } + + @Test + void testReplacesExistingPageParam() { + String uri = CloudControllerV3Client.pageUri("/v3/apps?per_page=5000&page=1", 4); + + Assertions.assertTrue(uri.contains("page=4"), uri); + Assertions.assertFalse(uri.contains("page=1"), uri); + // per_page must survive the replacement. + Assertions.assertTrue(uri.contains("per_page=5000"), uri); + } + + @Test + void testPreservesMultipleFilters() { + String first = "/v3/service_credential_bindings?per_page=5000&type=key&service_instance_guids=g1&names=n1"; + + String uri = CloudControllerV3Client.pageUri(first, 2); + + Assertions.assertTrue(uri.contains("page=2"), uri); + Assertions.assertTrue(uri.contains("type=key"), uri); + Assertions.assertTrue(uri.contains("service_instance_guids=g1"), uri); + Assertions.assertTrue(uri.contains("names=n1"), uri); + Assertions.assertTrue(uri.contains("per_page=5000"), uri); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ReactiveFanOutTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ReactiveFanOutTest.java new file mode 100644 index 0000000000..7cdc626d5c --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ReactiveFanOutTest.java @@ -0,0 +1,108 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.List; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.IntStream; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.springframework.http.HttpStatus; + +class ReactiveFanOutTest { + + @Test + void testEmptyReturnsEmpty() { + Assertions.assertEquals(List.of(), ReactiveFanOut.mapConcurrently(List.of(), x -> x)); + } + + @Test + void testSingleElementShortCircuits() { + List result = ReactiveFanOut.mapConcurrently(List.of("a"), s -> s + "!"); + + Assertions.assertEquals(List.of("a!"), result); + } + + @Test + void testResultsPreserveInputOrderEvenWhenTasksFinishOutOfOrder() { + List input = IntStream.rangeClosed(1, 20) + .boxed() + .toList(); + + // Later items finish sooner (inverse sleep) — the result must still be in input order. + List result = ReactiveFanOut.mapConcurrently(input, i -> { + sleep((20 - i)); + return i * 10; + }); + + List expected = input.stream() + .map(i -> i * 10) + .toList(); + Assertions.assertEquals(expected, result); + } + + @Test + @Timeout(10) + void testWorkRunsConcurrently() throws Exception { + int n = 8; + // If the mapper ran sequentially, the barrier (which needs all n parties) would never trip and the test would time out. + CyclicBarrier barrier = new CyclicBarrier(n); + List input = IntStream.range(0, n) + .boxed() + .toList(); + + List result = ReactiveFanOut.mapConcurrently(input, i -> { + try { + barrier.await(); + } catch (Exception e) { + throw new IllegalStateException(e); + } + return i; + }); + + Assertions.assertEquals(input, result); + } + + @Test + void testFirstFailurePropagatesUnwrapped() { + List input = List.of(1, 2, 3, 4); + + CloudOperationException thrown = Assertions.assertThrows(CloudOperationException.class, + () -> ReactiveFanOut.mapConcurrently(input, i -> { + if (i == 3) { + throw new CloudOperationException(HttpStatus.BAD_GATEWAY); + } + return i; + })); + + Assertions.assertEquals(HttpStatus.BAD_GATEWAY, thrown.getStatusCode()); + } + + @Test + void testMapperInvokedOncePerItem() { + List input = IntStream.range(0, 50) + .boxed() + .toList(); + AtomicInteger invocations = new AtomicInteger(); + + List result = ReactiveFanOut.mapConcurrently(input, i -> { + invocations.incrementAndGet(); + return i; + }); + + Assertions.assertEquals(input, result); + Assertions.assertEquals(input.size(), invocations.get()); + } + + private static void sleep(long millis) { + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + Thread.currentThread() + .interrupt(); + } + } + +} From 24631cdf06d5eb479ee48c8c9b74dceecf878a7a Mon Sep 17 00:00:00 2001 From: Krasimir Kargov Date: Thu, 13 Aug 2026 09:27:15 +0300 Subject: [PATCH 04/15] Adding more fixes for the migration LMCROSSITXSADEPLOY-3563 --- .../CloudControllerRestClientBuilder.java | 15 ++++-- .../CloudControllerRestClientFactory.java | 33 ++++++------ .../rest/CloudControllerRestClientV3Impl.java | 21 +++++--- .../rest/ServiceInstancesV3Operations.java | 51 +++++++++++++++++-- 4 files changed, 91 insertions(+), 29 deletions(-) diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientBuilder.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientBuilder.java index 65d75efb9e..e1cf3ba75c 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientBuilder.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientBuilder.java @@ -28,6 +28,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.oauth2.OAuthClient; import reactor.netty.http.client.HttpClient; import reactor.netty.resources.ConnectionProvider; +import reactor.netty.resources.LoopResources; /** * Builds the blocking Spring {@link RestClient} used by {@link CloudControllerRestClientV3Impl} to talk to the Cloud Controller v3 @@ -138,6 +139,13 @@ static HttpClient buildHttpClient(TransportOptions options) { .orElse(DEFAULT_CONNECT_TIMEOUT) .toMillis(); httpClient = httpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeoutMillis); + // Size the I/O event-loop pool from threadPoolSize, mirroring the OSS DefaultConnectionContext.threadPoolSize (which created a + // reactor-netty LoopResources the client ran on). When unset, reactor-netty uses its shared default LoopResources. + Optional threadPoolSize = options.threadPoolSize(); + if (threadPoolSize.isPresent()) { + LoopResources loopResources = LoopResources.create(CONNECTION_POOL_NAME + "-loop", threadPoolSize.get(), true); + httpClient = httpClient.runOn(loopResources); + } Optional responseTimeout = options.responseTimeout(); if (responseTimeout.isPresent()) { httpClient = httpClient.responseTimeout(responseTimeout.get()); @@ -201,12 +209,13 @@ public X509Certificate[] getAcceptedIssuers() { /** * The transport-tuning surface, honored by the Reactor-Netty transport: {@code connectTimeout} and {@code responseTimeout} on the - * client, {@code sslHandshakeTimeout} on the TLS layer, {@code connectionPoolSize} on the {@link ConnectionProvider}. Mirrors the - * knobs the OSS {@code DefaultConnectionContext} exposed. + * client, {@code sslHandshakeTimeout} on the TLS layer, {@code connectionPoolSize} on the {@link ConnectionProvider}, and + * {@code threadPoolSize} on the I/O event-loop ({@link LoopResources}). Mirrors the knobs the OSS {@code DefaultConnectionContext} + * exposed. */ public record TransportOptions(Optional connectTimeout, Optional responseTimeout, Optional sslHandshakeTimeout, Optional connectionPoolSize, - boolean trustSelfSignedCertificates) { + Optional threadPoolSize, boolean trustSelfSignedCertificates) { } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientFactory.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientFactory.java index 1a2607a32c..b3f0087d86 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientFactory.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientFactory.java @@ -1,5 +1,16 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + import org.cloudfoundry.multiapps.controller.client.facade.CloudCredentials; import org.cloudfoundry.multiapps.controller.client.facade.CloudException; import org.cloudfoundry.multiapps.controller.client.facade.adapters.LogCacheClient; @@ -12,18 +23,6 @@ import org.slf4j.LoggerFactory; import org.springframework.web.client.RestClient; import org.springframework.web.reactive.function.client.WebClient; - -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URL; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.time.Duration; -import java.util.Collections; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; - import reactor.netty.http.client.HttpClient; /** @@ -82,9 +81,12 @@ public CloudControllerRestClient createClient(URL controllerUrl, CloudCredential // per-MTA uploadTimeout, not the shared 900s response timeout. Build a dedicated RestClient whose response timeout is // that uploadTimeout, mirroring the OSS startUpload's .timeout(uploadTimeout). Everything else (pool, connect, TLS) matches. java.util.function.Function uploadRestClientFactory = uploadTimeout -> { + // Upload uses a one-off client with only its response timeout overridden; no dedicated event-loop pool (threadPoolSize empty) + // so it does not spin up its own LoopResources. var uploadOptions = new CloudControllerRestClientBuilder.TransportOptions(getConnectTimeout(), Optional.of(uploadTimeout), - getSslHandshakeTimeout(), getConnectionPoolSize(), - shouldTrustSelfSignedCertificates()); + getSslHandshakeTimeout(), getConnectionPoolSize(), + Optional.empty(), + shouldTrustSelfSignedCertificates()); return CloudControllerRestClientBuilder.build(origin, oAuthClient, requestTags, uploadOptions); }; return new CloudControllerRestClientV3Impl(v3ApiUrl, oAuthClient, target, restClient, cc, uploadRestClientFactory); @@ -118,7 +120,8 @@ private HostTransport hostTransport(URL controllerUrl, Map reque private CloudControllerRestClientBuilder.TransportOptions transportOptions() { return new CloudControllerRestClientBuilder.TransportOptions(getConnectTimeout(), getResponseTimeout(), getSslHandshakeTimeout(), - getConnectionPoolSize(), shouldTrustSelfSignedCertificates()); + getConnectionPoolSize(), getThreadPoolSize(), + shouldTrustSelfSignedCertificates()); } // scheme://host[:port] of the given URL, dropping any path (e.g. the trailing /v3). The RestClient baseUrl must be the origin only, diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientV3Impl.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientV3Impl.java index a21ad8b656..57a8b147e0 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientV3Impl.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientV3Impl.java @@ -606,12 +606,21 @@ public void stopApplication(String applicationName) { @Override public void rename(String applicationName, String newName) { UUID guid = getApplicationGuid(applicationName); - cc.getRestClient() - .patch() - .uri("/v3/apps/{guid}", guid) - .body(Map.of("name", newName)) - .retrieve() - .toBodilessEntity(); + try { + cc.getRestClient() + .patch() + .uri("/v3/apps/{guid}", guid) + .body(Map.of("name", newName)) + .retrieve() + .toBodilessEntity(); + } catch (CloudOperationException e) { + // Idempotent-rename tolerance (mirrors the OSS fallbackApplicationRename): the CC can answer 503 to a rename that in fact + // already took effect. If the app is already named newName, treat the rename as successful; otherwise propagate. + if (e.getStatusCode() == HttpStatus.SERVICE_UNAVAILABLE && newName.equals(getApplicationName(guid))) { + return; + } + throw e; + } } @Override diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3Operations.java index 87d43c10f4..0bea35bc76 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3Operations.java @@ -1,5 +1,6 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest; +import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -9,6 +10,7 @@ import java.util.stream.Collectors; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.Messages; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEntity; import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; @@ -253,7 +255,7 @@ private CloudServiceInstance mapWithAuxiliaryContent(V3ServiceInstance resource) if (servicePlanGuid == null) { return V3ServiceInstanceMapper.toCloudServiceInstance(resource, null, null); } - ServicePlanNames names = resolvePlanAndOfferingNames(servicePlanGuid); + ServicePlanNames names = resolvePlanAndOfferingNames(servicePlanGuid, resource.name()); return V3ServiceInstanceMapper.toCloudServiceInstance(resource, names.planName(), names.offeringName()); } @@ -283,21 +285,60 @@ private String servicePlanGuidOf(V3ServiceInstance resource) { .guid(); } - // Mirrors the OSS getServicePlanResource + getServiceOffering zip: resolve the plan's own name and its offering's name. - private ServicePlanNames resolvePlanAndOfferingNames(String servicePlanGuid) { - V3ServicePlan plan = cc.get("/v3/service_plans/" + servicePlanGuid, V3ServicePlan.class); + // Mirrors the OSS getServicePlanResource + getServiceOffering zip: resolve the plan's own name and its offering's name. Reproduces the + // OSS friendly-error mapping too — a 403/404 on the plan or offering lookup becomes a specific, human-readable CloudOperationException + // (instead of the generic one) so operators get the same messages as before. + private ServicePlanNames resolvePlanAndOfferingNames(String servicePlanGuid, String serviceInstanceName) { + V3ServicePlan plan = getServicePlanForNameResolution(servicePlanGuid, serviceInstanceName); if (plan == null) { return new ServicePlanNames(null, null); } String offeringName = null; String offeringGuid = offeringGuidOf(plan); if (offeringGuid != null) { - V3ServiceOffering offering = cc.get("/v3/service_offerings/" + offeringGuid, V3ServiceOffering.class); + V3ServiceOffering offering = getServiceOfferingForNameResolution(offeringGuid); offeringName = offering == null ? null : offering.name(); } return new ServicePlanNames(plan.name(), offeringName); } + private V3ServicePlan getServicePlanForNameResolution(String servicePlanGuid, String serviceInstanceName) { + try { + return cc.get("/v3/service_plans/" + servicePlanGuid, V3ServicePlan.class); + } catch (CloudOperationException e) { + if (e.getStatusCode() == HttpStatus.FORBIDDEN) { + throw new CloudOperationException(HttpStatus.FORBIDDEN, HttpStatus.FORBIDDEN.getReasonPhrase(), + MessageFormat.format(Messages.SERVICE_PLAN_WITH_GUID_0_NOT_AVAILABLE_FOR_SERVICE_INSTANCE_1, + servicePlanGuid, serviceInstanceName), + e); + } + if (e.getStatusCode() == HttpStatus.NOT_FOUND) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), + MessageFormat.format(Messages.NO_SERVICE_PLAN_FOUND, servicePlanGuid, + serviceInstanceName), + e); + } + throw e; + } + } + + private V3ServiceOffering getServiceOfferingForNameResolution(String offeringGuid) { + try { + return cc.get("/v3/service_offerings/" + offeringGuid, V3ServiceOffering.class); + } catch (CloudOperationException e) { + if (e.getStatusCode() == HttpStatus.FORBIDDEN) { + throw new CloudOperationException(HttpStatus.FORBIDDEN, HttpStatus.FORBIDDEN.getReasonPhrase(), + MessageFormat.format(Messages.SERVICE_OFFERING_WITH_GUID_0_IS_NOT_AVAILABLE, offeringGuid), + e); + } + if (e.getStatusCode() == HttpStatus.NOT_FOUND) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), + MessageFormat.format(Messages.SERVICE_OFFERING_WITH_GUID_0_NOT_FOUND, offeringGuid), e); + } + throw e; + } + } + private String offeringGuidOf(V3ServicePlan plan) { if (plan.relationships() == null || plan.relationships() .serviceOffering() == null From 0104970a761b7ab4ebf9703aa24eea250635d0e6 Mon Sep 17 00:00:00 2001 From: Krasimir Kargov Date: Thu, 20 Aug 2026 09:15:46 +0300 Subject: [PATCH 05/15] Fix test LMCROSSITXSADEPLOY-3563 --- .../process/util/OperationInFinalStateHandlerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/OperationInFinalStateHandlerTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/OperationInFinalStateHandlerTest.java index de3062a4e9..fc6f8aab18 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/OperationInFinalStateHandlerTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/OperationInFinalStateHandlerTest.java @@ -5,7 +5,7 @@ import java.util.List; import java.util.stream.Stream; -import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.api.model.ImmutableOperation; import org.cloudfoundry.multiapps.controller.api.model.Operation; import org.cloudfoundry.multiapps.controller.api.model.Operation.State; From 267c3bccd0d777340dda11a39b3972e1672308b7 Mon Sep 17 00:00:00 2001 From: Krasimir Kargov Date: Thu, 3 Sep 2026 16:36:37 +0300 Subject: [PATCH 06/15] Bettering everything as a whole - simplifications and logic changes LMCROSSITXSADEPLOY-3563 --- .../src/main/java/module-info.java | 2 + .../multiapps/controller/Constants.java | 38 ++ .../multiapps/controller/Messages.java | 45 +- .../controller/client/facade/AllowNulls.java | 5 - .../facade/adapters/LogCacheClient.java | 9 +- .../client/facade/domain/HealthCheckType.java | 3 - .../client/facade/domain/InstanceState.java | 6 +- .../client/facade/domain/JobState.java | 12 +- .../client/facade/domain/LastOperation.java | 5 - .../client/facade/domain/Metadata.java | 17 +- .../ServiceCredentialBindingOperation.java | 9 +- .../facade/domain/ServiceInstanceType.java | 16 +- .../facade/domain/ServiceOperation.java | 9 +- .../client/facade/domain/UserRole.java | 13 +- .../facade/rest/ApplicationsV3Operations.java | 268 ++++++++++ .../facade/rest/BuildsV3Operations.java | 26 +- .../rest/ClientConfigurationOptions.java | 9 + .../CloudControllerRestClientBuilder.java | 162 +++--- .../CloudControllerRestClientFactory.java | 162 +++--- .../rest/CloudControllerRestClientV3Impl.java | 505 ++++++------------ .../facade/rest/CloudControllerV3Client.java | 156 +++--- .../rest/CloudControllerV3Endpoints.java | 88 +++ .../client/facade/rest/CloudSpaceClient.java | 41 +- .../facade/rest/DomainsV3Operations.java | 78 +-- .../facade/rest/EventsV3Operations.java | 42 +- .../facade/rest/HttpConnectionPair.java | 8 + .../client/facade/rest/JobsV3Operations.java | 16 +- .../facade/rest/PackagesV3Operations.java | 99 ++-- .../facade/rest/ProcessesV3Operations.java | 119 +++-- .../client/facade/rest/ReactiveFanOut.java | 30 +- .../client/facade/rest/RolesV3Operations.java | 20 +- .../facade/rest/RoutesV3Operations.java | 169 +++--- .../rest/ServiceBindingsV3Operations.java | 146 ++--- .../rest/ServiceBrokersV3Operations.java | 82 ++- .../rest/ServiceInstancesV3Operations.java | 201 +++---- .../facade/rest/ServiceKeysV3Operations.java | 131 +++-- .../rest/ServiceOfferingsV3Operations.java | 37 +- .../facade/rest/ServicePlansV3Operations.java | 76 ++- .../facade/rest/StacksV3Operations.java | 33 +- .../client/facade/rest/TasksV3Operations.java | 53 +- .../rest/resources/LogCacheReadResponse.java | 13 +- .../facade/rest/resources/V3Application.java | 4 - .../rest/resources/V3ApplicationMapper.java | 6 +- .../facade/rest/resources/V3AuditEvent.java | 4 - .../rest/resources/V3AuditEventMapper.java | 7 +- .../facade/rest/resources/V3BuildMapper.java | 7 +- .../facade/rest/resources/V3Domain.java | 7 - .../facade/rest/resources/V3DomainMapper.java | 4 - .../facade/rest/resources/V3Droplet.java | 2 - .../rest/resources/V3InstancesInfoMapper.java | 9 +- .../client/facade/rest/resources/V3Job.java | 3 - .../facade/rest/resources/V3JobMapper.java | 6 +- .../facade/rest/resources/V3ListResponse.java | 7 +- .../rest/resources/V3PackageMapper.java | 12 +- .../facade/rest/resources/V3Process.java | 20 - .../rest/resources/V3ProcessMapper.java | 6 +- .../rest/resources/V3ResourceMappers.java | 19 +- .../client/facade/rest/resources/V3Role.java | 3 - .../facade/rest/resources/V3RoleMapper.java | 4 - .../client/facade/rest/resources/V3Route.java | 3 - .../facade/rest/resources/V3RouteMapper.java | 20 +- .../resources/V3ServiceBindingMapper.java | 12 +- .../rest/resources/V3ServiceBroker.java | 2 - .../rest/resources/V3ServiceBrokerMapper.java | 9 - .../rest/resources/V3ServiceInstance.java | 5 +- .../resources/V3ServiceInstanceMapper.java | 34 +- .../rest/resources/V3ServiceKeyDetails.java | 3 - .../rest/resources/V3ServiceKeyMapper.java | 15 +- .../resources/V3ServiceOfferingMapper.java | 7 +- .../rest/resources/V3ServicePlanMapper.java | 10 +- .../client/facade/rest/resources/V3Space.java | 1 + .../facade/rest/resources/V3StackMapper.java | 4 - .../facade/rest/resources/V3TaskMapper.java | 5 +- .../CloudControllerClientIntegrationTest.java | 12 +- .../CloudControllerV3ClientPageUriTest.java | 7 +- .../facade/rest/ReactiveFanOutTest.java | 9 +- .../process/jobs/OrphanedDataCleaner.java | 18 +- ...ServiceCreateUpdateServiceActionsStep.java | 2 +- 78 files changed, 1602 insertions(+), 1665 deletions(-) create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Constants.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3Operations.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ClientConfigurationOptions.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Endpoints.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/HttpConnectionPair.java diff --git a/multiapps-controller-client/src/main/java/module-info.java b/multiapps-controller-client/src/main/java/module-info.java index 111c91e054..dfd1045713 100644 --- a/multiapps-controller-client/src/main/java/module-info.java +++ b/multiapps-controller-client/src/main/java/module-info.java @@ -38,5 +38,7 @@ requires static java.compiler; requires static jakarta.inject; requires static org.immutables.value; + requires io.netty.codec; + requires java.desktop; } \ No newline at end of file diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Constants.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Constants.java new file mode 100644 index 0000000000..705fc88798 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Constants.java @@ -0,0 +1,38 @@ +package org.cloudfoundry.multiapps.controller; + +import java.time.Duration; +import java.util.concurrent.TimeUnit; + +public class Constants { + + public static final Duration DEFAULT_CONNECT_TIMEOUT = Duration.ofMinutes(1); + public static final Duration JOB_POLL_MIN_INTERVAL = Duration.ofSeconds(1); + public static final Duration JOB_POLL_MAX_INTERVAL = Duration.ofSeconds(15); + public static final Duration DELETE_JOB_TIMEOUT = Duration.ofMinutes(5); + public static final Duration BINDING_OPERATIONS_TIMEOUT = Duration.ofMinutes(10); + public static final long PACKAGE_UPLOAD_JOB_POLLING_PERIOD = TimeUnit.SECONDS.toMillis(5); + + public static final int DEFAULT_CONNECTION_POOL_SIZE = 192; + public static final int UNDEFINED_PORT = -1; + public static final int MAX_CONCURRENT_PAGES = 256; + public static final int DEFAULT_CONCURRENT_TASKS = 256; + public static final int MAX_CHAR_LENGTH_FOR_PARAMS_IN_REQUEST = 4000; + + public static final String CONNECTION_POOL_NAME = "cf-controller-client"; + public static final String LOOP_RESOURCES_SUFFIX = "-loop-resources"; + public static final String CF_API_V3 = "/v3"; + public static final String HREF = "href"; + public static final String CLOUD_CONTROLLER_CF_ROOT_DOCUMENT_NAME = "cloud_controller_v3"; + public static final String LOG_CACHE_CF_ROOT_DOCUMENT_NAME = "log_cache"; + public static final String API_HOST_PREFIX = "api."; + public static final String LOG_CACHE_PREFIX = "log-cache."; + public static final String ROOT_DOCUMENT_LINKS_LIST = "links"; + + public static final String EMPTY_STRING = ""; + public static final String COLON = ":"; + public static final String PROTOCOL_SEPARATOR = "://"; + + public static final String PACKAGE_LINK = "package"; + public static final String WEB_PROCESS_TYPE = "web"; + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Messages.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Messages.java index af0d03d5f0..8705623590 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Messages.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Messages.java @@ -3,9 +3,52 @@ public class Messages { // INFO messages - public static final String WAITING_MS_BEFORE_RETRYING_WITH_TIMEOUT_OF_MS = "Waiting: {} ms before retrying with timeout of: {} ms"; + public static final String WAITING_MS_BEFORE_RETRYING_WITH_TIMEOUT_OF_MS = "Waiting: {} ms before retrying with timeout of: {} ms."; public static final String RATE_LIMITED_BY_CC_WAITING_S = "CC returned 429 with Retry-After: {} s. Waiting {} s (capped) before retrying."; public static final String RATE_LIMITED_BY_CC_NO_HEADER_WAITING_MS = "CC returned 429 without Retry-After header. Waiting {} ms before retrying."; public static final String RANDOM_WAIT_BEFORE_RETRY_MS = "Waiting {} ms (randomized) before retrying failed CC operation."; + // Exception messages + public static final String ERROR_OCCURRED_SETTING_UP_DEFAULT_SSL_CONTEXT = "An error occurred while setting up the default SSLContext."; + public static final String ERROR_OCCURRED_SETTING_UP_ALWAYS_APPROVING_SSL_CONTEXT = "An error occurred while setting up the always approving SSLContext."; + public static final String COULD_NOT_DETERMINE_API_ORIGIN_FROM_0 = "Could not determine the API origin from \"{0}\"."; + public static final String COULD_NOT_RESOLVE_URL_FROM_0_1 = "Could not resolve URL from \"{0}\" \"{1}\"."; + public static final String COULD_NOT_RESOLVE_LOG_CACHE_URL_FROM_0 = "Could not derive the log-cache URL from \"{0}\"."; + public static final String CF_ROOT_DOCUMENT_REQUEST_TO_0_RETURNED_1 = "CF root request to \"{0}\" returned \"{1}\"."; + public static final String INTERRUPTED_WHILE_CALLING_THE_CF_ROOT_URL_AT_0 = "Interrupted while calling the CF root URL at \"{0}\"."; + public static final String FAILED_TO_CALL_THE_CF_ROOT_AT_0_WITH_1 = "Failed to call the CF root at \"{0}\" with: \"{1}\"."; + public static final String SERVICE_KEY_0_NOT_FOUND = "Service key \"{0}\" not found."; + public static final String SERVICE_INSTANCE_0_NOT_FOUND = "Service instance \"{0}\" not found."; + public static final String SERVICE_BROKER_0_NOT_FOUND = "Service broker \"{0}\" not found."; + public static final String APPLICATION_0_NOT_FOUND = "Application \"{0}\" not found."; + public static final String DOMAIN_0_NOT_FOUND = "Domain \"{0}\" not found."; + public static final String SERVICE_PLAN_0_NOT_FOUND = "Service plan \"{0}\" not found."; + public static final String STACK_0_NOT_FOUND = "Stack \"{0}\" not found."; + public static final String ORGANISATION_0_NOT_FOUND = "Organization \"{0}\" not found."; + public static final String SPACE_WITH_GUID_0_NOT_FOUND = "Space with GUID \"{0}\" not found."; + public static final String APPLICATION_WITH_GUID_0_DOES_NOT_HAVE_A_DROPLET = "Application with guid \"{0}\" does not have a droplet"; + public static final String HOST_0_NOT_FOUND_FOR_DOMAIN_1 = "Host \"{0}\" not found for domain \"{1}\"."; + public static final String UNABLE_TO_0_WITHOUT_SPECIFYING_ORGANIZATION_AND_SPACE_TO_USE = "Unable to \"{0}\" without specifying organization and space to use."; + public static final String DOMAIN_0_NOT_FOUND_FOR_URI_1 = "Domain \"{0}\" not found for URI \"{1}\""; + public static final String SERVICE_BINDING_BETWEEN_SERVICE_WITH_GUID_0_AND_APPLICATION_WITH_GUID_1_NOT_FOUND = "Service binding between service with GUID \"{0}\" and application with GUID \"{1}\" not found."; + public static final String SERVICE_PLAN_WITH_GUID_0_NOT_AVAILABLE_FOR_SERVICE_INSTANCE_1 = "Service plan with guid \"{0}\" is not available for service instance \"{1}\"."; + public static final String NO_SERVICE_PLAN_FOUND = "Service plan with guid \"{0}\" for service instance with name \"{1}\" was not found."; + public static final String SERVICE_OFFERING_WITH_GUID_0_IS_NOT_AVAILABLE = "Service offering with guid \"{0}\" is not available."; + public static final String SERVICE_OFFERING_WITH_GUID_0_NOT_FOUND = "Service offering with guid \"{0}\" not found."; + public static final String TARGET_SPACE_REQUIRED_TO_CREATE_AN_APPLICATION = "A target space is required to create an application."; + public static final String JOB_0_DID_NOT_COMPLETE_WITHIN_1 = "Job \"{0}\" did not complete within \"{1}\""; + public static final String ORGANIZATION_WITH_GUID_0_NOT_FOUND = "Organization with GUID \"{0}\" not found."; + public static final String SPACE_0_NOT_FOUND_IN_ORGANIZATION_1 = "Space \"{0}\" not found in organization \"{1}\""; + public static final String INTERRUPTED_WHILE_POLLING_ASYNC_JOB_0 = "Interrupted while polling an async job: \"{0}\""; + public static final String UNKNOWN_JOB_STATE_0 = "Unknown job state: \"{0}\""; + public static final String UNKNOWN_SERVICE_INSTANCE_TYPE_0 = "Unknown service instance type: \"{0}\""; + public static final String UNKNOWN_USER_ROLE_0 = "Unknown user role: \"{0}\""; + + //Status texts + public static final String NOT_FOUND = "Not Found"; + public static final String JOB_TIMEOUT = "Job Timeout"; + public static final String JOB_FAILED = "Job Failed"; + public static final String BAD_REQUEST = "Bad Request"; + public static final String FORBIDDEN = "Forbidden"; + } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/AllowNulls.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/AllowNulls.java index 680efaa956..944e54edf6 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/AllowNulls.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/AllowNulls.java @@ -5,11 +5,6 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * Marks an Immutables collection attribute (typically a {@code Map}) as permitting {@code null} values. Project-owned replacement for - * {@code org.cloudfoundry.AllowNulls} (from cf-java-client) so the domain model no longer depends on the OSS client. Immutables - * recognises this marker by its simple name {@code AllowNulls}. - */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.CLASS) public @interface AllowNulls { diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/LogCacheClient.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/LogCacheClient.java index 67d4bba810..304a80dff8 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/LogCacheClient.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/LogCacheClient.java @@ -21,13 +21,6 @@ import org.slf4j.LoggerFactory; import org.springframework.web.client.RestClient; -/** - * Fetches recent application logs from Log-Cache. Reimplemented on a plain synchronous Spring {@link RestClient} against Log-Cache's - * JSON {@code read} REST API ({@code GET /api/v1/read/{source_id}}), removing the previous dependency on the OSS cf-java-client - * ({@code org.cloudfoundry.logcache.v1.*} / {@code org.cloudfoundry.reactor.*}). The {@link RestClient} is pre-configured with the - * log-cache base URL, bearer-token auth and request tags by the factory; this class only builds the request and maps the response to - * {@link ApplicationLog}, preserving the previous behaviour exactly. - */ public class LogCacheClient { private static final Logger LOGGER = LoggerFactory.getLogger(LogCacheClient.class); @@ -47,6 +40,7 @@ public List getRecentLogs(UUID applicationGuid, LocalDateTime of if (response == null) { throw new CloudException(MessageFormat.format(Messages.FAILED_TO_FETCH_APP_LOGS_FOR_APP, applicationGuid)); } + LOGGER.info(Messages.APP_LOGS_WERE_FETCHED_SUCCESSFULLY); return response.batch() .stream() @@ -61,6 +55,7 @@ private LogCacheReadResponse readApplicationLogs(UUID applicationGuid, LocalDate var secondsInNanos = Duration.ofSeconds(instant.getEpochSecond()) .toNanos(); long startTime = secondsInNanos + instant.getNano() + 1; + return restClient.get() .uri(uriBuilder -> uriBuilder.path("/api/v1/read/{sourceId}") .queryParam("envelope_types", "LOG") diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/HealthCheckType.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/HealthCheckType.java index e618ee4b45..992fa758a3 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/HealthCheckType.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/HealthCheckType.java @@ -3,9 +3,6 @@ public enum HealthCheckType { HTTP, PORT, PROCESS, @Deprecated NONE; - /** - * The CF v3 wire value (lowercase), matching the OSS {@code HealthCheckType.getValue()} this enum replaces at call sites. - */ public String getValue() { return name().toLowerCase(); } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/InstanceState.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/InstanceState.java index 90bd5f95d6..a5425130ed 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/InstanceState.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/InstanceState.java @@ -2,11 +2,7 @@ public enum InstanceState { CRASHED, DOWN, RUNNING, STARTING, UNKNOWN; - - /** - * Resolve from the CF v3 process {@code state} wire value (e.g. {@code RUNNING}). Takes the raw string rather than the OSS - * {@code ProcessState} enum, so the domain no longer depends on cf-java-client. Unknown/blank values map to {@link #UNKNOWN}. - */ + public static InstanceState valueOfWithDefault(String state) { if (state == null) { return UNKNOWN; diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/JobState.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/JobState.java index ccb2f6521f..9c3445f45a 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/JobState.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/JobState.java @@ -1,14 +1,11 @@ package org.cloudfoundry.multiapps.controller.client.facade.domain; +import java.text.MessageFormat; + import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; +import org.cloudfoundry.multiapps.controller.Messages; -/** - * State of an asynchronous Cloud Controller v3 job. Project-owned replacement for {@code org.cloudfoundry.client.v3.jobs.JobState}, - * so the domain model no longer depends on the OSS cf-java-client. JSON (de)serialization uses the lowercase wire value - * ({@code processing} / {@code polling} / {@code complete} / {@code failed}) via {@link JsonValue} / {@link JsonCreator} — matching the - * OSS enum so persisted models round-trip identically. - */ public enum JobState { PROCESSING("processing"), POLLING("polling"), COMPLETE("complete"), FAILED("failed"); @@ -31,6 +28,7 @@ public static JobState from(String value) { return state; } } - throw new IllegalArgumentException("Unknown job state: " + value); + + throw new IllegalArgumentException(MessageFormat.format(Messages.UNKNOWN_JOB_STATE_0, value)); } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/LastOperation.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/LastOperation.java index 8aefe77484..0e43b64c12 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/LastOperation.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/LastOperation.java @@ -1,9 +1,4 @@ package org.cloudfoundry.multiapps.controller.client.facade.domain; -/** - * Project-owned view of a Cloud Controller v3 resource's {@code last_operation} (raw string fields), replacing - * {@code org.cloudfoundry.client.v3.LastOperation} so the domain no longer depends on the OSS cf-java-client. All fields are the raw CF - * wire values; any may be {@code null}. - */ public record LastOperation(String type, String state, String description, String createdAt, String updatedAt) { } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/Metadata.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/Metadata.java index 979e357d6d..342e1d2d44 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/Metadata.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/Metadata.java @@ -8,15 +8,6 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -/** - * Project-owned replacement for {@code org.cloudfoundry.client.v3.Metadata} (CF v3 resource labels + annotations), so the domain model no - * longer depends on the OSS cf-java-client. - *

- * It is a faithful drop-in: the fluent {@link Builder} exposes the same {@code label(k,v)} / {@code annotation(k,v)} / - * {@code labels(map)} / {@code annotations(map)} methods the codebase already calls, and JSON (de)serialization uses the same - * {@code {"labels": {...}, "annotations": {...}}} shape as the OSS type, so persisted models (e.g. {@code CloudEntity.getV3Metadata()}) - * round-trip identically. - */ public final class Metadata { private final Map labels; @@ -47,9 +38,11 @@ public boolean equals(Object o) { if (this == o) { return true; } + if (o == null || getClass() != o.getClass()) { return false; } + Metadata metadata = (Metadata) o; return labels.equals(metadata.labels) && annotations.equals(metadata.annotations); } @@ -72,14 +65,12 @@ public static final class Builder { private Builder() { } - /** - * Copy labels and annotations from an existing {@link Metadata}. Mirrors the OSS builder's {@code from(Metadata)}. - */ public Builder from(Metadata metadata) { if (metadata != null) { this.labels.putAll(metadata.getLabels()); this.annotations.putAll(metadata.getAnnotations()); } + return this; } @@ -92,6 +83,7 @@ public Builder labels(Map labels) { if (labels != null) { this.labels.putAll(labels); } + return this; } @@ -108,6 +100,7 @@ public Builder annotations(Map annotations) { if (annotations != null) { this.annotations.putAll(annotations); } + return this; } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/ServiceCredentialBindingOperation.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/ServiceCredentialBindingOperation.java index ea06444480..73403202f1 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/ServiceCredentialBindingOperation.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/ServiceCredentialBindingOperation.java @@ -5,11 +5,10 @@ import java.time.format.DateTimeFormatter; import java.util.Arrays; -import org.immutables.value.Value; - import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import org.cloudfoundry.multiapps.controller.client.facade.Nullable; +import org.immutables.value.Value; @Value.Immutable @JsonSerialize(as = ImmutableServiceCredentialBindingOperation.class) @@ -35,6 +34,7 @@ public static ServiceCredentialBindingOperation from(LastOperation lastOperation String lastOperationDescription = lastOperation.description(); String lastOperationCreatedAt = lastOperation.createdAt(); String lastOperationUpdatedAt = lastOperation.updatedAt(); + return ImmutableServiceCredentialBindingOperation.builder() .type(ServiceCredentialBindingOperation.Type.fromString(lastOperationType)) .state(ServiceCredentialBindingOperation.State.fromString(lastOperationState)) @@ -54,8 +54,9 @@ public static Type fromString(String value) { .filter(type -> type.toString() .equals(value)) .findFirst() - .orElseThrow(() -> new IllegalArgumentException(MessageFormat.format("Illegal service binding operation type: \"{0}\"", - value))); + .orElseThrow( + () -> new IllegalArgumentException(MessageFormat.format("Illegal service binding operation type: \"{0}\"", + value))); } @Override diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/ServiceInstanceType.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/ServiceInstanceType.java index 90618abbf9..434443b306 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/ServiceInstanceType.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/ServiceInstanceType.java @@ -1,15 +1,11 @@ package org.cloudfoundry.multiapps.controller.client.facade.domain; +import java.text.MessageFormat; + import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; +import org.cloudfoundry.multiapps.controller.Messages; -/** - * Type of a Cloud Controller v3 service instance. Project-owned replacement for - * {@code org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceType}, so the domain model no longer depends on the OSS - * cf-java-client. Values mirror the CF v3 service instance {@code type} field, and JSON (de)serialization uses the wire value - * ({@code managed} / {@code user-provided}) via {@link JsonValue} / {@link JsonCreator} — matching the OSS enum's behaviour so persisted - * models round-trip identically. - */ public enum ServiceInstanceType { MANAGED("managed"), USER_PROVIDED("user-provided"); @@ -25,9 +21,6 @@ public String getValue() { return value; } - /** - * Resolve from the CF v3 wire value ({@code managed} / {@code user-provided}). Mirrors the OSS {@code ServiceInstanceType.from}. - */ @JsonCreator public static ServiceInstanceType from(String value) { for (ServiceInstanceType type : values()) { @@ -35,6 +28,7 @@ public static ServiceInstanceType from(String value) { return type; } } - throw new IllegalArgumentException("Unknown service instance type: " + value); + + throw new IllegalArgumentException(MessageFormat.format(Messages.UNKNOWN_SERVICE_INSTANCE_TYPE_0, value)); } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/ServiceOperation.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/ServiceOperation.java index b799c4c3ed..7edff81be7 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/ServiceOperation.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/ServiceOperation.java @@ -83,10 +83,11 @@ public static ServiceOperation fromLastOperation(LastOperation lastOperation) { if (lastOperation == null || lastOperation.type() == null || lastOperation.state() == null) { return null; } - Type type = Type.fromString(lastOperation.type()); - State state = State.fromString(lastOperation.state()); - String description = lastOperation.description(); - return new ServiceOperation(type, description, state); + + Type operationType = Type.fromString(lastOperation.type()); + State operationState = State.fromString(lastOperation.state()); + String operationDescription = lastOperation.description(); + return new ServiceOperation(operationType, operationDescription, operationState); } @Override diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/UserRole.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/UserRole.java index ed0b02389f..8644a5cdf2 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/UserRole.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/UserRole.java @@ -1,9 +1,12 @@ package org.cloudfoundry.multiapps.controller.client.facade.domain; +import java.text.MessageFormat; import java.util.Arrays; import java.util.Map; import java.util.stream.Collectors; +import org.cloudfoundry.multiapps.controller.Messages; + public enum UserRole { ORGANIZATION_AUDITOR, @@ -16,17 +19,15 @@ public enum UserRole { private static final Map NAMES_TO_VALUES = Arrays.stream(values()) .collect(Collectors.toMap(UserRole::getName, - roleType -> roleType)); + roleType -> roleType)); - /** - * Resolve from the CF v3 role {@code type} wire value (e.g. {@code space_developer}). Takes the raw string rather than the OSS - * {@code RoleType} enum, so the domain no longer depends on cf-java-client. - */ public static UserRole fromRoleType(String roleTypeValue) { UserRole userRole = NAMES_TO_VALUES.get(roleTypeValue); + if (userRole == null) { - throw new IllegalArgumentException("Unknown user role: " + roleTypeValue); + throw new IllegalArgumentException(MessageFormat.format(Messages.UNKNOWN_USER_ROLE_0, roleTypeValue)); } + return userRole; } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3Operations.java new file mode 100644 index 0000000000..12a19a3b8b --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3Operations.java @@ -0,0 +1,268 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.text.MessageFormat; +import java.time.Duration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.Messages; +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Staging; +import org.cloudfoundry.multiapps.controller.client.facade.dto.ApplicationToCreateDto; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Application; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ApplicationMapper; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; + +public class ApplicationsV3Operations { + + private static final ParameterizedTypeReference> APPLICATION_PAGE = new ParameterizedTypeReference<>() { + }; + + private static final Duration DELETE_JOB_TIMEOUT = Duration.ofMinutes(5); + + private final CloudControllerV3Client cc; + private final CloudSpace target; + + public ApplicationsV3Operations(CloudControllerV3Client cc, CloudSpace target) { + this.cc = cc; + this.target = target; + } + + public UUID createApplication(ApplicationToCreateDto dto) { + if (target == null || target.getGuid() == null) { + throw new CloudOperationException(HttpStatus.BAD_REQUEST, Messages.BAD_REQUEST, + Messages.TARGET_SPACE_REQUIRED_TO_CREATE_AN_APPLICATION); + } + Map body = new HashMap<>(); + body.put("name", dto.getName()); + body.put("lifecycle", buildLifecycle(dto.getStaging())); + body.put("relationships", Map.of("space", Map.of("data", Map.of("guid", target.getGuid() + .toString())))); + if (dto.getEnv() != null) { + body.put("environment_variables", dto.getEnv()); + } + if (dto.getMetadata() != null) { + body.put("metadata", Map.of("labels", dto.getMetadata() + .getLabels(), + "annotations", dto.getMetadata() + .getAnnotations())); + } + V3Application created = cc.getRestClient() + .post() + .uri(CloudControllerV3Endpoints.APPS) + .body(body) + .retrieve() + .body(V3Application.class); + UUID appGuid = UUID.fromString(created.guid()); + Map scale = new HashMap<>(); + if (dto.getMemoryInMb() != null) { + scale.put("memory_in_mb", dto.getMemoryInMb()); + } + if (dto.getDiskQuotaInMb() != null) { + scale.put("disk_in_mb", dto.getDiskQuotaInMb()); + } + if (!scale.isEmpty()) { + scaleWebProcess(appGuid, scale); + } + return appGuid; + } + + private Map buildLifecycle(Staging staging) { + if (staging == null) { + return Map.of("type", "buildpack", "data", Map.of()); + } + if (staging.getDockerInfo() != null) { + return Map.of("type", "docker", "data", Map.of()); + } + String type = staging.getLifecycleType() != null ? staging.getLifecycleType() + .name() + .toLowerCase() + : "buildpack"; + Map data = new HashMap<>(); + if (staging.getBuildpacks() != null) { + data.put("buildpacks", staging.getBuildpacks()); + } + if (staging.getStackName() != null) { + data.put("stack", staging.getStackName()); + } + return Map.of("type", type, "data", data); + } + + public void deleteApplication(String applicationName) { + UUID applicationGuid = getApplicationGuid(applicationName); + var response = cc.getRestClient() + .delete() + .uri(CloudControllerV3Endpoints.APP_BY_GUID, applicationGuid) + .retrieve() + .toBodilessEntity(); + cc.followAsyncJob(response, DELETE_JOB_TIMEOUT); + } + + public CloudApplication getApplication(String applicationName) { + return getApplication(applicationName, true); + } + + public CloudApplication getApplication(String applicationName, boolean required) { + V3Application app = findApplicationByName(applicationName); + if (app == null) { + if (required) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + MessageFormat.format(Messages.APPLICATION_0_NOT_FOUND, applicationName)); + } + return null; + } + return V3ApplicationMapper.toCloudApplication(app, target); + } + + public UUID getApplicationGuid(String applicationName) { + return getApplication(applicationName).getGuid(); + } + + public String getApplicationName(UUID applicationGuid) { + V3Application app = cc.get(CloudControllerV3Endpoints.APPS + "/" + applicationGuid, V3Application.class); + return app == null ? null : app.name(); + } + + public Map getApplicationEnvironment(UUID applicationGuid) { + V3Application.V3EnvironmentVariables env = cc.get(CloudControllerV3Endpoints.APPS + "/" + applicationGuid + + "/environment_variables", + V3Application.V3EnvironmentVariables.class); + return env == null || env.var() == null ? Map.of() : env.var(); + } + + public Map getApplicationEnvironment(String applicationName) { + return getApplicationEnvironment(getApplicationGuid(applicationName)); + } + + public List getApplications() { + return listApplications(applicationsQuery(null)).stream() + .map(app -> V3ApplicationMapper.toCloudApplication(app, target)) + .toList(); + } + + public List getApplicationsByMetadataLabelSelector(String labelSelector) { + String query = applicationsQuery(null); + if (labelSelector != null) { + query = query + CloudControllerV3Endpoints.AMPERSAND_LABEL_SELECTOR + labelSelector; + } + return listApplications(query).stream() + .map(app -> V3ApplicationMapper.toCloudApplication(app, target)) + .toList(); + } + + public void startApplication(String applicationName) { + UUID guid = getApplicationGuid(applicationName); + cc.getRestClient() + .post() + .uri(CloudControllerV3Endpoints.APP_START, guid) + .retrieve() + .toBodilessEntity(); + } + + public void stopApplication(String applicationName) { + UUID guid = getApplicationGuid(applicationName); + cc.getRestClient() + .post() + .uri(CloudControllerV3Endpoints.APP_STOP, guid) + .retrieve() + .toBodilessEntity(); + } + + public void rename(String applicationName, String newName) { + UUID guid = getApplicationGuid(applicationName); + try { + cc.getRestClient() + .patch() + .uri(CloudControllerV3Endpoints.APP_BY_GUID, guid) + .body(Map.of("name", newName)) + .retrieve() + .toBodilessEntity(); + } catch (CloudOperationException e) { + //the Cloud Controller can return 503 but the rename might have happened already + if (e.getStatusCode() == HttpStatus.SERVICE_UNAVAILABLE && newName.equals(getApplicationName(guid))) { + return; + } + throw e; + } + } + + public void updateApplicationInstances(String applicationName, int instances) { + scaleWebProcess(getApplicationGuid(applicationName), Map.of("instances", instances)); + } + + public void updateApplicationMemory(String applicationName, int memory) { + scaleWebProcess(getApplicationGuid(applicationName), Map.of("memory_in_mb", memory)); + } + + public void updateApplicationDiskQuota(String applicationName, int disk) { + scaleWebProcess(getApplicationGuid(applicationName), Map.of("disk_in_mb", disk)); + } + + public void updateApplicationEnv(String applicationName, Map env) { + UUID guid = getApplicationGuid(applicationName); + cc.getRestClient() + .patch() + .uri(CloudControllerV3Endpoints.APP_ENV_VARS, guid) + .body(Map.of("var", env)) + .retrieve() + .toBodilessEntity(); + } + + public void bindDropletToApp(UUID dropletGuid, UUID applicationGuid) { + cc.getRestClient() + .patch() + .uri(CloudControllerV3Endpoints.APP_CURRENT_DROPLET, applicationGuid) + .body(Map.of("data", Map.of("guid", dropletGuid.toString()))) + .retrieve() + .toBodilessEntity(); + } + + public void updateApplicationMetadata(UUID guid, Metadata metadata) { + cc.getRestClient() + .patch() + .uri(CloudControllerV3Endpoints.APP_BY_GUID, guid) + .body(Map.of("metadata", Map.of("labels", metadata.getLabels(), "annotations", metadata.getAnnotations()))) + .retrieve() + .toBodilessEntity(); + } + + private void scaleWebProcess(UUID applicationGuid, Map scaleBody) { + cc.getRestClient() + .post() + .uri(CloudControllerV3Endpoints.APP_WEB_PROCESS_SCALE, applicationGuid) + .body(scaleBody) + .retrieve() + .toBodilessEntity(); + } + + private V3Application findApplicationByName(String applicationName) { + List apps = listApplications(applicationsQuery(applicationName)); + return apps.isEmpty() ? null : apps.get(0); + } + + private List listApplications(String query) { + return cc.list(query, APPLICATION_PAGE); + } + + private String applicationsQuery(String name) { + StringBuilder query = new StringBuilder(CloudControllerV3Endpoints.APPS + CloudControllerV3Endpoints.QUERY_PER_PAGE + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE); + if (target != null && target.getGuid() != null) { + query.append(CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) + .append(target.getGuid()); + } + if (name != null) { + query.append(CloudControllerV3Endpoints.AMPERSAND_NAMES) + .append(name); + } + return query.toString(); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3Operations.java index 2605aeb4a8..c9416c6bd0 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3Operations.java @@ -5,33 +5,23 @@ import java.util.UUID; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudBuild; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Build; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3BuildMapper; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; import org.springframework.core.ParameterizedTypeReference; -/** - * CF v3 Builds operations for the in-house {@link CloudControllerRestClientV3Impl}. Reproduces the HTTP shape of the OSS - * {@code CloudControllerRestClientImpl} build methods (endpoints from {@code builds()} / {@code applicationsV3().listBuilds}), mapping the - * v3 wire model to the {@link CloudBuild} domain type via {@link V3BuildMapper}. - */ public class BuildsV3Operations { - private static final int MAX_PAGE_SIZE = 5000; - private final CloudControllerV3Client cc; - private final CloudSpace target; - public BuildsV3Operations(CloudControllerV3Client cc, CloudSpace target) { + public BuildsV3Operations(CloudControllerV3Client cc) { this.cc = cc; - this.target = target; } public CloudBuild createBuild(UUID packageGuid) { V3Build build = cc.getRestClient() .post() - .uri("/v3/builds") + .uri(CloudControllerV3Endpoints.BUILDS) .body(Map.of("package", Map.of("guid", packageGuid.toString()))) .retrieve() .body(V3Build.class); @@ -39,23 +29,25 @@ public CloudBuild createBuild(UUID packageGuid) { } public CloudBuild getBuild(UUID buildGuid) { - V3Build build = cc.get("/v3/builds/" + buildGuid, V3Build.class); + V3Build build = cc.get(CloudControllerV3Endpoints.BUILDS + "/" + buildGuid, V3Build.class); return V3BuildMapper.toCloudBuild(build); } public List getBuildsForApplication(UUID applicationGuid) { - String uri = "/v3/apps/" + applicationGuid + "/builds?per_page=" + MAX_PAGE_SIZE; + String uri = CloudControllerV3Endpoints.APPS + "/" + applicationGuid + "/builds" + CloudControllerV3Endpoints.QUERY_PER_PAGE + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; return cc.list(uri, new ParameterizedTypeReference>() { - }) + }) .stream() .map(V3BuildMapper::toCloudBuild) .toList(); } public List getBuildsForPackage(UUID packageGuid) { - String uri = "/v3/builds?package_guids=" + packageGuid + "&per_page=" + MAX_PAGE_SIZE; + String uri = CloudControllerV3Endpoints.BUILDS + CloudControllerV3Endpoints.QUERY_PACKAGE_GUIDS + packageGuid + + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; return cc.list(uri, new ParameterizedTypeReference>() { - }) + }) .stream() .map(V3BuildMapper::toCloudBuild) .toList(); diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ClientConfigurationOptions.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ClientConfigurationOptions.java new file mode 100644 index 0000000000..5e9ef98e2d --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ClientConfigurationOptions.java @@ -0,0 +1,9 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.time.Duration; +import java.util.Optional; + +public record ClientConfigurationOptions(Optional connectionTimeout, Optional responseTimeout, + Optional sslHandshakeTimeout, Optional connectionPoolSize, + Optional threadPoolSize, boolean trustSelfSignedCertificates) { +} \ No newline at end of file diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientBuilder.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientBuilder.java index e1cf3ba75c..754b658584 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientBuilder.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientBuilder.java @@ -6,9 +6,16 @@ import java.util.List; import java.util.Map; import java.util.Optional; - +import javax.net.ssl.SSLException; import javax.net.ssl.X509TrustManager; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.netty.channel.ChannelOption; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; +import org.cloudfoundry.multiapps.controller.Constants; +import org.cloudfoundry.multiapps.controller.Messages; +import org.cloudfoundry.multiapps.controller.client.facade.oauth2.OAuthClient; import org.springframework.http.HttpHeaders; import org.springframework.http.client.ReactorClientHttpRequestFactory; import org.springframework.http.client.reactive.ReactorClientHttpConnector; @@ -17,91 +24,50 @@ import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.web.client.RestClient; -import org.springframework.web.reactive.function.client.ExchangeFilterFunction; import org.springframework.web.reactive.function.client.ClientRequest; +import org.springframework.web.reactive.function.client.ExchangeFilterFunction; import org.springframework.web.reactive.function.client.WebClient; - -import com.fasterxml.jackson.databind.ObjectMapper; - -import io.netty.channel.ChannelOption; -import io.netty.handler.ssl.SslContextBuilder; -import org.cloudfoundry.multiapps.controller.client.facade.oauth2.OAuthClient; import reactor.netty.http.client.HttpClient; import reactor.netty.resources.ConnectionProvider; import reactor.netty.resources.LoopResources; +import reactor.netty.tcp.SslProvider; -/** - * Builds the blocking Spring {@link RestClient} used by {@link CloudControllerRestClientV3Impl} to talk to the Cloud Controller v3 - * REST API directly, without the OSS cf-java-client. - *

- * The transport is Reactor-Netty (via Spring's {@link ReactorClientHttpRequestFactory}, which lives in {@code spring-web} — NOT - * {@code spring-webflux}). This is the same HTTP engine the deploy-service ran on under the OSS client, so its proven behavior - * (connection pooling, TLS, timeouts, proxy handling on the target landscapes) is preserved; only the cf-java-client library on top of it - * was removed. The tuning knobs the OSS {@code DefaultConnectionContext} honored are restored here: pool size (via {@link ConnectionProvider}), - * connect timeout, and response timeout. The caller still consumes everything synchronously through {@link RestClient} — Reactor-Netty is - * used purely as the transport, exactly as before. - *

- * Every request carries a bearer token from {@link OAuthClient} (refreshed as needed), and non-2xx responses are mapped to - * {@code CloudOperationException} by the existing {@link CloudControllerResponseErrorHandler}. - */ public final class CloudControllerRestClientBuilder { - private static final Duration DEFAULT_CONNECT_TIMEOUT = Duration.ofMinutes(1); - private static final int DEFAULT_CONNECTION_POOL_SIZE = 192; - private static final String CONNECTION_POOL_NAME = "cf-controller-client"; - private CloudControllerRestClientBuilder() { } - public static RestClient build(URL v3ApiUrl, OAuthClient oAuthClient, Map requestTags, TransportOptions options) { - return buildRestClient(buildHttpClient(options), v3ApiUrl, oAuthClient, requestTags); + public static RestClient build(URL v3ApiUrl, OAuthClient oAuthClient, Map requestTags, + ClientConfigurationOptions configOptions) { + return buildRestClient(buildHttpClient(configOptions), v3ApiUrl, oAuthClient, requestTags); } - /** - * Build a blocking {@link RestClient} on the given (already-configured) Reactor-Netty {@link HttpClient}. Sharing one - * {@code HttpClient} between the RestClient and the {@link #buildWebClient WebClient} means both ride the same connection pool, - * timeouts, TLS and proxy settings. - */ public static RestClient buildRestClient(HttpClient httpClient, URL baseUrl, OAuthClient oAuthClient, Map requestTags) { return RestClient.builder() .baseUrl(baseUrl.toString()) .requestFactory(new ReactorClientHttpRequestFactory(httpClient)) - // Pass a fully-formed converter list via the List overload — deliberately NOT the Consumer overload. - // messageConverters(Consumer) first calls initMessageConverters() to build Spring's DEFAULT list and only then - // hands it to the consumer; that default init eagerly constructs AllEncompassingFormHttpMessageConverter -> - // MappingJackson2YamlHttpMessageConverter -> Jackson2ObjectMapperBuilder.yaml(), which loads a YAMLFactory that is - // incompatible with this codebase's jackson-core (mixed Jackson 2/3 classpath) and throws VerifyError at load time - // — so clearing the list afterwards is too late. The List overload skips initMessageConverters() entirely. - // Converters: byte[] + String + JSON (from an ObjectMapper we control) for the v3 JSON APIs, plus a plain - // FormHttpMessageConverter for multipart/form-data (the POST /v3/packages/{guid}/upload app-bits upload). We use - // FormHttpMessageConverter, NOT AllEncompassingFormHttpMessageConverter, precisely because the latter is what - // triggers the YAML VerifyError above. .messageConverters(List.of(new ByteArrayHttpMessageConverter(), new StringHttpMessageConverter(), new FormHttpMessageConverter(), new MappingJackson2HttpMessageConverter(new ObjectMapper()))) .requestInterceptor((request, body, execution) -> { String authorization = oAuthClient.getAuthorizationHeaderValue(); + if (authorization != null) { request.getHeaders() .set(HttpHeaders.AUTHORIZATION, authorization); } - requestTags.forEach((key, value) -> request.getHeaders() - .set(key, value)); + + requestTags.forEach((String key, String value) -> request.getHeaders() + .set(key, value)); + return execution.execute(request, body); }) .defaultStatusHandler(new CloudControllerResponseErrorHandler()) .build(); } - /** - * Build a reactive {@link WebClient} on the same Reactor-Netty {@link HttpClient} as the RestClient, applying the identical - * per-request bearer auth (from {@link OAuthClient}, refreshed as needed) and request-tag headers. This is used only for the - * intra-operation concurrency spots (concurrent pagination) — the same mechanism the OSS client used — while the rest of the client - * stays on the blocking RestClient. Response error handling for these calls is done by the callers (they map WebClient errors the - * same way {@link CloudControllerResponseErrorHandler} does for the RestClient). - */ public static WebClient buildWebClient(HttpClient httpClient, URL baseUrl, OAuthClient oAuthClient, Map requestTags) { return WebClient.builder() @@ -113,90 +79,95 @@ public static WebClient buildWebClient(HttpClient httpClient, URL baseUrl, OAuth private static ExchangeFilterFunction authAndTagsFilter(OAuthClient oAuthClient, Map requestTags) { return (request, next) -> { - ClientRequest.Builder builder = ClientRequest.from(request); - String authorization = oAuthClient.getAuthorizationHeaderValue(); - if (authorization != null) { - builder.header(HttpHeaders.AUTHORIZATION, authorization); + ClientRequest.Builder requestBuilder = ClientRequest.from(request); + String authorizationValue = oAuthClient.getAuthorizationHeaderValue(); + + if (authorizationValue != null) { + requestBuilder.header(HttpHeaders.AUTHORIZATION, authorizationValue); } - requestTags.forEach(builder::header); - return next.exchange(builder.build()); + + requestTags.forEach((String key, String value) -> { + requestBuilder.header(key, value); + }); + + return next.exchange(requestBuilder.build()); }; } - static HttpClient buildHttpClient(TransportOptions options) { - int poolSize = options.connectionPoolSize() - .orElse(DEFAULT_CONNECTION_POOL_SIZE); - ConnectionProvider connectionProvider = ConnectionProvider.builder(CONNECTION_POOL_NAME) + static HttpClient buildHttpClient(ClientConfigurationOptions configOptions) { + int poolSize = configOptions.connectionPoolSize() + .orElse(Constants.DEFAULT_CONNECTION_POOL_SIZE); + ConnectionProvider connectionProvider = ConnectionProvider.builder(Constants.CONNECTION_POOL_NAME) .maxConnections(poolSize) .build(); HttpClient httpClient = HttpClient.create(connectionProvider) .followRedirect(true) - // Honor JVM standard proxy settings (-Dhttp(s).proxyHost/proxyPort/nonProxyHosts), matching the - // OSS DefaultConnectionContext's proxy support. No-op when no proxy is configured, so it is safe - // on direct-connect landscapes and required on proxied ones. .proxyWithSystemProperties(); - int connectTimeoutMillis = (int) options.connectTimeout() - .orElse(DEFAULT_CONNECT_TIMEOUT) - .toMillis(); + int connectTimeoutMillis = (int) configOptions.connectionTimeout() + .orElse(Constants.DEFAULT_CONNECT_TIMEOUT) + .toMillis(); httpClient = httpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeoutMillis); - // Size the I/O event-loop pool from threadPoolSize, mirroring the OSS DefaultConnectionContext.threadPoolSize (which created a - // reactor-netty LoopResources the client ran on). When unset, reactor-netty uses its shared default LoopResources. - Optional threadPoolSize = options.threadPoolSize(); + + Optional threadPoolSize = configOptions.threadPoolSize(); if (threadPoolSize.isPresent()) { - LoopResources loopResources = LoopResources.create(CONNECTION_POOL_NAME + "-loop", threadPoolSize.get(), true); + LoopResources loopResources = LoopResources.create(Constants.CONNECTION_POOL_NAME + Constants.LOOP_RESOURCES_SUFFIX, + threadPoolSize.get(), true); httpClient = httpClient.runOn(loopResources); } - Optional responseTimeout = options.responseTimeout(); + + Optional responseTimeout = configOptions.responseTimeout(); if (responseTimeout.isPresent()) { httpClient = httpClient.responseTimeout(responseTimeout.get()); } - Optional handshakeTimeout = options.sslHandshakeTimeout(); - if (options.trustSelfSignedCertificates()) { - httpClient = httpClient.secure(spec -> { - var sslBuilder = spec.sslContext(buildTrustAllSslContext()); - handshakeTimeout.ifPresent(sslBuilder::handshakeTimeout); + + Optional handshakeTimeout = configOptions.sslHandshakeTimeout(); + if (configOptions.trustSelfSignedCertificates()) { + httpClient = httpClient.secure(sslSpecification -> { + SslProvider.Builder sslBuilder = sslSpecification.sslContext(buildTrustAllSslContext()); + if (handshakeTimeout.isPresent()) { + sslBuilder.handshakeTimeout(handshakeTimeout.get()); + } }); } else if (handshakeTimeout.isPresent()) { - httpClient = httpClient.secure(spec -> { - var sslBuilder = spec.sslContext(defaultClientSslContext()); + httpClient = httpClient.secure(sslSpecification -> { + SslProvider.Builder sslBuilder = sslSpecification.sslContext(buildDefaultClientSslContext()); sslBuilder.handshakeTimeout(handshakeTimeout.get()); }); } else { httpClient = httpClient.secure(); } + return httpClient; } - private static io.netty.handler.ssl.SslContext defaultClientSslContext() { + private static SslContext buildDefaultClientSslContext() { try { return SslContextBuilder.forClient() .build(); - } catch (javax.net.ssl.SSLException e) { - throw new IllegalStateException("An error occurred setting up the default SSLContext", e); + } catch (SSLException e) { + throw new IllegalStateException(Messages.ERROR_OCCURRED_SETTING_UP_DEFAULT_SSL_CONTEXT, e); } } - private static io.netty.handler.ssl.SslContext buildTrustAllSslContext() { + private static SslContext buildTrustAllSslContext() { try { return SslContextBuilder.forClient() - .trustManager(trustAllManager()) + .trustManager(getTrustAllCertificatesManager()) .build(); - } catch (javax.net.ssl.SSLException e) { - throw new IllegalStateException("An error occurred setting up the SSLContext", e); + } catch (SSLException e) { + throw new IllegalStateException(Messages.ERROR_OCCURRED_SETTING_UP_ALWAYS_APPROVING_SSL_CONTEXT, e); } } - private static X509TrustManager trustAllManager() { + private static X509TrustManager getTrustAllCertificatesManager() { return new X509TrustManager() { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) { - // NOSONAR - self-signed certificate trust is opt-in via TransportOptions } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) { - // NOSONAR - self-signed certificate trust is opt-in via TransportOptions } @Override @@ -207,15 +178,4 @@ public X509Certificate[] getAcceptedIssuers() { }; } - /** - * The transport-tuning surface, honored by the Reactor-Netty transport: {@code connectTimeout} and {@code responseTimeout} on the - * client, {@code sslHandshakeTimeout} on the TLS layer, {@code connectionPoolSize} on the {@link ConnectionProvider}, and - * {@code threadPoolSize} on the I/O event-loop ({@link LoopResources}). Mirrors the knobs the OSS {@code DefaultConnectionContext} - * exposed. - */ - public record TransportOptions(Optional connectTimeout, Optional responseTimeout, - Optional sslHandshakeTimeout, Optional connectionPoolSize, - Optional threadPoolSize, boolean trustSelfSignedCertificates) { - } - } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientFactory.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientFactory.java index b3f0087d86..32c8c45c33 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientFactory.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientFactory.java @@ -5,12 +5,16 @@ import java.net.URL; import java.net.http.HttpRequest; import java.net.http.HttpResponse; +import java.text.MessageFormat; import java.time.Duration; import java.util.Collections; import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import org.cloudfoundry.multiapps.controller.Constants; +import org.cloudfoundry.multiapps.controller.Messages; import org.cloudfoundry.multiapps.controller.client.facade.CloudCredentials; import org.cloudfoundry.multiapps.controller.client.facade.CloudException; import org.cloudfoundry.multiapps.controller.client.facade.adapters.LogCacheClient; @@ -19,26 +23,16 @@ import org.cloudfoundry.multiapps.controller.client.facade.util.JsonUtil; import org.cloudfoundry.multiapps.controller.client.facade.util.RestUtil; import org.immutables.value.Value; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.web.client.RestClient; import org.springframework.web.reactive.function.client.WebClient; import reactor.netty.http.client.HttpClient; -/** - * Builds the in-house {@link CloudControllerRestClient} (and the auxiliary {@link CloudSpaceClient} / {@link LogCacheClient}) that talk to - * the Cloud Controller v3 REST API directly through a blocking Spring {@link RestClient}, with no dependency on the OSS cf-java-client. - */ @Value.Immutable public abstract class CloudControllerRestClientFactory { - private static final Logger LOGGER = LoggerFactory.getLogger(CloudControllerRestClientFactory.class); - private final RestUtil restUtil = new RestUtil(); - // Per-host transport cache — mirrors the OSS connectionContextCache. One shared reactor-netty HttpClient (pool + TLS) and one root-link - // discovery per Cloud Controller host, reused across the CF / space / log-cache clients. The per-call OAuth token and request tags are - // NOT cached here; they are applied per RestClient/WebClient build (cheap), so caching the transport is safe. - private final Map hostTransports = new ConcurrentHashMap<>(); + + private final Map cachedHttpConnectionPair = new ConcurrentHashMap<>(); public abstract Optional getSslHandshakeTimeout(); @@ -63,139 +57,143 @@ public CloudControllerRestClient createClient(URL controllerUrl, CloudCredential } public CloudControllerRestClient createClient(URL controllerUrl, CloudCredentials credentials, CloudSpace target) { - return createClient(controllerUrl, credentials, target, createOAuthClient(controllerUrl, credentials.getOrigin()), + return createClient(controllerUrl, credentials, target, createOAuthClient(controllerUrl), Collections.emptyMap()); } public CloudControllerRestClient createClient(URL controllerUrl, CloudCredentials credentials, CloudSpace target, OAuthClient oAuthClient, Map requestTags) { oAuthClient.init(credentials); - HostTransport transport = hostTransport(controllerUrl, requestTags); - URL v3ApiUrl = transport.v3ApiUrl(); - URL origin = toOrigin(v3ApiUrl); - HttpClient httpClient = transport.httpClient(); - RestClient restClient = CloudControllerRestClientBuilder.buildRestClient(httpClient, origin, oAuthClient, requestTags); - WebClient webClient = CloudControllerRestClientBuilder.buildWebClient(httpClient, origin, oAuthClient, requestTags); - CloudControllerV3Client cc = new CloudControllerV3Client(restClient, webClient); - // Per-upload client factory: the app-bits upload (POST /v3/packages/{guid}/upload) must be bounded by the caller's - // per-MTA uploadTimeout, not the shared 900s response timeout. Build a dedicated RestClient whose response timeout is - // that uploadTimeout, mirroring the OSS startUpload's .timeout(uploadTimeout). Everything else (pool, connect, TLS) matches. - java.util.function.Function uploadRestClientFactory = uploadTimeout -> { - // Upload uses a one-off client with only its response timeout overridden; no dedicated event-loop pool (threadPoolSize empty) - // so it does not spin up its own LoopResources. - var uploadOptions = new CloudControllerRestClientBuilder.TransportOptions(getConnectTimeout(), Optional.of(uploadTimeout), + HttpConnectionPair httpConnectionPair = getCachedHttpConnectionPair(controllerUrl, requestTags); + URL v3ApiUrl = httpConnectionPair.v3ApiUrl(); + URL baseUrl = extractBaseUrl(v3ApiUrl); + + HttpClient httpClient = httpConnectionPair.httpClient(); + RestClient restClient = CloudControllerRestClientBuilder.buildRestClient(httpClient, baseUrl, oAuthClient, requestTags); + WebClient webClient = CloudControllerRestClientBuilder.buildWebClient(httpClient, baseUrl, oAuthClient, requestTags); + + CloudControllerV3Client client = new CloudControllerV3Client(restClient, webClient); + + Function uploadRestClientFactory = uploadTimeout -> { + ClientConfigurationOptions uploadOptions = new ClientConfigurationOptions(getConnectTimeout(), Optional.of(uploadTimeout), getSslHandshakeTimeout(), getConnectionPoolSize(), Optional.empty(), shouldTrustSelfSignedCertificates()); - return CloudControllerRestClientBuilder.build(origin, oAuthClient, requestTags, uploadOptions); + return CloudControllerRestClientBuilder.build(baseUrl, oAuthClient, requestTags, uploadOptions); }; - return new CloudControllerRestClientV3Impl(v3ApiUrl, oAuthClient, target, restClient, cc, uploadRestClientFactory); + + return new CloudControllerRestClientV3Impl(target, client, uploadRestClientFactory); } public CloudSpaceClient createSpaceClient(URL controllerUrl, OAuthClient oAuthClient, Map requestTags) { - HostTransport transport = hostTransport(controllerUrl, requestTags); - URL origin = toOrigin(transport.v3ApiUrl()); - RestClient restClient = CloudControllerRestClientBuilder.buildRestClient(transport.httpClient(), origin, oAuthClient, requestTags); - WebClient webClient = CloudControllerRestClientBuilder.buildWebClient(transport.httpClient(), origin, oAuthClient, requestTags); + HttpConnectionPair httpConnectionPair = getCachedHttpConnectionPair(controllerUrl, requestTags); + URL baseUrl = extractBaseUrl(httpConnectionPair.v3ApiUrl()); + + RestClient restClient = CloudControllerRestClientBuilder.buildRestClient(httpConnectionPair.httpClient(), baseUrl, oAuthClient, + requestTags); + WebClient webClient = CloudControllerRestClientBuilder.buildWebClient(httpConnectionPair.httpClient(), baseUrl, oAuthClient, + requestTags); + return new CloudSpaceClient(new CloudControllerV3Client(restClient, webClient)); } public LogCacheClient createLogCacheClient(URL controllerUrl, OAuthClient oAuthClient, Map requestTags) { - HostTransport transport = hostTransport(controllerUrl, requestTags); - RestClient restClient = CloudControllerRestClientBuilder.buildRestClient(transport.httpClient(), transport.logCacheUrl(), + HttpConnectionPair httpConnectionPair = getCachedHttpConnectionPair(controllerUrl, requestTags); + RestClient restClient = CloudControllerRestClientBuilder.buildRestClient(httpConnectionPair.httpClient(), + httpConnectionPair.logCacheUrl(), oAuthClient, requestTags); return new LogCacheClient(restClient); } - // Get (or lazily build) the shared per-host transport: one reactor-netty HttpClient + the resolved v3 / log-cache URLs, discovered - // once per host. Keyed on the controller host so the CF / space / log-cache clients for the same landscape share the pool. - private HostTransport hostTransport(URL controllerUrl, Map requestTags) { - return hostTransports.computeIfAbsent(controllerUrl.getHost(), host -> { - HttpClient httpClient = CloudControllerRestClientBuilder.buildHttpClient(transportOptions()); - URL v3ApiUrl = deriveV3ApiUrl(controllerUrl, requestTags); - URL logCacheUrl = deriveLogCacheUrl(controllerUrl, requestTags); - return new HostTransport(httpClient, v3ApiUrl, logCacheUrl); + private HttpConnectionPair getCachedHttpConnectionPair(URL controllerUrl, Map requestTags) { + return cachedHttpConnectionPair.computeIfAbsent(controllerUrl.getHost(), host -> { + HttpClient httpClient = CloudControllerRestClientBuilder.buildHttpClient(getClientConfigurationOptions()); + URL v3ApiUrl = resolveV3ApiUrl(controllerUrl, requestTags); + URL logCacheUrl = resolveLogCacheUrl(controllerUrl, requestTags); + return new HttpConnectionPair(httpClient, v3ApiUrl, logCacheUrl); }); } - private CloudControllerRestClientBuilder.TransportOptions transportOptions() { - return new CloudControllerRestClientBuilder.TransportOptions(getConnectTimeout(), getResponseTimeout(), getSslHandshakeTimeout(), - getConnectionPoolSize(), getThreadPoolSize(), - shouldTrustSelfSignedCertificates()); + private ClientConfigurationOptions getClientConfigurationOptions() { + return new ClientConfigurationOptions(getConnectTimeout(), getResponseTimeout(), getSslHandshakeTimeout(), + getConnectionPoolSize(), getThreadPoolSize(), + shouldTrustSelfSignedCertificates()); } - // scheme://host[:port] of the given URL, dropping any path (e.g. the trailing /v3). The RestClient baseUrl must be the origin only, - // because every operation path already carries the "/v3/..." prefix. - private URL toOrigin(URL url) { + private URL extractBaseUrl(URL url) { try { int port = url.getPort(); - String origin = url.getProtocol() + "://" + url.getHost() + (port == -1 ? "" : ":" + port); + String origin = url.getProtocol() + Constants.PROTOCOL_SEPARATOR + url.getHost() + (port == Constants.UNDEFINED_PORT + ? Constants.EMPTY_STRING + : Constants.COLON + port); return URI.create(origin) .toURL(); } catch (MalformedURLException e) { - throw new IllegalArgumentException("Could not derive the API origin from " + url, e); + throw new IllegalArgumentException(MessageFormat.format(Messages.COULD_NOT_DETERMINE_API_ORIGIN_FROM_0, url), e); } } - // Resolve the CF v3 API base URL from the CF root "cloud_controller_v3" link; fall back to controllerUrl + "/v3". - @SuppressWarnings("unchecked") - private URL deriveV3ApiUrl(URL controllerUrl, Map requestTags) { + private URL resolveV3ApiUrl(URL controllerUrl, Map requestTags) { try { - Map links = fetchRootLinks(controllerUrl, requestTags); - Map ccv3 = (Map) links.get("cloud_controller_v3"); - return URI.create((String) ccv3.get("href")) + Map rootDocumentLinks = getCloudControllerRootDocumentLinks(controllerUrl, requestTags); + Map cloudControllerUrlV3 = (Map) rootDocumentLinks.get( + Constants.CLOUD_CONTROLLER_CF_ROOT_DOCUMENT_NAME); + return URI.create((String) cloudControllerUrlV3.get(Constants.HREF)) .toURL(); } catch (Exception e) { - return fallbackUrl(controllerUrl, "/v3"); + return fallbackUrl(controllerUrl, Constants.CF_API_V3); } } - // Log-Cache base URL from the CF root "log_cache" link; fall back to the "log-cache" subdomain of the controller host. - @SuppressWarnings("unchecked") - private URL deriveLogCacheUrl(URL controllerUrl, Map requestTags) { + private URL resolveLogCacheUrl(URL controllerUrl, Map requestTags) { try { - Map links = fetchRootLinks(controllerUrl, requestTags); - Map logCache = (Map) links.get("log_cache"); - return URI.create((String) logCache.get("href")) + Map rootDocumentLinks = getCloudControllerRootDocumentLinks(controllerUrl, requestTags); + Map logCacheUrl = (Map) rootDocumentLinks.get(Constants.LOG_CACHE_CF_ROOT_DOCUMENT_NAME); + return URI.create((String) logCacheUrl.get(Constants.HREF)) .toURL(); } catch (Exception e) { String host = controllerUrl.getHost(); - String logCacheHost = host.startsWith("api.") ? "log-cache." + host.substring("api.".length()) : "log-cache." + host; + String logCacheHost = host.startsWith(Constants.API_HOST_PREFIX) ? Constants.LOG_CACHE_PREFIX + host.substring( + Constants.API_HOST_PREFIX.length()) : Constants.LOG_CACHE_PREFIX + host; try { - return URI.create(controllerUrl.getProtocol() + "://" + logCacheHost) + return URI.create(controllerUrl.getProtocol() + Constants.PROTOCOL_SEPARATOR + logCacheHost) .toURL(); } catch (MalformedURLException me) { - throw new CloudException("Could not derive the log-cache URL from " + controllerUrl, me); + throw new CloudException(MessageFormat.format(Messages.COULD_NOT_RESOLVE_LOG_CACHE_URL_FROM_0, controllerUrl), me); } } } @SuppressWarnings("unchecked") - private Map fetchRootLinks(URL controllerUrl, Map requestTags) { + private Map getCloudControllerRootDocumentLinks(URL controllerUrl, Map requestTags) { try { HttpRequest.Builder requestBuilder = HttpRequest.newBuilder() .GET() .uri(controllerUrl.toURI()) - .timeout(Duration.ofMinutes(1)); + .timeout(Constants.DEFAULT_CONNECT_TIMEOUT); requestTags.forEach(requestBuilder::header); - HttpResponse response = rootHttpClient().send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString()); + HttpResponse response = getSimpleHttpClient().send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() / 100 != 2) { - throw new CloudException("CF root request to " + controllerUrl + " returned " + response.statusCode()); + throw new CloudException( + MessageFormat.format(Messages.CF_ROOT_DOCUMENT_REQUEST_TO_0_RETURNED_1, controllerUrl, response.statusCode())); } + return (Map) JsonUtil.convertJsonToMap(response.body()) - .get("links"); + .get(Constants.ROOT_DOCUMENT_LINKS_LIST); } catch (InterruptedException e) { Thread.currentThread() .interrupt(); - throw new CloudException("Interrupted while calling the CF root at " + controllerUrl, e); + throw new CloudException(MessageFormat.format(Messages.INTERRUPTED_WHILE_CALLING_THE_CF_ROOT_URL_AT_0, controllerUrl), e); } catch (Exception e) { - throw new CloudException("Failed to call the CF root at " + controllerUrl + ": " + e.getMessage(), e); + throw new CloudException(MessageFormat.format(Messages.FAILED_TO_CALL_THE_CF_ROOT_AT_0_WITH_1, controllerUrl, e.getMessage()), + e); } } - private static java.net.http.HttpClient rootHttpClient() { + private static java.net.http.HttpClient getSimpleHttpClient() { return java.net.http.HttpClient.newBuilder() - .connectTimeout(Duration.ofMinutes(1)) + .connectTimeout(Constants.DEFAULT_CONNECT_TIMEOUT) .build(); } @@ -204,17 +202,13 @@ private URL fallbackUrl(URL controllerUrl, String pathSuffix) { return URI.create(controllerUrl.toString() + pathSuffix) .toURL(); } catch (MalformedURLException e) { - throw new IllegalArgumentException("Could not derive URL from " + controllerUrl + pathSuffix, e); + throw new IllegalArgumentException( + MessageFormat.format(Messages.COULD_NOT_RESOLVE_URL_FROM_0_1, controllerUrl, pathSuffix), e); } } - private OAuthClient createOAuthClient(URL controllerUrl, String origin) { + private OAuthClient createOAuthClient(URL controllerUrl) { return restUtil.createOAuthClientByControllerUrl(controllerUrl, shouldTrustSelfSignedCertificates()); } - // The cached, host-scoped transport: one shared reactor-netty HttpClient (pool + TLS + proxy) plus the CF root-link discovery results - // (v3 API + log-cache base URLs), resolved once per host. - private record HostTransport(HttpClient httpClient, URL v3ApiUrl, URL logCacheUrl) { - } - } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientV3Impl.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientV3Impl.java index 57a8b147e0..031d2c4960 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientV3Impl.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientV3Impl.java @@ -1,15 +1,16 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest; -import java.net.URL; import java.nio.file.Path; +import java.text.MessageFormat; import java.time.Duration; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.UUID; +import java.util.function.Function; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; +import org.cloudfoundry.multiapps.controller.Messages; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.UploadStatusCallback; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; @@ -31,103 +32,55 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.DockerInfo; import org.cloudfoundry.multiapps.controller.client.facade.domain.DropletInfo; import org.cloudfoundry.multiapps.controller.client.facade.domain.InstancesInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ServicePlanVisibility; import org.cloudfoundry.multiapps.controller.client.facade.domain.Staging; import org.cloudfoundry.multiapps.controller.client.facade.domain.Upload; import org.cloudfoundry.multiapps.controller.client.facade.domain.UserRole; import org.cloudfoundry.multiapps.controller.client.facade.dto.ApplicationToCreateDto; -import org.cloudfoundry.multiapps.controller.client.facade.oauth2.OAuthClient; -import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Application; -import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ApplicationMapper; -import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; -import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpStatus; import org.springframework.web.client.RestClient; -/** - * A {@link CloudControllerRestClient} implementation that talks to the Cloud Controller v3 REST API directly through a blocking Spring - * {@link RestClient}, with no dependency on the OSS cf-java-client. - *

- * This is the target implementation of the "migrate off cf-java-client" PoC. It is selected in - * {@link CloudControllerRestClientFactory} via a feature flag; when the flag is off, the OSS-backed - * {@link CloudControllerRestClientImpl} is used instead, so the two can be A/B compared against the same live Cloud Foundry. - *

- * SCAFFOLD STATE: every operation currently throws {@link UnsupportedOperationException}. Operations are being filled in - * resource-group by resource-group per docs/cf-java-client-migration/02-endpoint-inventory.md. The shared machinery (pagination walker, - * async job poller, JSON model) lands first and is reused by every operation. - */ public class CloudControllerRestClientV3Impl implements CloudControllerRestClient { - // CF v3 caps per_page at 5000; use a large page to minimise round-trips (the pagination walker still handles multiple pages). - private static final int DEFAULT_PAGE_SIZE = 5000; - // Matches the OSS impl's DELETE_JOB_TIMEOUT for async resource deletions. - private static final Duration DELETE_JOB_TIMEOUT = Duration.ofMinutes(5); - - private final URL v3ApiUrl; - private final OAuthClient oAuthClient; private final CloudSpace target; - private final RestClient restClient; - private final CloudControllerV3Client cc; - private final BuildsV3Operations buildsOps; - private final DomainsV3Operations domainsOps; - private final EventsV3Operations eventsOps; - private final JobsV3Operations jobsOps; - private final PackagesV3Operations packagesOps; - private final ProcessesV3Operations processesOps; - private final RolesV3Operations rolesOps; - private final RoutesV3Operations routesOps; - private final ServiceBindingsV3Operations serviceBindingsOps; - private final ServiceBrokersV3Operations serviceBrokersOps; - private final ServiceInstancesV3Operations serviceInstancesOps; - private final ServiceKeysV3Operations serviceKeysOps; - private final ServiceOfferingsV3Operations serviceOfferingsOps; - private final ServicePlansV3Operations servicePlansOps; - private final StacksV3Operations stacksOps; - private final TasksV3Operations tasksOps; - - public CloudControllerRestClientV3Impl(URL v3ApiUrl, OAuthClient oAuthClient, CloudSpace target, RestClient restClient) { - this(v3ApiUrl, oAuthClient, target, restClient, null); - } - - public CloudControllerRestClientV3Impl(URL v3ApiUrl, OAuthClient oAuthClient, CloudSpace target, RestClient restClient, - java.util.function.Function uploadRestClientFactory) { - this(v3ApiUrl, oAuthClient, target, restClient, new CloudControllerV3Client(restClient), uploadRestClientFactory); - } - - public CloudControllerRestClientV3Impl(URL v3ApiUrl, OAuthClient oAuthClient, CloudSpace target, RestClient restClient, - CloudControllerV3Client cc, - java.util.function.Function uploadRestClientFactory) { - this.v3ApiUrl = v3ApiUrl; - this.oAuthClient = oAuthClient; + + private final ApplicationsV3Operations applicationsOperations; + private final BuildsV3Operations buildsOperations; + private final DomainsV3Operations domainsOperations; + private final EventsV3Operations eventsOperations; + private final JobsV3Operations jobsOperations; + private final PackagesV3Operations packagesOperations; + private final ProcessesV3Operations processesOperations; + private final RolesV3Operations rolesOperations; + private final RoutesV3Operations routesOperations; + private final ServiceBindingsV3Operations serviceBindingsOperations; + private final ServiceBrokersV3Operations serviceBrokersOperations; + private final ServiceInstancesV3Operations serviceInstancesOperations; + private final ServiceKeysV3Operations serviceKeysOperations; + private final ServiceOfferingsV3Operations serviceOfferingsOperations; + private final StacksV3Operations stacksOperations; + private final TasksV3Operations tasksOperations; + + public CloudControllerRestClientV3Impl(CloudSpace target, CloudControllerV3Client cloudControllerClient, + Function uploadRestClientFactory) { this.target = target; - this.restClient = restClient; - this.cc = cc; - this.buildsOps = new BuildsV3Operations(cc, target); - this.domainsOps = new DomainsV3Operations(cc, target); - this.eventsOps = new EventsV3Operations(cc, target); - this.jobsOps = new JobsV3Operations(cc, target); - this.packagesOps = new PackagesV3Operations(cc, target, uploadRestClientFactory); - this.processesOps = new ProcessesV3Operations(cc, target); - this.rolesOps = new RolesV3Operations(cc, target); - this.routesOps = new RoutesV3Operations(cc, target); - this.serviceBindingsOps = new ServiceBindingsV3Operations(cc, target); - this.serviceBrokersOps = new ServiceBrokersV3Operations(cc, target); - this.serviceInstancesOps = new ServiceInstancesV3Operations(cc, target); - this.serviceKeysOps = new ServiceKeysV3Operations(cc, target); - this.serviceOfferingsOps = new ServiceOfferingsV3Operations(cc, target); - this.servicePlansOps = new ServicePlansV3Operations(cc, target); - this.stacksOps = new StacksV3Operations(cc, target); - this.tasksOps = new TasksV3Operations(cc, target); - } - - private static UnsupportedOperationException notImplemented() { - StackWalker.StackFrame caller = StackWalker.getInstance() - .walk(frames -> frames.skip(1) - .findFirst() - .orElse(null)); - String operation = caller == null ? "operation" : caller.getMethodName(); - return new UnsupportedOperationException( - "CloudControllerRestClientV3Impl." + operation + " is not implemented yet (cf-java-client migration PoC scaffold)."); + this.applicationsOperations = new ApplicationsV3Operations(cloudControllerClient, target); + this.buildsOperations = new BuildsV3Operations(cloudControllerClient); + this.domainsOperations = new DomainsV3Operations(cloudControllerClient, target); + this.eventsOperations = new EventsV3Operations(cloudControllerClient, target); + this.jobsOperations = new JobsV3Operations(cloudControllerClient); + this.packagesOperations = new PackagesV3Operations(cloudControllerClient, target, uploadRestClientFactory); + this.processesOperations = new ProcessesV3Operations(cloudControllerClient, target); + this.rolesOperations = new RolesV3Operations(cloudControllerClient); + this.routesOperations = new RoutesV3Operations(cloudControllerClient, target); + this.serviceBindingsOperations = new ServiceBindingsV3Operations(cloudControllerClient, target); + this.serviceBrokersOperations = new ServiceBrokersV3Operations(cloudControllerClient, target); + this.serviceInstancesOperations = new ServiceInstancesV3Operations(cloudControllerClient, target); + this.serviceKeysOperations = new ServiceKeysV3Operations(cloudControllerClient); + this.serviceOfferingsOperations = new ServiceOfferingsV3Operations(cloudControllerClient, target); + this.stacksOperations = new StacksV3Operations(cloudControllerClient); + this.tasksOperations = new TasksV3Operations(cloudControllerClient, target); } @Override @@ -137,454 +90,349 @@ public CloudSpace getTarget() { @Override public void addDomain(String domainName) { - domainsOps.addDomain(domainName); + domainsOperations.addDomain(domainName); } @Override public void addRoute(String host, String domainName, String path) { - routesOps.addRoute(host, domainName, path); + routesOperations.addRoute(host, domainName, path); } @Override public Optional bindServiceInstance(String bindingName, String applicationName, String serviceInstanceName) { - return serviceBindingsOps.bindServiceInstance(bindingName, applicationName, serviceInstanceName); + return serviceBindingsOperations.bindServiceInstance(bindingName, applicationName, serviceInstanceName); } @Override public Optional bindServiceInstance(String bindingName, String applicationName, String serviceInstanceName, Map parameters) { - return serviceBindingsOps.bindServiceInstance(bindingName, applicationName, serviceInstanceName, parameters); + return serviceBindingsOperations.bindServiceInstance(bindingName, applicationName, serviceInstanceName, parameters); } @Override public void createApplication(ApplicationToCreateDto dto) { - if (target == null || target.getGuid() == null) { - throw new CloudOperationException(HttpStatus.BAD_REQUEST, "Bad Request", "A target space is required to create an application."); - } - Map body = new java.util.HashMap<>(); - body.put("name", dto.getName()); - body.put("lifecycle", buildLifecycle(dto.getStaging())); - body.put("relationships", Map.of("space", Map.of("data", Map.of("guid", target.getGuid() - .toString())))); - if (dto.getEnv() != null) { - body.put("environment_variables", dto.getEnv()); - } - if (dto.getMetadata() != null) { - body.put("metadata", Map.of("labels", dto.getMetadata() - .getLabels(), - "annotations", dto.getMetadata() - .getAnnotations())); - } - V3Application created = cc.getRestClient() - .post() - .uri("/v3/apps") - .body(body) - .retrieve() - .body(V3Application.class); - UUID appGuid = UUID.fromString(created.guid()); - // Follow-up attributes, mirroring the OSS updateApplicationAttributes: staging/process, memory+disk scale, then routes. + applicationsOperations.createApplication(dto); + if (dto.getStaging() != null) { - processesOps.updateApplicationStaging(dto.getName(), dto.getStaging()); - } - Map scale = new java.util.HashMap<>(); - if (dto.getMemoryInMb() != null) { - scale.put("memory_in_mb", dto.getMemoryInMb()); - } - if (dto.getDiskQuotaInMb() != null) { - scale.put("disk_in_mb", dto.getDiskQuotaInMb()); - } - if (!scale.isEmpty()) { - scaleWebProcess(appGuid, scale); + updateApplicationStaging(dto.getName(), dto.getStaging()); } + if (dto.getRoutes() != null && !dto.getRoutes() .isEmpty()) { updateApplicationRoutes(dto.getName(), dto.getRoutes()); } } - // Builds the CF v3 lifecycle object: docker when DockerInfo is present, otherwise buildpack/cnb with buildpacks + stack. - private Map buildLifecycle(Staging staging) { - if (staging == null) { - return Map.of("type", "buildpack", "data", Map.of()); - } - if (staging.getDockerInfo() != null) { - return Map.of("type", "docker", "data", Map.of()); - } - String type = staging.getLifecycleType() != null ? staging.getLifecycleType() - .name() - .toLowerCase() - : "buildpack"; - Map data = new java.util.HashMap<>(); - if (staging.getBuildpacks() != null) { - data.put("buildpacks", staging.getBuildpacks()); - } - if (staging.getStackName() != null) { - data.put("stack", staging.getStackName()); - } - return Map.of("type", type, "data", data); - } - @Override public void createServiceInstance(CloudServiceInstance serviceInstance) { - serviceInstancesOps.createServiceInstance(serviceInstance); + serviceInstancesOperations.createServiceInstance(serviceInstance); } @Override public String createServiceBroker(CloudServiceBroker serviceBroker) { - return serviceBrokersOps.createServiceBroker(serviceBroker); + return serviceBrokersOperations.createServiceBroker(serviceBroker); } @Override public CloudServiceKey createAndFetchServiceKey(CloudServiceKey keyModel, String serviceInstanceName) { - return serviceKeysOps.createAndFetchServiceKey(keyModel, getServiceInstance(serviceInstanceName)); + return serviceKeysOperations.createAndFetchServiceKey(keyModel, getServiceInstance(serviceInstanceName)); } @Override public Optional createServiceKey(CloudServiceKey keyModel, String serviceInstanceName) { - return serviceKeysOps.createServiceKey(keyModel, getServiceInstance(serviceInstanceName)); + return serviceKeysOperations.createServiceKey(keyModel, getServiceInstance(serviceInstanceName)); } @Override public Optional createServiceKey(String serviceInstanceName, String serviceKeyName, Map parameters) { - return serviceKeysOps.createServiceKey(getServiceInstance(serviceInstanceName), serviceKeyName, parameters); + return serviceKeysOperations.createServiceKey(getServiceInstance(serviceInstanceName), serviceKeyName, parameters); } @Override public void createUserProvidedServiceInstance(CloudServiceInstance serviceInstance) { - serviceInstancesOps.createUserProvidedServiceInstance(serviceInstance); + serviceInstancesOperations.createUserProvidedServiceInstance(serviceInstance); } @Override public void deleteApplication(String applicationName) { - UUID applicationGuid = getApplicationGuid(applicationName); - var response = cc.getRestClient() - .delete() - .uri("/v3/apps/{guid}", applicationGuid) - .retrieve() - .toBodilessEntity(); - cc.followAsyncJob(response, DELETE_JOB_TIMEOUT); + applicationsOperations.deleteApplication(applicationName); } @Override public void deleteDomain(String domainName) { - domainsOps.deleteDomain(domainName); + domainsOperations.deleteDomain(domainName); } @Override public void deleteOrphanedRoutes() { - routesOps.deleteOrphanedRoutes(); + routesOperations.deleteOrphanedRoutes(); } @Override public void deleteRoute(String host, String domainName, String path) { - routesOps.deleteRoute(host, domainName, path); + routesOperations.deleteRoute(host, domainName, path); } @Override public void deleteServiceInstance(String serviceInstanceName) { - serviceInstancesOps.deleteServiceInstance(serviceInstanceName); + serviceInstancesOperations.deleteServiceInstance(serviceInstanceName); } @Override public void deleteServiceInstance(CloudServiceInstance serviceInstance) { - serviceInstancesOps.deleteServiceInstance(serviceInstance); + serviceInstancesOperations.deleteServiceInstance(serviceInstance); } @Override public String deleteServiceBroker(String name) { - return serviceBrokersOps.deleteServiceBroker(name); + return serviceBrokersOperations.deleteServiceBroker(name); } @Override public Optional deleteServiceBinding(String serviceInstanceName, String serviceKeyName) { - // Mirrors the OSS impl: resolve the named service key, then delete its binding by GUID. CloudServiceKey serviceKey = getServiceKey(serviceInstanceName, serviceKeyName); + if (serviceKey == null) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Service key " + serviceKeyName + " not found."); + throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + MessageFormat.format(Messages.SERVICE_KEY_0_NOT_FOUND, serviceKeyName)); } + return deleteServiceBinding(serviceKey.getGuid()); } @Override public Optional deleteServiceBinding(UUID bindingGuid) { - return serviceBindingsOps.deleteServiceBinding(bindingGuid); + return serviceBindingsOperations.deleteServiceBinding(bindingGuid); } @Override public CloudApplication getApplication(String applicationName) { - return getApplication(applicationName, true); + return applicationsOperations.getApplication(applicationName); } @Override public CloudApplication getApplication(String applicationName, boolean required) { - V3Application app = findApplicationByName(applicationName); - if (app == null) { - if (required) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Application " + applicationName + " not found."); - } - return null; - } - return V3ApplicationMapper.toCloudApplication(app, target); + return applicationsOperations.getApplication(applicationName, required); } @Override public UUID getApplicationGuid(String applicationName) { - return getApplication(applicationName).getGuid(); + return applicationsOperations.getApplicationGuid(applicationName); } @Override public String getApplicationName(UUID applicationGuid) { - V3Application app = cc.get("/v3/apps/" + applicationGuid, V3Application.class); - return app == null ? null : app.name(); + return applicationsOperations.getApplicationName(applicationGuid); } @Override public Map getApplicationEnvironment(UUID applicationGuid) { - V3Application.V3EnvironmentVariables env = cc.get("/v3/apps/" + applicationGuid + "/environment_variables", - V3Application.V3EnvironmentVariables.class); - return env == null || env.var() == null ? Map.of() : env.var(); + return applicationsOperations.getApplicationEnvironment(applicationGuid); } @Override public Map getApplicationEnvironment(String applicationName) { - return getApplicationEnvironment(getApplicationGuid(applicationName)); + return applicationsOperations.getApplicationEnvironment(applicationName); } @Override public List getApplications() { - return listApplications(applicationsQuery(null)).stream() - .map(app -> V3ApplicationMapper.toCloudApplication(app, target)) - .toList(); + return applicationsOperations.getApplications(); } @Override public List getApplicationsByMetadataLabelSelector(String labelSelector) { - String query = applicationsQuery(null); - if (labelSelector != null) { - query = query + "&label_selector=" + labelSelector; - } - return listApplications(query).stream() - .map(app -> V3ApplicationMapper.toCloudApplication(app, target)) - .toList(); - } - - private V3Application findApplicationByName(String applicationName) { - List apps = listApplications(applicationsQuery(applicationName)); - return apps.isEmpty() ? null : apps.get(0); - } - - private List listApplications(String query) { - return cc.list(query, new ParameterizedTypeReference>() { - }); - } - - // Applications in the target space, optionally filtered by exact name. - private String applicationsQuery(String name) { - StringBuilder query = new StringBuilder("/v3/apps?per_page=" + DEFAULT_PAGE_SIZE); - if (target != null && target.getGuid() != null) { - query.append("&space_guids=") - .append(target.getGuid()); - } - if (name != null) { - query.append("&names=") - .append(name); - } - return query.toString(); + return applicationsOperations.getApplicationsByMetadataLabelSelector(labelSelector); } @Override public List getApplicationEvents(String applicationName) { - return eventsOps.getApplicationEvents(applicationName); + return eventsOperations.getApplicationEvents(applicationName); } @Override public List getEventsByTarget(UUID uuid) { - return eventsOps.getEventsByTarget(uuid); + return eventsOperations.getEventsByTarget(uuid); } @Override public InstancesInfo getApplicationInstances(CloudApplication app) { - return processesOps.getApplicationInstances(app); + return processesOperations.getApplicationInstances(app); } @Override public InstancesInfo getApplicationInstances(UUID applicationGuid) { - return processesOps.getApplicationInstances(applicationGuid); + return processesOperations.getApplicationInstances(applicationGuid); } @Override public CloudProcess getApplicationProcess(UUID applicationGuid) { - return processesOps.getApplicationProcess(applicationGuid); + return processesOperations.getApplicationProcess(applicationGuid); } @Override public List getApplicationRoutes(UUID applicationGuid) { - return routesOps.getApplicationRoutes(applicationGuid); + return routesOperations.getApplicationRoutes(applicationGuid); } @Override public boolean getApplicationSshEnabled(UUID applicationGuid) { - return processesOps.getApplicationSshEnabled(applicationGuid); + return processesOperations.getApplicationSshEnabled(applicationGuid); } @Override public Map getApplicationFeatures(UUID applicationGuid) { - return processesOps.getApplicationFeatures(applicationGuid); + return processesOperations.getApplicationFeatures(applicationGuid); } @Override public CloudDomain getDefaultDomain() { - return domainsOps.getDefaultDomain(); + return domainsOperations.getDefaultDomain(); } @Override public List getDomains() { - return domainsOps.getDomains(); + return domainsOperations.getDomains(); } @Override public List getDomainsForOrganization() { - return domainsOps.getDomainsForOrganization(); + return domainsOperations.getDomainsForOrganization(); } @Override public List getEvents() { - return eventsOps.getEvents(); + return eventsOperations.getEvents(); } @Override public List getPrivateDomains() { - return domainsOps.getPrivateDomains(); + return domainsOperations.getPrivateDomains(); } @Override public List getRoutes(String domainName) { - return routesOps.getRoutes(domainName); + return routesOperations.getRoutes(domainName); } @Override public UUID getRequiredServiceInstanceGuid(String name) { - return serviceInstancesOps.getRequiredServiceInstanceGuid(name); + return serviceInstancesOperations.getRequiredServiceInstanceGuid(name); } @Override public CloudServiceInstance getServiceInstance(String serviceInstanceName) { - return serviceInstancesOps.getServiceInstance(serviceInstanceName); + return serviceInstancesOperations.getServiceInstance(serviceInstanceName); } @Override public CloudServiceInstance getServiceInstance(String serviceInstanceName, boolean required) { - return serviceInstancesOps.getServiceInstance(serviceInstanceName, required); + return serviceInstancesOperations.getServiceInstance(serviceInstanceName, required); } @Override public String getServiceInstanceName(UUID serviceInstanceGuid) { - return serviceInstancesOps.getServiceInstanceName(serviceInstanceGuid); + return serviceInstancesOperations.getServiceInstanceName(serviceInstanceGuid); } @Override public CloudServiceInstance getServiceInstanceWithoutAuxiliaryContent(String serviceInstanceName) { - return serviceInstancesOps.getServiceInstanceWithoutAuxiliaryContent(serviceInstanceName); + return serviceInstancesOperations.getServiceInstanceWithoutAuxiliaryContent(serviceInstanceName); } @Override public CloudServiceInstance getServiceInstanceWithoutAuxiliaryContent(String serviceInstanceName, boolean required) { - return serviceInstancesOps.getServiceInstanceWithoutAuxiliaryContent(serviceInstanceName, required); + return serviceInstancesOperations.getServiceInstanceWithoutAuxiliaryContent(serviceInstanceName, required); } @Override public CloudServiceBinding getServiceBinding(UUID serviceBindingGuid) { - return serviceBindingsOps.getServiceBinding(serviceBindingGuid); + return serviceBindingsOperations.getServiceBinding(serviceBindingGuid); } @Override public List getServiceAppBindings(UUID serviceInstanceGuid) { - return serviceBindingsOps.getServiceAppBindings(serviceInstanceGuid); + return serviceBindingsOperations.getServiceAppBindings(serviceInstanceGuid); } @Override public List getAppBindings(UUID applicationGuid) { - return serviceBindingsOps.getAppBindings(applicationGuid); + return serviceBindingsOperations.getAppBindings(applicationGuid); } @Override public List getServiceBindingsForApplication(UUID applicationId, UUID serviceInstanceGuid) { - return serviceBindingsOps.getServiceBindingsForApplication(applicationId, serviceInstanceGuid); + return serviceBindingsOperations.getServiceBindingsForApplication(applicationId, serviceInstanceGuid); } @Override public CloudServiceBroker getServiceBroker(String name) { - return serviceBrokersOps.getServiceBroker(name); + return serviceBrokersOperations.getServiceBroker(name); } @Override public CloudServiceBroker getServiceBroker(String name, boolean required) { - return serviceBrokersOps.getServiceBroker(name, required); + return serviceBrokersOperations.getServiceBroker(name, required); } @Override public List getServiceBrokers() { - return serviceBrokersOps.getServiceBrokers(); + return serviceBrokersOperations.getServiceBrokers(); } @Override public CloudServiceKey getServiceKey(String serviceInstanceName, String serviceKeyName) { - return serviceKeysOps.getServiceKey(getServiceInstance(serviceInstanceName), serviceKeyName); + return serviceKeysOperations.getServiceKey(getServiceInstance(serviceInstanceName), serviceKeyName); } @Override public List getServiceKeys(String serviceInstanceName) { - return serviceKeysOps.getServiceKeys(getServiceInstance(serviceInstanceName)); + return serviceKeysOperations.getServiceKeys(getServiceInstance(serviceInstanceName)); } @Override public List getServiceKeysWithCredentials(String serviceInstanceName) { - return serviceKeysOps.getServiceKeysWithCredentials(getServiceInstance(serviceInstanceName)); + return serviceKeysOperations.getServiceKeysWithCredentials(getServiceInstance(serviceInstanceName)); } @Override public List getServiceKeys(CloudServiceInstance serviceInstance) { - return serviceKeysOps.getServiceKeys(serviceInstance); + return serviceKeysOperations.getServiceKeys(serviceInstance); } @Override public List getServiceKeysWithCredentials(CloudServiceInstance serviceInstance) { - return serviceKeysOps.getServiceKeysWithCredentials(serviceInstance); + return serviceKeysOperations.getServiceKeysWithCredentials(serviceInstance); } @Override public List getServiceOfferings() { - return serviceOfferingsOps.getServiceOfferings(); + return serviceOfferingsOperations.getServiceOfferings(); } @Override public List getSharedDomains() { - return domainsOps.getSharedDomains(); + return domainsOperations.getSharedDomains(); } @Override public CloudStack getStack(String name) { - return stacksOps.getStack(name); + return stacksOperations.getStack(name); } @Override public CloudStack getStack(String name, boolean required) { - return stacksOps.getStack(name, required); + return stacksOperations.getStack(name, required); } @Override public List getStacks() { - return stacksOps.getStacks(); + return stacksOperations.getStacks(); } @Override public void startApplication(String applicationName) { - UUID guid = getApplicationGuid(applicationName); - cc.getRestClient() - .post() - .uri("/v3/apps/{guid}/actions/start", guid) - .retrieve() - .toBodilessEntity(); + applicationsOperations.startApplication(applicationName); } @Override @@ -595,257 +443,212 @@ public void restartApplication(String applicationName) { @Override public void stopApplication(String applicationName) { - UUID guid = getApplicationGuid(applicationName); - cc.getRestClient() - .post() - .uri("/v3/apps/{guid}/actions/stop", guid) - .retrieve() - .toBodilessEntity(); + applicationsOperations.stopApplication(applicationName); } @Override public void rename(String applicationName, String newName) { - UUID guid = getApplicationGuid(applicationName); - try { - cc.getRestClient() - .patch() - .uri("/v3/apps/{guid}", guid) - .body(Map.of("name", newName)) - .retrieve() - .toBodilessEntity(); - } catch (CloudOperationException e) { - // Idempotent-rename tolerance (mirrors the OSS fallbackApplicationRename): the CC can answer 503 to a rename that in fact - // already took effect. If the app is already named newName, treat the rename as successful; otherwise propagate. - if (e.getStatusCode() == HttpStatus.SERVICE_UNAVAILABLE && newName.equals(getApplicationName(guid))) { - return; - } - throw e; - } + applicationsOperations.rename(applicationName, newName); } @Override public void updateApplicationInstances(String applicationName, int instances) { - scaleWebProcess(getApplicationGuid(applicationName), Map.of("instances", instances)); + applicationsOperations.updateApplicationInstances(applicationName, instances); } @Override public void updateApplicationMemory(String applicationName, int memory) { - scaleWebProcess(getApplicationGuid(applicationName), Map.of("memory_in_mb", memory)); + applicationsOperations.updateApplicationMemory(applicationName, memory); } @Override public void updateApplicationDiskQuota(String applicationName, int disk) { - scaleWebProcess(getApplicationGuid(applicationName), Map.of("disk_in_mb", disk)); + applicationsOperations.updateApplicationDiskQuota(applicationName, disk); } @Override public void updateApplicationEnv(String applicationName, Map env) { - UUID guid = getApplicationGuid(applicationName); - cc.getRestClient() - .patch() - .uri("/v3/apps/{guid}/environment_variables", guid) - .body(Map.of("var", env)) - .retrieve() - .toBodilessEntity(); + applicationsOperations.updateApplicationEnv(applicationName, env); } @Override public void bindDropletToApp(UUID dropletGuid, UUID applicationGuid) { - cc.getRestClient() - .patch() - .uri("/v3/apps/{guid}/relationships/current_droplet", applicationGuid) - .body(Map.of("data", Map.of("guid", dropletGuid.toString()))) - .retrieve() - .toBodilessEntity(); - } - - private void scaleWebProcess(UUID applicationGuid, Map scaleBody) { - cc.getRestClient() - .post() - .uri("/v3/apps/{guid}/processes/web/actions/scale", applicationGuid) - .body(scaleBody) - .retrieve() - .toBodilessEntity(); + applicationsOperations.bindDropletToApp(dropletGuid, applicationGuid); } @Override public List unbindServiceInstance(String applicationName, String serviceInstanceName) { - return serviceBindingsOps.unbindServiceInstance(applicationName, serviceInstanceName); + return serviceBindingsOperations.unbindServiceInstance(applicationName, serviceInstanceName); } @Override public List unbindServiceInstance(UUID applicationGuid, UUID serviceInstanceGuid) { - return serviceBindingsOps.unbindServiceInstance(applicationGuid, serviceInstanceGuid); + return serviceBindingsOperations.unbindServiceInstance(applicationGuid, serviceInstanceGuid); } @Override public void updateApplicationStaging(String applicationName, Staging staging) { - processesOps.updateApplicationStaging(applicationName, staging); + processesOperations.updateApplicationStaging(applicationName, staging); } @Override public void updateApplicationRoutes(String applicationName, Set routes) { - routesOps.updateApplicationRoutes(applicationName, routes); + routesOperations.updateApplicationRoutes(applicationName, routes); } @Override public String updateServiceBroker(CloudServiceBroker serviceBroker) { - return serviceBrokersOps.updateServiceBroker(serviceBroker); + return serviceBrokersOperations.updateServiceBroker(serviceBroker); } @Override public void updateServicePlanVisibilityForBroker(String name, ServicePlanVisibility visibility) { - serviceBrokersOps.updateServicePlanVisibilityForBroker(name, visibility); + serviceBrokersOperations.updateServicePlanVisibilityForBroker(name, visibility); } @Override public void updateServicePlan(String serviceName, String planName) { - serviceInstancesOps.updateServicePlan(serviceName, planName); + serviceInstancesOperations.updateServicePlan(serviceName, planName); } @Override public void updateServiceParameters(String serviceName, Map parameters) { - serviceInstancesOps.updateServiceParameters(serviceName, parameters); + serviceInstancesOperations.updateServiceParameters(serviceName, parameters); } @Override public void updateServiceSyslogDrainUrl(String serviceName, String syslogDrainUrl) { - serviceInstancesOps.updateServiceSyslogDrainUrl(serviceName, syslogDrainUrl); + serviceInstancesOperations.updateServiceSyslogDrainUrl(serviceName, syslogDrainUrl); } @Override public void updateServiceTags(String serviceName, List tags) { - serviceInstancesOps.updateServiceTags(serviceName, tags); + serviceInstancesOperations.updateServiceTags(serviceName, tags); } @Override public CloudPackage asyncUploadApplication(String applicationName, Path file, UploadStatusCallback callback, Duration uploadTimeout) { - return packagesOps.asyncUploadApplication(applicationName, file, callback, uploadTimeout); + return packagesOperations.asyncUploadApplication(applicationName, file, callback, uploadTimeout); } @Override public Upload getUploadStatus(UUID packageGuid) { - return packagesOps.getUploadStatus(packageGuid); + return packagesOperations.getUploadStatus(packageGuid); } @Override public CloudTask getTask(UUID taskGuid) { - return tasksOps.getTask(taskGuid); + return tasksOperations.getTask(taskGuid); } @Override public List getTasks(String applicationName) { - return tasksOps.getTasks(applicationName); + return tasksOperations.getTasks(applicationName); } @Override public CloudTask runTask(String applicationName, CloudTask task) { - return tasksOps.runTask(applicationName, task); + return tasksOperations.runTask(applicationName, task); } @Override public CloudTask cancelTask(UUID taskGuid) { - return tasksOps.cancelTask(taskGuid); + return tasksOperations.cancelTask(taskGuid); } @Override public CloudBuild createBuild(UUID packageGuid) { - return buildsOps.createBuild(packageGuid); + return buildsOperations.createBuild(packageGuid); } @Override public CloudBuild getBuild(UUID packageGuid) { - return buildsOps.getBuild(packageGuid); + return buildsOperations.getBuild(packageGuid); } @Override public List getBuildsForApplication(UUID applicationGuid) { - return buildsOps.getBuildsForApplication(applicationGuid); + return buildsOperations.getBuildsForApplication(applicationGuid); } @Override public Map getServiceInstanceParameters(UUID guid) { - return serviceInstancesOps.getServiceInstanceParameters(guid); + return serviceInstancesOperations.getServiceInstanceParameters(guid); } @Override public Map getUserProvidedServiceInstanceParameters(UUID guid) { - return serviceInstancesOps.getUserProvidedServiceInstanceParameters(guid); + return serviceInstancesOperations.getUserProvidedServiceInstanceParameters(guid); } @Override public Map getServiceBindingParameters(UUID guid) { - return serviceBindingsOps.getServiceBindingParameters(guid); + return serviceBindingsOperations.getServiceBindingParameters(guid); } @Override public List getBuildsForPackage(UUID packageGuid) { - return buildsOps.getBuildsForPackage(packageGuid); + return buildsOperations.getBuildsForPackage(packageGuid); } @Override public List getServiceInstancesWithoutAuxiliaryContentByNames(List names) { - return serviceInstancesOps.getServiceInstancesWithoutAuxiliaryContentByNames(names); + return serviceInstancesOperations.getServiceInstancesWithoutAuxiliaryContentByNames(names); } @Override public List getServiceInstancesByMetadataLabelSelector(String labelSelector) { - return serviceInstancesOps.getServiceInstancesByMetadataLabelSelector(labelSelector); + return serviceInstancesOperations.getServiceInstancesByMetadataLabelSelector(labelSelector); } @Override public List getServiceInstancesWithoutAuxiliaryContentByMetadataLabelSelector(String labelSelector) { - return serviceInstancesOps.getServiceInstancesWithoutAuxiliaryContentByMetadataLabelSelector(labelSelector); + return serviceInstancesOperations.getServiceInstancesWithoutAuxiliaryContentByMetadataLabelSelector(labelSelector); } @Override public void updateApplicationMetadata(UUID guid, Metadata metadata) { - cc.getRestClient() - .patch() - .uri("/v3/apps/{guid}", guid) - .body(Map.of("metadata", Map.of("labels", metadata.getLabels(), "annotations", metadata.getAnnotations()))) - .retrieve() - .toBodilessEntity(); + applicationsOperations.updateApplicationMetadata(guid, metadata); } @Override public void updateServiceInstanceMetadata(UUID guid, Metadata metadata) { - serviceInstancesOps.updateServiceInstanceMetadata(guid, metadata); + serviceInstancesOperations.updateServiceInstanceMetadata(guid, metadata); } @Override public void updateServiceBindingMetadata(UUID guid, Metadata metadata) { - serviceBindingsOps.updateServiceBindingMetadata(guid, metadata); + serviceBindingsOperations.updateServiceBindingMetadata(guid, metadata); } @Override public DropletInfo getCurrentDropletForApplication(UUID applicationGuid) { - return processesOps.getCurrentDropletForApplication(applicationGuid); + return processesOperations.getCurrentDropletForApplication(applicationGuid); } @Override public CloudPackage getPackage(UUID packageGuid) { - return packagesOps.getPackage(packageGuid); + return packagesOperations.getPackage(packageGuid); } @Override public List getPackagesForApplication(UUID applicationGuid) { - return packagesOps.getPackagesForApplication(applicationGuid); + return packagesOperations.getPackagesForApplication(applicationGuid); } @Override public Set getUserRolesBySpaceAndUser(UUID spaceGuid, UUID userGuid) { - return rolesOps.getUserRolesBySpaceAndUser(spaceGuid, userGuid); + return rolesOperations.getUserRolesBySpaceAndUser(spaceGuid, userGuid); } @Override public CloudPackage createDockerPackage(UUID applicationGuid, DockerInfo dockerInfo) { - return packagesOps.createDockerPackage(applicationGuid, dockerInfo); + return packagesOperations.createDockerPackage(applicationGuid, dockerInfo); } @Override public CloudAsyncJob getAsyncJob(String jobId) { - return jobsOps.getAsyncJob(jobId); + return jobsOperations.getAsyncJob(jobId); } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Client.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Client.java index 3c8b53b46f..7ad492bafa 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Client.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Client.java @@ -1,54 +1,31 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest; import java.net.URI; +import java.text.MessageFormat; import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.Optional; +import org.cloudfoundry.multiapps.controller.Constants; +import org.cloudfoundry.multiapps.controller.Messages; import org.cloudfoundry.multiapps.controller.client.facade.CloudException; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Job; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; import org.springframework.core.ParameterizedTypeReference; -import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.client.RestClient; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClientResponseException; import org.springframework.web.util.UriComponentsBuilder; - import reactor.core.publisher.Flux; -/** - * Low-level Cloud Controller v3 access shared by every operation of {@link CloudControllerRestClientV3Impl}. This is the reusable - * machinery of the cf-java-client replacement: - *

    - *
  • typed {@code GET} / list helpers over the configured {@link RestClient} (error mapping is already attached to the RestClient via - * {@link CloudControllerResponseErrorHandler});
  • - *
  • a {@link #list(String, ParameterizedTypeReference) pagination walker} that follows {@code pagination.next.href} across pages;
  • - *
  • an {@link #waitForAsyncJob(String, Duration) async job poller} reproducing the OSS {@code JobV3Util} contract (exponential - * backoff 1s→15s, capped at a timeout, until COMPLETE/FAILED).
  • - *
- * It deals in the thin {@code resources.*} wire types; mapping those to the {@code domain.Cloud*} model happens in the per-resource - * operations that call this. - */ public class CloudControllerV3Client { - - // Mirrors the OSS JobV3Util backoff so async behaviour is unchanged. - private static final Duration JOB_POLL_MIN_INTERVAL = Duration.ofSeconds(1); - private static final Duration JOB_POLL_MAX_INTERVAL = Duration.ofSeconds(15); - private static final Duration DEFAULT_JOB_TIMEOUT = Duration.ofMinutes(5); - - // Reactor's default flatMap concurrency — the in-flight cap the OSS PaginationUtils effectively used when fetching pages 2..N. - private static final int MAX_CONCURRENT_PAGES = 256; - private final RestClient restClient; - // Optional reactive client on the SAME reactor-netty transport, used only to fetch pages 2..N concurrently (as the OSS client did via - // Reactor flatMap). When null (e.g. the auxiliary space/log-cache clients that never paginate large sets), list() falls back to the - // sequential next.href walk. + private final WebClient webClient; public CloudControllerV3Client(RestClient restClient) { @@ -67,10 +44,6 @@ public T get(String uri, Class responseType) { .body(responseType); } - /** - * GET a single resource, returning empty on a 404 rather than throwing — several callers rely on "not found" being a normal, - * non-exceptional outcome (the OSS impl expressed this with {@code onErrorResume} on NOT_FOUND). - */ public Optional getOptional(String uri, Class responseType) { try { return Optional.ofNullable(get(uri, responseType)); @@ -82,56 +55,52 @@ public Optional getOptional(String uri, Class responseType) { } } - /** - * List every resource of a v3 list endpoint across all pages. Mirrors the OSS {@code PaginationUtils.requestClientV3Resources}: - * fetch page 1, read {@code pagination.total_pages}, then request pages {@code 2..N} concurrently (bounded by - * {@value #MAX_CONCURRENT_PAGES}) via the reactive {@link WebClient} on the shared transport, and concatenate the results in page - * order. This restores the intra-operation concurrency the OSS client had on large, multi-page listings. - *

- * Falls back to the sequential {@code pagination.next.href} walk when no {@link WebClient} is configured or the response does not - * report {@code total_pages} — preserving correctness on any endpoint that omits it. - */ public List list(String firstPageUri, ParameterizedTypeReference> pageType) { String firstPageRelativeUri = toRelativeIfAbsolute(firstPageUri); + V3ListResponse firstPage = restClient.get() .uri(firstPageRelativeUri) .retrieve() .body(pageType); + if (firstPage == null) { return new ArrayList<>(); } + Integer totalPages = firstPage.pagination() == null ? null : firstPage.pagination() .totalPages(); + if (webClient == null || totalPages == null || totalPages <= 1) { return listSequentially(firstPage, pageType); } - List all = new ArrayList<>(firstPage.resources()); - all.addAll(fetchRemainingPagesConcurrently(firstPageRelativeUri, totalPages, pageType)); - return all; + + List resultFromAllPages = new ArrayList<>(firstPage.resources()); + resultFromAllPages.addAll(fetchRemainingPagesConcurrently(firstPageRelativeUri, totalPages, pageType)); + return resultFromAllPages; } - // Sequential pagination via pagination.next.href — the fallback path (and the whole behaviour when no WebClient is set). private List listSequentially(V3ListResponse firstPage, ParameterizedTypeReference> pageType) { - List all = new ArrayList<>(firstPage.resources()); + List resultFromAllPages = new ArrayList<>(firstPage.resources()); String nextUri = toRelativeIfAbsolute(firstPage.nextPageHref()); + while (nextUri != null) { - V3ListResponse page = restClient.get() - .uri(nextUri) - .retrieve() - .body(pageType); - if (page == null) { + V3ListResponse currentPage = restClient.get() + .uri(nextUri) + .retrieve() + .body(pageType); + + if (currentPage == null) { break; } - all.addAll(page.resources()); - nextUri = toRelativeIfAbsolute(page.nextPageHref()); + + resultFromAllPages.addAll(currentPage.resources()); + nextUri = toRelativeIfAbsolute(currentPage.nextPageHref()); } - return all; + + return resultFromAllPages; } - // Fetch pages 2..totalPages concurrently on the reactive transport, preserving page order (flatMapSequential), and flatten their - // resources. Errors propagate unwrapped, matching the RestClient error mapping (WebClient maps non-2xx to WebClientResponseException; - // we surface it as a CloudOperationException to keep callers' handling uniform). private List fetchRemainingPagesConcurrently(String firstPageRelativeUri, int totalPages, ParameterizedTypeReference> pageType) { return Flux.range(2, totalPages - 1) @@ -140,13 +109,12 @@ private List fetchRemainingPagesConcurrently(String firstPageRelativeUri, .retrieve() .bodyToMono(pageType) .onErrorMap(WebClientResponseException.class, this::toCloudOperationException), - MAX_CONCURRENT_PAGES) + Constants.MAX_CONCURRENT_PAGES) .flatMapIterable(V3ListResponse::resources) .collectList() .block(); } - // Set/replace the "page" query parameter on the first-page URI, keeping per_page and every filter intact. Package-visible for testing. static String pageUri(String firstPageRelativeUri, int page) { return UriComponentsBuilder.fromUriString(firstPageRelativeUri) .replaceQueryParam("page", page) @@ -157,53 +125,55 @@ static String pageUri(String firstPageRelativeUri, int page) { private CloudOperationException toCloudOperationException(WebClientResponseException e) { HttpStatus status = HttpStatus.valueOf(e.getStatusCode() .value()); + return new CloudOperationException(status, status.getReasonPhrase(), e.getResponseBodyAsString(), e); } - /** - * Follow the {@code Location} header of a 202-Accepted async response to its job, then poll to completion. - */ public void followAsyncJob(ResponseEntity acceptedResponse, Duration timeout) { URI location = acceptedResponse.getHeaders() .getLocation(); + if (location == null) { - return; // Operation completed synchronously (no job to poll). + return; // when location is null, the job has already completed and we can return immediately } - waitForAsyncJob(extractJobGuid(location.toString()), timeout); - } - public void waitForAsyncJob(String jobGuid) { - waitForAsyncJob(jobGuid, DEFAULT_JOB_TIMEOUT); + waitForAsyncJob(extractJobGuid(location.toString()), timeout); } - /** - * Poll {@code GET /v3/jobs/{guid}} until the job reaches a terminal state, with exponential backoff bounded by {@code timeout}. - * Throws {@link CloudOperationException} if the job fails, or if it does not complete within the timeout — matching the OSS - * {@code JobV3Util} contract. - */ public V3Job waitForAsyncJob(String jobGuid, Duration timeout) { - long deadlineNanos = System.nanoTime() + timeout.toNanos(); - Duration interval = JOB_POLL_MIN_INTERVAL; + long startTimeNanos = System.nanoTime(); + long timeoutNanos = timeout.toNanos(); + Duration interval = Constants.JOB_POLL_MIN_INTERVAL; + while (true) { - V3Job job = get("/v3/jobs/" + jobGuid, V3Job.class); - if (job != null && job.isTerminal()) { - if (job.isFailed()) { - throw jobFailed(job); + V3Job asyncJob = get("/v3/jobs/" + jobGuid, V3Job.class); + + if (asyncJob != null && asyncJob.isTerminal()) { + + if (asyncJob.isFailed()) { + throw jobFailed(asyncJob); } - return job; + + return asyncJob; } - if (System.nanoTime() >= deadlineNanos) { - throw new CloudOperationException(HttpStatus.GATEWAY_TIMEOUT, "Job timeout", - "Job " + jobGuid + " did not complete within " + timeout); + + long elapsedNanos = System.nanoTime() - startTimeNanos; + if (elapsedNanos >= timeoutNanos) { + throw new CloudOperationException(HttpStatus.GATEWAY_TIMEOUT, Messages.JOB_TIMEOUT, + MessageFormat.format(Messages.JOB_0_DID_NOT_COMPLETE_WITHIN_1, jobGuid, timeout)); } - sleep(interval); + + long remainingNanos = timeoutNanos - elapsedNanos; + Duration sleepDuration = interval.toNanos() > remainingNanos ? Duration.ofNanos(remainingNanos) : interval; + + sleep(sleepDuration); interval = nextInterval(interval); } } private static Duration nextInterval(Duration current) { Duration doubled = current.multipliedBy(2); - return doubled.compareTo(JOB_POLL_MAX_INTERVAL) > 0 ? JOB_POLL_MAX_INTERVAL : doubled; + return doubled.compareTo(Constants.JOB_POLL_MAX_INTERVAL) > 0 ? Constants.JOB_POLL_MAX_INTERVAL : doubled; } private static void sleep(Duration interval) { @@ -212,12 +182,13 @@ private static void sleep(Duration interval) { } catch (InterruptedException e) { Thread.currentThread() .interrupt(); - throw new CloudException("Interrupted while polling an async job", e); + throw new CloudException(MessageFormat.format(Messages.INTERRUPTED_WHILE_POLLING_ASYNC_JOB_0, e.getMessage()), e); } } private static CloudOperationException jobFailed(V3Job job) { String detail = "Job failed"; + if (job.errors() != null && !job.errors() .isEmpty()) { detail = job.errors() @@ -226,30 +197,37 @@ private static CloudOperationException jobFailed(V3Job job) { .reduce((a, b) -> a + "\n" + b) .orElse(detail); } - return new CloudOperationException(HttpStatus.UNPROCESSABLE_ENTITY, "Job failed", detail); + + return new CloudOperationException(HttpStatus.UNPROCESSABLE_ENTITY, Messages.JOB_FAILED, detail); } private static String extractJobGuid(String location) { - int idx = location.lastIndexOf("/v3/jobs/"); - if (idx < 0) { + int index = location.lastIndexOf("/v3/jobs/"); + + if (index < 0) { return location.substring(location.lastIndexOf('/') + 1); } - return location.substring(idx + "/v3/jobs/".length()); + + return location.substring(index + "/v3/jobs/".length()); } - // The next.href CF returns is absolute; RestClient has a baseUrl, so strip the origin to a path+query to keep requests baseUrl-relative. private String toRelativeIfAbsolute(String href) { if (href == null) { return null; } + URI uri = URI.create(href); + if (uri.isAbsolute()) { String pathAndQuery = uri.getRawPath(); + if (uri.getRawQuery() != null) { pathAndQuery = pathAndQuery + "?" + uri.getRawQuery(); } + return pathAndQuery; } + return href; } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Endpoints.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Endpoints.java new file mode 100644 index 0000000000..5cbce89a69 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Endpoints.java @@ -0,0 +1,88 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +/** + * The Cloud Controller v3 API surface used by the in-house CF client — resource roots, {@code {guid}} path templates, and query-parameter + * fragments — in one place, so the {@code V3Operations} classes reference named constants instead of inline "magic string" URLs. + *

+ * Two URI mechanisms are represented, matching how the operations classes issue requests: + *

    + *
  • Path templates (e.g. {@link #APP_BY_GUID}) contain Spring {@code {placeholder}}s expanded by {@code RestClient.uri(template, + * args...)} — the placeholders are intentionally kept literally.
  • + *
  • Roots + query fragments (e.g. {@link #APPS} + {@link #QUERY_PER_PAGE} + {@link #AMPERSAND_SPACE_GUIDS}) are concatenated by the caller + * into a query string. The fragments include their leading {@code ?}/{@code &} and trailing {@code =} exactly as the callers write them, + * so swapping a literal for a constant is a byte-identical substitution.
  • + *
+ */ +public final class CloudControllerV3Endpoints { + + private CloudControllerV3Endpoints() { + } + + public static final int DEFAULT_PAGE_SIZE = 5000; + + public static final String APPS = "/v3/apps"; + public static final String ROUTES = "/v3/routes"; + public static final String DOMAINS = "/v3/domains"; + public static final String SERVICE_INSTANCES = "/v3/service_instances"; + public static final String SERVICE_CREDENTIAL_BINDINGS = "/v3/service_credential_bindings"; + public static final String SERVICE_BROKERS = "/v3/service_brokers"; + public static final String SERVICE_OFFERINGS = "/v3/service_offerings"; + public static final String SERVICE_PLANS = "/v3/service_plans"; + public static final String PACKAGES = "/v3/packages"; + public static final String BUILDS = "/v3/builds"; + public static final String TASKS = "/v3/tasks"; + public static final String PROCESSES = "/v3/processes"; + public static final String STACKS = "/v3/stacks"; + public static final String AUDIT_EVENTS = "/v3/audit_events"; + public static final String JOBS = "/v3/jobs"; + public static final String ROLES = "/v3/roles"; + public static final String ORGANIZATIONS = "/v3/organizations"; + public static final String SPACES = "/v3/spaces"; + + public static final String APP_BY_GUID = "/v3/apps/{guid}"; + public static final String APP_START = "/v3/apps/{guid}/actions/start"; + public static final String APP_STOP = "/v3/apps/{guid}/actions/stop"; + public static final String APP_ENV_VARS = "/v3/apps/{guid}/environment_variables"; + public static final String APP_FEATURE = "/v3/apps/{guid}/features/{name}"; + public static final String APP_WEB_PROCESS_SCALE = "/v3/apps/{guid}/processes/web/actions/scale"; + public static final String APP_CURRENT_DROPLET = "/v3/apps/{guid}/relationships/current_droplet"; + public static final String APP_TASKS = "/v3/apps/{guid}/tasks"; + public static final String ROUTE_BY_GUID = "/v3/routes/{guid}"; + public static final String ROUTE_DESTINATIONS = "/v3/routes/{guid}/destinations"; + public static final String ROUTE_DESTINATION_BY_GUID = "/v3/routes/{routeGuid}/destinations/{destinationGuid}"; + public static final String DOMAIN_BY_GUID = "/v3/domains/{guid}"; + public static final String SERVICE_INSTANCE_BY_GUID = "/v3/service_instances/{guid}"; + public static final String SERVICE_BROKER_BY_GUID = "/v3/service_brokers/{guid}"; + public static final String SERVICE_CREDENTIAL_BINDING_BY_GUID = "/v3/service_credential_bindings/{guid}"; + public static final String SERVICE_PLAN_VISIBILITY = "/v3/service_plans/{guid}/visibility"; + public static final String PROCESS_BY_GUID = "/v3/processes/{guid}"; + public static final String TASK_CANCEL = "/v3/tasks/{guid}/actions/cancel"; + public static final String PACKAGE_UPLOAD = "/v3/packages/{guid}/upload"; + public static final String SPACE_UNMAPPED_ROUTES = "/v3/spaces/{guid}/routes?unmapped=true"; + + public static final String QUERY_PER_PAGE = "?per_page="; + public static final String QUERY_NAMES = "?names="; + public static final String QUERY_PACKAGE_GUIDS = "?package_guids="; + public static final String QUERY_APP_GUIDS = "?app_guids="; + public static final String QUERY_GUIDS = "?guids="; + public static final String QUERY_SERVICE_INSTANCE_GUIDS = "?service_instance_guids="; + public static final String QUERY_SERVICE_OFFERING_GUIDS = "?service_offering_guids="; + public static final String QUERY_SERVICE_BROKER_GUIDS = "?service_broker_guids="; + public static final String QUERY_ORGANIZATION_GUIDS = "?organization_guids="; + + public static final String AMPERSAND_PER_PAGE = "&per_page="; + public static final String AMPERSAND_SPACE_GUIDS = "&space_guids="; + public static final String AMPERSAND_NAMES = "&names="; + public static final String AMPERSAND_TYPE = "&type="; + public static final String AMPERSAND_LABEL_SELECTOR = "&label_selector="; + public static final String AMPERSAND_DOMAIN_GUIDS = "&domain_guids="; + public static final String AMPERSAND_SERVICE_INSTANCE_GUIDS = "&service_instance_guids="; + public static final String AMPERSAND_SERVICE_OFFERING_GUIDS = "&service_offering_guids="; + public static final String AMPERSAND_SERVICE_BROKER_NAMES = "&service_broker_names="; + public static final String AMPERSAND_APP_GUIDS = "&app_guids="; + public static final String AMPERSAND_USER_GUIDS = "&user_guids="; + public static final String AMPERSAND_TARGET_GUIDS = "&target_guids="; + public static final String AMPERSAND_HOSTS = "&hosts="; + public static final String AMPERSAND_PATHS = "&paths="; + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudSpaceClient.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudSpaceClient.java index 2f860840b0..0644d62545 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudSpaceClient.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudSpaceClient.java @@ -1,8 +1,10 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest; +import java.text.MessageFormat; import java.util.List; import java.util.UUID; +import org.cloudfoundry.multiapps.controller.Messages; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudOrganization; @@ -15,11 +17,6 @@ import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpStatus; -/** - * Resolves a target {@link CloudSpace} (space + its organization) by GUID or by org-name/space-name, talking directly to the CF v3 REST - * API through {@link CloudControllerV3Client}. Replaces the OSS cf-java-client-based implementation (which used {@code SpacesV3} / - * {@code OrganizationsV3}); behaviour and 404 semantics are preserved. - */ public class CloudSpaceClient { private static final List CHARS_TO_ENCODE = List.of(","); @@ -31,32 +28,44 @@ public CloudSpaceClient(CloudControllerV3Client cc) { } public CloudSpace getSpace(UUID spaceGuid) { - V3Space space = cc.getOptional("/v3/spaces/" + spaceGuid, V3Space.class) - .orElseThrow(() -> new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", - "Space with GUID " + spaceGuid + " not found.")); + V3Space space = cc.getOptional(CloudControllerV3Endpoints.SPACES + "/" + spaceGuid, V3Space.class) + .orElseThrow(() -> new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + MessageFormat.format(Messages.SPACE_WITH_GUID_0_NOT_FOUND, + spaceGuid))); + String orgGuid = space.organizationGuid(); - V3Organization org = cc.getOptional("/v3/organizations/" + orgGuid, V3Organization.class) - .orElseThrow(() -> new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", - "Organization with GUID " + orgGuid + " not found.")); + V3Organization org = cc.getOptional(CloudControllerV3Endpoints.ORGANIZATIONS + "/" + orgGuid, V3Organization.class) + .orElseThrow(() -> new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + MessageFormat.format( + Messages.ORGANIZATION_WITH_GUID_0_NOT_FOUND, orgGuid))); + return mapToCloudSpace(space, org); } public CloudSpace getSpace(String organizationName, String spaceName) { - List orgs = cc.list("/v3/organizations?names=" + encodeAsQueryParam(organizationName), + List orgs = cc.list(CloudControllerV3Endpoints.ORGANIZATIONS + CloudControllerV3Endpoints.QUERY_NAMES + + encodeAsQueryParam(organizationName), new ParameterizedTypeReference>() { }); + if (orgs.isEmpty()) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Organization " + organizationName + " not found."); + throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + MessageFormat.format(Messages.ORGANISATION_0_NOT_FOUND, organizationName)); } + V3Organization org = orgs.get(0); - List spaces = cc.list("/v3/spaces?organization_guids=" + org.guid() + "&names=" + encodeAsQueryParam(spaceName), + List spaces = cc.list(CloudControllerV3Endpoints.SPACES + CloudControllerV3Endpoints.QUERY_ORGANIZATION_GUIDS + org.guid() + + CloudControllerV3Endpoints.AMPERSAND_NAMES + encodeAsQueryParam(spaceName), new ParameterizedTypeReference>() { }); + if (spaces.isEmpty()) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", - "Space " + spaceName + " not found in organization " + organizationName); + throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + MessageFormat.format(Messages.SPACE_0_NOT_FOUND_IN_ORGANIZATION_1, spaceName, + organizationName)); } + return mapToCloudSpace(spaces.get(0), org); } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3Operations.java index f80b4c2ff9..f219954307 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3Operations.java @@ -1,11 +1,13 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest; -import java.time.Duration; +import java.text.MessageFormat; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.stream.Collectors; +import org.cloudfoundry.multiapps.controller.Constants; +import org.cloudfoundry.multiapps.controller.Messages; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudDomain; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEntity; @@ -18,27 +20,8 @@ import org.springframework.http.ResponseEntity; import org.springframework.util.Assert; -/** - * CF v3 Domains operations of the in-house {@code CloudControllerRestClient}. Reproduces the HTTP shape, filtering and domain - * mapping of the OSS {@code CloudControllerRestClientImpl} domain methods on top of the shared {@link CloudControllerV3Client} machinery. - * - *

- * Endpoint map (see {@code docs/cf-java-client-migration/02-endpoint-inventory.md}): - *

- *
    - *
  • {@code addDomain} → {@code POST /v3/domains} (org-scoped, non-async)
  • - *
  • {@code deleteDomain} → {@code DELETE /v3/domains/{guid}} (async job)
  • - *
  • {@code getDefaultDomain} → {@code GET /v3/organizations/{guid}/domains/default}
  • - *
  • {@code getDomains} / {@code getSharedDomains} / {@code getPrivateDomains} → {@code GET /v3/domains} (paginated, filtered)
  • - *
  • {@code getDomainsForOrganization} → {@code GET /v3/organizations/{guid}/domains} (paginated)
  • - *
- */ public class DomainsV3Operations { - private static final int PER_PAGE = 5000; - // Mirrors the OSS DELETE_JOB_TIMEOUT (Duration.ofMinutes(5)) used for domain/route/service deletes. - private static final Duration DELETE_JOB_TIMEOUT = Duration.ofMinutes(5); - private static final ParameterizedTypeReference> DOMAIN_LIST_TYPE = new ParameterizedTypeReference<>() { }; @@ -50,50 +33,34 @@ public DomainsV3Operations(CloudControllerV3Client cc, CloudSpace target) { this.target = target; } - /** - * Create a private domain in the target organization, but only if a domain with that name does not already exist — matching the OSS - * {@code addDomain}, which no-ops when the domain is already present. - */ public void addDomain(String domainName) { assertSpaceProvided("add domain"); CloudDomain domain = findDomainByName(domainName); + if (domain == null) { doCreateDomain(domainName); } + } - /** - * Delete the (required) domain by name. Reproduces the OSS {@code deleteDomain}: a missing domain throws - * {@code CloudOperationException(NOT_FOUND)}, and the delete follows the async job to completion. - */ public void deleteDomain(String domainName) { assertSpaceProvided("delete domain"); CloudDomain domain = findDomainByName(domainName, true); doDeleteDomain(domain.getGuid()); } - /** - * {@code GET /v3/organizations/{guid}/domains/default} → {@link CloudDomain}. Non-paginated single fetch, mapping the OSS - * {@code getDefaultDomain}. - */ public CloudDomain getDefaultDomain() { - V3Domain domain = cc.get("/v3/organizations/" + getTargetOrganizationGuid() + "/domains/default", V3Domain.class); + V3Domain domain = cc.get(CloudControllerV3Endpoints.ORGANIZATIONS + "/" + getTargetOrganizationGuid() + "/domains/default", + V3Domain.class); return V3DomainMapper.toCloudDomain(domain); } - /** - * All domains visible to the target ({@code GET /v3/domains}). - */ public List getDomains() { return getAllDomains().stream() .map(V3DomainMapper::toCloudDomain) .collect(Collectors.toList()); } - /** - * Shared domains only — those whose {@code relationships.organization.data} is {@code null}, matching the OSS - * {@code getSharedDomainResources} filter. - */ public List getSharedDomains() { return getAllDomains().stream() .filter(domain -> !domain.isPrivate()) @@ -101,10 +68,6 @@ public List getSharedDomains() { .collect(Collectors.toList()); } - /** - * Private domains only — those whose {@code relationships.organization.data} is present, matching the OSS - * {@code getPrivateDomainResources} filter. - */ public List getPrivateDomains() { return getAllDomains().stream() .filter(V3Domain::isPrivate) @@ -112,13 +75,11 @@ public List getPrivateDomains() { .collect(Collectors.toList()); } - /** - * Domains available to the target organization ({@code GET /v3/organizations/{guid}/domains}), matching the OSS - * {@code getDomainsForOrganization}. - */ public List getDomainsForOrganization() { assertSpaceProvided("access organization domains"); - String uri = "/v3/organizations/" + getTargetOrganizationGuid() + "/domains?per_page=" + PER_PAGE; + String uri = CloudControllerV3Endpoints.ORGANIZATIONS + "/" + getTargetOrganizationGuid() + "/domains" + + CloudControllerV3Endpoints.QUERY_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; + return cc.list(uri, DOMAIN_LIST_TYPE) .stream() .map(V3DomainMapper::toCloudDomain) @@ -126,19 +87,25 @@ public List getDomainsForOrganization() { } private List getAllDomains() { - return cc.list("/v3/domains?per_page=" + PER_PAGE, DOMAIN_LIST_TYPE); + return cc.list(CloudControllerV3Endpoints.DOMAINS + CloudControllerV3Endpoints.QUERY_PER_PAGE + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE, DOMAIN_LIST_TYPE); } private CloudDomain findDomainByName(String name, boolean required) { CloudDomain domain = findDomainByName(name); + if (domain == null && required) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Domain " + name + " not found."); + throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + MessageFormat.format(Messages.DOMAIN_0_NOT_FOUND, name)); } + return domain; } private CloudDomain findDomainByName(String name) { - String uri = "/v3/domains?names=" + name + "&per_page=" + PER_PAGE; + String uri = CloudControllerV3Endpoints.DOMAINS + CloudControllerV3Endpoints.QUERY_NAMES + name + + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; + return cc.list(uri, DOMAIN_LIST_TYPE) .stream() .findFirst() @@ -149,7 +116,7 @@ private CloudDomain findDomainByName(String name) { private void doCreateDomain(String name) { cc.getRestClient() .post() - .uri("/v3/domains") + .uri(CloudControllerV3Endpoints.DOMAINS) .body(Map.of("name", name, "relationships", Map.of("organization", Map.of("data", Map.of("guid", getTargetOrganizationGuid().toString()))))) .retrieve() @@ -159,10 +126,10 @@ private void doCreateDomain(String name) { private void doDeleteDomain(UUID guid) { ResponseEntity response = cc.getRestClient() .delete() - .uri("/v3/domains/{guid}", guid.toString()) + .uri(CloudControllerV3Endpoints.DOMAIN_BY_GUID, guid.toString()) .retrieve() .toEntity(Void.class); - cc.followAsyncJob(response, DELETE_JOB_TIMEOUT); + cc.followAsyncJob(response, Constants.DELETE_JOB_TIMEOUT); } private UUID getTargetOrganizationGuid() { @@ -173,6 +140,7 @@ private UUID getGuid(CloudEntity entity) { if (entity == null || entity.getMetadata() == null) { return null; } + return entity.getMetadata() .getGuid(); } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/EventsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/EventsV3Operations.java index 1566597540..e348588f23 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/EventsV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/EventsV3Operations.java @@ -1,8 +1,10 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest; +import java.text.MessageFormat; import java.util.List; import java.util.UUID; +import org.cloudfoundry.multiapps.controller.Messages; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEvent; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; @@ -13,23 +15,8 @@ import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpStatus; -/** - * CF v3 audit event operations for the cf-java-client replacement. Reproduces the HTTP shape and domain-mapping of the OSS - * {@code CloudControllerRestClientImpl} event methods: - *
    - *
  • {@code getEvents()} → {@code GET /v3/audit_events} (paginated);
  • - *
  • {@code getEventsByTarget(uuid)} → {@code GET /v3/audit_events?target_guids=} (paginated);
  • - *
  • {@code getApplicationEvents(name)} → resolve the app guid in the target space, then {@code getEventsByTarget}.
  • - *
- * Audit events are a global resource, so — like the OSS impl — the listings are not filtered by the target space (only by target guid where - * requested). Application-name resolution mirrors the OSS impl's {@code getRequiredApplicationGuid}: it queries the target space and throws - * {@link CloudOperationException} with {@link HttpStatus#NOT_FOUND} when the application does not exist. - */ public class EventsV3Operations { - // CF v3 caps per_page at 5000; use a large page to minimise round-trips (the pagination walker still handles multiple pages). - private static final int DEFAULT_PAGE_SIZE = 5000; - private static final ParameterizedTypeReference> EVENT_PAGE = new ParameterizedTypeReference<>() { }; private static final ParameterizedTypeReference> APPLICATION_PAGE = new ParameterizedTypeReference<>() { @@ -44,14 +31,17 @@ public EventsV3Operations(CloudControllerV3Client cc, CloudSpace target) { } public List getEvents() { - return cc.list("/v3/audit_events?per_page=" + DEFAULT_PAGE_SIZE, EVENT_PAGE) + return cc.list(CloudControllerV3Endpoints.AUDIT_EVENTS + CloudControllerV3Endpoints.QUERY_PER_PAGE + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE, EVENT_PAGE) .stream() .map(V3AuditEventMapper::toCloudEvent) .toList(); } public List getEventsByTarget(UUID uuid) { - String query = "/v3/audit_events?per_page=" + DEFAULT_PAGE_SIZE + "&target_guids=" + uuid; + String query = CloudControllerV3Endpoints.AUDIT_EVENTS + CloudControllerV3Endpoints.QUERY_PER_PAGE + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE + CloudControllerV3Endpoints.AMPERSAND_TARGET_GUIDS + uuid; + return cc.list(query, EVENT_PAGE) .stream() .map(V3AuditEventMapper::toCloudEvent) @@ -60,23 +50,29 @@ public List getEventsByTarget(UUID uuid) { public List getApplicationEvents(String applicationName) { UUID applicationGuid = getRequiredApplicationGuid(applicationName); + return getEventsByTarget(applicationGuid); } - // Mirrors the OSS impl's getRequiredApplicationGuid: look up the app in the target space and 404 if it is absent. private UUID getRequiredApplicationGuid(String applicationName) { - StringBuilder query = new StringBuilder("/v3/apps?per_page=" + DEFAULT_PAGE_SIZE); + StringBuilder query = new StringBuilder(CloudControllerV3Endpoints.APPS + CloudControllerV3Endpoints.QUERY_PER_PAGE + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE); + if (target != null && target.getGuid() != null) { - query.append("&space_guids=") + query.append(CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) .append(target.getGuid()); } - query.append("&names=") + + query.append(CloudControllerV3Endpoints.AMPERSAND_NAMES) .append(applicationName); List apps = cc.list(query.toString(), APPLICATION_PAGE); + if (apps.isEmpty()) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Application " + applicationName + " not found."); + throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + MessageFormat.format(Messages.APPLICATION_0_NOT_FOUND, applicationName)); } - return UUID.fromString(apps.get(0) + + return UUID.fromString(apps.getFirst() .guid()); } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/HttpConnectionPair.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/HttpConnectionPair.java new file mode 100644 index 0000000000..191f0e73ca --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/HttpConnectionPair.java @@ -0,0 +1,8 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.net.URL; + +import reactor.netty.http.client.HttpClient; + +public record HttpConnectionPair(HttpClient httpClient, URL v3ApiUrl, URL logCacheUrl) { +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/JobsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/JobsV3Operations.java index b2048dc6b3..0bb253ebe6 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/JobsV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/JobsV3Operations.java @@ -1,30 +1,20 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudAsyncJob; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Job; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3JobMapper; -/** - * CF v3 Jobs operations of the in-house {@code CloudControllerRestClient}. Reproduces the HTTP shape and domain mapping of the OSS - * {@code CloudControllerRestClientImpl#getAsyncJob} on top of the shared {@link CloudControllerV3Client} machinery. - */ public class JobsV3Operations { private final CloudControllerV3Client cc; - private final CloudSpace target; - public JobsV3Operations(CloudControllerV3Client cc, CloudSpace target) { + public JobsV3Operations(CloudControllerV3Client cc) { this.cc = cc; - this.target = target; } - /** - * {@code GET /v3/jobs/{guid}} → {@link CloudAsyncJob}. Non-paginated, synchronous fetch. A missing job yields a - * {@code CloudOperationException(NOT_FOUND)}, matching the OSS {@code fetch(...)} contract (which does not swallow 404s here). - */ public CloudAsyncJob getAsyncJob(String jobId) { - V3Job job = cc.get("/v3/jobs/" + jobId, V3Job.class); + V3Job job = cc.get(CloudControllerV3Endpoints.JOBS + "/" + jobId, V3Job.class); + return V3JobMapper.toCloudAsyncJob(job); } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3Operations.java index b336615131..d58fd4ba14 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3Operations.java @@ -1,11 +1,18 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest; import java.nio.file.Path; +import java.text.MessageFormat; import java.time.Duration; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.UUID; -import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.cloudfoundry.multiapps.controller.Constants; +import org.cloudfoundry.multiapps.controller.Messages; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.UploadStatusCallback; import org.cloudfoundry.multiapps.controller.client.facade.domain.BitsData; @@ -30,87 +37,77 @@ import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestClient; -/** - * CF v3 packages operations of the cf-java-client replacement. Reproduces the HTTP shape and domain mapping of the OSS - * {@code CloudControllerRestClientImpl} package methods: - *
    - *
  • {@code getPackage(guid)} → {@code GET /v3/packages/{guid}};
  • - *
  • {@code getPackagesForApplication(appGuid)} → {@code GET /v3/apps/{guid}/packages} (paginated);
  • - *
  • {@code createDockerPackage(appGuid, dockerInfo)} → {@code POST /v3/packages} then re-fetch;
  • - *
  • {@code asyncUploadApplication(...)} → create a bits package ({@code POST /v3/packages}), - * upload the bits multipart ({@code POST /v3/packages/{guid}/upload}), then poll status in a background thread;
  • - *
  • {@code getUploadStatus(guid)} → derived from {@code getPackage(guid)}.
  • - *
- */ public class PackagesV3Operations { - // CF v3 caps per_page at 5000; use a large page to minimise round-trips (the pagination walker still handles multiple pages). - private static final int DEFAULT_PAGE_SIZE = 5000; - // Mirrors the OSS CloudControllerRestClientImpl.PACKAGE_UPLOAD_JOB_POLLING_PERIOD. - private static final long PACKAGE_UPLOAD_JOB_POLLING_PERIOD = TimeUnit.SECONDS.toMillis(5); - private static final ParameterizedTypeReference> PACKAGE_PAGE = new ParameterizedTypeReference<>() { }; private final CloudControllerV3Client cc; private final CloudSpace target; - // Builds a RestClient whose response timeout is the given per-upload timeout, so a stalled bits upload is bounded by the - // user-configured UPLOAD_TIMEOUT (mirrors the OSS .timeout(uploadTimeout)). May be null, in which case the shared client is used. - private final java.util.function.Function uploadRestClientFactory; + + private final Function uploadRestClientFactory; public PackagesV3Operations(CloudControllerV3Client cc, CloudSpace target) { this(cc, target, null); } public PackagesV3Operations(CloudControllerV3Client cc, CloudSpace target, - java.util.function.Function uploadRestClientFactory) { + Function uploadRestClientFactory) { this.cc = cc; this.target = target; this.uploadRestClientFactory = uploadRestClientFactory; } public CloudPackage getPackage(UUID packageGuid) { - V3Package resource = cc.get("/v3/packages/" + packageGuid, V3Package.class); + V3Package resource = cc.get(CloudControllerV3Endpoints.PACKAGES + "/" + packageGuid, V3Package.class); + return resource == null ? null : V3PackageMapper.toCloudPackage(resource); } public List getPackagesForApplication(UUID applicationGuid) { - return cc.list("/v3/apps/" + applicationGuid + "/packages?per_page=" + DEFAULT_PAGE_SIZE, PACKAGE_PAGE) + return cc.list(CloudControllerV3Endpoints.APPS + "/" + applicationGuid + "/packages" + CloudControllerV3Endpoints.QUERY_PER_PAGE + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE, PACKAGE_PAGE) .stream() .map(V3PackageMapper::toCloudPackage) .toList(); } public CloudPackage createDockerPackage(UUID applicationGuid, DockerInfo dockerInfo) { - java.util.Map data = new java.util.HashMap<>(); + Map data = new HashMap<>(); data.put("image", dockerInfo.getImage()); + DockerCredentials credentials = dockerInfo.getCredentials(); if (credentials != null) { if (credentials.getUsername() != null) { data.put("username", credentials.getUsername()); } + if (credentials.getPassword() != null) { data.put("password", credentials.getPassword()); } } + V3Package created = cc.getRestClient() .post() - .uri("/v3/packages") - .body(java.util.Map.of("type", "docker", "data", data, "relationships", - applicationRelationship(applicationGuid))) + .uri(CloudControllerV3Endpoints.PACKAGES) + .body(Map.of("type", "docker", "data", data, "relationships", + applicationRelationship(applicationGuid))) .retrieve() .body(V3Package.class); + return getPackage(UUID.fromString(created.guid())); } public Upload getUploadStatus(UUID packageGuid) { CloudPackage cloudPackage = getPackage(packageGuid); ErrorDetails errorDetails = null; + if (cloudPackage.getType() == CloudPackage.Type.BITS) { errorDetails = ImmutableErrorDetails.builder() .description(((BitsData) cloudPackage.getData()).getError()) .build(); } + return ImmutableUpload.builder() .status(cloudPackage.getStatus()) .errorDetails(errorDetails) @@ -132,11 +129,10 @@ private CloudPackage startUpload(String applicationName, Path file, Duration upl MultiValueMap body = new LinkedMultiValueMap<>(); body.add("bits", new FileSystemResource(file)); - // Bound the bits upload by the caller's per-MTA uploadTimeout via a dedicated RestClient whose response timeout is that value - // (see CloudControllerRestClientFactory). Fall back to the shared client if no factory/timeout was supplied. + RestClient uploadClient = resolveUploadClient(uploadTimeout); uploadClient.post() - .uri("/v3/packages/{guid}/upload", packageGuid) + .uri(CloudControllerV3Endpoints.PACKAGE_UPLOAD, packageGuid) .contentType(MediaType.MULTIPART_FORM_DATA) .body(body) .retrieve() @@ -149,38 +145,46 @@ private RestClient resolveUploadClient(Duration uploadTimeout) { if (uploadRestClientFactory != null && uploadTimeout != null && !uploadTimeout.isZero() && !uploadTimeout.isNegative()) { return uploadRestClientFactory.apply(uploadTimeout); } + return cc.getRestClient(); } private CloudPackage createBitsPackage(UUID applicationGuid) { V3Package created = cc.getRestClient() .post() - .uri("/v3/packages") - .body(java.util.Map.of("type", "bits", "relationships", applicationRelationship(applicationGuid))) + .uri(CloudControllerV3Endpoints.PACKAGES) + .body(Map.of("type", "bits", "relationships", applicationRelationship(applicationGuid))) .retrieve() .body(V3Package.class); + return getPackage(UUID.fromString(created.guid())); } - private static java.util.Map applicationRelationship(UUID applicationGuid) { - return java.util.Map.of("app", java.util.Map.of("data", java.util.Map.of("guid", applicationGuid.toString()))); + private static Map applicationRelationship(UUID applicationGuid) { + return Map.of("app", java.util.Map.of("data", java.util.Map.of("guid", applicationGuid.toString()))); } private UUID getRequiredApplicationGuid(String applicationName) { - StringBuilder query = new StringBuilder("/v3/apps?per_page=" + DEFAULT_PAGE_SIZE); + StringBuilder query = new StringBuilder(CloudControllerV3Endpoints.APPS + CloudControllerV3Endpoints.QUERY_PER_PAGE + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE); + if (target != null && target.getGuid() != null) { - query.append("&space_guids=") + query.append(CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) .append(target.getGuid()); } - query.append("&names=") + + query.append(CloudControllerV3Endpoints.AMPERSAND_NAMES) .append(applicationName); List apps = cc.list(query.toString(), new ParameterizedTypeReference>() { }); - if (apps.isEmpty() || apps.get(0) + + if (apps.isEmpty() || apps.getFirst() .guid() == null) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Application " + applicationName + " not found."); + throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + MessageFormat.format(Messages.APPLICATION_0_NOT_FOUND, applicationName)); } - return UUID.fromString(apps.get(0) + + return UUID.fromString(apps.getFirst() .guid()); } @@ -194,16 +198,19 @@ private void processAsyncUpload(CloudPackage cloudPackage, UploadStatusCallback Upload upload = getUploadStatus(cloudPackage.getGuid()); Status uploadStatus = upload.getStatus(); boolean unsubscribe = callback.onProgress(uploadStatus.toString()); + if (unsubscribe || isUploadReady(uploadStatus)) { return; } + if (hasUploadFailed(uploadStatus)) { callback.onError(upload.getErrorDetails() .getDescription()); return; } + try { - Thread.sleep(PACKAGE_UPLOAD_JOB_POLLING_PERIOD); + Thread.sleep(Constants.PACKAGE_UPLOAD_JOB_POLLING_PERIOD); } catch (InterruptedException e) { Thread.currentThread() .interrupt(); @@ -220,12 +227,8 @@ private boolean hasUploadFailed(Status status) { return status == Status.EXPIRED || status == Status.FAILED; } - /** - * Minimal wire-model of a v3 app resource, used only to resolve an application's GUID by name when starting an upload. The - * fully-featured app wire model lives in {@code V3Application}; this local record keeps the packages helper self-contained. - */ - @com.fasterxml.jackson.annotation.JsonIgnoreProperties(ignoreUnknown = true) - private record V3App(@com.fasterxml.jackson.annotation.JsonProperty("guid") String guid) { + @JsonIgnoreProperties(ignoreUnknown = true) + private record V3App(@JsonProperty("guid") String guid) { } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3Operations.java index def82d3124..ed19e19222 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3Operations.java @@ -1,11 +1,14 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest; +import java.text.MessageFormat; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; +import org.cloudfoundry.multiapps.controller.Constants; +import org.cloudfoundry.multiapps.controller.Messages; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudProcess; @@ -24,29 +27,8 @@ import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpStatus; -/** - * CF v3 process-family operations for the cf-java-client replacement. Reproduces the HTTP shape and domain mapping of the - * OSS {@code CloudControllerRestClientImpl} process/instance/feature/droplet methods: - *
    - *
  • {@code getApplicationProcess(appGuid)} → {@code GET /v3/apps/{guid}/processes/web};
  • - *
  • {@code getApplicationInstances(app)} / {@code getApplicationInstances(appGuid)} → - * {@code GET /v3/apps/{guid}/processes/web/stats} (empty for a non-STARTED app);
  • - *
  • {@code getApplicationSshEnabled(appGuid)} → {@code GET /v3/apps/{guid}/ssh_enabled} (default {@code false});
  • - *
  • {@code getApplicationFeatures(appGuid)} → {@code GET /v3/apps/{guid}/features} (paginated);
  • - *
  • {@code getCurrentDropletForApplication(appGuid)} → {@code GET /v3/apps/{guid}/droplets/current} (404 → - * {@link CloudOperationException});
  • - *
  • {@code updateApplicationStaging(appName, staging)} → {@code PATCH /v3/apps/{guid}} (lifecycle) then update the web process - * ({@code PATCH /v3/apps/{guid}/features/{name}} per feature and {@code PATCH /v3/processes/{guid}}).
  • - *
- */ public class ProcessesV3Operations { - // CF v3 caps per_page at 5000; use a large page to minimise round-trips (the pagination walker still handles multiple pages). - private static final int DEFAULT_PAGE_SIZE = 5000; - // The "package" link key in the current-droplet response; the package GUID is the last path segment of its href. - private static final String PACKAGE_LINK = "package"; - private static final String WEB_PROCESS_TYPE = "web"; - private final CloudControllerV3Client cc; private final CloudSpace target; @@ -56,8 +38,11 @@ public ProcessesV3Operations(CloudControllerV3Client cc, CloudSpace target) { } public CloudProcess getApplicationProcess(UUID applicationGuid) { - V3Process process = cc.get("/v3/apps/" + applicationGuid + "/processes/" + WEB_PROCESS_TYPE, V3Process.class); - return process == null ? null : V3ProcessMapper.toCloudProcess(process); + V3Process applicationProcess = cc.get( + CloudControllerV3Endpoints.APPS + "/" + applicationGuid + "/processes/" + Constants.WEB_PROCESS_TYPE, + V3Process.class); + + return applicationProcess == null ? null : V3ProcessMapper.toCloudProcess(applicationProcess); } public InstancesInfo getApplicationInstances(CloudApplication application) { @@ -65,6 +50,7 @@ public InstancesInfo getApplicationInstances(CloudApplication application) { .equals(CloudApplication.State.STARTED)) { return findApplicationInstances(application.getGuid()); } + return ImmutableInstancesInfo.builder() .instances(Collections.emptyList()) .build(); @@ -75,46 +61,60 @@ public InstancesInfo getApplicationInstances(UUID applicationGuid) { } private InstancesInfo findApplicationInstances(UUID applicationGuid) { - V3Process.V3ProcessStats stats = cc.get("/v3/apps/" + applicationGuid + "/processes/" + WEB_PROCESS_TYPE + "/stats", - V3Process.V3ProcessStats.class); + V3Process.V3ProcessStats stats = cc.get(CloudControllerV3Endpoints.APPS + "/" + applicationGuid + "/processes/" + + Constants.WEB_PROCESS_TYPE + "/stats", + V3Process.V3ProcessStats.class); + return V3InstancesInfoMapper.toInstancesInfo(stats); } public boolean getApplicationSshEnabled(UUID applicationGuid) { - V3Process.V3SshEnabled sshEnabled = cc.get("/v3/apps/" + applicationGuid + "/ssh_enabled", V3Process.V3SshEnabled.class); + V3Process.V3SshEnabled sshEnabled = cc.get(CloudControllerV3Endpoints.APPS + "/" + applicationGuid + "/ssh_enabled", + V3Process.V3SshEnabled.class); + if (sshEnabled == null || sshEnabled.enabled() == null) { return false; } + return sshEnabled.enabled(); } public Map getApplicationFeatures(UUID applicationGuid) { - List features = cc.list("/v3/apps/" + applicationGuid + "/features?per_page=" + DEFAULT_PAGE_SIZE, - new ParameterizedTypeReference>() { - }); + List applicationFeatures = cc.list(CloudControllerV3Endpoints.APPS + "/" + applicationGuid + "/features" + + CloudControllerV3Endpoints.QUERY_PER_PAGE + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE, + new ParameterizedTypeReference>() { + }); + Map result = new HashMap<>(); - for (V3Process.V3AppFeature feature : features) { + for (V3Process.V3AppFeature feature : applicationFeatures) { result.put(feature.name(), feature.enabled()); } + return result; } public DropletInfo getCurrentDropletForApplication(UUID applicationGuid) { - V3Droplet droplet = cc.get("/v3/apps/" + applicationGuid + "/droplets/current", V3Droplet.class); - if (droplet == null) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not found", - "Application with guid " + applicationGuid + " does not have a droplet"); + V3Droplet currentDroplet = cc.get(CloudControllerV3Endpoints.APPS + "/" + applicationGuid + "/droplets/current", V3Droplet.class); + + if (currentDroplet == null) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + MessageFormat.format(Messages.APPLICATION_WITH_GUID_0_DOES_NOT_HAVE_A_DROPLET, + applicationGuid)); } - return parseDropletInfo(droplet); + + return parseDropletInfo(currentDroplet); } private DropletInfo parseDropletInfo(V3Droplet droplet) { String packageUrl = droplet.links() - .get(PACKAGE_LINK) + .get(Constants.PACKAGE_LINK) .href(); + if (packageUrl.endsWith("/")) { packageUrl = packageUrl.substring(0, packageUrl.lastIndexOf("/")); } + String packageGuid = packageUrl.substring(packageUrl.lastIndexOf("/") + 1); return ImmutableDropletInfo.builder() .guid(UUID.fromString(droplet.guid())) @@ -124,54 +124,63 @@ private DropletInfo parseDropletInfo(V3Droplet droplet) { public void updateApplicationStaging(String applicationName, Staging staging) { UUID applicationGuid = getRequiredApplicationGuid(applicationName); + cc.getRestClient() .patch() - .uri("/v3/apps/{guid}", applicationGuid) + .uri(CloudControllerV3Endpoints.APP_BY_GUID, applicationGuid) .body(Map.of("lifecycle", buildApplicationLifecycle(staging))) .retrieve() .toBodilessEntity(); updateApplicationProcess(applicationGuid, staging); } - // Mirrors the OSS impl's buildApplicationLifecycle: Docker if DockerInfo present, otherwise a buildpack (or CNB) lifecycle. private Map buildApplicationLifecycle(Staging staging) { if (staging.getDockerInfo() != null) { return Map.of("type", "docker", "data", Map.of()); } + String lifecycleType = staging.getLifecycleType() != null ? staging.getLifecycleType() - .name() - .toLowerCase() - : "buildpack"; + .name() + .toLowerCase() + : "buildpack"; + if ("cnb".equals(lifecycleType) && (staging.getBuildpacks() == null || staging.getBuildpacks() .isEmpty())) { throw new IllegalArgumentException("Buildpacks are required for the CNB lifecycle type"); } + Map data = new HashMap<>(); if (staging.getStackName() != null) { data.put("stack", staging.getStackName()); } + if (staging.getBuildpacks() != null) { data.put("buildpacks", staging.getBuildpacks()); } + return Map.of("type", lifecycleType, "data", data); } - // Mirrors the OSS impl's updateApplicationProcess: toggle app features, then PATCH the web process command + health checks. private void updateApplicationProcess(UUID applicationGuid, Staging staging) { staging.getAppFeatures() .forEach((featureName, enabled) -> updateAppFeature(applicationGuid, featureName, enabled)); - V3Process process = cc.get("/v3/apps/" + applicationGuid + "/processes/" + WEB_PROCESS_TYPE, V3Process.class); + + V3Process process = cc.get(CloudControllerV3Endpoints.APPS + "/" + applicationGuid + "/processes/" + Constants.WEB_PROCESS_TYPE, + V3Process.class); + Map updateProcessBody = new HashMap<>(); updateProcessBody.put("command", staging.getCommand()); if (staging.getHealthCheckType() != null) { updateProcessBody.put("health_check", buildHealthCheck(staging)); } + if (staging.getReadinessHealthCheckType() != null) { updateProcessBody.put("readiness_health_check", buildReadinessHealthCheck(staging)); } + cc.getRestClient() .patch() - .uri("/v3/processes/{guid}", process.guid()) + .uri(CloudControllerV3Endpoints.PROCESS_BY_GUID, process.guid()) .body(updateProcessBody) .retrieve() .toBodilessEntity(); @@ -180,7 +189,7 @@ private void updateApplicationProcess(UUID applicationGuid, Staging staging) { private void updateAppFeature(UUID applicationGuid, String featureName, boolean enabled) { cc.getRestClient() .patch() - .uri("/v3/apps/{guid}/features/{name}", applicationGuid, featureName) + .uri(CloudControllerV3Endpoints.APP_FEATURE, applicationGuid, featureName) .body(Map.of("enabled", enabled)) .retrieve() .toBodilessEntity(); @@ -192,6 +201,7 @@ private Map buildHealthCheck(Staging staging) { putIfNotNull(data, "timeout", staging.getHealthCheckTimeout()); putIfNotNull(data, "invocation_timeout", staging.getInvocationTimeout()); putIfNotNull(data, "interval", staging.getHealthCheckInterval()); + Map healthCheck = new HashMap<>(); healthCheck.put("type", staging.getHealthCheckType()); healthCheck.put("data", data); @@ -203,6 +213,7 @@ private Map buildReadinessHealthCheck(Staging staging) { putIfNotNull(data, "invocation_timeout", staging.getReadinessHealthCheckInvocationTimeout()); putIfNotNull(data, "endpoint", staging.getReadinessHealthCheckHttpEndpoint()); putIfNotNull(data, "interval", staging.getReadinessHealthCheckInterval()); + Map readinessHealthCheck = new HashMap<>(); readinessHealthCheck.put("type", staging.getReadinessHealthCheckType()); readinessHealthCheck.put("data", data); @@ -215,22 +226,28 @@ private static void putIfNotNull(Map map, String key, Object val } } - // Mirrors the OSS impl's getRequiredApplicationGuid: look up the app in the target space and 404 if it is absent. private UUID getRequiredApplicationGuid(String applicationName) { - StringBuilder query = new StringBuilder("/v3/apps?per_page=" + DEFAULT_PAGE_SIZE); + StringBuilder query = new StringBuilder(CloudControllerV3Endpoints.APPS + CloudControllerV3Endpoints.QUERY_PER_PAGE + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE); + if (target != null && target.getGuid() != null) { - query.append("&space_guids=") + query.append(CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) .append(target.getGuid()); } - query.append("&names=") + + query.append(CloudControllerV3Endpoints.AMPERSAND_NAMES) .append(applicationName); + List apps = cc.list(query.toString(), new ParameterizedTypeReference>() { }); + if (apps.isEmpty()) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Application " + applicationName + " not found."); + throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + MessageFormat.format(Messages.APPLICATION_0_NOT_FOUND, applicationName)); } - return UUID.fromString(apps.get(0) + + return UUID.fromString(apps.getFirst() .guid()); } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ReactiveFanOut.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ReactiveFanOut.java index 04a36caa1a..5743f7be7d 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ReactiveFanOut.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ReactiveFanOut.java @@ -3,49 +3,29 @@ import java.util.List; import java.util.function.Function; +import org.cloudfoundry.multiapps.controller.Constants; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; -/** - * Restores the intra-operation concurrency the OSS cf-java-client had for free. The OSS client composed per-item work - * (e.g. fetching credentials for N service keys, or resolving the plan/offering of N service instances) as a reactive graph and let - * Reactor's {@code flatMap} run those calls concurrently (default concurrency 256) before a single {@code .block()} at the boundary. - * The synchronous {@link RestClient}-based rewrite would otherwise do the same work strictly sequentially ({@code stream().map(fetch)}). - *

- * This helper reproduces exactly that pattern — {@code Flux.fromIterable(...).flatMap(...).block()} — reusing the existing - * blocking per-item mappers unchanged. Each blocking mapper is offloaded onto {@link Schedulers#boundedElastic()} so the calls - * run concurrently without pinning the caller's thread; {@code flatMap} bounds how many are in flight (Reactor's default 256, matching - * the OSS cap). Results are returned in input order ({@link Flux#flatMapSequential} preserves order while still subscribing - * eagerly), and the first error aborts the rest and propagates unwrapped — the same fail-fast semantics a {@code .block()} over a failed - * {@code flatMap} had in the OSS path. - *

- * This uses only Reactor (a Spring library already on the classpath as the transport); it does not reintroduce cf-java-client. - */ final class ReactiveFanOut { - // Reactor's default flatMap concurrency — the in-flight cap the OSS client effectively used for per-item fan-out. - private static final int MAX_CONCURRENCY = 256; - private ReactiveFanOut() { } - /** - * Apply {@code mapper} (a blocking function) to every item concurrently, bounded by {@value #MAX_CONCURRENCY} in-flight calls, and - * return the results in the same order as {@code items}. The single-element and empty cases short-circuit without touching the - * reactive machinery (the overwhelmingly common case for typical MTAs). - */ static List mapConcurrently(List items, Function mapper) { if (items.isEmpty()) { return List.of(); } + if (items.size() == 1) { - return List.of(mapper.apply(items.get(0))); + return List.of(mapper.apply(items.getFirst())); } + return Flux.fromIterable(items) .flatMapSequential(item -> Mono.fromCallable(() -> mapper.apply(item)) .subscribeOn(Schedulers.boundedElastic()), - MAX_CONCURRENCY) + Constants.DEFAULT_CONCURRENT_TASKS) .collectList() .block(); } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RolesV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RolesV3Operations.java index 9a1102b6d6..4bf20566af 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RolesV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RolesV3Operations.java @@ -5,38 +5,28 @@ import java.util.UUID; import java.util.stream.Collectors; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; import org.cloudfoundry.multiapps.controller.client.facade.domain.UserRole; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Role; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3RoleMapper; import org.springframework.core.ParameterizedTypeReference; -/** - * CF v3 {@code Roles} operations for the in-house {@link CloudControllerRestClient} replacement. Implements the role-related methods of - * the interface by listing {@code GET /v3/roles} filtered by space and user, and mapping each wire {@link V3Role} to the project's - * {@link UserRole} domain enum via {@link V3RoleMapper}. - * - *

- * Mirrors the OSS {@code CloudControllerRestClientImpl#getUserRolesBySpaceAndUser}, which lists roles filtered by {@code space_id}, - * {@code user_id} and all role {@code types}, then collects the results into an {@link EnumSet}. - *

- */ public class RolesV3Operations { private static final ParameterizedTypeReference> ROLE_PAGE = new ParameterizedTypeReference<>() { }; private final CloudControllerV3Client cc; - private final CloudSpace target; - public RolesV3Operations(CloudControllerV3Client cc, CloudSpace target) { + public RolesV3Operations(CloudControllerV3Client cc) { this.cc = cc; - this.target = target; } public Set getUserRolesBySpaceAndUser(UUID spaceGuid, UUID userGuid) { - String uri = "/v3/roles?per_page=5000&space_guids=" + spaceGuid + "&user_guids=" + userGuid; + String uri = CloudControllerV3Endpoints.ROLES + CloudControllerV3Endpoints.QUERY_PER_PAGE + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE + CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS + spaceGuid + + CloudControllerV3Endpoints.AMPERSAND_USER_GUIDS + userGuid; + return cc.list(uri, ROLE_PAGE) .stream() .map(V3RoleMapper::toUserRole) diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3Operations.java index ee9df786f2..c0363e6d90 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3Operations.java @@ -1,6 +1,6 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest; -import java.time.Duration; +import java.text.MessageFormat; import java.util.List; import java.util.Map; import java.util.Objects; @@ -9,6 +9,10 @@ import java.util.UUID; import java.util.stream.Collectors; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.cloudfoundry.multiapps.controller.Constants; +import org.cloudfoundry.multiapps.controller.Messages; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudRoute; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; @@ -17,31 +21,12 @@ import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Route; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3RouteMapper; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; -/** - * CF v3 routes operations of the cf-java-client replacement. Reproduces the HTTP shape, async handling and domain mapping of the - * OSS {@code CloudControllerRestClientImpl} route methods: - *
    - *
  • {@code addRoute(host, domainName, path)} → resolve the domain, then {@code POST /v3/routes} (idempotent: reuse an existing - * route if one already matches);
  • - *
  • {@code deleteRoute(host, domainName, path)} → resolve the route in the target space and {@code DELETE /v3/routes/{guid}} - * (async job), 404 if not found;
  • - *
  • {@code deleteOrphanedRoutes()} → {@code DELETE /v3/spaces/{guid}/routes?unmapped=true} (async job);
  • - *
  • {@code getRoutes(domainName)} → {@code GET /v3/routes?domain_guids=…&space_guids=…} (paginated);
  • - *
  • {@code getApplicationRoutes(appGuid)} → {@code GET /v3/apps/{guid}/routes} (paginated);
  • - *
  • {@code updateApplicationRoutes(appName, routes)} → diff current vs requested destinations, then insert/remove destinations.
  • - *
- * All listings are scoped to the target space exactly where the OSS impl scopes them. - */ public class RoutesV3Operations { private static final ParameterizedTypeReference> ROUTE_PAGE = new ParameterizedTypeReference<>() { @@ -49,11 +34,6 @@ public class RoutesV3Operations { private static final ParameterizedTypeReference> DOMAIN_PAGE = new ParameterizedTypeReference<>() { }; - // CF v3 caps per_page at 5000; matches the other V3 operations. - private static final int DEFAULT_PAGE_SIZE = 5000; - // Mirrors the OSS DELETE_JOB_TIMEOUT. - private static final Duration DELETE_JOB_TIMEOUT = Duration.ofMinutes(5); - private final CloudControllerV3Client cc; private final CloudSpace target; @@ -70,21 +50,24 @@ public void addRoute(String host, String domainName, String path) { public void deleteRoute(String host, String domainName, String path) { assertSpaceProvided("delete route for domain"); + UUID routeGuid = getRouteGuid(getRequiredDomainGuid(domainName), host, path); if (routeGuid == null) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", - "Host " + host + " not found for domain " + domainName + "."); + throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + MessageFormat.format(Messages.HOST_0_NOT_FOUND_FOR_DOMAIN_1, host, domainName)); } + doDeleteRoute(routeGuid); } public void deleteOrphanedRoutes() { ResponseEntity response = cc.getRestClient() .delete() - .uri("/v3/spaces/{guid}/routes?unmapped=true", getTargetSpaceGuid()) + .uri(CloudControllerV3Endpoints.SPACE_UNMAPPED_ROUTES, getTargetSpaceGuid()) .retrieve() .toEntity(Void.class); - cc.followAsyncJob(response, DELETE_JOB_TIMEOUT); + + cc.followAsyncJob(response, Constants.DELETE_JOB_TIMEOUT); } public List getRoutes(String domainName) { @@ -94,56 +77,70 @@ public List getRoutes(String domainName) { } public List getApplicationRoutes(UUID applicationGuid) { - return listRoutes("/v3/apps/" + applicationGuid + "/routes?per_page=" + DEFAULT_PAGE_SIZE).stream() - .map(route -> V3RouteMapper.toCloudRoute( - route, applicationGuid)) - .toList(); + return listRoutes(CloudControllerV3Endpoints.APPS + "/" + applicationGuid + "/routes" + CloudControllerV3Endpoints.QUERY_PER_PAGE + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE).stream() + .map(route -> V3RouteMapper.toCloudRoute( + route, applicationGuid)) + .toList(); } public void updateApplicationRoutes(String applicationName, Set updatedRoutes) { UUID applicationGuid = getRequiredApplicationGuid(applicationName); + List appRoutes = listRoutes( - "/v3/apps/" + applicationGuid + "/routes?per_page=" + DEFAULT_PAGE_SIZE).stream() - .map(V3RouteMapper::toCloudRoute) - .toList(); + CloudControllerV3Endpoints.APPS + "/" + applicationGuid + "/routes" + CloudControllerV3Endpoints.QUERY_PER_PAGE + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE).stream() + .map(V3RouteMapper::toCloudRoute) + .toList(); + Set outdatedRoutes = getOutdatedRoutes(applicationGuid, appRoutes, updatedRoutes); Set newRoutes = getNewRoutes(applicationGuid, appRoutes, updatedRoutes); + removeRoutes(outdatedRoutes, applicationGuid); addRoutes(newRoutes, applicationGuid); } - // --- route listing helpers ------------------------------------------------------------------- - private List findRoutesByDomainGuid(UUID domainGuid) { - StringBuilder query = new StringBuilder("/v3/routes?per_page=" + DEFAULT_PAGE_SIZE); - query.append("&domain_guids=") + StringBuilder query = new StringBuilder( + CloudControllerV3Endpoints.ROUTES + CloudControllerV3Endpoints.QUERY_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE); + + query.append(CloudControllerV3Endpoints.AMPERSAND_DOMAIN_GUIDS) .append(domainGuid); - query.append("&space_guids=") + + query.append(CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) .append(getTargetSpaceGuid()); + return listRoutes(query.toString()).stream() .map(V3RouteMapper::toCloudRoute) .toList(); } private UUID getRouteGuid(UUID domainGuid, String host, String path) { - StringBuilder query = new StringBuilder("/v3/routes?per_page=" + DEFAULT_PAGE_SIZE); - query.append("&domain_guids=") + StringBuilder query = new StringBuilder( + CloudControllerV3Endpoints.ROUTES + CloudControllerV3Endpoints.QUERY_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE); + + query.append(CloudControllerV3Endpoints.AMPERSAND_DOMAIN_GUIDS) .append(domainGuid); - query.append("&space_guids=") + + query.append(CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) .append(getTargetSpaceGuid()); + if (host != null) { - query.append("&hosts=") + query.append(CloudControllerV3Endpoints.AMPERSAND_HOSTS) .append(host); } + if (path != null) { - query.append("&paths=") + query.append(CloudControllerV3Endpoints.AMPERSAND_PATHS) .append(path); } + List routes = listRoutes(query.toString()); if (CollectionUtils.isEmpty(routes)) { return null; } - return UUID.fromString(routes.get(0) + + return UUID.fromString(routes.getFirst() .guid()); } @@ -151,15 +148,16 @@ private List listRoutes(String query) { return cc.list(query, ROUTE_PAGE); } - // --- add / bind / remove --------------------------------------------------------------------- - private void addRoutes(Set routes, UUID applicationGuid) { Map domains = getDomainsFromRoutes(routes); + for (CloudRoute route : routes) { validateDomainForRoute(route, domains); + UUID domainGuid = domains.get(route.getDomain() .getName()); UUID routeGuid = getOrAddRoute(domainGuid, route.getHost(), route.getPath()); + bindRoute(routeGuid, applicationGuid, route.getRequestedProtocol()); } } @@ -177,9 +175,11 @@ private void removeRoutes(Set routes, UUID applicationGuid) { private UUID getOrAddRoute(UUID domainGuid, String host, String path) { UUID routeGuid = getRouteGuid(domainGuid, host, path); + if (routeGuid == null) { routeGuid = doAddRoute(domainGuid, host, path); } + return routeGuid; } @@ -187,28 +187,30 @@ private UUID doAddRoute(UUID domainGuid, String host, String path) { assertSpaceProvided("add route"); V3Route created = cc.getRestClient() .post() - .uri("/v3/routes") + .uri(CloudControllerV3Endpoints.ROUTES) .body(Map.of("host", host == null ? "" : host, "path", path == null ? "" : path, "relationships", Map.of("domain", toOneRelationship(domainGuid), "space", toOneRelationship(getTargetSpaceGuid())))) .retrieve() .body(V3Route.class); + return UUID.fromString(created.guid()); } private void doDeleteRoute(UUID guid) { ResponseEntity response = cc.getRestClient() .delete() - .uri("/v3/routes/{guid}", guid) + .uri(CloudControllerV3Endpoints.ROUTE_BY_GUID, guid) .retrieve() .toEntity(Void.class); - cc.followAsyncJob(response, DELETE_JOB_TIMEOUT); + + cc.followAsyncJob(response, Constants.DELETE_JOB_TIMEOUT); } private void bindRoute(UUID routeGuid, UUID applicationGuid, String protocol) { cc.getRestClient() .post() - .uri("/v3/routes/{guid}/destinations", routeGuid) + .uri(CloudControllerV3Endpoints.ROUTE_DESTINATIONS, routeGuid) .body(Map.of("destinations", List.of(createDestination(applicationGuid, protocol)))) .retrieve() .toBodilessEntity(); @@ -217,7 +219,7 @@ private void bindRoute(UUID routeGuid, UUID applicationGuid, String protocol) { private void unbindRoute(UUID routeGuid, UUID destinationGuid) { cc.getRestClient() .delete() - .uri("/v3/routes/{routeGuid}/destinations/{destinationGuid}", routeGuid, destinationGuid) + .uri(CloudControllerV3Endpoints.ROUTE_DESTINATION_BY_GUID, routeGuid, destinationGuid) .retrieve() .toBodilessEntity(); } @@ -226,6 +228,7 @@ private Map createDestination(UUID applicationGuid, String proto if (protocol == null) { return Map.of("app", Map.of("guid", applicationGuid.toString())); } + return Map.of("app", Map.of("guid", applicationGuid.toString()), "protocol", protocol); } @@ -233,8 +236,6 @@ private static Map toOneRelationship(UUID guid) { return Map.of("data", Map.of("guid", guid.toString())); } - // --- diffing (mirror of the OSS impl) -------------------------------------------------------- - private Set getOutdatedRoutes(UUID applicationGuid, List currentRoutes, Set updatedRoutes) { return currentRoutes.stream() .filter(currentRoute -> isRouteOutdated(applicationGuid, currentRoute, updatedRoutes)) @@ -243,9 +244,11 @@ private Set getOutdatedRoutes(UUID applicationGuid, List private boolean isRouteOutdated(UUID applicationGuid, CloudRoute currentRoute, Set updatedRoutes) { Optional updatedRoute = findRoute(currentRoute.getUrl(), updatedRoutes); + if (updatedRoute.isEmpty()) { return true; } + return isProtocolChanged(applicationGuid, currentRoute, updatedRoute.get()); } @@ -257,9 +260,11 @@ private Set getNewRoutes(UUID applicationGuid, List curr private boolean isRouteUpdated(UUID applicationGuid, CloudRoute updatedRoute, List currentRoutes) { Optional currentRoute = findRoute(updatedRoute.getUrl(), currentRoutes); + if (currentRoute.isEmpty()) { return true; } + return isProtocolChanged(applicationGuid, currentRoute.get(), updatedRoute); } @@ -273,6 +278,7 @@ private boolean isProtocolChanged(UUID applicationGuid, CloudRoute currentRoute, if (updatedRoute.getRequestedProtocol() == null) { return false; } + return currentRoute.getDestinations() .stream() .filter(routeDestination -> Objects.equals(routeDestination.getApplicationGuid(), applicationGuid)) @@ -283,28 +289,35 @@ private boolean isProtocolChanged(UUID applicationGuid, CloudRoute currentRoute, private void validateDomainForRoute(CloudRoute route, Map existingDomains) { String domain = route.getDomain() .getName(); + if (!StringUtils.hasLength(domain) || !existingDomains.containsKey(domain)) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), - "Domain '" + domain + "' not found for URI " + route.getUrl()); + throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + MessageFormat.format(Messages.DOMAIN_0_NOT_FOUND_FOR_URI_1, domain, route.getUrl())); } - } - // --- domain resolution (minimal inline lookups; the Domains group owns the full wire model) -- + } private UUID getRequiredDomainGuid(String name) { UUID guid = findDomainGuidByName(name); + if (guid == null) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Domain " + name + " not found."); + throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + MessageFormat.format(Messages.DOMAIN_0_NOT_FOUND, name)); } + return guid; } private UUID findDomainGuidByName(String name) { - List domains = cc.list("/v3/domains?names=" + name + "&per_page=" + DEFAULT_PAGE_SIZE, DOMAIN_PAGE); + List domains = cc.list(CloudControllerV3Endpoints.DOMAINS + CloudControllerV3Endpoints.QUERY_NAMES + name + + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE, + DOMAIN_PAGE); + if (CollectionUtils.isEmpty(domains)) { return null; } - return UUID.fromString(domains.get(0) + + return UUID.fromString(domains.getFirst() .guid()); } @@ -314,31 +327,39 @@ private Map getDomainsFromRoutes(Set routes) { .getName()) .filter(StringUtils::hasLength) .collect(Collectors.toSet()); + if (domainNames.isEmpty()) { return Map.of(); } + String names = String.join(",", domainNames); - return cc.list("/v3/domains?names=" + names + "&per_page=" + DEFAULT_PAGE_SIZE, DOMAIN_PAGE) + return cc.list(CloudControllerV3Endpoints.DOMAINS + CloudControllerV3Endpoints.QUERY_NAMES + names + + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE, DOMAIN_PAGE) .stream() .collect(Collectors.toMap(V3Domain::name, domain -> UUID.fromString(domain.guid()))); } - // --- application resolution ------------------------------------------------------------------ - private UUID getRequiredApplicationGuid(String applicationName) { - StringBuilder query = new StringBuilder("/v3/apps?per_page=" + DEFAULT_PAGE_SIZE); - query.append("&names=") + StringBuilder query = new StringBuilder( + CloudControllerV3Endpoints.APPS + CloudControllerV3Endpoints.QUERY_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE); + + query.append(CloudControllerV3Endpoints.AMPERSAND_NAMES) .append(applicationName); + if (target != null && target.getGuid() != null) { - query.append("&space_guids=") + query.append(CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) .append(target.getGuid()); } + List apps = cc.list(query.toString(), new ParameterizedTypeReference>() { }); + if (CollectionUtils.isEmpty(apps)) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Application " + applicationName + " not found."); + throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + MessageFormat.format(Messages.APPLICATION_0_NOT_FOUND, applicationName)); } - return UUID.fromString(apps.get(0) + + return UUID.fromString(apps.getFirst() .guid()); } @@ -348,13 +369,11 @@ private UUID getTargetSpaceGuid() { private void assertSpaceProvided(String operation) { if (target == null) { - throw new IllegalArgumentException("Unable to " + operation + " without specifying organization and space to use."); + throw new IllegalArgumentException( + MessageFormat.format(Messages.UNABLE_TO_0_WITHOUT_SPECIFYING_ORGANIZATION_AND_SPACE_TO_USE, operation)); } } - /** - * Minimal wire-model of a CF v3 application resource — only the {@code guid} needed to resolve an application by name. - */ @JsonIgnoreProperties(ignoreUnknown = true) private record V3AppRef(@JsonProperty("guid") String guid) { } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3Operations.java index 25e04c2831..5124d1aec4 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3Operations.java @@ -1,6 +1,7 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest; import java.net.URI; +import java.text.MessageFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -8,10 +9,13 @@ import java.util.Optional; import java.util.UUID; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.cloudfoundry.multiapps.controller.Messages; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBinding; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Application; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceBinding; @@ -21,38 +25,8 @@ import org.springframework.http.ResponseEntity; import org.springframework.util.CollectionUtils; -/** - * CF v3 service credential bindings (application bindings) operations of the in-house {@code CloudControllerRestClient}. - * Reproduces the HTTP shape, filtering, async handling and domain mapping of the OSS {@code CloudControllerRestClientImpl} binding - * methods on top of the shared {@link CloudControllerV3Client} machinery. - * - *

- * Endpoint map (see {@code docs/cf-java-client-migration/02-endpoint-inventory.md}): - *

- *
    - *
  • {@code bindServiceInstance} → {@code POST /v3/service_credential_bindings} (type=app, async job)
  • - *
  • {@code unbindServiceInstance} / {@code deleteServiceBinding} → {@code DELETE /v3/service_credential_bindings/{guid}} (async - * job)
  • - *
  • {@code getServiceBinding} → {@code GET /v3/service_credential_bindings?guids={guid}} (single)
  • - *
  • {@code getServiceAppBindings} → {@code GET /v3/service_credential_bindings?service_instance_guids=…&type=app} (paginated)
  • - *
  • {@code getAppBindings} → {@code GET /v3/service_credential_bindings?app_guids=…} (paginated)
  • - *
  • {@code getServiceBindingsForApplication} → {@code GET /v3/service_credential_bindings?app_guids=…&service_instance_guids=…} - * (paginated)
  • - *
  • {@code getServiceBindingParameters} → {@code GET /v3/service_credential_bindings/{guid}/parameters}
  • - *
  • {@code updateServiceBindingMetadata} → {@code PATCH /v3/service_credential_bindings/{guid}}
  • - *
- * - *

- * As in the OSS impl, the async create/delete operations do not block on the job: they return the job GUID (extracted from the - * {@code Location} header of the 202 response) so the caller can poll it. This matches - * {@code delegate.serviceBindingsV3().create(...).getJobId()} / {@code .delete(...).block()} which yield the job id without waiting. - *

- */ public class ServiceBindingsV3Operations { - // CF v3 caps per_page at 5000; use a large page to minimise round-trips (the pagination walker still handles multiple pages). - private static final int PER_PAGE = 5000; - private static final ParameterizedTypeReference> BINDING_LIST_TYPE = new ParameterizedTypeReference<>() { }; private static final ParameterizedTypeReference> APPLICATION_LIST_TYPE = new ParameterizedTypeReference<>() { @@ -80,22 +54,25 @@ public Optional bindServiceInstance(String bindingName, String applicati body.put("type", "app"); body.put("relationships", Map.of("app", toOneRelationship(applicationGuid), "service_instance", toOneRelationship(serviceInstanceGuid))); + if (!CollectionUtils.isEmpty(parameters)) { body.put("parameters", parameters); } ResponseEntity response = cc.getRestClient() .post() - .uri("/v3/service_credential_bindings") + .uri(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS) .body(body) .retrieve() .toBodilessEntity(); - return extractJobGuid(response); + + return extractJobGuidFromResponseHeader(response); } public List unbindServiceInstance(String applicationName, String serviceInstanceName) { UUID applicationGuid = getRequiredApplicationGuid(applicationName); UUID serviceInstanceGuid = getRequiredServiceInstanceGuid(serviceInstanceName); + return doUnbindServiceInstance(applicationGuid, serviceInstanceGuid); } @@ -108,7 +85,9 @@ public Optional deleteServiceBinding(UUID bindingGuid) { } public CloudServiceBinding getServiceBinding(UUID serviceBindingGuid) { - String uri = "/v3/service_credential_bindings?guids=" + serviceBindingGuid + "&per_page=" + PER_PAGE; + String uri = CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS + CloudControllerV3Endpoints.QUERY_GUIDS + serviceBindingGuid + + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; + return cc.list(uri, BINDING_LIST_TYPE) .stream() .findFirst() @@ -117,24 +96,34 @@ public CloudServiceBinding getServiceBinding(UUID serviceBindingGuid) { } public List getServiceAppBindings(UUID serviceInstanceGuid) { - String uri = "/v3/service_credential_bindings?service_instance_guids=" + serviceInstanceGuid + "&type=app&per_page=" + PER_PAGE; + String uri = CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS + CloudControllerV3Endpoints.QUERY_SERVICE_INSTANCE_GUIDS + + serviceInstanceGuid + CloudControllerV3Endpoints.AMPERSAND_TYPE + "app" + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; + return listBindings(uri); } public List getAppBindings(UUID applicationGuid) { - String uri = "/v3/service_credential_bindings?app_guids=" + applicationGuid + "&per_page=" + PER_PAGE; + String uri = CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS + CloudControllerV3Endpoints.QUERY_APP_GUIDS + applicationGuid + + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; + return listBindings(uri); } public List getServiceBindingsForApplication(UUID applicationId, UUID serviceInstanceGuid) { - String uri = "/v3/service_credential_bindings?app_guids=" + applicationId + "&service_instance_guids=" + serviceInstanceGuid - + "&per_page=" + PER_PAGE; + String uri = CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS + CloudControllerV3Endpoints.QUERY_APP_GUIDS + applicationId + + CloudControllerV3Endpoints.AMPERSAND_SERVICE_INSTANCE_GUIDS + serviceInstanceGuid + + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; + return listBindings(uri); } public Map getServiceBindingParameters(UUID guid) { @SuppressWarnings("unchecked") - Map parameters = cc.get("/v3/service_credential_bindings/" + guid + "/parameters", Map.class); + Map parameters = cc.get(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS + "/" + guid + "/parameters", + Map.class); + return parameters; } @@ -142,9 +131,10 @@ public void updateServiceBindingMetadata(UUID guid, Metadata metadata) { Map metadataBody = new HashMap<>(); metadataBody.put("labels", metadata.getLabels()); metadataBody.put("annotations", metadata.getAnnotations()); + cc.getRestClient() .patch() - .uri("/v3/service_credential_bindings/{guid}", guid) + .uri(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDING_BY_GUID, guid) .body(Map.of("metadata", metadataBody)) .retrieve() .toBodilessEntity(); @@ -157,20 +147,24 @@ private List listBindings(String uri) { .toList(); } - // Mirrors the OSS doUnbindServiceInstance: resolve the app<->service bindings and 404 when none exist, then delete each. private List doUnbindServiceInstance(UUID applicationGuid, UUID serviceInstanceGuid) { List serviceBindingGuids = getServiceBindingGuids(applicationGuid, serviceInstanceGuid); + if (serviceBindingGuids.isEmpty()) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", - "Service binding between service with GUID " + serviceInstanceGuid - + " and application with GUID " + applicationGuid + " not found."); + throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, MessageFormat.format( + Messages.SERVICE_BINDING_BETWEEN_SERVICE_WITH_GUID_0_AND_APPLICATION_WITH_GUID_1_NOT_FOUND, serviceInstanceGuid, + applicationGuid)); } + return doDeleteServiceBindings(serviceBindingGuids); } private List getServiceBindingGuids(UUID applicationGuid, UUID serviceInstanceGuid) { - String uri = "/v3/service_credential_bindings?app_guids=" + applicationGuid + "&service_instance_guids=" + serviceInstanceGuid - + "&per_page=" + PER_PAGE; + String uri = CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS + CloudControllerV3Endpoints.QUERY_APP_GUIDS + applicationGuid + + CloudControllerV3Endpoints.AMPERSAND_SERVICE_INSTANCE_GUIDS + serviceInstanceGuid + + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; + return cc.list(uri, BINDING_LIST_TYPE) .stream() .map(binding -> UUID.fromString(binding.guid())) @@ -180,6 +174,7 @@ private List getServiceBindingGuids(UUID applicationGuid, UUID serviceInst private List doDeleteServiceBindings(List guids) { List jobIds = new ArrayList<>(); List errors = new ArrayList<>(); + for (UUID guid : guids) { try { deleteSingleServiceBinding(guid).ifPresent(jobIds::add); @@ -187,6 +182,7 @@ private List doDeleteServiceBindings(List guids) { errors.add(e); } } + throwOnErrors(errors); return jobIds; } @@ -194,29 +190,33 @@ private List doDeleteServiceBindings(List guids) { private Optional deleteSingleServiceBinding(UUID guid) { ResponseEntity response = cc.getRestClient() .delete() - .uri("/v3/service_credential_bindings/{guid}", guid) + .uri(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDING_BY_GUID, guid) .retrieve() .toBodilessEntity(); - return extractJobGuid(response); + + return extractJobGuidFromResponseHeader(response); } private void throwOnErrors(List errors) { if (errors.isEmpty()) { return; } - CloudOperationException first = errors.get(0); + + CloudOperationException firstException = errors.getFirst(); errors.subList(1, errors.size()) - .forEach(first::addSuppressed); - throw first; + .forEach(firstException::addSuppressed); + + throw firstException; } - // Extract the job GUID from the Location header of a 202-Accepted response (path ends in /v3/jobs/{guid}); empty if synchronous. - private Optional extractJobGuid(ResponseEntity response) { + private Optional extractJobGuidFromResponseHeader(ResponseEntity response) { URI location = response.getHeaders() .getLocation(); + if (location == null) { return Optional.empty(); } + String path = location.getPath(); return Optional.of(path.substring(path.lastIndexOf('/') + 1)); } @@ -225,45 +225,55 @@ private Map toOneRelationship(UUID guid) { return Map.of("data", Map.of("guid", guid.toString())); } - // Mirrors the OSS getRequiredApplicationGuid: look up the app in the target space and 404 if it is absent. private UUID getRequiredApplicationGuid(String applicationName) { - StringBuilder query = new StringBuilder("/v3/apps?per_page=" + PER_PAGE); + StringBuilder query = new StringBuilder( + CloudControllerV3Endpoints.APPS + CloudControllerV3Endpoints.QUERY_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE); + if (target != null && target.getGuid() != null) { - query.append("&space_guids=") + query.append(CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) .append(target.getGuid()); } - query.append("&names=") + + query.append(CloudControllerV3Endpoints.AMPERSAND_NAMES) .append(applicationName); + List apps = cc.list(query.toString(), APPLICATION_LIST_TYPE); + if (apps.isEmpty()) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Application " + applicationName + " not found."); + throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + MessageFormat.format(Messages.APPLICATION_0_NOT_FOUND, applicationName)); } - return UUID.fromString(apps.get(0) + + return UUID.fromString(apps.getFirst() .guid()); } - // Mirrors the OSS getRequiredServiceInstanceGuid: look up the service instance in the target space and 404 if it is absent. private UUID getRequiredServiceInstanceGuid(String name) { - StringBuilder query = new StringBuilder("/v3/service_instances?per_page=" + PER_PAGE); + StringBuilder query = new StringBuilder(CloudControllerV3Endpoints.SERVICE_INSTANCES + CloudControllerV3Endpoints.QUERY_PER_PAGE + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE); + if (target != null && target.getGuid() != null) { - query.append("&space_guids=") + query.append(CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) .append(target.getGuid()); } - query.append("&names=") + + query.append(CloudControllerV3Endpoints.AMPERSAND_NAMES) .append(name); List instances = cc.list(query.toString(), new ParameterizedTypeReference>() { }); + if (instances.isEmpty()) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Service instance " + name + " not found."); + throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + MessageFormat.format(Messages.SERVICE_INSTANCE_0_NOT_FOUND, name)); } - return UUID.fromString(instances.get(0) + + return UUID.fromString(instances.getFirst() .guid()); } - // Minimal wire model to resolve a service instance GUID by name without depending on the (separately-owned) ServiceInstances group. - @com.fasterxml.jackson.annotation.JsonIgnoreProperties(ignoreUnknown = true) - private record V3ServiceInstanceRef(@com.fasterxml.jackson.annotation.JsonProperty("guid") String guid) { + @JsonIgnoreProperties(ignoreUnknown = true) + private record V3ServiceInstanceRef(@JsonProperty("guid") String guid) { } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBrokersV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBrokersV3Operations.java index 8a7acff08a..6b1db34c3e 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBrokersV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBrokersV3Operations.java @@ -1,11 +1,15 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest; import java.net.URI; +import java.text.MessageFormat; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.cloudfoundry.multiapps.controller.Messages; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBroker; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; @@ -18,35 +22,8 @@ import org.springframework.http.ResponseEntity; import org.springframework.util.Assert; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * CF v3 service broker operations of the in-house {@code CloudControllerRestClient}. Reproduces the HTTP shape, filtering, - * async job handling and domain mapping of the OSS {@code CloudControllerRestClientImpl} service-broker methods on top of the shared - * {@link CloudControllerV3Client} machinery. - * - *

- * Endpoint map (see {@code docs/cf-java-client-migration/02-endpoint-inventory.md}): - *

- *
    - *
  • {@code createServiceBroker} → {@code POST /v3/service_brokers} (async job; returns the job GUID)
  • - *
  • {@code deleteServiceBroker} → {@code DELETE /v3/service_brokers/{guid}} (async job; returns the job GUID)
  • - *
  • {@code getServiceBroker(name[, required])} → {@code GET /v3/service_brokers?names=} (paginated, first match)
  • - *
  • {@code getServiceBrokers} → {@code GET /v3/service_brokers} (paginated)
  • - *
  • {@code updateServiceBroker} → {@code PATCH /v3/service_brokers/{guid}} (async job; returns the job GUID or {@code null})
  • - *
  • {@code updateServicePlanVisibilityForBroker} → per plan {@code PATCH /v3/service_plans/{guid}/visibility}
  • - *
- * - *

- * As in the OSS impl, {@code createServiceBroker}/{@code deleteServiceBroker}/{@code updateServiceBroker} return the async job GUID - * without waiting for the job to complete — the caller polls the job separately. - *

- */ public class ServiceBrokersV3Operations { - private static final int PER_PAGE = 5000; - private static final ParameterizedTypeReference> BROKER_PAGE = new ParameterizedTypeReference<>() { }; private static final ParameterizedTypeReference> OFFERING_PAGE = new ParameterizedTypeReference<>() { @@ -69,13 +46,14 @@ public String createServiceBroker(CloudServiceBroker serviceBroker) { body.put("name", serviceBroker.getName()); body.put("url", serviceBroker.getUrl()); body.put("authentication", basicAuthentication(serviceBroker)); + if (serviceBroker.getSpaceGuid() != null) { body.put("relationships", Map.of("space", Map.of("data", Map.of("guid", serviceBroker.getSpaceGuid())))); } ResponseEntity response = cc.getRestClient() .post() - .uri("/v3/service_brokers") + .uri(CloudControllerV3Endpoints.SERVICE_BROKERS) .body(body) .retrieve() .toBodilessEntity(); @@ -86,11 +64,13 @@ public String deleteServiceBroker(String name) { CloudServiceBroker broker = getServiceBroker(name); UUID guid = broker.getMetadata() .getGuid(); + ResponseEntity response = cc.getRestClient() .delete() - .uri("/v3/service_brokers/{guid}", guid.toString()) + .uri(CloudControllerV3Endpoints.SERVICE_BROKER_BY_GUID, guid.toString()) .retrieve() .toBodilessEntity(); + return extractJobGuid(response); } @@ -100,14 +80,18 @@ public CloudServiceBroker getServiceBroker(String name) { public CloudServiceBroker getServiceBroker(String name, boolean required) { CloudServiceBroker serviceBroker = findServiceBrokerByName(name); + if (serviceBroker == null && required) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Service broker " + name + " not found."); + throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + MessageFormat.format(Messages.SERVICE_BROKER_0_NOT_FOUND, name)); } + return serviceBroker; } public List getServiceBrokers() { - return cc.list("/v3/service_brokers?per_page=" + PER_PAGE, BROKER_PAGE) + return cc.list(CloudControllerV3Endpoints.SERVICE_BROKERS + CloudControllerV3Endpoints.QUERY_PER_PAGE + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE, BROKER_PAGE) .stream() .map(V3ServiceBrokerMapper::toCloudServiceBroker) .toList(); @@ -125,11 +109,11 @@ public String updateServiceBroker(CloudServiceBroker serviceBroker) { ResponseEntity response = cc.getRestClient() .patch() - .uri("/v3/service_brokers/{guid}", brokerGuid.toString()) + .uri(CloudControllerV3Endpoints.SERVICE_BROKER_BY_GUID, brokerGuid.toString()) .body(body) .retrieve() .toBodilessEntity(); - // A synchronous update returns 200 with no Location header (no job); an async update returns 202 with a job GUID. + return extractJobGuid(response); } @@ -137,13 +121,17 @@ public void updateServicePlanVisibilityForBroker(String name, ServicePlanVisibil CloudServiceBroker broker = getServiceBroker(name); UUID brokerGuid = broker.getMetadata() .getGuid(); + for (UUID servicePlanGuid : findServicePlanGuidsByBrokerGuid(brokerGuid)) { updateServicePlanVisibility(servicePlanGuid, visibility); } } private CloudServiceBroker findServiceBrokerByName(String name) { - return cc.list("/v3/service_brokers?names=" + name + "&per_page=" + PER_PAGE, BROKER_PAGE) + return cc.list( + CloudControllerV3Endpoints.SERVICE_BROKERS + CloudControllerV3Endpoints.QUERY_NAMES + name + + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE, BROKER_PAGE) .stream() .findFirst() .map(V3ServiceBrokerMapper::toCloudServiceBroker) @@ -158,8 +146,10 @@ private List findServicePlanGuidsByBrokerGuid(UUID brokerGuid) { } private List findServiceOfferingGuidsByBrokerGuid(UUID brokerGuid) { - String uri = "/v3/service_offerings?service_broker_guids=" + brokerGuid + "&space_guids=" + getTargetSpaceGuid() + "&per_page=" - + PER_PAGE; + String uri = CloudControllerV3Endpoints.SERVICE_OFFERINGS + CloudControllerV3Endpoints.QUERY_SERVICE_BROKER_GUIDS + brokerGuid + + CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS + getTargetSpaceGuid() + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; + return cc.list(uri, OFFERING_PAGE) .stream() .map(ServiceOfferingRef::guid) @@ -168,7 +158,10 @@ private List findServiceOfferingGuidsByBrokerGuid(UUID brokerGuid) { } private List findServicePlanGuidsByOfferingGuid(UUID serviceOfferingGuid) { - String uri = "/v3/service_plans?service_offering_guids=" + serviceOfferingGuid + "&per_page=" + PER_PAGE; + String uri = + CloudControllerV3Endpoints.SERVICE_PLANS + CloudControllerV3Endpoints.QUERY_SERVICE_OFFERING_GUIDS + serviceOfferingGuid + + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; + return cc.list(uri, PLAN_PAGE) .stream() .map(ServicePlanRef::guid) @@ -177,11 +170,9 @@ private List findServicePlanGuidsByOfferingGuid(UUID serviceOfferingGuid) } private void updateServicePlanVisibility(UUID servicePlanGuid, ServicePlanVisibility visibility) { - // ServicePlanVisibility.toString() yields the lowercase CF visibility type (public/admin/organization), - // matching the OSS Visibility.from(visibility.toString()) mapping. cc.getRestClient() .patch() - .uri("/v3/service_plans/{guid}/visibility", servicePlanGuid.toString()) + .uri(CloudControllerV3Endpoints.SERVICE_PLAN_VISIBILITY, servicePlanGuid.toString()) .body(Map.of("type", visibility.toString())) .retrieve() .toBodilessEntity(); @@ -202,19 +193,22 @@ private String getTargetSpaceGuid() { .toString(); } - // A 202-Accepted async response carries the job in its Location header (/v3/jobs/{guid}); a synchronous 200 has none. private static String extractJobGuid(ResponseEntity response) { URI location = response.getHeaders() .getLocation(); + if (location == null) { return null; } + String value = location.toString(); - int idx = value.lastIndexOf("/v3/jobs/"); - if (idx < 0) { + int index = value.lastIndexOf("/v3/jobs/"); + + if (index < 0) { return value.substring(value.lastIndexOf('/') + 1); } - return value.substring(idx + "/v3/jobs/".length()); + + return value.substring(index + "/v3/jobs/".length()); } @JsonIgnoreProperties(ignoreUnknown = true) diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3Operations.java index 0bea35bc76..e004dcc6be 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3Operations.java @@ -9,12 +9,13 @@ import java.util.UUID; import java.util.stream.Collectors; +import org.cloudfoundry.multiapps.controller.Constants; +import org.cloudfoundry.multiapps.controller.Messages; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; -import org.cloudfoundry.multiapps.controller.client.facade.Messages; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEntity; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceInstance; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceInstanceMapper; @@ -24,42 +25,8 @@ import org.springframework.http.HttpStatus; import org.springframework.util.Assert; -/** - * CF v3 service instance operations of the in-house {@code CloudControllerRestClient}. Reproduces the HTTP shape, filtering, - * space-scoping and domain-mapping of the OSS {@code CloudControllerRestClientImpl} service-instance methods on top of the shared - * {@link CloudControllerV3Client} machinery. - * - *

- * Endpoint map (see {@code docs/cf-java-client-migration/02-endpoint-inventory.md}): - *

- *
    - *
  • {@code createServiceInstance} → {@code POST /v3/service_instances} (type=managed)
  • - *
  • {@code createUserProvidedServiceInstance} → {@code POST /v3/service_instances} (type=user-provided)
  • - *
  • {@code deleteServiceInstance} → {@code DELETE /v3/service_instances/{guid}}
  • - *
  • {@code getServiceInstance} / {@code getRequiredServiceInstanceGuid} / {@code getServiceInstanceWithoutAuxiliaryContent} → - * {@code GET /v3/service_instances?space_guids=…&names=…} (paginated)
  • - *
  • {@code getServiceInstanceName} → {@code GET /v3/service_instances/{guid}}
  • - *
  • {@code getServiceInstanceParameters} → {@code GET /v3/service_instances/{guid}/parameters}
  • - *
  • {@code getUserProvidedServiceInstanceParameters} → {@code GET /v3/service_instances/{guid}/credentials}
  • - *
  • {@code getServiceInstancesWithoutAuxiliaryContentByNames} → {@code GET /v3/service_instances?space_guids=…&names=…} (batched)
  • - *
  • {@code getServiceInstancesByMetadataLabelSelector} (+ WithoutAuxiliaryContent) → - * {@code GET /v3/service_instances?space_guids=…&label_selector=…} (paginated)
  • - *
  • {@code updateServicePlan} / {@code updateServiceParameters} / {@code updateServiceTags} / {@code updateServiceSyslogDrainUrl} / - * {@code updateServiceInstanceMetadata} → {@code PATCH /v3/service_instances/{guid}}
  • - *
- * - *

- * Async note: for managed instances CF returns 202 for create/update/delete, but the OSS impl does not follow the job — it returns - * as soon as the request is accepted. This helper preserves that behaviour (no {@code followAsyncJob} on these calls). - *

- */ public class ServiceInstancesV3Operations { - // CF v3 caps per_page at 5000; use a large page to minimise round-trips (the pagination walker still handles multiple pages). - private static final int PER_PAGE = 5000; - // Mirrors the OSS MAX_CHAR_LENGTH_FOR_PARAMS_IN_REQUEST used to batch the "byNames" lookups. - private static final int MAX_CHAR_LENGTH_FOR_PARAMS_IN_REQUEST = 4000; - private static final ParameterizedTypeReference> SERVICE_INSTANCE_LIST_TYPE = new ParameterizedTypeReference<>() { }; @@ -87,7 +54,7 @@ public void createServiceInstance(CloudServiceInstance serviceInstance) { cc.getRestClient() .post() - .uri("/v3/service_instances") + .uri(CloudControllerV3Endpoints.SERVICE_INSTANCES) .body(body) .retrieve() .toBodilessEntity(); @@ -109,7 +76,7 @@ public void createUserProvidedServiceInstance(CloudServiceInstance serviceInstan cc.getRestClient() .post() - .uri("/v3/service_instances") + .uri(CloudControllerV3Endpoints.SERVICE_INSTANCES) .body(body) .retrieve() .toBodilessEntity(); @@ -126,9 +93,12 @@ public void deleteServiceInstance(CloudServiceInstance serviceInstance) { public UUID getRequiredServiceInstanceGuid(String name) { V3ServiceInstance resource = findServiceInstanceResourceByName(name); + if (resource == null) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Service instance " + name + " not found."); + throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + MessageFormat.format(Messages.SERVICE_INSTANCE_0_NOT_FOUND, name)); } + return UUID.fromString(resource.guid()); } @@ -138,11 +108,14 @@ public CloudServiceInstance getServiceInstance(String serviceInstanceName) { public CloudServiceInstance getServiceInstance(String serviceInstanceName, boolean required) { CloudServiceInstance serviceInstance = findServiceInstanceByName(serviceInstanceName); + return getServiceInstanceIfRequired(serviceInstanceName, serviceInstance, required); } public String getServiceInstanceName(UUID serviceInstanceGuid) { - V3ServiceInstance serviceInstance = cc.get("/v3/service_instances/" + serviceInstanceGuid, V3ServiceInstance.class); + V3ServiceInstance serviceInstance = cc.get(CloudControllerV3Endpoints.SERVICE_INSTANCES + "/" + serviceInstanceGuid, + V3ServiceInstance.class); + return serviceInstance == null ? null : serviceInstance.name(); } @@ -152,43 +125,51 @@ public CloudServiceInstance getServiceInstanceWithoutAuxiliaryContent(String ser public CloudServiceInstance getServiceInstanceWithoutAuxiliaryContent(String serviceInstanceName, boolean required) { V3ServiceInstance resource = findServiceInstanceResourceByName(serviceInstanceName); + CloudServiceInstance serviceInstance = resource == null ? null : V3ServiceInstanceMapper.toCloudServiceInstance(resource, null, null); + return getServiceInstanceIfRequired(serviceInstanceName, serviceInstance, required); } public Map getServiceInstanceParameters(UUID guid) { - return getParameters("/v3/service_instances/" + guid + "/parameters"); + return getParameters(CloudControllerV3Endpoints.SERVICE_INSTANCES + "/" + guid + "/parameters"); } public Map getUserProvidedServiceInstanceParameters(UUID guid) { - return getParameters("/v3/service_instances/" + guid + "/credentials"); + return getParameters(CloudControllerV3Endpoints.SERVICE_INSTANCES + "/" + guid + "/credentials"); } public List getServiceInstancesWithoutAuxiliaryContentByNames(List names) { List allServiceInstances = new ArrayList<>(); - for (List batch : toBatches(names, MAX_CHAR_LENGTH_FOR_PARAMS_IN_REQUEST)) { - String uri = "/v3/service_instances?per_page=" + PER_PAGE + "&space_guids=" + getTargetSpaceGuid() + "&names=" - + String.join(",", batch); + + for (List batch : toBatches(names, Constants.MAX_CHAR_LENGTH_FOR_PARAMS_IN_REQUEST)) { + String uri = CloudControllerV3Endpoints.SERVICE_INSTANCES + CloudControllerV3Endpoints.QUERY_PER_PAGE + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE + CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS + getTargetSpaceGuid() + + CloudControllerV3Endpoints.AMPERSAND_NAMES + String.join(",", batch); + cc.list(uri, SERVICE_INSTANCE_LIST_TYPE) .stream() .map(V3ServiceInstanceMapper::toCloudServiceInstanceWithoutAuxiliaryContent) .forEach(allServiceInstances::add); } + return allServiceInstances; } public List getServiceInstancesByMetadataLabelSelector(String labelSelector) { - String uri = "/v3/service_instances?per_page=" + PER_PAGE + "&space_guids=" + getTargetSpaceGuid() + "&label_selector=" - + labelSelector; - // Each managed instance needs its plan + offering resolved via extra GETs (mapWithAuxiliaryContent). The OSS client ran this - // enrichment concurrently across the list; preserve that so a large label-selector result doesn't serialize the per-instance calls. - return ReactiveFanOut.mapConcurrently(cc.list(uri, SERVICE_INSTANCE_LIST_TYPE), this::mapWithAuxiliaryContent); + String uri = CloudControllerV3Endpoints.SERVICE_INSTANCES + CloudControllerV3Endpoints.QUERY_PER_PAGE + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE + CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS + getTargetSpaceGuid() + + CloudControllerV3Endpoints.AMPERSAND_LABEL_SELECTOR + labelSelector; + + return ReactiveFanOut.mapConcurrently(cc.list(uri, SERVICE_INSTANCE_LIST_TYPE), this::mapWithSupportingContent); } public List getServiceInstancesWithoutAuxiliaryContentByMetadataLabelSelector(String labelSelector) { - String uri = "/v3/service_instances?per_page=" + PER_PAGE + "&space_guids=" + getTargetSpaceGuid() + "&label_selector=" - + labelSelector; + String uri = CloudControllerV3Endpoints.SERVICE_INSTANCES + CloudControllerV3Endpoints.QUERY_PER_PAGE + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE + CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS + getTargetSpaceGuid() + + CloudControllerV3Endpoints.AMPERSAND_LABEL_SELECTOR + labelSelector; + return cc.list(uri, SERVICE_INSTANCE_LIST_TYPE) .stream() .map(resource -> V3ServiceInstanceMapper.toCloudServiceInstance(resource, null, null)) @@ -197,9 +178,11 @@ public List getServiceInstancesWithoutAuxiliaryContentByMe public void updateServicePlan(String serviceName, String planName) { CloudServiceInstance service = getServiceInstance(serviceName); + if (service.isUserProvided()) { return; } + UUID planGuid = findPlanGuidForService(service, planName); patchServiceInstance(service.getGuid(), Map.of("relationships", Map.of("service_plan", toOneRelationship(planGuid.toString())))); } @@ -217,9 +200,11 @@ public void updateServiceTags(String serviceName, List tags) { public void updateServiceSyslogDrainUrl(String serviceName, String syslogDrainUrl) { CloudServiceInstance service = getServiceInstanceWithoutAuxiliaryContent(serviceName); + if (!service.isUserProvided()) { return; } + String updatedSyslogDrain = hasText(syslogDrainUrl) ? syslogDrainUrl : ""; patchServiceInstance(service.getGuid(), Map.of("syslog_drain_url", updatedSyslogDrain)); } @@ -232,35 +217,42 @@ public void updateServiceInstanceMetadata(UUID guid, Metadata metadata) { private CloudServiceInstance getServiceInstanceIfRequired(String serviceInstanceName, CloudServiceInstance serviceInstance, boolean required) { if (serviceInstance == null && required) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Service instance " + serviceInstanceName + " not found."); + throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + MessageFormat.format(Messages.SERVICE_INSTANCE_0_NOT_FOUND, serviceInstanceName)); } + return serviceInstance; } - // Mirrors the OSS findServiceInstanceByName -> zipWithAuxiliaryServiceInstanceContent: for managed instances the plan and offering - // names are resolved and included; user-provided instances are mapped as-is. private CloudServiceInstance findServiceInstanceByName(String name) { V3ServiceInstance resource = findServiceInstanceResourceByName(name); + if (resource == null) { return null; } - return mapWithAuxiliaryContent(resource); + + return mapWithSupportingContent(resource); } - private CloudServiceInstance mapWithAuxiliaryContent(V3ServiceInstance resource) { + private CloudServiceInstance mapWithSupportingContent(V3ServiceInstance resource) { if (isUserProvided(resource)) { return V3ServiceInstanceMapper.toCloudServiceInstance(resource, null, null); } + String servicePlanGuid = servicePlanGuidOf(resource); if (servicePlanGuid == null) { return V3ServiceInstanceMapper.toCloudServiceInstance(resource, null, null); } + ServicePlanNames names = resolvePlanAndOfferingNames(servicePlanGuid, resource.name()); return V3ServiceInstanceMapper.toCloudServiceInstance(resource, names.planName(), names.offeringName()); } private V3ServiceInstance findServiceInstanceResourceByName(String name) { - String uri = "/v3/service_instances?per_page=" + PER_PAGE + "&space_guids=" + getTargetSpaceGuid() + "&names=" + name; + String uri = CloudControllerV3Endpoints.SERVICE_INSTANCES + CloudControllerV3Endpoints.QUERY_PER_PAGE + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE + CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS + getTargetSpaceGuid() + + CloudControllerV3Endpoints.AMPERSAND_NAMES + name; + return cc.list(uri, SERVICE_INSTANCE_LIST_TYPE) .stream() .findFirst() @@ -279,62 +271,70 @@ private String servicePlanGuidOf(V3ServiceInstance resource) { .data() == null) { return null; } + return resource.relationships() .servicePlan() .data() .guid(); } - // Mirrors the OSS getServicePlanResource + getServiceOffering zip: resolve the plan's own name and its offering's name. Reproduces the - // OSS friendly-error mapping too — a 403/404 on the plan or offering lookup becomes a specific, human-readable CloudOperationException - // (instead of the generic one) so operators get the same messages as before. private ServicePlanNames resolvePlanAndOfferingNames(String servicePlanGuid, String serviceInstanceName) { V3ServicePlan plan = getServicePlanForNameResolution(servicePlanGuid, serviceInstanceName); + if (plan == null) { return new ServicePlanNames(null, null); } + String offeringName = null; String offeringGuid = offeringGuidOf(plan); + if (offeringGuid != null) { V3ServiceOffering offering = getServiceOfferingForNameResolution(offeringGuid); offeringName = offering == null ? null : offering.name(); } + return new ServicePlanNames(plan.name(), offeringName); } private V3ServicePlan getServicePlanForNameResolution(String servicePlanGuid, String serviceInstanceName) { try { - return cc.get("/v3/service_plans/" + servicePlanGuid, V3ServicePlan.class); + return cc.get(CloudControllerV3Endpoints.SERVICE_PLANS + "/" + servicePlanGuid, V3ServicePlan.class); } catch (CloudOperationException e) { if (e.getStatusCode() == HttpStatus.FORBIDDEN) { - throw new CloudOperationException(HttpStatus.FORBIDDEN, HttpStatus.FORBIDDEN.getReasonPhrase(), - MessageFormat.format(Messages.SERVICE_PLAN_WITH_GUID_0_NOT_AVAILABLE_FOR_SERVICE_INSTANCE_1, - servicePlanGuid, serviceInstanceName), + throw new CloudOperationException(HttpStatus.FORBIDDEN, Messages.FORBIDDEN, + MessageFormat.format( + Messages.SERVICE_PLAN_WITH_GUID_0_NOT_AVAILABLE_FOR_SERVICE_INSTANCE_1, + servicePlanGuid, serviceInstanceName), e); } + if (e.getStatusCode() == HttpStatus.NOT_FOUND) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), + throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, MessageFormat.format(Messages.NO_SERVICE_PLAN_FOUND, servicePlanGuid, serviceInstanceName), e); } + throw e; } } private V3ServiceOffering getServiceOfferingForNameResolution(String offeringGuid) { try { - return cc.get("/v3/service_offerings/" + offeringGuid, V3ServiceOffering.class); + return cc.get(CloudControllerV3Endpoints.SERVICE_OFFERINGS + "/" + offeringGuid, V3ServiceOffering.class); } catch (CloudOperationException e) { if (e.getStatusCode() == HttpStatus.FORBIDDEN) { - throw new CloudOperationException(HttpStatus.FORBIDDEN, HttpStatus.FORBIDDEN.getReasonPhrase(), - MessageFormat.format(Messages.SERVICE_OFFERING_WITH_GUID_0_IS_NOT_AVAILABLE, offeringGuid), + throw new CloudOperationException(HttpStatus.FORBIDDEN, Messages.FORBIDDEN, + MessageFormat.format(Messages.SERVICE_OFFERING_WITH_GUID_0_IS_NOT_AVAILABLE, + offeringGuid), e); } + if (e.getStatusCode() == HttpStatus.NOT_FOUND) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), + throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, MessageFormat.format(Messages.SERVICE_OFFERING_WITH_GUID_0_NOT_FOUND, offeringGuid), e); } + throw e; } } @@ -347,61 +347,73 @@ private String offeringGuidOf(V3ServicePlan plan) { .data() == null) { return null; } + return plan.relationships() .serviceOffering() .data() .guid(); } - // Mirrors the OSS findPlanForService: list the offerings for the service's label (optionally scoped by broker), then find the plan by - // name; a missing plan throws CloudOperationException(NOT_FOUND). private UUID findPlanGuidForService(CloudServiceInstance service, String planName) { List offeringGuids = findServiceOfferingGuids(service.getLabel(), service.getBroker()); + for (String offeringGuid : offeringGuids) { - String uri = "/v3/service_plans?per_page=" + PER_PAGE + "&service_offering_guids=" + offeringGuid + "&names=" + planName; + String uri = CloudControllerV3Endpoints.SERVICE_PLANS + CloudControllerV3Endpoints.QUERY_PER_PAGE + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE + CloudControllerV3Endpoints.AMPERSAND_SERVICE_OFFERING_GUIDS + offeringGuid + + CloudControllerV3Endpoints.AMPERSAND_NAMES + planName; + V3ServicePlan plan = cc.list(uri, new ParameterizedTypeReference>() { - }) + }) .stream() .findFirst() .orElse(null); + if (plan != null) { return UUID.fromString(plan.guid()); } } - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Service plan " + planName + " not found."); + + throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + MessageFormat.format(Messages.SERVICE_PLAN_0_NOT_FOUND, planName)); } private List findServiceOfferingGuids(String label, String broker) { - StringBuilder uri = new StringBuilder("/v3/service_offerings?per_page=").append(PER_PAGE) - .append("&space_guids=") - .append(getTargetSpaceGuid()) - .append("&names=") - .append(label); + StringBuilder uri = new StringBuilder( + CloudControllerV3Endpoints.SERVICE_OFFERINGS + CloudControllerV3Endpoints.QUERY_PER_PAGE).append( + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .append( + CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) + .append( + getTargetSpaceGuid()) + .append( + CloudControllerV3Endpoints.AMPERSAND_NAMES) + .append( + label); + if (hasText(broker)) { - uri.append("&service_broker_names=") + uri.append(CloudControllerV3Endpoints.AMPERSAND_SERVICE_BROKER_NAMES) .append(broker); } + return cc.list(uri.toString(), new ParameterizedTypeReference>() { - }) + }) .stream() .map(V3ServiceOffering::guid) .collect(Collectors.toList()); } private void doDeleteServiceInstance(UUID serviceInstanceGuid) { - // OSS parity: the delete is issued and the response consumed, but the async job is NOT followed. cc.getRestClient() .delete() - .uri("/v3/service_instances/{guid}", serviceInstanceGuid.toString()) + .uri(CloudControllerV3Endpoints.SERVICE_INSTANCE_BY_GUID, serviceInstanceGuid.toString()) .retrieve() .toBodilessEntity(); } private void patchServiceInstance(UUID guid, Map body) { - // OSS parity: the update is issued and the response consumed, but the async job is NOT followed. cc.getRestClient() .patch() - .uri("/v3/service_instances/{guid}", guid.toString()) + .uri(CloudControllerV3Endpoints.SERVICE_INSTANCE_BY_GUID, guid.toString()) .body(body) .retrieve() .toBodilessEntity(); @@ -420,10 +432,12 @@ private Map toMetadataMap(Metadata metadata) { if (metadata == null) { return null; } - Map map = new HashMap<>(); - map.put("labels", metadata.getLabels() == null ? Map.of() : metadata.getLabels()); - map.put("annotations", metadata.getAnnotations() == null ? Map.of() : metadata.getAnnotations()); - return map; + + Map metadataMapResult = new HashMap<>(); + metadataMapResult.put("labels", metadata.getLabels() == null ? Map.of() : metadata.getLabels()); + metadataMapResult.put("annotations", metadata.getAnnotations() == null ? Map.of() : metadata.getAnnotations()); + + return metadataMapResult; } private static void putIfNotNull(Map body, String key, Object value) { @@ -436,27 +450,31 @@ private static boolean hasText(String value) { return value != null && !value.isBlank(); } - // Mirrors the OSS toBatches: split a large collection into batches bounded by the total character length of the elements. private List> toBatches(Collection largeList, int maxCharLength) { if (largeList.isEmpty()) { return List.of(); } + List> batches = new ArrayList<>(); int currentBatchLength = 0; int currentBatchIndex = 0; batches.add(new ArrayList<>()); + for (T element : largeList) { int elementLength = element.toString() .length(); + if (elementLength + currentBatchLength >= maxCharLength) { batches.add(new ArrayList<>()); currentBatchIndex++; currentBatchLength = 0; } + batches.get(currentBatchIndex) .add(element); currentBatchLength += elementLength; } + return batches; } @@ -468,6 +486,7 @@ private UUID getGuid(CloudEntity entity) { if (entity == null || entity.getMetadata() == null) { return null; } + return entity.getMetadata() .getGuid(); } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3Operations.java index e6e422acfe..b85c8b7aef 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3Operations.java @@ -1,7 +1,6 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest; import java.net.URI; -import java.time.Duration; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -9,12 +8,12 @@ import java.util.Optional; import java.util.UUID; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType; +import org.cloudfoundry.multiapps.controller.Constants; import org.cloudfoundry.multiapps.controller.client.facade.Messages; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceBinding; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceKeyDetails; @@ -22,72 +21,51 @@ import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.ResponseEntity; -/** - * CF v3 service key operations for the cf-java-client replacement. A service key is a service credential binding of - * {@code type=key}, so all endpoints target {@code /v3/service_credential_bindings}. Reproduces the HTTP shape and domain-mapping of the - * OSS {@code CloudControllerRestClientImpl} service-key methods: - *
    - *
  • create → {@code POST /v3/service_credential_bindings} (type=key) — returns a job (202);
  • - *
  • list → {@code GET /v3/service_credential_bindings?type=key&service_instance_guids={guid}[&names={name}]} (paginated);
  • - *
  • credentials → {@code GET /v3/service_credential_bindings/{guid}/details}.
  • - *
- * - *

- * The interface methods that accept a {@code serviceInstanceName} require the owning {@link CloudServiceInstance} (for its GUID and to - * embed in the resulting {@link CloudServiceKey}). Mirroring the OSS impl — which first calls {@code getServiceInstance(name)} - * — these operations take the already-resolved {@link CloudServiceInstance}; the integrator resolves it in - * {@code CloudControllerRestClientV3Impl} before delegating. - *

- */ public class ServiceKeysV3Operations { - // CF v3 caps per_page at 5000; use a large page to minimise round-trips (the pagination walker still handles multiple pages). - private static final int DEFAULT_PAGE_SIZE = 5000; - // Mirrors the OSS BINDING_OPERATIONS_TIMEOUT used when synchronously creating (and fetching) a service key. - private static final Duration BINDING_OPERATIONS_TIMEOUT = Duration.ofMinutes(5); - private final CloudControllerV3Client cc; - private final CloudSpace target; - public ServiceKeysV3Operations(CloudControllerV3Client cc, CloudSpace target) { + public ServiceKeysV3Operations(CloudControllerV3Client cc) { this.cc = cc; - this.target = target; } - // --- create ----------------------------------------------------------------------------------------------------------------- - public CloudServiceKey createAndFetchServiceKey(CloudServiceKey keyModel, CloudServiceInstance serviceInstance) { - ResponseEntity accepted = postServiceKey(keyModel.getName(), keyModel.getCredentials(), keyModel.getV3Metadata(), - serviceInstance); - cc.followAsyncJob(accepted, BINDING_OPERATIONS_TIMEOUT); - V3ServiceBinding created = getServiceKeyResourceByNameAndServiceInstanceGuid(keyModel.getName(), serviceInstance.getGuid()); - if (created == null) { + ResponseEntity acceptedResponse = postServiceKey(keyModel.getName(), keyModel.getCredentials(), keyModel.getV3Metadata(), + serviceInstance); + + cc.followAsyncJob(acceptedResponse, Constants.BINDING_OPERATIONS_TIMEOUT); + + V3ServiceBinding createdServiceKey = getServiceKeyResourceByNameAndServiceInstanceGuid(keyModel.getName(), + serviceInstance.getGuid()); + if (createdServiceKey == null) { return null; } - Map credentials = getServiceKeyCredentials(created.guid()); - return V3ServiceKeyMapper.toCloudServiceKey(created, serviceInstance, credentials); + + Map credentials = getServiceKeyCredentials(createdServiceKey.guid()); + return V3ServiceKeyMapper.toCloudServiceKey(createdServiceKey, serviceInstance, credentials); } public Optional createServiceKey(CloudServiceKey keyModel, CloudServiceInstance serviceInstance) { - ResponseEntity accepted = postServiceKey(keyModel.getName(), keyModel.getCredentials(), keyModel.getV3Metadata(), - serviceInstance); - return extractJobGuid(accepted); + ResponseEntity acceptedResponse = postServiceKey(keyModel.getName(), keyModel.getCredentials(), keyModel.getV3Metadata(), + serviceInstance); + + return extractJobGuid(acceptedResponse); } public Optional createServiceKey(CloudServiceInstance serviceInstance, String serviceKeyName, Map parameters) { - ResponseEntity accepted = postServiceKey(serviceKeyName, parameters, null, serviceInstance); - return extractJobGuid(accepted); - } + ResponseEntity acceptedResponse = postServiceKey(serviceKeyName, parameters, null, serviceInstance); - // --- read ------------------------------------------------------------------------------------------------------------------- + return extractJobGuid(acceptedResponse); + } public CloudServiceKey getServiceKey(CloudServiceInstance serviceInstance, String serviceKeyName) { - V3ServiceBinding key = getServiceKeyResourceByNameAndServiceInstanceGuid(serviceKeyName, serviceInstance.getGuid()); - if (key == null) { + V3ServiceBinding serviceKey = getServiceKeyResourceByNameAndServiceInstanceGuid(serviceKeyName, serviceInstance.getGuid()); + if (serviceKey == null) { return null; } - Map credentials = getServiceKeyCredentials(key.guid()); - return V3ServiceKeyMapper.toCloudServiceKey(key, serviceInstance, credentials); + + Map credentials = getServiceKeyCredentials(serviceKey.guid()); + return V3ServiceKeyMapper.toCloudServiceKey(serviceKey, serviceInstance, credentials); } public List getServiceKeys(CloudServiceInstance serviceInstance) { @@ -97,24 +75,21 @@ public List getServiceKeys(CloudServiceInstance serviceInstance } public List getServiceKeysWithCredentials(CloudServiceInstance serviceInstance) { - // Each key needs a separate GET /details for its credentials. The OSS client fetched these concurrently (reactive flatMap); do the - // same here so a service with many keys doesn't serialize N credential round-trips. return ReactiveFanOut.mapConcurrently(listServiceKeyResources(serviceInstance.getGuid()), key -> V3ServiceKeyMapper.toCloudServiceKey(key, serviceInstance, getServiceKeyCredentials(key.guid()))); } - // --- helpers ---------------------------------------------------------------------------------------------------------------- - private ResponseEntity postServiceKey(String name, Map parameters, Metadata metadata, CloudServiceInstance serviceInstance) { if (serviceInstance.getType() != ServiceInstanceType.MANAGED) { throw new IllegalArgumentException( String.format(Messages.CANT_CREATE_SERVICE_KEY_FOR_USER_PROVIDED_SERVICE, serviceInstance.getName())); } + return cc.getRestClient() .post() - .uri("/v3/service_credential_bindings") + .uri(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS) .body(buildCreateServiceKeyBody(name, parameters, metadata, serviceInstance.getGuid())) .retrieve() .toBodilessEntity(); @@ -122,53 +97,66 @@ private ResponseEntity postServiceKey(String name, Map par private Map buildCreateServiceKeyBody(String name, Map parameters, Metadata metadata, UUID serviceInstanceGuid) { - Map body = new HashMap<>(); - body.put("type", "key"); - body.put("name", name); - body.put("relationships", Map.of("service_instance", Map.of("data", Map.of("guid", serviceInstanceGuid.toString())))); + Map resultBody = new HashMap<>(); + resultBody.put("type", "key"); + resultBody.put("name", name); + resultBody.put("relationships", Map.of("service_instance", Map.of("data", Map.of("guid", serviceInstanceGuid.toString())))); + if (parameters != null && !parameters.isEmpty()) { - body.put("parameters", parameters); + resultBody.put("parameters", parameters); } + Map metadataBody = buildMetadataBody(metadata); if (metadataBody != null) { - body.put("metadata", metadataBody); + resultBody.put("metadata", metadataBody); } - return body; + + return resultBody; } private Map buildMetadataBody(Metadata metadata) { if (metadata == null) { return null; } + Map metadataBody = new HashMap<>(); if (metadata.getLabels() != null) { metadataBody.put("labels", metadata.getLabels()); } + if (metadata.getAnnotations() != null) { metadataBody.put("annotations", metadata.getAnnotations()); } + return metadataBody.isEmpty() ? null : metadataBody; } private V3ServiceBinding getServiceKeyResourceByNameAndServiceInstanceGuid(String name, UUID serviceInstanceGuid) { - String query = "/v3/service_credential_bindings?per_page=" + DEFAULT_PAGE_SIZE + "&type=key&service_instance_guids=" - + serviceInstanceGuid + "&names=" + name; + String query = CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS + CloudControllerV3Endpoints.QUERY_PER_PAGE + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE + CloudControllerV3Endpoints.AMPERSAND_TYPE + "key" + + CloudControllerV3Endpoints.AMPERSAND_SERVICE_INSTANCE_GUIDS + serviceInstanceGuid + CloudControllerV3Endpoints.AMPERSAND_NAMES + + name; + List keys = cc.list(query, new ParameterizedTypeReference>() { }); - return keys.isEmpty() ? null : keys.get(0); + + return keys.isEmpty() ? null : keys.getFirst(); } private List listServiceKeyResources(UUID serviceInstanceGuid) { - String query = "/v3/service_credential_bindings?per_page=" + DEFAULT_PAGE_SIZE + "&type=key&service_instance_guids=" - + serviceInstanceGuid; + String query = CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS + CloudControllerV3Endpoints.QUERY_PER_PAGE + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE + CloudControllerV3Endpoints.AMPERSAND_TYPE + "key" + + CloudControllerV3Endpoints.AMPERSAND_SERVICE_INSTANCE_GUIDS + serviceInstanceGuid; + return cc.list(query, new ParameterizedTypeReference>() { }); } - // CF v3 returns 404 when fetching the details of a service key whose creation failed; treat that as empty credentials, matching the OSS impl. private Map getServiceKeyCredentials(String keyGuid) { - Optional details = cc.getOptional("/v3/service_credential_bindings/" + keyGuid + "/details", + Optional details = cc.getOptional(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS + "/" + keyGuid + + "/details", V3ServiceKeyDetails.class); + return details.map(V3ServiceKeyDetails::credentials) .orElse(Collections.emptyMap()); } @@ -176,12 +164,15 @@ private Map getServiceKeyCredentials(String keyGuid) { private Optional extractJobGuid(ResponseEntity accepted) { URI location = accepted.getHeaders() .getLocation(); + if (location == null) { return Optional.empty(); } + String href = location.toString(); - int idx = href.lastIndexOf("/v3/jobs/"); - String jobGuid = idx < 0 ? href.substring(href.lastIndexOf('/') + 1) : href.substring(idx + "/v3/jobs/".length()); + int index = href.lastIndexOf("/v3/jobs/"); + String jobGuid = index < 0 ? href.substring(href.lastIndexOf('/') + 1) : href.substring(index + "/v3/jobs/".length()); + return Optional.of(jobGuid); } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3Operations.java index 9f5672110d..8d6cfe3cab 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3Operations.java @@ -12,29 +12,8 @@ import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServicePlanMapper; import org.springframework.core.ParameterizedTypeReference; -/** - * CF v3 service offerings operations of the in-house {@code CloudControllerRestClient}. Reproduces the HTTP shape, filtering and - * per-offering plan fan-out of the OSS {@code CloudControllerRestClientImpl#getServiceOfferings} on top of the shared - * {@link CloudControllerV3Client} machinery. - * - *

- * Endpoint map (see {@code docs/cf-java-client-migration/02-endpoint-inventory.md}): - *

- *
    - *
  • list offerings → {@code GET /v3/service_offerings?space_guids=} (paginated);
  • - *
  • resolve each offering's plans → {@code GET /v3/service_plans?service_offering_guids=} (paginated).
  • - *
- * - *

- * This mirrors the OSS {@code getServiceResources()} + {@code zipWithAuxiliaryServiceOfferingContent(...)} flow: the offerings are scoped - * to the target space, and each offering is enriched with the plans belonging to it before being mapped to the domain object. - *

- */ public class ServiceOfferingsV3Operations { - // CF v3 caps per_page at 5000; use a large page to minimise round-trips (the pagination walker still handles multiple pages). - private static final int PER_PAGE = 5000; - private static final ParameterizedTypeReference> OFFERING_LIST_TYPE = new ParameterizedTypeReference<>() { }; private static final ParameterizedTypeReference> PLAN_LIST_TYPE = new ParameterizedTypeReference<>() { @@ -54,25 +33,29 @@ public List getServiceOfferings() { .toList(); } - // Mirrors the OSS getServiceResources(): list the target space's offerings (space_guids filter), walking all pages. private List getServiceOfferingResources() { - StringBuilder query = new StringBuilder("/v3/service_offerings?per_page=" + PER_PAGE); + StringBuilder query = new StringBuilder(CloudControllerV3Endpoints.SERVICE_OFFERINGS + CloudControllerV3Endpoints.QUERY_PER_PAGE + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE); + if (target != null && target.getGuid() != null) { - query.append("&space_guids=") + query.append(CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) .append(target.getGuid()); } + return cc.list(query.toString(), OFFERING_LIST_TYPE); } - // Mirrors the OSS zipWithAuxiliaryServiceOfferingContent(...): fetch the offering's plans, then map to the domain object. private CloudServiceOffering toCloudServiceOfferingWithPlans(V3ServiceOffering serviceOffering) { List servicePlans = getServicePlans(serviceOffering.guid()); + return V3ServiceOfferingMapper.toCloudServiceOffering(serviceOffering, servicePlans); } - // Mirrors the OSS getServicePlanResourcesByServiceOfferingGuid(...): list the plans belonging to the given offering, walking all pages. private List getServicePlans(String serviceOfferingGuid) { - String uri = "/v3/service_plans?service_offering_guids=" + serviceOfferingGuid + "&per_page=" + PER_PAGE; + String uri = + CloudControllerV3Endpoints.SERVICE_PLANS + CloudControllerV3Endpoints.QUERY_SERVICE_OFFERING_GUIDS + serviceOfferingGuid + + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; + return cc.list(uri, PLAN_LIST_TYPE) .stream() .map(V3ServicePlanMapper::toCloudServicePlan) diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3Operations.java index d3e2fd116d..7a9c033585 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3Operations.java @@ -1,9 +1,13 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest; +import java.text.MessageFormat; import java.util.List; import java.util.Map; import java.util.UUID; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.cloudfoundry.multiapps.controller.Messages; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; import org.cloudfoundry.multiapps.controller.client.facade.domain.ServicePlanVisibility; @@ -12,31 +16,8 @@ import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpStatus; -/** - * CF v3 service plans operations of the in-house {@code CloudControllerRestClient}. Reproduces the HTTP shape, filtering and - * broker-scoped fan-out of the OSS {@code CloudControllerRestClientImpl#updateServicePlanVisibilityForBroker} on top of the shared - * {@link CloudControllerV3Client} machinery. - * - *

- * Endpoint map (see {@code docs/cf-java-client-migration/02-endpoint-inventory.md}): - *

- *
    - *
  • resolve broker by name → {@code GET /v3/service_brokers?names=} (404 when absent, as the broker is required);
  • - *
  • resolve the broker's offerings → {@code GET /v3/service_offerings?service_broker_guids=&space_guids=};
  • - *
  • resolve each offering's plans → {@code GET /v3/service_plans?service_offering_guids=};
  • - *
  • update visibility → {@code PATCH /v3/service_plans/{guid}/visibility} with body {@code { "type": "" }}.
  • - *
- * - *

- * The {@code visibility} body value is the lowercase CF visibility token ({@code public|admin|organization}); this matches the OSS impl, - * which passes {@code Visibility.from(visibility.toString())} where {@link ServicePlanVisibility#toString()} lower-cases the enum name. - *

- */ public class ServicePlansV3Operations { - // CF v3 caps per_page at 5000; use a large page to minimise round-trips (the pagination walker still handles multiple pages). - private static final int PER_PAGE = 5000; - private static final ParameterizedTypeReference> BROKER_LIST_TYPE = new ParameterizedTypeReference<>() { }; private static final ParameterizedTypeReference> OFFERING_LIST_TYPE = new ParameterizedTypeReference<>() { @@ -60,28 +41,32 @@ public void updateServicePlanVisibilityForBroker(String name, ServicePlanVisibil } } - // Mirrors the OSS getServiceBroker(name, true): find the broker by name in the (global) broker list and 404 if it is absent. private UUID getRequiredServiceBrokerGuid(String name) { - String uri = "/v3/service_brokers?names=" + name + "&per_page=" + PER_PAGE; + String uri = CloudControllerV3Endpoints.SERVICE_BROKERS + CloudControllerV3Endpoints.QUERY_NAMES + name + + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; + return cc.list(uri, BROKER_LIST_TYPE) .stream() .findFirst() .map(broker -> UUID.fromString(broker.guid())) - .orElseThrow(() -> new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", - "Service broker " + name + " not found.")); + .orElseThrow(() -> new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + MessageFormat.format(Messages.SERVICE_BROKER_0_NOT_FOUND, name))); } - // Mirrors the OSS findServicePlansByBrokerGuid: list the broker's offerings (scoped to the target space), then each offering's plans. private List findServicePlanGuidsByBrokerGuid(UUID brokerGuid) { - List offeringGuids = findServiceOfferingGuidsByBrokerGuid(brokerGuid); - if (offeringGuids.isEmpty()) { + List offeredServicesGuids = findServiceOfferingGuidsByBrokerGuid(brokerGuid); + if (offeredServicesGuids.isEmpty()) { return List.of(); } - String offeringGuidsFilter = offeringGuids.stream() - .map(UUID::toString) - .reduce((a, b) -> a + "," + b) - .orElse(""); - String uri = "/v3/service_plans?service_offering_guids=" + offeringGuidsFilter + "&per_page=" + PER_PAGE; + + String offeringGuidsFilter = offeredServicesGuids.stream() + .map(UUID::toString) + .reduce((a, b) -> a + "," + b) + .orElse(""); + + String uri = CloudControllerV3Endpoints.SERVICE_PLANS + CloudControllerV3Endpoints.QUERY_SERVICE_OFFERING_GUIDS + + offeringGuidsFilter + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; + return cc.list(uri, PLAN_LIST_TYPE) .stream() .map(plan -> UUID.fromString(plan.guid())) @@ -89,34 +74,37 @@ private List findServicePlanGuidsByBrokerGuid(UUID brokerGuid) { } private List findServiceOfferingGuidsByBrokerGuid(UUID brokerGuid) { - StringBuilder query = new StringBuilder("/v3/service_offerings?service_broker_guids=" + brokerGuid + "&per_page=" + PER_PAGE); + StringBuilder query = new StringBuilder(CloudControllerV3Endpoints.SERVICE_OFFERINGS + + CloudControllerV3Endpoints.QUERY_SERVICE_BROKER_GUIDS + brokerGuid + + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE); + if (target != null && target.getGuid() != null) { - query.append("&space_guids=") + query.append(CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) .append(target.getGuid()); } + return cc.list(query.toString(), OFFERING_LIST_TYPE) .stream() .map(offering -> UUID.fromString(offering.guid())) .toList(); } - // Mirrors the OSS updateServicePlanVisibility(UUID, ServicePlanVisibility): PATCH the plan's visibility with the lowercase CF token. private void updateServicePlanVisibility(UUID servicePlanGuid, ServicePlanVisibility visibility) { cc.getRestClient() .patch() - .uri("/v3/service_plans/{guid}/visibility", servicePlanGuid) + .uri(CloudControllerV3Endpoints.SERVICE_PLAN_VISIBILITY, servicePlanGuid) .body(Map.of("type", visibility.toString())) .retrieve() .toBodilessEntity(); } - // Minimal wire models to resolve GUIDs by name/relationship without depending on the (separately-owned) broker/offering groups. - @com.fasterxml.jackson.annotation.JsonIgnoreProperties(ignoreUnknown = true) - private record V3ServiceBrokerRef(@com.fasterxml.jackson.annotation.JsonProperty("guid") String guid) { + @JsonIgnoreProperties(ignoreUnknown = true) + private record V3ServiceBrokerRef(@JsonProperty("guid") String guid) { } - @com.fasterxml.jackson.annotation.JsonIgnoreProperties(ignoreUnknown = true) - private record V3ServiceOfferingRef(@com.fasterxml.jackson.annotation.JsonProperty("guid") String guid) { + @JsonIgnoreProperties(ignoreUnknown = true) + private record V3ServiceOfferingRef(@JsonProperty("guid") String guid) { } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/StacksV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/StacksV3Operations.java index 18e8537756..d8cc570f5a 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/StacksV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/StacksV3Operations.java @@ -1,9 +1,10 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest; +import java.text.MessageFormat; import java.util.List; +import org.cloudfoundry.multiapps.controller.Messages; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudStack; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Stack; @@ -11,26 +12,15 @@ import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpStatus; -/** - * CF v3 stacks operations of the cf-java-client replacement. Reproduces the HTTP shape and domain mapping of the OSS - * {@code CloudControllerRestClientImpl} stack methods: - *
    - *
  • {@code getStacks()} → {@code GET /v3/stacks} (paginated);
  • - *
  • {@code getStack(name[, required])} → {@code GET /v3/stacks?names=}, taking the first match.
  • - *
- * Stacks are a global resource, so — like the OSS impl — the listing is not filtered by the target space. - */ public class StacksV3Operations { private static final ParameterizedTypeReference> STACK_PAGE = new ParameterizedTypeReference<>() { }; private final CloudControllerV3Client cc; - private final CloudSpace target; - public StacksV3Operations(CloudControllerV3Client cc, CloudSpace target) { + public StacksV3Operations(CloudControllerV3Client cc) { this.cc = cc; - this.target = target; } public CloudStack getStack(String name) { @@ -38,22 +28,27 @@ public CloudStack getStack(String name) { } public CloudStack getStack(String name, boolean required) { - CloudStack stack = findStackByName(name); - if (stack == null && required) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Stack " + name + " not found."); + CloudStack resultStack = findStackByName(name); + + if (resultStack == null && required) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + MessageFormat.format(Messages.STACK_0_NOT_FOUND, name)); } - return stack; + + return resultStack; } public List getStacks() { - return cc.list("/v3/stacks?per_page=5000", STACK_PAGE) + return cc.list(CloudControllerV3Endpoints.STACKS + CloudControllerV3Endpoints.QUERY_PER_PAGE + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE, STACK_PAGE) .stream() .map(V3StackMapper::toCloudStack) .toList(); } private CloudStack findStackByName(String name) { - return cc.list("/v3/stacks?names=" + name + "&per_page=5000", STACK_PAGE) + return cc.list(CloudControllerV3Endpoints.STACKS + CloudControllerV3Endpoints.QUERY_NAMES + name + + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE, STACK_PAGE) .stream() .map(V3StackMapper::toCloudStack) .findFirst() diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/TasksV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/TasksV3Operations.java index 0fd5155e8d..486480dd58 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/TasksV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/TasksV3Operations.java @@ -1,10 +1,12 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest; +import java.text.MessageFormat; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; +import org.cloudfoundry.multiapps.controller.Messages; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudTask; @@ -15,23 +17,8 @@ import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpStatus; -/** - * CF v3 task operations for the cf-java-client replacement. Reproduces the HTTP shape and domain-mapping of the OSS - * {@code CloudControllerRestClientImpl} task methods: - *
    - *
  • {@code getTask} → {@code GET /v3/tasks/{guid}}
  • - *
  • {@code getTasks} → {@code GET /v3/tasks?app_guids={appGuid}} (paginated)
  • - *
  • {@code runTask} → {@code POST /v3/apps/{appGuid}/tasks}
  • - *
  • {@code cancelTask} → {@code POST /v3/tasks/{guid}/actions/cancel}
  • - *
- * Application-name resolution mirrors the OSS impl's {@code getRequiredApplicationGuid}: it queries the target space and throws - * {@link CloudOperationException} with {@link HttpStatus#NOT_FOUND} when the application does not exist. - */ public class TasksV3Operations { - // CF v3 caps per_page at 5000; use a large page to minimise round-trips (the pagination walker still handles multiple pages). - private static final int DEFAULT_PAGE_SIZE = 5000; - private final CloudControllerV3Client cc; private final CloudSpace target; @@ -41,15 +28,20 @@ public TasksV3Operations(CloudControllerV3Client cc, CloudSpace target) { } public CloudTask getTask(UUID taskGuid) { - V3Task task = cc.get("/v3/tasks/" + taskGuid, V3Task.class); + V3Task task = cc.get(CloudControllerV3Endpoints.TASKS + "/" + taskGuid, V3Task.class); + return task == null ? null : V3TaskMapper.toCloudTask(task); } public List getTasks(String applicationName) { UUID applicationGuid = getRequiredApplicationGuid(applicationName); - String query = "/v3/tasks?per_page=" + DEFAULT_PAGE_SIZE + "&app_guids=" + applicationGuid; + + String query = CloudControllerV3Endpoints.TASKS + CloudControllerV3Endpoints.QUERY_PER_PAGE + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE + CloudControllerV3Endpoints.AMPERSAND_APP_GUIDS + applicationGuid; + List tasks = cc.list(query, new ParameterizedTypeReference>() { }); + return tasks.stream() .map(V3TaskMapper::toCloudTask) .toList(); @@ -57,21 +49,24 @@ public List getTasks(String applicationName) { public CloudTask runTask(String applicationName, CloudTask task) { UUID applicationGuid = getRequiredApplicationGuid(applicationName); + V3Task created = cc.getRestClient() .post() - .uri("/v3/apps/{guid}/tasks", applicationGuid) + .uri(CloudControllerV3Endpoints.APP_TASKS, applicationGuid) .body(buildCreateTaskBody(task)) .retrieve() .body(V3Task.class); + return created == null ? null : V3TaskMapper.toCloudTask(created); } public CloudTask cancelTask(UUID taskGuid) { V3Task cancelled = cc.getRestClient() .post() - .uri("/v3/tasks/{guid}/actions/cancel", taskGuid) + .uri(CloudControllerV3Endpoints.TASK_CANCEL, taskGuid) .retrieve() .body(V3Task.class); + return cancelled == null ? null : V3TaskMapper.toCloudTask(cancelled); } @@ -79,33 +74,41 @@ private Map buildCreateTaskBody(CloudTask task) { Map body = new HashMap<>(); body.put("command", task.getCommand()); body.put("name", task.getName()); + CloudTask.Limits limits = task.getLimits(); if (limits != null) { if (limits.getMemory() != null) { body.put("memory_in_mb", limits.getMemory()); } + if (limits.getDisk() != null) { body.put("disk_in_mb", limits.getDisk()); } } + return body; } - // Mirrors the OSS impl's getRequiredApplicationGuid: look up the app in the target space and 404 if it is absent. private UUID getRequiredApplicationGuid(String applicationName) { - StringBuilder query = new StringBuilder("/v3/apps?per_page=" + DEFAULT_PAGE_SIZE); + StringBuilder query = new StringBuilder(CloudControllerV3Endpoints.APPS + CloudControllerV3Endpoints.QUERY_PER_PAGE + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE); + if (target != null && target.getGuid() != null) { - query.append("&space_guids=") + query.append(CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) .append(target.getGuid()); } - query.append("&names=") + + query.append(CloudControllerV3Endpoints.AMPERSAND_NAMES) .append(applicationName); List apps = cc.list(query.toString(), new ParameterizedTypeReference>() { }); + if (apps.isEmpty()) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Application " + applicationName + " not found."); + throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + MessageFormat.format(Messages.APPLICATION_0_NOT_FOUND, applicationName)); } - return UUID.fromString(apps.get(0) + + return UUID.fromString(apps.getFirst() .guid()); } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/LogCacheReadResponse.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/LogCacheReadResponse.java index 3965703cdb..010e348dea 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/LogCacheReadResponse.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/LogCacheReadResponse.java @@ -7,18 +7,6 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; -/** - * Thin Jackson wire-model of a Log-Cache {@code read} response ({@code GET /api/v1/read/{source_id}}), which is a plain JSON/REST API: - * - *
- * { "envelopes": { "batch": [
- *     { "source_id": "...", "timestamp": "1699999999000000000", "tags": { "source_type": "APP/PROC/WEB" },
- *       "log": { "payload": "<base64>", "type": "OUT" | "ERR" } } ] } }
- * 
- * - * Replaces the OSS {@code org.cloudfoundry.logcache.v1.*} types so the log-cache client no longer depends on cf-java-client. Only the - * fields the mapper needs are declared. - */ @JsonIgnoreProperties(ignoreUnknown = true) public record LogCacheReadResponse(@JsonProperty("envelopes") EnvelopeBatch envelopes) { @@ -26,6 +14,7 @@ public List batch() { if (envelopes == null || envelopes.batch() == null) { return Collections.emptyList(); } + return envelopes.batch(); } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Application.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Application.java index 2eab011a70..9394d5c894 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Application.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Application.java @@ -42,10 +42,6 @@ public record V3ToOneRelationship(@JsonProperty("data") V3RelationshipData data) public record V3RelationshipData(@JsonProperty("guid") String guid) { } - /** - * The CF v3 app environment variables response ({@code GET /v3/apps/{guid}/environment_variables}): - * {@code { "var": { "KEY": "VALUE", ... } }}. - */ @JsonIgnoreProperties(ignoreUnknown = true) public record V3EnvironmentVariables(@JsonProperty("var") Map var) { } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ApplicationMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ApplicationMapper.java index ae4de63eb3..cfab780cd7 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ApplicationMapper.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ApplicationMapper.java @@ -10,10 +10,6 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.Lifecycle; import org.cloudfoundry.multiapps.controller.client.facade.domain.LifecycleType; -/** - * Maps the {@link V3Application} wire model to the project's {@link CloudApplication} domain object. Mirrors the OSS - * {@code RawCloudApplication} adapter field-for-field, so both client implementations yield identical domain objects. - */ public final class V3ApplicationMapper { private static final String BUILDPACKS = "buildpacks"; @@ -41,6 +37,7 @@ private static Lifecycle parseLifecycle(V3Application.V3Lifecycle lifecycle) { if (lifecycle == null || lifecycle.type() == null) { return null; } + return ImmutableLifecycle.builder() .type(LifecycleType.valueOf(lifecycle.type() .toUpperCase())) @@ -52,6 +49,7 @@ private static Map extractLifecycleData(V3Application.V3Lifecycl if (data == null) { return Collections.emptyMap(); } + return Map.of(BUILDPACKS, data.buildpacks() == null ? Collections.emptyList() : data.buildpacks(), STACK, data.stack() == null ? "" : data.stack()); } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AuditEvent.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AuditEvent.java index de24e0babc..95907cb759 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AuditEvent.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AuditEvent.java @@ -12,10 +12,6 @@ * "target": { "guid": "...", "type": "app", "name": "..." }, * "data": { ... }, "space": {...}, "organization": {...} } * - * - * Only the fields the domain {@code CloudEvent} needs are mapped; everything else is ignored so CF can evolve the payload without breaking - * us. Mirrors the CF v3 JSON consumed by the OSS {@code AuditEventResource} adapter (whose {@code actor}/{@code target} carry - * {@code guid}/{@code name}/{@code type}). */ @JsonIgnoreProperties(ignoreUnknown = true) public record V3AuditEvent(@JsonProperty("guid") String guid, @JsonProperty("created_at") String createdAt, diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AuditEventMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AuditEventMapper.java index ede9fc9837..e06b1b1518 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AuditEventMapper.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AuditEventMapper.java @@ -5,12 +5,6 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudEvent; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudEvent.ImmutableParticipant; -/** - * Maps the {@link V3AuditEvent} wire model to the project's {@link CloudEvent} domain object. Mirrors the OSS {@code RawCloudEvent} adapter - * field-for-field, so both client implementations yield identical domain objects. - *

- * As in the OSS adapter, a {@code null} actor/target still yields an empty (non-{@code null}) {@link Participant}. - */ public final class V3AuditEventMapper { private V3AuditEventMapper() { @@ -30,6 +24,7 @@ private static Participant parseParticipant(V3AuditEvent.V3Participant participa return ImmutableParticipant.builder() .build(); } + return ImmutableParticipant.builder() .guid(V3ResourceMappers.parseNullableGuid(participant.guid())) .name(participant.name()) diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3BuildMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3BuildMapper.java index c40417b701..fffddea5e7 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3BuildMapper.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3BuildMapper.java @@ -7,10 +7,6 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudBuild.ImmutablePackageInfo; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableDropletInfo; -/** - * Maps the {@link V3Build} wire model to the project's {@link CloudBuild} domain object. Mirrors the OSS {@code RawCloudBuild} adapter - * field-for-field, so both client implementations yield identical domain objects. - */ public final class V3BuildMapper { private V3BuildMapper() { @@ -31,6 +27,7 @@ private static CloudBuild.CreatedBy parseCreatedBy(V3Build.V3CreatedBy createdBy if (createdBy == null) { return null; } + return ImmutableCreatedBy.builder() .guid(V3ResourceMappers.parseNullableGuid(createdBy.guid())) .name(createdBy.name()) @@ -41,6 +38,7 @@ private static CloudBuild.PackageInfo parsePackageInfo(V3Build.V3PackageReferenc if (inputPackage == null) { return null; } + return ImmutablePackageInfo.of(V3ResourceMappers.parseNullableGuid(inputPackage.guid())); } @@ -48,6 +46,7 @@ private static DropletInfo parseDropletInfo(V3Build.V3DropletReference droplet) if (droplet == null || droplet.guid() == null) { return null; } + return ImmutableDropletInfo.builder() .guid(V3ResourceMappers.parseNullableGuid(droplet.guid())) .build(); diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Domain.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Domain.java index 8bc9403d99..2234f4c698 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Domain.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Domain.java @@ -12,9 +12,6 @@ * "metadata": { "labels": {...}, "annotations": {...} }, * "relationships": { "organization": { "data": { "guid": "..." } | null } } } * - * - * The {@code relationships.organization.data} discriminates shared domains (data == null) from private domains (data != null); this - * mirrors the filtering the OSS impl applied on {@code DomainResource.getRelationships().getOrganization().getData()}. */ @JsonIgnoreProperties(ignoreUnknown = true) public record V3Domain(@JsonProperty("guid") String guid, @JsonProperty("name") String name, @@ -33,10 +30,6 @@ public record V3ToOneRelationship(@JsonProperty("data") V3RelationshipData data) public record V3RelationshipData(@JsonProperty("guid") String guid) { } - /** - * True when this domain is scoped to an organization (a private domain), i.e. its {@code relationships.organization.data} is present. - * A shared domain has {@code data == null}. Mirrors the OSS {@code getPrivateDomainResources}/{@code getSharedDomainResources} filters. - */ public boolean isPrivate() { return relationships != null && relationships.organization() != null && relationships.organization() .data() != null; diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3DomainMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3DomainMapper.java index 98e1c96bb8..b0f9af0eed 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3DomainMapper.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3DomainMapper.java @@ -3,10 +3,6 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudDomain; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudDomain; -/** - * Maps the {@link V3Domain} wire model to the project's {@link CloudDomain} domain object. Mirrors the OSS {@code RawCloudDomain} adapter - * field-for-field (metadata from the resource envelope + {@code name}), so both client implementations yield identical domain objects. - */ public final class V3DomainMapper { private V3DomainMapper() { diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Droplet.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Droplet.java index 5281407ddd..f69486e983 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Droplet.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Droplet.java @@ -11,8 +11,6 @@ *

  * { "guid": "...", "links": { "package": { "href": "https://.../v3/packages/{guid}" }, ... } }
  * 
- * - * The client only needs the droplet {@code guid} and the {@code package} link, from which the package GUID is derived. */ @JsonIgnoreProperties(ignoreUnknown = true) public record V3Droplet(@JsonProperty("guid") String guid, @JsonProperty("links") Map links) { diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3InstancesInfoMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3InstancesInfoMapper.java index f111e8acc6..ed1109da12 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3InstancesInfoMapper.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3InstancesInfoMapper.java @@ -10,14 +10,6 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.InstanceState; import org.cloudfoundry.multiapps.controller.client.facade.domain.InstancesInfo; -/** - * Maps the {@link V3Process.V3ProcessStats} wire model to the project's {@link InstancesInfo} domain object. Mirrors the OSS - * {@code RawInstancesInfo} adapter field-for-field, so both client implementations yield identical domain objects. - *

- * The instance state string comes from CF ({@code GET /v3/apps/{guid}/processes/web/stats}) and is resolved via - * {@link InstanceState#valueOfWithDefault(String)} (which falls back to {@link InstanceState#UNKNOWN} for unrecognised values), so no OSS - * leaf type is involved. - */ public final class V3InstancesInfoMapper { private V3InstancesInfoMapper() { @@ -33,6 +25,7 @@ private static List parseProcessStatistics(List - * - * The async job poller in {@link org.cloudfoundry.multiapps.controller.client.facade.rest.CloudControllerV3Client} polls this until - * {@code state} reaches a terminal value (COMPLETE / FAILED), mirroring the OSS {@code JobV3Util} behaviour. */ @JsonIgnoreProperties(ignoreUnknown = true) public record V3Job(@JsonProperty("guid") String guid, @JsonProperty("created_at") String createdAt, diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3JobMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3JobMapper.java index 89a153a5e4..b643ed0130 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3JobMapper.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3JobMapper.java @@ -7,10 +7,6 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudAsyncJob; import org.cloudfoundry.multiapps.controller.client.facade.domain.JobState; -/** - * Maps the {@link V3Job} wire model to the project's {@link CloudAsyncJob} domain object. Mirrors the OSS {@code RawCloudAsyncJob} adapter - * field-for-field, so both client implementations yield identical domain objects. - */ public final class V3JobMapper { private V3JobMapper() { @@ -35,6 +31,7 @@ private static String getWarnings(V3Job job) { if (warnings == null) { return ""; } + return warnings.stream() .map(V3Job.V3Warning::detail) .collect(Collectors.joining(",")); @@ -45,6 +42,7 @@ private static String getErrors(V3Job job) { if (errors == null) { return ""; } + return errors.stream() .map(V3JobMapper::joinErrorDetails) .collect(Collectors.joining(",")); diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ListResponse.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ListResponse.java index 43e91ce3a5..5476df61e6 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ListResponse.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ListResponse.java @@ -15,12 +15,6 @@ * "resources": [ ... ] * } * - * - * Only the fields the client actually needs are mapped; everything else is ignored so CF can evolve the payload without breaking us. - * The {@link org.cloudfoundry.multiapps.controller.client.facade.rest.CloudControllerV3Client pagination walker} follows - * {@code pagination.next.href} until it is {@code null}, accumulating {@link #resources()} from every page. - * - * @param the wire type of a single resource in this list */ @JsonIgnoreProperties(ignoreUnknown = true) public record V3ListResponse(@JsonProperty("pagination") Pagination pagination, @JsonProperty("resources") List resources) { @@ -33,6 +27,7 @@ public String nextPageHref() { if (pagination == null || pagination.next() == null) { return null; } + return pagination.next() .href(); } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3PackageMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3PackageMapper.java index 4f62f4b347..d0ba2e0616 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3PackageMapper.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3PackageMapper.java @@ -1,15 +1,12 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; +import org.cloudfoundry.multiapps.controller.client.facade.domain.BitsData; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudPackage; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableBitsData; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudPackage; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableDockerData; import org.cloudfoundry.multiapps.controller.client.facade.domain.Status; -/** - * Maps the {@link V3Package} wire model to the project's {@link CloudPackage} domain object. Mirrors the OSS {@code RawCloudPackage} - * adapter field-for-field, so both client implementations yield identical domain objects. - */ public final class V3PackageMapper { private V3PackageMapper() { @@ -37,6 +34,7 @@ private static CloudPackage.PackageData parseData(V3Package resource) { if (resource.type() != null && CloudPackage.Type.from(resource.type()) == CloudPackage.Type.BITS) { return parseBitsData(resource.data()); } + return parseDockerData(resource.data()); } @@ -45,17 +43,19 @@ private static CloudPackage.PackageData parseBitsData(V3Package.V3PackageData da return ImmutableBitsData.builder() .build(); } + return ImmutableBitsData.builder() .checksum(parseBitsChecksum(data.checksum())) .error(data.error()) .build(); } - private static org.cloudfoundry.multiapps.controller.client.facade.domain.BitsData.Checksum - parseBitsChecksum(V3Package.V3Checksum checksum) { + private static BitsData.Checksum + parseBitsChecksum(V3Package.V3Checksum checksum) { if (checksum == null) { return null; } + return ImmutableBitsData.ImmutableChecksum.builder() .algorithm(checksum.type()) .value(checksum.value()) diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Process.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Process.java index 263a4d29dc..d010a950ee 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Process.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Process.java @@ -7,12 +7,6 @@ /** * Thin Jackson wire-models for the CF v3 process family of endpoints consumed by the client: - *

    - *
  • {@link V3Process} — {@code GET /v3/apps/{guid}/processes/web} (also {@code GET /v3/processes/{guid}});
  • - *
  • {@link V3ProcessStats} — {@code GET /v3/apps/{guid}/processes/web/stats};
  • - *
  • {@link V3AppFeatures} / {@link V3AppFeature} — {@code GET /v3/apps/{guid}/features};
  • - *
  • {@link V3SshEnabled} — {@code GET /v3/apps/{guid}/ssh_enabled}.
  • - *
* *
  * // process
@@ -20,8 +14,6 @@
  *   "health_check": { "type": "port|http|process", "data": { "timeout": N, "invocation_timeout": N, "endpoint": "...", "interval": N } },
  *   "readiness_health_check": { "type": "process|http|port", "data": { "invocation_timeout": N, "endpoint": "...", "interval": N } } }
  * 
- * - * Every record ignores unknown fields so CF can evolve the payload without breaking us. */ @JsonIgnoreProperties(ignoreUnknown = true) public record V3Process(@JsonProperty("guid") String guid, @JsonProperty("command") String command, @@ -39,10 +31,6 @@ public record V3HealthCheckData(@JsonProperty("timeout") Integer timeout, @JsonProperty("endpoint") String endpoint, @JsonProperty("interval") Integer interval) { } - /** - * The CF v3 process statistics response ({@code GET /v3/apps/{guid}/processes/web/stats}): - * {@code { "resources": [ { "index": 0, "state": "RUNNING", "routable": true }, ... ] }}. - */ @JsonIgnoreProperties(ignoreUnknown = true) public record V3ProcessStats(@JsonProperty("resources") List resources) { } @@ -52,18 +40,10 @@ public record V3ProcessStatsResource(@JsonProperty("index") Integer index, @Json @JsonProperty("routable") String routable) { } - /** - * The CF v3 app features response ({@code GET /v3/apps/{guid}/features}): - * {@code { "resources": [ { "name": "ssh", "enabled": true }, ... ] }}. - */ @JsonIgnoreProperties(ignoreUnknown = true) public record V3AppFeature(@JsonProperty("name") String name, @JsonProperty("enabled") Boolean enabled) { } - /** - * The CF v3 app SSH-enabled response ({@code GET /v3/apps/{guid}/ssh_enabled}): - * {@code { "enabled": true, "reason": "..." }}. - */ @JsonIgnoreProperties(ignoreUnknown = true) public record V3SshEnabled(@JsonProperty("enabled") Boolean enabled, @JsonProperty("reason") String reason) { } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ProcessMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ProcessMapper.java index 94de49472a..49718c5fd4 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ProcessMapper.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ProcessMapper.java @@ -4,10 +4,6 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.HealthCheckType; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudProcess; -/** - * Maps the {@link V3Process} wire model to the project's {@link CloudProcess} domain object. Mirrors the OSS - * {@code RawCloudProcess} adapter field-for-field, so both client implementations yield identical domain objects. - */ public final class V3ProcessMapper { private V3ProcessMapper() { @@ -21,6 +17,7 @@ public static CloudProcess toCloudProcess(V3Process process) { String healthCheckHttpEndpoint = null; Integer healthCheckInvocationTimeout = null; Integer healthCheckInterval = null; + if (healthCheck != null && healthCheck.data() != null) { V3Process.V3HealthCheckData healthCheckData = healthCheck.data(); healthCheckTimeout = healthCheckData.timeout(); @@ -32,6 +29,7 @@ public static CloudProcess toCloudProcess(V3Process process) { Integer readinessHealthCheckInvocationTimeout = null; String readinessHealthCheckHttpEndpoint = null; Integer readinessHealthCheckInterval = null; + if (readinessHealthCheck != null && readinessHealthCheck.data() != null) { V3Process.V3HealthCheckData readinessHealthCheckData = readinessHealthCheck.data(); readinessHealthCheckInvocationTimeout = readinessHealthCheckData.invocationTimeout(); diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ResourceMappers.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ResourceMappers.java index e5fc872a2b..e8500919ee 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ResourceMappers.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ResourceMappers.java @@ -1,20 +1,16 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; import java.time.LocalDateTime; +import java.time.OffsetDateTime; import java.time.format.DateTimeParseException; import java.util.UUID; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Helpers shared by all CF v3 wire-model mappers: parse the resource envelope fields ({@code guid}, {@code created_at}, - * {@code updated_at}) into the project's {@link CloudMetadata}, and parse GUIDs/dates leniently — mirroring the behaviour of the OSS - * {@code RawCloudEntity} so the two client implementations produce identical domain objects. - */ public final class V3ResourceMappers { private static final Logger LOGGER = LoggerFactory.getLogger(V3ResourceMappers.class); @@ -30,14 +26,11 @@ public static CloudMetadata parseMetadata(String guid, String createdAt, String .build(); } - /** - * Build the project's v3 {@link Metadata} (labels/annotations) from the wire representation. Kept as the OSS leaf type for now; it - * is re-homed to a project-owned type in the leaf-type migration step. - */ public static Metadata toV3Metadata(V3Metadata metadata) { if (metadata == null) { return null; } + return Metadata.builder() .labels(metadata.labels()) .annotations(metadata.annotations()) @@ -48,6 +41,7 @@ public static UUID parseNullableGuid(String guid) { if (guid == null) { return null; } + try { return UUID.fromString(guid); } catch (IllegalArgumentException e) { @@ -60,10 +54,11 @@ public static LocalDateTime parseNullableDate(String date) { if (date == null) { return null; } + try { // CF v3 timestamps are ISO-8601 with offset, e.g. 2026-08-04T10:15:30Z - return java.time.OffsetDateTime.parse(date) - .toLocalDateTime(); + return OffsetDateTime.parse(date) + .toLocalDateTime(); } catch (DateTimeParseException e) { LOGGER.warn("Could not parse date string: \"{}\"", date, e); return null; diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Role.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Role.java index f280bc0267..f192169500 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Role.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Role.java @@ -12,9 +12,6 @@ * "relationships": { "user": { "data": { "guid": "..." } }, "space": { "data": { "guid": "..." } | null }, * "organization": { "data": { "guid": "..." } | null } } } * - * - * Only {@code type} is consumed by the mapper (mirroring the OSS {@code RawUserRole}); the remaining fields are documented for - * completeness and ignored so CF can evolve the payload without breaking us. */ @JsonIgnoreProperties(ignoreUnknown = true) public record V3Role(@JsonProperty("guid") String guid, @JsonProperty("created_at") String createdAt, diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RoleMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RoleMapper.java index 5b75520137..7e5d4372a1 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RoleMapper.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RoleMapper.java @@ -2,10 +2,6 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.UserRole; -/** - * Maps the {@link V3Role} wire model to the project's {@link UserRole} domain enum. Passes the raw CF v3 role {@code type} wire value - * (e.g. {@code space_developer}) straight to {@link UserRole#fromRoleType(String)}, so this mapper depends on no OSS types. - */ public final class V3RoleMapper { private V3RoleMapper() { diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Route.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Route.java index 6cc6e7f991..b29aa562d7 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Route.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Route.java @@ -16,9 +16,6 @@ * "metadata": { "labels": {...}, "annotations": {...} }, * "relationships": { "space": { "data": { "guid": "..." } }, "domain": { "data": { "guid": "..." } } } } * - * - * Mirrors exactly the fields the OSS {@code RawCloudRoute} adapter consumes from {@code org.cloudfoundry.client.v3.routes.Route}, so - * both client implementations yield identical {@code CloudRoute} domain objects. */ @JsonIgnoreProperties(ignoreUnknown = true) public record V3Route(@JsonProperty("guid") String guid, @JsonProperty("host") String host, @JsonProperty("path") String path, diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RouteMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RouteMapper.java index b33b856d80..6c8691d6e4 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RouteMapper.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RouteMapper.java @@ -12,35 +12,22 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableRouteDestination; import org.cloudfoundry.multiapps.controller.client.facade.domain.RouteDestination; -/** - * Maps the {@link V3Route} wire model to the project's {@link CloudRoute} domain object. Mirrors the OSS {@code RawCloudRoute} adapter - * field-for-field (including the domain-name derivation from the route URL and the requested-protocol lookup by application GUID), so - * both client implementations yield identical domain objects. - */ public final class V3RouteMapper { private V3RouteMapper() { } - /** - * Maps a route without an application context (equivalent to OSS {@code ImmutableRawCloudRoute.of(route)}): the requested protocol is - * always {@code null}. - */ public static CloudRoute toCloudRoute(V3Route route) { return toCloudRoute(route, null); } - /** - * Maps a route in the context of a specific application (equivalent to OSS - * {@code ImmutableRawCloudRoute.builder().route(route).applicationGuid(applicationGuid).build()}): the requested protocol is taken - * from the destination that points at {@code applicationGuid}. - */ public static CloudRoute toCloudRoute(V3Route route, UUID applicationGuid) { List destinations = mapDestinations(route); String domainGuid = route.relationships() .domain() .data() .guid(); + return ImmutableCloudRoute.builder() .metadata(V3ResourceMappers.parseMetadata(route.guid(), route.createdAt(), route.updatedAt())) .appsUsingRoute(route.destinations() @@ -62,15 +49,19 @@ private static String computeDomain(V3Route route) { String domain = route.url(); String host = route.host() == null ? "" : route.host(); String path = route.path() == null ? "" : route.path(); + if (!host.isEmpty()) { domain = domain.substring(host.length() + 1); } + if (!path.isEmpty()) { domain = domain.substring(0, domain.indexOf('/')); } + if (route.port() != null) { domain = domain.substring(0, domain.indexOf(':')); } + return domain; } @@ -94,6 +85,7 @@ private static String computeRequestedProtocol(List destinatio if (applicationGuid == null) { return null; } + return destinations.stream() .filter(destination -> Objects.equals(destination.getApplicationGuid(), applicationGuid)) .findFirst() diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBindingMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBindingMapper.java index 8a06576d65..96a3bf82c6 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBindingMapper.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBindingMapper.java @@ -9,15 +9,6 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableServiceCredentialBindingOperation; import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceCredentialBindingOperation; -/** - * Maps the {@link V3ServiceBinding} wire model to the project's {@link CloudServiceBinding} domain object. Mirrors the OSS - * {@code RawCloudServiceBinding} adapter field-for-field, so both client implementations yield identical domain objects: - *
    - *
  • {@code applicationGuid} — nullable; from {@code relationships.app.data.guid} (absent for {@code type=key} bindings);
  • - *
  • {@code serviceInstanceGuid} — required; from {@code relationships.service_instance.data.guid};
  • - *
  • {@code serviceBindingOperation} — from {@code last_operation}, mirroring {@link ServiceCredentialBindingOperation#from}.
  • - *
- */ public final class V3ServiceBindingMapper { private V3ServiceBindingMapper() { @@ -37,9 +28,11 @@ private static UUID parseApplicationGuid(V3ServiceBinding binding) { V3ServiceBinding.V3ToOneRelationship application = binding.relationships() == null ? null : binding.relationships() .application(); + if (application == null || application.data() == null) { return null; } + return V3ResourceMappers.parseNullableGuid(application.data() .guid()); } @@ -55,6 +48,7 @@ private static ServiceCredentialBindingOperation parseServiceBindingOperation(V3 if (lastOperation == null) { return null; } + return ImmutableServiceCredentialBindingOperation.builder() .type(ServiceCredentialBindingOperation.Type.fromString(lastOperation.type())) .state(ServiceCredentialBindingOperation.State.fromString(lastOperation.state())) diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBroker.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBroker.java index 4be2e36a9b..2fe6d5d6d8 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBroker.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBroker.java @@ -11,8 +11,6 @@ * "metadata": { "labels": {...}, "annotations": {...} }, * "relationships": { "space": { "data": { "guid": "..." } | null } } } * - * - * Only the fields the client maps into {@code CloudServiceBroker} are declared; everything else is ignored. */ @JsonIgnoreProperties(ignoreUnknown = true) public record V3ServiceBroker(@JsonProperty("guid") String guid, @JsonProperty("name") String name, @JsonProperty("url") String url, diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBrokerMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBrokerMapper.java index 34d1bab18d..8b2b1cff2b 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBrokerMapper.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBrokerMapper.java @@ -5,15 +5,6 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBroker; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceBroker; -/** - * Maps the {@link V3ServiceBroker} wire model to the project's {@link CloudServiceBroker} domain object. Mirrors the OSS - * {@code RawCloudServiceBroker} adapter field-for-field, so both client implementations yield identical domain objects. - * - *

- * As in the OSS adapter, {@code username}/{@code password} are write-only credentials that CF never returns, so they are not mapped; - * only {@code name}, {@code url} and the (optional) space GUID from {@code relationships.space.data.guid} are populated. - *

- */ public final class V3ServiceBrokerMapper { private V3ServiceBrokerMapper() { diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstance.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstance.java index a220b724b3..1f01ad0c34 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstance.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstance.java @@ -16,14 +16,13 @@ * "metadata": { "labels": {...}, "annotations": {...} }, * "relationships": { "space": { "data": { "guid": "..." } }, "service_plan": { "data": { "guid": "..." } } } } * - * - * Only the fields the client needs are mapped; everything else is ignored so CF can evolve the payload without breaking us. */ @JsonIgnoreProperties(ignoreUnknown = true) public record V3ServiceInstance(@JsonProperty("guid") String guid, @JsonProperty("name") String name, @JsonProperty("type") String type, @JsonProperty("created_at") String createdAt, @JsonProperty("updated_at") String updatedAt, @JsonProperty("tags") List tags, @JsonProperty("syslog_drain_url") String syslogDrainUrl, - @JsonProperty("last_operation") V3LastOperation lastOperation, @JsonProperty("metadata") V3Metadata metadata, + @JsonProperty("last_operation") V3LastOperation lastOperation, + @JsonProperty("metadata") V3Metadata metadata, @JsonProperty("relationships") V3Relationships relationships) { @JsonIgnoreProperties(ignoreUnknown = true) diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstanceMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstanceMapper.java index 45b6e73b51..2746c0cb29 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstanceMapper.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstanceMapper.java @@ -1,29 +1,15 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType; import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceOperation; -/** - * Maps the {@link V3ServiceInstance} wire model to the project's {@link CloudServiceInstance} domain object. Mirrors the OSS - * {@code RawCloudServiceInstance} and {@code RawV3CloudServiceInstance} adapters field-for-field, so both client implementations yield - * identical domain objects. - * - *

- * The OSS {@code RawCloudServiceInstance} derives {@code plan} and {@code label} from auxiliary {@code ServicePlan} / - * {@code ServiceOffering} resources it is zipped with; those names are resolved by the operations helper and passed in here. - * {@code RawV3CloudServiceInstance} (used for the {@code byNames} batch lookups) maps only the base fields with no auxiliary content. - *

- */ public final class V3ServiceInstanceMapper { private V3ServiceInstanceMapper() { } - /** - * Full mapping mirroring the OSS {@code RawCloudServiceInstance.derive()}: base fields plus the resolved plan/offering names. - */ public static CloudServiceInstance toCloudServiceInstance(V3ServiceInstance serviceInstance, String planName, String labelName) { return ImmutableCloudServiceInstance.builder() .metadata(V3ResourceMappers.parseMetadata(serviceInstance.guid(), @@ -35,16 +21,12 @@ public static CloudServiceInstance toCloudServiceInstance(V3ServiceInstance serv .label(labelName) .type(parseType(serviceInstance.type())) .tags(serviceInstance.tags() == null ? java.util.Collections.emptyList() - : serviceInstance.tags()) + : serviceInstance.tags()) .lastOperation(parseLastOperation(serviceInstance.lastOperation())) .syslogDrainUrl(serviceInstance.syslogDrainUrl()) .build(); } - /** - * Base-only mapping mirroring the OSS {@code RawV3CloudServiceInstance.derive()}: metadata, v3Metadata, name and tags (used by the - * {@code byNames} batch lookups, which never resolve plan/offering and leave {@code type} unset). - */ public static CloudServiceInstance toCloudServiceInstanceWithoutAuxiliaryContent(V3ServiceInstance serviceInstance) { return ImmutableCloudServiceInstance.builder() .metadata(V3ResourceMappers.parseMetadata(serviceInstance.guid(), @@ -53,35 +35,33 @@ public static CloudServiceInstance toCloudServiceInstanceWithoutAuxiliaryContent .v3Metadata(V3ResourceMappers.toV3Metadata(serviceInstance.metadata())) .name(serviceInstance.name()) .tags(serviceInstance.tags() == null ? java.util.Collections.emptyList() - : serviceInstance.tags()) + : serviceInstance.tags()) .build(); } - /** - * Map the CF v3 {@code type} JSON value ({@code managed} / {@code user-provided}) to the project's {@link ServiceInstanceType}. - */ private static ServiceInstanceType parseType(String type) { if (type == null) { return null; } + for (ServiceInstanceType serviceInstanceType : ServiceInstanceType.values()) { if (serviceInstanceType.getValue() .equals(type)) { return serviceInstanceType; } } + return null; } - /** - * Mirrors {@code ServiceOperation.fromLastOperation(LastOperation)}: a {@code null} operation, type or state yields {@code null}. - */ private static ServiceOperation parseLastOperation(V3ServiceInstance.V3LastOperation lastOperation) { if (lastOperation == null || lastOperation.type() == null || lastOperation.state() == null) { return null; } + ServiceOperation.Type type = ServiceOperation.Type.fromString(lastOperation.type()); ServiceOperation.State state = ServiceOperation.State.fromString(lastOperation.state()); + return new ServiceOperation(type, lastOperation.description(), state); } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceKeyDetails.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceKeyDetails.java index d844dfaa51..2d1df8b410 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceKeyDetails.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceKeyDetails.java @@ -13,9 +13,6 @@ *
  * { "credentials": { ... }, "syslog_drain_url": "...", "volume_mounts": [ ... ] }
  * 
- * - * Mirrors the OSS impl's {@code serviceBindingsV3().getDetails(...)} call. The CF v3 API returns a 404 when fetching the details of a - * service key whose creation failed; callers translate that into empty credentials (see {@code ServiceKeysV3Operations}). */ @JsonIgnoreProperties(ignoreUnknown = true) public record V3ServiceKeyDetails(@JsonProperty("credentials") Map credentials, diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceKeyMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceKeyMapper.java index bc617456c8..23a6f16816 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceKeyMapper.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceKeyMapper.java @@ -8,20 +8,6 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableServiceCredentialBindingOperation; import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceCredentialBindingOperation; -/** - * Maps a CF v3 service credential binding of {@code type=key} ({@link V3ServiceBinding}) to the project's {@link CloudServiceKey} domain - * object. Mirrors the OSS {@code RawCloudServiceKey#derive()} adapter field-for-field, so both client implementations yield identical - * domain objects: - *
    - *
  • {@code metadata} — from the binding resource envelope ({@code guid}, {@code created_at}, {@code updated_at});
  • - *
  • {@code v3Metadata} — labels/annotations from {@code metadata};
  • - *
  • {@code name} — the service key name;
  • - *
  • {@code credentials} — nullable; fetched separately from the binding details endpoint (absent when listing without - * credentials);
  • - *
  • {@code serviceInstance} — the owning (already-derived) {@link CloudServiceInstance};
  • - *
  • {@code serviceKeyOperation} — from {@code last_operation}, mirroring {@link ServiceCredentialBindingOperation#from}.
  • - *
- */ public final class V3ServiceKeyMapper { private V3ServiceKeyMapper() { @@ -47,6 +33,7 @@ private static ServiceCredentialBindingOperation parseServiceKeyOperation(V3Serv if (lastOperation == null) { return null; } + return ImmutableServiceCredentialBindingOperation.builder() .type(ServiceCredentialBindingOperation.Type.fromString(lastOperation.type())) .state(ServiceCredentialBindingOperation.State.fromString(lastOperation.state())) diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceOfferingMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceOfferingMapper.java index 7393d9a97e..d98d5f2b3f 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceOfferingMapper.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceOfferingMapper.java @@ -7,11 +7,6 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServicePlan; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceOffering; -/** - * Maps the {@link V3ServiceOffering} wire model (plus the service plans fetched separately for it) to the project's - * {@link CloudServiceOffering} domain object. Mirrors the OSS {@code RawCloudServiceOffering} adapter field-for-field, so both client - * implementations yield identical domain objects. - */ public final class V3ServiceOfferingMapper { private V3ServiceOfferingMapper() { @@ -39,6 +34,7 @@ private static Boolean extractBindable(V3ServiceOffering.V3BrokerCatalog brokerC if (brokerCatalog == null || brokerCatalog.features() == null) { return null; } + return brokerCatalog.features() .bindable(); } @@ -56,6 +52,7 @@ private static String extractBrokerId(V3ServiceOffering.V3Relationships relation .data() == null) { return null; } + return relationships.serviceBroker() .data() .guid(); diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServicePlanMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServicePlanMapper.java index 4a7c197e67..9b6711fa94 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServicePlanMapper.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServicePlanMapper.java @@ -3,13 +3,8 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServicePlan; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServicePlan; -/** - * Maps the {@link V3ServicePlan} wire model to the project's {@link CloudServicePlan} domain object. Mirrors the OSS - * {@code RawCloudServicePlan} adapter field-for-field, so both client implementations yield identical domain objects. - */ public final class V3ServicePlanMapper { - - // CF v3 visibility_type value that maps to the domain's isPublic flag (see RawCloudServicePlan#derive). + private static final String PUBLIC_VISIBILITY = "public"; private V3ServicePlanMapper() { @@ -40,9 +35,10 @@ private static String extractBrokerCatalogId(V3ServicePlan.V3BrokerCatalog broke private static String extractServiceOfferingId(V3ServicePlan.V3Relationships relationships) { if (relationships == null || relationships.serviceOffering() == null || relationships.serviceOffering() - .data() == null) { + .data() == null) { return null; } + return relationships.serviceOffering() .data() .guid(); diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Space.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Space.java index 4e7c0d28c8..70e8c3f4bd 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Space.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Space.java @@ -20,6 +20,7 @@ public String organizationGuid() { .data() == null) { return null; } + return relationships.organization() .data() .guid(); diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3StackMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3StackMapper.java index 9d4c7c84d9..c821ec9104 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3StackMapper.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3StackMapper.java @@ -3,10 +3,6 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudStack; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudStack; -/** - * Maps the {@link V3Stack} wire model to the project's {@link CloudStack} domain object. Mirrors the OSS - * {@code RawCloudStack} adapter field-for-field, so both client implementations yield identical domain objects. - */ public final class V3StackMapper { private V3StackMapper() { diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3TaskMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3TaskMapper.java index 1341ea722e..bde50c807e 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3TaskMapper.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3TaskMapper.java @@ -3,10 +3,6 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudTask; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudTask; -/** - * Maps the {@link V3Task} wire model to the project's {@link CloudTask} domain object. Mirrors the OSS {@code RawCloudTask} adapter - * field-for-field, so both client implementations yield identical domain objects. - */ public final class V3TaskMapper { private V3TaskMapper() { @@ -27,6 +23,7 @@ private static CloudTask.Result parseResult(V3Task task) { if (task.result() == null) { return null; } + return ImmutableCloudTask.ImmutableResult.of(task.result() .failureReason()); } diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/CloudControllerClientIntegrationTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/CloudControllerClientIntegrationTest.java index 3b0e958345..9c7ea635fe 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/CloudControllerClientIntegrationTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/CloudControllerClientIntegrationTest.java @@ -12,6 +12,8 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.Lifecycle; import org.cloudfoundry.multiapps.controller.client.facade.domain.LifecycleType; import org.cloudfoundry.multiapps.controller.client.facade.domain.Staging; +import org.cloudfoundry.multiapps.controller.client.facade.oauth2.OAuthClient; +import org.cloudfoundry.multiapps.controller.client.facade.rest.CloudControllerRestClientFactory; import org.cloudfoundry.multiapps.controller.client.facade.rest.CloudSpaceClient; import org.cloudfoundry.multiapps.controller.client.facade.rest.ImmutableCloudControllerRestClientFactory; import org.cloudfoundry.multiapps.controller.client.facade.util.RestUtil; @@ -37,10 +39,10 @@ static void login() throws MalformedURLException { CloudCredentials credentials = getCloudCredentials(); URL apiUrl = URI.create(ITVariable.CF_API.getValue()) .toURL(); - var clientFactory = ImmutableCloudControllerRestClientFactory.builder() - .shouldTrustSelfSignedCertificates(true) - .build(); - var oauthClient = new RestUtil().createOAuthClientByControllerUrl(apiUrl, true); + CloudControllerRestClientFactory clientFactory = ImmutableCloudControllerRestClientFactory.builder() + .shouldTrustSelfSignedCertificates(true) + .build(); + OAuthClient oauthClient = new RestUtil().createOAuthClientByControllerUrl(apiUrl, true); oauthClient.init(credentials); CloudSpaceClient spaceClient = clientFactory.createSpaceClient(apiUrl, oauthClient, Collections.emptyMap()); target = spaceClient.getSpace(ITVariable.ORG.getValue(), ITVariable.SPACE.getValue()); @@ -66,7 +68,7 @@ protected Lifecycle createLifecycle(Staging staging) { .build(); } - var data = new HashMap(); + HashMap data = new HashMap(); data.put("buildpacks", staging.getBuildpacks()); data.put("stack", DEFAULT_STACK); diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3ClientPageUriTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3ClientPageUriTest.java index c5e4eb67a8..a9a385f183 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3ClientPageUriTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3ClientPageUriTest.java @@ -6,7 +6,7 @@ class CloudControllerV3ClientPageUriTest { @Test - void testAddsPageWhenAbsentAndKeepsExistingQuery() { + void testPageUriAddsPageWhenAbsentAndKeepsExistingQuery() { String uri = CloudControllerV3Client.pageUri("/v3/apps?per_page=5000&space_guids=abc", 3); Assertions.assertTrue(uri.contains("page=3"), uri); @@ -15,17 +15,16 @@ void testAddsPageWhenAbsentAndKeepsExistingQuery() { } @Test - void testReplacesExistingPageParam() { + void testPageUriReplacesExistingPageParam() { String uri = CloudControllerV3Client.pageUri("/v3/apps?per_page=5000&page=1", 4); Assertions.assertTrue(uri.contains("page=4"), uri); Assertions.assertFalse(uri.contains("page=1"), uri); - // per_page must survive the replacement. Assertions.assertTrue(uri.contains("per_page=5000"), uri); } @Test - void testPreservesMultipleFilters() { + void testPageUriPreservesMultipleFilters() { String first = "/v3/service_credential_bindings?per_page=5000&type=key&service_instance_guids=g1&names=n1"; String uri = CloudControllerV3Client.pageUri(first, 2); diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ReactiveFanOutTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ReactiveFanOutTest.java index 7cdc626d5c..752a9e8c3f 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ReactiveFanOutTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ReactiveFanOutTest.java @@ -14,24 +14,23 @@ class ReactiveFanOutTest { @Test - void testEmptyReturnsEmpty() { + void testMapConcurrentlyReturnsEmptyWhenEmpty() { Assertions.assertEquals(List.of(), ReactiveFanOut.mapConcurrently(List.of(), x -> x)); } @Test - void testSingleElementShortCircuits() { + void testMapConcurrentlyShortCircuitsWhenSingleElement() { List result = ReactiveFanOut.mapConcurrently(List.of("a"), s -> s + "!"); Assertions.assertEquals(List.of("a!"), result); } @Test - void testResultsPreserveInputOrderEvenWhenTasksFinishOutOfOrder() { + void testMapConcurrentlyResultPreserveInputOrderEvenWhenTasksFinishOutOfOrder() { List input = IntStream.rangeClosed(1, 20) .boxed() .toList(); - // Later items finish sooner (inverse sleep) — the result must still be in input order. List result = ReactiveFanOut.mapConcurrently(input, i -> { sleep((20 - i)); return i * 10; @@ -45,7 +44,7 @@ void testResultsPreserveInputOrderEvenWhenTasksFinishOutOfOrder() { @Test @Timeout(10) - void testWorkRunsConcurrently() throws Exception { + void testMapConcurrentlyWorkRunsConcurrently() throws Exception { int n = 8; // If the mapper ran sequentially, the barrier (which needs all n parties) would never trip and the test would time out. CyclicBarrier barrier = new CyclicBarrier(n); diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/jobs/OrphanedDataCleaner.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/jobs/OrphanedDataCleaner.java index 73cd64bc99..9b8fbabb61 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/jobs/OrphanedDataCleaner.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/jobs/OrphanedDataCleaner.java @@ -8,8 +8,10 @@ import org.cloudfoundry.multiapps.controller.client.facade.CloudCredentials; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; -import org.cloudfoundry.multiapps.controller.client.facade.rest.ImmutableCloudControllerRestClientFactory; +import org.cloudfoundry.multiapps.controller.client.facade.oauth2.OAuthClient; +import org.cloudfoundry.multiapps.controller.client.facade.rest.CloudControllerRestClientFactory; import org.cloudfoundry.multiapps.controller.client.facade.rest.CloudSpaceClient; +import org.cloudfoundry.multiapps.controller.client.facade.rest.ImmutableCloudControllerRestClientFactory; import org.cloudfoundry.multiapps.controller.core.auditlogging.MtaConfigurationPurgerAuditLog; import org.cloudfoundry.multiapps.controller.core.cf.OAuthClientFactory; import org.cloudfoundry.multiapps.controller.core.util.ApplicationConfiguration; @@ -97,13 +99,13 @@ protected void initSpaceClient() { CloudCredentials cloudCredentials = new CloudCredentials(configuration.getGlobalAuditorUser(), configuration.getGlobalAuditorPassword(), SecurityUtil.CLIENT_ID, SecurityUtil.CLIENT_SECRET, configuration.getGlobalAuditorOrigin()); - var clientFactory = ImmutableCloudControllerRestClientFactory.builder() - .connectTimeout(Duration.ofMinutes(5)) - .responseTimeout(Duration.ofMinutes(5)) - .connectionPoolSize(1) - .threadPoolSize(1) - .build(); - var oauthClient = oAuthClientFactory.createOAuthClient(); + CloudControllerRestClientFactory clientFactory = ImmutableCloudControllerRestClientFactory.builder() + .connectTimeout(Duration.ofMinutes(5)) + .responseTimeout(Duration.ofMinutes(5)) + .connectionPoolSize(1) + .threadPoolSize(1) + .build(); + OAuthClient oauthClient = oAuthClientFactory.createOAuthClient(); oauthClient.init(cloudCredentials); spaceClient = clientFactory.createSpaceClient(configuration.getControllerUrl(), oauthClient, Collections.emptyMap()); } diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceCreateUpdateServiceActionsStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceCreateUpdateServiceActionsStep.java index 818e6de5fc..bcb90f5197 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceCreateUpdateServiceActionsStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceCreateUpdateServiceActionsStep.java @@ -16,13 +16,13 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.MapUtils; import org.apache.commons.lang3.ObjectUtils; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.common.SLException; import org.cloudfoundry.multiapps.common.util.JsonUtil; import org.cloudfoundry.multiapps.controller.client.facade.CloudControllerClient; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceOperation; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudServiceInstanceExtended; import org.cloudfoundry.multiapps.controller.client.lib.domain.ImmutableCloudServiceInstanceExtended; From af165c3a8a98d3b155a7ddfc036515278fc733cb Mon Sep 17 00:00:00 2001 From: Krasimir Kargov Date: Thu, 10 Sep 2026 08:58:37 +0300 Subject: [PATCH 07/15] Adding unit tests for coverage and fixing Sonar LMCROSSITXSADEPLOY-3563 --- .../multiapps/controller/Messages.java | 6 + .../client/facade/domain/CloudAsyncJob.java | 1 + .../client/facade/domain/CloudEntity.java | 3 - .../client/facade/domain/JobState.java | 29 +---- .../facade/rest/ApplicationsV3Operations.java | 40 +++++- .../facade/rest/BuildsV3Operations.java | 4 + .../CloudControllerRestClientBuilder.java | 4 +- .../facade/rest/CloudControllerV3Client.java | 10 +- .../rest/CloudControllerV3Endpoints.java | 19 +-- .../facade/rest/DomainsV3Operations.java | 15 ++- .../facade/rest/PackagesV3Operations.java | 9 +- .../facade/rest/ProcessesV3Operations.java | 14 ++- .../facade/rest/RoutesV3Operations.java | 1 + .../rest/ServiceInstancesV3Operations.java | 5 +- .../facade/rest/ServiceKeysV3Operations.java | 3 +- .../facade/rest/ServicePlansV3Operations.java | 1 + .../facade/rest/resources/V3Application.java | 2 +- .../rest/resources/V3InstancesInfoMapper.java | 3 +- .../rest/resources/V3PackageMapper.java | 1 + .../rest/resources/V3ProcessMapper.java | 5 +- .../rest/resources/V3ResourceMappers.java | 7 +- .../facade/rest/DomainsV3OperationsTest.java | 94 ++++++++++++++ .../facade/rest/EventsV3OperationsTest.java | 118 ++++++++++++++++++ .../facade/rest/ReactiveFanOutTest.java | 33 ++--- .../ServiceInstancesV3OperationsTest.java | 118 ++++++++++++++++++ .../ServiceOfferingsV3OperationsTest.java | 102 +++++++++++++++ .../facade/rest/StacksV3OperationsTest.java | 95 ++++++++++++++ .../resources/V3ApplicationMapperTest.java | 87 +++++++++++++ .../resources/V3AuditEventMapperTest.java | 60 +++++++++ .../rest/resources/V3BuildMapperTest.java | 66 ++++++++++ .../rest/resources/V3DomainMapperTest.java | 58 +++++++++ .../resources/V3InstancesInfoMapperTest.java | 60 +++++++++ .../rest/resources/V3JobMapperTest.java | 92 ++++++++++++++ .../rest/resources/V3ListResponseTest.java | 50 ++++++++ .../rest/resources/V3PackageMapperTest.java | 85 +++++++++++++ .../rest/resources/V3ProcessMapperTest.java | 61 +++++++++ .../rest/resources/V3ResourceMappersTest.java | 94 ++++++++++++++ .../rest/resources/V3RoleMapperTest.java | 28 +++++ .../rest/resources/V3RouteMapperTest.java | 105 ++++++++++++++++ .../resources/V3ServiceBindingMapperTest.java | 70 +++++++++++ .../resources/V3ServiceBrokerMapperTest.java | 44 +++++++ .../V3ServiceInstanceMapperTest.java | 104 +++++++++++++++ .../resources/V3ServiceKeyMapperTest.java | 50 ++++++++ .../V3ServiceOfferingMapperTest.java | 73 +++++++++++ .../resources/V3ServicePlanMapperTest.java | 67 ++++++++++ .../rest/resources/V3StackMapperTest.java | 32 +++++ .../rest/resources/V3TaskMapperTest.java | 65 ++++++++++ 47 files changed, 1993 insertions(+), 100 deletions(-) create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3OperationsTest.java create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/EventsV3OperationsTest.java create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3OperationsTest.java create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3OperationsTest.java create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/StacksV3OperationsTest.java create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ApplicationMapperTest.java create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AuditEventMapperTest.java create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3BuildMapperTest.java create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3DomainMapperTest.java create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3InstancesInfoMapperTest.java create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3JobMapperTest.java create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ListResponseTest.java create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3PackageMapperTest.java create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ProcessMapperTest.java create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ResourceMappersTest.java create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RoleMapperTest.java create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RouteMapperTest.java create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBindingMapperTest.java create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBrokerMapperTest.java create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstanceMapperTest.java create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceKeyMapperTest.java create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceOfferingMapperTest.java create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServicePlanMapperTest.java create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3StackMapperTest.java create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3TaskMapperTest.java diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Messages.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Messages.java index 8705623590..bb9cdda346 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Messages.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Messages.java @@ -8,6 +8,10 @@ public class Messages { public static final String RATE_LIMITED_BY_CC_NO_HEADER_WAITING_MS = "CC returned 429 without Retry-After header. Waiting {} ms before retrying."; public static final String RANDOM_WAIT_BEFORE_RETRY_MS = "Waiting {} ms (randomized) before retrying failed CC operation."; + // WARN messages + public static final String COULD_NOT_PARSE_GUID_STRING = "Could not parse GUID string: \"{0}\""; + public static final String COULD_NOT_PARSE_DATE_STRING = "Could not parse date string: \"{0}\""; + // Exception messages public static final String ERROR_OCCURRED_SETTING_UP_DEFAULT_SSL_CONTEXT = "An error occurred while setting up the default SSLContext."; public static final String ERROR_OCCURRED_SETTING_UP_ALWAYS_APPROVING_SSL_CONTEXT = "An error occurred while setting up the always approving SSLContext."; @@ -43,6 +47,8 @@ public class Messages { public static final String UNKNOWN_JOB_STATE_0 = "Unknown job state: \"{0}\""; public static final String UNKNOWN_SERVICE_INSTANCE_TYPE_0 = "Unknown service instance type: \"{0}\""; public static final String UNKNOWN_USER_ROLE_0 = "Unknown user role: \"{0}\""; + public static final String CANNOT_CREATE_DOMAIN_0_WITHOUT_ORGANIZATION = "Cannot create domain \"{0}\" without specifying an organization."; + public static final String PACKAGE_0_NOT_FOUND = "Package \"{0}\" not found."; //Status texts public static final String NOT_FOUND = "Not Found"; diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudAsyncJob.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudAsyncJob.java index 20b52febc8..d458160350 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudAsyncJob.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudAsyncJob.java @@ -11,6 +11,7 @@ @JsonDeserialize(as = ImmutableCloudAsyncJob.class) public abstract class CloudAsyncJob extends CloudEntity implements Derivable { + @Nullable public abstract JobState getState(); @Nullable diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudEntity.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudEntity.java index aef922d829..0b12510362 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudEntity.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudEntity.java @@ -2,14 +2,11 @@ import java.util.UUID; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; - import org.cloudfoundry.multiapps.controller.client.facade.Nullable; /** * Do not extend {@code Derivable} in this interface. It is tempting, because all of its children have the same implementation, but * implementing the {@code derive()} method here leads to this bug: https://github.com/immutables/immutables/issues/1045 - * */ public abstract class CloudEntity { diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/JobState.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/JobState.java index 9c3445f45a..de2a7a4de2 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/JobState.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/JobState.java @@ -1,34 +1,7 @@ package org.cloudfoundry.multiapps.controller.client.facade.domain; -import java.text.MessageFormat; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonValue; -import org.cloudfoundry.multiapps.controller.Messages; - public enum JobState { - PROCESSING("processing"), POLLING("polling"), COMPLETE("complete"), FAILED("failed"); - - private final String value; - - JobState(String value) { - this.value = value; - } - - @JsonValue - public String getValue() { - return value; - } - - @JsonCreator - public static JobState from(String value) { - for (JobState state : values()) { - if (state.value.equals(value)) { - return state; - } - } + PROCESSING, POLLING, COMPLETE, FAILED - throw new IllegalArgumentException(MessageFormat.format(Messages.UNKNOWN_JOB_STATE_0, value)); - } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3Operations.java index 12a19a3b8b..d7ac2ceb98 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3Operations.java @@ -40,6 +40,7 @@ public UUID createApplication(ApplicationToCreateDto dto) { throw new CloudOperationException(HttpStatus.BAD_REQUEST, Messages.BAD_REQUEST, Messages.TARGET_SPACE_REQUIRED_TO_CREATE_AN_APPLICATION); } + Map body = new HashMap<>(); body.put("name", dto.getName()); body.put("lifecycle", buildLifecycle(dto.getStaging())); @@ -48,12 +49,14 @@ public UUID createApplication(ApplicationToCreateDto dto) { if (dto.getEnv() != null) { body.put("environment_variables", dto.getEnv()); } + if (dto.getMetadata() != null) { body.put("metadata", Map.of("labels", dto.getMetadata() .getLabels(), "annotations", dto.getMetadata() .getAnnotations())); } + V3Application created = cc.getRestClient() .post() .uri(CloudControllerV3Endpoints.APPS) @@ -61,16 +64,20 @@ public UUID createApplication(ApplicationToCreateDto dto) { .retrieve() .body(V3Application.class); UUID appGuid = UUID.fromString(created.guid()); + Map scale = new HashMap<>(); if (dto.getMemoryInMb() != null) { scale.put("memory_in_mb", dto.getMemoryInMb()); } + if (dto.getDiskQuotaInMb() != null) { scale.put("disk_in_mb", dto.getDiskQuotaInMb()); } + if (!scale.isEmpty()) { scaleWebProcess(appGuid, scale); } + return appGuid; } @@ -78,30 +85,37 @@ private Map buildLifecycle(Staging staging) { if (staging == null) { return Map.of("type", "buildpack", "data", Map.of()); } + if (staging.getDockerInfo() != null) { return Map.of("type", "docker", "data", Map.of()); } + String type = staging.getLifecycleType() != null ? staging.getLifecycleType() .name() .toLowerCase() : "buildpack"; + Map data = new HashMap<>(); if (staging.getBuildpacks() != null) { data.put("buildpacks", staging.getBuildpacks()); } + if (staging.getStackName() != null) { data.put("stack", staging.getStackName()); } + return Map.of("type", type, "data", data); } public void deleteApplication(String applicationName) { UUID applicationGuid = getApplicationGuid(applicationName); + var response = cc.getRestClient() .delete() .uri(CloudControllerV3Endpoints.APP_BY_GUID, applicationGuid) .retrieve() .toBodilessEntity(); + cc.followAsyncJob(response, DELETE_JOB_TIMEOUT); } @@ -111,6 +125,7 @@ public CloudApplication getApplication(String applicationName) { public CloudApplication getApplication(String applicationName, boolean required) { V3Application app = findApplicationByName(applicationName); + if (app == null) { if (required) { throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, @@ -118,6 +133,7 @@ public CloudApplication getApplication(String applicationName, boolean required) } return null; } + return V3ApplicationMapper.toCloudApplication(app, target); } @@ -127,14 +143,18 @@ public UUID getApplicationGuid(String applicationName) { public String getApplicationName(UUID applicationGuid) { V3Application app = cc.get(CloudControllerV3Endpoints.APPS + "/" + applicationGuid, V3Application.class); + return app == null ? null : app.name(); } public Map getApplicationEnvironment(UUID applicationGuid) { - V3Application.V3EnvironmentVariables env = cc.get(CloudControllerV3Endpoints.APPS + "/" + applicationGuid - + "/environment_variables", - V3Application.V3EnvironmentVariables.class); - return env == null || env.var() == null ? Map.of() : env.var(); + V3Application.V3EnvironmentVariables environmentVariablesJson = cc.get(CloudControllerV3Endpoints.APPS + "/" + applicationGuid + + "/environment_variables", + V3Application.V3EnvironmentVariables.class); + + return environmentVariablesJson == null || environmentVariablesJson.environmentVariables() == null + ? Map.of() + : environmentVariablesJson.environmentVariables(); } public Map getApplicationEnvironment(String applicationName) { @@ -149,9 +169,11 @@ public List getApplications() { public List getApplicationsByMetadataLabelSelector(String labelSelector) { String query = applicationsQuery(null); + if (labelSelector != null) { query = query + CloudControllerV3Endpoints.AMPERSAND_LABEL_SELECTOR + labelSelector; } + return listApplications(query).stream() .map(app -> V3ApplicationMapper.toCloudApplication(app, target)) .toList(); @@ -159,6 +181,7 @@ public List getApplicationsByMetadataLabelSelector(String labe public void startApplication(String applicationName) { UUID guid = getApplicationGuid(applicationName); + cc.getRestClient() .post() .uri(CloudControllerV3Endpoints.APP_START, guid) @@ -168,6 +191,7 @@ public void startApplication(String applicationName) { public void stopApplication(String applicationName) { UUID guid = getApplicationGuid(applicationName); + cc.getRestClient() .post() .uri(CloudControllerV3Endpoints.APP_STOP, guid) @@ -177,6 +201,7 @@ public void stopApplication(String applicationName) { public void rename(String applicationName, String newName) { UUID guid = getApplicationGuid(applicationName); + try { cc.getRestClient() .patch() @@ -207,6 +232,7 @@ public void updateApplicationDiskQuota(String applicationName, int disk) { public void updateApplicationEnv(String applicationName, Map env) { UUID guid = getApplicationGuid(applicationName); + cc.getRestClient() .patch() .uri(CloudControllerV3Endpoints.APP_ENV_VARS, guid) @@ -244,7 +270,8 @@ private void scaleWebProcess(UUID applicationGuid, Map scaleBody private V3Application findApplicationByName(String applicationName) { List apps = listApplications(applicationsQuery(applicationName)); - return apps.isEmpty() ? null : apps.get(0); + + return apps.isEmpty() ? null : apps.getFirst(); } private List listApplications(String query) { @@ -254,14 +281,17 @@ private List listApplications(String query) { private String applicationsQuery(String name) { StringBuilder query = new StringBuilder(CloudControllerV3Endpoints.APPS + CloudControllerV3Endpoints.QUERY_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE); + if (target != null && target.getGuid() != null) { query.append(CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) .append(target.getGuid()); } + if (name != null) { query.append(CloudControllerV3Endpoints.AMPERSAND_NAMES) .append(name); } + return query.toString(); } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3Operations.java index c9416c6bd0..6f14e93898 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3Operations.java @@ -25,17 +25,20 @@ public CloudBuild createBuild(UUID packageGuid) { .body(Map.of("package", Map.of("guid", packageGuid.toString()))) .retrieve() .body(V3Build.class); + return V3BuildMapper.toCloudBuild(build); } public CloudBuild getBuild(UUID buildGuid) { V3Build build = cc.get(CloudControllerV3Endpoints.BUILDS + "/" + buildGuid, V3Build.class); + return V3BuildMapper.toCloudBuild(build); } public List getBuildsForApplication(UUID applicationGuid) { String uri = CloudControllerV3Endpoints.APPS + "/" + applicationGuid + "/builds" + CloudControllerV3Endpoints.QUERY_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; + return cc.list(uri, new ParameterizedTypeReference>() { }) .stream() @@ -46,6 +49,7 @@ public List getBuildsForApplication(UUID applicationGuid) { public List getBuildsForPackage(UUID packageGuid) { String uri = CloudControllerV3Endpoints.BUILDS + CloudControllerV3Endpoints.QUERY_PACKAGE_GUIDS + packageGuid + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; + return cc.list(uri, new ParameterizedTypeReference>() { }) .stream() diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientBuilder.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientBuilder.java index 754b658584..2e33365552 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientBuilder.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientBuilder.java @@ -86,9 +86,7 @@ private static ExchangeFilterFunction authAndTagsFilter(OAuthClient oAuthClient, requestBuilder.header(HttpHeaders.AUTHORIZATION, authorizationValue); } - requestTags.forEach((String key, String value) -> { - requestBuilder.header(key, value); - }); + requestTags.forEach(requestBuilder::header); return next.exchange(requestBuilder.build()); }; diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Client.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Client.java index 7ad492bafa..84bf32cdd8 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Client.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Client.java @@ -56,6 +56,10 @@ public Optional getOptional(String uri, Class responseType) { } public List list(String firstPageUri, ParameterizedTypeReference> pageType) { + if (firstPageUri == null) { + return new ArrayList<>(); + } + String firstPageRelativeUri = toRelativeIfAbsolute(firstPageUri); V3ListResponse firstPage = restClient.get() @@ -146,7 +150,7 @@ public V3Job waitForAsyncJob(String jobGuid, Duration timeout) { Duration interval = Constants.JOB_POLL_MIN_INTERVAL; while (true) { - V3Job asyncJob = get("/v3/jobs/" + jobGuid, V3Job.class); + V3Job asyncJob = get(CloudControllerV3Endpoints.JOBS + jobGuid, V3Job.class); if (asyncJob != null && asyncJob.isTerminal()) { @@ -202,13 +206,13 @@ private static CloudOperationException jobFailed(V3Job job) { } private static String extractJobGuid(String location) { - int index = location.lastIndexOf("/v3/jobs/"); + int index = location.lastIndexOf(CloudControllerV3Endpoints.JOBS); if (index < 0) { return location.substring(location.lastIndexOf('/') + 1); } - return location.substring(index + "/v3/jobs/".length()); + return location.substring(index + CloudControllerV3Endpoints.JOBS.length()); } private String toRelativeIfAbsolute(String href) { diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Endpoints.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Endpoints.java index 5cbce89a69..5f82bf893b 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Endpoints.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Endpoints.java @@ -1,23 +1,7 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest; -/** - * The Cloud Controller v3 API surface used by the in-house CF client — resource roots, {@code {guid}} path templates, and query-parameter - * fragments — in one place, so the {@code V3Operations} classes reference named constants instead of inline "magic string" URLs. - *

- * Two URI mechanisms are represented, matching how the operations classes issue requests: - *

    - *
  • Path templates (e.g. {@link #APP_BY_GUID}) contain Spring {@code {placeholder}}s expanded by {@code RestClient.uri(template, - * args...)} — the placeholders are intentionally kept literally.
  • - *
  • Roots + query fragments (e.g. {@link #APPS} + {@link #QUERY_PER_PAGE} + {@link #AMPERSAND_SPACE_GUIDS}) are concatenated by the caller - * into a query string. The fragments include their leading {@code ?}/{@code &} and trailing {@code =} exactly as the callers write them, - * so swapping a literal for a constant is a byte-identical substitution.
  • - *
- */ public final class CloudControllerV3Endpoints { - private CloudControllerV3Endpoints() { - } - public static final int DEFAULT_PAGE_SIZE = 5000; public static final String APPS = "/v3/apps"; @@ -31,7 +15,8 @@ private CloudControllerV3Endpoints() { public static final String PACKAGES = "/v3/packages"; public static final String BUILDS = "/v3/builds"; public static final String TASKS = "/v3/tasks"; - public static final String PROCESSES = "/v3/processes"; + // public static final String PROCESSES = "/v3/processes"; + public static final String PROCESSES = "/processes/"; public static final String STACKS = "/v3/stacks"; public static final String AUDIT_EVENTS = "/v3/audit_events"; public static final String JOBS = "/v3/jobs"; diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3Operations.java index f219954307..307519f105 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3Operations.java @@ -4,7 +4,6 @@ import java.util.List; import java.util.Map; import java.util.UUID; -import java.util.stream.Collectors; import org.cloudfoundry.multiapps.controller.Constants; import org.cloudfoundry.multiapps.controller.Messages; @@ -58,21 +57,21 @@ public CloudDomain getDefaultDomain() { public List getDomains() { return getAllDomains().stream() .map(V3DomainMapper::toCloudDomain) - .collect(Collectors.toList()); + .toList(); } public List getSharedDomains() { return getAllDomains().stream() .filter(domain -> !domain.isPrivate()) .map(V3DomainMapper::toCloudDomain) - .collect(Collectors.toList()); + .toList(); } public List getPrivateDomains() { return getAllDomains().stream() .filter(V3Domain::isPrivate) .map(V3DomainMapper::toCloudDomain) - .collect(Collectors.toList()); + .toList(); } public List getDomainsForOrganization() { @@ -83,7 +82,7 @@ public List getDomainsForOrganization() { return cc.list(uri, DOMAIN_LIST_TYPE) .stream() .map(V3DomainMapper::toCloudDomain) - .collect(Collectors.toList()); + .toList(); } private List getAllDomains() { @@ -114,6 +113,12 @@ private CloudDomain findDomainByName(String name) { } private void doCreateDomain(String name) { + UUID organizationGuid = getTargetOrganizationGuid(); + if (organizationGuid == null) { + throw new CloudOperationException(HttpStatus.BAD_REQUEST, Messages.BAD_REQUEST, + MessageFormat.format(Messages.CANNOT_CREATE_DOMAIN_0_WITHOUT_ORGANIZATION, name)); + } + cc.getRestClient() .post() .uri(CloudControllerV3Endpoints.DOMAINS) diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3Operations.java index d58fd4ba14..5da52aa034 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3Operations.java @@ -59,9 +59,14 @@ public PackagesV3Operations(CloudControllerV3Client cc, CloudSpace target, } public CloudPackage getPackage(UUID packageGuid) { - V3Package resource = cc.get(CloudControllerV3Endpoints.PACKAGES + "/" + packageGuid, V3Package.class); + V3Package packageResource = cc.get(CloudControllerV3Endpoints.PACKAGES + "/" + packageGuid, V3Package.class); - return resource == null ? null : V3PackageMapper.toCloudPackage(resource); + if (packageResource == null) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + MessageFormat.format(Messages.PACKAGE_0_NOT_FOUND, packageGuid)); + } + + return V3PackageMapper.toCloudPackage(packageResource); } public List getPackagesForApplication(UUID applicationGuid) { diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3Operations.java index ed19e19222..b1e6170439 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3Operations.java @@ -39,7 +39,7 @@ public ProcessesV3Operations(CloudControllerV3Client cc, CloudSpace target) { public CloudProcess getApplicationProcess(UUID applicationGuid) { V3Process applicationProcess = cc.get( - CloudControllerV3Endpoints.APPS + "/" + applicationGuid + "/processes/" + Constants.WEB_PROCESS_TYPE, + CloudControllerV3Endpoints.APPS + "/" + applicationGuid + CloudControllerV3Endpoints.PROCESSES + Constants.WEB_PROCESS_TYPE, V3Process.class); return applicationProcess == null ? null : V3ProcessMapper.toCloudProcess(applicationProcess); @@ -61,9 +61,10 @@ public InstancesInfo getApplicationInstances(UUID applicationGuid) { } private InstancesInfo findApplicationInstances(UUID applicationGuid) { - V3Process.V3ProcessStats stats = cc.get(CloudControllerV3Endpoints.APPS + "/" + applicationGuid + "/processes/" - + Constants.WEB_PROCESS_TYPE + "/stats", - V3Process.V3ProcessStats.class); + V3Process.V3ProcessStats stats = cc.get( + CloudControllerV3Endpoints.APPS + "/" + applicationGuid + CloudControllerV3Endpoints.PROCESSES + Constants.WEB_PROCESS_TYPE + + "/stats", + V3Process.V3ProcessStats.class); return V3InstancesInfoMapper.toInstancesInfo(stats); } @@ -165,8 +166,9 @@ private void updateApplicationProcess(UUID applicationGuid, Staging staging) { staging.getAppFeatures() .forEach((featureName, enabled) -> updateAppFeature(applicationGuid, featureName, enabled)); - V3Process process = cc.get(CloudControllerV3Endpoints.APPS + "/" + applicationGuid + "/processes/" + Constants.WEB_PROCESS_TYPE, - V3Process.class); + V3Process process = cc.get( + CloudControllerV3Endpoints.APPS + "/" + applicationGuid + CloudControllerV3Endpoints.PROCESSES + Constants.WEB_PROCESS_TYPE, + V3Process.class); Map updateProcessBody = new HashMap<>(); updateProcessBody.put("command", staging.getCommand()); diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3Operations.java index c0363e6d90..4ff3bce492 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3Operations.java @@ -73,6 +73,7 @@ public void deleteOrphanedRoutes() { public List getRoutes(String domainName) { assertSpaceProvided("get routes for domain"); UUID domainGuid = getRequiredDomainGuid(domainName); + return findRoutesByDomainGuid(domainGuid); } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3Operations.java index e004dcc6be..aef28801ee 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3Operations.java @@ -7,7 +7,6 @@ import java.util.List; import java.util.Map; import java.util.UUID; -import java.util.stream.Collectors; import org.cloudfoundry.multiapps.controller.Constants; import org.cloudfoundry.multiapps.controller.Messages; @@ -173,7 +172,7 @@ public List getServiceInstancesWithoutAuxiliaryContentByMe return cc.list(uri, SERVICE_INSTANCE_LIST_TYPE) .stream() .map(resource -> V3ServiceInstanceMapper.toCloudServiceInstance(resource, null, null)) - .collect(Collectors.toList()); + .toList(); } public void updateServicePlan(String serviceName, String planName) { @@ -399,7 +398,7 @@ private List findServiceOfferingGuids(String label, String broker) { }) .stream() .map(V3ServiceOffering::guid) - .collect(Collectors.toList()); + .toList(); } private void doDeleteServiceInstance(UUID serviceInstanceGuid) { diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3Operations.java index b85c8b7aef..08b74ae624 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3Operations.java @@ -170,8 +170,7 @@ private Optional extractJobGuid(ResponseEntity accepted) { } String href = location.toString(); - int index = href.lastIndexOf("/v3/jobs/"); - String jobGuid = index < 0 ? href.substring(href.lastIndexOf('/') + 1) : href.substring(index + "/v3/jobs/".length()); + String jobGuid = href.substring(href.lastIndexOf('/') + 1); return Optional.of(jobGuid); } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3Operations.java index 7a9c033585..a6e8f8741b 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3Operations.java @@ -36,6 +36,7 @@ public ServicePlansV3Operations(CloudControllerV3Client cc, CloudSpace target) { public void updateServicePlanVisibilityForBroker(String name, ServicePlanVisibility visibility) { UUID brokerGuid = getRequiredServiceBrokerGuid(name); List servicePlanGuids = findServicePlanGuidsByBrokerGuid(brokerGuid); + for (UUID servicePlanGuid : servicePlanGuids) { updateServicePlanVisibility(servicePlanGuid, visibility); } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Application.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Application.java index 9394d5c894..9c821a7066 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Application.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Application.java @@ -43,7 +43,7 @@ public record V3RelationshipData(@JsonProperty("guid") String guid) { } @JsonIgnoreProperties(ignoreUnknown = true) - public record V3EnvironmentVariables(@JsonProperty("var") Map var) { + public record V3EnvironmentVariables(@JsonProperty("var") Map environmentVariables) { } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3InstancesInfoMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3InstancesInfoMapper.java index ed1109da12..a1cc4f16bb 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3InstancesInfoMapper.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3InstancesInfoMapper.java @@ -2,7 +2,6 @@ import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableInstanceInfo; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableInstancesInfo; @@ -28,7 +27,7 @@ private static List parseProcessStatistics(List result = operations.getDomains(); + + Assertions.assertEquals(2, result.size()); + } + + @Test + void testGetSharedDomainsReturnsOnlyNonPrivate() { + mockDomainList(getSharedDomain("shared.example.com"), getPrivateDomain("private.example.com")); + + List result = operations.getSharedDomains(); + + Assertions.assertEquals(1, result.size()); + Assertions.assertEquals("shared.example.com", result.getFirst() + .getName()); + } + + @Test + void testGetPrivateDomainsReturnsOnlyPrivate() { + mockDomainList(getSharedDomain("shared.example.com"), getPrivateDomain("private.example.com")); + + List result = operations.getPrivateDomains(); + + Assertions.assertEquals(1, result.size()); + Assertions.assertEquals("private.example.com", result.getFirst() + .getName()); + } + + @Test + void testGetSharedDomainsReturnsEmptyWhenNoDomains() { + mockDomainList(); + + Assertions.assertTrue(operations.getSharedDomains() + .isEmpty()); + } + + private void mockDomainList(V3Domain... domains) { + Mockito.when(cc.list(ArgumentMatchers.anyString(), + ArgumentMatchers.>> any())) + .thenReturn(List.of(domains)); + } + + private static V3Domain getSharedDomain(String name) { + return new V3Domain(GUID_STRING, name, null, null, null, null); + } + + private static V3Domain getPrivateDomain(String name) { + V3DomainRelationships relationships = new V3DomainRelationships(new V3ToOneRelationship(new V3RelationshipData(ORG_GUID))); + return new V3Domain(GUID_STRING, name, null, null, null, relationships); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/EventsV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/EventsV3OperationsTest.java new file mode 100644 index 0000000000..61a5166f34 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/EventsV3OperationsTest.java @@ -0,0 +1,118 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.List; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEvent; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Application; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3AuditEvent; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; + +class EventsV3OperationsTest { + + private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + private static final UUID SPACE_GUID = UUID.fromString("11111111-2222-3333-4444-555555555555"); + private static final UUID TARGET_GUID = UUID.fromString("99999999-8888-7777-6666-555555555555"); + + @Mock + private CloudControllerV3Client cc; + @Mock + private CloudSpace target; + + private EventsV3Operations operations; + + @BeforeEach + void setUp() throws Exception { + MockitoAnnotations.openMocks(this) + .close(); + operations = new EventsV3Operations(cc, target); + } + + @Test + void testGetEventsMapsResults() { + Mockito.when( + cc.list(ArgumentMatchers.anyString(), ArgumentMatchers.>> any())) + .thenReturn(List.of(getAuditEvent("audit.app.update"), getAuditEvent("audit.app.create"))); + + List result = operations.getEvents(); + + Assertions.assertEquals(2, result.size()); + Assertions.assertEquals("audit.app.update", result.get(0) + .getType()); + } + + @Test + void testGetEventsByTargetIncludesTargetGuidInQuery() { + Mockito.when( + cc.list(ArgumentMatchers.anyString(), ArgumentMatchers.>> any())) + .thenReturn(List.of()); + + operations.getEventsByTarget(TARGET_GUID); + + ArgumentCaptor uriCaptor = ArgumentCaptor.forClass(String.class); + Mockito.verify(cc) + .list(uriCaptor.capture(), ArgumentMatchers.>> any()); + Assertions.assertTrue(uriCaptor.getValue() + .contains("/v3/audit_events"), uriCaptor.getValue()); + Assertions.assertTrue(uriCaptor.getValue() + .contains("target_guids=" + TARGET_GUID), uriCaptor.getValue()); + } + + @Test + void testGetApplicationEventsThrowsWhenApplicationNotFound() { + Mockito.when(target.getGuid()) + .thenReturn(SPACE_GUID); + + Mockito.when(cc.list(ArgumentMatchers.contains("/v3/apps"), + ArgumentMatchers.>> any())) + .thenReturn(List.of()); + + CloudOperationException thrown = Assertions.assertThrows(CloudOperationException.class, + () -> operations.getApplicationEvents("missing-app")); + + Assertions.assertEquals(HttpStatus.NOT_FOUND, thrown.getStatusCode()); + } + + @Test + void testGetApplicationEventsQueryScopesAppsToSpaceWhenTargetPresent() { + Mockito.when(target.getGuid()) + .thenReturn(SPACE_GUID); + Mockito.when(cc.list(ArgumentMatchers.contains("/v3/apps"), + ArgumentMatchers.>> any())) + .thenReturn(List.of(new V3Application(GUID_STRING, "my-app", "STARTED", null, null, null, null, null))); + Mockito.when(cc.list(ArgumentMatchers.contains("/v3/audit_events"), + ArgumentMatchers.>> any())) + .thenReturn(List.of()); + + operations.getApplicationEvents("my-app"); + + ArgumentCaptor uriCaptor = ArgumentCaptor.forClass(String.class); + Mockito.verify(cc, Mockito.times(2)) + .list(uriCaptor.capture(), ArgumentMatchers.>> any()); + String appsQuery = uriCaptor.getAllValues() + .stream() + .filter(uri -> uri.contains("/v3/apps")) + .findFirst() + .orElseThrow(); + Assertions.assertTrue(appsQuery.contains("space_guids=" + SPACE_GUID), appsQuery); + Assertions.assertTrue(appsQuery.contains("names=my-app"), appsQuery); + } + + private static V3AuditEvent getAuditEvent(String type) { + return new V3AuditEvent(GUID_STRING, null, null, type, new V3AuditEvent.V3Participant(GUID_STRING, "user", "admin"), + new V3AuditEvent.V3Participant(GUID_STRING, "app", "my-app")); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ReactiveFanOutTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ReactiveFanOutTest.java index 752a9e8c3f..4fa71742a3 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ReactiveFanOutTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ReactiveFanOutTest.java @@ -1,6 +1,7 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest; import java.util.List; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.IntStream; @@ -26,13 +27,25 @@ void testMapConcurrentlyShortCircuitsWhenSingleElement() { } @Test - void testMapConcurrentlyResultPreserveInputOrderEvenWhenTasksFinishOutOfOrder() { + @Timeout(10) + void testMapConcurrentlyResultPreserveInputOrderEvenWhenTasksFinishOutOfOrder() throws Exception { List input = IntStream.rangeClosed(1, 20) .boxed() .toList(); + CountDownLatch lastItemDone = new CountDownLatch(1); List result = ReactiveFanOut.mapConcurrently(input, i -> { - sleep((20 - i)); + if (i == 20) { + lastItemDone.countDown(); + } else if (i == 1) { + try { + lastItemDone.await(); + } catch (InterruptedException e) { + Thread.currentThread() + .interrupt(); + throw new IllegalStateException(e); + } + } return i * 10; }); @@ -44,9 +57,8 @@ void testMapConcurrentlyResultPreserveInputOrderEvenWhenTasksFinishOutOfOrder() @Test @Timeout(10) - void testMapConcurrentlyWorkRunsConcurrently() throws Exception { + void testMapConcurrentlyWorkRunsConcurrently() { int n = 8; - // If the mapper ran sequentially, the barrier (which needs all n parties) would never trip and the test would time out. CyclicBarrier barrier = new CyclicBarrier(n); List input = IntStream.range(0, n) .boxed() @@ -65,7 +77,7 @@ void testMapConcurrentlyWorkRunsConcurrently() throws Exception { } @Test - void testFirstFailurePropagatesUnwrapped() { + void testMapConcurrentlyPropagatesUnwrappedOnFirstFailure() { List input = List.of(1, 2, 3, 4); CloudOperationException thrown = Assertions.assertThrows(CloudOperationException.class, @@ -80,7 +92,7 @@ void testFirstFailurePropagatesUnwrapped() { } @Test - void testMapperInvokedOncePerItem() { + void testMapConcurrentlyIsInvokedOncePerItem() { List input = IntStream.range(0, 50) .boxed() .toList(); @@ -95,13 +107,4 @@ void testMapperInvokedOncePerItem() { Assertions.assertEquals(input.size(), invocations.get()); } - private static void sleep(long millis) { - try { - Thread.sleep(millis); - } catch (InterruptedException e) { - Thread.currentThread() - .interrupt(); - } - } - } diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3OperationsTest.java new file mode 100644 index 0000000000..8451dff9b8 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3OperationsTest.java @@ -0,0 +1,118 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.List; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceInstance; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentMatchers; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; + +class ServiceInstancesV3OperationsTest { + + private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + private static final UUID SPACE_GUID = UUID.fromString("11111111-2222-3333-4444-555555555555"); + + @Mock + private CloudControllerV3Client cc; + @Mock + private CloudSpace target; + @Mock + private CloudMetadata targetMetadata; + + private ServiceInstancesV3Operations operations; + + @BeforeEach + void setUp() throws Exception { + MockitoAnnotations.openMocks(this) + .close(); + Mockito.when(target.getMetadata()) + .thenReturn(targetMetadata); + Mockito.when(targetMetadata.getGuid()) + .thenReturn(SPACE_GUID); + operations = new ServiceInstancesV3Operations(cc, target); + } + + @Test + void testGetRequiredServiceInstanceGuidReturnsGuidWhenFound() { + mockServiceInstanceList(getManagedInstance("my-service")); + + UUID result = operations.getRequiredServiceInstanceGuid("my-service"); + + Assertions.assertEquals(GUID_STRING, result.toString()); + } + + @Test + void testGetRequiredServiceInstanceGuidThrowsWhenNotFound() { + mockServiceInstanceList(); + + CloudOperationException thrown = Assertions.assertThrows(CloudOperationException.class, + () -> operations.getRequiredServiceInstanceGuid("missing")); + + Assertions.assertEquals(HttpStatus.NOT_FOUND, thrown.getStatusCode()); + } + + @Test + void testGetServiceInstanceWithoutAuxiliaryContentReturnsNullWhenNotRequiredAndMissing() { + mockServiceInstanceList(); + + Assertions.assertNull(operations.getServiceInstanceWithoutAuxiliaryContent("missing", false)); + } + + @Test + void testGetServiceInstanceWithoutAuxiliaryContentThrowsWhenRequiredAndMissing() { + mockServiceInstanceList(); + + CloudOperationException thrown = Assertions.assertThrows(CloudOperationException.class, + () -> operations.getServiceInstanceWithoutAuxiliaryContent("missing")); + + Assertions.assertEquals(HttpStatus.NOT_FOUND, thrown.getStatusCode()); + } + + @Test + void testGetServiceInstanceWithoutAuxiliaryContentMapsWhenFound() { + mockServiceInstanceList(getManagedInstance("my-service")); + + CloudServiceInstance result = operations.getServiceInstanceWithoutAuxiliaryContent("my-service"); + + Assertions.assertEquals("my-service", result.getName()); + } + + @Test + void testGetServiceInstanceNameReturnsNameFromGet() { + Mockito.when(cc.get(ArgumentMatchers.contains("/v3/service_instances/"), ArgumentMatchers.eq(V3ServiceInstance.class))) + .thenReturn(getManagedInstance("my-service")); + + Assertions.assertEquals("my-service", operations.getServiceInstanceName(SPACE_GUID)); + } + + @Test + void testGetServiceInstanceNameReturnsNullWhenGetReturnsNull() { + Mockito.when(cc.get(ArgumentMatchers.anyString(), ArgumentMatchers.eq(V3ServiceInstance.class))) + .thenReturn(null); + + Assertions.assertNull(operations.getServiceInstanceName(SPACE_GUID)); + } + + private void mockServiceInstanceList(V3ServiceInstance... instances) { + Mockito.when(cc.list(ArgumentMatchers.anyString(), + ArgumentMatchers.>> any())) + .thenReturn(List.of(instances)); + } + + private static V3ServiceInstance getManagedInstance(String name) { + return new V3ServiceInstance(GUID_STRING, name, "managed", null, null, null, null, null, null, null); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3OperationsTest.java new file mode 100644 index 0000000000..a9204e7683 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3OperationsTest.java @@ -0,0 +1,102 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.List; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceOffering; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceOffering; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServicePlan; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.springframework.core.ParameterizedTypeReference; + +class ServiceOfferingsV3OperationsTest { + + private static final String OFFERING_GUID = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + private static final String PLAN_GUID = "22222222-3333-4444-5555-666666666666"; + private static final UUID SPACE_GUID = UUID.fromString("11111111-2222-3333-4444-555555555555"); + + @Mock + private CloudControllerV3Client cc; + @Mock + private CloudSpace target; + + private ServiceOfferingsV3Operations operations; + + @BeforeEach + void setUp() throws Exception { + MockitoAnnotations.openMocks(this) + .close(); + operations = new ServiceOfferingsV3Operations(cc, target); + } + + @Test + void testGetServiceOfferingsMapsOfferingWithItsPlans() { + Mockito.when(cc.list(ArgumentMatchers.contains("/v3/service_offerings"), + ArgumentMatchers.>> any())) + .thenReturn(List.of(getOffering())); + Mockito.when(cc.list(ArgumentMatchers.contains("/v3/service_plans"), + ArgumentMatchers.>> any())) + .thenReturn(List.of(getPlan("plan-a"), getPlan("plan-b"))); + + List result = operations.getServiceOfferings(); + + Assertions.assertEquals(1, result.size()); + Assertions.assertEquals("my-offering", result.get(0) + .getName()); + Assertions.assertEquals(2, result.get(0) + .getServicePlans() + .size()); + } + + @Test + void testGetServiceOfferingsScopesQueryToSpaceWhenTargetHasGuid() { + Mockito.when(target.getGuid()) + .thenReturn(SPACE_GUID); + Mockito.when(cc.list(ArgumentMatchers.contains("/v3/service_offerings"), + ArgumentMatchers.>> any())) + .thenReturn(List.of()); + + operations.getServiceOfferings(); + + ArgumentCaptor uriCaptor = ArgumentCaptor.forClass(String.class); + Mockito.verify(cc) + .list(uriCaptor.capture(), ArgumentMatchers.>> any()); + Assertions.assertTrue(uriCaptor.getValue() + .contains("space_guids=" + SPACE_GUID), uriCaptor.getValue()); + } + + @Test + void testGetServiceOfferingsExcludesSpaceGuidWhenTargetGuidNull() { + Mockito.when(target.getGuid()) + .thenReturn(null); + Mockito.when(cc.list(ArgumentMatchers.contains("/v3/service_offerings"), + ArgumentMatchers.>> any())) + .thenReturn(List.of()); + + operations.getServiceOfferings(); + + ArgumentCaptor uriCaptor = ArgumentCaptor.forClass(String.class); + Mockito.verify(cc) + .list(uriCaptor.capture(), ArgumentMatchers.>> any()); + Assertions.assertFalse(uriCaptor.getValue() + .contains("space_guids="), uriCaptor.getValue()); + } + + private static V3ServiceOffering getOffering() { + return new V3ServiceOffering(OFFERING_GUID, "my-offering", "desc", true, false, null, null, null, null, null, null); + } + + private static V3ServicePlan getPlan(String name) { + return new V3ServicePlan(PLAN_GUID, name, null, true, "public", null, null, null, null, null); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/StacksV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/StacksV3OperationsTest.java new file mode 100644 index 0000000000..435924d2cb --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/StacksV3OperationsTest.java @@ -0,0 +1,95 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.List; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudStack; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Stack; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; + +class StacksV3OperationsTest { + + private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + + @Mock + private CloudControllerV3Client cc; + + private StacksV3Operations operations; + + @BeforeEach + void setUp() throws Exception { + MockitoAnnotations.openMocks(this) + .close(); + operations = new StacksV3Operations(cc); + } + + @Test + void testGetStackReturnsMappedStackWhenFound() { + mockStackList(new V3Stack(GUID_STRING, "cflinuxfs4", "Ubuntu-based stack", null, null, null)); + + CloudStack result = operations.getStack("cflinuxfs4"); + + Assertions.assertEquals("cflinuxfs4", result.getName()); + Assertions.assertEquals("Ubuntu-based stack", result.getDescription()); + } + + @Test + void testGetStackRequiredThrowsWhenNotFound() { + mockStackList(); + + CloudOperationException thrown = Assertions.assertThrows(CloudOperationException.class, () -> operations.getStack("missing")); + + Assertions.assertEquals(HttpStatus.NOT_FOUND, thrown.getStatusCode()); + } + + @Test + void testGetStackNotRequiredReturnsNullWhenNotFound() { + mockStackList(); + + Assertions.assertNull(operations.getStack("missing", false)); + } + + @Test + void testGetStacksMapsAllResults() { + mockStackList(new V3Stack(GUID_STRING, "cflinuxfs4", null, null, null, null), + new V3Stack(GUID_STRING, "cflinuxfs3", null, null, null, null)); + + List result = operations.getStacks(); + + Assertions.assertEquals(2, result.size()); + Assertions.assertEquals("cflinuxfs4", result.getFirst() + .getName()); + } + + @Test + void testGetStacksQueriesStacksEndpointWithPageSize() { + mockStackList(); + + operations.getStacks(); + + ArgumentCaptor uriCaptor = ArgumentCaptor.forClass(String.class); + Mockito.verify(cc) + .list(uriCaptor.capture(), ArgumentMatchers.>> any()); + Assertions.assertTrue(uriCaptor.getValue() + .contains("/v3/stacks"), uriCaptor.getValue()); + Assertions.assertTrue(uriCaptor.getValue() + .contains("per_page="), uriCaptor.getValue()); + } + + private void mockStackList(V3Stack... stacks) { + Mockito.when(cc.list(ArgumentMatchers.anyString(), + ArgumentMatchers.>> any())) + .thenReturn(List.of(stacks)); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ApplicationMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ApplicationMapperTest.java new file mode 100644 index 0000000000..516b66daa8 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ApplicationMapperTest.java @@ -0,0 +1,87 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.List; +import java.util.Map; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; +import org.cloudfoundry.multiapps.controller.client.facade.domain.LifecycleType; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Application.V3Lifecycle; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Application.V3LifecycleData; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class V3ApplicationMapperTest { + + private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + + @Test + void testToCloudApplicationMapsAllFields() { + V3Lifecycle lifecycle = new V3Lifecycle("buildpack", new V3LifecycleData(List.of("java_buildpack"), "cflinuxfs4")); + V3Application app = new V3Application(GUID_STRING, "my-app", "STARTED", null, null, lifecycle, null, null); + + CloudApplication result = V3ApplicationMapper.toCloudApplication(app, null); + + Assertions.assertEquals("my-app", result.getName()); + Assertions.assertEquals(CloudApplication.State.STARTED, result.getState()); + Assertions.assertEquals(LifecycleType.BUILDPACK, result.getLifecycle() + .getType()); + Assertions.assertEquals(Map.of("buildpacks", List.of("java_buildpack"), "stack", "cflinuxfs4"), result.getLifecycle() + .getData()); + } + + @Test + void testToCloudApplicationNullStateReturnsNullState() { + V3Application app = new V3Application(GUID_STRING, "my-app", null, null, null, null, null, null); + + Assertions.assertNull(V3ApplicationMapper.toCloudApplication(app, null) + .getState()); + } + + @Test + void testToCloudApplicationNullLifecycleReturnsNullLifecycle() { + V3Application app = new V3Application(GUID_STRING, "my-app", "STOPPED", null, null, null, null, null); + + Assertions.assertNull(V3ApplicationMapper.toCloudApplication(app, null) + .getLifecycle()); + } + + @Test + void testToCloudApplicationLifecycleWithNullTypeReturnsNullLifecycle() { + V3Lifecycle lifecycle = new V3Lifecycle(null, new V3LifecycleData(List.of(), "cflinuxfs4")); + V3Application app = new V3Application(GUID_STRING, "my-app", "STARTED", null, null, lifecycle, null, null); + + Assertions.assertNull(V3ApplicationMapper.toCloudApplication(app, null) + .getLifecycle()); + } + + @Test + void testToCloudApplicationLifecycleTypeIsUpperCased() { + V3Lifecycle lifecycle = new V3Lifecycle("docker", null); + V3Application app = new V3Application(GUID_STRING, "my-app", "STARTED", null, null, lifecycle, null, null); + + Assertions.assertEquals(LifecycleType.DOCKER, V3ApplicationMapper.toCloudApplication(app, null) + .getLifecycle() + .getType()); + } + + @Test + void testToCloudApplicationNullLifecycleDataReturnsEmptyDataMap() { + V3Lifecycle lifecycle = new V3Lifecycle("buildpack", null); + V3Application app = new V3Application(GUID_STRING, "my-app", "STARTED", null, null, lifecycle, null, null); + + Assertions.assertEquals(Map.of(), V3ApplicationMapper.toCloudApplication(app, null) + .getLifecycle() + .getData()); + } + + @Test + void testToCloudApplicationLifecycleDataWithNullFieldsReturnsDefaultedMap() { + V3Lifecycle lifecycle = new V3Lifecycle("buildpack", new V3LifecycleData(null, null)); + V3Application app = new V3Application(GUID_STRING, "my-app", "STARTED", null, null, lifecycle, null, null); + + Assertions.assertEquals(Map.of("buildpacks", List.of(), "stack", ""), V3ApplicationMapper.toCloudApplication(app, null) + .getLifecycle() + .getData()); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AuditEventMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AuditEventMapperTest.java new file mode 100644 index 0000000000..14098b42ca --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AuditEventMapperTest.java @@ -0,0 +1,60 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEvent; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3AuditEvent.V3Participant; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class V3AuditEventMapperTest { + + private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + private static final String ACTOR_GUID = "11111111-2222-3333-4444-555555555555"; + private static final String TARGET_GUID = "99999999-8888-7777-6666-555555555555"; + + @Test + void testToCloudEventMapsAllFields() { + V3AuditEvent event = new V3AuditEvent(GUID_STRING, "2026-08-04T10:15:30Z", null, "audit.app.update", + new V3Participant(ACTOR_GUID, "user", "admin"), + new V3Participant(TARGET_GUID, "app", "my-app")); + + CloudEvent result = V3AuditEventMapper.toCloudEvent(event); + + Assertions.assertEquals("audit.app.update", result.getType()); + Assertions.assertEquals(ACTOR_GUID, result.getActor() + .getGuid() + .toString()); + Assertions.assertEquals("admin", result.getActor() + .getName()); + Assertions.assertEquals("user", result.getActor() + .getType()); + Assertions.assertEquals("my-app", result.getTarget() + .getName()); + } + + @Test + void testToCloudEventNullActorReturnsEmptyParticipant() { + V3AuditEvent event = new V3AuditEvent(GUID_STRING, null, null, "audit.app.update", null, + new V3Participant(TARGET_GUID, "app", "my-app")); + + CloudEvent result = V3AuditEventMapper.toCloudEvent(event); + + Assertions.assertNotNull(result.getActor()); + Assertions.assertNull(result.getActor() + .getGuid()); + Assertions.assertNull(result.getActor() + .getName()); + } + + @Test + void testToCloudEventNullTargetReturnsEmptyParticipant() { + V3AuditEvent event = new V3AuditEvent(GUID_STRING, null, null, "audit.app.update", + new V3Participant(ACTOR_GUID, "user", "admin"), null); + + CloudEvent result = V3AuditEventMapper.toCloudEvent(event); + + Assertions.assertNotNull(result.getTarget()); + Assertions.assertNull(result.getTarget() + .getGuid()); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3BuildMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3BuildMapperTest.java new file mode 100644 index 0000000000..f4dbeaa9b2 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3BuildMapperTest.java @@ -0,0 +1,66 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudBuild; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Build.V3CreatedBy; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Build.V3DropletReference; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Build.V3PackageReference; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class V3BuildMapperTest { + + private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + private static final String USER_GUID = "11111111-2222-3333-4444-555555555555"; + private static final String PACKAGE_GUID = "22222222-3333-4444-5555-666666666666"; + private static final String DROPLET_GUID = "33333333-4444-5555-6666-777777777777"; + + @Test + void testToCloudBuildMapsAllFields() { + V3Build build = new V3Build(GUID_STRING, null, null, "STAGED", null, new V3CreatedBy(USER_GUID, "admin"), + new V3PackageReference(PACKAGE_GUID), new V3DropletReference(DROPLET_GUID)); + + CloudBuild result = V3BuildMapper.toCloudBuild(build); + + Assertions.assertEquals(CloudBuild.State.STAGED, result.getState()); + Assertions.assertEquals(USER_GUID, result.getCreatedBy() + .getGuid() + .toString()); + Assertions.assertEquals("admin", result.getCreatedBy() + .getName()); + Assertions.assertEquals(PACKAGE_GUID, result.getPackageInfo() + .getGuid() + .toString()); + Assertions.assertEquals(DROPLET_GUID, result.getDropletInfo() + .getGuid() + .toString()); + } + + @Test + void testToCloudBuildNullCreatedByPackageAndDropletReturnNulls() { + V3Build build = new V3Build(GUID_STRING, null, null, "STAGING", "boom", null, null, null); + + CloudBuild result = V3BuildMapper.toCloudBuild(build); + + Assertions.assertNull(result.getCreatedBy()); + Assertions.assertNull(result.getPackageInfo()); + Assertions.assertNull(result.getDropletInfo()); + Assertions.assertEquals("boom", result.getError()); + } + + @Test + void testToCLoudBuildDropletWithNullGuidReturnsNullDropletInfo() { + V3Build build = new V3Build(GUID_STRING, null, null, "FAILED", null, null, null, new V3DropletReference(null)); + + Assertions.assertNull(V3BuildMapper.toCloudBuild(build) + .getDropletInfo()); + } + + @Test + void testToCloudBuildNullStateReturnsNullState() { + V3Build build = new V3Build(GUID_STRING, null, null, null, null, null, null, null); + + Assertions.assertNull(V3BuildMapper.toCloudBuild(build) + .getState()); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3DomainMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3DomainMapperTest.java new file mode 100644 index 0000000000..c18bdffd23 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3DomainMapperTest.java @@ -0,0 +1,58 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudDomain; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Domain.V3DomainRelationships; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Domain.V3RelationshipData; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Domain.V3ToOneRelationship; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class V3DomainMapperTest { + + private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + private static final String ORG_GUID = "11111111-2222-3333-4444-555555555555"; + + @Test + void testToCloudDomainMapsNameAndGuid() { + V3Domain domain = new V3Domain(GUID_STRING, "example.com", null, null, null, null); + + CloudDomain result = V3DomainMapper.toCloudDomain(domain); + + Assertions.assertEquals("example.com", result.getName()); + Assertions.assertEquals(GUID_STRING, result.getGuid() + .toString()); + } + + @Test + void testToCloudDomainWithNullGuidReturnsNullGuid() { + V3Domain domain = new V3Domain(null, "example.com", null, null, null, null); + + Assertions.assertNull(V3DomainMapper.toCloudDomain(domain) + .getMetadata() + .getGuid()); + } + + @Test + void testToCloudDomainIsPrivateWhenOrganizationDataPresent() { + V3DomainRelationships relationships = new V3DomainRelationships(new V3ToOneRelationship(new V3RelationshipData(ORG_GUID))); + V3Domain domain = new V3Domain(GUID_STRING, "private.example.com", null, null, null, relationships); + + Assertions.assertTrue(domain.isPrivate()); + } + + @Test + void testToCloudDomainIsNotPrivateWhenRelationshipsNull() { + V3Domain domain = new V3Domain(GUID_STRING, "shared.example.com", null, null, null, null); + + Assertions.assertFalse(domain.isPrivate()); + } + + @Test + void testToCloudDomainIsNotPrivateWhenOrganizationDataNull() { + V3DomainRelationships relationships = new V3DomainRelationships(new V3ToOneRelationship(null)); + V3Domain domain = new V3Domain(GUID_STRING, "shared.example.com", null, null, null, relationships); + + Assertions.assertFalse(domain.isPrivate()); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3InstancesInfoMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3InstancesInfoMapperTest.java new file mode 100644 index 0000000000..0df54cd4f7 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3InstancesInfoMapperTest.java @@ -0,0 +1,60 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.List; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.InstanceState; +import org.cloudfoundry.multiapps.controller.client.facade.domain.InstancesInfo; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class V3InstancesInfoMapperTest { + + @Test + void testToInstancesInfoWithNullResourcesReturnsEmptyList() { + InstancesInfo result = V3InstancesInfoMapper.toInstancesInfo(new V3Process.V3ProcessStats(null)); + + Assertions.assertTrue(result.getInstances() + .isEmpty()); + } + + @Test + void testToInstancesInfoMapsEachResource() { + V3Process.V3ProcessStats stats = new V3Process.V3ProcessStats(List.of(new V3Process.V3ProcessStatsResource(0, "RUNNING", "true"), + new V3Process.V3ProcessStatsResource(1, "CRASHED", "false"))); + + InstancesInfo result = V3InstancesInfoMapper.toInstancesInfo(stats); + + Assertions.assertEquals(2, result.getInstances() + .size()); + Assertions.assertEquals(0, result.getInstances() + .getFirst() + .getIndex()); + Assertions.assertEquals(InstanceState.RUNNING, result.getInstances() + .getFirst() + .getState()); + Assertions.assertTrue(result.getInstances() + .getFirst() + .isRoutable()); + Assertions.assertEquals(InstanceState.CRASHED, result.getInstances() + .get(1) + .getState()); + Assertions.assertFalse(result.getInstances() + .get(1) + .isRoutable()); + } + + @Test + void testToInstancesInfoDefaultsUnknownStateAndNonBooleanRoutable() { + V3Process.V3ProcessStats stats = new V3Process.V3ProcessStats(List.of(new V3Process.V3ProcessStatsResource(0, "bogus", null))); + + InstancesInfo result = V3InstancesInfoMapper.toInstancesInfo(stats); + + Assertions.assertEquals(InstanceState.UNKNOWN, result.getInstances() + .getFirst() + .getState()); + Assertions.assertFalse(result.getInstances() + .getFirst() + .isRoutable()); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3JobMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3JobMapperTest.java new file mode 100644 index 0000000000..38f4c8b0b2 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3JobMapperTest.java @@ -0,0 +1,92 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.List; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudAsyncJob; +import org.cloudfoundry.multiapps.controller.client.facade.domain.JobState; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class V3JobMapperTest { + + private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + + @Test + void testToCloudAsyncJobMapsAllFields() { + V3Job job = new V3Job(GUID_STRING, "2026-08-04T10:15:30Z", "2026-08-05T11:16:31Z", "PROCESSING", "app.apply_manifest", + List.of(new V3Job.V3Error("Something failed", "CF-Error", 10001)), + List.of(new V3Job.V3Warning("A warning"))); + + CloudAsyncJob result = V3JobMapper.toCloudAsyncJob(job); + + Assertions.assertEquals(JobState.PROCESSING, result.getState()); + Assertions.assertEquals("app.apply_manifest", result.getOperation()); + Assertions.assertEquals("A warning", result.getWarnings()); + Assertions.assertEquals("10001 CF-Error Something failed", result.getErrors()); + Assertions.assertEquals(GUID_STRING, result.getGuid() + .toString()); + } + + @Test + void testToCloudAsyncJobParseStateIsCaseInsensitive() { + V3Job job = buildJobWithState("complete"); + + Assertions.assertEquals(JobState.COMPLETE, V3JobMapper.toCloudAsyncJob(job) + .getState()); + } + + @Test + void testToCloudAsyncJobParseStateWithNullReturnsNullState() { + V3Job job = buildJobWithState(null); + + Assertions.assertNull(V3JobMapper.toCloudAsyncJob(job) + .getState()); + } + + @Test + void testToCloudAsyncJobParseStateWithUnknownValueThrows() { + V3Job job = buildJobWithState("bogus"); + + Assertions.assertThrows(IllegalArgumentException.class, () -> V3JobMapper.toCloudAsyncJob(job)); + } + + @Test + void testToCloudAsyncJobNullWarningsReturnEmptyString() { + V3Job job = new V3Job(GUID_STRING, null, null, "FAILED", null, List.of(), null); + + Assertions.assertEquals("", V3JobMapper.toCloudAsyncJob(job) + .getWarnings()); + } + + @Test + void testToCloudAsyncJobNullErrorsReturnEmptyString() { + V3Job job = new V3Job(GUID_STRING, null, null, "FAILED", null, null, List.of()); + + Assertions.assertEquals("", V3JobMapper.toCloudAsyncJob(job) + .getErrors()); + } + + @Test + void testToCloudAsyncJobMultipleWarningsAreJoinedWithComma() { + V3Job job = new V3Job(GUID_STRING, null, null, "POLLING", null, null, + List.of(new V3Job.V3Warning("first warning"), new V3Job.V3Warning("second warning"))); + + Assertions.assertEquals("first warning,second warning", V3JobMapper.toCloudAsyncJob(job) + .getWarnings()); + } + + @Test + void testToCloudAsyncJobMultipleErrorsAreJoinedWithComma() { + V3Job job = new V3Job(GUID_STRING, null, null, "FAILED", null, + List.of(new V3Job.V3Error("detail error one", "Title1", 1), + new V3Job.V3Error("detail error two", "Title2", 2)), null); + + Assertions.assertEquals("1 Title1 detail error one,2 Title2 detail error two", V3JobMapper.toCloudAsyncJob(job) + .getErrors()); + } + + private static V3Job buildJobWithState(String state) { + return new V3Job(GUID_STRING, null, null, state, null, null, null); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ListResponseTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ListResponseTest.java new file mode 100644 index 0000000000..efd989cee0 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ListResponseTest.java @@ -0,0 +1,50 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.List; + +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse.Link; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse.Pagination; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class V3ListResponseTest { + + @Test + void testResourcesWithNullReturnsEmptyList() { + V3ListResponse response = new V3ListResponse<>(null, null); + + Assertions.assertTrue(response.resources() + .isEmpty()); + } + + @Test + void testResourcesReturnsSuppliedList() { + V3ListResponse response = new V3ListResponse<>(null, List.of("a", "b")); + + Assertions.assertEquals(List.of("a", "b"), response.resources()); + } + + @Test + void testNextPageHrefWithNullPaginationReturnsNull() { + V3ListResponse response = new V3ListResponse<>(null, List.of()); + + Assertions.assertNull(response.nextPageHref()); + } + + @Test + void testNextPageHrefWithNullNextLinkReturnsNull() { + Pagination pagination = new Pagination(0, 1, null, null); + V3ListResponse response = new V3ListResponse<>(pagination, List.of()); + + Assertions.assertNull(response.nextPageHref()); + } + + @Test + void testNextPageHrefReturnsHref() { + Pagination pagination = new Pagination(100, 2, new Link("/v3/apps?page=2"), null); + V3ListResponse response = new V3ListResponse<>(pagination, List.of()); + + Assertions.assertEquals("/v3/apps?page=2", response.nextPageHref()); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3PackageMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3PackageMapperTest.java new file mode 100644 index 0000000000..660bd0ad73 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3PackageMapperTest.java @@ -0,0 +1,85 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.BitsData; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudPackage; +import org.cloudfoundry.multiapps.controller.client.facade.domain.DockerData; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Status; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Package.V3Checksum; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Package.V3PackageData; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class V3PackageMapperTest { + + private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + + @Test + void testToCloudPackageMapsBitsPackage() { + V3PackageData data = new V3PackageData(new V3Checksum("sha256", "abc123"), "no error", null, null, null); + V3Package resource = new V3Package(GUID_STRING, "bits", "READY", null, null, data); + + CloudPackage result = V3PackageMapper.toCloudPackage(resource); + + Assertions.assertEquals(Status.READY, result.getStatus()); + Assertions.assertEquals(CloudPackage.Type.BITS, result.getType()); + BitsData bitsData = (BitsData) result.getData(); + Assertions.assertEquals("sha256", bitsData.getChecksum() + .getAlgorithm()); + Assertions.assertEquals("abc123", bitsData.getChecksum() + .getValue()); + Assertions.assertEquals("no error", bitsData.getError()); + } + + @Test + void testToCloudPackageMapsDockerPackage() { + V3PackageData data = new V3PackageData(null, null, "my/image:latest", "user", "pass"); + V3Package resource = new V3Package(GUID_STRING, "docker", "READY", null, null, data); + + CloudPackage result = V3PackageMapper.toCloudPackage(resource); + + Assertions.assertEquals(CloudPackage.Type.DOCKER, result.getType()); + DockerData dockerData = (DockerData) result.getData(); + Assertions.assertEquals("my/image:latest", dockerData.getImage()); + Assertions.assertEquals("user", dockerData.getUsername()); + } + + @Test + void testToCloudPackageNullStateReturnsNullStatus() { + V3Package resource = new V3Package(GUID_STRING, "bits", null, null, null, null); + + Assertions.assertNull(V3PackageMapper.toCloudPackage(resource) + .getStatus()); + } + + @Test + void testToCloudPackageNullTypeReturnsNullType() { + V3Package resource = new V3Package(GUID_STRING, null, "READY", null, null, null); + + Assertions.assertNull(V3PackageMapper.toCloudPackage(resource) + .getType()); + } + + @Test + void testToCloudPackageBitsPackageWithNullDataReturnsEmptyBitsData() { + V3Package resource = new V3Package(GUID_STRING, "bits", "READY", null, null, null); + + BitsData bitsData = (BitsData) V3PackageMapper.toCloudPackage(resource) + .getData(); + + Assertions.assertNull(bitsData.getChecksum()); + Assertions.assertNull(bitsData.getError()); + } + + @Test + void testToCloudPackageBitsPackageWithNullChecksumReturnsNullChecksum() { + V3PackageData data = new V3PackageData(null, "err", null, null, null); + V3Package resource = new V3Package(GUID_STRING, "bits", "READY", null, null, data); + + BitsData bitsData = (BitsData) V3PackageMapper.toCloudPackage(resource) + .getData(); + + Assertions.assertNull(bitsData.getChecksum()); + Assertions.assertEquals("err", bitsData.getError()); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ProcessMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ProcessMapperTest.java new file mode 100644 index 0000000000..3abefbf940 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ProcessMapperTest.java @@ -0,0 +1,61 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudProcess; +import org.cloudfoundry.multiapps.controller.client.facade.domain.HealthCheckType; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Process.V3HealthCheck; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Process.V3HealthCheckData; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class V3ProcessMapperTest { + + private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + + @Test + void testToCloudProcessMapsAllFields() { + V3HealthCheckData healthCheckData = new V3HealthCheckData(60, 10, "/health", 5); + V3HealthCheckData readinessData = new V3HealthCheckData(null, 8, "/ready", 3); + V3Process process = new V3Process(GUID_STRING, "start.sh", 3, 512, 2048, new V3HealthCheck("http", healthCheckData), + new V3HealthCheck("http", readinessData)); + + CloudProcess result = V3ProcessMapper.toCloudProcess(process); + + Assertions.assertEquals("start.sh", result.getCommand()); + Assertions.assertEquals(3, result.getInstances()); + Assertions.assertEquals(HealthCheckType.HTTP, result.getHealthCheckType()); + Assertions.assertEquals("/health", result.getHealthCheckHttpEndpoint()); + Assertions.assertEquals(60, result.getHealthCheckTimeout()); + Assertions.assertEquals("http", result.getReadinessHealthCheckType()); + Assertions.assertEquals("/ready", result.getReadinessHealthCheckHttpEndpoint()); + } + + @Test + void testToCloudProcessNullHealthCheckDefaultsToPort() { + V3Process process = new V3Process(GUID_STRING, "start.sh", 1, 256, 1024, null, null); + + CloudProcess result = V3ProcessMapper.toCloudProcess(process); + + Assertions.assertEquals(HealthCheckType.PORT, result.getHealthCheckType()); + Assertions.assertNull(result.getReadinessHealthCheckType()); + } + + @Test + void testToCloudProcessHealthCheckTypeIsUpperCased() { + V3Process process = new V3Process(GUID_STRING, "s", 1, 256, 1024, new V3HealthCheck("process", null), null); + + Assertions.assertEquals(HealthCheckType.PROCESS, V3ProcessMapper.toCloudProcess(process) + .getHealthCheckType()); + } + + @Test + void testToCloudProcessNullHealthCheckDataLeavesEndpointFieldsNull() { + V3Process process = new V3Process(GUID_STRING, "s", 1, 256, 1024, new V3HealthCheck("port", null), null); + + CloudProcess result = V3ProcessMapper.toCloudProcess(process); + + Assertions.assertEquals(HealthCheckType.PORT, result.getHealthCheckType()); + Assertions.assertNull(result.getHealthCheckHttpEndpoint()); + Assertions.assertNull(result.getHealthCheckTimeout()); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ResourceMappersTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ResourceMappersTest.java new file mode 100644 index 0000000000..36c206a403 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ResourceMappersTest.java @@ -0,0 +1,94 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.time.LocalDateTime; +import java.util.Map; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class V3ResourceMappersTest { + + private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + private static final UUID GUID = UUID.fromString(GUID_STRING); + private static final String CREATED_AT_STRING = "2026-08-04T10:15:30Z"; + private static final String UPDATED_AT_STRING = "2026-08-05T11:16:31Z"; + + @Test + void testParseMetadataPopulatesAllFields() { + CloudMetadata metadata = V3ResourceMappers.parseMetadata(GUID_STRING, CREATED_AT_STRING, UPDATED_AT_STRING); + + Assertions.assertEquals(GUID, metadata.getGuid()); + Assertions.assertEquals(LocalDateTime.of(2026, 8, 4, 10, 15, 30), metadata.getCreatedAt()); + Assertions.assertEquals(LocalDateTime.of(2026, 8, 5, 11, 16, 31), metadata.getUpdatedAt()); + } + + @Test + void testParseMetadataWithAllNullsReturnsEmptyMetadata() { + CloudMetadata metadata = V3ResourceMappers.parseMetadata(null, null, null); + + Assertions.assertNull(metadata.getGuid()); + Assertions.assertNull(metadata.getCreatedAt()); + Assertions.assertNull(metadata.getUpdatedAt()); + } + + @Test + void testParseNullableGuidWithValidGuid() { + Assertions.assertEquals(GUID, V3ResourceMappers.parseNullableGuid(GUID_STRING)); + } + + @Test + void testParseNullableGuidWithNullReturnsNull() { + Assertions.assertNull(V3ResourceMappers.parseNullableGuid(null)); + } + + @Test + void testParseNullableGuidWithMalformedStringReturnsNull() { + Assertions.assertNull(V3ResourceMappers.parseNullableGuid("not-a-guid")); + } + + @Test + void testParseNullableDateWithValidOffsetDateTime() { + Assertions.assertEquals(LocalDateTime.of(2026, 8, 4, 10, 15, 30), V3ResourceMappers.parseNullableDate(CREATED_AT_STRING)); + } + + @Test + void testParseNullableDateWithNullReturnsNull() { + Assertions.assertNull(V3ResourceMappers.parseNullableDate(null)); + } + + @Test + void testParseNullableDateWithMalformedStringReturnsNull() { + Assertions.assertNull(V3ResourceMappers.parseNullableDate("2026/08/04 10:15")); + } + + @Test + void testToV3MetadataWithNullReturnsNull() { + Assertions.assertNull(V3ResourceMappers.toV3Metadata(null)); + } + + @Test + void testToV3MetadataCopiesLabelsAndAnnotations() { + V3Metadata wire = new V3Metadata(Map.of("label-key", "label-value"), Map.of("annotation-key", "annotation-value")); + + Metadata metadata = V3ResourceMappers.toV3Metadata(wire); + + Assertions.assertEquals(Map.of("label-key", "label-value"), metadata.getLabels()); + Assertions.assertEquals(Map.of("annotation-key", "annotation-value"), metadata.getAnnotations()); + } + + @Test + void testToV3MetadataWithNullMapsReturnsEmptyMaps() { + V3Metadata wire = new V3Metadata(null, null); + + Metadata metadata = V3ResourceMappers.toV3Metadata(wire); + + Assertions.assertTrue(metadata.getLabels() + .isEmpty()); + Assertions.assertTrue(metadata.getAnnotations() + .isEmpty()); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RoleMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RoleMapperTest.java new file mode 100644 index 0000000000..7b156b67dc --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RoleMapperTest.java @@ -0,0 +1,28 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.UserRole; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class V3RoleMapperTest { + + @ParameterizedTest + @CsvSource({ "organization_auditor,ORGANIZATION_AUDITOR", "organization_billing_manager,ORGANIZATION_BILLING_MANAGER", + "organization_manager,ORGANIZATION_MANAGER", "organization_user,ORGANIZATION_USER", "space_auditor,SPACE_AUDITOR", + "space_developer,SPACE_DEVELOPER", "space_manager,SPACE_MANAGER" }) + void testToUserRoleMapsEachType(String wireType, UserRole expected) { + V3Role role = new V3Role("guid", null, null, wireType); + + Assertions.assertEquals(expected, V3RoleMapper.toUserRole(role)); + } + + @Test + void testToUserRoleWithUnknownTypeThrows() { + V3Role role = new V3Role("guid", null, null, "galactic_overlord"); + + Assertions.assertThrows(IllegalArgumentException.class, () -> V3RoleMapper.toUserRole(role)); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RouteMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RouteMapperTest.java new file mode 100644 index 0000000000..83c937ea09 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RouteMapperTest.java @@ -0,0 +1,105 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.List; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudRoute; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Route.V3Destination; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Route.V3DestinationApp; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Route.V3RelationshipData; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Route.V3RouteRelationships; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Route.V3ToOneRelationship; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class V3RouteMapperTest { + + private static final String ROUTE_GUID = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + private static final String DOMAIN_GUID = "11111111-2222-3333-4444-555555555555"; + private static final String APP_GUID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"; + private static final String DESTINATION_GUID = "99999999-8888-7777-6666-555555555555"; + + @Test + void testToCloudRouteComputesHostBasedDomain() { + V3Route route = buildRoute("myhost", "", null, "myhost.example.com", List.of()); + + CloudRoute result = V3RouteMapper.toCloudRoute(route); + + Assertions.assertEquals("example.com", result.getDomain() + .getName()); + Assertions.assertEquals("myhost", result.getHost()); + } + + @Test + void testToCloudRouteComputesDomainWhenNoHost() { + V3Route route = buildRoute("", "", null, "example.com", List.of()); + + Assertions.assertEquals("example.com", V3RouteMapper.toCloudRoute(route) + .getDomain() + .getName()); + } + + @Test + void testToCloudRouteStripsPathFromDomain() { + V3Route route = buildRoute("myhost", "/foo", null, "myhost.example.com/foo", List.of()); + + Assertions.assertEquals("example.com", V3RouteMapper.toCloudRoute(route) + .getDomain() + .getName()); + } + + @Test + void testToCloudRouteStripsPortFromDomain() { + V3Route route = buildRoute("", "", 8080, "tcp.example.com:8080", List.of()); + + Assertions.assertEquals("tcp.example.com", V3RouteMapper.toCloudRoute(route) + .getDomain() + .getName()); + } + + @Test + void testToCloudRouteCountsAppsUsingRoute() { + V3Route route = buildRoute("myhost", "", null, "myhost.example.com", + List.of(buildDestination("http1"), buildDestination("http2"))); + + Assertions.assertEquals(2, V3RouteMapper.toCloudRoute(route) + .getAppsUsingRoute()); + } + + @Test + void testToCloudRouteRequestedProtocolIsNullWhenNoApplicationGuidGiven() { + V3Route route = buildRoute("myhost", "", null, "myhost.example.com", List.of(buildDestination("http2"))); + + Assertions.assertNull(V3RouteMapper.toCloudRoute(route) + .getRequestedProtocol()); + } + + @Test + void testToCloudRouteRequestedProtocolMatchesApplicationGuid() { + V3Route route = buildRoute("myhost", "", null, "myhost.example.com", List.of(buildDestination("http2"))); + + CloudRoute result = V3RouteMapper.toCloudRoute(route, UUID.fromString(APP_GUID)); + + Assertions.assertEquals("http2", result.getRequestedProtocol()); + } + + @Test + void testToCloudRouteRequestedProtocolIsNullWhenApplicationGuidDoesNotMatch() { + V3Route route = buildRoute("myhost", "", null, "myhost.example.com", List.of(buildDestination("http2"))); + + CloudRoute result = V3RouteMapper.toCloudRoute(route, UUID.randomUUID()); + + Assertions.assertNull(result.getRequestedProtocol()); + } + + private static V3Route buildRoute(String host, String path, Integer port, String url, List destinations) { + V3RouteRelationships relationships = new V3RouteRelationships(new V3ToOneRelationship(new V3RelationshipData(DOMAIN_GUID)), + new V3ToOneRelationship(new V3RelationshipData(DOMAIN_GUID))); + return new V3Route(ROUTE_GUID, host, path, port, url, null, null, destinations, null, relationships); + } + + private static V3Destination buildDestination(String protocol) { + return new V3Destination(DESTINATION_GUID, new V3DestinationApp(APP_GUID), 8080, 1, protocol); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBindingMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBindingMapperTest.java new file mode 100644 index 0000000000..a70a5bfa7e --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBindingMapperTest.java @@ -0,0 +1,70 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBinding; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceCredentialBindingOperation; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceBinding.V3LastOperation; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceBinding.V3RelationshipData; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceBinding.V3Relationships; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceBinding.V3ToOneRelationship; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class V3ServiceBindingMapperTest { + + private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + private static final String APP_GUID = "11111111-2222-3333-4444-555555555555"; + private static final String INSTANCE_GUID = "99999999-8888-7777-6666-555555555555"; + + @Test + void testToCloudServiceBindingMapsAllFields() { + V3LastOperation lastOperation = new V3LastOperation("create", "succeeded", "done", "2026-08-04T10:15:30Z", + "2026-08-04T10:16:30Z"); + V3ServiceBinding binding = new V3ServiceBinding(GUID_STRING, "my-binding", "app", null, null, lastOperation, null, + buildRelationships(APP_GUID, INSTANCE_GUID)); + + CloudServiceBinding result = V3ServiceBindingMapper.toCloudServiceBinding(binding); + + Assertions.assertEquals(APP_GUID, result.getApplicationGuid() + .toString()); + Assertions.assertEquals(INSTANCE_GUID, result.getServiceInstanceGuid() + .toString()); + Assertions.assertEquals(ServiceCredentialBindingOperation.Type.CREATE, result.getServiceBindingOperation() + .getType()); + Assertions.assertEquals(ServiceCredentialBindingOperation.State.SUCCEEDED, result.getServiceBindingOperation() + .getState()); + } + + @Test + void testToCloudServiceBindingNullApplicationRelationshipReturnsNullApplicationGuid() { + V3Relationships relationships = new V3Relationships(null, new V3ToOneRelationship(new V3RelationshipData(INSTANCE_GUID))); + V3ServiceBinding binding = new V3ServiceBinding(GUID_STRING, "app-binding", "app", null, null, null, null, relationships); + + Assertions.assertNull(V3ServiceBindingMapper.toCloudServiceBinding(binding) + .getApplicationGuid()); + } + + @Test + void testToCloudServiceBindingNullApplicationDataReturnsNullApplicationGuid() { + V3Relationships relationships = new V3Relationships(new V3ToOneRelationship(null), + new V3ToOneRelationship(new V3RelationshipData(INSTANCE_GUID))); + V3ServiceBinding binding = new V3ServiceBinding(GUID_STRING, "b", "app", null, null, null, null, relationships); + + Assertions.assertNull(V3ServiceBindingMapper.toCloudServiceBinding(binding) + .getApplicationGuid()); + } + + @Test + void testToCloudServiceBindingNullLastOperationReturnsNullOperation() { + V3ServiceBinding binding = new V3ServiceBinding(GUID_STRING, "b", "app", null, null, null, null, + buildRelationships(APP_GUID, INSTANCE_GUID)); + + Assertions.assertNull(V3ServiceBindingMapper.toCloudServiceBinding(binding) + .getServiceBindingOperation()); + } + + private static V3Relationships buildRelationships(String appGuid, String instanceGuid) { + return new V3Relationships(new V3ToOneRelationship(new V3RelationshipData(appGuid)), + new V3ToOneRelationship(new V3RelationshipData(instanceGuid))); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBrokerMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBrokerMapperTest.java new file mode 100644 index 0000000000..255a5c68c8 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBrokerMapperTest.java @@ -0,0 +1,44 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBroker; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceBroker.V3RelationshipData; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceBroker.V3Relationships; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceBroker.V3ToOneRelationship; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class V3ServiceBrokerMapperTest { + + private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + private static final String SPACE_GUID = "11111111-2222-3333-4444-555555555555"; + + @Test + void testToCloudServiceBrokerMapsAllFields() { + V3Relationships relationships = new V3Relationships(new V3ToOneRelationship(new V3RelationshipData(SPACE_GUID))); + V3ServiceBroker broker = new V3ServiceBroker(GUID_STRING, "my-broker", "https://broker", null, null, null, relationships); + + CloudServiceBroker result = V3ServiceBrokerMapper.toCloudServiceBroker(broker); + + Assertions.assertEquals("my-broker", result.getName()); + Assertions.assertEquals("https://broker", result.getUrl()); + Assertions.assertEquals(SPACE_GUID, result.getSpaceGuid()); + } + + @Test + void testToCloudServiceBrokerNullRelationshipsReturnNullSpaceGuid() { + V3ServiceBroker broker = new V3ServiceBroker(GUID_STRING, "my-broker", "https://broker", null, null, null, null); + + Assertions.assertNull(V3ServiceBrokerMapper.toCloudServiceBroker(broker) + .getSpaceGuid()); + } + + @Test + void testToCloudServiceBrokerNullSpaceDataReturnsNullSpaceGuid() { + V3Relationships relationships = new V3Relationships(new V3ToOneRelationship(null)); + V3ServiceBroker broker = new V3ServiceBroker(GUID_STRING, "my-broker", "https://broker", null, null, null, relationships); + + Assertions.assertNull(V3ServiceBrokerMapper.toCloudServiceBroker(broker) + .getSpaceGuid()); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstanceMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstanceMapperTest.java new file mode 100644 index 0000000000..43181d182d --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstanceMapperTest.java @@ -0,0 +1,104 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.List; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceOperation; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceInstance.V3LastOperation; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class V3ServiceInstanceMapperTest { + + private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + + @Test + void testToCloudServiceInstanceMapsAllFields() { + V3ServiceInstance instance = new V3ServiceInstance(GUID_STRING, "my-service", "managed", null, null, List.of("tag1", "tag2"), + "syslog://drain", new V3LastOperation("create", "succeeded", "done"), null, + null); + + CloudServiceInstance result = V3ServiceInstanceMapper.toCloudServiceInstance(instance, "my-plan", "my-label"); + + Assertions.assertEquals("my-service", result.getName()); + Assertions.assertEquals("my-plan", result.getPlan()); + Assertions.assertEquals("my-label", result.getLabel()); + Assertions.assertEquals(ServiceInstanceType.MANAGED, result.getType()); + Assertions.assertEquals(List.of("tag1", "tag2"), result.getTags()); + Assertions.assertEquals("syslog://drain", result.getSyslogDrainUrl()); + Assertions.assertEquals(ServiceOperation.Type.CREATE, result.getLastOperation() + .getType()); + Assertions.assertEquals(ServiceOperation.State.SUCCEEDED, result.getLastOperation() + .getState()); + } + + @Test + void testToCloudServiceInstanceWithUserProvidedType() { + V3ServiceInstance instance = buildInstanceWithType("user-provided"); + + Assertions.assertEquals(ServiceInstanceType.USER_PROVIDED, V3ServiceInstanceMapper.toCloudServiceInstance(instance, null, null) + .getType()); + } + + @Test + void testToCloudServiceInstanceWithNullTypeReturnsNullType() { + V3ServiceInstance instance = buildInstanceWithType(null); + + Assertions.assertNull(V3ServiceInstanceMapper.toCloudServiceInstance(instance, null, null) + .getType()); + } + + @Test + void testToCloudServiceInstanceWithUnknownTypeReturnsNullType() { + V3ServiceInstance instance = buildInstanceWithType("bogus"); + + Assertions.assertNull(V3ServiceInstanceMapper.toCloudServiceInstance(instance, null, null) + .getType()); + } + + @Test + void testToCloudServiceInstanceNullTagsReturnEmptyList() { + V3ServiceInstance instance = new V3ServiceInstance(GUID_STRING, "s", "managed", null, null, null, null, null, null, null); + + Assertions.assertTrue(V3ServiceInstanceMapper.toCloudServiceInstance(instance, null, null) + .getTags() + .isEmpty()); + } + + @Test + void testToCloudServiceInstanceNullLastOperationReturnsNullOperation() { + V3ServiceInstance instance = new V3ServiceInstance(GUID_STRING, "s", "managed", null, null, null, null, null, null, null); + + Assertions.assertNull(V3ServiceInstanceMapper.toCloudServiceInstance(instance, null, null) + .getLastOperation()); + } + + @Test + void testToCloudServiceInstanceLastOperationWithNullTypeReturnsNullOperation() { + V3ServiceInstance instance = new V3ServiceInstance(GUID_STRING, "s", "managed", null, null, null, null, + new V3LastOperation(null, "succeeded", "d"), null, null); + + Assertions.assertNull(V3ServiceInstanceMapper.toCloudServiceInstance(instance, null, null) + .getLastOperation()); + } + + @Test + void testToCloudServiceInstanceWithoutAuxiliaryContentExcludesPlanAndOperation() { + V3ServiceInstance instance = new V3ServiceInstance(GUID_STRING, "my-service", "managed", null, null, List.of("t"), "syslog", + new V3LastOperation("create", "succeeded", "d"), null, null); + + CloudServiceInstance result = V3ServiceInstanceMapper.toCloudServiceInstanceWithoutAuxiliaryContent(instance); + + Assertions.assertEquals("my-service", result.getName()); + Assertions.assertEquals(List.of("t"), result.getTags()); + Assertions.assertNull(result.getPlan()); + Assertions.assertNull(result.getLastOperation()); + Assertions.assertNull(result.getType()); + } + + private static V3ServiceInstance buildInstanceWithType(String type) { + return new V3ServiceInstance(GUID_STRING, "s", type, null, null, null, null, null, null, null); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceKeyMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceKeyMapperTest.java new file mode 100644 index 0000000000..8818c2d95e --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceKeyMapperTest.java @@ -0,0 +1,50 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.Map; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceCredentialBindingOperation; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceBinding.V3LastOperation; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class V3ServiceKeyMapperTest { + + private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + + @Test + void testToCloudServiceKeyMapsNameAndCredentials() { + V3ServiceBinding key = new V3ServiceBinding(GUID_STRING, "my-key", "key", null, null, null, null, null); + + CloudServiceKey result = V3ServiceKeyMapper.toCloudServiceKey(key, null, Map.of("username", "u", "password", "p")); + + Assertions.assertEquals("my-key", result.getName()); + Assertions.assertEquals(Map.of("username", "u", "password", "p"), result.getCredentials()); + Assertions.assertNull(result.getServiceKeyOperation()); + } + + @Test + void testToCloudServiceKeyWithoutCredentialsReturnsNullCredentials() { + V3ServiceBinding key = new V3ServiceBinding(GUID_STRING, "my-key", "key", null, null, null, null, null); + + Assertions.assertNull(V3ServiceKeyMapper.toCloudServiceKey(key, null) + .getCredentials()); + } + + @Test + void testToCloudServiceKeyMapsLastOperation() { + V3LastOperation lastOperation = new V3LastOperation("create", "succeeded", "done", "2026-08-04T10:15:30Z", + "2026-08-04T10:16:30Z"); + V3ServiceBinding key = new V3ServiceBinding(GUID_STRING, "my-key", "key", null, null, lastOperation, null, null); + + CloudServiceKey result = V3ServiceKeyMapper.toCloudServiceKey(key, null, null); + + Assertions.assertEquals(ServiceCredentialBindingOperation.Type.CREATE, result.getServiceKeyOperation() + .getType()); + Assertions.assertEquals(ServiceCredentialBindingOperation.State.SUCCEEDED, result.getServiceKeyOperation() + .getState()); + Assertions.assertEquals("done", result.getServiceKeyOperation() + .getDescription()); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceOfferingMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceOfferingMapperTest.java new file mode 100644 index 0000000000..8e01652ea3 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceOfferingMapperTest.java @@ -0,0 +1,73 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.List; +import java.util.Map; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceOffering; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceOffering.V3BrokerCatalog; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceOffering.V3Features; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceOffering.V3RelationshipData; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceOffering.V3Relationships; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceOffering.V3ToOneRelationship; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class V3ServiceOfferingMapperTest { + + private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + private static final String BROKER_GUID = "11111111-2222-3333-4444-555555555555"; + + @Test + void testToCloudServiceOfferingMapsAllFields() { + V3Features features = new V3Features(true, null, null, null, null); + V3BrokerCatalog brokerCatalog = new V3BrokerCatalog("catalog-id", Map.of("displayName", "My Service"), features); + V3Relationships relationships = new V3Relationships(new V3ToOneRelationship(new V3RelationshipData(BROKER_GUID))); + V3ServiceOffering offering = new V3ServiceOffering(GUID_STRING, "my-offering", "An offering", true, true, "https://docs", + null, null, brokerCatalog, relationships, null); + + CloudServiceOffering result = V3ServiceOfferingMapper.toCloudServiceOffering(offering, List.of()); + + Assertions.assertEquals("my-offering", result.getName()); + Assertions.assertEquals("An offering", result.getDescription()); + Assertions.assertTrue(result.isAvailable()); + Assertions.assertTrue(result.isBindable()); + Assertions.assertTrue(result.isShareable()); + Assertions.assertEquals("https://docs", result.getDocUrl()); + Assertions.assertEquals("catalog-id", result.getUniqueId()); + Assertions.assertEquals(BROKER_GUID, result.getBrokerId()); + Assertions.assertEquals(Map.of("displayName", "My Service"), result.getExtra()); + } + + @Test + void testToCloudServiceOfferingNullBrokerCatalogReturnsNullDerivedFields() { + V3ServiceOffering offering = new V3ServiceOffering(GUID_STRING, "o", null, true, false, null, null, null, null, null, null); + + CloudServiceOffering result = V3ServiceOfferingMapper.toCloudServiceOffering(offering, List.of()); + + Assertions.assertNull(result.isBindable()); + Assertions.assertNull(result.getUniqueId()); + Assertions.assertNull(result.getExtra()); + Assertions.assertNull(result.getBrokerId()); + } + + @Test + void testToCloudServiceOfferingNullFeaturesReturnsNullBindable() { + V3BrokerCatalog brokerCatalog = new V3BrokerCatalog("catalog-id", null, null); + V3ServiceOffering offering = new V3ServiceOffering(GUID_STRING, "o", null, true, false, null, null, null, brokerCatalog, null, + null); + + Assertions.assertNull(V3ServiceOfferingMapper.toCloudServiceOffering(offering, List.of()) + .isBindable()); + } + + @Test + void testToCloudServiceOfferingNullServiceBrokerRelationshipReturnsNullBrokerId() { + V3Relationships relationships = new V3Relationships(null); + V3ServiceOffering offering = new V3ServiceOffering(GUID_STRING, "o", null, true, false, null, null, null, null, relationships, + null); + + Assertions.assertNull(V3ServiceOfferingMapper.toCloudServiceOffering(offering, List.of()) + .getBrokerId()); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServicePlanMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServicePlanMapperTest.java new file mode 100644 index 0000000000..fa2a0b11d6 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServicePlanMapperTest.java @@ -0,0 +1,67 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import java.util.Map; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServicePlan; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServicePlan.V3BrokerCatalog; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServicePlan.V3RelationshipData; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServicePlan.V3Relationships; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServicePlan.V3ToOneRelationship; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class V3ServicePlanMapperTest { + + private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + private static final String OFFERING_GUID = "11111111-2222-3333-4444-555555555555"; + + @Test + void testToCloudServicePlanMapsAllFields() { + V3BrokerCatalog brokerCatalog = new V3BrokerCatalog("catalog-id", Map.of("k", "v")); + V3Relationships relationships = new V3Relationships(new V3ToOneRelationship(new V3RelationshipData(OFFERING_GUID))); + V3ServicePlan plan = new V3ServicePlan(GUID_STRING, "my-plan", "A plan", true, "public", null, null, brokerCatalog, + relationships, null); + + CloudServicePlan result = V3ServicePlanMapper.toCloudServicePlan(plan); + + Assertions.assertEquals("my-plan", result.getName()); + Assertions.assertEquals("A plan", result.getDescription()); + Assertions.assertTrue(result.isFree()); + Assertions.assertTrue(result.isPublic()); + Assertions.assertEquals("catalog-id", result.getUniqueId()); + Assertions.assertEquals(OFFERING_GUID, result.getServiceOfferingId()); + Assertions.assertEquals(Map.of("k", "v"), result.getExtra()); + } + + @Test + void testToCloudServicePlanNonPublicVisibilityIsNotPublic() { + V3ServicePlan plan = buildPlanWithVisibility("organization"); + + Assertions.assertFalse(V3ServicePlanMapper.toCloudServicePlan(plan) + .isPublic()); + } + + @Test + void testToCloudServicePlanNullVisibilityReturnsNullIsPublic() { + V3ServicePlan plan = buildPlanWithVisibility(null); + + Assertions.assertNull(V3ServicePlanMapper.toCloudServicePlan(plan) + .isPublic()); + } + + @Test + void testToCloudServicePlanNullBrokerCatalogAndRelationshipsReturnNulls() { + V3ServicePlan plan = new V3ServicePlan(GUID_STRING, "p", null, false, "public", null, null, null, null, null); + + CloudServicePlan result = V3ServicePlanMapper.toCloudServicePlan(plan); + + Assertions.assertNull(result.getUniqueId()); + Assertions.assertNull(result.getExtra()); + Assertions.assertNull(result.getServiceOfferingId()); + } + + private static V3ServicePlan buildPlanWithVisibility(String visibilityType) { + return new V3ServicePlan(GUID_STRING, "p", null, false, visibilityType, null, null, null, null, null); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3StackMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3StackMapperTest.java new file mode 100644 index 0000000000..a25bb98836 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3StackMapperTest.java @@ -0,0 +1,32 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudStack; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class V3StackMapperTest { + + private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + + @Test + void testToCloudStackMapsAllFields() { + V3Stack stack = new V3Stack(GUID_STRING, "cflinuxfs4", "Ubuntu-based stack", "2026-08-04T10:15:30Z", "2026-08-04T10:16:30Z", null); + + CloudStack result = V3StackMapper.toCloudStack(stack); + + Assertions.assertEquals("cflinuxfs4", result.getName()); + Assertions.assertEquals("Ubuntu-based stack", result.getDescription()); + Assertions.assertEquals(GUID_STRING, result.getGuid() + .toString()); + } + + @Test + void testToCloudStackWithNullGuidReturnsNullGuid() { + V3Stack stack = new V3Stack(null, "cflinuxfs4", null, null, null, null); + + Assertions.assertNull(V3StackMapper.toCloudStack(stack) + .getMetadata() + .getGuid()); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3TaskMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3TaskMapperTest.java new file mode 100644 index 0000000000..b07d6b1eee --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3TaskMapperTest.java @@ -0,0 +1,65 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudTask; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class V3TaskMapperTest { + + private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + + @Test + void testToCloudTaskMapsAllFields() { + V3Task task = new V3Task(GUID_STRING, "migrate-db", "rails db:migrate", "SUCCEEDED", 256, 1024, + new V3Task.V3Result(null), "2026-08-04T10:15:30Z", "2026-08-04T10:16:30Z", null); + + CloudTask result = V3TaskMapper.toCloudTask(task); + + Assertions.assertEquals("migrate-db", result.getName()); + Assertions.assertEquals("rails db:migrate", result.getCommand()); + Assertions.assertEquals(CloudTask.State.SUCCEEDED, result.getState()); + Assertions.assertEquals(256, result.getLimits() + .getMemory()); + Assertions.assertEquals(1024, result.getLimits() + .getDisk()); + Assertions.assertNull(result.getResult() + .getFailureReason()); + } + + @Test + void testToCloudTaskWithNullResultReturnsNullResult() { + V3Task task = buildTaskWithState("RUNNING"); + + Assertions.assertNull(V3TaskMapper.toCloudTask(task) + .getResult()); + } + + @Test + void testToCloudTaskMapsFailureReason() { + V3Task task = new V3Task(GUID_STRING, "t", "cmd", "FAILED", 256, 1024, new V3Task.V3Result("boom"), null, null, null); + + Assertions.assertEquals("boom", V3TaskMapper.toCloudTask(task) + .getResult() + .getFailureReason()); + } + + @Test + void testToCloudTaskWithNullStateReturnsNullState() { + V3Task task = buildTaskWithState(null); + + Assertions.assertNull(V3TaskMapper.toCloudTask(task) + .getState()); + } + + @Test + void testToCloudTaskWithUnknownStateThrows() { + V3Task task = buildTaskWithState("bogus"); + + Assertions.assertThrows(IllegalArgumentException.class, () -> V3TaskMapper.toCloudTask(task)); + } + + private static V3Task buildTaskWithState(String state) { + return new V3Task(GUID_STRING, "t", "cmd", state, 256, 1024, null, null, null, null); + } + +} From 696856cbfd568b98a26995a80fbfedba23d02fb2 Mon Sep 17 00:00:00 2001 From: Krasimir Kargov Date: Fri, 11 Sep 2026 19:09:08 +0300 Subject: [PATCH 08/15] Adding more unit tests and fixing existing ones LMCROSSITXSADEPLOY-3563 --- multiapps-controller-client/pom.xml | 16 + .../rest/ApplicationsV3OperationsTest.java | 549 ++++++++++++++++++ .../facade/rest/BuildsV3OperationsTest.java | 144 +++++ .../CloudControllerRestClientV3ImplTest.java | 506 ++++++++++++++++ .../rest/CloudControllerV3ClientTest.java | 158 +++++ .../facade/rest/CloudSpaceClientTest.java | 136 +++++ .../facade/rest/DomainsV3OperationsTest.java | 184 ++++++ .../rest/MockControllerClientFactory.java | 51 ++ .../facade/rest/PackagesV3OperationsTest.java | 202 +++++++ .../rest/ProcessesV3OperationsTest.java | 418 +++++++++++++ .../facade/rest/RolesV3OperationsTest.java | 111 ++++ .../facade/rest/RoutesV3OperationsTest.java | 346 +++++++++++ .../rest/ServiceBindingsV3OperationsTest.java | 384 ++++++++++++ .../rest/ServiceBrokersV3OperationsTest.java | 297 ++++++++++ .../ServiceInstancesV3OperationsTest.java | 416 ++++++++++++- .../rest/ServiceKeysV3OperationsTest.java | 241 ++++++++ .../ServiceOfferingsV3OperationsTest.java | 6 +- .../rest/ServicePlansV3OperationsTest.java | 145 +++++ .../facade/rest/TasksV3OperationsTest.java | 118 ++++ 19 files changed, 4404 insertions(+), 24 deletions(-) create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3OperationsTest.java create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3OperationsTest.java create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientV3ImplTest.java create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3ClientTest.java create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudSpaceClientTest.java create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/MockControllerClientFactory.java create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3OperationsTest.java create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3OperationsTest.java create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RolesV3OperationsTest.java create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3OperationsTest.java create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3OperationsTest.java create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBrokersV3OperationsTest.java create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3OperationsTest.java create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3OperationsTest.java create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/TasksV3OperationsTest.java diff --git a/multiapps-controller-client/pom.xml b/multiapps-controller-client/pom.xml index 1cb1ba3888..fbf6c87a5e 100644 --- a/multiapps-controller-client/pom.xml +++ b/multiapps-controller-client/pom.xml @@ -105,6 +105,22 @@ micrometer-core ${micrometer.version}
+ + + + org.springframework + spring-test + ${spring.version} + test + + + + + org.hamcrest + hamcrest + 2.2 + test +
diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3OperationsTest.java new file mode 100644 index 0000000000..8574140df9 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3OperationsTest.java @@ -0,0 +1,549 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableStaging; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.dto.ApplicationToCreateDto; +import org.cloudfoundry.multiapps.controller.client.facade.dto.ImmutableApplicationToCreateDto; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.web.client.match.MockRestRequestMatchers; +import org.springframework.test.web.client.response.MockRestResponseCreators; + +class ApplicationsV3OperationsTest { + + private static final String SPACE_GUID = "11111111-2222-3333-4444-555555555555"; + private static final String APP_GUID = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + private static final String APPS_BASE = MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000"; + + @Mock + private CloudSpace target; + + private MockControllerClientFactory factory; + + private ApplicationsV3Operations operations; + + @BeforeEach + void setUp() throws Exception { + MockitoAnnotations.openMocks(this) + .close(); + factory = MockControllerClientFactory.create(); + operations = new ApplicationsV3Operations(factory.client(), target); + } + + @Test + void testCreateApplicationPostsAndReturnsGuid() { + Mockito.when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withSuccess(appJson(APP_GUID, "my-app", "STOPPED"), MediaType.APPLICATION_JSON)); + + ApplicationToCreateDto dto = ImmutableApplicationToCreateDto.builder() + .name("my-app") + .build(); + + UUID result = operations.createApplication(dto); + + Assertions.assertEquals(UUID.fromString(APP_GUID), result); + factory.verify(); + } + + @Test + void testCreateApplicationWithScaleAlsoScalesWebProcess() { + Mockito.when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withSuccess(appJson(APP_GUID, "my-app", "STOPPED"), MediaType.APPLICATION_JSON)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/processes/web/actions/scale")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withSuccess()); + + ApplicationToCreateDto dto = ImmutableApplicationToCreateDto.builder() + .name("my-app") + .memoryInMb(512) + .diskQuotaInMb(1024) + .staging(ImmutableStaging.builder() + .addBuildpacks("java_buildpack") + .stackName("cflinuxfs4") + .build()) + .env(Map.of("FOO", "bar")) + .metadata(Metadata.builder() + .label("k", "v") + .build()) + .build(); + + UUID result = operations.createApplication(dto); + + Assertions.assertEquals(UUID.fromString(APP_GUID), result); + factory.verify(); + } + + @Test + void testCreateApplicationThrowsWhenTargetGuidIsNull() { + Mockito.when(target.getGuid()) + .thenReturn(null); + + ApplicationToCreateDto dto = ImmutableApplicationToCreateDto.builder() + .name("my-app") + .build(); + + Assertions.assertThrows(CloudOperationException.class, () -> operations.createApplication(dto)); + } + + @Test + void testDeleteApplicationLooksUpGuidAndDeletes() { + Mockito.when(target.getGuid()) + .thenReturn(null); + + stubAppLookupByName("my-app", APP_GUID); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.DELETE)) + .andRespond(MockRestResponseCreators.withSuccess()); + + operations.deleteApplication("my-app"); + + factory.verify(); + } + + @Test + void testDeleteApplicationThrowsWhenApplicationNotFound() { + Mockito.when(target.getGuid()) + .thenReturn(null); + + stubEmptyAppLookupByName("missing-app"); + + Assertions.assertThrows(CloudOperationException.class, () -> operations.deleteApplication("missing-app")); + } + + @Test + void testGetApplicationReturnsMappedApplication() { + Mockito.when(target.getGuid()) + .thenReturn(null); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(APPS_BASE + "&names=my-app")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(listJson(appJson(APP_GUID, "my-app", "STARTED")), + MediaType.APPLICATION_JSON)); + + CloudApplication result = operations.getApplication("my-app"); + + Assertions.assertEquals("my-app", result.getName()); + Assertions.assertEquals(CloudApplication.State.STARTED, result.getState()); + Assertions.assertEquals(UUID.fromString(APP_GUID), result.getGuid()); + factory.verify(); + } + + @Test + void testGetApplicationThrowsWhenRequiredAndNotFound() { + Mockito.when(target.getGuid()) + .thenReturn(null); + + stubEmptyAppLookupByName("missing-app"); + + Assertions.assertThrows(CloudOperationException.class, () -> operations.getApplication("missing-app")); + } + + @Test + void testGetApplicationReturnsNullWhenNotRequiredAndNotFound() { + Mockito.when(target.getGuid()) + .thenReturn(null); + + stubEmptyAppLookupByName("missing-app"); + + CloudApplication result = operations.getApplication("missing-app", false); + + Assertions.assertNull(result); + factory.verify(); + } + + @Test + void testGetApplicationGuidReturnsGuid() { + Mockito.when(target.getGuid()) + .thenReturn(null); + + stubAppLookupByName("my-app", APP_GUID); + + UUID result = operations.getApplicationGuid("my-app"); + + Assertions.assertEquals(UUID.fromString(APP_GUID), result); + factory.verify(); + } + + @Test + void testGetApplicationQueryIncludesSpaceGuidWhenPresent() { + Mockito.when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); + factory.server() + .expect(MockRestRequestMatchers.requestTo(APPS_BASE + "&space_guids=" + SPACE_GUID + "&names=my-app")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(listJson(appJson(APP_GUID, "my-app", "STARTED")), + MediaType.APPLICATION_JSON)); + + CloudApplication result = operations.getApplication("my-app"); + + Assertions.assertEquals("my-app", result.getName()); + factory.verify(); + } + + @Test + void testGetApplicationNameReturnsName() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(appJson(APP_GUID, "my-app", "STARTED"), MediaType.APPLICATION_JSON)); + + String result = operations.getApplicationName(UUID.fromString(APP_GUID)); + + Assertions.assertEquals("my-app", result); + factory.verify(); + } + + @Test + void testGetApplicationEnvironmentByGuidReturnsVariables() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/environment_variables")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"var\":{\"FOO\":\"bar\"}}", MediaType.APPLICATION_JSON)); + + Map result = operations.getApplicationEnvironment(UUID.fromString(APP_GUID)); + + Assertions.assertEquals(Map.of("FOO", "bar"), result); + factory.verify(); + } + + @Test + void testGetApplicationEnvironmentByGuidReturnsEmptyWhenNoVars() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/environment_variables")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{}", MediaType.APPLICATION_JSON)); + + Map result = operations.getApplicationEnvironment(UUID.fromString(APP_GUID)); + + Assertions.assertTrue(result.isEmpty()); + factory.verify(); + } + + @Test + void testGetApplicationEnvironmentByNameResolvesGuidFirst() { + Mockito.when(target.getGuid()) + .thenReturn(null); + + stubAppLookupByName("my-app", APP_GUID); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/environment_variables")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"var\":{\"A\":\"B\"}}", MediaType.APPLICATION_JSON)); + + Map result = operations.getApplicationEnvironment("my-app"); + + Assertions.assertEquals(Map.of("A", "B"), result); + factory.verify(); + } + + @Test + void testGetApplicationsReturnsMappedList() { + Mockito.when(target.getGuid()) + .thenReturn(null); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(APPS_BASE)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(listJson(appJson(APP_GUID, "my-app", "STARTED")), + MediaType.APPLICATION_JSON)); + + List result = operations.getApplications(); + + Assertions.assertEquals(1, result.size()); + Assertions.assertEquals("my-app", result.getFirst() + .getName()); + factory.verify(); + } + + @Test + void testGetApplicationsReturnsEmptyList() { + Mockito.when(target.getGuid()) + .thenReturn(null); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(APPS_BASE)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + + List result = operations.getApplications(); + + Assertions.assertTrue(result.isEmpty()); + factory.verify(); + } + + @Test + void testGetApplicationsByMetadataLabelSelectorAppendsSelector() { + Mockito.when(target.getGuid()) + .thenReturn(null); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(APPS_BASE + "&label_selector=env")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(listJson(appJson(APP_GUID, "my-app", "STARTED")), + MediaType.APPLICATION_JSON)); + + List result = operations.getApplicationsByMetadataLabelSelector("env"); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testGetApplicationsByMetadataLabelSelectorWithNullSelector() { + Mockito.when(target.getGuid()) + .thenReturn(null); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(APPS_BASE)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + + List result = operations.getApplicationsByMetadataLabelSelector(null); + + Assertions.assertTrue(result.isEmpty()); + factory.verify(); + } + + @Test + void testStartApplicationPostsToStartAction() { + Mockito.when(target.getGuid()) + .thenReturn(null); + + stubAppLookupByName("my-app", APP_GUID); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/actions/start")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withSuccess()); + + operations.startApplication("my-app"); + + factory.verify(); + } + + @Test + void testStopApplicationPostsToStopAction() { + Mockito.when(target.getGuid()) + .thenReturn(null); + + stubAppLookupByName("my-app", APP_GUID); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/actions/stop")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withSuccess()); + + operations.stopApplication("my-app"); + + factory.verify(); + } + + @Test + void testRenameUpdatesName() { + Mockito.when(target.getGuid()) + .thenReturn(null); + + stubAppLookupByName("my-app", APP_GUID); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andRespond(MockRestResponseCreators.withSuccess()); + + operations.rename("my-app", "new-name"); + + factory.verify(); + } + + @Test + void testRenameSwallowsServiceUnavailableWhenAlreadyRenamed() { + Mockito.when(target.getGuid()) + .thenReturn(null); + + stubAppLookupByName("my-app", APP_GUID); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.SERVICE_UNAVAILABLE)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(appJson(APP_GUID, "new-name", "STOPPED"), MediaType.APPLICATION_JSON)); + + operations.rename("my-app", "new-name"); + + factory.verify(); + } + + @Test + void testRenameRethrowsServiceUnavailableWhenNotRenamed() { + Mockito.when(target.getGuid()) + .thenReturn(null); + + stubAppLookupByName("my-app", APP_GUID); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.SERVICE_UNAVAILABLE)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(appJson(APP_GUID, "still-old", "STOPPED"), MediaType.APPLICATION_JSON)); + + Assertions.assertThrows(CloudOperationException.class, () -> operations.rename("my-app", "new-name")); + } + + @Test + void testUpdateApplicationInstancesScalesWebProcess() { + stubScaleFlow(); + + operations.updateApplicationInstances("my-app", 3); + + factory.verify(); + } + + @Test + void testUpdateApplicationMemoryScalesWebProcess() { + stubScaleFlow(); + + operations.updateApplicationMemory("my-app", 512); + + factory.verify(); + } + + @Test + void testUpdateApplicationDiskQuotaScalesWebProcess() { + stubScaleFlow(); + + operations.updateApplicationDiskQuota("my-app", 2048); + + factory.verify(); + } + + @Test + void testUpdateApplicationEnvUpdatesEnvVars() { + Mockito.when(target.getGuid()) + .thenReturn(null); + + stubAppLookupByName("my-app", APP_GUID); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/environment_variables")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andRespond(MockRestResponseCreators.withSuccess()); + + operations.updateApplicationEnv("my-app", Map.of("FOO", "bar")); + + factory.verify(); + } + + @Test + void testBindDropletToAppUpdatesCurrentDroplet() { + UUID dropletGuid = UUID.fromString("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/relationships/current_droplet")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andRespond(MockRestResponseCreators.withSuccess()); + + operations.bindDropletToApp(dropletGuid, UUID.fromString(APP_GUID)); + + factory.verify(); + } + + @Test + void testUpdateApplicationMetadataUpdatesApp() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andRespond(MockRestResponseCreators.withSuccess()); + + Metadata metadata = Metadata.builder() + .label("k", "v") + .annotation("a", "b") + .build(); + + operations.updateApplicationMetadata(UUID.fromString(APP_GUID), metadata); + + factory.verify(); + } + + private void stubScaleFlow() { + Mockito.when(target.getGuid()) + .thenReturn(null); + + stubAppLookupByName("my-app", APP_GUID); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/processes/web/actions/scale")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withSuccess()); + } + + private void stubAppLookupByName(String name, String guid) { + factory.server() + .expect(MockRestRequestMatchers.requestTo(APPS_BASE + "&names=" + name)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(listJson(appJson(guid, name, "STARTED")), MediaType.APPLICATION_JSON)); + } + + private void stubEmptyAppLookupByName(String name) { + factory.server() + .expect(MockRestRequestMatchers.requestTo(APPS_BASE + "&names=" + name)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + } + + private static String appJson(String guid, String name, String state) { + return "{\"guid\":\"" + guid + "\",\"name\":\"" + name + "\",\"state\":\"" + state + + "\",\"lifecycle\":{\"type\":\"buildpack\",\"data\":{\"buildpacks\":[\"java_buildpack\"],\"stack\":\"cflinuxfs4\"}}}"; + } + + private static String listJson(String... resources) { + return "{\"resources\":[" + String.join(",", resources) + "]}"; + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3OperationsTest.java new file mode 100644 index 0000000000..1b51c26456 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3OperationsTest.java @@ -0,0 +1,144 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.List; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudBuild; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.web.client.match.MockRestRequestMatchers; +import org.springframework.test.web.client.response.MockRestResponseCreators; + +class BuildsV3OperationsTest { + + private static final String BUILD_GUID = "11111111-2222-3333-4444-555555555555"; + private static final String PACKAGE_GUID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"; + private static final String APP_GUID = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + private static final String DROPLET_GUID = "99999999-8888-7777-6666-555555555555"; + + private MockControllerClientFactory factory; + private BuildsV3Operations operations; + + @BeforeEach + void setUp() { + factory = MockControllerClientFactory.create(); + operations = new BuildsV3Operations(factory.client()); + } + + @Test + void testCreateBuildPostsPackageAndReturnsMappedBuild() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/builds")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withSuccess(getBuildJson("STAGING"), MediaType.APPLICATION_JSON)); + + CloudBuild result = operations.createBuild(UUID.fromString(PACKAGE_GUID)); + + Assertions.assertEquals(CloudBuild.State.STAGING, result.getState()); + Assertions.assertEquals(UUID.fromString(BUILD_GUID), result.getMetadata() + .getGuid()); + Assertions.assertEquals(UUID.fromString(PACKAGE_GUID), result.getPackageInfo() + .getGuid()); + factory.verify(); + } + + @Test + void testGetBuildReturnsMappedBuild() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/builds/" + BUILD_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(getBuildJson("STAGED"), MediaType.APPLICATION_JSON)); + + CloudBuild result = operations.getBuild(UUID.fromString(BUILD_GUID)); + + Assertions.assertEquals(CloudBuild.State.STAGED, result.getState()); + Assertions.assertEquals(UUID.fromString(DROPLET_GUID), result.getDropletInfo() + .getGuid()); + factory.verify(); + } + + @Test + void testGetBuildThrowsWhenNotFound() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/builds/" + BUILD_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.NOT_FOUND)); + + Assertions.assertThrows(CloudOperationException.class, () -> operations.getBuild(UUID.fromString(BUILD_GUID))); + } + + @Test + void testGetBuildsForApplicationReturnsMappedList() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/builds?per_page=5000")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[" + getBuildJson("STAGED") + "]}", + MediaType.APPLICATION_JSON)); + + List result = operations.getBuildsForApplication(UUID.fromString(APP_GUID)); + + Assertions.assertEquals(1, result.size()); + Assertions.assertEquals(CloudBuild.State.STAGED, result.getFirst() + .getState()); + factory.verify(); + } + + @Test + void testGetBuildsForApplicationReturnsEmptyList() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/builds?per_page=5000")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + + List result = operations.getBuildsForApplication(UUID.fromString(APP_GUID)); + + Assertions.assertEquals(0, result.size()); + factory.verify(); + } + + @Test + void testGetBuildsForPackageReturnsMappedList() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/builds?package_guids=" + + PACKAGE_GUID + "&per_page=5000")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[" + getBuildJson("FAILED") + "]}", + MediaType.APPLICATION_JSON)); + + List result = operations.getBuildsForPackage(UUID.fromString(PACKAGE_GUID)); + + Assertions.assertEquals(1, result.size()); + Assertions.assertEquals(CloudBuild.State.FAILED, result.getFirst() + .getState()); + Assertions.assertEquals("staging failed", result.getFirst() + .getError()); + factory.verify(); + } + + @Test + void testGetBuildsForPackageReturnsEmptyList() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/builds?package_guids=" + + PACKAGE_GUID + "&per_page=5000")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + + List result = operations.getBuildsForPackage(UUID.fromString(PACKAGE_GUID)); + + Assertions.assertEquals(0, result.size()); + factory.verify(); + } + + private static String getBuildJson(String state) { + return "{\"guid\":\"" + BUILD_GUID + "\",\"state\":\"" + state + "\",\"error\":\"staging failed\",\"package\":{\"guid\":\"" + + PACKAGE_GUID + "\"},\"droplet\":{\"guid\":\"" + DROPLET_GUID + "\"}}"; + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientV3ImplTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientV3ImplTest.java new file mode 100644 index 0000000000..f8579c7358 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientV3ImplTest.java @@ -0,0 +1,506 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.time.Duration; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.function.Function; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudBuild; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudDomain; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEvent; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudRoute; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudStack; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudTask; +import org.cloudfoundry.multiapps.controller.client.facade.dto.ApplicationToCreateDto; +import org.cloudfoundry.multiapps.controller.client.facade.dto.ImmutableApplicationToCreateDto; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.web.client.match.MockRestRequestMatchers; +import org.springframework.test.web.client.response.MockRestResponseCreators; +import org.springframework.web.client.RestClient; + +class CloudControllerRestClientV3ImplTest { + + private static final String SPACE_GUID = "11111111-2222-3333-4444-555555555555"; + private static final String APP_GUID = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + private static final String TASK_GUID = "99999999-8888-7777-6666-555555555555"; + private static final String BUILD_GUID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"; + private static final String PACKAGE_GUID = "bbbbbbbb-cccc-dddd-eeee-ffffffffffff"; + private static final String SERVICE_INSTANCE_GUID = "cccccccc-dddd-eeee-ffff-000000000000"; + private static final String DOMAIN_NAME = "example.com"; + private static final String APPS_BASE = MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000"; + + @Mock + private CloudSpace target; + @Mock + private CloudMetadata targetMetadata; + + private MockControllerClientFactory factory; + private CloudControllerRestClientV3Impl client; + + @BeforeEach + void setUp() throws Exception { + MockitoAnnotations.openMocks(this) + .close(); + factory = MockControllerClientFactory.create(); + Function uploadRestClientFactory = duration -> factory.client() + .getRestClient(); + client = new CloudControllerRestClientV3Impl(target, factory.client(), uploadRestClientFactory); + } + + @Test + void testGetTargetReturnsInjectedSpace() { + Assertions.assertSame(target, client.getTarget()); + } + + @Test + void testGetStackReturnsMappedStack() { + factory.server() + .expect(MockRestRequestMatchers.requestTo( + MockControllerClientFactory.BASE_URL + "/v3/stacks?names=cflinuxfs4&per_page=5000")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(getStackListJson(), MediaType.APPLICATION_JSON)); + + CloudStack result = client.getStack("cflinuxfs4"); + + Assertions.assertEquals("cflinuxfs4", result.getName()); + factory.verify(); + } + + @Test + void testGetStacksReturnsMappedList() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/stacks?per_page=5000")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(getStackListJson(), MediaType.APPLICATION_JSON)); + + List result = client.getStacks(); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testGetDomainsReturnsMappedList() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/domains?per_page=5000")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(getDomainListJson(), MediaType.APPLICATION_JSON)); + + List result = client.getDomains(); + + Assertions.assertEquals(1, result.size()); + Assertions.assertEquals(DOMAIN_NAME, result.getFirst() + .getName()); + factory.verify(); + } + + @Test + void testGetSharedDomainsReturnsMappedList() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/domains?per_page=5000")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(getDomainListJson(), MediaType.APPLICATION_JSON)); + + List result = client.getSharedDomains(); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testGetApplicationReturnsMappedApplication() { + Mockito.when(target.getGuid()) + .thenReturn(null); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(APPS_BASE + "&names=my-app")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(getAppListJson(), MediaType.APPLICATION_JSON)); + + CloudApplication result = client.getApplication("my-app"); + + Assertions.assertEquals("my-app", result.getName()); + Assertions.assertEquals(UUID.fromString(APP_GUID), result.getGuid()); + factory.verify(); + } + + @Test + void testGetApplicationsReturnsMappedList() { + Mockito.when(target.getGuid()) + .thenReturn(null); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(APPS_BASE)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(getAppListJson(), MediaType.APPLICATION_JSON)); + + List result = client.getApplications(); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testGetApplicationGuidReturnsGuid() { + Mockito.when(target.getGuid()) + .thenReturn(null); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(APPS_BASE + "&names=my-app")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(getAppListJson(), MediaType.APPLICATION_JSON)); + + UUID result = client.getApplicationGuid("my-app"); + + Assertions.assertEquals(UUID.fromString(APP_GUID), result); + factory.verify(); + } + + @Test + void testGetApplicationNameReturnsName() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(getAppJson(), MediaType.APPLICATION_JSON)); + + String result = client.getApplicationName(UUID.fromString(APP_GUID)); + + Assertions.assertEquals("my-app", result); + factory.verify(); + } + + @Test + void testGetApplicationEnvironmentReturnsVariables() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/environment_variables")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"var\":{\"FOO\":\"bar\"}}", MediaType.APPLICATION_JSON)); + + Map result = client.getApplicationEnvironment(UUID.fromString(APP_GUID)); + + Assertions.assertEquals(Map.of("FOO", "bar"), result); + factory.verify(); + } + + @Test + void testCreateApplicationWithoutStagingOrRoutesOnlyPosts() { + Mockito.when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withSuccess(getAppJson(), MediaType.APPLICATION_JSON)); + + ApplicationToCreateDto dto = ImmutableApplicationToCreateDto.builder() + .name("my-app") + .build(); + + client.createApplication(dto); + + factory.verify(); + } + + @Test + void testCreateApplicationWithStagingUpdatesStagingThroughProcess() { + Mockito.when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withSuccess(getAppJson(), MediaType.APPLICATION_JSON)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(APPS_BASE + "&space_guids=" + SPACE_GUID + "&names=my-app")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(getAppListJson(), MediaType.APPLICATION_JSON)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andRespond(MockRestResponseCreators.withSuccess()); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/processes/web")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(getWebProcessJson(), MediaType.APPLICATION_JSON)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/processes/" + APP_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andRespond(MockRestResponseCreators.withSuccess()); + + ApplicationToCreateDto dto = ImmutableApplicationToCreateDto.builder() + .name("my-app") + .staging( + org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableStaging.builder() + .command( + "run.sh") + .build()) + .build(); + + client.createApplication(dto); + + factory.verify(); + } + + @Test + void testGetEventsReturnsMappedList() { + factory.server() + .expect(MockRestRequestMatchers.requestTo( + MockControllerClientFactory.BASE_URL + "/v3/audit_events?per_page=5000")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(getAuditEventListJson(), MediaType.APPLICATION_JSON)); + + List result = client.getEvents(); + + Assertions.assertEquals(1, result.size()); + Assertions.assertEquals("audit.app.update", result.getFirst() + .getType()); + factory.verify(); + } + + @Test + void testGetEventsByTargetReturnsMappedList() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/audit_events?per_page=5000" + + "&target_guids=" + APP_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(getAuditEventListJson(), MediaType.APPLICATION_JSON)); + + List result = client.getEventsByTarget(UUID.fromString(APP_GUID)); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testGetServiceInstanceReturnsMappedInstance() { + stubTargetSpaceMetadata(); + stubServiceInstanceLookup(); + + client.getServiceInstance("my-service", false); + + factory.verify(); + } + + @Test + void testGetServiceInstanceNameReturnsName() { + factory.server() + .expect(MockRestRequestMatchers.requestTo( + MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + SERVICE_INSTANCE_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(getServiceInstanceJson(), MediaType.APPLICATION_JSON)); + + String result = client.getServiceInstanceName(UUID.fromString(SERVICE_INSTANCE_GUID)); + + Assertions.assertEquals("my-service", result); + factory.verify(); + } + + @Test + void testGetServiceInstanceWithoutAuxiliaryContentReturnsMappedInstance() { + stubTargetSpaceMetadata(); + stubServiceInstanceLookup(); + + CloudServiceInstance result = client.getServiceInstanceWithoutAuxiliaryContent("my-service"); + + Assertions.assertEquals("my-service", result.getName()); + factory.verify(); + } + + @Test + void testGetRequiredServiceInstanceGuidReturnsGuid() { + stubTargetSpaceMetadata(); + stubServiceInstanceLookup(); + + UUID result = client.getRequiredServiceInstanceGuid("my-service"); + + Assertions.assertEquals(UUID.fromString(SERVICE_INSTANCE_GUID), result); + factory.verify(); + } + + @Test + void testGetTaskReturnsMappedTask() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/tasks/" + TASK_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(getTaskJson(), MediaType.APPLICATION_JSON)); + + CloudTask result = client.getTask(UUID.fromString(TASK_GUID)); + + Assertions.assertEquals("migrate", result.getName()); + Assertions.assertEquals(CloudTask.State.SUCCEEDED, result.getState()); + factory.verify(); + } + + @Test + void testGetTasksReturnsMappedList() { + Mockito.when(target.getGuid()) + .thenReturn(null); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(APPS_BASE + "&names=my-app")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(getAppListJson(), MediaType.APPLICATION_JSON)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/tasks?per_page=5000&app_guids=" + + APP_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[" + getTaskJson() + "]}", MediaType.APPLICATION_JSON)); + + List result = client.getTasks("my-app"); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testGetBuildReturnsMappedBuild() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/builds/" + BUILD_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(getBuildJson(), MediaType.APPLICATION_JSON)); + + CloudBuild result = client.getBuild(UUID.fromString(BUILD_GUID)); + + Assertions.assertEquals(CloudBuild.State.STAGED, result.getState()); + factory.verify(); + } + + @Test + void testGetBuildsForApplicationReturnsMappedList() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/builds?per_page=5000")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[" + getBuildJson() + "]}", MediaType.APPLICATION_JSON)); + + List result = client.getBuildsForApplication(UUID.fromString(APP_GUID)); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testGetBuildsForPackageReturnsMappedList() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/builds?package_guids=" + PACKAGE_GUID + + "&per_page=5000")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[" + getBuildJson() + "]}", MediaType.APPLICATION_JSON)); + + List result = client.getBuildsForPackage(UUID.fromString(PACKAGE_GUID)); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testGetApplicationRoutesReturnsMappedList() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/routes?per_page=5000")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[" + getRouteJson() + "]}", MediaType.APPLICATION_JSON)); + + List result = client.getApplicationRoutes(UUID.fromString(APP_GUID)); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testGetBuildPropagatesCloudOperationExceptionOnNotFound() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/builds/" + BUILD_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.NOT_FOUND)); + + Assertions.assertThrows(CloudOperationException.class, () -> client.getBuild(UUID.fromString(BUILD_GUID))); + } + + private void stubTargetSpaceMetadata() { + Mockito.when(target.getMetadata()) + .thenReturn(targetMetadata); + Mockito.when(targetMetadata.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); + } + + private void stubServiceInstanceLookup() { + factory.server() + .expect(MockRestRequestMatchers.requestTo( + MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=5000&space_guids=" + SPACE_GUID + + "&names=my-service")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[" + getServiceInstanceJson() + "]}", + MediaType.APPLICATION_JSON)); + } + + private static String getStackListJson() { + return "{\"resources\":[{\"guid\":\"" + APP_GUID + "\",\"name\":\"cflinuxfs4\",\"description\":\"Ubuntu\"}]}"; + } + + private static String getDomainListJson() { + return "{\"resources\":[{\"guid\":\"" + APP_GUID + "\",\"name\":\"" + DOMAIN_NAME + "\"}]}"; + } + + private static String getAppListJson() { + return "{\"resources\":[" + getAppJson() + "]}"; + } + + private static String getAppJson() { + return "{\"guid\":\"" + APP_GUID + "\",\"name\":\"my-app\",\"state\":\"STARTED\"," + + "\"lifecycle\":{\"type\":\"buildpack\",\"data\":{\"buildpacks\":[\"java_buildpack\"],\"stack\":\"cflinuxfs4\"}}}"; + } + + private static String getWebProcessJson() { + return "{\"guid\":\"" + APP_GUID + "\",\"type\":\"web\",\"command\":\"old.sh\",\"instances\":1,\"memory_in_mb\":256," + + "\"disk_in_mb\":1024}"; + } + + private static String getAuditEventListJson() { + return "{\"resources\":[{\"guid\":\"" + APP_GUID + "\",\"type\":\"audit.app.update\"," + + "\"actor\":{\"guid\":\"" + APP_GUID + "\",\"type\":\"user\",\"name\":\"admin\"}," + + "\"target\":{\"guid\":\"" + APP_GUID + "\",\"type\":\"app\",\"name\":\"my-app\"}}]}"; + } + + private static String getServiceInstanceJson() { + return "{\"guid\":\"" + SERVICE_INSTANCE_GUID + "\",\"name\":\"my-service\",\"type\":\"managed\"}"; + } + + private static String getTaskJson() { + return "{\"guid\":\"" + TASK_GUID + "\",\"name\":\"migrate\",\"command\":\"rails db:migrate\",\"state\":\"SUCCEEDED\"," + + "\"memory_in_mb\":256,\"disk_in_mb\":1024}"; + } + + private static String getBuildJson() { + return "{\"guid\":\"" + BUILD_GUID + "\",\"state\":\"STAGED\",\"package\":{\"guid\":\"" + PACKAGE_GUID + "\"}," + + "\"droplet\":{\"guid\":\"" + APP_GUID + "\"}}"; + } + + private static String getRouteJson() { + return "{\"guid\":\"" + APP_GUID + "\",\"host\":\"myhost\",\"path\":\"/mypath\",\"url\":\"myhost.example.com/mypath\"," + + "\"destinations\":[],\"relationships\":{\"space\":{\"data\":{\"guid\":\"" + SPACE_GUID + "\"}}," + + "\"domain\":{\"data\":{\"guid\":\"" + APP_GUID + "\"}}}}"; + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3ClientTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3ClientTest.java new file mode 100644 index 0000000000..0be1b7f6fd --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3ClientTest.java @@ -0,0 +1,158 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.net.URI; +import java.time.Duration; +import java.util.Optional; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Job; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.test.web.client.match.MockRestRequestMatchers; +import org.springframework.test.web.client.response.MockRestResponseCreators; + +class CloudControllerV3ClientTest { + + private static final String JOB_GUID = "66666666-6666-6666-6666-666666666666"; + private static final Duration TIMEOUT = Duration.ofMillis(10); + + private MockControllerClientFactory factory; + private CloudControllerV3Client client; + + @BeforeEach + void setUp() { + factory = MockControllerClientFactory.create(); + client = factory.client(); + } + + @Test + void testGetReturnsMappedBody() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(getJobJson("COMPLETE"), MediaType.APPLICATION_JSON)); + + V3Job job = client.get("/v3/jobs/" + JOB_GUID, V3Job.class); + + Assertions.assertEquals(JOB_GUID, job.guid()); + Assertions.assertEquals("COMPLETE", job.state()); + factory.verify(); + } + + @Test + void testGetThrowsOnNotFound() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.NOT_FOUND)); + + Assertions.assertThrows(CloudOperationException.class, () -> client.get("/v3/jobs/" + JOB_GUID, V3Job.class)); + } + + @Test + void testGetOptionalReturnsPresentOn200() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(getJobJson("COMPLETE"), MediaType.APPLICATION_JSON)); + + Optional job = client.getOptional("/v3/jobs/" + JOB_GUID, V3Job.class); + + Assertions.assertTrue(job.isPresent()); + Assertions.assertEquals(JOB_GUID, job.get() + .guid()); + factory.verify(); + } + + @Test + void testGetOptionalReturnsEmptyOn404() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.NOT_FOUND)); + + Optional job = client.getOptional("/v3/jobs/" + JOB_GUID, V3Job.class); + + Assertions.assertTrue(job.isEmpty()); + factory.verify(); + } + + @Test + void testGetOptionalRethrowsNonNotFound() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.INTERNAL_SERVER_ERROR)); + + Assertions.assertThrows(CloudOperationException.class, () -> client.getOptional("/v3/jobs/" + JOB_GUID, V3Job.class)); + } + + @Test + void testWaitForAsyncJobReturnsWhenComplete() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(getJobJson("COMPLETE"), MediaType.APPLICATION_JSON)); + + V3Job job = client.waitForAsyncJob("/" + JOB_GUID, TIMEOUT); + + Assertions.assertEquals(JOB_GUID, job.guid()); + Assertions.assertTrue(job.isComplete()); + factory.verify(); + } + + @Test + void testWaitForAsyncJobThrowsWhenFailed() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(getFailedJobJson(), MediaType.APPLICATION_JSON)); + + CloudOperationException exception = Assertions.assertThrows(CloudOperationException.class, + () -> client.waitForAsyncJob("/" + JOB_GUID, TIMEOUT)); + + Assertions.assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, exception.getStatusCode()); + factory.verify(); + } + + @Test + void testFollowAsyncJobFollowsLocation() { + HttpHeaders headers = new HttpHeaders(); + headers.setLocation(URI.create(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID)); + ResponseEntity accepted = new ResponseEntity<>(headers, HttpStatus.ACCEPTED); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(getJobJson("COMPLETE"), MediaType.APPLICATION_JSON)); + + client.followAsyncJob(accepted, TIMEOUT); + + factory.verify(); + } + + @Test + void testFollowAsyncJobReturnsImmediatelyWhenNoLocation() { + ResponseEntity created = new ResponseEntity<>(new HttpHeaders(), HttpStatus.CREATED); + + client.followAsyncJob(created, TIMEOUT); + + factory.verify(); + } + + private static String getJobJson(String state) { + return "{\"guid\":\"" + JOB_GUID + "\",\"state\":\"" + state + "\"}"; + } + + private static String getFailedJobJson() { + return "{\"guid\":\"" + JOB_GUID + "\",\"state\":\"FAILED\",\"errors\":[{\"detail\":\"boom\",\"title\":\"CF-JobFailed\"," + + "\"code\":10008}]}"; + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudSpaceClientTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudSpaceClientTest.java new file mode 100644 index 0000000000..85c58cfdd1 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudSpaceClientTest.java @@ -0,0 +1,136 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.web.client.match.MockRestRequestMatchers; +import org.springframework.test.web.client.response.MockRestResponseCreators; + +class CloudSpaceClientTest { + + private static final String SPACE_GUID = "11111111-1111-1111-1111-111111111111"; + private static final String ORG_GUID = "22222222-2222-2222-2222-222222222222"; + private static final String SPACE_NAME = "my-space"; + private static final String ORG_NAME = "my-org"; + + private MockControllerClientFactory factory; + private CloudSpaceClient client; + + @BeforeEach + void setUp() { + factory = MockControllerClientFactory.create(); + client = new CloudSpaceClient(factory.client()); + } + + @Test + void testGetSpaceByGuidReturnsMappedSpace() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/spaces/" + SPACE_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(getSpaceJson(), MediaType.APPLICATION_JSON)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/organizations/" + ORG_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(getOrgJson(), MediaType.APPLICATION_JSON)); + + CloudSpace result = client.getSpace(UUID.fromString(SPACE_GUID)); + + Assertions.assertEquals(SPACE_NAME, result.getName()); + Assertions.assertEquals(UUID.fromString(SPACE_GUID), result.getGuid()); + Assertions.assertEquals(ORG_NAME, result.getOrganization() + .getName()); + Assertions.assertEquals(UUID.fromString(ORG_GUID), result.getOrganization() + .getGuid()); + factory.verify(); + } + + @Test + void testGetSpaceByGuidThrowsWhenSpaceNotFound() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/spaces/" + SPACE_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.NOT_FOUND)); + + Assertions.assertThrows(CloudOperationException.class, () -> client.getSpace(UUID.fromString(SPACE_GUID))); + } + + @Test + void testGetSpaceByGuidThrowsWhenOrganizationNotFound() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/spaces/" + SPACE_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(getSpaceJson(), MediaType.APPLICATION_JSON)); + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/organizations/" + ORG_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.NOT_FOUND)); + + Assertions.assertThrows(CloudOperationException.class, () -> client.getSpace(UUID.fromString(SPACE_GUID))); + } + + @Test + void testGetSpaceByNameReturnsMappedSpace() { + stubOrgLookupWithResult(); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/spaces?organization_guids=" + ORG_GUID + + "&names=" + SPACE_NAME)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[" + getSpaceJson() + "]}", MediaType.APPLICATION_JSON)); + + CloudSpace result = client.getSpace(ORG_NAME, SPACE_NAME); + + Assertions.assertEquals(SPACE_NAME, result.getName()); + Assertions.assertEquals(ORG_NAME, result.getOrganization() + .getName()); + factory.verify(); + } + + @Test + void testGetSpaceByNameThrowsWhenOrganizationNotFound() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/organizations?names=" + ORG_NAME)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + + Assertions.assertThrows(CloudOperationException.class, () -> client.getSpace(ORG_NAME, SPACE_NAME)); + } + + @Test + void testGetSpaceByNameThrowsWhenSpaceNotFound() { + stubOrgLookupWithResult(); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/spaces?organization_guids=" + ORG_GUID + + "&names=" + SPACE_NAME)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + + Assertions.assertThrows(CloudOperationException.class, () -> client.getSpace(ORG_NAME, SPACE_NAME)); + } + + private void stubOrgLookupWithResult() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/organizations?names=" + ORG_NAME)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[" + getOrgJson() + "]}", MediaType.APPLICATION_JSON)); + } + + private static String getSpaceJson() { + return "{\"guid\":\"" + SPACE_GUID + "\",\"name\":\"" + SPACE_NAME + "\"," + + "\"relationships\":{\"organization\":{\"data\":{\"guid\":\"" + ORG_GUID + "\"}}}}"; + } + + private static String getOrgJson() { + return "{\"guid\":\"" + ORG_GUID + "\",\"name\":\"" + ORG_NAME + "\"}"; + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3OperationsTest.java index 756a65c1e0..332d96a9f3 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3OperationsTest.java @@ -1,9 +1,15 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest; +import java.net.URI; import java.util.List; +import java.util.UUID; +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudDomain; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudOrganization; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudOrganization; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Domain; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Domain.V3DomainRelationships; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Domain.V3RelationshipData; @@ -17,11 +23,19 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.web.client.match.MockRestRequestMatchers; +import org.springframework.test.web.client.response.MockRestResponseCreators; class DomainsV3OperationsTest { private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; private static final String ORG_GUID = "11111111-2222-3333-4444-555555555555"; + private static final String DOMAIN_GUID = "22222222-2222-2222-2222-222222222222"; + private static final String JOB_GUID = "66666666-6666-6666-6666-666666666666"; + private static final String DOMAIN_NAME = "example.com"; @Mock private CloudControllerV3Client cc; @@ -30,11 +44,16 @@ class DomainsV3OperationsTest { private DomainsV3Operations operations; + private MockControllerClientFactory factory; + private DomainsV3Operations serverOperations; + @BeforeEach void setUp() throws Exception { MockitoAnnotations.openMocks(this) .close(); operations = new DomainsV3Operations(cc, target); + factory = MockControllerClientFactory.create(); + serverOperations = new DomainsV3Operations(factory.client(), target); } @Test @@ -76,6 +95,170 @@ void testGetSharedDomainsReturnsEmptyWhenNoDomains() { .isEmpty()); } + @Test + void testAddDomainCreatesWhenAbsent() { + Mockito.when(target.getOrganization()) + .thenReturn(getOrganizationWithGuid(ORG_GUID)); + + stubEmptyDomainLookup(DOMAIN_NAME); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/domains")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withSuccess(getDomainResourceJson(DOMAIN_GUID, DOMAIN_NAME), + MediaType.APPLICATION_JSON)); + + serverOperations.addDomain(DOMAIN_NAME); + + factory.verify(); + } + + @Test + void testAddDomainSkipsCreationWhenDomainAlreadyExists() { + stubDomainLookupWithResult(DOMAIN_NAME, DOMAIN_GUID); + + serverOperations.addDomain(DOMAIN_NAME); + + factory.verify(); + } + + @Test + void testAddDomainThrowsWhenOrganizationMissing() { + stubEmptyDomainLookup(DOMAIN_NAME); + + Assertions.assertThrows(CloudOperationException.class, () -> serverOperations.addDomain(DOMAIN_NAME)); + } + + @Test + void testAddDomainThrowsWhenSpaceNotProvided() { + DomainsV3Operations noSpacedDomainOperations = new DomainsV3Operations(factory.client(), null); + + Assertions.assertThrows(IllegalArgumentException.class, () -> noSpacedDomainOperations.addDomain(DOMAIN_NAME)); + } + + @Test + void testDeleteDomainDeletesFoundDomain() { + stubDomainLookupWithResult(DOMAIN_NAME, DOMAIN_GUID); + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/domains/" + DOMAIN_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.DELETE)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.ACCEPTED) + .location(URI.create(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID))); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"guid\":\"" + JOB_GUID + "\",\"state\":\"COMPLETE\"}", + MediaType.APPLICATION_JSON)); + + serverOperations.deleteDomain(DOMAIN_NAME); + + factory.verify(); + } + + @Test + void testDeleteDomainThrowsWhenDomainNotFound() { + stubEmptyDomainLookup(DOMAIN_NAME); + + Assertions.assertThrows(CloudOperationException.class, () -> serverOperations.deleteDomain(DOMAIN_NAME)); + } + + @Test + void testDeleteDomainThrowsWhenSpaceNotProvided() { + DomainsV3Operations noSpaceOperations = new DomainsV3Operations(factory.client(), null); + + Assertions.assertThrows(IllegalArgumentException.class, () -> noSpaceOperations.deleteDomain(DOMAIN_NAME)); + } + + @Test + void testGetDefaultDomainReturnsMappedDomain() { + Mockito.when(target.getOrganization()) + .thenReturn(getOrganizationWithGuid(ORG_GUID)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo( + MockControllerClientFactory.BASE_URL + "/v3/organizations/" + ORG_GUID + "/domains/default")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(getDomainResourceJson(DOMAIN_GUID, DOMAIN_NAME), + MediaType.APPLICATION_JSON)); + + CloudDomain result = serverOperations.getDefaultDomain(); + + Assertions.assertEquals(DOMAIN_NAME, result.getName()); + factory.verify(); + } + + @Test + void testGetDomainsForOrganizationReturnsMappedDomains() { + Mockito.when(target.getOrganization()) + .thenReturn(getOrganizationWithGuid(ORG_GUID)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo( + MockControllerClientFactory.BASE_URL + "/v3/organizations/" + ORG_GUID + "/domains?per_page=5000")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess( + "{\"resources\":[" + getDomainResourceJson(DOMAIN_GUID, DOMAIN_NAME) + "]}", MediaType.APPLICATION_JSON)); + + List result = serverOperations.getDomainsForOrganization(); + + Assertions.assertEquals(1, result.size()); + Assertions.assertEquals(DOMAIN_NAME, result.getFirst() + .getName()); + factory.verify(); + } + + @Test + void testGetDomainsForOrganizationReturnsEmptyWhenNoDomains() { + Mockito.when(target.getOrganization()) + .thenReturn(getOrganizationWithGuid(ORG_GUID)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo( + MockControllerClientFactory.BASE_URL + "/v3/organizations/" + ORG_GUID + "/domains?per_page=5000")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + + Assertions.assertTrue(serverOperations.getDomainsForOrganization() + .isEmpty()); + factory.verify(); + } + + @Test + void testGetDomainsForOrganizationThrowsWhenSpaceNotProvided() { + DomainsV3Operations noSpaceOperations = new DomainsV3Operations(factory.client(), null); + + Assertions.assertThrows(IllegalArgumentException.class, noSpaceOperations::getDomainsForOrganization); + } + + private void stubEmptyDomainLookup(String name) { + factory.server() + .expect(MockRestRequestMatchers.requestTo( + MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + name + "&per_page=5000")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + } + + private void stubDomainLookupWithResult(String name, String domainGuid) { + factory.server() + .expect(MockRestRequestMatchers.requestTo( + MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + name + "&per_page=5000")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess( + "{\"resources\":[" + getDomainResourceJson(domainGuid, name) + "]}", MediaType.APPLICATION_JSON)); + } + + private static CloudOrganization getOrganizationWithGuid(String guid) { + return ImmutableCloudOrganization.builder() + .metadata(ImmutableCloudMetadata.of(UUID.fromString(guid))) + .name("my-org") + .build(); + } + + private static String getDomainResourceJson(String guid, String name) { + return "{\"guid\":\"" + guid + "\",\"name\":\"" + name + "\"}"; + } + private void mockDomainList(V3Domain... domains) { Mockito.when(cc.list(ArgumentMatchers.anyString(), ArgumentMatchers.>> any())) @@ -88,6 +271,7 @@ private static V3Domain getSharedDomain(String name) { private static V3Domain getPrivateDomain(String name) { V3DomainRelationships relationships = new V3DomainRelationships(new V3ToOneRelationship(new V3RelationshipData(ORG_GUID))); + return new V3Domain(GUID_STRING, name, null, null, null, relationships); } diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/MockControllerClientFactory.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/MockControllerClientFactory.java new file mode 100644 index 0000000000..a75217ae8d --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/MockControllerClientFactory.java @@ -0,0 +1,51 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.http.converter.ByteArrayHttpMessageConverter; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.web.client.RestClient; + +final class MockControllerClientFactory { + + static final String BASE_URL = "https://api.cf.test.base.com"; + + private final CloudControllerV3Client client; + private final MockRestServiceServer server; + + private MockControllerClientFactory(CloudControllerV3Client client, MockRestServiceServer server) { + this.client = client; + this.server = server; + } + + static MockControllerClientFactory create() { + RestClient.Builder builder = RestClient.builder() + .baseUrl(BASE_URL) + .messageConverters(converters -> { + converters.clear(); + converters.add(new ByteArrayHttpMessageConverter()); + converters.add(new StringHttpMessageConverter()); + converters.add(new MappingJackson2HttpMessageConverter(new ObjectMapper())); + }) + .defaultStatusHandler(new CloudControllerResponseErrorHandler()); + MockRestServiceServer server = MockRestServiceServer.bindTo(builder) + .build(); + + CloudControllerV3Client client = new CloudControllerV3Client(builder.build()); + return new MockControllerClientFactory(client, server); + } + + CloudControllerV3Client client() { + return client; + } + + MockRestServiceServer server() { + return server; + } + + void verify() { + server.verify(); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3OperationsTest.java new file mode 100644 index 0000000000..7d636d729c --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3OperationsTest.java @@ -0,0 +1,202 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.List; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudPackage; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.DockerInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableDockerCredentials; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableDockerInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Status; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Upload; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.web.client.match.MockRestRequestMatchers; +import org.springframework.test.web.client.response.MockRestResponseCreators; + +class PackagesV3OperationsTest { + + private static final String PACKAGE_GUID = "11111111-2222-3333-4444-555555555555"; + private static final String APP_GUID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"; + + @Mock + private CloudSpace target; + + private MockControllerClientFactory factory; + private PackagesV3Operations operations; + + @BeforeEach + void setUp() throws Exception { + MockitoAnnotations.openMocks(this) + .close(); + factory = MockControllerClientFactory.create(); + operations = new PackagesV3Operations(factory.client(), target); + } + + @Test + void testGetPackageReturnsMappedPackage() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/packages/" + PACKAGE_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(bitsPackageJson("READY", null), MediaType.APPLICATION_JSON)); + + CloudPackage result = operations.getPackage(UUID.fromString(PACKAGE_GUID)); + + Assertions.assertEquals(CloudPackage.Type.BITS, result.getType()); + Assertions.assertEquals(Status.READY, result.getStatus()); + Assertions.assertEquals(PACKAGE_GUID, result.getGuid() + .toString()); + factory.verify(); + } + + @Test + void testGetPackageThrowsWhenResponseIsNull() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/packages/" + PACKAGE_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("", MediaType.APPLICATION_JSON)); + + UUID packageGuid = UUID.fromString(PACKAGE_GUID); + Assertions.assertThrows(CloudOperationException.class, () -> operations.getPackage(packageGuid)); + } + + @Test + void testGetPackageThrowsOnNotFoundStatus() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/packages/" + PACKAGE_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.NOT_FOUND)); + + UUID packageGuid = UUID.fromString(PACKAGE_GUID); + Assertions.assertThrows(CloudOperationException.class, () -> operations.getPackage(packageGuid)); + } + + @Test + void testGetPackagesForApplicationReturnsMappedList() { + factory.server() + .expect(MockRestRequestMatchers.requestTo( + MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/packages?per_page=5000")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[" + bitsPackageJson("READY", null) + "]}", + MediaType.APPLICATION_JSON)); + + List result = operations.getPackagesForApplication(UUID.fromString(APP_GUID)); + + Assertions.assertEquals(1, result.size()); + Assertions.assertEquals(Status.READY, result.getFirst() + .getStatus()); + factory.verify(); + } + + @Test + void testGetPackagesForApplicationReturnsEmptyList() { + factory.server() + .expect(MockRestRequestMatchers.requestTo( + MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/packages?per_page=5000")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + + List result = operations.getPackagesForApplication(UUID.fromString(APP_GUID)); + + Assertions.assertEquals(0, result.size()); + factory.verify(); + } + + @Test + void testCreateDockerPackagePostsAndReturnsCreatedPackage() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/packages")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withSuccess(dockerPackageJson(), MediaType.APPLICATION_JSON)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/packages/" + PACKAGE_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(dockerPackageJson(), MediaType.APPLICATION_JSON)); + + DockerInfo dockerInfo = ImmutableDockerInfo.builder() + .image("my/image:latest") + .build(); + + CloudPackage result = operations.createDockerPackage(UUID.fromString(APP_GUID), dockerInfo); + + Assertions.assertEquals(CloudPackage.Type.DOCKER, result.getType()); + Assertions.assertEquals(PACKAGE_GUID, result.getGuid() + .toString()); + factory.verify(); + } + + @Test + void testCreateDockerPackageWithCredentials() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/packages")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withSuccess(dockerPackageJson(), MediaType.APPLICATION_JSON)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/packages/" + PACKAGE_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(dockerPackageJson(), MediaType.APPLICATION_JSON)); + + DockerInfo dockerInfo = ImmutableDockerInfo.builder() + .image("my/image:latest") + .credentials(ImmutableDockerCredentials.builder() + .username("user") + .password("secret") + .build()) + .build(); + + CloudPackage result = operations.createDockerPackage(UUID.fromString(APP_GUID), dockerInfo); + + Assertions.assertEquals(CloudPackage.Type.DOCKER, result.getType()); + factory.verify(); + } + + @Test + void testGetUploadStatusForBitsPackageIncludesErrorDetails() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/packages/" + PACKAGE_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(bitsPackageJson("FAILED", "boom"), MediaType.APPLICATION_JSON)); + + Upload result = operations.getUploadStatus(UUID.fromString(PACKAGE_GUID)); + + Assertions.assertEquals(Status.FAILED, result.getStatus()); + Assertions.assertEquals("boom", result.getErrorDetails() + .getDescription()); + factory.verify(); + } + + @Test + void testGetUploadStatusForDockerPackageHasNoErrorDetails() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/packages/" + PACKAGE_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(dockerPackageJson(), MediaType.APPLICATION_JSON)); + + Upload result = operations.getUploadStatus(UUID.fromString(PACKAGE_GUID)); + + Assertions.assertEquals(Status.READY, result.getStatus()); + Assertions.assertNull(result.getErrorDetails()); + factory.verify(); + } + + private static String bitsPackageJson(String state, String error) { + String data = error == null ? "{}" : "{\"error\":\"" + error + "\"}"; + return "{\"guid\":\"" + PACKAGE_GUID + "\",\"type\":\"bits\",\"state\":\"" + state + "\",\"data\":" + data + "}"; + } + + private static String dockerPackageJson() { + return "{\"guid\":\"" + PACKAGE_GUID + + "\",\"type\":\"docker\",\"state\":\"READY\",\"data\":{\"image\":\"my/image:latest\"}}"; + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3OperationsTest.java new file mode 100644 index 0000000000..2fce71fbb9 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3OperationsTest.java @@ -0,0 +1,418 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.Map; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudProcess; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.DropletInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.HealthCheckType; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudApplication; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableStaging; +import org.cloudfoundry.multiapps.controller.client.facade.domain.InstanceState; +import org.cloudfoundry.multiapps.controller.client.facade.domain.InstancesInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.LifecycleType; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Staging; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.web.client.match.MockRestRequestMatchers; +import org.springframework.test.web.client.response.MockRestResponseCreators; + +class ProcessesV3OperationsTest { + + private static final String APP_GUID = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + private static final String PROCESS_GUID = "11111111-2222-3333-4444-555555555555"; + private static final String PACKAGE_GUID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"; + private static final String DROPLET_GUID = "99999999-8888-7777-6666-555555555555"; + + @Mock + private CloudSpace target; + + private MockControllerClientFactory factory; + private ProcessesV3Operations operations; + + @BeforeEach + void setUp() throws Exception { + MockitoAnnotations.openMocks(this) + .close(); + factory = MockControllerClientFactory.create(); + operations = new ProcessesV3Operations(factory.client(), target); + } + + @Test + void testGetApplicationProcessReturnsMappedProcess() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/processes/web")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(getProcessJson(), MediaType.APPLICATION_JSON)); + + CloudProcess result = operations.getApplicationProcess(UUID.fromString(APP_GUID)); + + Assertions.assertEquals("start-cmd", result.getCommand()); + Assertions.assertEquals(2, result.getInstances()); + Assertions.assertEquals(256, result.getMemoryInMb()); + Assertions.assertEquals(1024, result.getDiskInMb()); + Assertions.assertEquals(HealthCheckType.HTTP, result.getHealthCheckType()); + Assertions.assertEquals("/health", result.getHealthCheckHttpEndpoint()); + factory.verify(); + } + + @Test + void testGetApplicationProcessReturnsNullWhenNoBody() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/processes/web")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.NO_CONTENT)); + + CloudProcess result = operations.getApplicationProcess(UUID.fromString(APP_GUID)); + + Assertions.assertNull(result); + factory.verify(); + } + + @Test + void testGetApplicationInstancesByGuidReturnsMappedInstances() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/processes/web/stats")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(getStatsJson(), MediaType.APPLICATION_JSON)); + + InstancesInfo result = operations.getApplicationInstances(UUID.fromString(APP_GUID)); + + Assertions.assertEquals(2, result.getInstances() + .size()); + Assertions.assertEquals(InstanceState.RUNNING, result.getInstances() + .get(0) + .getState()); + Assertions.assertEquals(InstanceState.CRASHED, result.getInstances() + .get(1) + .getState()); + factory.verify(); + } + + @Test + void testGetApplicationInstancesForStartedApplicationQueriesStats() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/processes/web/stats")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(getStatsJson(), MediaType.APPLICATION_JSON)); + + CloudApplication application = ImmutableCloudApplication.builder() + .metadata(ImmutableCloudMetadata.builder() + .guid(UUID.fromString(APP_GUID)) + .build()) + .name("my-app") + .state(CloudApplication.State.STARTED) + .build(); + + InstancesInfo result = operations.getApplicationInstances(application); + + Assertions.assertEquals(2, result.getInstances() + .size()); + factory.verify(); + } + + @Test + void testGetApplicationInstancesForStoppedApplicationReturnsEmptyWithoutHttp() { + CloudApplication application = ImmutableCloudApplication.builder() + .metadata(ImmutableCloudMetadata.builder() + .guid(UUID.fromString(APP_GUID)) + .build()) + .name("my-app") + .state(CloudApplication.State.STOPPED) + .build(); + + InstancesInfo result = operations.getApplicationInstances(application); + + Assertions.assertTrue(result.getInstances() + .isEmpty()); + factory.verify(); + } + + @Test + void testGetApplicationSshEnabledReturnsTrue() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/ssh_enabled")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"enabled\":true,\"reason\":\"\"}", MediaType.APPLICATION_JSON)); + + Assertions.assertTrue(operations.getApplicationSshEnabled(UUID.fromString(APP_GUID))); + factory.verify(); + } + + @Test + void testGetApplicationSshEnabledReturnsFalseWhenEnabledIsNull() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/ssh_enabled")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"reason\":\"disabled globally\"}", MediaType.APPLICATION_JSON)); + + Assertions.assertFalse(operations.getApplicationSshEnabled(UUID.fromString(APP_GUID))); + factory.verify(); + } + + @Test + void testGetApplicationFeaturesReturnsMap() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/features?per_page=5000")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess( + "{\"resources\":[{\"name\":\"ssh\",\"enabled\":true},{\"name\":\"revisions\",\"enabled\":false}]}", + MediaType.APPLICATION_JSON)); + + Map result = operations.getApplicationFeatures(UUID.fromString(APP_GUID)); + + Assertions.assertEquals(2, result.size()); + Assertions.assertTrue(result.get("ssh")); + Assertions.assertFalse(result.get("revisions")); + factory.verify(); + } + + @Test + void testGetApplicationFeaturesReturnsEmptyMap() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/features?per_page=5000")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + + Map result = operations.getApplicationFeatures(UUID.fromString(APP_GUID)); + + Assertions.assertTrue(result.isEmpty()); + factory.verify(); + } + + @Test + void testGetCurrentDropletForApplicationReturnsDropletInfo() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/droplets/current")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(getDropletJson(MockControllerClientFactory.BASE_URL + "/v3/packages/" + + PACKAGE_GUID), MediaType.APPLICATION_JSON)); + + DropletInfo result = operations.getCurrentDropletForApplication(UUID.fromString(APP_GUID)); + + Assertions.assertEquals(UUID.fromString(DROPLET_GUID), result.getGuid()); + Assertions.assertEquals(UUID.fromString(PACKAGE_GUID), result.getPackageGuid()); + factory.verify(); + } + + @Test + void testGetCurrentDropletForApplicationHandlesTrailingSlashInPackageLink() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/droplets/current")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(getDropletJson(MockControllerClientFactory.BASE_URL + "/v3/packages/" + + PACKAGE_GUID + "/"), MediaType.APPLICATION_JSON)); + + DropletInfo result = operations.getCurrentDropletForApplication(UUID.fromString(APP_GUID)); + + Assertions.assertEquals(UUID.fromString(PACKAGE_GUID), result.getPackageGuid()); + factory.verify(); + } + + @Test + void testGetCurrentDropletForApplicationThrowsWhenNoDroplet() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/droplets/current")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.NO_CONTENT)); + + Assertions.assertThrows(CloudOperationException.class, + () -> operations.getCurrentDropletForApplication(UUID.fromString(APP_GUID))); + } + + @Test + void testUpdateApplicationStagingUpdatesAppAndProcess() { + Mockito.when(target.getGuid()) + .thenReturn(null); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000&names=my-app")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[{\"guid\":\"" + APP_GUID + "\"}]}", + MediaType.APPLICATION_JSON)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andRespond(MockRestResponseCreators.withSuccess("{}", MediaType.APPLICATION_JSON)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/processes/web")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(getProcessJson(), MediaType.APPLICATION_JSON)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/processes/" + PROCESS_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andRespond(MockRestResponseCreators.withSuccess("{}", MediaType.APPLICATION_JSON)); + + Staging staging = ImmutableStaging.builder() + .command("start-cmd") + .stackName("cflinuxfs4") + .addBuildpacks("java_buildpack") + .healthCheckType("http") + .healthCheckHttpEndpoint("/health") + .healthCheckTimeout(60) + .readinessHealthCheckType("http") + .readinessHealthCheckHttpEndpoint("/ready") + .build(); + + operations.updateApplicationStaging("my-app", staging); + + factory.verify(); + } + + @Test + void testUpdateApplicationStagingUpdatesAppFeatures() { + Mockito.when(target.getGuid()) + .thenReturn(null); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000&names=my-app")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[{\"guid\":\"" + APP_GUID + "\"}]}", + MediaType.APPLICATION_JSON)); + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andRespond(MockRestResponseCreators.withSuccess("{}", MediaType.APPLICATION_JSON)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/features/ssh")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andRespond(MockRestResponseCreators.withSuccess("{}", MediaType.APPLICATION_JSON)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/processes/web")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(getProcessJson(), MediaType.APPLICATION_JSON)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/processes/" + PROCESS_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andRespond(MockRestResponseCreators.withSuccess("{}", MediaType.APPLICATION_JSON)); + + Staging staging = ImmutableStaging.builder() + .command("start-cmd") + .appFeatures(Map.of("ssh", true)) + .build(); + + operations.updateApplicationStaging("my-app", staging); + + factory.verify(); + } + + @Test + void testUpdateApplicationStagingIncludesSpaceGuidInLookupQuery() { + UUID spaceGuid = UUID.randomUUID(); + Mockito.when(target.getGuid()) + .thenReturn(spaceGuid); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000&space_guids=" + + spaceGuid + "&names=my-app")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[{\"guid\":\"" + APP_GUID + "\"}]}", + MediaType.APPLICATION_JSON)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andRespond(MockRestResponseCreators.withSuccess("{}", MediaType.APPLICATION_JSON)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/processes/web")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(getProcessJson(), MediaType.APPLICATION_JSON)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/processes/" + PROCESS_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andRespond(MockRestResponseCreators.withSuccess("{}", MediaType.APPLICATION_JSON)); + + Staging staging = ImmutableStaging.builder() + .command("start-cmd") + .build(); + + operations.updateApplicationStaging("my-app", staging); + + factory.verify(); + } + + @Test + void testUpdateApplicationStagingThrowsWhenApplicationNotFound() { + Mockito.when(target.getGuid()) + .thenReturn(null); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + + "/v3/apps?per_page=5000&names=missing-app")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + + Staging staging = ImmutableStaging.builder() + .command("cmd") + .build(); + + Assertions.assertThrows(CloudOperationException.class, () -> operations.updateApplicationStaging("missing-app", staging)); + } + + @Test + void testUpdateApplicationStagingWithCnbLifecycleWithoutBuildpacksThrows() { + Mockito.when(target.getGuid()) + .thenReturn(null); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000&names=my-app")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[{\"guid\":\"" + APP_GUID + "\"}]}", + MediaType.APPLICATION_JSON)); + + Staging staging = ImmutableStaging.builder() + .command("cmd") + .lifecycleType(LifecycleType.CNB) + .build(); + + Assertions.assertThrows(IllegalArgumentException.class, () -> operations.updateApplicationStaging("my-app", staging)); + } + + private static String getProcessJson() { + return "{\"guid\":\"" + PROCESS_GUID + "\",\"command\":\"start-cmd\",\"instances\":2,\"memory_in_mb\":256,\"disk_in_mb\":1024," + + "\"health_check\":{\"type\":\"http\",\"data\":{\"timeout\":60,\"invocation_timeout\":5,\"endpoint\":\"/health\"," + + "\"interval\":10}}}"; + } + + private static String getStatsJson() { + return "{\"resources\":[{\"index\":0,\"state\":\"RUNNING\",\"routable\":\"true\"}," + + "{\"index\":1,\"state\":\"CRASHED\",\"routable\":\"false\"}]}"; + } + + private static String getDropletJson(String packageHref) { + return "{\"guid\":\"" + DROPLET_GUID + "\",\"links\":{\"package\":{\"href\":\"" + packageHref + "\"}}}"; + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RolesV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RolesV3OperationsTest.java new file mode 100644 index 0000000000..0cda8c936a --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RolesV3OperationsTest.java @@ -0,0 +1,111 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.Set; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.UserRole; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.web.client.match.MockRestRequestMatchers; +import org.springframework.test.web.client.response.MockRestResponseCreators; + +class RolesV3OperationsTest { + + private static final UUID SPACE_GUID = UUID.fromString("11111111-2222-3333-4444-555555555555"); + private static final UUID USER_GUID = UUID.fromString("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"); + private static final String ROLES_URI = "/v3/roles?per_page=5000&space_guids=" + SPACE_GUID + "&user_guids=" + USER_GUID; + + @Mock + private CloudSpace target; + + private MockControllerClientFactory factory; + private RolesV3Operations operations; + + @BeforeEach + void setUp() throws Exception { + MockitoAnnotations.openMocks(this) + .close(); + factory = MockControllerClientFactory.create(); + operations = new RolesV3Operations(factory.client()); + } + + @Test + void testGetUserRolesReturnsMappedRoles() { + String json = "{\"resources\":[{\"guid\":\"r1\",\"type\":\"space_developer\"}," + + "{\"guid\":\"r2\",\"type\":\"space_manager\"}]}"; + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + ROLES_URI)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(json, MediaType.APPLICATION_JSON)); + + Set result = operations.getUserRolesBySpaceAndUser(SPACE_GUID, USER_GUID); + + Assertions.assertEquals(2, result.size()); + Assertions.assertTrue(result.contains(UserRole.SPACE_DEVELOPER)); + Assertions.assertTrue(result.contains(UserRole.SPACE_MANAGER)); + factory.verify(); + } + + @Test + void testGetUserRolesDeduplicatesRepeatedRoleTypes() { + String json = "{\"resources\":[{\"guid\":\"r1\",\"type\":\"space_developer\"}," + + "{\"guid\":\"r2\",\"type\":\"space_developer\"}]}"; + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + ROLES_URI)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(json, MediaType.APPLICATION_JSON)); + + Set result = operations.getUserRolesBySpaceAndUser(SPACE_GUID, USER_GUID); + + Assertions.assertEquals(1, result.size()); + Assertions.assertTrue(result.contains(UserRole.SPACE_DEVELOPER)); + factory.verify(); + } + + @Test + void testGetUserRolesReturnsEmptySetWhenNoRoles() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + ROLES_URI)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + + Set result = operations.getUserRolesBySpaceAndUser(SPACE_GUID, USER_GUID); + + Assertions.assertTrue(result.isEmpty()); + factory.verify(); + } + + @Test + void testGetUserRolesThrowsOnUnknownRoleType() { + String json = "{\"resources\":[{\"guid\":\"r1\",\"type\":\"galactic_overlord\"}]}"; + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + ROLES_URI)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(json, MediaType.APPLICATION_JSON)); + + Assertions.assertThrows(IllegalArgumentException.class, + () -> operations.getUserRolesBySpaceAndUser(SPACE_GUID, USER_GUID)); + } + + @Test + void testGetUserRolesThrowsOnServerError() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + ROLES_URI)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.INTERNAL_SERVER_ERROR)); + + Assertions.assertThrows(CloudOperationException.class, + () -> operations.getUserRolesBySpaceAndUser(SPACE_GUID, USER_GUID)); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3OperationsTest.java new file mode 100644 index 0000000000..85ae3bd8ea --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3OperationsTest.java @@ -0,0 +1,346 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.net.URI; +import java.util.List; +import java.util.Set; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudRoute; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudDomain; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudRoute; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.web.client.match.MockRestRequestMatchers; +import org.springframework.test.web.client.response.MockRestResponseCreators; + +class RoutesV3OperationsTest { + + private static final String SPACE_GUID = "11111111-1111-1111-1111-111111111111"; + private static final String DOMAIN_GUID = "22222222-2222-2222-2222-222222222222"; + private static final String ROUTE_GUID = "33333333-3333-3333-3333-333333333333"; + private static final String APP_GUID = "44444444-4444-4444-4444-444444444444"; + private static final String DESTINATION_GUID = "55555555-5555-5555-5555-555555555555"; + private static final String JOB_GUID = "66666666-6666-6666-6666-666666666666"; + private static final String DOMAIN_NAME = "example.com"; + + @Mock + private CloudSpace target; + + private MockControllerClientFactory factory; + private RoutesV3Operations operations; + + @BeforeEach + void setUp() throws Exception { + MockitoAnnotations.openMocks(this) + .close(); + factory = MockControllerClientFactory.create(); + operations = new RoutesV3Operations(factory.client(), target); + } + + @Test + void testAddRoutePostsToRoutes() { + Mockito.when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); + + stubDomainLookup(DOMAIN_NAME, DOMAIN_GUID); + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/routes")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withSuccess(getRouteJson(), MediaType.APPLICATION_JSON)); + + operations.addRoute("myhost", DOMAIN_NAME, "/mypath"); + + factory.verify(); + } + + @Test + void testAddRouteThrowsWhenDomainNotFound() { + Mockito.when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); + stubEmptyDomainLookup(DOMAIN_NAME); + + Assertions.assertThrows(CloudOperationException.class, () -> operations.addRoute("myhost", DOMAIN_NAME, "/mypath")); + } + + @Test + void testAddRouteThrowsWhenSpaceNotProvided() { + RoutesV3Operations noSpaceOperations = new RoutesV3Operations(factory.client(), null); + + Assertions.assertThrows(IllegalArgumentException.class, () -> noSpaceOperations.addRoute("myhost", DOMAIN_NAME, "/mypath")); + } + + @Test + void testDeleteRouteDeletesFoundRoute() { + Mockito.when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); + + stubDomainLookup(DOMAIN_NAME, DOMAIN_GUID); + stubRouteLookupWithResult(); + stubAsyncDelete(MockControllerClientFactory.BASE_URL + "/v3/routes/" + ROUTE_GUID); + + operations.deleteRoute("myhost", DOMAIN_NAME, "/mypath"); + + factory.verify(); + } + + @Test + void testDeleteRouteThrowsWhenRouteNotFound() { + Mockito.when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); + + stubDomainLookup(DOMAIN_NAME, DOMAIN_GUID); + factory.server() + .expect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + + Assertions.assertThrows(CloudOperationException.class, () -> operations.deleteRoute("myhost", DOMAIN_NAME, "/mypath")); + } + + @Test + void testDeleteRouteThrowsWhenSpaceNotProvided() { + RoutesV3Operations noSpaceOperations = new RoutesV3Operations(factory.client(), null); + + Assertions.assertThrows(IllegalArgumentException.class, () -> noSpaceOperations.deleteRoute("myhost", DOMAIN_NAME, "/mypath")); + } + + @Test + void testDeleteOrphanedRoutes() { + Mockito.when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); + + stubAsyncDelete(MockControllerClientFactory.BASE_URL + "/v3/spaces/" + SPACE_GUID + "/routes?unmapped=true"); + + operations.deleteOrphanedRoutes(); + + factory.verify(); + } + + @Test + void testGetRoutesReturnsMappedRoutes() { + Mockito.when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); + + stubDomainLookup(DOMAIN_NAME, DOMAIN_GUID); + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/routes?per_page=5000&domain_guids=" + + DOMAIN_GUID + "&space_guids=" + SPACE_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[" + getRouteJson() + "]}", MediaType.APPLICATION_JSON)); + + List routes = operations.getRoutes(DOMAIN_NAME); + + Assertions.assertEquals(1, routes.size()); + Assertions.assertEquals("myhost", routes.getFirst() + .getHost()); + factory.verify(); + } + + @Test + void testGetRoutesThrowsWhenDomainNotFound() { + Mockito.when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); + + stubEmptyDomainLookup(DOMAIN_NAME); + + Assertions.assertThrows(CloudOperationException.class, () -> operations.getRoutes(DOMAIN_NAME)); + } + + @Test + void testGetRoutesThrowsWhenSpaceNotProvided() { + RoutesV3Operations noSpaceOperations = new RoutesV3Operations(factory.client(), null); + + Assertions.assertThrows(IllegalArgumentException.class, () -> noSpaceOperations.getRoutes(DOMAIN_NAME)); + } + + @Test + void testGetApplicationRoutesReturnsMappedRoutes() { + factory.server() + .expect(MockRestRequestMatchers.requestTo( + MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/routes?per_page=5000")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[" + getRouteJson() + "]}", MediaType.APPLICATION_JSON)); + + List routes = operations.getApplicationRoutes(UUID.fromString(APP_GUID)); + + Assertions.assertEquals(1, routes.size()); + Assertions.assertEquals("myhost.example.com/mypath", routes.getFirst() + .getUrl()); + factory.verify(); + } + + @Test + void testGetApplicationRoutesReturnsEmptyList() { + factory.server() + .expect(MockRestRequestMatchers.requestTo( + MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/routes?per_page=5000")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + + List routes = operations.getApplicationRoutes(UUID.fromString(APP_GUID)); + + Assertions.assertEquals(0, routes.size()); + factory.verify(); + } + + @Test + void testUpdateApplicationRoutesAddsNewRoute() { + Mockito.when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); + + stubAppLookup("my-app"); + + factory.server() + .expect(MockRestRequestMatchers.requestTo( + MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/routes?per_page=5000")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo( + MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + DOMAIN_NAME + "&per_page=5000")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(getDomainJson(DOMAIN_GUID), MediaType.APPLICATION_JSON)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/routes?per_page=5000&domain_guids=" + + DOMAIN_GUID + "&space_guids=" + SPACE_GUID + "&hosts=myhost&paths=/mypath")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/routes")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withSuccess(getRouteJson(), MediaType.APPLICATION_JSON)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo( + MockControllerClientFactory.BASE_URL + "/v3/routes/" + ROUTE_GUID + "/destinations")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withSuccess()); + + CloudRoute newRoute = ImmutableCloudRoute.builder() + .domain(ImmutableCloudDomain.builder() + .name(DOMAIN_NAME) + .build()) + .host("myhost") + .path("/mypath") + .url("myhost.example.com/mypath") + .build(); + + operations.updateApplicationRoutes("my-app", Set.of(newRoute)); + + factory.verify(); + } + + @Test + void testUpdateApplicationRoutesRemovesOutdatedRoute() { + Mockito.when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); + + stubAppLookup("my-app"); + + factory.server() + .expect(MockRestRequestMatchers.requestTo( + MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/routes?per_page=5000")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[" + getRouteWithDestinationJson() + "]}", + MediaType.APPLICATION_JSON)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo( + MockControllerClientFactory.BASE_URL + "/v3/routes/" + ROUTE_GUID + "/destinations/" + DESTINATION_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.DELETE)) + .andRespond(MockRestResponseCreators.withSuccess()); + + operations.updateApplicationRoutes("my-app", Set.of()); + + factory.verify(); + } + + @Test + void testUpdateApplicationRoutesThrowsWhenApplicationNotFound() { + Mockito.when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000&names=missing-app" + + "&space_guids=" + SPACE_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + + Assertions.assertThrows(CloudOperationException.class, () -> operations.updateApplicationRoutes("missing-app", Set.of())); + } + + private void stubDomainLookup(String name, String domainGuid) { + factory.server() + .expect(MockRestRequestMatchers.requestTo( + MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + name + "&per_page=5000")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(getDomainJson(domainGuid), MediaType.APPLICATION_JSON)); + } + + private void stubEmptyDomainLookup(String name) { + factory.server() + .expect(MockRestRequestMatchers.requestTo( + MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + name + "&per_page=5000")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + } + + private void stubRouteLookupWithResult() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/routes?per_page=5000&domain_guids=" + + DOMAIN_GUID + "&space_guids=" + SPACE_GUID + "&hosts=myhost&paths=/mypath")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[" + getRouteJson() + "]}", MediaType.APPLICATION_JSON)); + } + + private void stubAppLookup(String appName) { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000&names=" + appName + + "&space_guids=" + SPACE_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[{\"guid\":\"" + APP_GUID + "\"}]}", + MediaType.APPLICATION_JSON)); + } + + private void stubAsyncDelete(String uri) { + factory.server() + .expect(MockRestRequestMatchers.requestTo(uri)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.DELETE)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.ACCEPTED) + .location(URI.create(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID))); + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"guid\":\"" + JOB_GUID + "\",\"state\":\"COMPLETE\"}", + MediaType.APPLICATION_JSON)); + } + + private static String getRouteJson() { + return "{\"guid\":\"" + ROUTE_GUID + "\",\"host\":\"myhost\",\"path\":\"/mypath\",\"url\":\"myhost.example.com/mypath\"," + + "\"destinations\":[],\"relationships\":{\"space\":{\"data\":{\"guid\":\"" + SPACE_GUID + "\"}}," + + "\"domain\":{\"data\":{\"guid\":\"" + DOMAIN_GUID + "\"}}}}"; + } + + private static String getRouteWithDestinationJson() { + return "{\"guid\":\"" + ROUTE_GUID + "\",\"host\":\"myhost\",\"path\":\"/mypath\",\"url\":\"myhost.example.com/mypath\"," + + "\"destinations\":[{\"guid\":\"" + DESTINATION_GUID + "\",\"app\":{\"guid\":\"" + APP_GUID + "\"},\"protocol\":\"http1\"}]," + + "\"relationships\":{\"space\":{\"data\":{\"guid\":\"" + SPACE_GUID + "\"}}," + + "\"domain\":{\"data\":{\"guid\":\"" + DOMAIN_GUID + "\"}}}}"; + } + + private static String getDomainJson(String domainGuid) { + return "{\"resources\":[{\"guid\":\"" + domainGuid + "\",\"name\":\"" + DOMAIN_NAME + "\"}]}"; + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3OperationsTest.java new file mode 100644 index 0000000000..efb4212a0e --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3OperationsTest.java @@ -0,0 +1,384 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.net.URI; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBinding; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.web.client.match.MockRestRequestMatchers; +import org.springframework.test.web.client.response.MockRestResponseCreators; + +class ServiceBindingsV3OperationsTest { + + private static final String APP_GUID = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + private static final String SERVICE_INSTANCE_GUID = "11111111-2222-3333-4444-555555555555"; + private static final String BINDING_GUID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"; + private static final String JOB_GUID = "99999999-8888-7777-6666-555555555555"; + + @Mock + private CloudSpace target; + + private MockControllerClientFactory factory; + private ServiceBindingsV3Operations operations; + + @BeforeEach + void setUp() throws Exception { + MockitoAnnotations.openMocks(this) + .close(); + factory = MockControllerClientFactory.create(); + operations = new ServiceBindingsV3Operations(factory.client(), target); + } + + @Test + void testBindServiceInstanceReturnsJobGuidFromLocation() { + Mockito.when(target.getGuid()) + .thenReturn(null); + + stubAppLookup("my-app"); + stubServiceInstanceLookup("my-service"); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.ACCEPTED) + .location(URI.create(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID))); + + Optional jobId = operations.bindServiceInstance("my-binding", "my-app", "my-service"); + + Assertions.assertTrue(jobId.isPresent()); + Assertions.assertEquals(JOB_GUID, jobId.get()); + factory.verify(); + } + + @Test + void testBindServiceInstanceWithParametersReturnsEmptyWhenNoLocation() { + Mockito.when(target.getGuid()) + .thenReturn(null); + + stubAppLookup("my-app"); + stubServiceInstanceLookup("my-service"); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.CREATED)); + + Optional jobId = operations.bindServiceInstance("my-binding", "my-app", "my-service", Map.of("key", "value")); + + Assertions.assertTrue(jobId.isEmpty()); + factory.verify(); + } + + @Test + void testBindServiceInstanceUsesSpaceGuidInLookupWhenTargetHasGuid() { + UUID spaceGuid = UUID.randomUUID(); + Mockito.when(target.getGuid()) + .thenReturn(spaceGuid); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000&space_guids=" + + spaceGuid + "&names=my-app")) + .andRespond(MockRestResponseCreators.withSuccess(getResourcesJson(APP_GUID), MediaType.APPLICATION_JSON)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + + "/v3/service_instances?per_page=5000&space_guids=" + spaceGuid + + "&names=my-service")) + .andRespond(MockRestResponseCreators.withSuccess(getResourcesJson(SERVICE_INSTANCE_GUID), MediaType.APPLICATION_JSON)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.CREATED)); + + Optional jobId = operations.bindServiceInstance("my-binding", "my-app", "my-service"); + + Assertions.assertTrue(jobId.isEmpty()); + factory.verify(); + } + + @Test + void testBindServiceInstanceThrowsWhenApplicationNotFound() { + Mockito.when(target.getGuid()) + .thenReturn(null); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000&names=missing-app")) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + + Assertions.assertThrows(CloudOperationException.class, + () -> operations.bindServiceInstance("my-binding", "missing-app", "my-service")); + } + + @Test + void testBindServiceInstanceThrowsWhenServiceInstanceNotFound() { + Mockito.when(target.getGuid()) + .thenReturn(null); + + stubAppLookup("my-app"); + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + + "/v3/service_instances?per_page=5000&names=missing-service")) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + + Assertions.assertThrows(CloudOperationException.class, + () -> operations.bindServiceInstance("my-binding", "my-app", "missing-service")); + } + + @Test + void testUnbindServiceInstanceByNameDeletesFoundBindings() { + Mockito.when(target.getGuid()) + .thenReturn(null); + + stubAppLookup("my-app"); + stubServiceInstanceLookup("my-service"); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + + "/v3/service_credential_bindings?app_guids=" + APP_GUID + + "&service_instance_guids=" + SERVICE_INSTANCE_GUID + + "&per_page=5000")) + .andRespond(MockRestResponseCreators.withSuccess(getResourcesJson(BINDING_GUID), MediaType.APPLICATION_JSON)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings/" + + BINDING_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.DELETE)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.ACCEPTED) + .location(URI.create(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID))); + + List jobIds = operations.unbindServiceInstance("my-app", "my-service"); + + Assertions.assertEquals(List.of(JOB_GUID), jobIds); + factory.verify(); + } + + @Test + void testUnbindServiceInstanceByGuidDeletesFoundBindings() { + UUID appGuid = UUID.fromString(APP_GUID); + UUID serviceInstanceGuid = UUID.fromString(SERVICE_INSTANCE_GUID); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + + "/v3/service_credential_bindings?app_guids=" + APP_GUID + + "&service_instance_guids=" + SERVICE_INSTANCE_GUID + + "&per_page=5000")) + .andRespond(MockRestResponseCreators.withSuccess(getResourcesJson(BINDING_GUID), MediaType.APPLICATION_JSON)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings/" + + BINDING_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.DELETE)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.NO_CONTENT)); + + List jobIds = operations.unbindServiceInstance(appGuid, serviceInstanceGuid); + + Assertions.assertTrue(jobIds.isEmpty()); + factory.verify(); + } + + @Test + void testUnbindServiceInstanceThrowsWhenNoBindingFound() { + UUID appGuid = UUID.fromString(APP_GUID); + UUID serviceInstanceGuid = UUID.fromString(SERVICE_INSTANCE_GUID); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + + "/v3/service_credential_bindings?app_guids=" + APP_GUID + + "&service_instance_guids=" + SERVICE_INSTANCE_GUID + + "&per_page=5000")) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + + Assertions.assertThrows(CloudOperationException.class, + () -> operations.unbindServiceInstance(appGuid, serviceInstanceGuid)); + } + + @Test + void testDeleteServiceBindingReturnsJobGuid() { + UUID bindingGuid = UUID.fromString(BINDING_GUID); + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings/" + + BINDING_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.DELETE)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.ACCEPTED) + .location(URI.create(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID))); + + Optional jobId = operations.deleteServiceBinding(bindingGuid); + + Assertions.assertTrue(jobId.isPresent()); + Assertions.assertEquals(JOB_GUID, jobId.get()); + factory.verify(); + } + + @Test + void testGetServiceBindingReturnsMappedBinding() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings?guids=" + + BINDING_GUID + "&per_page=5000")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(getBindingListJson(), MediaType.APPLICATION_JSON)); + + CloudServiceBinding result = operations.getServiceBinding(UUID.fromString(BINDING_GUID)); + + Assertions.assertEquals(UUID.fromString(APP_GUID), result.getApplicationGuid()); + Assertions.assertEquals(UUID.fromString(SERVICE_INSTANCE_GUID), result.getServiceInstanceGuid()); + factory.verify(); + } + + @Test + void testGetServiceBindingReturnsNullWhenNoBindingFound() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings?guids=" + + BINDING_GUID + "&per_page=5000")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + + CloudServiceBinding result = operations.getServiceBinding(UUID.fromString(BINDING_GUID)); + + Assertions.assertNull(result); + factory.verify(); + } + + @Test + void testGetServiceAppBindingsReturnsMappedList() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + + "/v3/service_credential_bindings?service_instance_guids=" + + SERVICE_INSTANCE_GUID + "&type=app&per_page=5000")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(getBindingListJson(), MediaType.APPLICATION_JSON)); + + List result = operations.getServiceAppBindings(UUID.fromString(SERVICE_INSTANCE_GUID)); + + Assertions.assertEquals(1, result.size()); + Assertions.assertEquals(UUID.fromString(SERVICE_INSTANCE_GUID), result.getFirst() + .getServiceInstanceGuid()); + factory.verify(); + } + + @Test + void testGetAppBindingsReturnsEmptyList() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + + "/v3/service_credential_bindings?app_guids=" + APP_GUID + "&per_page=5000")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + + List result = operations.getAppBindings(UUID.fromString(APP_GUID)); + + Assertions.assertTrue(result.isEmpty()); + factory.verify(); + } + + @Test + void testGetServiceBindingsForApplicationReturnsMappedList() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + + "/v3/service_credential_bindings?app_guids=" + APP_GUID + + "&service_instance_guids=" + SERVICE_INSTANCE_GUID + + "&per_page=5000")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(getBindingListJson(), MediaType.APPLICATION_JSON)); + + List result = operations.getServiceBindingsForApplication(UUID.fromString(APP_GUID), + UUID.fromString(SERVICE_INSTANCE_GUID)); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testGetServiceBindingParametersReturnsMap() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings/" + + BINDING_GUID + "/parameters")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"foo\":\"bar\"}", MediaType.APPLICATION_JSON)); + + Map result = operations.getServiceBindingParameters(UUID.fromString(BINDING_GUID)); + + Assertions.assertEquals("bar", result.get("foo")); + factory.verify(); + } + + @Test + void testGetServiceBindingParametersThrowsWhenNotFound() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings/" + + BINDING_GUID + "/parameters")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.NOT_FOUND)); + + Assertions.assertThrows(CloudOperationException.class, + () -> operations.getServiceBindingParameters(UUID.fromString(BINDING_GUID))); + } + + @Test + void testUpdateServiceBindingMetadataUpdates() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings/" + + BINDING_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andRespond(MockRestResponseCreators.withSuccess("{\"guid\":\"" + BINDING_GUID + "\"}", MediaType.APPLICATION_JSON)); + + Metadata metadata = Metadata.builder() + .label("key", "value") + .build(); + + Assertions.assertDoesNotThrow(() -> operations.updateServiceBindingMetadata(UUID.fromString(BINDING_GUID), metadata)); + factory.verify(); + } + + @Test + void testUpdateServiceBindingMetadataThrowsOnError() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings/" + + BINDING_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.UNPROCESSABLE_ENTITY)); + + Metadata metadata = Metadata.builder() + .build(); + + Assertions.assertThrows(CloudOperationException.class, + () -> operations.updateServiceBindingMetadata(UUID.fromString(BINDING_GUID), metadata)); + } + + private void stubAppLookup(String appName) { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000&names=" + appName)) + .andRespond(MockRestResponseCreators.withSuccess(getResourcesJson(APP_GUID), MediaType.APPLICATION_JSON)); + } + + private void stubServiceInstanceLookup(String serviceName) { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=5000&names=" + + serviceName)) + .andRespond(MockRestResponseCreators.withSuccess(getResourcesJson(SERVICE_INSTANCE_GUID), MediaType.APPLICATION_JSON)); + } + + private static String getResourcesJson(String guid) { + return "{\"resources\":[{\"guid\":\"" + guid + "\"}]}"; + } + + private static String getBindingListJson() { + return "{\"resources\":[{\"guid\":\"" + BINDING_GUID + "\",\"name\":\"my-binding\",\"type\":\"app\"," + + "\"relationships\":{\"app\":{\"data\":{\"guid\":\"" + APP_GUID + "\"}}," + + "\"service_instance\":{\"data\":{\"guid\":\"" + SERVICE_INSTANCE_GUID + "\"}}}}]}"; + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBrokersV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBrokersV3OperationsTest.java new file mode 100644 index 0000000000..1d49ee67f5 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBrokersV3OperationsTest.java @@ -0,0 +1,297 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.net.URI; +import java.util.List; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBroker; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceBroker; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServicePlanVisibility; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.web.client.match.MockRestRequestMatchers; +import org.springframework.test.web.client.response.MockRestResponseCreators; + +class ServiceBrokersV3OperationsTest { + + private static final String BROKER_GUID = "11111111-2222-3333-4444-555555555555"; + private static final String SPACE_GUID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"; + private static final String OFFERING_GUID = "66666666-7777-8888-9999-000000000000"; + private static final String PLAN_GUID = "abcabcab-cdcd-efef-1212-343434343434"; + private static final String JOB_GUID = "99999999-8888-7777-6666-555555555555"; + + private static final String BROKERS_URI = MockControllerClientFactory.BASE_URL + "/v3/service_brokers"; + + @Mock + private CloudSpace target; + @Mock + private CloudMetadata targetMetadata; + + private MockControllerClientFactory factory; + private ServiceBrokersV3Operations operations; + + @BeforeEach + void setUp() throws Exception { + MockitoAnnotations.openMocks(this) + .close(); + factory = MockControllerClientFactory.create(); + operations = new ServiceBrokersV3Operations(factory.client(), target); + } + + @Test + void testGetServiceBrokersReturnsMappedList() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(BROKERS_URI + "?per_page=5000")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[" + getBrokerJson("broker-a") + "]}", + MediaType.APPLICATION_JSON)); + + List result = operations.getServiceBrokers(); + + Assertions.assertEquals(1, result.size()); + Assertions.assertEquals("broker-a", result.getFirst() + .getName()); + Assertions.assertEquals("https://broker-a.example.com", result.getFirst() + .getUrl()); + factory.verify(); + } + + @Test + void testGetServiceBrokersReturnsEmptyList() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(BROKERS_URI + "?per_page=5000")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + + List result = operations.getServiceBrokers(); + + Assertions.assertTrue(result.isEmpty()); + factory.verify(); + } + + @Test + void testGetServiceBrokerReturnsMappedBroker() { + stubBrokerLookup("broker-a", "{\"resources\":[" + getBrokerJson("broker-a") + "]}"); + + CloudServiceBroker result = operations.getServiceBroker("broker-a"); + + Assertions.assertEquals("broker-a", result.getName()); + Assertions.assertEquals(UUID.fromString(BROKER_GUID), result.getMetadata() + .getGuid()); + factory.verify(); + } + + @Test + void testGetServiceBrokerThrowsWhenRequiredAndMissing() { + stubBrokerLookup("missing", "{\"resources\":[]}"); + + Assertions.assertThrows(CloudOperationException.class, () -> operations.getServiceBroker("missing")); + factory.verify(); + } + + @Test + void testGetServiceBrokerReturnsNullWhenNotRequiredAndMissing() { + stubBrokerLookup("missing", "{\"resources\":[]}"); + + Assertions.assertNull(operations.getServiceBroker("missing", false)); + factory.verify(); + } + + @Test + void testCreateServiceBrokerReturnsJobGuidFromLocation() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(BROKERS_URI)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.ACCEPTED) + .location(URI.create(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID))); + + CloudServiceBroker broker = ImmutableCloudServiceBroker.builder() + .name("broker-a") + .url("https://broker-a.example.com") + .username("user") + .password("pass") + .spaceGuid(SPACE_GUID) + .build(); + + String jobGuid = operations.createServiceBroker(broker); + + Assertions.assertEquals(JOB_GUID, jobGuid); + factory.verify(); + } + + @Test + void testCreateServiceBrokerReturnsNullWhenNoLocation() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(BROKERS_URI)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.CREATED)); + + CloudServiceBroker broker = ImmutableCloudServiceBroker.builder() + .name("broker-a") + .url("https://broker-a.example.com") + .build(); + + Assertions.assertNull(operations.createServiceBroker(broker)); + factory.verify(); + } + + @Test + void testCreateServiceBrokerThrowsWhenNull() { + Assertions.assertThrows(IllegalArgumentException.class, () -> operations.createServiceBroker(null)); + } + + @Test + void testDeleteServiceBrokerLooksUpThenDeletesAndReturnsJobGuid() { + stubBrokerLookup("broker-a", "{\"resources\":[" + getBrokerJson("broker-a") + "]}"); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(BROKERS_URI + "/" + BROKER_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.DELETE)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.ACCEPTED) + .location(URI.create(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID))); + + String jobGuid = operations.deleteServiceBroker("broker-a"); + + Assertions.assertEquals(JOB_GUID, jobGuid); + factory.verify(); + } + + @Test + void testDeleteServiceBrokerThrowsWhenBrokerMissing() { + stubBrokerLookup("missing", "{\"resources\":[]}"); + + Assertions.assertThrows(CloudOperationException.class, () -> operations.deleteServiceBroker("missing")); + factory.verify(); + } + + @Test + void testUpdateServiceBrokerLooksUpThenUpdatesAndReturnsJobGuid() { + stubBrokerLookup("broker-a", "{\"resources\":[" + getBrokerJson("broker-a") + "]}"); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(BROKERS_URI + "/" + BROKER_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.ACCEPTED) + .location(URI.create(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID))); + + CloudServiceBroker broker = ImmutableCloudServiceBroker.builder() + .name("broker-a") + .url("https://broker-a-new.example.com") + .username("user") + .password("pass") + .build(); + + String jobGuid = operations.updateServiceBroker(broker); + + Assertions.assertEquals(JOB_GUID, jobGuid); + factory.verify(); + } + + @Test + void testUpdateServiceBrokerThrowsWhenNull() { + Assertions.assertThrows(IllegalArgumentException.class, () -> operations.updateServiceBroker(null)); + } + + @Test + void testUpdateServiceBrokerThrowsWhenBrokerMissing() { + stubBrokerLookup("missing", "{\"resources\":[]}"); + + CloudServiceBroker broker = ImmutableCloudServiceBroker.builder() + .name("missing") + .url("https://missing.example.com") + .build(); + + Assertions.assertThrows(CloudOperationException.class, () -> operations.updateServiceBroker(broker)); + factory.verify(); + } + + @Test + void testUpdateServicePlanVisibilityForBrokerUpdatesEachPlan() { + Mockito.when(target.getMetadata()) + .thenReturn(targetMetadata); + + Mockito.when(targetMetadata.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); + + stubBrokerLookup("broker-a", "{\"resources\":[" + getBrokerJson("broker-a") + "]}"); + + factory.server() + .expect( + MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_offerings?service_broker_guids=" + + BROKER_GUID + "&space_guids=" + SPACE_GUID + "&per_page=5000")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[{\"guid\":\"" + OFFERING_GUID + "\"}]}", + MediaType.APPLICATION_JSON)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_plans?service_offering_guids=" + + OFFERING_GUID + "&per_page=5000")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[{\"guid\":\"" + PLAN_GUID + "\"}]}", + MediaType.APPLICATION_JSON)); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_plans/" + PLAN_GUID + + "/visibility")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andRespond(MockRestResponseCreators.withSuccess("{\"type\":\"public\"}", MediaType.APPLICATION_JSON)); + + operations.updateServicePlanVisibilityForBroker("broker-a", ServicePlanVisibility.PUBLIC); + + factory.verify(); + } + + @Test + void testUpdateServicePlanVisibilityForBrokerWithNoOfferingsUpdatesNothing() { + Mockito.when(target.getMetadata()) + .thenReturn(targetMetadata); + + Mockito.when(targetMetadata.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); + + stubBrokerLookup("broker-a", "{\"resources\":[" + getBrokerJson("broker-a") + "]}"); + + factory.server() + .expect( + MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_offerings?service_broker_guids=" + + BROKER_GUID + "&space_guids=" + SPACE_GUID + "&per_page=5000")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + + operations.updateServicePlanVisibilityForBroker("broker-a", ServicePlanVisibility.ADMIN); + + factory.verify(); + } + + @Test + void testUpdateServicePlanVisibilityForBrokerThrowsWhenBrokerMissing() { + stubBrokerLookup("missing", "{\"resources\":[]}"); + + Assertions.assertThrows(CloudOperationException.class, + () -> operations.updateServicePlanVisibilityForBroker("missing", ServicePlanVisibility.PUBLIC)); + factory.verify(); + } + + private void stubBrokerLookup(String name, String responseJson) { + factory.server() + .expect(MockRestRequestMatchers.requestTo(BROKERS_URI + "?names=" + name + "&per_page=5000")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(responseJson, MediaType.APPLICATION_JSON)); + } + + private static String getBrokerJson(String name) { + return "{\"guid\":\"" + BROKER_GUID + "\",\"name\":\"" + name + "\",\"url\":\"https://" + name + + ".example.com\",\"relationships\":{\"space\":{\"data\":{\"guid\":\"" + SPACE_GUID + "\"}}}}"; + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3OperationsTest.java index 8451dff9b8..aab642299a 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3OperationsTest.java @@ -1,31 +1,35 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest; import java.util.List; +import java.util.Map; import java.util.UUID; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; -import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; -import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.ArgumentMatchers; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.web.client.match.MockRestRequestMatchers; +import org.springframework.test.web.client.response.MockRestResponseCreators; class ServiceInstancesV3OperationsTest { private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; private static final UUID SPACE_GUID = UUID.fromString("11111111-2222-3333-4444-555555555555"); + private static final String OFFERING_GUID = "aaaaaaaa-1111-1111-1111-aaaaaaaaaaaa"; + private static final String PLAN_GUID = "bbbbbbbb-2222-2222-2222-bbbbbbbbbbbb"; - @Mock - private CloudControllerV3Client cc; @Mock private CloudSpace target; @Mock @@ -33,20 +37,23 @@ class ServiceInstancesV3OperationsTest { private ServiceInstancesV3Operations operations; + private MockControllerClientFactory factory; + @BeforeEach void setUp() throws Exception { MockitoAnnotations.openMocks(this) .close(); + factory = MockControllerClientFactory.create(); Mockito.when(target.getMetadata()) .thenReturn(targetMetadata); Mockito.when(targetMetadata.getGuid()) .thenReturn(SPACE_GUID); - operations = new ServiceInstancesV3Operations(cc, target); + operations = new ServiceInstancesV3Operations(factory.client(), target); } @Test void testGetRequiredServiceInstanceGuidReturnsGuidWhenFound() { - mockServiceInstanceList(getManagedInstance("my-service")); + stubServiceInstanceLookup("my-service", getManagedInstanceJson("my-service")); UUID result = operations.getRequiredServiceInstanceGuid("my-service"); @@ -55,7 +62,7 @@ void testGetRequiredServiceInstanceGuidReturnsGuidWhenFound() { @Test void testGetRequiredServiceInstanceGuidThrowsWhenNotFound() { - mockServiceInstanceList(); + stubServiceInstanceLookup("missing", null); CloudOperationException thrown = Assertions.assertThrows(CloudOperationException.class, () -> operations.getRequiredServiceInstanceGuid("missing")); @@ -65,14 +72,14 @@ void testGetRequiredServiceInstanceGuidThrowsWhenNotFound() { @Test void testGetServiceInstanceWithoutAuxiliaryContentReturnsNullWhenNotRequiredAndMissing() { - mockServiceInstanceList(); + stubServiceInstanceLookup("missing", null); Assertions.assertNull(operations.getServiceInstanceWithoutAuxiliaryContent("missing", false)); } @Test void testGetServiceInstanceWithoutAuxiliaryContentThrowsWhenRequiredAndMissing() { - mockServiceInstanceList(); + stubServiceInstanceLookup("missing", null); CloudOperationException thrown = Assertions.assertThrows(CloudOperationException.class, () -> operations.getServiceInstanceWithoutAuxiliaryContent("missing")); @@ -82,7 +89,7 @@ void testGetServiceInstanceWithoutAuxiliaryContentThrowsWhenRequiredAndMissing() @Test void testGetServiceInstanceWithoutAuxiliaryContentMapsWhenFound() { - mockServiceInstanceList(getManagedInstance("my-service")); + stubServiceInstanceLookup("my-service", getManagedInstanceJson("my-service")); CloudServiceInstance result = operations.getServiceInstanceWithoutAuxiliaryContent("my-service"); @@ -91,28 +98,393 @@ void testGetServiceInstanceWithoutAuxiliaryContentMapsWhenFound() { @Test void testGetServiceInstanceNameReturnsNameFromGet() { - Mockito.when(cc.get(ArgumentMatchers.contains("/v3/service_instances/"), ArgumentMatchers.eq(V3ServiceInstance.class))) - .thenReturn(getManagedInstance("my-service")); + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + SPACE_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(getManagedInstanceJson("my-service"), MediaType.APPLICATION_JSON)); Assertions.assertEquals("my-service", operations.getServiceInstanceName(SPACE_GUID)); + factory.verify(); } @Test void testGetServiceInstanceNameReturnsNullWhenGetReturnsNull() { - Mockito.when(cc.get(ArgumentMatchers.anyString(), ArgumentMatchers.eq(V3ServiceInstance.class))) - .thenReturn(null); + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + SPACE_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess()); Assertions.assertNull(operations.getServiceInstanceName(SPACE_GUID)); + factory.verify(); + } + + @Test + void testCreateServiceInstancePostsManagedInstance() { + stubOfferingLookup("my-offering", null); + stubPlanLookup("my-plan"); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_instances")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withSuccess()); + + CloudServiceInstance serviceInstance = ImmutableCloudServiceInstance.builder() + .name("my-service") + .label("my-offering") + .plan("my-plan") + .build(); + + operations.createServiceInstance(serviceInstance); + + factory.verify(); + } + + @Test + void testCreateServiceInstanceThrowsWhenPlanNotFound() { + stubOfferingLookup("my-offering", null); + factory.server() + .expect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + + CloudServiceInstance serviceInstance = ImmutableCloudServiceInstance.builder() + .name("my-service") + .label("my-offering") + .plan("missing-plan") + .build(); + + Assertions.assertThrows(CloudOperationException.class, () -> operations.createServiceInstance(serviceInstance)); + } + + @Test + void testCreateServiceInstanceThrowsWhenSpaceNotProvided() { + ServiceInstancesV3Operations noSpaceOperations = new ServiceInstancesV3Operations(factory.client(), null); + + CloudServiceInstance serviceInstance = ImmutableCloudServiceInstance.builder() + .name("my-service") + .label("my-offering") + .plan("my-plan") + .build(); + + Assertions.assertThrows(IllegalArgumentException.class, () -> noSpaceOperations.createServiceInstance(serviceInstance)); + } + + @Test + void testCreateUserProvidedServiceInstancePosts() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_instances")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withSuccess()); + + CloudServiceInstance serviceInstance = ImmutableCloudServiceInstance.builder() + .name("my-ups") + .syslogDrainUrl("syslog://example.com") + .credentials(Map.of("user", "admin")) + .build(); + + operations.createUserProvidedServiceInstance(serviceInstance); + + factory.verify(); + } + + @Test + void testCreateUserProvidedServiceInstanceThrowsWhenSpaceNotProvided() { + ServiceInstancesV3Operations noSpaceOperations = new ServiceInstancesV3Operations(factory.client(), null); + + CloudServiceInstance serviceInstance = ImmutableCloudServiceInstance.builder() + .name("my-ups") + .build(); + + Assertions.assertThrows(IllegalArgumentException.class, () -> noSpaceOperations.createUserProvidedServiceInstance(serviceInstance)); + } + + @Test + void testDeleteServiceInstanceByNameDeletesFoundInstance() { + stubServiceInstanceLookup("my-service", getManagedInstanceJson("my-service")); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + GUID_STRING)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.DELETE)) + .andRespond(MockRestResponseCreators.withSuccess()); + + operations.deleteServiceInstance("my-service"); + + factory.verify(); + } + + @Test + void testDeleteServiceInstanceByNameThrowsWhenNotFound() { + stubServiceInstanceLookup("missing", null); + + Assertions.assertThrows(CloudOperationException.class, () -> operations.deleteServiceInstance("missing")); + } + + @Test + void testDeleteServiceInstanceByInstanceDeletes() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + GUID_STRING)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.DELETE)) + .andRespond(MockRestResponseCreators.withSuccess()); + + CloudServiceInstance serviceInstance = ImmutableCloudServiceInstance.builder() + .metadata( + org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata.builder() + .guid( + UUID.fromString( + GUID_STRING)) + .build()) + .name("my-service") + .build(); + + operations.deleteServiceInstance(serviceInstance); + + factory.verify(); + } + + @Test + void testUpdateServicePlanUpdatesRelationship() { + stubServiceInstanceLookup("my-service", getManagedInstanceJson("my-service")); + + stubPlanForNameResolution(); + stubOfferingForNameResolution(); + + stubOfferingLookup("my-offering", null); + stubPlanLookup("new-plan"); + stubPatch(); + + operations.updateServicePlan("my-service", "new-plan"); + + factory.verify(); + } + + @Test + void testUpdateServicePlanSkipsUserProvided() { + stubServiceInstanceLookup("my-ups", getUserProvidedInstanceJson("my-ups")); + + operations.updateServicePlan("my-ups", "new-plan"); + + factory.verify(); + } + + @Test + void testUpdateServiceParametersUpdatesManaged() { + stubServiceInstanceLookup("my-service", getManagedInstanceJson("my-service")); + stubPatch(); + + operations.updateServiceParameters("my-service", Map.of("foo", "bar")); + + factory.verify(); + } + + @Test + void testUpdateServiceParametersUpdatesUserProvided() { + stubServiceInstanceLookup("my-ups", getUserProvidedInstanceJson("my-ups")); + stubPatch(); + + operations.updateServiceParameters("my-ups", Map.of("user", "admin")); + + factory.verify(); + } + + @Test + void testUpdateServiceTagsUpdates() { + stubServiceInstanceLookup("my-service", getManagedInstanceJson("my-service")); + stubPatch(); + + operations.updateServiceTags("my-service", List.of("tag1", "tag2")); + + factory.verify(); + } + + @Test + void testUpdateServiceTagsThrowsWhenNotFound() { + stubServiceInstanceLookup("missing", null); + + Assertions.assertThrows(CloudOperationException.class, () -> operations.updateServiceTags("missing", List.of("tag1"))); + } + + @Test + void testUpdateServiceSyslogDrainUrlUpdatesUserProvided() { + stubServiceInstanceLookup("my-ups", getUserProvidedInstanceJson("my-ups")); + stubPatch(); + + operations.updateServiceSyslogDrainUrl("my-ups", "syslog://new.example.com"); + + factory.verify(); + } + + @Test + void testUpdateServiceSyslogDrainUrlSkipsManaged() { + stubServiceInstanceLookup("my-service", getManagedInstanceJson("my-service")); + + operations.updateServiceSyslogDrainUrl("my-service", "syslog://new.example.com"); + + factory.verify(); + } + + @Test + void testUpdateServiceInstanceMetadataUpdates() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + GUID_STRING)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andRespond(MockRestResponseCreators.withSuccess()); + + Metadata metadata = Metadata.builder() + .label("key", "value") + .build(); + + operations.updateServiceInstanceMetadata(UUID.fromString(GUID_STRING), metadata); + + factory.verify(); + } + + @Test + void testUpdateServiceInstanceMetadataUpdatesWithNullMetadata() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + GUID_STRING)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andRespond(MockRestResponseCreators.withSuccess()); + + operations.updateServiceInstanceMetadata(UUID.fromString(GUID_STRING), null); + + factory.verify(); + } + + @Test + void testGetServiceInstancesByMetadataLabelSelectorMapsUserProvided() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=5000" + + "&space_guids=" + SPACE_GUID + "&label_selector=env")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[" + getUserProvidedInstanceJson("ups-1") + "]}", + MediaType.APPLICATION_JSON)); + + List result = operations.getServiceInstancesByMetadataLabelSelector("env"); + + Assertions.assertEquals(1, result.size()); + Assertions.assertEquals("ups-1", result.getFirst() + .getName()); + factory.verify(); + } + + @Test + void testGetServiceInstancesByMetadataLabelSelectorReturnsEmpty() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=5000" + + "&space_guids=" + SPACE_GUID + "&label_selector=env")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + + List result = operations.getServiceInstancesByMetadataLabelSelector("env"); + + Assertions.assertEquals(0, result.size()); + factory.verify(); + } + + @Test + void testGetServiceInstancesWithoutAuxiliaryContentByMetadataLabelSelectorMaps() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=5000" + + "&space_guids=" + SPACE_GUID + "&label_selector=env")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[" + getManagedInstanceJson("mi-1") + "]}", + MediaType.APPLICATION_JSON)); + + List result = operations.getServiceInstancesWithoutAuxiliaryContentByMetadataLabelSelector("env"); + + Assertions.assertEquals(1, result.size()); + Assertions.assertEquals("mi-1", result.getFirst() + .getName()); + Assertions.assertEquals(ServiceInstanceType.MANAGED, result.getFirst() + .getType()); + factory.verify(); + } + + @Test + void testGetServiceInstancesWithoutAuxiliaryContentByNames() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=5000" + + "&space_guids=" + SPACE_GUID + "&names=my-service")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[" + getManagedInstanceJson("my-service") + "]}", + MediaType.APPLICATION_JSON)); + + List result = operations.getServiceInstancesWithoutAuxiliaryContentByNames(List.of("my-service")); + + Assertions.assertEquals(1, result.size()); + Assertions.assertEquals("my-service", result.getFirst() + .getName()); + factory.verify(); + } + + @Test + void testGetServiceInstancesWithoutAuxiliaryContentByNamesReturnsEmptyForEmptyInput() { + Assertions.assertEquals(0, operations.getServiceInstancesWithoutAuxiliaryContentByNames(List.of()) + .size()); + + factory.verify(); + } + + private void stubServiceInstanceLookup(String name, String instanceJson) { + String body = instanceJson == null ? "{\"resources\":[]}" : "{\"resources\":[" + instanceJson + "]}"; + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=5000" + + "&space_guids=" + SPACE_GUID + "&names=" + name)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(body, MediaType.APPLICATION_JSON)); + } + + private void stubOfferingLookup(String label, String broker) { + String uri = MockControllerClientFactory.BASE_URL + "/v3/service_offerings?per_page=5000&space_guids=" + SPACE_GUID + "&names=" + + label + (broker == null ? "" : "&service_broker_names=" + broker); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(uri)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[{\"guid\":\"" + OFFERING_GUID + "\",\"name\":\"" + label + + "\"}]}", MediaType.APPLICATION_JSON)); + } + + private void stubPlanLookup(String planName) { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_plans?per_page=5000" + + "&service_offering_guids=" + OFFERING_GUID + "&names=" + planName)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[{\"guid\":\"" + PLAN_GUID + "\",\"name\":\"" + planName + + "\"}]}", MediaType.APPLICATION_JSON)); + } + + private void stubPlanForNameResolution() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_plans/" + PLAN_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"guid\":\"" + PLAN_GUID + "\",\"name\":\"my-plan\",\"relationships\":" + + "{\"service_offering\":{\"data\":{\"guid\":\"" + OFFERING_GUID + + "\"}}}}", MediaType.APPLICATION_JSON)); + } + + private void stubOfferingForNameResolution() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_offerings/" + OFFERING_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"guid\":\"" + OFFERING_GUID + "\",\"name\":\"my-offering\"}", + MediaType.APPLICATION_JSON)); + } + + private void stubPatch() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + GUID_STRING)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andRespond(MockRestResponseCreators.withSuccess()); } - private void mockServiceInstanceList(V3ServiceInstance... instances) { - Mockito.when(cc.list(ArgumentMatchers.anyString(), - ArgumentMatchers.>> any())) - .thenReturn(List.of(instances)); + private static String getManagedInstanceJson(String name) { + return "{\"guid\":\"" + GUID_STRING + "\",\"name\":\"" + name + "\",\"type\":\"managed\",\"relationships\":" + + "{\"service_plan\":{\"data\":{\"guid\":\"" + PLAN_GUID + "\"}}}}"; } - private static V3ServiceInstance getManagedInstance(String name) { - return new V3ServiceInstance(GUID_STRING, name, "managed", null, null, null, null, null, null, null); + private static String getUserProvidedInstanceJson(String name) { + return "{\"guid\":\"" + GUID_STRING + "\",\"name\":\"" + name + "\",\"type\":\"user-provided\"}"; } } diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3OperationsTest.java new file mode 100644 index 0000000000..93b86a3513 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3OperationsTest.java @@ -0,0 +1,241 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceKey; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.web.client.match.MockRestRequestMatchers; +import org.springframework.test.web.client.response.MockRestResponseCreators; + +class ServiceKeysV3OperationsTest { + + private static final String SERVICE_INSTANCE_GUID = "11111111-2222-3333-4444-555555555555"; + private static final String KEY_GUID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"; + private static final String JOB_GUID = "99999999-8888-7777-6666-555555555555"; + private static final String KEY_NAME = "my-key"; + + private static final String LIST_KEYS_QUERY = "/v3/service_credential_bindings?per_page=5000&type=key&service_instance_guids=" + + SERVICE_INSTANCE_GUID; + private static final String LOOKUP_KEY_QUERY = LIST_KEYS_QUERY + "&names=" + KEY_NAME; + + private MockControllerClientFactory factory; + private ServiceKeysV3Operations operations; + + @BeforeEach + void setUp() throws Exception { + MockitoAnnotations.openMocks(this) + .close(); + factory = MockControllerClientFactory.create(); + operations = new ServiceKeysV3Operations(factory.client()); + } + + @Test + void testGetServiceKeyReturnsMappedKeyWithCredentials() { + stubKeyLookup(getServiceKeyResourcesJson()); + stubCredentials(); + + CloudServiceKey result = operations.getServiceKey(getManagedServiceInstance(), KEY_NAME); + + Assertions.assertEquals(KEY_NAME, result.getName()); + Assertions.assertEquals("secret", result.getCredentials() + .get("password")); + Assertions.assertEquals(UUID.fromString(KEY_GUID), result.getMetadata() + .getGuid()); + factory.verify(); + } + + @Test + void testGetServiceKeyReturnsNullWhenNotFound() { + stubKeyLookup("{\"resources\":[]}"); + + CloudServiceKey result = operations.getServiceKey(getManagedServiceInstance(), KEY_NAME); + + Assertions.assertNull(result); + factory.verify(); + } + + @Test + void testGetServiceKeysReturnsMappedKeysWithoutCredentials() { + stubKeyList(getServiceKeyResourcesJson()); + + List result = operations.getServiceKeys(getManagedServiceInstance()); + + Assertions.assertEquals(1, result.size()); + Assertions.assertEquals(KEY_NAME, result.getFirst() + .getName()); + factory.verify(); + } + + @Test + void testGetServiceKeysReturnsEmptyListWhenNoKeys() { + stubKeyList("{\"resources\":[]}"); + + List result = operations.getServiceKeys(getManagedServiceInstance()); + + Assertions.assertEquals(0, result.size()); + factory.verify(); + } + + @Test + void testGetServiceKeysWithCredentialsFetchesDetailsPerKey() { + stubKeyList(getServiceKeyResourcesJson()); + stubCredentials(); + + List result = operations.getServiceKeysWithCredentials(getManagedServiceInstance()); + + Assertions.assertEquals(1, result.size()); + Assertions.assertEquals("secret", result.getFirst() + .getCredentials() + .get("password")); + factory.verify(); + } + + @Test + void testCreateServiceKeyFromModelReturnsJobGuid() { + stubAcceptedPost(); + + Optional result = operations.createServiceKey(getServiceKeyModel(), getManagedServiceInstance()); + + Assertions.assertEquals(Optional.of(JOB_GUID), result); + factory.verify(); + } + + @Test + void testCreateServiceKeyWithParametersReturnsJobGuid() { + stubAcceptedPost(); + + Optional result = operations.createServiceKey(getManagedServiceInstance(), KEY_NAME, Map.of("k", "v")); + + Assertions.assertEquals(Optional.of(JOB_GUID), result); + factory.verify(); + } + + @Test + void testCreateServiceKeyReturnsEmptyWhenNoLocationHeader() { + factory.server() + .expect(MockRestRequestMatchers.requestTo( + MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.ACCEPTED)); + + Optional result = operations.createServiceKey(getManagedServiceInstance(), KEY_NAME, null); + + Assertions.assertEquals(Optional.empty(), result); + factory.verify(); + } + + @Test + void testCreateServiceKeyThrowsForUserProvidedService() { + Assertions.assertThrows(IllegalArgumentException.class, + () -> operations.createServiceKey(getUserProvidedServiceInstance(), KEY_NAME, null)); + } + + @Test + void testCreateAndFetchServiceKeyFollowsJobThenReturnsMappedKey() { + stubAcceptedPost(); + stubJobComplete(); + stubKeyLookup(getServiceKeyResourcesJson()); + stubCredentials(); + + CloudServiceKey result = operations.createAndFetchServiceKey(getServiceKeyModel(), getManagedServiceInstance()); + + Assertions.assertEquals(KEY_NAME, result.getName()); + Assertions.assertEquals("secret", result.getCredentials() + .get("password")); + factory.verify(); + } + + @Test + void testCreateAndFetchServiceKeyReturnsNullWhenKeyNotFoundAfterJob() { + stubAcceptedPost(); + stubJobComplete(); + stubKeyLookup("{\"resources\":[]}"); + + CloudServiceKey result = operations.createAndFetchServiceKey(getServiceKeyModel(), getManagedServiceInstance()); + + Assertions.assertNull(result); + factory.verify(); + } + + private void stubKeyLookup(String responseJson) { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + LOOKUP_KEY_QUERY)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(responseJson, MediaType.APPLICATION_JSON)); + } + + private void stubKeyList(String responseJson) { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + LIST_KEYS_QUERY)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(responseJson, MediaType.APPLICATION_JSON)); + } + + private void stubCredentials() { + factory.server() + .expect(MockRestRequestMatchers.requestTo( + MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings/" + KEY_GUID + "/details")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"credentials\":{\"password\":\"secret\"}}", + MediaType.APPLICATION_JSON)); + } + + private void stubAcceptedPost() { + factory.server() + .expect(MockRestRequestMatchers.requestTo( + MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.ACCEPTED) + .location(java.net.URI.create( + MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID))); + } + + private void stubJobComplete() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"guid\":\"" + JOB_GUID + "\",\"state\":\"COMPLETE\"}", + MediaType.APPLICATION_JSON)); + } + + private static String getServiceKeyResourcesJson() { + return "{\"resources\":[{\"guid\":\"" + KEY_GUID + "\",\"name\":\"" + KEY_NAME + "\",\"type\":\"key\"}]}"; + } + + private static CloudServiceInstance getManagedServiceInstance() { + return ImmutableCloudServiceInstance.builder() + .name("my-service") + .metadata(ImmutableCloudMetadata.of(UUID.fromString(SERVICE_INSTANCE_GUID))) + .type(ServiceInstanceType.MANAGED) + .build(); + } + + private static CloudServiceInstance getUserProvidedServiceInstance() { + return ImmutableCloudServiceInstance.builder() + .name("my-ups") + .metadata(ImmutableCloudMetadata.of(UUID.fromString(SERVICE_INSTANCE_GUID))) + .type(ServiceInstanceType.USER_PROVIDED) + .build(); + } + + private static CloudServiceKey getServiceKeyModel() { + return ImmutableCloudServiceKey.builder() + .name(KEY_NAME) + .build(); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3OperationsTest.java index a9204e7683..48de698301 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3OperationsTest.java @@ -50,9 +50,9 @@ ArgumentMatchers.>> any List result = operations.getServiceOfferings(); Assertions.assertEquals(1, result.size()); - Assertions.assertEquals("my-offering", result.get(0) + Assertions.assertEquals("my-offering", result.getFirst() .getName()); - Assertions.assertEquals(2, result.get(0) + Assertions.assertEquals(2, result.getFirst() .getServicePlans() .size()); } @@ -61,6 +61,7 @@ ArgumentMatchers.>> any void testGetServiceOfferingsScopesQueryToSpaceWhenTargetHasGuid() { Mockito.when(target.getGuid()) .thenReturn(SPACE_GUID); + Mockito.when(cc.list(ArgumentMatchers.contains("/v3/service_offerings"), ArgumentMatchers.>> any())) .thenReturn(List.of()); @@ -78,6 +79,7 @@ ArgumentMatchers.>> void testGetServiceOfferingsExcludesSpaceGuidWhenTargetGuidNull() { Mockito.when(target.getGuid()) .thenReturn(null); + Mockito.when(cc.list(ArgumentMatchers.contains("/v3/service_offerings"), ArgumentMatchers.>> any())) .thenReturn(List.of()); diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3OperationsTest.java new file mode 100644 index 0000000000..bd2a4fce20 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3OperationsTest.java @@ -0,0 +1,145 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServicePlanVisibility; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.web.client.match.MockRestRequestMatchers; +import org.springframework.test.web.client.response.MockRestResponseCreators; + +class ServicePlansV3OperationsTest { + + private static final String BROKER_GUID = "11111111-1111-1111-1111-111111111111"; + private static final String OFFERING_GUID = "22222222-2222-2222-2222-222222222222"; + private static final String PLAN_GUID = "33333333-3333-3333-3333-333333333333"; + private static final UUID SPACE_GUID = UUID.fromString("44444444-4444-4444-4444-444444444444"); + private static final String BROKER_NAME = "my-broker"; + + @Mock + private CloudSpace target; + + private MockControllerClientFactory factory; + private ServicePlansV3Operations operations; + + @BeforeEach + void setUp() throws Exception { + MockitoAnnotations.openMocks(this) + .close(); + factory = MockControllerClientFactory.create(); + operations = new ServicePlansV3Operations(factory.client(), target); + } + + @Test + void testUpdateServicePlanVisibilityForBrokerUpdatesAllPlansWhenNoSpaceFilter() { + Mockito.when(target.getGuid()) + .thenReturn(null); + + stubBrokerLookup("{\"resources\":[{\"guid\":\"" + BROKER_GUID + "\"}]}"); + stubOfferingLookup(null, "{\"resources\":[{\"guid\":\"" + OFFERING_GUID + "\"}]}"); + stubPlanLookup("{\"resources\":[{\"guid\":\"" + PLAN_GUID + "\"}]}"); + stubVisibilityPatch(PLAN_GUID); + + operations.updateServicePlanVisibilityForBroker(BROKER_NAME, ServicePlanVisibility.PUBLIC); + + factory.verify(); + } + + @Test + void testUpdateServicePlanVisibilityForBrokerAppendsSpaceGuidWhenTargetHasGuid() { + Mockito.when(target.getGuid()) + .thenReturn(SPACE_GUID); + stubBrokerLookup("{\"resources\":[{\"guid\":\"" + BROKER_GUID + "\"}]}"); + stubOfferingLookup(SPACE_GUID, "{\"resources\":[{\"guid\":\"" + OFFERING_GUID + "\"}]}"); + stubPlanLookup("{\"resources\":[{\"guid\":\"" + PLAN_GUID + "\"}]}"); + stubVisibilityPatch(PLAN_GUID); + + operations.updateServicePlanVisibilityForBroker(BROKER_NAME, ServicePlanVisibility.ORGANIZATION); + + factory.verify(); + } + + @Test + void testUpdateServicePlanVisibilityForBrokerThrowsWhenBrokerNotFound() { + stubBrokerLookup("{\"resources\":[]}"); + + Assertions.assertThrows(CloudOperationException.class, + () -> operations.updateServicePlanVisibilityForBroker(BROKER_NAME, ServicePlanVisibility.PUBLIC)); + factory.verify(); + } + + @Test + void testUpdateServicePlanVisibilityForBrokerDoesNothingWhenNoOfferings() { + Mockito.when(target.getGuid()) + .thenReturn(null); + + stubBrokerLookup("{\"resources\":[{\"guid\":\"" + BROKER_GUID + "\"}]}"); + stubOfferingLookup(null, "{\"resources\":[]}"); + + operations.updateServicePlanVisibilityForBroker(BROKER_NAME, ServicePlanVisibility.PUBLIC); + + factory.verify(); + } + + @Test + void testUpdateServicePlanVisibilityForBrokerDoesNothingWhenNoPlans() { + Mockito.when(target.getGuid()) + .thenReturn(null); + + stubBrokerLookup("{\"resources\":[{\"guid\":\"" + BROKER_GUID + "\"}]}"); + stubOfferingLookup(null, "{\"resources\":[{\"guid\":\"" + OFFERING_GUID + "\"}]}"); + stubPlanLookup("{\"resources\":[]}"); + + operations.updateServicePlanVisibilityForBroker(BROKER_NAME, ServicePlanVisibility.PUBLIC); + + factory.verify(); + } + + private void stubBrokerLookup(String responseJson) { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_brokers?names=" + + BROKER_NAME + "&per_page=5000")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(responseJson, MediaType.APPLICATION_JSON)); + } + + private void stubOfferingLookup(UUID spaceGuid, String responseJson) { + String uri = MockControllerClientFactory.BASE_URL + "/v3/service_offerings?service_broker_guids=" + BROKER_GUID + + "&per_page=5000"; + + if (spaceGuid != null) { + uri = uri + "&space_guids=" + spaceGuid; + } + + factory.server() + .expect(MockRestRequestMatchers.requestTo(uri)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(responseJson, MediaType.APPLICATION_JSON)); + } + + private void stubPlanLookup(String responseJson) { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_plans?service_offering_guids=" + + OFFERING_GUID + "&per_page=5000")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(responseJson, MediaType.APPLICATION_JSON)); + } + + private void stubVisibilityPatch(String planGuid) { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_plans/" + planGuid + + "/visibility")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andRespond(MockRestResponseCreators.withStatus(HttpStatus.OK)); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/TasksV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/TasksV3OperationsTest.java new file mode 100644 index 0000000000..516cb322d0 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/TasksV3OperationsTest.java @@ -0,0 +1,118 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudTask; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudTask; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.test.web.client.match.MockRestRequestMatchers; +import org.springframework.test.web.client.response.MockRestResponseCreators; + +class TasksV3OperationsTest { + + private static final String APP_GUID = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; + private static final String TASK_GUID = "99999999-8888-7777-6666-555555555555"; + + @Mock + private CloudSpace target; + + private MockControllerClientFactory factory; + private TasksV3Operations operations; + + @BeforeEach + void setUp() throws Exception { + MockitoAnnotations.openMocks(this) + .close(); + factory = MockControllerClientFactory.create(); + operations = new TasksV3Operations(factory.client(), target); + } + + @Test + void testGetTaskReturnsMappedTask() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/tasks/" + TASK_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess(getTaskJson("SUCCEEDED"), MediaType.APPLICATION_JSON)); + + CloudTask result = operations.getTask(UUID.fromString(TASK_GUID)); + + Assertions.assertEquals("migrate", result.getName()); + Assertions.assertEquals(CloudTask.State.SUCCEEDED, result.getState()); + factory.verify(); + } + + @Test + void testRunTaskPostsBodyAndReturnsCreatedTask() { + stubAppLookup(); + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/tasks")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withSuccess(getTaskJson("RUNNING"), MediaType.APPLICATION_JSON)); + + CloudTask task = ImmutableCloudTask.builder() + .name("migrate") + .command("rails db:migrate") + .build(); + + CloudTask result = operations.runTask("my-app", task); + + Assertions.assertEquals(CloudTask.State.RUNNING, result.getState()); + factory.verify(); + } + + @Test + void testCancelTaskPostsToCancelAction() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/tasks/" + TASK_GUID + + "/actions/cancel")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withSuccess(getTaskJson("CANCELING"), MediaType.APPLICATION_JSON)); + + CloudTask result = operations.cancelTask(UUID.fromString(TASK_GUID)); + + Assertions.assertEquals(CloudTask.State.CANCELING, result.getState()); + factory.verify(); + } + + @Test + void testRunTaskThrowsWhenApplicationNotFound() { + Mockito.when(target.getGuid()) + .thenReturn(null); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000&names=missing-app")) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + + CloudTask task = ImmutableCloudTask.builder() + .name("migrate") + .command("cmd") + .build(); + + Assertions.assertThrows(CloudOperationException.class, () -> operations.runTask("missing-app", task)); + } + + private void stubAppLookup() { + Mockito.when(target.getGuid()) + .thenReturn(null); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000&names=my-app")) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[{\"guid\":\"" + APP_GUID + "\"}]}", + MediaType.APPLICATION_JSON)); + } + + private static String getTaskJson(String state) { + return "{\"guid\":\"" + TASK_GUID + "\",\"name\":\"migrate\",\"command\":\"rails db:migrate\",\"state\":\"" + state + + "\",\"memory_in_mb\":256,\"disk_in_mb\":1024}"; + } + +} From cd813ba5fbed6c59bfc5aa9c1588033b9823ea81 Mon Sep 17 00:00:00 2001 From: Krasimir Kargov Date: Tue, 15 Sep 2026 12:09:11 +0300 Subject: [PATCH 09/15] Fixing sonar issues and additional unit tests for coverage LMCROSSITXSADEPLOY-3563 --- .../multiapps/controller/Constants.java | 4 + .../rest/CloudControllerV3Endpoints.java | 5 +- .../rest/ApplicationsV3OperationsTest.java | 93 +- .../facade/rest/BuildsV3OperationsTest.java | 3 +- .../CloudControllerRestClientV3ImplTest.java | 1104 ++++++++++++++++- .../facade/rest/CloudSpaceClientTest.java | 6 +- .../facade/rest/DomainsV3OperationsTest.java | 27 +- .../facade/rest/EventsV3OperationsTest.java | 53 +- .../rest/ProcessesV3OperationsTest.java | 26 +- .../facade/rest/ReactiveFanOutTest.java | 2 +- .../facade/rest/RoutesV3OperationsTest.java | 43 +- .../rest/ServiceBindingsV3OperationsTest.java | 33 +- .../rest/ServiceBrokersV3OperationsTest.java | 19 +- .../ServiceInstancesV3OperationsTest.java | 14 +- .../rest/ServiceKeysV3OperationsTest.java | 4 +- .../ServiceOfferingsV3OperationsTest.java | 44 +- .../rest/ServicePlansV3OperationsTest.java | 19 +- .../facade/rest/StacksV3OperationsTest.java | 14 +- .../facade/rest/TasksV3OperationsTest.java | 11 +- 19 files changed, 1286 insertions(+), 238 deletions(-) diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Constants.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Constants.java index 705fc88798..f5c6047cbe 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Constants.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Constants.java @@ -35,4 +35,8 @@ public class Constants { public static final String PACKAGE_LINK = "package"; public static final String WEB_PROCESS_TYPE = "web"; + private Constants() { + + } + } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Endpoints.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Endpoints.java index 5f82bf893b..58211f4cee 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Endpoints.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Endpoints.java @@ -15,7 +15,6 @@ public final class CloudControllerV3Endpoints { public static final String PACKAGES = "/v3/packages"; public static final String BUILDS = "/v3/builds"; public static final String TASKS = "/v3/tasks"; - // public static final String PROCESSES = "/v3/processes"; public static final String PROCESSES = "/processes/"; public static final String STACKS = "/v3/stacks"; public static final String AUDIT_EVENTS = "/v3/audit_events"; @@ -70,4 +69,8 @@ public final class CloudControllerV3Endpoints { public static final String AMPERSAND_HOSTS = "&hosts="; public static final String AMPERSAND_PATHS = "&paths="; + private CloudControllerV3Endpoints() { + + } + } diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3OperationsTest.java index 8574140df9..fd361be660 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3OperationsTest.java @@ -15,7 +15,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; @@ -23,6 +22,8 @@ import org.springframework.test.web.client.match.MockRestRequestMatchers; import org.springframework.test.web.client.response.MockRestResponseCreators; +import static org.mockito.Mockito.when; + class ApplicationsV3OperationsTest { private static final String SPACE_GUID = "11111111-2222-3333-4444-555555555555"; @@ -46,8 +47,8 @@ void setUp() throws Exception { @Test void testCreateApplicationPostsAndReturnsGuid() { - Mockito.when(target.getGuid()) - .thenReturn(UUID.fromString(SPACE_GUID)); + when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); factory.server() .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps")) @@ -66,8 +67,8 @@ void testCreateApplicationPostsAndReturnsGuid() { @Test void testCreateApplicationWithScaleAlsoScalesWebProcess() { - Mockito.when(target.getGuid()) - .thenReturn(UUID.fromString(SPACE_GUID)); + when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); factory.server() .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps")) @@ -102,8 +103,8 @@ void testCreateApplicationWithScaleAlsoScalesWebProcess() { @Test void testCreateApplicationThrowsWhenTargetGuidIsNull() { - Mockito.when(target.getGuid()) - .thenReturn(null); + when(target.getGuid()) + .thenReturn(null); ApplicationToCreateDto dto = ImmutableApplicationToCreateDto.builder() .name("my-app") @@ -114,8 +115,8 @@ void testCreateApplicationThrowsWhenTargetGuidIsNull() { @Test void testDeleteApplicationLooksUpGuidAndDeletes() { - Mockito.when(target.getGuid()) - .thenReturn(null); + when(target.getGuid()) + .thenReturn(null); stubAppLookupByName("my-app", APP_GUID); @@ -131,8 +132,8 @@ void testDeleteApplicationLooksUpGuidAndDeletes() { @Test void testDeleteApplicationThrowsWhenApplicationNotFound() { - Mockito.when(target.getGuid()) - .thenReturn(null); + when(target.getGuid()) + .thenReturn(null); stubEmptyAppLookupByName("missing-app"); @@ -141,8 +142,8 @@ void testDeleteApplicationThrowsWhenApplicationNotFound() { @Test void testGetApplicationReturnsMappedApplication() { - Mockito.when(target.getGuid()) - .thenReturn(null); + when(target.getGuid()) + .thenReturn(null); factory.server() .expect(MockRestRequestMatchers.requestTo(APPS_BASE + "&names=my-app")) @@ -160,8 +161,8 @@ void testGetApplicationReturnsMappedApplication() { @Test void testGetApplicationThrowsWhenRequiredAndNotFound() { - Mockito.when(target.getGuid()) - .thenReturn(null); + when(target.getGuid()) + .thenReturn(null); stubEmptyAppLookupByName("missing-app"); @@ -170,8 +171,8 @@ void testGetApplicationThrowsWhenRequiredAndNotFound() { @Test void testGetApplicationReturnsNullWhenNotRequiredAndNotFound() { - Mockito.when(target.getGuid()) - .thenReturn(null); + when(target.getGuid()) + .thenReturn(null); stubEmptyAppLookupByName("missing-app"); @@ -183,8 +184,8 @@ void testGetApplicationReturnsNullWhenNotRequiredAndNotFound() { @Test void testGetApplicationGuidReturnsGuid() { - Mockito.when(target.getGuid()) - .thenReturn(null); + when(target.getGuid()) + .thenReturn(null); stubAppLookupByName("my-app", APP_GUID); @@ -196,8 +197,8 @@ void testGetApplicationGuidReturnsGuid() { @Test void testGetApplicationQueryIncludesSpaceGuidWhenPresent() { - Mockito.when(target.getGuid()) - .thenReturn(UUID.fromString(SPACE_GUID)); + when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); factory.server() .expect(MockRestRequestMatchers.requestTo(APPS_BASE + "&space_guids=" + SPACE_GUID + "&names=my-app")) .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) @@ -253,8 +254,8 @@ void testGetApplicationEnvironmentByGuidReturnsEmptyWhenNoVars() { @Test void testGetApplicationEnvironmentByNameResolvesGuidFirst() { - Mockito.when(target.getGuid()) - .thenReturn(null); + when(target.getGuid()) + .thenReturn(null); stubAppLookupByName("my-app", APP_GUID); @@ -272,8 +273,8 @@ void testGetApplicationEnvironmentByNameResolvesGuidFirst() { @Test void testGetApplicationsReturnsMappedList() { - Mockito.when(target.getGuid()) - .thenReturn(null); + when(target.getGuid()) + .thenReturn(null); factory.server() .expect(MockRestRequestMatchers.requestTo(APPS_BASE)) @@ -291,8 +292,8 @@ void testGetApplicationsReturnsMappedList() { @Test void testGetApplicationsReturnsEmptyList() { - Mockito.when(target.getGuid()) - .thenReturn(null); + when(target.getGuid()) + .thenReturn(null); factory.server() .expect(MockRestRequestMatchers.requestTo(APPS_BASE)) @@ -307,8 +308,8 @@ void testGetApplicationsReturnsEmptyList() { @Test void testGetApplicationsByMetadataLabelSelectorAppendsSelector() { - Mockito.when(target.getGuid()) - .thenReturn(null); + when(target.getGuid()) + .thenReturn(null); factory.server() .expect(MockRestRequestMatchers.requestTo(APPS_BASE + "&label_selector=env")) @@ -324,8 +325,8 @@ void testGetApplicationsByMetadataLabelSelectorAppendsSelector() { @Test void testGetApplicationsByMetadataLabelSelectorWithNullSelector() { - Mockito.when(target.getGuid()) - .thenReturn(null); + when(target.getGuid()) + .thenReturn(null); factory.server() .expect(MockRestRequestMatchers.requestTo(APPS_BASE)) @@ -340,8 +341,8 @@ void testGetApplicationsByMetadataLabelSelectorWithNullSelector() { @Test void testStartApplicationPostsToStartAction() { - Mockito.when(target.getGuid()) - .thenReturn(null); + when(target.getGuid()) + .thenReturn(null); stubAppLookupByName("my-app", APP_GUID); @@ -358,8 +359,8 @@ void testStartApplicationPostsToStartAction() { @Test void testStopApplicationPostsToStopAction() { - Mockito.when(target.getGuid()) - .thenReturn(null); + when(target.getGuid()) + .thenReturn(null); stubAppLookupByName("my-app", APP_GUID); @@ -376,8 +377,8 @@ void testStopApplicationPostsToStopAction() { @Test void testRenameUpdatesName() { - Mockito.when(target.getGuid()) - .thenReturn(null); + when(target.getGuid()) + .thenReturn(null); stubAppLookupByName("my-app", APP_GUID); @@ -393,8 +394,8 @@ void testRenameUpdatesName() { @Test void testRenameSwallowsServiceUnavailableWhenAlreadyRenamed() { - Mockito.when(target.getGuid()) - .thenReturn(null); + when(target.getGuid()) + .thenReturn(null); stubAppLookupByName("my-app", APP_GUID); @@ -415,8 +416,8 @@ void testRenameSwallowsServiceUnavailableWhenAlreadyRenamed() { @Test void testRenameRethrowsServiceUnavailableWhenNotRenamed() { - Mockito.when(target.getGuid()) - .thenReturn(null); + when(target.getGuid()) + .thenReturn(null); stubAppLookupByName("my-app", APP_GUID); @@ -462,8 +463,8 @@ void testUpdateApplicationDiskQuotaScalesWebProcess() { @Test void testUpdateApplicationEnvUpdatesEnvVars() { - Mockito.when(target.getGuid()) - .thenReturn(null); + when(target.getGuid()) + .thenReturn(null); stubAppLookupByName("my-app", APP_GUID); @@ -511,11 +512,11 @@ void testUpdateApplicationMetadataUpdatesApp() { } private void stubScaleFlow() { - Mockito.when(target.getGuid()) - .thenReturn(null); + when(target.getGuid()) + .thenReturn(null); stubAppLookupByName("my-app", APP_GUID); - + factory.server() .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/processes/web/actions/scale")) diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3OperationsTest.java index 1b51c26456..e2b5dbe687 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3OperationsTest.java @@ -69,7 +69,8 @@ void testGetBuildThrowsWhenNotFound() { .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) .andRespond(MockRestResponseCreators.withStatus(HttpStatus.NOT_FOUND)); - Assertions.assertThrows(CloudOperationException.class, () -> operations.getBuild(UUID.fromString(BUILD_GUID))); + UUID buildGuid = UUID.fromString(BUILD_GUID); + Assertions.assertThrows(CloudOperationException.class, () -> operations.getBuild(buildGuid)); } @Test diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientV3ImplTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientV3ImplTest.java index f8579c7358..ec501cd039 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientV3ImplTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientV3ImplTest.java @@ -3,6 +3,8 @@ import java.time.Duration; import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.Set; import java.util.UUID; import java.util.function.Function; @@ -12,18 +14,37 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudDomain; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEvent; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudOrganization; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudPackage; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudProcess; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudRoute; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBinding; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBroker; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceOffering; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudStack; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudTask; +import org.cloudfoundry.multiapps.controller.client.facade.domain.DropletInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudDomain; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudOrganization; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudRoute; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceBroker; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudTask; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableStaging; +import org.cloudfoundry.multiapps.controller.client.facade.domain.InstancesInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ServicePlanVisibility; +import org.cloudfoundry.multiapps.controller.client.facade.domain.UserRole; import org.cloudfoundry.multiapps.controller.client.facade.dto.ApplicationToCreateDto; import org.cloudfoundry.multiapps.controller.client.facade.dto.ImmutableApplicationToCreateDto; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; @@ -32,6 +53,8 @@ import org.springframework.test.web.client.response.MockRestResponseCreators; import org.springframework.web.client.RestClient; +import static org.mockito.Mockito.when; + class CloudControllerRestClientV3ImplTest { private static final String SPACE_GUID = "11111111-2222-3333-4444-555555555555"; @@ -40,8 +63,15 @@ class CloudControllerRestClientV3ImplTest { private static final String BUILD_GUID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"; private static final String PACKAGE_GUID = "bbbbbbbb-cccc-dddd-eeee-ffffffffffff"; private static final String SERVICE_INSTANCE_GUID = "cccccccc-dddd-eeee-ffff-000000000000"; + private static final String BINDING_GUID = "dddddddd-eeee-ffff-0000-111111111111"; + private static final String BROKER_GUID = "eeeeeeee-ffff-0000-1111-222222222222"; + private static final String SERVICE_KEY_GUID = "ffffffff-0000-1111-2222-333333333333"; + private static final String OFFERING_GUID = "a1a1a1a1-b2b2-c3c3-d4d4-e5e5e5e5e5e5"; + private static final String PLAN_GUID = "f6f6f6f6-a7a7-b8b8-c9c9-d0d0d0d0d0d0"; private static final String DOMAIN_NAME = "example.com"; private static final String APPS_BASE = MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000"; + private static final String SI_BASE = MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=5000"; + private static final String BINDINGS = MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings"; @Mock private CloudSpace target; @@ -123,8 +153,8 @@ void testGetSharedDomainsReturnsMappedList() { @Test void testGetApplicationReturnsMappedApplication() { - Mockito.when(target.getGuid()) - .thenReturn(null); + when(target.getGuid()) + .thenReturn(null); factory.server() .expect(MockRestRequestMatchers.requestTo(APPS_BASE + "&names=my-app")) @@ -140,8 +170,8 @@ void testGetApplicationReturnsMappedApplication() { @Test void testGetApplicationsReturnsMappedList() { - Mockito.when(target.getGuid()) - .thenReturn(null); + when(target.getGuid()) + .thenReturn(null); factory.server() .expect(MockRestRequestMatchers.requestTo(APPS_BASE)) @@ -156,8 +186,8 @@ void testGetApplicationsReturnsMappedList() { @Test void testGetApplicationGuidReturnsGuid() { - Mockito.when(target.getGuid()) - .thenReturn(null); + when(target.getGuid()) + .thenReturn(null); factory.server() .expect(MockRestRequestMatchers.requestTo(APPS_BASE + "&names=my-app")) @@ -199,8 +229,8 @@ void testGetApplicationEnvironmentReturnsVariables() { @Test void testCreateApplicationWithoutStagingOrRoutesOnlyPosts() { - Mockito.when(target.getGuid()) - .thenReturn(UUID.fromString(SPACE_GUID)); + when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); factory.server() .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps")) @@ -218,8 +248,8 @@ void testCreateApplicationWithoutStagingOrRoutesOnlyPosts() { @Test void testCreateApplicationWithStagingUpdatesStagingThroughProcess() { - Mockito.when(target.getGuid()) - .thenReturn(UUID.fromString(SPACE_GUID)); + when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); factory.server() .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps")) @@ -353,8 +383,8 @@ void testGetTaskReturnsMappedTask() { @Test void testGetTasksReturnsMappedList() { - Mockito.when(target.getGuid()) - .thenReturn(null); + when(target.getGuid()) + .thenReturn(null); factory.server() .expect(MockRestRequestMatchers.requestTo(APPS_BASE + "&names=my-app")) @@ -435,56 +465,1042 @@ void testGetBuildPropagatesCloudOperationExceptionOnNotFound() { .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) .andRespond(MockRestResponseCreators.withStatus(HttpStatus.NOT_FOUND)); - Assertions.assertThrows(CloudOperationException.class, () -> client.getBuild(UUID.fromString(BUILD_GUID))); + UUID buildGuid = UUID.fromString(BUILD_GUID); + Assertions.assertThrows(CloudOperationException.class, () -> client.getBuild(buildGuid)); } - private void stubTargetSpaceMetadata() { - Mockito.when(target.getMetadata()) - .thenReturn(targetMetadata); - Mockito.when(targetMetadata.getGuid()) - .thenReturn(UUID.fromString(SPACE_GUID)); + @Test + void testGetApplicationWithRequiredFlag() { + when(target.getGuid()).thenReturn(null); + stubGet(APPS_BASE + "&names=my-app", getAppListJson()); + + CloudApplication result = client.getApplication("my-app", false); + + Assertions.assertEquals("my-app", result.getName()); + factory.verify(); } - private void stubServiceInstanceLookup() { + @Test + void testGetApplicationEnvironmentByName() { + when(target.getGuid()).thenReturn(null); + stubGet(APPS_BASE + "&names=my-app", getAppListJson()); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/environment_variables", + "{\"var\":{\"FOO\":\"bar\"}}"); + + Map result = client.getApplicationEnvironment("my-app"); + + Assertions.assertEquals(Map.of("FOO", "bar"), result); + factory.verify(); + } + + @Test + void testGetApplicationsByMetadataLabelSelector() { + when(target.getGuid()).thenReturn(null); + stubGet(APPS_BASE + "&label_selector=env%3Dprod", getAppListJson()); + + List result = client.getApplicationsByMetadataLabelSelector("env=prod"); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testStartApplication() { + when(target.getGuid()).thenReturn(null); + stubGet(APPS_BASE + "&names=my-app", getAppListJson()); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/actions/start", HttpMethod.POST); + + client.startApplication("my-app"); + + factory.verify(); + } + + @Test + void testStopApplication() { + when(target.getGuid()).thenReturn(null); + stubGet(APPS_BASE + "&names=my-app", getAppListJson()); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/actions/stop", HttpMethod.POST); + + client.stopApplication("my-app"); + + factory.verify(); + } + + @Test + void testRestartApplication() { + when(target.getGuid()).thenReturn(null); + stubGet(APPS_BASE + "&names=my-app", getAppListJson()); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/actions/stop", HttpMethod.POST); + stubGet(APPS_BASE + "&names=my-app", getAppListJson()); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/actions/start", HttpMethod.POST); + + client.restartApplication("my-app"); + + factory.verify(); + } + + @Test + void testRenameApplication() { + when(target.getGuid()).thenReturn(null); + stubGet(APPS_BASE + "&names=my-app", getAppListJson()); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID, HttpMethod.PATCH); + + client.rename("my-app", "new-name"); + + factory.verify(); + } + + @Test + void testUpdateApplicationInstances() { + when(target.getGuid()).thenReturn(null); + stubGet(APPS_BASE + "&names=my-app", getAppListJson()); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/processes/web/actions/scale", HttpMethod.POST); + + client.updateApplicationInstances("my-app", 3); + + factory.verify(); + } + + @Test + void testUpdateApplicationMemory() { + when(target.getGuid()).thenReturn(null); + stubGet(APPS_BASE + "&names=my-app", getAppListJson()); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/processes/web/actions/scale", HttpMethod.POST); + + client.updateApplicationMemory("my-app", 512); + + factory.verify(); + } + + @Test + void testUpdateApplicationDiskQuota() { + when(target.getGuid()).thenReturn(null); + stubGet(APPS_BASE + "&names=my-app", getAppListJson()); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/processes/web/actions/scale", HttpMethod.POST); + + client.updateApplicationDiskQuota("my-app", 2048); + + factory.verify(); + } + + @Test + void testUpdateApplicationEnv() { + when(target.getGuid()).thenReturn(null); + stubGet(APPS_BASE + "&names=my-app", getAppListJson()); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/environment_variables", HttpMethod.PATCH); + + client.updateApplicationEnv("my-app", Map.of("FOO", "bar")); + + factory.verify(); + } + + @Test + void testBindDropletToApp() { + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/relationships/current_droplet", HttpMethod.PATCH); + + client.bindDropletToApp(UUID.fromString(BUILD_GUID), UUID.fromString(APP_GUID)); + + factory.verify(); + } + + @Test + void testUpdateApplicationMetadata() { + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID, HttpMethod.PATCH); + + client.updateApplicationMetadata(UUID.fromString(APP_GUID), Metadata.builder() + .label("key", "value") + .build()); + + factory.verify(); + } + + @Test + void testDeleteApplication() { + when(target.getGuid()).thenReturn(null); + stubGet(APPS_BASE + "&names=my-app", getAppListJson()); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID, HttpMethod.DELETE); + + client.deleteApplication("my-app"); + + factory.verify(); + } + + @Test + void testGetApplicationProcess() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/processes/web", getWebProcessJson()); + + CloudProcess result = client.getApplicationProcess(UUID.fromString(APP_GUID)); + + Assertions.assertNotNull(result); + factory.verify(); + } + + @Test + void testGetApplicationInstancesByGuid() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/processes/web/stats", + "{\"resources\":[{\"state\":\"RUNNING\",\"index\":0}]}"); + + InstancesInfo result = client.getApplicationInstances(UUID.fromString(APP_GUID)); + + Assertions.assertNotNull(result); + factory.verify(); + } + + @Test + void testGetApplicationSshEnabled() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/ssh_enabled", "{\"enabled\":true}"); + + Assertions.assertTrue(client.getApplicationSshEnabled(UUID.fromString(APP_GUID))); + factory.verify(); + } + + @Test + void testGetApplicationFeatures() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/features?per_page=5000", + "{\"resources\":[{\"name\":\"ssh\",\"enabled\":true}]}"); + + Map result = client.getApplicationFeatures(UUID.fromString(APP_GUID)); + + Assertions.assertEquals(Boolean.TRUE, result.get("ssh")); + factory.verify(); + } + + @Test + void testGetCurrentDropletForApplication() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/droplets/current", + "{\"guid\":\"" + BUILD_GUID + "\",\"links\":{\"package\":{\"href\":\"https://api.example.com/v3/packages/" + PACKAGE_GUID + + "\"}}}"); + + DropletInfo result = client.getCurrentDropletForApplication(UUID.fromString(APP_GUID)); + + Assertions.assertEquals(UUID.fromString(PACKAGE_GUID), result.getPackageGuid()); + factory.verify(); + } + + @Test + void testUpdateApplicationStaging() { + when(target.getGuid()).thenReturn(null); + stubGet(APPS_BASE + "&names=my-app", getAppListJson()); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID, HttpMethod.PATCH); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/processes/web", getWebProcessJson()); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/processes/" + APP_GUID, HttpMethod.PATCH); + + client.updateApplicationStaging("my-app", ImmutableStaging.builder() + .command("run.sh") + .build()); + + factory.verify(); + } + + @Test + void testGetApplicationEvents() { + when(target.getGuid()).thenReturn(null); + stubGet(APPS_BASE + "&names=my-app", getAppListJson()); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/audit_events?per_page=5000&target_guids=" + APP_GUID, + getAuditEventListJson()); + + List result = client.getApplicationEvents("my-app"); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testGetDefaultDomain() { + stubTargetOrganization(); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/organizations/" + SPACE_GUID + "/domains/default", + "{\"guid\":\"" + APP_GUID + "\",\"name\":\"" + DOMAIN_NAME + "\"}"); + + CloudDomain result = client.getDefaultDomain(); + + Assertions.assertEquals(DOMAIN_NAME, result.getName()); + factory.verify(); + } + + @Test + void testGetPrivateDomains() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?per_page=5000", + "{\"resources\":[{\"guid\":\"" + APP_GUID + "\",\"name\":\"" + DOMAIN_NAME + + "\",\"relationships\":{\"organization\":{\"data\":{\"guid\":\"" + SPACE_GUID + "\"}}}}]}"); + + List result = client.getPrivateDomains(); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testGetDomainsForOrganization() { + stubTargetOrganization(); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/organizations/" + SPACE_GUID + "/domains?per_page=5000", + getDomainListJson()); + + List result = client.getDomainsForOrganization(); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testAddDomainCreatesWhenMissing() { + stubTargetOrganization(); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + DOMAIN_NAME + "&per_page=5000", + "{\"resources\":[]}"); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/domains", HttpMethod.POST); + + client.addDomain(DOMAIN_NAME); + + factory.verify(); + } + + @Test + void testDeleteDomain() { + stubTargetOrganization(); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + DOMAIN_NAME + "&per_page=5000", getDomainListJson()); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/domains/" + APP_GUID, HttpMethod.DELETE); + + client.deleteDomain(DOMAIN_NAME); + + factory.verify(); + } + + @Test + void testAddRoute() { + when(target.getGuid()).thenReturn(UUID.fromString(SPACE_GUID)); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + DOMAIN_NAME + "&per_page=5000", getDomainListJson()); factory.server() - .expect(MockRestRequestMatchers.requestTo( - MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=5000&space_guids=" + SPACE_GUID - + "&names=my-service")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[" + getServiceInstanceJson() + "]}", - MediaType.APPLICATION_JSON)); + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/routes")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withSuccess(getRouteJson(), MediaType.APPLICATION_JSON)); + + client.addRoute("myhost", DOMAIN_NAME, "/mypath"); + + factory.verify(); } - private static String getStackListJson() { - return "{\"resources\":[{\"guid\":\"" + APP_GUID + "\",\"name\":\"cflinuxfs4\",\"description\":\"Ubuntu\"}]}"; + @Test + void testGetRoutes() { + when(target.getGuid()).thenReturn(UUID.fromString(SPACE_GUID)); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + DOMAIN_NAME + "&per_page=5000", getDomainListJson()); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/routes?per_page=5000&domain_guids=" + APP_GUID + "&space_guids=" + SPACE_GUID, + "{\"resources\":[" + getRouteJson() + "]}"); + + List result = client.getRoutes(DOMAIN_NAME); + + Assertions.assertEquals(1, result.size()); + factory.verify(); } - private static String getDomainListJson() { - return "{\"resources\":[{\"guid\":\"" + APP_GUID + "\",\"name\":\"" + DOMAIN_NAME + "\"}]}"; + @Test + void testDeleteOrphanedRoutes() { + when(target.getGuid()).thenReturn(UUID.fromString(SPACE_GUID)); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/spaces/" + SPACE_GUID + "/routes?unmapped=true", HttpMethod.DELETE); + + client.deleteOrphanedRoutes(); + + factory.verify(); } - private static String getAppListJson() { - return "{\"resources\":[" + getAppJson() + "]}"; + @Test + void testDeleteRoute() { + when(target.getGuid()).thenReturn(UUID.fromString(SPACE_GUID)); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + DOMAIN_NAME + "&per_page=5000", getDomainListJson()); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/routes?per_page=5000&domain_guids=" + APP_GUID + "&space_guids=" + SPACE_GUID + + "&hosts=myhost&paths=/mypath", "{\"resources\":[" + getRouteJson() + "]}"); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/routes/" + APP_GUID, HttpMethod.DELETE); + + client.deleteRoute("myhost", DOMAIN_NAME, "/mypath"); + + factory.verify(); } - private static String getAppJson() { - return "{\"guid\":\"" + APP_GUID + "\",\"name\":\"my-app\",\"state\":\"STARTED\"," - + "\"lifecycle\":{\"type\":\"buildpack\",\"data\":{\"buildpacks\":[\"java_buildpack\"],\"stack\":\"cflinuxfs4\"}}}"; + @Test + void testUpdateApplicationRoutesAddsRoute() { + when(target.getGuid()).thenReturn(UUID.fromString(SPACE_GUID)); + stubGet(APPS_BASE + "&names=my-app&space_guids=" + SPACE_GUID, "{\"resources\":[{\"guid\":\"" + APP_GUID + "\"}]}"); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/routes?per_page=5000", "{\"resources\":[]}"); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + DOMAIN_NAME + "&per_page=5000", + "{\"resources\":[{\"guid\":\"" + APP_GUID + "\",\"name\":\"" + DOMAIN_NAME + "\"}]}"); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/routes?per_page=5000&domain_guids=" + APP_GUID + "&space_guids=" + SPACE_GUID + + "&hosts=myhost&paths=/mypath", "{\"resources\":[]}"); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/routes")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withSuccess(getRouteJson(), MediaType.APPLICATION_JSON)); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/routes/" + APP_GUID + "/destinations", HttpMethod.POST); + + CloudRoute route = ImmutableCloudRoute.builder() + .domain(ImmutableCloudDomain.builder() + .name(DOMAIN_NAME) + .build()) + .host("myhost") + .path("/mypath") + .url("myhost.example.com/mypath") + .build(); + + client.updateApplicationRoutes("my-app", Set.of(route)); + + factory.verify(); } - private static String getWebProcessJson() { - return "{\"guid\":\"" + APP_GUID + "\",\"type\":\"web\",\"command\":\"old.sh\",\"instances\":1,\"memory_in_mb\":256," - + "\"disk_in_mb\":1024}"; + @Test + void testGetServiceInstanceSingleArgument() { + stubTargetSpaceMetadata(); + stubServiceInstanceLookupWithPlan(); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_plans/" + PLAN_GUID, "{\"guid\":\"" + PLAN_GUID + + "\",\"name\":\"my-plan\"}"); + + CloudServiceInstance result = client.getServiceInstance("my-service", false); + + Assertions.assertEquals("my-service", result.getName()); + factory.verify(); } - private static String getAuditEventListJson() { - return "{\"resources\":[{\"guid\":\"" + APP_GUID + "\",\"type\":\"audit.app.update\"," - + "\"actor\":{\"guid\":\"" + APP_GUID + "\",\"type\":\"user\",\"name\":\"admin\"}," - + "\"target\":{\"guid\":\"" + APP_GUID + "\",\"type\":\"app\",\"name\":\"my-app\"}}]}"; + @Test + void testGetServiceInstanceWithoutAuxiliaryContentRequiredFlag() { + stubTargetSpaceMetadata(); + stubServiceInstanceLookup(); + + CloudServiceInstance result = client.getServiceInstanceWithoutAuxiliaryContent("my-service", false); + + Assertions.assertEquals("my-service", result.getName()); + factory.verify(); } - private static String getServiceInstanceJson() { - return "{\"guid\":\"" + SERVICE_INSTANCE_GUID + "\",\"name\":\"my-service\",\"type\":\"managed\"}"; + @Test + void testCreateServiceInstance() { + stubTargetSpaceMetadata(); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_offerings?per_page=5000&space_guids=" + SPACE_GUID + + "&names=my-offering", "{\"resources\":[{\"guid\":\"" + OFFERING_GUID + "\",\"name\":\"my-offering\"}]}"); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_plans?per_page=5000&service_offering_guids=" + OFFERING_GUID + + "&names=my-plan", "{\"resources\":[{\"guid\":\"" + PLAN_GUID + "\",\"name\":\"my-plan\"}]}"); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/service_instances", HttpMethod.POST); + + CloudServiceInstance serviceInstance = ImmutableCloudServiceInstance.builder() + .name("my-service") + .label("my-offering") + .plan("my-plan") + .build(); + + client.createServiceInstance(serviceInstance); + + factory.verify(); + } + + @Test + void testCreateUserProvidedServiceInstance() { + stubTargetSpaceMetadata(); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/service_instances", HttpMethod.POST); + + CloudServiceInstance serviceInstance = ImmutableCloudServiceInstance.builder() + .name("my-ups") + .build(); + + client.createUserProvidedServiceInstance(serviceInstance); + + factory.verify(); + } + + @Test + void testDeleteServiceInstanceByName() { + stubTargetSpaceMetadata(); + stubServiceInstanceLookup(); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + SERVICE_INSTANCE_GUID, HttpMethod.DELETE); + + client.deleteServiceInstance("my-service"); + + factory.verify(); + } + + @Test + void testDeleteServiceInstanceByInstance() { + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + SERVICE_INSTANCE_GUID, HttpMethod.DELETE); + + CloudServiceInstance serviceInstance = ImmutableCloudServiceInstance.builder() + .name("my-service") + .metadata(ImmutableCloudMetadata.builder() + .guid( + UUID.fromString( + SERVICE_INSTANCE_GUID)) + .build()) + .build(); + + client.deleteServiceInstance(serviceInstance); + + factory.verify(); + } + + @Test + void testGetServiceInstanceParameters() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + SERVICE_INSTANCE_GUID + "/parameters", + "{\"foo\":\"bar\"}"); + + Map result = client.getServiceInstanceParameters(UUID.fromString(SERVICE_INSTANCE_GUID)); + + Assertions.assertEquals("bar", result.get("foo")); + factory.verify(); + } + + @Test + void testGetUserProvidedServiceInstanceParameters() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + SERVICE_INSTANCE_GUID + "/credentials", + "{\"user\":\"admin\"}"); + + Map result = client.getUserProvidedServiceInstanceParameters(UUID.fromString(SERVICE_INSTANCE_GUID)); + + Assertions.assertEquals("admin", result.get("user")); + factory.verify(); + } + + @Test + void testGetServiceInstancesWithoutAuxiliaryContentByNames() { + stubTargetSpaceMetadata(); + stubGet(SI_BASE + "&space_guids=" + SPACE_GUID + "&names=my-service", + "{\"resources\":[" + getServiceInstanceJson() + "]}"); + + List result = client.getServiceInstancesWithoutAuxiliaryContentByNames(List.of("my-service")); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testGetServiceInstancesByMetadataLabelSelector() { + stubTargetSpaceMetadata(); + stubGet(SI_BASE + "&space_guids=" + SPACE_GUID + "&label_selector=env", + "{\"resources\":[{\"guid\":\"" + SERVICE_INSTANCE_GUID + "\",\"name\":\"my-service\",\"type\":\"user-provided\"}]}"); + + List result = client.getServiceInstancesByMetadataLabelSelector("env"); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testGetServiceInstancesWithoutAuxiliaryContentByMetadataLabelSelector() { + stubTargetSpaceMetadata(); + stubGet(SI_BASE + "&space_guids=" + SPACE_GUID + "&label_selector=env", + "{\"resources\":[" + getServiceInstanceJson() + "]}"); + + List result = client.getServiceInstancesWithoutAuxiliaryContentByMetadataLabelSelector("env"); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testUpdateServiceParameters() { + stubTargetSpaceMetadata(); + stubServiceInstanceLookup(); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + SERVICE_INSTANCE_GUID, HttpMethod.PATCH); + + client.updateServiceParameters("my-service", Map.of("foo", "bar")); + + factory.verify(); + } + + @Test + void testUpdateServiceTags() { + stubTargetSpaceMetadata(); + stubServiceInstanceLookup(); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + SERVICE_INSTANCE_GUID, HttpMethod.PATCH); + + client.updateServiceTags("my-service", List.of("t1", "t2")); + + factory.verify(); + } + + @Test + void testUpdateServiceSyslogDrainUrlSkipsManaged() { + stubTargetSpaceMetadata(); + stubServiceInstanceLookup(); + + client.updateServiceSyslogDrainUrl("my-service", "syslog://x"); + + factory.verify(); + } + + @Test + void testUpdateServiceInstanceMetadata() { + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + SERVICE_INSTANCE_GUID, HttpMethod.PATCH); + + client.updateServiceInstanceMetadata(UUID.fromString(SERVICE_INSTANCE_GUID), Metadata.builder() + .label("k", "v") + .build()); + + factory.verify(); + } + + @Test + void testBindServiceInstance() { + when(target.getGuid()).thenReturn(UUID.fromString(SPACE_GUID)); + stubGet(APPS_BASE + "&space_guids=" + SPACE_GUID + "&names=my-app", getAppListJson()); + stubGet(SI_BASE + "&space_guids=" + SPACE_GUID + "&names=my-service", + "{\"resources\":[" + getServiceInstanceJson() + "]}"); + stubMethod(BINDINGS, HttpMethod.POST); + + Optional result = client.bindServiceInstance("my-binding", "my-app", "my-service"); + + Assertions.assertTrue(result.isEmpty()); + factory.verify(); + } + + @Test + void testBindServiceInstanceWithParameters() { + when(target.getGuid()).thenReturn(UUID.fromString(SPACE_GUID)); + stubGet(APPS_BASE + "&space_guids=" + SPACE_GUID + "&names=my-app", getAppListJson()); + stubGet(SI_BASE + "&space_guids=" + SPACE_GUID + "&names=my-service", + "{\"resources\":[" + getServiceInstanceJson() + "]}"); + stubMethod(BINDINGS, HttpMethod.POST); + + client.bindServiceInstance("my-binding", "my-app", "my-service", Map.of("p", "v")); + + factory.verify(); + } + + @Test + void testGetServiceBinding() { + stubGet(BINDINGS + "?guids=" + BINDING_GUID + "&per_page=5000", + "{\"resources\":[" + getBindingJson() + "]}"); + + CloudServiceBinding result = client.getServiceBinding(UUID.fromString(BINDING_GUID)); + + Assertions.assertNotNull(result); + factory.verify(); + } + + @Test + void testGetServiceAppBindings() { + stubGet(BINDINGS + "?service_instance_guids=" + SERVICE_INSTANCE_GUID + "&type=app&per_page=5000", + "{\"resources\":[" + getBindingJson() + "]}"); + + List result = client.getServiceAppBindings(UUID.fromString(SERVICE_INSTANCE_GUID)); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testGetAppBindings() { + stubGet(BINDINGS + "?app_guids=" + APP_GUID + "&per_page=5000", "{\"resources\":[" + getBindingJson() + "]}"); + + List result = client.getAppBindings(UUID.fromString(APP_GUID)); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testGetServiceBindingsForApplication() { + stubGet(BINDINGS + "?app_guids=" + APP_GUID + "&service_instance_guids=" + SERVICE_INSTANCE_GUID + "&per_page=5000", + "{\"resources\":[" + getBindingJson() + "]}"); + + List result = client.getServiceBindingsForApplication(UUID.fromString(APP_GUID), + UUID.fromString(SERVICE_INSTANCE_GUID)); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testGetServiceBindingParameters() { + stubGet(BINDINGS + "/" + BINDING_GUID + "/parameters", "{\"foo\":\"bar\"}"); + + Map result = client.getServiceBindingParameters(UUID.fromString(BINDING_GUID)); + + Assertions.assertEquals("bar", result.get("foo")); + factory.verify(); + } + + @Test + void testUpdateServiceBindingMetadata() { + stubMethod(BINDINGS + "/" + BINDING_GUID, HttpMethod.PATCH); + + client.updateServiceBindingMetadata(UUID.fromString(BINDING_GUID), Metadata.builder() + .label("k", "v") + .build()); + + factory.verify(); + } + + @Test + void testDeleteServiceBindingByGuid() { + stubMethod(BINDINGS + "/" + BINDING_GUID, HttpMethod.DELETE); + + Optional result = client.deleteServiceBinding(UUID.fromString(BINDING_GUID)); + + Assertions.assertTrue(result.isEmpty()); + factory.verify(); + } + + @Test + void testUnbindServiceInstanceByName() { + when(target.getGuid()).thenReturn(UUID.fromString(SPACE_GUID)); + stubGet(APPS_BASE + "&space_guids=" + SPACE_GUID + "&names=my-app", getAppListJson()); + stubGet(SI_BASE + "&space_guids=" + SPACE_GUID + "&names=my-service", + "{\"resources\":[" + getServiceInstanceJson() + "]}"); + stubGet(BINDINGS + "?app_guids=" + APP_GUID + "&service_instance_guids=" + SERVICE_INSTANCE_GUID + "&per_page=5000", + "{\"resources\":[" + getBindingJson() + "]}"); + stubMethod(BINDINGS + "/" + BINDING_GUID, HttpMethod.DELETE); + + List result = client.unbindServiceInstance("my-app", "my-service"); + + Assertions.assertTrue(result.isEmpty()); + factory.verify(); + } + + @Test + void testUnbindServiceInstanceByGuid() { + stubGet(BINDINGS + "?app_guids=" + APP_GUID + "&service_instance_guids=" + SERVICE_INSTANCE_GUID + "&per_page=5000", + "{\"resources\":[" + getBindingJson() + "]}"); + stubMethod(BINDINGS + "/" + BINDING_GUID, HttpMethod.DELETE); + + client.unbindServiceInstance(UUID.fromString(APP_GUID), UUID.fromString(SERVICE_INSTANCE_GUID)); + + factory.verify(); + } + + @Test + void testGetServiceKey() { + stubTargetSpaceMetadata(); + stubServiceInstanceLookupWithPlan(); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_plans/" + PLAN_GUID, "{\"guid\":\"" + PLAN_GUID + + "\",\"name\":\"my-plan\"}"); + stubGet(BINDINGS + "?per_page=5000&type=key&service_instance_guids=" + SERVICE_INSTANCE_GUID + "&names=my-key", + "{\"resources\":[" + getServiceKeyJson() + "]}"); + stubGet(BINDINGS + "/" + SERVICE_KEY_GUID + "/details", "{\"credentials\":{\"user\":\"admin\"}}"); + + CloudServiceKey result = client.getServiceKey("my-service", "my-key"); + + Assertions.assertNotNull(result); + factory.verify(); + } + + @Test + void testGetServiceKeysByName() { + stubTargetSpaceMetadata(); + stubServiceInstanceLookupWithPlan(); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_plans/" + PLAN_GUID, "{\"guid\":\"" + PLAN_GUID + + "\",\"name\":\"my-plan\"}"); + stubGet(BINDINGS + "?per_page=5000&type=key&service_instance_guids=" + SERVICE_INSTANCE_GUID, + "{\"resources\":[" + getServiceKeyJson() + "]}"); + + List result = client.getServiceKeys("my-service"); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testCreateServiceKeyWithParameters() { + stubTargetSpaceMetadata(); + stubServiceInstanceLookupWithPlan(); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_plans/" + PLAN_GUID, "{\"guid\":\"" + PLAN_GUID + + "\",\"name\":\"my-plan\"}"); + stubMethod(BINDINGS, HttpMethod.POST); + + Optional result = client.createServiceKey("my-service", "my-key", Map.of("p", "v")); + + Assertions.assertTrue(result.isEmpty()); + factory.verify(); + } + + @Test + void testCreateServiceBroker() { + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/service_brokers", HttpMethod.POST); + + CloudServiceBroker broker = ImmutableCloudServiceBroker.builder() + .name("my-broker") + .url("https://broker.example.com") + .username("user") + .password("pass") + .build(); + + Assertions.assertNull(client.createServiceBroker(broker)); + factory.verify(); + } + + @Test + void testGetServiceBroker() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_brokers?names=my-broker&per_page=5000", getBrokerListJson()); + + CloudServiceBroker result = client.getServiceBroker("my-broker"); + + Assertions.assertEquals("my-broker", result.getName()); + factory.verify(); + } + + @Test + void testGetServiceBrokerWithRequiredFlag() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_brokers?names=my-broker&per_page=5000", getBrokerListJson()); + + Assertions.assertNotNull(client.getServiceBroker("my-broker", false)); + factory.verify(); + } + + @Test + void testGetServiceBrokers() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_brokers?per_page=5000", getBrokerListJson()); + + List result = client.getServiceBrokers(); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testUpdateServiceBroker() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_brokers?names=my-broker&per_page=5000", getBrokerListJson()); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/service_brokers/" + BROKER_GUID, HttpMethod.PATCH); + + CloudServiceBroker broker = ImmutableCloudServiceBroker.builder() + .name("my-broker") + .url("https://broker.example.com") + .build(); + + client.updateServiceBroker(broker); + + factory.verify(); + } + + @Test + void testDeleteServiceBroker() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_brokers?names=my-broker&per_page=5000", getBrokerListJson()); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/service_brokers/" + BROKER_GUID, HttpMethod.DELETE); + + Assertions.assertNull(client.deleteServiceBroker("my-broker")); + factory.verify(); + } + + @Test + void testUpdateServicePlanVisibilityForBroker() { + stubTargetSpaceMetadata(); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_brokers?names=my-broker&per_page=5000", getBrokerListJson()); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_offerings?service_broker_guids=" + BROKER_GUID + "&space_guids=" + + SPACE_GUID + "&per_page=5000", "{\"resources\":[{\"guid\":\"" + OFFERING_GUID + "\"}]}"); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_plans?service_offering_guids=" + OFFERING_GUID + "&per_page=5000", + "{\"resources\":[{\"guid\":\"" + PLAN_GUID + "\"}]}"); + stubMethod(MockControllerClientFactory.BASE_URL + "/v3/service_plans/" + PLAN_GUID + "/visibility", HttpMethod.PATCH); + + client.updateServicePlanVisibilityForBroker("my-broker", ServicePlanVisibility.PUBLIC); + + factory.verify(); + } + + @Test + void testGetServiceOfferings() { + when(target.getGuid()).thenReturn(UUID.fromString(SPACE_GUID)); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_offerings?per_page=5000&space_guids=" + SPACE_GUID, + "{\"resources\":[{\"guid\":\"" + OFFERING_GUID + "\",\"name\":\"my-offering\"}]}"); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_plans?service_offering_guids=" + OFFERING_GUID + "&per_page=5000", + "{\"resources\":[{\"guid\":\"" + PLAN_GUID + "\",\"name\":\"my-plan\"}]}"); + + List result = client.getServiceOfferings(); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testRunTask() { + when(target.getGuid()).thenReturn(null); + stubGet(APPS_BASE + "&names=my-app", getAppListJson()); + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/tasks")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withSuccess(getTaskJson(), MediaType.APPLICATION_JSON)); + + CloudTask task = ImmutableCloudTask.builder() + .name("migrate") + .command("rails db:migrate") + .build(); + + CloudTask result = client.runTask("my-app", task); + + Assertions.assertEquals("migrate", result.getName()); + factory.verify(); + } + + @Test + void testCancelTask() { + factory.server() + .expect(MockRestRequestMatchers.requestTo( + MockControllerClientFactory.BASE_URL + "/v3/tasks/" + TASK_GUID + "/actions/cancel")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withSuccess(getTaskJson(), MediaType.APPLICATION_JSON)); + + CloudTask result = client.cancelTask(UUID.fromString(TASK_GUID)); + + Assertions.assertEquals("migrate", result.getName()); + factory.verify(); + } + + @Test + void testCreateBuild() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/builds")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) + .andRespond(MockRestResponseCreators.withSuccess(getBuildJson(), MediaType.APPLICATION_JSON)); + + CloudBuild result = client.createBuild(UUID.fromString(PACKAGE_GUID)); + + Assertions.assertEquals(CloudBuild.State.STAGED, result.getState()); + factory.verify(); + } + + @Test + void testGetPackage() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/packages/" + PACKAGE_GUID, getPackageJson()); + + CloudPackage result = client.getPackage(UUID.fromString(PACKAGE_GUID)); + + Assertions.assertEquals(UUID.fromString(PACKAGE_GUID), result.getGuid()); + factory.verify(); + } + + @Test + void testGetPackagesForApplication() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/packages?per_page=5000", + "{\"resources\":[" + getPackageJson() + "]}"); + + List result = client.getPackagesForApplication(UUID.fromString(APP_GUID)); + + Assertions.assertEquals(1, result.size()); + factory.verify(); + } + + @Test + void testGetUserRolesBySpaceAndUser() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/roles?per_page=5000&space_guids=" + SPACE_GUID + "&user_guids=" + APP_GUID, + "{\"resources\":[{\"guid\":\"" + BINDING_GUID + "\",\"type\":\"space_developer\"}]}"); + + Set result = client.getUserRolesBySpaceAndUser(UUID.fromString(SPACE_GUID), UUID.fromString(APP_GUID)); + + Assertions.assertFalse(result.isEmpty()); + factory.verify(); + } + + @Test + void testGetAsyncJob() { + stubGet(MockControllerClientFactory.BASE_URL + "/v3/jobs/job-123", + "{\"guid\":\"job-123\",\"operation\":\"app.delete\",\"state\":\"COMPLETE\"}"); + + Assertions.assertNotNull(client.getAsyncJob("job-123")); + factory.verify(); + } + + private void stubTargetSpaceMetadata() { + when(target.getMetadata()) + .thenReturn(targetMetadata); + when(targetMetadata.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); + } + + private void stubServiceInstanceLookup() { + factory.server() + .expect(MockRestRequestMatchers.requestTo( + MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=5000&space_guids=" + SPACE_GUID + + "&names=my-service")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[" + getServiceInstanceJson() + "]}", + MediaType.APPLICATION_JSON)); + } + + private void stubServiceInstanceLookupWithPlan() { + factory.server() + .expect(MockRestRequestMatchers.requestTo( + MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=5000&space_guids=" + SPACE_GUID + + "&names=my-service")) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[" + getServiceInstanceWithPlanJson() + "]}", + MediaType.APPLICATION_JSON)); + } + + private void stubTargetOrganization() { + CloudOrganization organization = ImmutableCloudOrganization.builder() + .name("my-org") + .metadata( + ImmutableCloudMetadata.builder() + .guid( + UUID.fromString( + SPACE_GUID)) + .build()) + .build(); + when(target.getOrganization()).thenReturn(organization); + } + + private void stubGet(String uri, String responseJson) { + var response = responseJson == null ? MockRestResponseCreators.withSuccess() + : MockRestResponseCreators.withSuccess(responseJson, MediaType.APPLICATION_JSON); + + factory.server() + .expect(MockRestRequestMatchers.requestTo(uri)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(response); + } + + private void stubMethod(String uri, HttpMethod method) { + factory.server() + .expect(MockRestRequestMatchers.requestTo(uri)) + .andExpect(MockRestRequestMatchers.method(method)) + .andRespond(MockRestResponseCreators.withSuccess()); + } + + private static String getStackListJson() { + return "{\"resources\":[{\"guid\":\"" + APP_GUID + "\",\"name\":\"cflinuxfs4\",\"description\":\"Ubuntu\"}]}"; + } + + private static String getDomainListJson() { + return "{\"resources\":[{\"guid\":\"" + APP_GUID + "\",\"name\":\"" + DOMAIN_NAME + "\"}]}"; + } + + private static String getAppListJson() { + return "{\"resources\":[" + getAppJson() + "]}"; + } + + private static String getAppJson() { + return "{\"guid\":\"" + APP_GUID + "\",\"name\":\"my-app\",\"state\":\"STARTED\"," + + "\"lifecycle\":{\"type\":\"buildpack\",\"data\":{\"buildpacks\":[\"java_buildpack\"],\"stack\":\"cflinuxfs4\"}}}"; + } + + private static String getWebProcessJson() { + return "{\"guid\":\"" + APP_GUID + "\",\"type\":\"web\",\"command\":\"old.sh\",\"instances\":1,\"memory_in_mb\":256," + + "\"disk_in_mb\":1024}"; + } + + private static String getAuditEventListJson() { + return "{\"resources\":[{\"guid\":\"" + APP_GUID + "\",\"type\":\"audit.app.update\"," + + "\"actor\":{\"guid\":\"" + APP_GUID + "\",\"type\":\"user\",\"name\":\"admin\"}," + + "\"target\":{\"guid\":\"" + APP_GUID + "\",\"type\":\"app\",\"name\":\"my-app\"}}]}"; + } + + private static String getServiceInstanceJson() { + return "{\"guid\":\"" + SERVICE_INSTANCE_GUID + "\",\"name\":\"my-service\",\"type\":\"managed\"}"; + } + + private static String getServiceInstanceWithPlanJson() { + return "{\"guid\":\"" + SERVICE_INSTANCE_GUID + "\",\"name\":\"my-service\",\"type\":\"managed\",\"relationships\":" + + "{\"service_plan\":{\"data\":{\"guid\":\"" + PLAN_GUID + "\"}}}}"; + } + + private static String getBindingJson() { + return "{\"guid\":\"" + BINDING_GUID + "\",\"name\":\"my-binding\",\"type\":\"app\",\"relationships\":" + + "{\"app\":{\"data\":{\"guid\":\"" + APP_GUID + "\"}},\"service_instance\":{\"data\":{\"guid\":\"" + + SERVICE_INSTANCE_GUID + "\"}}}}"; + } + + private static String getServiceKeyJson() { + return "{\"guid\":\"" + SERVICE_KEY_GUID + "\",\"name\":\"my-key\",\"type\":\"key\",\"relationships\":" + + "{\"service_instance\":{\"data\":{\"guid\":\"" + SERVICE_INSTANCE_GUID + "\"}}}}"; + } + + private static String getBrokerListJson() { + return "{\"resources\":[{\"guid\":\"" + BROKER_GUID + "\",\"name\":\"my-broker\",\"url\":\"https://broker.example.com\"}]}"; + } + + private static String getPackageJson() { + return "{\"guid\":\"" + PACKAGE_GUID + "\",\"type\":\"bits\",\"state\":\"READY\"}"; } private static String getTaskJson() { diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudSpaceClientTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudSpaceClientTest.java index 85c58cfdd1..0a164751f8 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudSpaceClientTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudSpaceClientTest.java @@ -59,7 +59,8 @@ void testGetSpaceByGuidThrowsWhenSpaceNotFound() { .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) .andRespond(MockRestResponseCreators.withStatus(HttpStatus.NOT_FOUND)); - Assertions.assertThrows(CloudOperationException.class, () -> client.getSpace(UUID.fromString(SPACE_GUID))); + UUID spaceGuid = UUID.fromString(SPACE_GUID); + Assertions.assertThrows(CloudOperationException.class, () -> client.getSpace(spaceGuid)); } @Test @@ -73,7 +74,8 @@ void testGetSpaceByGuidThrowsWhenOrganizationNotFound() { .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) .andRespond(MockRestResponseCreators.withStatus(HttpStatus.NOT_FOUND)); - Assertions.assertThrows(CloudOperationException.class, () -> client.getSpace(UUID.fromString(SPACE_GUID))); + UUID spaceGuid = UUID.fromString(SPACE_GUID); + Assertions.assertThrows(CloudOperationException.class, () -> client.getSpace(spaceGuid)); } @Test diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3OperationsTest.java index 332d96a9f3..7e66681a35 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3OperationsTest.java @@ -20,7 +20,6 @@ import org.junit.jupiter.api.Test; import org.mockito.ArgumentMatchers; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpMethod; @@ -29,6 +28,8 @@ import org.springframework.test.web.client.match.MockRestRequestMatchers; import org.springframework.test.web.client.response.MockRestResponseCreators; +import static org.mockito.Mockito.when; + class DomainsV3OperationsTest { private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; @@ -97,8 +98,8 @@ void testGetSharedDomainsReturnsEmptyWhenNoDomains() { @Test void testAddDomainCreatesWhenAbsent() { - Mockito.when(target.getOrganization()) - .thenReturn(getOrganizationWithGuid(ORG_GUID)); + when(target.getOrganization()) + .thenReturn(getOrganizationWithGuid(ORG_GUID)); stubEmptyDomainLookup(DOMAIN_NAME); @@ -172,8 +173,8 @@ void testDeleteDomainThrowsWhenSpaceNotProvided() { @Test void testGetDefaultDomainReturnsMappedDomain() { - Mockito.when(target.getOrganization()) - .thenReturn(getOrganizationWithGuid(ORG_GUID)); + when(target.getOrganization()) + .thenReturn(getOrganizationWithGuid(ORG_GUID)); factory.server() .expect(MockRestRequestMatchers.requestTo( @@ -190,8 +191,8 @@ void testGetDefaultDomainReturnsMappedDomain() { @Test void testGetDomainsForOrganizationReturnsMappedDomains() { - Mockito.when(target.getOrganization()) - .thenReturn(getOrganizationWithGuid(ORG_GUID)); + when(target.getOrganization()) + .thenReturn(getOrganizationWithGuid(ORG_GUID)); factory.server() .expect(MockRestRequestMatchers.requestTo( @@ -210,8 +211,8 @@ void testGetDomainsForOrganizationReturnsMappedDomains() { @Test void testGetDomainsForOrganizationReturnsEmptyWhenNoDomains() { - Mockito.when(target.getOrganization()) - .thenReturn(getOrganizationWithGuid(ORG_GUID)); + when(target.getOrganization()) + .thenReturn(getOrganizationWithGuid(ORG_GUID)); factory.server() .expect(MockRestRequestMatchers.requestTo( @@ -260,9 +261,9 @@ private static String getDomainResourceJson(String guid, String name) { } private void mockDomainList(V3Domain... domains) { - Mockito.when(cc.list(ArgumentMatchers.anyString(), - ArgumentMatchers.>> any())) - .thenReturn(List.of(domains)); + when(cc.list(ArgumentMatchers.anyString(), + ArgumentMatchers.>> any())) + .thenReturn(List.of(domains)); } private static V3Domain getSharedDomain(String name) { @@ -271,7 +272,7 @@ private static V3Domain getSharedDomain(String name) { private static V3Domain getPrivateDomain(String name) { V3DomainRelationships relationships = new V3DomainRelationships(new V3ToOneRelationship(new V3RelationshipData(ORG_GUID))); - + return new V3Domain(GUID_STRING, name, null, null, null, relationships); } diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/EventsV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/EventsV3OperationsTest.java index 61a5166f34..26f565f5da 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/EventsV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/EventsV3OperationsTest.java @@ -15,11 +15,14 @@ import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatchers; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpStatus; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + class EventsV3OperationsTest { private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; @@ -42,28 +45,28 @@ void setUp() throws Exception { @Test void testGetEventsMapsResults() { - Mockito.when( - cc.list(ArgumentMatchers.anyString(), ArgumentMatchers.>> any())) - .thenReturn(List.of(getAuditEvent("audit.app.update"), getAuditEvent("audit.app.create"))); + when(cc.list(ArgumentMatchers.anyString(), + ArgumentMatchers.>> any())).thenReturn( + List.of(getAuditEvent("audit.app.update"), getAuditEvent("audit.app.create"))); List result = operations.getEvents(); Assertions.assertEquals(2, result.size()); - Assertions.assertEquals("audit.app.update", result.get(0) + Assertions.assertEquals("audit.app.update", result.getFirst() .getType()); } @Test void testGetEventsByTargetIncludesTargetGuidInQuery() { - Mockito.when( - cc.list(ArgumentMatchers.anyString(), ArgumentMatchers.>> any())) - .thenReturn(List.of()); + when( + cc.list(ArgumentMatchers.anyString(), ArgumentMatchers.>> any())) + .thenReturn(List.of()); operations.getEventsByTarget(TARGET_GUID); ArgumentCaptor uriCaptor = ArgumentCaptor.forClass(String.class); - Mockito.verify(cc) - .list(uriCaptor.capture(), ArgumentMatchers.>> any()); + verify(cc) + .list(uriCaptor.capture(), ArgumentMatchers.>> any()); Assertions.assertTrue(uriCaptor.getValue() .contains("/v3/audit_events"), uriCaptor.getValue()); Assertions.assertTrue(uriCaptor.getValue() @@ -72,12 +75,12 @@ void testGetEventsByTargetIncludesTargetGuidInQuery() { @Test void testGetApplicationEventsThrowsWhenApplicationNotFound() { - Mockito.when(target.getGuid()) - .thenReturn(SPACE_GUID); + when(target.getGuid()) + .thenReturn(SPACE_GUID); - Mockito.when(cc.list(ArgumentMatchers.contains("/v3/apps"), - ArgumentMatchers.>> any())) - .thenReturn(List.of()); + when(cc.list(ArgumentMatchers.contains("/v3/apps"), + ArgumentMatchers.>> any())) + .thenReturn(List.of()); CloudOperationException thrown = Assertions.assertThrows(CloudOperationException.class, () -> operations.getApplicationEvents("missing-app")); @@ -87,20 +90,20 @@ ArgumentMatchers.>> any @Test void testGetApplicationEventsQueryScopesAppsToSpaceWhenTargetPresent() { - Mockito.when(target.getGuid()) - .thenReturn(SPACE_GUID); - Mockito.when(cc.list(ArgumentMatchers.contains("/v3/apps"), - ArgumentMatchers.>> any())) - .thenReturn(List.of(new V3Application(GUID_STRING, "my-app", "STARTED", null, null, null, null, null))); - Mockito.when(cc.list(ArgumentMatchers.contains("/v3/audit_events"), - ArgumentMatchers.>> any())) - .thenReturn(List.of()); + when(target.getGuid()) + .thenReturn(SPACE_GUID); + when(cc.list(ArgumentMatchers.contains("/v3/apps"), + ArgumentMatchers.>> any())) + .thenReturn(List.of(new V3Application(GUID_STRING, "my-app", "STARTED", null, null, null, null, null))); + when(cc.list(ArgumentMatchers.contains("/v3/audit_events"), + ArgumentMatchers.>> any())) + .thenReturn(List.of()); operations.getApplicationEvents("my-app"); ArgumentCaptor uriCaptor = ArgumentCaptor.forClass(String.class); - Mockito.verify(cc, Mockito.times(2)) - .list(uriCaptor.capture(), ArgumentMatchers.>> any()); + verify(cc, times(2)) + .list(uriCaptor.capture(), ArgumentMatchers.>> any()); String appsQuery = uriCaptor.getAllValues() .stream() .filter(uri -> uri.contains("/v3/apps")) diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3OperationsTest.java index 2fce71fbb9..ab7cc6969c 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3OperationsTest.java @@ -20,7 +20,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; @@ -28,6 +27,8 @@ import org.springframework.test.web.client.match.MockRestRequestMatchers; import org.springframework.test.web.client.response.MockRestResponseCreators; +import static org.mockito.Mockito.when; + class ProcessesV3OperationsTest { private static final String APP_GUID = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; @@ -236,14 +237,15 @@ void testGetCurrentDropletForApplicationThrowsWhenNoDroplet() { .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) .andRespond(MockRestResponseCreators.withStatus(HttpStatus.NO_CONTENT)); + UUID appGuid = UUID.fromString(APP_GUID); Assertions.assertThrows(CloudOperationException.class, - () -> operations.getCurrentDropletForApplication(UUID.fromString(APP_GUID))); + () -> operations.getCurrentDropletForApplication(appGuid)); } @Test void testUpdateApplicationStagingUpdatesAppAndProcess() { - Mockito.when(target.getGuid()) - .thenReturn(null); + when(target.getGuid()) + .thenReturn(null); factory.server() .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000&names=my-app")) @@ -285,8 +287,8 @@ void testUpdateApplicationStagingUpdatesAppAndProcess() { @Test void testUpdateApplicationStagingUpdatesAppFeatures() { - Mockito.when(target.getGuid()) - .thenReturn(null); + when(target.getGuid()) + .thenReturn(null); factory.server() .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000&names=my-app")) @@ -328,8 +330,8 @@ void testUpdateApplicationStagingUpdatesAppFeatures() { @Test void testUpdateApplicationStagingIncludesSpaceGuidInLookupQuery() { UUID spaceGuid = UUID.randomUUID(); - Mockito.when(target.getGuid()) - .thenReturn(spaceGuid); + when(target.getGuid()) + .thenReturn(spaceGuid); factory.server() .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000&space_guids=" @@ -365,8 +367,8 @@ void testUpdateApplicationStagingIncludesSpaceGuidInLookupQuery() { @Test void testUpdateApplicationStagingThrowsWhenApplicationNotFound() { - Mockito.when(target.getGuid()) - .thenReturn(null); + when(target.getGuid()) + .thenReturn(null); factory.server() .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL @@ -383,8 +385,8 @@ void testUpdateApplicationStagingThrowsWhenApplicationNotFound() { @Test void testUpdateApplicationStagingWithCnbLifecycleWithoutBuildpacksThrows() { - Mockito.when(target.getGuid()) - .thenReturn(null); + when(target.getGuid()) + .thenReturn(null); factory.server() .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000&names=my-app")) diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ReactiveFanOutTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ReactiveFanOutTest.java index 4fa71742a3..192acd89a1 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ReactiveFanOutTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ReactiveFanOutTest.java @@ -28,7 +28,7 @@ void testMapConcurrentlyShortCircuitsWhenSingleElement() { @Test @Timeout(10) - void testMapConcurrentlyResultPreserveInputOrderEvenWhenTasksFinishOutOfOrder() throws Exception { + void testMapConcurrentlyResultPreserveInputOrderEvenWhenTasksFinishOutOfOrder() { List input = IntStream.rangeClosed(1, 20) .boxed() .toList(); diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3OperationsTest.java index 85ae3bd8ea..f3ba3c1f47 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3OperationsTest.java @@ -14,7 +14,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; @@ -22,6 +21,8 @@ import org.springframework.test.web.client.match.MockRestRequestMatchers; import org.springframework.test.web.client.response.MockRestResponseCreators; +import static org.mockito.Mockito.when; + class RoutesV3OperationsTest { private static final String SPACE_GUID = "11111111-1111-1111-1111-111111111111"; @@ -48,8 +49,8 @@ void setUp() throws Exception { @Test void testAddRoutePostsToRoutes() { - Mockito.when(target.getGuid()) - .thenReturn(UUID.fromString(SPACE_GUID)); + when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); stubDomainLookup(DOMAIN_NAME, DOMAIN_GUID); factory.server() @@ -64,8 +65,8 @@ void testAddRoutePostsToRoutes() { @Test void testAddRouteThrowsWhenDomainNotFound() { - Mockito.when(target.getGuid()) - .thenReturn(UUID.fromString(SPACE_GUID)); + when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); stubEmptyDomainLookup(DOMAIN_NAME); Assertions.assertThrows(CloudOperationException.class, () -> operations.addRoute("myhost", DOMAIN_NAME, "/mypath")); @@ -80,8 +81,8 @@ void testAddRouteThrowsWhenSpaceNotProvided() { @Test void testDeleteRouteDeletesFoundRoute() { - Mockito.when(target.getGuid()) - .thenReturn(UUID.fromString(SPACE_GUID)); + when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); stubDomainLookup(DOMAIN_NAME, DOMAIN_GUID); stubRouteLookupWithResult(); @@ -94,8 +95,8 @@ void testDeleteRouteDeletesFoundRoute() { @Test void testDeleteRouteThrowsWhenRouteNotFound() { - Mockito.when(target.getGuid()) - .thenReturn(UUID.fromString(SPACE_GUID)); + when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); stubDomainLookup(DOMAIN_NAME, DOMAIN_GUID); factory.server() @@ -114,8 +115,8 @@ void testDeleteRouteThrowsWhenSpaceNotProvided() { @Test void testDeleteOrphanedRoutes() { - Mockito.when(target.getGuid()) - .thenReturn(UUID.fromString(SPACE_GUID)); + when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); stubAsyncDelete(MockControllerClientFactory.BASE_URL + "/v3/spaces/" + SPACE_GUID + "/routes?unmapped=true"); @@ -126,8 +127,8 @@ void testDeleteOrphanedRoutes() { @Test void testGetRoutesReturnsMappedRoutes() { - Mockito.when(target.getGuid()) - .thenReturn(UUID.fromString(SPACE_GUID)); + when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); stubDomainLookup(DOMAIN_NAME, DOMAIN_GUID); factory.server() @@ -146,8 +147,8 @@ void testGetRoutesReturnsMappedRoutes() { @Test void testGetRoutesThrowsWhenDomainNotFound() { - Mockito.when(target.getGuid()) - .thenReturn(UUID.fromString(SPACE_GUID)); + when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); stubEmptyDomainLookup(DOMAIN_NAME); @@ -193,8 +194,8 @@ void testGetApplicationRoutesReturnsEmptyList() { @Test void testUpdateApplicationRoutesAddsNewRoute() { - Mockito.when(target.getGuid()) - .thenReturn(UUID.fromString(SPACE_GUID)); + when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); stubAppLookup("my-app"); @@ -243,8 +244,8 @@ void testUpdateApplicationRoutesAddsNewRoute() { @Test void testUpdateApplicationRoutesRemovesOutdatedRoute() { - Mockito.when(target.getGuid()) - .thenReturn(UUID.fromString(SPACE_GUID)); + when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); stubAppLookup("my-app"); @@ -268,8 +269,8 @@ void testUpdateApplicationRoutesRemovesOutdatedRoute() { @Test void testUpdateApplicationRoutesThrowsWhenApplicationNotFound() { - Mockito.when(target.getGuid()) - .thenReturn(UUID.fromString(SPACE_GUID)); + when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); factory.server() .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000&names=missing-app" diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3OperationsTest.java index efb4212a0e..af8707d934 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3OperationsTest.java @@ -14,7 +14,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; @@ -22,6 +21,8 @@ import org.springframework.test.web.client.match.MockRestRequestMatchers; import org.springframework.test.web.client.response.MockRestResponseCreators; +import static org.mockito.Mockito.when; + class ServiceBindingsV3OperationsTest { private static final String APP_GUID = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; @@ -45,8 +46,8 @@ void setUp() throws Exception { @Test void testBindServiceInstanceReturnsJobGuidFromLocation() { - Mockito.when(target.getGuid()) - .thenReturn(null); + when(target.getGuid()) + .thenReturn(null); stubAppLookup("my-app"); stubServiceInstanceLookup("my-service"); @@ -66,8 +67,8 @@ void testBindServiceInstanceReturnsJobGuidFromLocation() { @Test void testBindServiceInstanceWithParametersReturnsEmptyWhenNoLocation() { - Mockito.when(target.getGuid()) - .thenReturn(null); + when(target.getGuid()) + .thenReturn(null); stubAppLookup("my-app"); stubServiceInstanceLookup("my-service"); @@ -86,8 +87,8 @@ void testBindServiceInstanceWithParametersReturnsEmptyWhenNoLocation() { @Test void testBindServiceInstanceUsesSpaceGuidInLookupWhenTargetHasGuid() { UUID spaceGuid = UUID.randomUUID(); - Mockito.when(target.getGuid()) - .thenReturn(spaceGuid); + when(target.getGuid()) + .thenReturn(spaceGuid); factory.server() .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000&space_guids=" @@ -113,8 +114,8 @@ void testBindServiceInstanceUsesSpaceGuidInLookupWhenTargetHasGuid() { @Test void testBindServiceInstanceThrowsWhenApplicationNotFound() { - Mockito.when(target.getGuid()) - .thenReturn(null); + when(target.getGuid()) + .thenReturn(null); factory.server() .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000&names=missing-app")) @@ -126,8 +127,8 @@ void testBindServiceInstanceThrowsWhenApplicationNotFound() { @Test void testBindServiceInstanceThrowsWhenServiceInstanceNotFound() { - Mockito.when(target.getGuid()) - .thenReturn(null); + when(target.getGuid()) + .thenReturn(null); stubAppLookup("my-app"); factory.server() @@ -141,8 +142,8 @@ void testBindServiceInstanceThrowsWhenServiceInstanceNotFound() { @Test void testUnbindServiceInstanceByNameDeletesFoundBindings() { - Mockito.when(target.getGuid()) - .thenReturn(null); + when(target.getGuid()) + .thenReturn(null); stubAppLookup("my-app"); stubServiceInstanceLookup("my-service"); @@ -323,8 +324,9 @@ void testGetServiceBindingParametersThrowsWhenNotFound() { .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) .andRespond(MockRestResponseCreators.withStatus(HttpStatus.NOT_FOUND)); + UUID bindingGuid = UUID.fromString(BINDING_GUID); Assertions.assertThrows(CloudOperationException.class, - () -> operations.getServiceBindingParameters(UUID.fromString(BINDING_GUID))); + () -> operations.getServiceBindingParameters(bindingGuid)); } @Test @@ -354,8 +356,9 @@ void testUpdateServiceBindingMetadataThrowsOnError() { Metadata metadata = Metadata.builder() .build(); + UUID bindingGuid = UUID.fromString(BINDING_GUID); Assertions.assertThrows(CloudOperationException.class, - () -> operations.updateServiceBindingMetadata(UUID.fromString(BINDING_GUID), metadata)); + () -> operations.updateServiceBindingMetadata(bindingGuid, metadata)); } private void stubAppLookup(String appName) { diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBrokersV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBrokersV3OperationsTest.java index 1d49ee67f5..02204f9125 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBrokersV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBrokersV3OperationsTest.java @@ -14,7 +14,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; @@ -22,6 +21,8 @@ import org.springframework.test.web.client.match.MockRestRequestMatchers; import org.springframework.test.web.client.response.MockRestResponseCreators; +import static org.mockito.Mockito.when; + class ServiceBrokersV3OperationsTest { private static final String BROKER_GUID = "11111111-2222-3333-4444-555555555555"; @@ -217,11 +218,11 @@ void testUpdateServiceBrokerThrowsWhenBrokerMissing() { @Test void testUpdateServicePlanVisibilityForBrokerUpdatesEachPlan() { - Mockito.when(target.getMetadata()) - .thenReturn(targetMetadata); + when(target.getMetadata()) + .thenReturn(targetMetadata); - Mockito.when(targetMetadata.getGuid()) - .thenReturn(UUID.fromString(SPACE_GUID)); + when(targetMetadata.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); stubBrokerLookup("broker-a", "{\"resources\":[" + getBrokerJson("broker-a") + "]}"); @@ -253,11 +254,11 @@ void testUpdateServicePlanVisibilityForBrokerUpdatesEachPlan() { @Test void testUpdateServicePlanVisibilityForBrokerWithNoOfferingsUpdatesNothing() { - Mockito.when(target.getMetadata()) - .thenReturn(targetMetadata); + when(target.getMetadata()) + .thenReturn(targetMetadata); - Mockito.when(targetMetadata.getGuid()) - .thenReturn(UUID.fromString(SPACE_GUID)); + when(targetMetadata.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); stubBrokerLookup("broker-a", "{\"resources\":[" + getBrokerJson("broker-a") + "]}"); diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3OperationsTest.java index aab642299a..60451abbe4 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3OperationsTest.java @@ -15,7 +15,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; @@ -23,6 +22,8 @@ import org.springframework.test.web.client.match.MockRestRequestMatchers; import org.springframework.test.web.client.response.MockRestResponseCreators; +import static org.mockito.Mockito.when; + class ServiceInstancesV3OperationsTest { private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; @@ -44,10 +45,10 @@ void setUp() throws Exception { MockitoAnnotations.openMocks(this) .close(); factory = MockControllerClientFactory.create(); - Mockito.when(target.getMetadata()) - .thenReturn(targetMetadata); - Mockito.when(targetMetadata.getGuid()) - .thenReturn(SPACE_GUID); + when(target.getMetadata()) + .thenReturn(targetMetadata); + when(targetMetadata.getGuid()) + .thenReturn(SPACE_GUID); operations = new ServiceInstancesV3Operations(factory.client(), target); } @@ -299,7 +300,8 @@ void testUpdateServiceTagsUpdates() { void testUpdateServiceTagsThrowsWhenNotFound() { stubServiceInstanceLookup("missing", null); - Assertions.assertThrows(CloudOperationException.class, () -> operations.updateServiceTags("missing", List.of("tag1"))); + List tags = List.of("tag1"); + Assertions.assertThrows(CloudOperationException.class, () -> operations.updateServiceTags("missing", tags)); } @Test diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3OperationsTest.java index 93b86a3513..e61bce6f38 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3OperationsTest.java @@ -140,8 +140,10 @@ void testCreateServiceKeyReturnsEmptyWhenNoLocationHeader() { @Test void testCreateServiceKeyThrowsForUserProvidedService() { + CloudServiceInstance userProvidedServiceInstance = getUserProvidedServiceInstance(); + Assertions.assertThrows(IllegalArgumentException.class, - () -> operations.createServiceKey(getUserProvidedServiceInstance(), KEY_NAME, null)); + () -> operations.createServiceKey(userProvidedServiceInstance, KEY_NAME, null)); } @Test diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3OperationsTest.java index 48de698301..5b7c9367cf 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3OperationsTest.java @@ -14,10 +14,12 @@ import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatchers; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.springframework.core.ParameterizedTypeReference; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + class ServiceOfferingsV3OperationsTest { private static final String OFFERING_GUID = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; @@ -40,12 +42,12 @@ void setUp() throws Exception { @Test void testGetServiceOfferingsMapsOfferingWithItsPlans() { - Mockito.when(cc.list(ArgumentMatchers.contains("/v3/service_offerings"), - ArgumentMatchers.>> any())) - .thenReturn(List.of(getOffering())); - Mockito.when(cc.list(ArgumentMatchers.contains("/v3/service_plans"), - ArgumentMatchers.>> any())) - .thenReturn(List.of(getPlan("plan-a"), getPlan("plan-b"))); + when(cc.list(ArgumentMatchers.contains("/v3/service_offerings"), + ArgumentMatchers.>> any())) + .thenReturn(List.of(getOffering())); + when(cc.list(ArgumentMatchers.contains("/v3/service_plans"), + ArgumentMatchers.>> any())) + .thenReturn(List.of(getPlan("plan-a"), getPlan("plan-b"))); List result = operations.getServiceOfferings(); @@ -59,36 +61,36 @@ ArgumentMatchers.>> any @Test void testGetServiceOfferingsScopesQueryToSpaceWhenTargetHasGuid() { - Mockito.when(target.getGuid()) - .thenReturn(SPACE_GUID); + when(target.getGuid()) + .thenReturn(SPACE_GUID); - Mockito.when(cc.list(ArgumentMatchers.contains("/v3/service_offerings"), - ArgumentMatchers.>> any())) - .thenReturn(List.of()); + when(cc.list(ArgumentMatchers.contains("/v3/service_offerings"), + ArgumentMatchers.>> any())) + .thenReturn(List.of()); operations.getServiceOfferings(); ArgumentCaptor uriCaptor = ArgumentCaptor.forClass(String.class); - Mockito.verify(cc) - .list(uriCaptor.capture(), ArgumentMatchers.>> any()); + verify(cc) + .list(uriCaptor.capture(), ArgumentMatchers.>> any()); Assertions.assertTrue(uriCaptor.getValue() .contains("space_guids=" + SPACE_GUID), uriCaptor.getValue()); } @Test void testGetServiceOfferingsExcludesSpaceGuidWhenTargetGuidNull() { - Mockito.when(target.getGuid()) - .thenReturn(null); + when(target.getGuid()) + .thenReturn(null); - Mockito.when(cc.list(ArgumentMatchers.contains("/v3/service_offerings"), - ArgumentMatchers.>> any())) - .thenReturn(List.of()); + when(cc.list(ArgumentMatchers.contains("/v3/service_offerings"), + ArgumentMatchers.>> any())) + .thenReturn(List.of()); operations.getServiceOfferings(); ArgumentCaptor uriCaptor = ArgumentCaptor.forClass(String.class); - Mockito.verify(cc) - .list(uriCaptor.capture(), ArgumentMatchers.>> any()); + verify(cc) + .list(uriCaptor.capture(), ArgumentMatchers.>> any()); Assertions.assertFalse(uriCaptor.getValue() .contains("space_guids="), uriCaptor.getValue()); } diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3OperationsTest.java index bd2a4fce20..2f2bdabfdc 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3OperationsTest.java @@ -9,7 +9,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; @@ -17,6 +16,8 @@ import org.springframework.test.web.client.match.MockRestRequestMatchers; import org.springframework.test.web.client.response.MockRestResponseCreators; +import static org.mockito.Mockito.when; + class ServicePlansV3OperationsTest { private static final String BROKER_GUID = "11111111-1111-1111-1111-111111111111"; @@ -41,8 +42,8 @@ void setUp() throws Exception { @Test void testUpdateServicePlanVisibilityForBrokerUpdatesAllPlansWhenNoSpaceFilter() { - Mockito.when(target.getGuid()) - .thenReturn(null); + when(target.getGuid()) + .thenReturn(null); stubBrokerLookup("{\"resources\":[{\"guid\":\"" + BROKER_GUID + "\"}]}"); stubOfferingLookup(null, "{\"resources\":[{\"guid\":\"" + OFFERING_GUID + "\"}]}"); @@ -56,8 +57,8 @@ void testUpdateServicePlanVisibilityForBrokerUpdatesAllPlansWhenNoSpaceFilter() @Test void testUpdateServicePlanVisibilityForBrokerAppendsSpaceGuidWhenTargetHasGuid() { - Mockito.when(target.getGuid()) - .thenReturn(SPACE_GUID); + when(target.getGuid()) + .thenReturn(SPACE_GUID); stubBrokerLookup("{\"resources\":[{\"guid\":\"" + BROKER_GUID + "\"}]}"); stubOfferingLookup(SPACE_GUID, "{\"resources\":[{\"guid\":\"" + OFFERING_GUID + "\"}]}"); stubPlanLookup("{\"resources\":[{\"guid\":\"" + PLAN_GUID + "\"}]}"); @@ -79,8 +80,8 @@ void testUpdateServicePlanVisibilityForBrokerThrowsWhenBrokerNotFound() { @Test void testUpdateServicePlanVisibilityForBrokerDoesNothingWhenNoOfferings() { - Mockito.when(target.getGuid()) - .thenReturn(null); + when(target.getGuid()) + .thenReturn(null); stubBrokerLookup("{\"resources\":[{\"guid\":\"" + BROKER_GUID + "\"}]}"); stubOfferingLookup(null, "{\"resources\":[]}"); @@ -92,8 +93,8 @@ void testUpdateServicePlanVisibilityForBrokerDoesNothingWhenNoOfferings() { @Test void testUpdateServicePlanVisibilityForBrokerDoesNothingWhenNoPlans() { - Mockito.when(target.getGuid()) - .thenReturn(null); + when(target.getGuid()) + .thenReturn(null); stubBrokerLookup("{\"resources\":[{\"guid\":\"" + BROKER_GUID + "\"}]}"); stubOfferingLookup(null, "{\"resources\":[{\"guid\":\"" + OFFERING_GUID + "\"}]}"); diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/StacksV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/StacksV3OperationsTest.java index 435924d2cb..230b32c968 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/StacksV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/StacksV3OperationsTest.java @@ -12,11 +12,13 @@ import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatchers; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpStatus; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + class StacksV3OperationsTest { private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; @@ -78,8 +80,8 @@ void testGetStacksQueriesStacksEndpointWithPageSize() { operations.getStacks(); ArgumentCaptor uriCaptor = ArgumentCaptor.forClass(String.class); - Mockito.verify(cc) - .list(uriCaptor.capture(), ArgumentMatchers.>> any()); + verify(cc) + .list(uriCaptor.capture(), ArgumentMatchers.>> any()); Assertions.assertTrue(uriCaptor.getValue() .contains("/v3/stacks"), uriCaptor.getValue()); Assertions.assertTrue(uriCaptor.getValue() @@ -87,9 +89,9 @@ void testGetStacksQueriesStacksEndpointWithPageSize() { } private void mockStackList(V3Stack... stacks) { - Mockito.when(cc.list(ArgumentMatchers.anyString(), - ArgumentMatchers.>> any())) - .thenReturn(List.of(stacks)); + when(cc.list(ArgumentMatchers.anyString(), + ArgumentMatchers.>> any())) + .thenReturn(List.of(stacks)); } } diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/TasksV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/TasksV3OperationsTest.java index 516cb322d0..fc8df33bbf 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/TasksV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/TasksV3OperationsTest.java @@ -10,13 +10,14 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.test.web.client.match.MockRestRequestMatchers; import org.springframework.test.web.client.response.MockRestResponseCreators; +import static org.mockito.Mockito.when; + class TasksV3OperationsTest { private static final String APP_GUID = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; @@ -85,8 +86,8 @@ void testCancelTaskPostsToCancelAction() { @Test void testRunTaskThrowsWhenApplicationNotFound() { - Mockito.when(target.getGuid()) - .thenReturn(null); + when(target.getGuid()) + .thenReturn(null); factory.server() .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000&names=missing-app")) @@ -101,8 +102,8 @@ void testRunTaskThrowsWhenApplicationNotFound() { } private void stubAppLookup() { - Mockito.when(target.getGuid()) - .thenReturn(null); + when(target.getGuid()) + .thenReturn(null); factory.server() .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000&names=my-app")) From d32990e46897277bd44759f9557ef72f869fdca0 Mon Sep 17 00:00:00 2001 From: Krasimir Kargov Date: Mon, 28 Sep 2026 09:29:45 +0300 Subject: [PATCH 10/15] Fixing comments LMCROSSITXSADEPLOY-3563 --- .../multiapps/controller/Constants.java | 7 + .../multiapps/controller/Messages.java | 14 +- .../client/facade/domain/HealthCheckType.java | 4 - .../client/facade/domain/Metadata.java | 8 - .../facade/domain/ServiceInstanceType.java | 11 +- .../facade/rest/ApplicationsV3Operations.java | 176 +++++++++-------- .../facade/rest/BuildsV3Operations.java | 49 +++-- .../CloudControllerRestClientBuilder.java | 77 +++++--- .../CloudControllerRestClientFactory.java | 132 +++---------- .../rest/CloudControllerRestClientV3Impl.java | 31 +-- .../CloudControllerRootDocumentClient.java | 72 +++++++ .../facade/rest/CloudControllerV3Client.java | 76 +++++--- .../rest/CloudControllerV3Endpoints.java | 2 +- .../client/facade/rest/CloudSpaceClient.java | 57 +++--- .../facade/rest/DomainsV3Operations.java | 76 ++++---- .../facade/rest/EventsV3Operations.java | 53 ++--- .../client/facade/rest/JobsV3Operations.java | 18 +- .../facade/rest/PackagesV3Operations.java | 120 +++++++----- .../facade/rest/ProcessesV3Operations.java | 115 ++++++----- .../client/facade/rest/RolesV3Operations.java | 14 +- .../facade/rest/RoutesV3Operations.java | 174 +++++++++-------- .../rest/ServiceBindingsV3Operations.java | 121 ++++++------ .../rest/ServiceBrokersV3Operations.java | 108 +++++----- .../rest/ServiceInstancesV3Operations.java | 184 +++++++++--------- .../facade/rest/ServiceKeysV3Operations.java | 30 +-- .../rest/ServiceOfferingsV3Operations.java | 26 +-- .../facade/rest/ServicePlansV3Operations.java | 54 ++--- .../facade/rest/StacksV3Operations.java | 28 +-- .../client/facade/rest/TasksV3Operations.java | 42 ++-- .../facade/rest/resources/V3Application.java | 17 +- .../rest/resources/V3ApplicationMapper.java | 6 +- .../facade/rest/resources/V3AuditEvent.java | 8 +- .../client/facade/rest/resources/V3Build.java | 10 +- .../facade/rest/resources/V3Domain.java | 13 +- .../facade/rest/resources/V3Droplet.java | 2 +- .../facade/rest/resources/V3Fields.java | 27 +++ .../client/facade/rest/resources/V3Job.java | 9 +- .../facade/rest/resources/V3Organization.java | 4 +- .../facade/rest/resources/V3Package.java | 8 +- .../facade/rest/resources/V3Process.java | 10 +- .../client/facade/rest/resources/V3Role.java | 4 +- .../client/facade/rest/resources/V3Route.java | 22 +-- .../rest/resources/V3ServiceBinding.java | 30 +-- .../rest/resources/V3ServiceBroker.java | 15 +- .../rest/resources/V3ServiceInstance.java | 19 +- .../resources/V3ServiceInstanceMapper.java | 15 +- .../rest/resources/V3ServiceOffering.java | 18 +- .../facade/rest/resources/V3ServicePlan.java | 16 +- .../client/facade/rest/resources/V3Space.java | 12 +- .../client/facade/rest/resources/V3Stack.java | 6 +- .../client/facade/rest/resources/V3Task.java | 9 +- .../client/facade/util/UriUtil.java | 77 +++++++- ...sCloudControllerClientIntegrationTest.java | 26 +-- .../facade/rest/DomainsV3OperationsTest.java | 20 +- .../facade/rest/EventsV3OperationsTest.java | 32 +-- .../ServiceOfferingsV3OperationsTest.java | 30 +-- .../facade/rest/StacksV3OperationsTest.java | 10 +- .../BuildApplicationDeployModelStep.java | 12 +- 58 files changed, 1309 insertions(+), 1057 deletions(-) create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRootDocumentClient.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Fields.java diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Constants.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Constants.java index f5c6047cbe..6d883ecb72 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Constants.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Constants.java @@ -15,11 +15,14 @@ public class Constants { public static final int DEFAULT_CONNECTION_POOL_SIZE = 192; public static final int UNDEFINED_PORT = -1; public static final int MAX_CONCURRENT_PAGES = 256; + public static final int SECOND_PAGE = 2; public static final int DEFAULT_CONCURRENT_TASKS = 256; public static final int MAX_CHAR_LENGTH_FOR_PARAMS_IN_REQUEST = 4000; + public static final int JOB_POLL_INTERVAL_MULTIPLIER = 2; public static final String CONNECTION_POOL_NAME = "cf-controller-client"; public static final String LOOP_RESOURCES_SUFFIX = "-loop-resources"; + public static final String UPLOAD_MONITOR_THREAD_NAME_PREFIX = "app-upload-monitor-"; public static final String CF_API_V3 = "/v3"; public static final String HREF = "href"; public static final String CLOUD_CONTROLLER_CF_ROOT_DOCUMENT_NAME = "cloud_controller_v3"; @@ -27,10 +30,14 @@ public class Constants { public static final String API_HOST_PREFIX = "api."; public static final String LOG_CACHE_PREFIX = "log-cache."; public static final String ROOT_DOCUMENT_LINKS_LIST = "links"; + public static final String TYPE = "type"; + public static final String DATA = "data"; + public static final String CLOUD_NATIVE_BUILDPACK = "cnb"; public static final String EMPTY_STRING = ""; public static final String COLON = ":"; public static final String PROTOCOL_SEPARATOR = "://"; + public static final String NEW_LINE = "\n"; public static final String PACKAGE_LINK = "package"; public static final String WEB_PROCESS_TYPE = "web"; diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Messages.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Messages.java index bb9cdda346..2e8e38ecc1 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Messages.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Messages.java @@ -30,7 +30,15 @@ public class Messages { public static final String STACK_0_NOT_FOUND = "Stack \"{0}\" not found."; public static final String ORGANISATION_0_NOT_FOUND = "Organization \"{0}\" not found."; public static final String SPACE_WITH_GUID_0_NOT_FOUND = "Space with GUID \"{0}\" not found."; - public static final String APPLICATION_WITH_GUID_0_DOES_NOT_HAVE_A_DROPLET = "Application with guid \"{0}\" does not have a droplet"; + public static final String APPLICATION_WITH_GUID_0_DOES_NOT_HAVE_A_DROPLET = "Application with guid \"{0}\" does not have a droplet."; + public static final String BUILD_WITH_GUID_0_NOT_FOUND = "Build with guid \"{0}\" not found."; + public static final String DOMAIN_WITH_GUID_0_NOT_FOUND = "Domain with guid \"{0}\" not found."; + public static final String JOB_WITH_GUID_0_NOT_FOUND = "Job with guid \"{0}\" not found."; + public static final String STATISTICS_FOR_APPLICATION_INSTANCES_WITH_GUID_0_NOT_FOUND = "Statistics for application instance with guid \"{0}\" not found."; + public static final String APPLICATION_PROCESS_FOR_APPLICATION_WITH_GUID_0_NOT_FOUND = "Application process for application with guid \"{0}\" not found."; + public static final String PARAMETERS_OF_SERVICE_BINDING_WITH_GUID_0_NOT_FOUND = "Parameters of service binding with guid \"{0}\" not found."; + public static final String PARAMETERS_OF_SERVICE_INSTANCE_WITH_GUID_0_NOT_FOUND = "Parameters of service instance with guid \"{0}\" not found."; + public static final String CREDENTIALS_OF_SERVICE_INSTANCE_WITH_GUID_0_NOT_FOUND = "Credentials of service instance with guid \"{0}\" not found."; public static final String HOST_0_NOT_FOUND_FOR_DOMAIN_1 = "Host \"{0}\" not found for domain \"{1}\"."; public static final String UNABLE_TO_0_WITHOUT_SPECIFYING_ORGANIZATION_AND_SPACE_TO_USE = "Unable to \"{0}\" without specifying organization and space to use."; public static final String DOMAIN_0_NOT_FOUND_FOR_URI_1 = "Domain \"{0}\" not found for URI \"{1}\""; @@ -49,11 +57,15 @@ public class Messages { public static final String UNKNOWN_USER_ROLE_0 = "Unknown user role: \"{0}\""; public static final String CANNOT_CREATE_DOMAIN_0_WITHOUT_ORGANIZATION = "Cannot create domain \"{0}\" without specifying an organization."; public static final String PACKAGE_0_NOT_FOUND = "Package \"{0}\" not found."; + public static final String BUILDPACKS_ARE_REQUIRED_FOR_THE_BUILDPACK_LIFECYCLE_TYPE = "Buildpacks are required for the buildpack (Cloud Native Buildpack) lifecycle type."; //Status texts public static final String NOT_FOUND = "Not Found"; public static final String JOB_TIMEOUT = "Job Timeout"; public static final String JOB_FAILED = "Job Failed"; + public static final String JOB_FAILED_WITHOUT_DETAILS = "Job failed without any error details"; + public static final String JOB_0_WITH_OPERATION_1_FAILED_WITH_2 = "Job \"{0}\" (operation \"{1}\") failed with: {2}"; + public static final String JOB_0_RELATED_TO_OPERATION_1_FAILED_WITH_2 = "Job \"{0}\" related to operation \"{1}\" failed with: \"{2}\""; public static final String BAD_REQUEST = "Bad Request"; public static final String FORBIDDEN = "Forbidden"; diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/HealthCheckType.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/HealthCheckType.java index 992fa758a3..d6d3bf9971 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/HealthCheckType.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/HealthCheckType.java @@ -3,10 +3,6 @@ public enum HealthCheckType { HTTP, PORT, PROCESS, @Deprecated NONE; - public String getValue() { - return name().toLowerCase(); - } - @Override public String toString() { return this.name() diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/Metadata.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/Metadata.java index 342e1d2d44..8555421f22 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/Metadata.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/Metadata.java @@ -87,10 +87,6 @@ public Builder labels(Map labels) { return this; } - public Builder putAllLabels(Map labels) { - return labels(labels); - } - public Builder annotation(String key, String value) { this.annotations.put(key, value); return this; @@ -104,10 +100,6 @@ public Builder annotations(Map annotations) { return this; } - public Builder putAllAnnotations(Map annotations) { - return annotations(annotations); - } - public Metadata build() { return new Metadata(labels, annotations); } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/ServiceInstanceType.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/ServiceInstanceType.java index 434443b306..ac93bcb13e 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/ServiceInstanceType.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/ServiceInstanceType.java @@ -23,12 +23,21 @@ public String getValue() { @JsonCreator public static ServiceInstanceType from(String value) { + ServiceInstanceType serviceInstanceType = mapValue(value); + if (serviceInstanceType != null) { + return serviceInstanceType; + } + + throw new IllegalArgumentException(MessageFormat.format(Messages.UNKNOWN_SERVICE_INSTANCE_TYPE_0, value)); + } + + public static ServiceInstanceType mapValue(String value) { for (ServiceInstanceType type : values()) { if (type.value.equals(value)) { return type; } } - throw new IllegalArgumentException(MessageFormat.format(Messages.UNKNOWN_SERVICE_INSTANCE_TYPE_0, value)); + return null; } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3Operations.java index d7ac2ceb98..ac4f318cac 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3Operations.java @@ -27,25 +27,38 @@ public class ApplicationsV3Operations { private static final Duration DELETE_JOB_TIMEOUT = Duration.ofMinutes(5); - private final CloudControllerV3Client cc; - private final CloudSpace target; + private final CloudControllerV3Client client; + private final CloudSpace targetSpace; - public ApplicationsV3Operations(CloudControllerV3Client cc, CloudSpace target) { - this.cc = cc; - this.target = target; + public ApplicationsV3Operations(CloudControllerV3Client client, CloudSpace targetSpace) { + this.client = client; + this.targetSpace = targetSpace; } public UUID createApplication(ApplicationToCreateDto dto) { - if (target == null || target.getGuid() == null) { + validateTargetSpace(); + + Map body = buildApplicationBody(dto); + UUID appGuid = sendCreateApplicationRequest(body); + + scaleApplicationIfNeeded(appGuid, dto); + + return appGuid; + } + + private void validateTargetSpace() { + if (targetSpace == null || targetSpace.getGuid() == null) { throw new CloudOperationException(HttpStatus.BAD_REQUEST, Messages.BAD_REQUEST, Messages.TARGET_SPACE_REQUIRED_TO_CREATE_AN_APPLICATION); } + } + private Map buildApplicationBody(ApplicationToCreateDto dto) { Map body = new HashMap<>(); body.put("name", dto.getName()); body.put("lifecycle", buildLifecycle(dto.getStaging())); - body.put("relationships", Map.of("space", Map.of("data", Map.of("guid", target.getGuid() - .toString())))); + body.put("relationships", Map.of("space", Map.of("data", Map.of("guid", targetSpace.getGuid() + .toString())))); if (dto.getEnv() != null) { body.put("environment_variables", dto.getEnv()); } @@ -57,14 +70,20 @@ public UUID createApplication(ApplicationToCreateDto dto) { .getAnnotations())); } - V3Application created = cc.getRestClient() - .post() - .uri(CloudControllerV3Endpoints.APPS) - .body(body) - .retrieve() - .body(V3Application.class); - UUID appGuid = UUID.fromString(created.guid()); + return body; + } + + private UUID sendCreateApplicationRequest(Map body) { + V3Application created = client.getRestClient() + .post() + .uri(CloudControllerV3Endpoints.APPS) + .body(body) + .retrieve() + .body(V3Application.class); + return UUID.fromString(created.guid()); + } + private void scaleApplicationIfNeeded(UUID appGuid, ApplicationToCreateDto dto) { Map scale = new HashMap<>(); if (dto.getMemoryInMb() != null) { scale.put("memory_in_mb", dto.getMemoryInMb()); @@ -77,8 +96,6 @@ public UUID createApplication(ApplicationToCreateDto dto) { if (!scale.isEmpty()) { scaleWebProcess(appGuid, scale); } - - return appGuid; } private Map buildLifecycle(Staging staging) { @@ -110,13 +127,13 @@ private Map buildLifecycle(Staging staging) { public void deleteApplication(String applicationName) { UUID applicationGuid = getApplicationGuid(applicationName); - var response = cc.getRestClient() - .delete() - .uri(CloudControllerV3Endpoints.APP_BY_GUID, applicationGuid) - .retrieve() - .toBodilessEntity(); + var response = client.getRestClient() + .delete() + .uri(CloudControllerV3Endpoints.APP_BY_GUID, applicationGuid) + .retrieve() + .toBodilessEntity(); - cc.followAsyncJob(response, DELETE_JOB_TIMEOUT); + client.followAsyncJob(response, DELETE_JOB_TIMEOUT); } public CloudApplication getApplication(String applicationName) { @@ -126,15 +143,16 @@ public CloudApplication getApplication(String applicationName) { public CloudApplication getApplication(String applicationName, boolean required) { V3Application app = findApplicationByName(applicationName); - if (app == null) { - if (required) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, - MessageFormat.format(Messages.APPLICATION_0_NOT_FOUND, applicationName)); - } - return null; + if (app != null) { + return V3ApplicationMapper.toCloudApplication(app, targetSpace); } - return V3ApplicationMapper.toCloudApplication(app, target); + if (required) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + MessageFormat.format(Messages.APPLICATION_0_NOT_FOUND, applicationName)); + } + + return null; } public UUID getApplicationGuid(String applicationName) { @@ -142,15 +160,15 @@ public UUID getApplicationGuid(String applicationName) { } public String getApplicationName(UUID applicationGuid) { - V3Application app = cc.get(CloudControllerV3Endpoints.APPS + "/" + applicationGuid, V3Application.class); + V3Application app = client.get(CloudControllerV3Endpoints.APPS + "/" + applicationGuid, V3Application.class); return app == null ? null : app.name(); } public Map getApplicationEnvironment(UUID applicationGuid) { - V3Application.V3EnvironmentVariables environmentVariablesJson = cc.get(CloudControllerV3Endpoints.APPS + "/" + applicationGuid - + "/environment_variables", - V3Application.V3EnvironmentVariables.class); + V3Application.V3EnvironmentVariables environmentVariablesJson = client.get(CloudControllerV3Endpoints.APPS + "/" + applicationGuid + + "/environment_variables", + V3Application.V3EnvironmentVariables.class); return environmentVariablesJson == null || environmentVariablesJson.environmentVariables() == null ? Map.of() @@ -163,7 +181,7 @@ public Map getApplicationEnvironment(String applicationName) { public List getApplications() { return listApplications(applicationsQuery(null)).stream() - .map(app -> V3ApplicationMapper.toCloudApplication(app, target)) + .map(app -> V3ApplicationMapper.toCloudApplication(app, targetSpace)) .toList(); } @@ -175,40 +193,40 @@ public List getApplicationsByMetadataLabelSelector(String labe } return listApplications(query).stream() - .map(app -> V3ApplicationMapper.toCloudApplication(app, target)) + .map(app -> V3ApplicationMapper.toCloudApplication(app, targetSpace)) .toList(); } public void startApplication(String applicationName) { UUID guid = getApplicationGuid(applicationName); - cc.getRestClient() - .post() - .uri(CloudControllerV3Endpoints.APP_START, guid) - .retrieve() - .toBodilessEntity(); + client.getRestClient() + .post() + .uri(CloudControllerV3Endpoints.APP_START, guid) + .retrieve() + .toBodilessEntity(); } public void stopApplication(String applicationName) { UUID guid = getApplicationGuid(applicationName); - cc.getRestClient() - .post() - .uri(CloudControllerV3Endpoints.APP_STOP, guid) - .retrieve() - .toBodilessEntity(); + client.getRestClient() + .post() + .uri(CloudControllerV3Endpoints.APP_STOP, guid) + .retrieve() + .toBodilessEntity(); } public void rename(String applicationName, String newName) { UUID guid = getApplicationGuid(applicationName); try { - cc.getRestClient() - .patch() - .uri(CloudControllerV3Endpoints.APP_BY_GUID, guid) - .body(Map.of("name", newName)) - .retrieve() - .toBodilessEntity(); + client.getRestClient() + .patch() + .uri(CloudControllerV3Endpoints.APP_BY_GUID, guid) + .body(Map.of("name", newName)) + .retrieve() + .toBodilessEntity(); } catch (CloudOperationException e) { //the Cloud Controller can return 503 but the rename might have happened already if (e.getStatusCode() == HttpStatus.SERVICE_UNAVAILABLE && newName.equals(getApplicationName(guid))) { @@ -233,39 +251,39 @@ public void updateApplicationDiskQuota(String applicationName, int disk) { public void updateApplicationEnv(String applicationName, Map env) { UUID guid = getApplicationGuid(applicationName); - cc.getRestClient() - .patch() - .uri(CloudControllerV3Endpoints.APP_ENV_VARS, guid) - .body(Map.of("var", env)) - .retrieve() - .toBodilessEntity(); + client.getRestClient() + .patch() + .uri(CloudControllerV3Endpoints.APP_ENV_VARS, guid) + .body(Map.of("var", env)) + .retrieve() + .toBodilessEntity(); } public void bindDropletToApp(UUID dropletGuid, UUID applicationGuid) { - cc.getRestClient() - .patch() - .uri(CloudControllerV3Endpoints.APP_CURRENT_DROPLET, applicationGuid) - .body(Map.of("data", Map.of("guid", dropletGuid.toString()))) - .retrieve() - .toBodilessEntity(); + client.getRestClient() + .patch() + .uri(CloudControllerV3Endpoints.APP_CURRENT_DROPLET, applicationGuid) + .body(Map.of("data", Map.of("guid", dropletGuid.toString()))) + .retrieve() + .toBodilessEntity(); } public void updateApplicationMetadata(UUID guid, Metadata metadata) { - cc.getRestClient() - .patch() - .uri(CloudControllerV3Endpoints.APP_BY_GUID, guid) - .body(Map.of("metadata", Map.of("labels", metadata.getLabels(), "annotations", metadata.getAnnotations()))) - .retrieve() - .toBodilessEntity(); + client.getRestClient() + .patch() + .uri(CloudControllerV3Endpoints.APP_BY_GUID, guid) + .body(Map.of("metadata", Map.of("labels", metadata.getLabels(), "annotations", metadata.getAnnotations()))) + .retrieve() + .toBodilessEntity(); } private void scaleWebProcess(UUID applicationGuid, Map scaleBody) { - cc.getRestClient() - .post() - .uri(CloudControllerV3Endpoints.APP_WEB_PROCESS_SCALE, applicationGuid) - .body(scaleBody) - .retrieve() - .toBodilessEntity(); + client.getRestClient() + .post() + .uri(CloudControllerV3Endpoints.APP_WEB_PROCESS_SCALE, applicationGuid) + .body(scaleBody) + .retrieve() + .toBodilessEntity(); } private V3Application findApplicationByName(String applicationName) { @@ -275,16 +293,16 @@ private V3Application findApplicationByName(String applicationName) { } private List listApplications(String query) { - return cc.list(query, APPLICATION_PAGE); + return client.list(query, APPLICATION_PAGE); } private String applicationsQuery(String name) { StringBuilder query = new StringBuilder(CloudControllerV3Endpoints.APPS + CloudControllerV3Endpoints.QUERY_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE); - if (target != null && target.getGuid() != null) { + if (targetSpace != null && targetSpace.getGuid() != null) { query.append(CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) - .append(target.getGuid()); + .append(targetSpace.getGuid()); } if (name != null) { diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3Operations.java index 6f14e93898..8f752b29bf 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3Operations.java @@ -1,36 +1,45 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest; +import java.text.MessageFormat; import java.util.List; import java.util.Map; import java.util.UUID; +import org.cloudfoundry.multiapps.controller.Messages; +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudBuild; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Build; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3BuildMapper; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; public class BuildsV3Operations { - private final CloudControllerV3Client cc; + private final CloudControllerV3Client client; - public BuildsV3Operations(CloudControllerV3Client cc) { - this.cc = cc; + public BuildsV3Operations(CloudControllerV3Client client) { + this.client = client; } public CloudBuild createBuild(UUID packageGuid) { - V3Build build = cc.getRestClient() - .post() - .uri(CloudControllerV3Endpoints.BUILDS) - .body(Map.of("package", Map.of("guid", packageGuid.toString()))) - .retrieve() - .body(V3Build.class); + V3Build build = client.getRestClient() + .post() + .uri(CloudControllerV3Endpoints.BUILDS) + .body(Map.of("package", Map.of("guid", packageGuid.toString()))) + .retrieve() + .body(V3Build.class); return V3BuildMapper.toCloudBuild(build); } public CloudBuild getBuild(UUID buildGuid) { - V3Build build = cc.get(CloudControllerV3Endpoints.BUILDS + "/" + buildGuid, V3Build.class); + V3Build build = client.get(CloudControllerV3Endpoints.BUILDS + "/" + buildGuid, V3Build.class); + + if (build == null) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + MessageFormat.format(Messages.BUILD_WITH_GUID_0_NOT_FOUND, buildGuid)); + } return V3BuildMapper.toCloudBuild(build); } @@ -39,22 +48,22 @@ public List getBuildsForApplication(UUID applicationGuid) { String uri = CloudControllerV3Endpoints.APPS + "/" + applicationGuid + "/builds" + CloudControllerV3Endpoints.QUERY_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; - return cc.list(uri, new ParameterizedTypeReference>() { - }) - .stream() - .map(V3BuildMapper::toCloudBuild) - .toList(); + return client.list(uri, new ParameterizedTypeReference>() { + }) + .stream() + .map(V3BuildMapper::toCloudBuild) + .toList(); } public List getBuildsForPackage(UUID packageGuid) { String uri = CloudControllerV3Endpoints.BUILDS + CloudControllerV3Endpoints.QUERY_PACKAGE_GUIDS + packageGuid + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; - return cc.list(uri, new ParameterizedTypeReference>() { - }) - .stream() - .map(V3BuildMapper::toCloudBuild) - .toList(); + return client.list(uri, new ParameterizedTypeReference>() { + }) + .stream() + .map(V3BuildMapper::toCloudBuild) + .toList(); } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientBuilder.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientBuilder.java index 2e33365552..1ea4e993e1 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientBuilder.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientBuilder.java @@ -34,6 +34,8 @@ public final class CloudControllerRestClientBuilder { + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private CloudControllerRestClientBuilder() { } @@ -50,7 +52,7 @@ public static RestClient buildRestClient(HttpClient httpClient, URL baseUrl, OAu .messageConverters(List.of(new ByteArrayHttpMessageConverter(), new StringHttpMessageConverter(), new FormHttpMessageConverter(), - new MappingJackson2HttpMessageConverter(new ObjectMapper()))) + new MappingJackson2HttpMessageConverter(OBJECT_MAPPER))) .requestInterceptor((request, body, execution) -> { String authorization = oAuthClient.getAuthorizationHeaderValue(); @@ -92,50 +94,71 @@ private static ExchangeFilterFunction authAndTagsFilter(OAuthClient oAuthClient, }; } - static HttpClient buildHttpClient(ClientConfigurationOptions configOptions) { + public static HttpClient buildHttpClient(ClientConfigurationOptions configOptions) { + HttpClient httpClient = createBaseHttpClient(configOptions); + httpClient = configureConnectionTimeout(httpClient, configOptions); + httpClient = configureThreadPool(httpClient, configOptions); + httpClient = configureResponseTimeout(httpClient, configOptions); + httpClient = configureSsl(httpClient, configOptions); + + return httpClient; + } + + private static HttpClient createBaseHttpClient(ClientConfigurationOptions configOptions) { int poolSize = configOptions.connectionPoolSize() .orElse(Constants.DEFAULT_CONNECTION_POOL_SIZE); + ConnectionProvider connectionProvider = ConnectionProvider.builder(Constants.CONNECTION_POOL_NAME) .maxConnections(poolSize) .build(); - HttpClient httpClient = HttpClient.create(connectionProvider) - .followRedirect(true) - .proxyWithSystemProperties(); + + return HttpClient.create(connectionProvider) + .followRedirect(true) + .proxyWithSystemProperties(); + } + + private static HttpClient configureConnectionTimeout(HttpClient httpClient, ClientConfigurationOptions configOptions) { int connectTimeoutMillis = (int) configOptions.connectionTimeout() .orElse(Constants.DEFAULT_CONNECT_TIMEOUT) .toMillis(); - httpClient = httpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeoutMillis); + return httpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeoutMillis); + } + + private static HttpClient configureThreadPool(HttpClient httpClient, ClientConfigurationOptions configOptions) { Optional threadPoolSize = configOptions.threadPoolSize(); - if (threadPoolSize.isPresent()) { - LoopResources loopResources = LoopResources.create(Constants.CONNECTION_POOL_NAME + Constants.LOOP_RESOURCES_SUFFIX, - threadPoolSize.get(), true); - httpClient = httpClient.runOn(loopResources); + if (threadPoolSize.isEmpty()) { + return httpClient; } + LoopResources loopResources = LoopResources.create(Constants.CONNECTION_POOL_NAME + Constants.LOOP_RESOURCES_SUFFIX, + threadPoolSize.get(), true); + + return httpClient.runOn(loopResources); + } + + private static HttpClient configureResponseTimeout(HttpClient httpClient, ClientConfigurationOptions configOptions) { Optional responseTimeout = configOptions.responseTimeout(); - if (responseTimeout.isPresent()) { - httpClient = httpClient.responseTimeout(responseTimeout.get()); + if (responseTimeout.isEmpty()) { + return httpClient; } + return httpClient.responseTimeout(responseTimeout.get()); + } + + private static HttpClient configureSsl(HttpClient httpClient, ClientConfigurationOptions configOptions) { Optional handshakeTimeout = configOptions.sslHandshakeTimeout(); - if (configOptions.trustSelfSignedCertificates()) { - httpClient = httpClient.secure(sslSpecification -> { - SslProvider.Builder sslBuilder = sslSpecification.sslContext(buildTrustAllSslContext()); - if (handshakeTimeout.isPresent()) { - sslBuilder.handshakeTimeout(handshakeTimeout.get()); - } - }); - } else if (handshakeTimeout.isPresent()) { - httpClient = httpClient.secure(sslSpecification -> { - SslProvider.Builder sslBuilder = sslSpecification.sslContext(buildDefaultClientSslContext()); - sslBuilder.handshakeTimeout(handshakeTimeout.get()); - }); - } else { - httpClient = httpClient.secure(); + boolean trustSelfSignedCertificates = configOptions.trustSelfSignedCertificates(); + + if (!trustSelfSignedCertificates && handshakeTimeout.isEmpty()) { + return httpClient.secure(); } - return httpClient; + SslContext sslContext = trustSelfSignedCertificates ? buildTrustAllSslContext() : buildDefaultClientSslContext(); + return httpClient.secure(sslSpecification -> { + SslProvider.Builder sslBuilder = sslSpecification.sslContext(sslContext); + handshakeTimeout.ifPresent(timeout -> sslBuilder.handshakeTimeout(timeout)); + }); } private static SslContext buildDefaultClientSslContext() { diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientFactory.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientFactory.java index 32c8c45c33..70053e1236 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientFactory.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientFactory.java @@ -1,11 +1,6 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest; -import java.net.MalformedURLException; -import java.net.URI; import java.net.URL; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.text.MessageFormat; import java.time.Duration; import java.util.Collections; import java.util.Map; @@ -13,15 +8,12 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; -import org.cloudfoundry.multiapps.controller.Constants; -import org.cloudfoundry.multiapps.controller.Messages; import org.cloudfoundry.multiapps.controller.client.facade.CloudCredentials; -import org.cloudfoundry.multiapps.controller.client.facade.CloudException; import org.cloudfoundry.multiapps.controller.client.facade.adapters.LogCacheClient; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; import org.cloudfoundry.multiapps.controller.client.facade.oauth2.OAuthClient; -import org.cloudfoundry.multiapps.controller.client.facade.util.JsonUtil; import org.cloudfoundry.multiapps.controller.client.facade.util.RestUtil; +import org.cloudfoundry.multiapps.controller.client.facade.util.UriUtil; import org.immutables.value.Value; import org.springframework.web.client.RestClient; import org.springframework.web.reactive.function.client.WebClient; @@ -66,19 +58,17 @@ public CloudControllerRestClient createClient(URL controllerUrl, CloudCredential oAuthClient.init(credentials); HttpConnectionPair httpConnectionPair = getCachedHttpConnectionPair(controllerUrl, requestTags); URL v3ApiUrl = httpConnectionPair.v3ApiUrl(); - URL baseUrl = extractBaseUrl(v3ApiUrl); + URL baseUrl = UriUtil.extractBaseUrl(v3ApiUrl); HttpClient httpClient = httpConnectionPair.httpClient(); - RestClient restClient = CloudControllerRestClientBuilder.buildRestClient(httpClient, baseUrl, oAuthClient, requestTags); - WebClient webClient = CloudControllerRestClientBuilder.buildWebClient(httpClient, baseUrl, oAuthClient, requestTags); - - CloudControllerV3Client client = new CloudControllerV3Client(restClient, webClient); + CloudControllerV3Client client = buildCloudControllerV3Client(httpClient, baseUrl, oAuthClient, requestTags); Function uploadRestClientFactory = uploadTimeout -> { ClientConfigurationOptions uploadOptions = new ClientConfigurationOptions(getConnectTimeout(), Optional.of(uploadTimeout), getSslHandshakeTimeout(), getConnectionPoolSize(), Optional.empty(), shouldTrustSelfSignedCertificates()); + return CloudControllerRestClientBuilder.build(baseUrl, oAuthClient, requestTags, uploadOptions); }; @@ -87,18 +77,24 @@ public CloudControllerRestClient createClient(URL controllerUrl, CloudCredential public CloudSpaceClient createSpaceClient(URL controllerUrl, OAuthClient oAuthClient, Map requestTags) { HttpConnectionPair httpConnectionPair = getCachedHttpConnectionPair(controllerUrl, requestTags); - URL baseUrl = extractBaseUrl(httpConnectionPair.v3ApiUrl()); + URL baseUrl = UriUtil.extractBaseUrl(httpConnectionPair.v3ApiUrl()); + + CloudControllerV3Client client = buildCloudControllerV3Client(httpConnectionPair.httpClient(), baseUrl, oAuthClient, requestTags); + + return new CloudSpaceClient(client); + } - RestClient restClient = CloudControllerRestClientBuilder.buildRestClient(httpConnectionPair.httpClient(), baseUrl, oAuthClient, - requestTags); - WebClient webClient = CloudControllerRestClientBuilder.buildWebClient(httpConnectionPair.httpClient(), baseUrl, oAuthClient, - requestTags); + private CloudControllerV3Client buildCloudControllerV3Client(HttpClient httpClient, URL baseUrl, OAuthClient oAuthClient, + Map requestTags) { + RestClient restClient = CloudControllerRestClientBuilder.buildRestClient(httpClient, baseUrl, oAuthClient, requestTags); + WebClient webClient = CloudControllerRestClientBuilder.buildWebClient(httpClient, baseUrl, oAuthClient, requestTags); - return new CloudSpaceClient(new CloudControllerV3Client(restClient, webClient)); + return new CloudControllerV3Client(restClient, webClient); } public LogCacheClient createLogCacheClient(URL controllerUrl, OAuthClient oAuthClient, Map requestTags) { HttpConnectionPair httpConnectionPair = getCachedHttpConnectionPair(controllerUrl, requestTags); + RestClient restClient = CloudControllerRestClientBuilder.buildRestClient(httpConnectionPair.httpClient(), httpConnectionPair.logCacheUrl(), oAuthClient, requestTags); @@ -106,10 +102,13 @@ public LogCacheClient createLogCacheClient(URL controllerUrl, OAuthClient oAuthC } private HttpConnectionPair getCachedHttpConnectionPair(URL controllerUrl, Map requestTags) { - return cachedHttpConnectionPair.computeIfAbsent(controllerUrl.getHost(), host -> { + return cachedHttpConnectionPair.computeIfAbsent(controllerUrl.getHost(), _ -> { HttpClient httpClient = CloudControllerRestClientBuilder.buildHttpClient(getClientConfigurationOptions()); - URL v3ApiUrl = resolveV3ApiUrl(controllerUrl, requestTags); - URL logCacheUrl = resolveLogCacheUrl(controllerUrl, requestTags); + Map rootDocumentLinks = CloudControllerRootDocumentClient.getRootDocumentLinks(controllerUrl, requestTags); + + URL v3ApiUrl = UriUtil.resolveV3ApiUrl(controllerUrl, rootDocumentLinks); + URL logCacheUrl = UriUtil.resolveLogCacheUrl(controllerUrl, rootDocumentLinks); + return new HttpConnectionPair(httpClient, v3ApiUrl, logCacheUrl); }); } @@ -120,93 +119,6 @@ private ClientConfigurationOptions getClientConfigurationOptions() { shouldTrustSelfSignedCertificates()); } - private URL extractBaseUrl(URL url) { - try { - int port = url.getPort(); - String origin = url.getProtocol() + Constants.PROTOCOL_SEPARATOR + url.getHost() + (port == Constants.UNDEFINED_PORT - ? Constants.EMPTY_STRING - : Constants.COLON + port); - return URI.create(origin) - .toURL(); - } catch (MalformedURLException e) { - throw new IllegalArgumentException(MessageFormat.format(Messages.COULD_NOT_DETERMINE_API_ORIGIN_FROM_0, url), e); - } - } - - private URL resolveV3ApiUrl(URL controllerUrl, Map requestTags) { - try { - Map rootDocumentLinks = getCloudControllerRootDocumentLinks(controllerUrl, requestTags); - Map cloudControllerUrlV3 = (Map) rootDocumentLinks.get( - Constants.CLOUD_CONTROLLER_CF_ROOT_DOCUMENT_NAME); - return URI.create((String) cloudControllerUrlV3.get(Constants.HREF)) - .toURL(); - } catch (Exception e) { - return fallbackUrl(controllerUrl, Constants.CF_API_V3); - } - } - - private URL resolveLogCacheUrl(URL controllerUrl, Map requestTags) { - try { - Map rootDocumentLinks = getCloudControllerRootDocumentLinks(controllerUrl, requestTags); - Map logCacheUrl = (Map) rootDocumentLinks.get(Constants.LOG_CACHE_CF_ROOT_DOCUMENT_NAME); - return URI.create((String) logCacheUrl.get(Constants.HREF)) - .toURL(); - } catch (Exception e) { - String host = controllerUrl.getHost(); - String logCacheHost = host.startsWith(Constants.API_HOST_PREFIX) ? Constants.LOG_CACHE_PREFIX + host.substring( - Constants.API_HOST_PREFIX.length()) : Constants.LOG_CACHE_PREFIX + host; - try { - return URI.create(controllerUrl.getProtocol() + Constants.PROTOCOL_SEPARATOR + logCacheHost) - .toURL(); - } catch (MalformedURLException me) { - throw new CloudException(MessageFormat.format(Messages.COULD_NOT_RESOLVE_LOG_CACHE_URL_FROM_0, controllerUrl), me); - } - } - } - - @SuppressWarnings("unchecked") - private Map getCloudControllerRootDocumentLinks(URL controllerUrl, Map requestTags) { - try { - HttpRequest.Builder requestBuilder = HttpRequest.newBuilder() - .GET() - .uri(controllerUrl.toURI()) - .timeout(Constants.DEFAULT_CONNECT_TIMEOUT); - requestTags.forEach(requestBuilder::header); - HttpResponse response = getSimpleHttpClient().send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString()); - - if (response.statusCode() / 100 != 2) { - throw new CloudException( - MessageFormat.format(Messages.CF_ROOT_DOCUMENT_REQUEST_TO_0_RETURNED_1, controllerUrl, response.statusCode())); - } - - return (Map) JsonUtil.convertJsonToMap(response.body()) - .get(Constants.ROOT_DOCUMENT_LINKS_LIST); - } catch (InterruptedException e) { - Thread.currentThread() - .interrupt(); - throw new CloudException(MessageFormat.format(Messages.INTERRUPTED_WHILE_CALLING_THE_CF_ROOT_URL_AT_0, controllerUrl), e); - } catch (Exception e) { - throw new CloudException(MessageFormat.format(Messages.FAILED_TO_CALL_THE_CF_ROOT_AT_0_WITH_1, controllerUrl, e.getMessage()), - e); - } - } - - private static java.net.http.HttpClient getSimpleHttpClient() { - return java.net.http.HttpClient.newBuilder() - .connectTimeout(Constants.DEFAULT_CONNECT_TIMEOUT) - .build(); - } - - private URL fallbackUrl(URL controllerUrl, String pathSuffix) { - try { - return URI.create(controllerUrl.toString() + pathSuffix) - .toURL(); - } catch (MalformedURLException e) { - throw new IllegalArgumentException( - MessageFormat.format(Messages.COULD_NOT_RESOLVE_URL_FROM_0_1, controllerUrl, pathSuffix), e); - } - } - private OAuthClient createOAuthClient(URL controllerUrl) { return restUtil.createOAuthClientByControllerUrl(controllerUrl, shouldTrustSelfSignedCertificates()); } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientV3Impl.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientV3Impl.java index 031d2c4960..5d666f9c78 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientV3Impl.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientV3Impl.java @@ -43,7 +43,7 @@ public class CloudControllerRestClientV3Impl implements CloudControllerRestClient { - private final CloudSpace target; + private final CloudSpace targetSpace; private final ApplicationsV3Operations applicationsOperations; private final BuildsV3Operations buildsOperations; @@ -62,32 +62,33 @@ public class CloudControllerRestClientV3Impl implements CloudControllerRestClien private final StacksV3Operations stacksOperations; private final TasksV3Operations tasksOperations; - public CloudControllerRestClientV3Impl(CloudSpace target, CloudControllerV3Client cloudControllerClient, + public CloudControllerRestClientV3Impl(CloudSpace targetSpace, CloudControllerV3Client cloudControllerClient, Function uploadRestClientFactory) { - this.target = target; - this.applicationsOperations = new ApplicationsV3Operations(cloudControllerClient, target); + this.targetSpace = targetSpace; + this.applicationsOperations = new ApplicationsV3Operations(cloudControllerClient, targetSpace); this.buildsOperations = new BuildsV3Operations(cloudControllerClient); - this.domainsOperations = new DomainsV3Operations(cloudControllerClient, target); - this.eventsOperations = new EventsV3Operations(cloudControllerClient, target); + this.domainsOperations = new DomainsV3Operations(cloudControllerClient, targetSpace); + this.eventsOperations = new EventsV3Operations(cloudControllerClient, targetSpace); this.jobsOperations = new JobsV3Operations(cloudControllerClient); - this.packagesOperations = new PackagesV3Operations(cloudControllerClient, target, uploadRestClientFactory); - this.processesOperations = new ProcessesV3Operations(cloudControllerClient, target); + this.packagesOperations = new PackagesV3Operations(cloudControllerClient, targetSpace, uploadRestClientFactory); + this.processesOperations = new ProcessesV3Operations(cloudControllerClient, targetSpace); this.rolesOperations = new RolesV3Operations(cloudControllerClient); - this.routesOperations = new RoutesV3Operations(cloudControllerClient, target); - this.serviceBindingsOperations = new ServiceBindingsV3Operations(cloudControllerClient, target); - this.serviceBrokersOperations = new ServiceBrokersV3Operations(cloudControllerClient, target); - this.serviceInstancesOperations = new ServiceInstancesV3Operations(cloudControllerClient, target); + this.routesOperations = new RoutesV3Operations(cloudControllerClient, targetSpace); + this.serviceBindingsOperations = new ServiceBindingsV3Operations(cloudControllerClient, targetSpace); + this.serviceBrokersOperations = new ServiceBrokersV3Operations(cloudControllerClient, targetSpace); + this.serviceInstancesOperations = new ServiceInstancesV3Operations(cloudControllerClient, targetSpace); this.serviceKeysOperations = new ServiceKeysV3Operations(cloudControllerClient); - this.serviceOfferingsOperations = new ServiceOfferingsV3Operations(cloudControllerClient, target); + this.serviceOfferingsOperations = new ServiceOfferingsV3Operations(cloudControllerClient, targetSpace); this.stacksOperations = new StacksV3Operations(cloudControllerClient); - this.tasksOperations = new TasksV3Operations(cloudControllerClient, target); + this.tasksOperations = new TasksV3Operations(cloudControllerClient, targetSpace); } @Override public CloudSpace getTarget() { - return target; + return targetSpace; } + @Override public void addDomain(String domainName) { domainsOperations.addDomain(domainName); diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRootDocumentClient.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRootDocumentClient.java new file mode 100644 index 0000000000..5bcd1a6bf4 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRootDocumentClient.java @@ -0,0 +1,72 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.net.URISyntaxException; +import java.net.URL; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.text.MessageFormat; +import java.util.Map; + +import org.cloudfoundry.multiapps.common.util.MiscUtil; +import org.cloudfoundry.multiapps.controller.Constants; +import org.cloudfoundry.multiapps.controller.Messages; +import org.cloudfoundry.multiapps.controller.client.facade.CloudException; +import org.cloudfoundry.multiapps.controller.client.facade.util.JsonUtil; + +public class CloudControllerRootDocumentClient { + + private CloudControllerRootDocumentClient() { + } + + public static Map getRootDocumentLinks(URL controllerUrl, Map requestTags) { + try (HttpClient httpClient = createHttpClient()) { + HttpRequest request = buildRootDocumentRequest(controllerUrl, requestTags); + HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + + return extractRootDocumentLinks(controllerUrl, response); + } catch (InterruptedException e) { + Thread.currentThread() + .interrupt(); + + throw new CloudException(MessageFormat.format(Messages.INTERRUPTED_WHILE_CALLING_THE_CF_ROOT_URL_AT_0, controllerUrl), e); + } catch (Exception e) { + throw new CloudException(MessageFormat.format(Messages.FAILED_TO_CALL_THE_CF_ROOT_AT_0_WITH_1, controllerUrl, e.getMessage()), + e); + } + } + + private static HttpRequest buildRootDocumentRequest(URL controllerUrl, Map requestTags) throws URISyntaxException { + HttpRequest.Builder requestBuilder = HttpRequest.newBuilder() + .GET() + .uri(controllerUrl.toURI()) + .timeout(Constants.DEFAULT_CONNECT_TIMEOUT); + + requestTags.forEach(requestBuilder::header); + + return requestBuilder.build(); + } + + private static Map extractRootDocumentLinks(URL controllerUrl, HttpResponse response) { + if (!isSuccessful(response.statusCode())) { + throw new CloudException( + MessageFormat.format(Messages.CF_ROOT_DOCUMENT_REQUEST_TO_0_RETURNED_1, controllerUrl, response.statusCode())); + } + + return MiscUtil.cast(JsonUtil.convertJsonToMap(response.body()) + .get(Constants.ROOT_DOCUMENT_LINKS_LIST)); + } + + private static boolean isSuccessful(int statusCode) { + return statusCode >= 200 && statusCode < 300; + } + + private static HttpClient createHttpClient() { + return HttpClient.newBuilder() + .connectTimeout(Constants.DEFAULT_CONNECT_TIMEOUT) + .build(); + } + +} + + diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Client.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Client.java index 84bf32cdd8..3a2df48acd 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Client.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Client.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.Optional; +import org.cloudfoundry.multiapps.common.util.MiscUtil; import org.cloudfoundry.multiapps.controller.Constants; import org.cloudfoundry.multiapps.controller.Messages; import org.cloudfoundry.multiapps.controller.client.facade.CloudException; @@ -44,6 +45,13 @@ public T get(String uri, Class responseType) { .body(responseType); } + private T get(String uri, ParameterizedTypeReference responseType) { + return restClient.get() + .uri(uri) + .retrieve() + .body(responseType); + } + public Optional getOptional(String uri, Class responseType) { try { return Optional.ofNullable(get(uri, responseType)); @@ -59,13 +67,10 @@ public List list(String firstPageUri, ParameterizedTypeReference(); } - + String firstPageRelativeUri = toRelativeIfAbsolute(firstPageUri); - V3ListResponse firstPage = restClient.get() - .uri(firstPageRelativeUri) - .retrieve() - .body(pageType); + V3ListResponse firstPage = get(firstPageRelativeUri, pageType); if (firstPage == null) { return new ArrayList<>(); @@ -89,10 +94,7 @@ private List listSequentially(V3ListResponse firstPage, ParameterizedT String nextUri = toRelativeIfAbsolute(firstPage.nextPageHref()); while (nextUri != null) { - V3ListResponse currentPage = restClient.get() - .uri(nextUri) - .retrieve() - .body(pageType); + V3ListResponse currentPage = get(nextUri, pageType); if (currentPage == null) { break; @@ -107,7 +109,7 @@ private List listSequentially(V3ListResponse firstPage, ParameterizedT private List fetchRemainingPagesConcurrently(String firstPageRelativeUri, int totalPages, ParameterizedTypeReference> pageType) { - return Flux.range(2, totalPages - 1) + return Flux.range(Constants.SECOND_PAGE, totalPages - 1) .flatMapSequential(page -> webClient.get() .uri(pageUri(firstPageRelativeUri, page)) .retrieve() @@ -161,48 +163,56 @@ public V3Job waitForAsyncJob(String jobGuid, Duration timeout) { return asyncJob; } - long elapsedNanos = System.nanoTime() - startTimeNanos; - if (elapsedNanos >= timeoutNanos) { - throw new CloudOperationException(HttpStatus.GATEWAY_TIMEOUT, Messages.JOB_TIMEOUT, - MessageFormat.format(Messages.JOB_0_DID_NOT_COMPLETE_WITHIN_1, jobGuid, timeout)); - } - + long elapsedNanos = determineElapsedTime(startTimeNanos, timeout, jobGuid); long remainingNanos = timeoutNanos - elapsedNanos; - Duration sleepDuration = interval.toNanos() > remainingNanos ? Duration.ofNanos(remainingNanos) : interval; + Duration sleepDuration = interval.toNanos() > remainingNanos ? Duration.ofNanos(remainingNanos) : interval; sleep(sleepDuration); + interval = nextInterval(interval); } } private static Duration nextInterval(Duration current) { - Duration doubled = current.multipliedBy(2); - return doubled.compareTo(Constants.JOB_POLL_MAX_INTERVAL) > 0 ? Constants.JOB_POLL_MAX_INTERVAL : doubled; + Duration increasedInterval = current.multipliedBy(Constants.JOB_POLL_INTERVAL_MULTIPLIER); + + return increasedInterval.compareTo(Constants.JOB_POLL_MAX_INTERVAL) > 0 ? Constants.JOB_POLL_MAX_INTERVAL : increasedInterval; } private static void sleep(Duration interval) { try { - Thread.sleep(interval.toMillis()); - } catch (InterruptedException e) { - Thread.currentThread() - .interrupt(); + MiscUtil.sleep(interval.toMillis()); + } catch (IllegalStateException e) { throw new CloudException(MessageFormat.format(Messages.INTERRUPTED_WHILE_POLLING_ASYNC_JOB_0, e.getMessage()), e); } } private static CloudOperationException jobFailed(V3Job job) { - String detail = "Job failed"; + String detail = Messages.JOB_FAILED_WITHOUT_DETAILS; if (job.errors() != null && !job.errors() .isEmpty()) { detail = job.errors() .stream() - .map(V3Job.V3Error::detail) - .reduce((a, b) -> a + "\n" + b) + .map(CloudControllerV3Client::extractErrorMessage) + .reduce((combinedMessages, nextMessage) -> combinedMessages + Constants.NEW_LINE + nextMessage) .orElse(detail); } - return new CloudOperationException(HttpStatus.UNPROCESSABLE_ENTITY, Messages.JOB_FAILED, detail); + String message = MessageFormat.format(Messages.JOB_0_WITH_OPERATION_1_FAILED_WITH_2, job.guid(), job.operation(), detail); + return new CloudOperationException(HttpStatus.UNPROCESSABLE_ENTITY, Messages.JOB_FAILED, message); + } + + private static String extractErrorMessage(V3Job.V3Error error) { + if (error.detail() != null) { + return error.detail(); + } + + if (error.title() != null) { + return error.title(); + } + + return Messages.JOB_FAILED_WITHOUT_DETAILS; } private static String extractJobGuid(String location) { @@ -235,6 +245,18 @@ private String toRelativeIfAbsolute(String href) { return href; } + private long determineElapsedTime(long startTimeNanos, Duration timeout, String jobGuid) { + long elapsedNanos = System.nanoTime() - startTimeNanos; + long timeoutNanos = timeout.toNanos(); + + if (elapsedNanos >= timeoutNanos) { + throw new CloudOperationException(HttpStatus.GATEWAY_TIMEOUT, Messages.JOB_TIMEOUT, + MessageFormat.format(Messages.JOB_0_DID_NOT_COMPLETE_WITHIN_1, jobGuid, timeout)); + } + + return elapsedNanos; + } + public RestClient getRestClient() { return restClient; } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Endpoints.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Endpoints.java index 58211f4cee..911b21aefb 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Endpoints.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Endpoints.java @@ -1,6 +1,6 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest; -public final class CloudControllerV3Endpoints { +public class CloudControllerV3Endpoints { public static final int DEFAULT_PAGE_SIZE = 5000; diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudSpaceClient.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudSpaceClient.java index 0644d62545..f1cc684535 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudSpaceClient.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudSpaceClient.java @@ -21,52 +21,63 @@ public class CloudSpaceClient { private static final List CHARS_TO_ENCODE = List.of(","); - private final CloudControllerV3Client cc; + private final CloudControllerV3Client client; - public CloudSpaceClient(CloudControllerV3Client cc) { - this.cc = cc; + public CloudSpaceClient(CloudControllerV3Client client) { + this.client = client; } public CloudSpace getSpace(UUID spaceGuid) { - V3Space space = cc.getOptional(CloudControllerV3Endpoints.SPACES + "/" + spaceGuid, V3Space.class) - .orElseThrow(() -> new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, - MessageFormat.format(Messages.SPACE_WITH_GUID_0_NOT_FOUND, - spaceGuid))); + V3Space space = client.getOptional(CloudControllerV3Endpoints.SPACES + "/" + spaceGuid, V3Space.class) + .orElseThrow(() -> new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + MessageFormat.format(Messages.SPACE_WITH_GUID_0_NOT_FOUND, + spaceGuid))); String orgGuid = space.organizationGuid(); - V3Organization org = cc.getOptional(CloudControllerV3Endpoints.ORGANIZATIONS + "/" + orgGuid, V3Organization.class) - .orElseThrow(() -> new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, - MessageFormat.format( - Messages.ORGANIZATION_WITH_GUID_0_NOT_FOUND, orgGuid))); + V3Organization org = client.getOptional(CloudControllerV3Endpoints.ORGANIZATIONS + "/" + orgGuid, V3Organization.class) + .orElseThrow(() -> new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + MessageFormat.format( + Messages.ORGANIZATION_WITH_GUID_0_NOT_FOUND, + orgGuid))); return mapToCloudSpace(space, org); } public CloudSpace getSpace(String organizationName, String spaceName) { - List orgs = cc.list(CloudControllerV3Endpoints.ORGANIZATIONS + CloudControllerV3Endpoints.QUERY_NAMES - + encodeAsQueryParam(organizationName), - new ParameterizedTypeReference>() { - }); + V3Organization organization = findOrganizationByName(organizationName); + V3Space space = findSpaceByName(spaceName, organization); - if (orgs.isEmpty()) { + return mapToCloudSpace(space, organization); + } + + private V3Organization findOrganizationByName(String organizationName) { + List organizations = client.list(CloudControllerV3Endpoints.ORGANIZATIONS + CloudControllerV3Endpoints.QUERY_NAMES + + encodeAsQueryParam(organizationName), + new ParameterizedTypeReference>() { + }); + + if (organizations.isEmpty()) { throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, MessageFormat.format(Messages.ORGANISATION_0_NOT_FOUND, organizationName)); } - V3Organization org = orgs.get(0); + return organizations.getFirst(); + } - List spaces = cc.list(CloudControllerV3Endpoints.SPACES + CloudControllerV3Endpoints.QUERY_ORGANIZATION_GUIDS + org.guid() - + CloudControllerV3Endpoints.AMPERSAND_NAMES + encodeAsQueryParam(spaceName), - new ParameterizedTypeReference>() { - }); + private V3Space findSpaceByName(String spaceName, V3Organization org) { + List spaces = client.list( + CloudControllerV3Endpoints.SPACES + CloudControllerV3Endpoints.QUERY_ORGANIZATION_GUIDS + org.guid() + + CloudControllerV3Endpoints.AMPERSAND_NAMES + encodeAsQueryParam(spaceName), + new ParameterizedTypeReference>() { + }); if (spaces.isEmpty()) { throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, MessageFormat.format(Messages.SPACE_0_NOT_FOUND_IN_ORGANIZATION_1, spaceName, - organizationName)); + org.name())); } - return mapToCloudSpace(spaces.get(0), org); + return spaces.getFirst(); } private String encodeAsQueryParam(String param) { diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3Operations.java index 307519f105..448d191bdb 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3Operations.java @@ -24,12 +24,12 @@ public class DomainsV3Operations { private static final ParameterizedTypeReference> DOMAIN_LIST_TYPE = new ParameterizedTypeReference<>() { }; - private final CloudControllerV3Client cc; - private final CloudSpace target; + private final CloudControllerV3Client client; + private final CloudSpace targetSpace; - public DomainsV3Operations(CloudControllerV3Client cc, CloudSpace target) { - this.cc = cc; - this.target = target; + public DomainsV3Operations(CloudControllerV3Client client, CloudSpace targetSpace) { + this.client = client; + this.targetSpace = targetSpace; } public void addDomain(String domainName) { @@ -49,8 +49,15 @@ public void deleteDomain(String domainName) { } public CloudDomain getDefaultDomain() { - V3Domain domain = cc.get(CloudControllerV3Endpoints.ORGANIZATIONS + "/" + getTargetOrganizationGuid() + "/domains/default", - V3Domain.class); + UUID organizationGuid = getTargetOrganizationGuid(); + V3Domain domain = client.get(CloudControllerV3Endpoints.ORGANIZATIONS + "/" + organizationGuid + "/domains/default", + V3Domain.class); + + if (domain == null) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + MessageFormat.format(Messages.DOMAIN_WITH_GUID_0_NOT_FOUND, organizationGuid)); + } + return V3DomainMapper.toCloudDomain(domain); } @@ -79,15 +86,15 @@ public List getDomainsForOrganization() { String uri = CloudControllerV3Endpoints.ORGANIZATIONS + "/" + getTargetOrganizationGuid() + "/domains" + CloudControllerV3Endpoints.QUERY_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; - return cc.list(uri, DOMAIN_LIST_TYPE) - .stream() - .map(V3DomainMapper::toCloudDomain) - .toList(); + return client.list(uri, DOMAIN_LIST_TYPE) + .stream() + .map(V3DomainMapper::toCloudDomain) + .toList(); } private List getAllDomains() { - return cc.list(CloudControllerV3Endpoints.DOMAINS + CloudControllerV3Endpoints.QUERY_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE, DOMAIN_LIST_TYPE); + return client.list(CloudControllerV3Endpoints.DOMAINS + CloudControllerV3Endpoints.QUERY_PER_PAGE + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE, DOMAIN_LIST_TYPE); } private CloudDomain findDomainByName(String name, boolean required) { @@ -105,11 +112,11 @@ private CloudDomain findDomainByName(String name) { String uri = CloudControllerV3Endpoints.DOMAINS + CloudControllerV3Endpoints.QUERY_NAMES + name + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; - return cc.list(uri, DOMAIN_LIST_TYPE) - .stream() - .findFirst() - .map(V3DomainMapper::toCloudDomain) - .orElse(null); + return client.list(uri, DOMAIN_LIST_TYPE) + .stream() + .findFirst() + .map(V3DomainMapper::toCloudDomain) + .orElse(null); } private void doCreateDomain(String name) { @@ -119,26 +126,29 @@ private void doCreateDomain(String name) { MessageFormat.format(Messages.CANNOT_CREATE_DOMAIN_0_WITHOUT_ORGANIZATION, name)); } - cc.getRestClient() - .post() - .uri(CloudControllerV3Endpoints.DOMAINS) - .body(Map.of("name", name, "relationships", - Map.of("organization", Map.of("data", Map.of("guid", getTargetOrganizationGuid().toString()))))) - .retrieve() - .toBodilessEntity(); + client.getRestClient() + .post() + .uri(CloudControllerV3Endpoints.DOMAINS) + .body(Map.of("name", name, "relationships", organizationRelationship(organizationGuid))) + .retrieve() + .toBodilessEntity(); + } + + private Map organizationRelationship(UUID organizationGuid) { + return Map.of("organization", Map.of("data", Map.of("guid", organizationGuid.toString()))); } private void doDeleteDomain(UUID guid) { - ResponseEntity response = cc.getRestClient() - .delete() - .uri(CloudControllerV3Endpoints.DOMAIN_BY_GUID, guid.toString()) - .retrieve() - .toEntity(Void.class); - cc.followAsyncJob(response, Constants.DELETE_JOB_TIMEOUT); + ResponseEntity response = client.getRestClient() + .delete() + .uri(CloudControllerV3Endpoints.DOMAIN_BY_GUID, guid.toString()) + .retrieve() + .toEntity(Void.class); + client.followAsyncJob(response, Constants.DELETE_JOB_TIMEOUT); } private UUID getTargetOrganizationGuid() { - return getGuid(target.getOrganization()); + return getGuid(targetSpace.getOrganization()); } private UUID getGuid(CloudEntity entity) { @@ -151,7 +161,7 @@ private UUID getGuid(CloudEntity entity) { } private void assertSpaceProvided(String operation) { - Assert.notNull(target, "Unable to " + operation + " without specifying organization and space to use."); + Assert.notNull(targetSpace, "Unable to " + operation + " without specifying organization and space to use."); } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/EventsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/EventsV3Operations.java index e348588f23..abb177ef21 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/EventsV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/EventsV3Operations.java @@ -22,30 +22,30 @@ public class EventsV3Operations { private static final ParameterizedTypeReference> APPLICATION_PAGE = new ParameterizedTypeReference<>() { }; - private final CloudControllerV3Client cc; - private final CloudSpace target; + private final CloudControllerV3Client client; + private final CloudSpace targetSpace; - public EventsV3Operations(CloudControllerV3Client cc, CloudSpace target) { - this.cc = cc; - this.target = target; + public EventsV3Operations(CloudControllerV3Client client, CloudSpace targetSpace) { + this.client = client; + this.targetSpace = targetSpace; } public List getEvents() { - return cc.list(CloudControllerV3Endpoints.AUDIT_EVENTS + CloudControllerV3Endpoints.QUERY_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE, EVENT_PAGE) - .stream() - .map(V3AuditEventMapper::toCloudEvent) - .toList(); + return client.list(CloudControllerV3Endpoints.AUDIT_EVENTS + CloudControllerV3Endpoints.QUERY_PER_PAGE + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE, EVENT_PAGE) + .stream() + .map(V3AuditEventMapper::toCloudEvent) + .toList(); } public List getEventsByTarget(UUID uuid) { String query = CloudControllerV3Endpoints.AUDIT_EVENTS + CloudControllerV3Endpoints.QUERY_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE + CloudControllerV3Endpoints.AMPERSAND_TARGET_GUIDS + uuid; - return cc.list(query, EVENT_PAGE) - .stream() - .map(V3AuditEventMapper::toCloudEvent) - .toList(); + return client.list(query, EVENT_PAGE) + .stream() + .map(V3AuditEventMapper::toCloudEvent) + .toList(); } public List getApplicationEvents(String applicationName) { @@ -55,25 +55,30 @@ public List getApplicationEvents(String applicationName) { } private UUID getRequiredApplicationGuid(String applicationName) { + List apps = client.list(buildApplicationQuery(applicationName), APPLICATION_PAGE); + + if (apps.isEmpty()) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + MessageFormat.format(Messages.APPLICATION_0_NOT_FOUND, applicationName)); + } + + return UUID.fromString(apps.getFirst() + .guid()); + } + + private String buildApplicationQuery(String applicationName) { StringBuilder query = new StringBuilder(CloudControllerV3Endpoints.APPS + CloudControllerV3Endpoints.QUERY_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE); - if (target != null && target.getGuid() != null) { + if (targetSpace != null && targetSpace.getGuid() != null) { query.append(CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) - .append(target.getGuid()); + .append(targetSpace.getGuid()); } query.append(CloudControllerV3Endpoints.AMPERSAND_NAMES) .append(applicationName); - List apps = cc.list(query.toString(), APPLICATION_PAGE); - - if (apps.isEmpty()) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, - MessageFormat.format(Messages.APPLICATION_0_NOT_FOUND, applicationName)); - } - return UUID.fromString(apps.getFirst() - .guid()); + return query.toString(); } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/JobsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/JobsV3Operations.java index 0bb253ebe6..048b58152f 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/JobsV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/JobsV3Operations.java @@ -1,19 +1,29 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest; +import java.text.MessageFormat; + +import org.cloudfoundry.multiapps.controller.Messages; +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudAsyncJob; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Job; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3JobMapper; +import org.springframework.http.HttpStatus; public class JobsV3Operations { - private final CloudControllerV3Client cc; + private final CloudControllerV3Client client; - public JobsV3Operations(CloudControllerV3Client cc) { - this.cc = cc; + public JobsV3Operations(CloudControllerV3Client client) { + this.client = client; } public CloudAsyncJob getAsyncJob(String jobId) { - V3Job job = cc.get(CloudControllerV3Endpoints.JOBS + "/" + jobId, V3Job.class); + V3Job job = client.get(CloudControllerV3Endpoints.JOBS + "/" + jobId, V3Job.class); + + if (job == null) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + MessageFormat.format(Messages.JOB_WITH_GUID_0_NOT_FOUND, jobId)); + } return V3JobMapper.toCloudAsyncJob(job); } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3Operations.java index 5da52aa034..a9da042d7d 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3Operations.java @@ -7,10 +7,14 @@ import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; import java.util.function.Function; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import org.cloudfoundry.multiapps.common.util.MiscUtil; import org.cloudfoundry.multiapps.controller.Constants; import org.cloudfoundry.multiapps.controller.Messages; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; @@ -42,24 +46,27 @@ public class PackagesV3Operations { private static final ParameterizedTypeReference> PACKAGE_PAGE = new ParameterizedTypeReference<>() { }; - private final CloudControllerV3Client cc; - private final CloudSpace target; + private final CloudControllerV3Client client; + + private final CloudSpace targetSpace; private final Function uploadRestClientFactory; - public PackagesV3Operations(CloudControllerV3Client cc, CloudSpace target) { - this(cc, target, null); + private final ExecutorService uploadMonitorExecutor = createUploadMonitorExecutor(); + + public PackagesV3Operations(CloudControllerV3Client client, CloudSpace targetSpace) { + this(client, targetSpace, null); } - public PackagesV3Operations(CloudControllerV3Client cc, CloudSpace target, - Function uploadRestClientFactory) { - this.cc = cc; - this.target = target; + public PackagesV3Operations(CloudControllerV3Client client, CloudSpace targetSpace, + Function uploadRestClientFactory) { + this.client = client; + this.targetSpace = targetSpace; this.uploadRestClientFactory = uploadRestClientFactory; } public CloudPackage getPackage(UUID packageGuid) { - V3Package packageResource = cc.get(CloudControllerV3Endpoints.PACKAGES + "/" + packageGuid, V3Package.class); + V3Package packageResource = client.get(CloudControllerV3Endpoints.PACKAGES + "/" + packageGuid, V3Package.class); if (packageResource == null) { throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, @@ -70,11 +77,11 @@ public CloudPackage getPackage(UUID packageGuid) { } public List getPackagesForApplication(UUID applicationGuid) { - return cc.list(CloudControllerV3Endpoints.APPS + "/" + applicationGuid + "/packages" + CloudControllerV3Endpoints.QUERY_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE, PACKAGE_PAGE) - .stream() - .map(V3PackageMapper::toCloudPackage) - .toList(); + return client.list(CloudControllerV3Endpoints.APPS + "/" + applicationGuid + "/packages" + CloudControllerV3Endpoints.QUERY_PER_PAGE + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE, PACKAGE_PAGE) + .stream() + .map(V3PackageMapper::toCloudPackage) + .toList(); } public CloudPackage createDockerPackage(UUID applicationGuid, DockerInfo dockerInfo) { @@ -82,23 +89,18 @@ public CloudPackage createDockerPackage(UUID applicationGuid, DockerInfo dockerI data.put("image", dockerInfo.getImage()); DockerCredentials credentials = dockerInfo.getCredentials(); - if (credentials != null) { - if (credentials.getUsername() != null) { - data.put("username", credentials.getUsername()); - } - - if (credentials.getPassword() != null) { - data.put("password", credentials.getPassword()); - } + if (credentials != null && credentials.getUsername() != null && credentials.getPassword() != null) { + data.put("username", credentials.getUsername()); + data.put("password", credentials.getPassword()); } - V3Package created = cc.getRestClient() - .post() - .uri(CloudControllerV3Endpoints.PACKAGES) - .body(Map.of("type", "docker", "data", data, "relationships", - applicationRelationship(applicationGuid))) - .retrieve() - .body(V3Package.class); + V3Package created = client.getRestClient() + .post() + .uri(CloudControllerV3Endpoints.PACKAGES) + .body(Map.of("type", "docker", "data", data, "relationships", + applicationRelationship(applicationGuid))) + .retrieve() + .body(V3Package.class); return getPackage(UUID.fromString(created.guid())); } @@ -151,36 +153,26 @@ private RestClient resolveUploadClient(Duration uploadTimeout) { return uploadRestClientFactory.apply(uploadTimeout); } - return cc.getRestClient(); + return client.getRestClient(); } private CloudPackage createBitsPackage(UUID applicationGuid) { - V3Package created = cc.getRestClient() - .post() - .uri(CloudControllerV3Endpoints.PACKAGES) - .body(Map.of("type", "bits", "relationships", applicationRelationship(applicationGuid))) - .retrieve() - .body(V3Package.class); + V3Package created = client.getRestClient() + .post() + .uri(CloudControllerV3Endpoints.PACKAGES) + .body(Map.of("type", "bits", "relationships", applicationRelationship(applicationGuid))) + .retrieve() + .body(V3Package.class); return getPackage(UUID.fromString(created.guid())); } private static Map applicationRelationship(UUID applicationGuid) { - return Map.of("app", java.util.Map.of("data", java.util.Map.of("guid", applicationGuid.toString()))); + return Map.of("app", Map.of("data", Map.of("guid", applicationGuid.toString()))); } private UUID getRequiredApplicationGuid(String applicationName) { - StringBuilder query = new StringBuilder(CloudControllerV3Endpoints.APPS + CloudControllerV3Endpoints.QUERY_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE); - - if (target != null && target.getGuid() != null) { - query.append(CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) - .append(target.getGuid()); - } - - query.append(CloudControllerV3Endpoints.AMPERSAND_NAMES) - .append(applicationName); - List apps = cc.list(query.toString(), new ParameterizedTypeReference>() { + List apps = client.list(buildApplicationQuery(applicationName), new ParameterizedTypeReference>() { }); if (apps.isEmpty() || apps.getFirst() @@ -193,9 +185,23 @@ private UUID getRequiredApplicationGuid(String applicationName) { .guid()); } + private String buildApplicationQuery(String applicationName) { + StringBuilder query = new StringBuilder(CloudControllerV3Endpoints.APPS + CloudControllerV3Endpoints.QUERY_PER_PAGE + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE); + + if (targetSpace != null && targetSpace.getGuid() != null) { + query.append(CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) + .append(targetSpace.getGuid()); + } + + query.append(CloudControllerV3Endpoints.AMPERSAND_NAMES) + .append(applicationName); + + return query.toString(); + } + private void processAsyncUploadInBackground(CloudPackage cloudPackage, UploadStatusCallback callback) { - String threadName = String.format("App upload monitor: %s", cloudPackage.getGuid()); - new Thread(() -> processAsyncUpload(cloudPackage, callback), threadName).start(); + uploadMonitorExecutor.execute(() -> processAsyncUpload(cloudPackage, callback)); } private void processAsyncUpload(CloudPackage cloudPackage, UploadStatusCallback callback) { @@ -215,10 +221,8 @@ private void processAsyncUpload(CloudPackage cloudPackage, UploadStatusCallback } try { - Thread.sleep(Constants.PACKAGE_UPLOAD_JOB_POLLING_PERIOD); - } catch (InterruptedException e) { - Thread.currentThread() - .interrupt(); + MiscUtil.sleep(Constants.PACKAGE_UPLOAD_JOB_POLLING_PERIOD); + } catch (IllegalStateException e) { return; } } @@ -232,6 +236,14 @@ private boolean hasUploadFailed(Status status) { return status == Status.EXPIRED || status == Status.FAILED; } + private ExecutorService createUploadMonitorExecutor() { + ThreadFactory threadFactory = Thread.ofVirtual() + .name(Constants.UPLOAD_MONITOR_THREAD_NAME_PREFIX, 0) + .factory(); + + return Executors.newThreadPerTaskExecutor(threadFactory); + } + @JsonIgnoreProperties(ignoreUnknown = true) private record V3App(@JsonProperty("guid") String guid) { } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3Operations.java index b1e6170439..a3b210eec3 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3Operations.java @@ -29,16 +29,16 @@ public class ProcessesV3Operations { - private final CloudControllerV3Client cc; - private final CloudSpace target; + private final CloudControllerV3Client client; + private final CloudSpace targetSpace; - public ProcessesV3Operations(CloudControllerV3Client cc, CloudSpace target) { - this.cc = cc; - this.target = target; + public ProcessesV3Operations(CloudControllerV3Client client, CloudSpace targetSpace) { + this.client = client; + this.targetSpace = targetSpace; } public CloudProcess getApplicationProcess(UUID applicationGuid) { - V3Process applicationProcess = cc.get( + V3Process applicationProcess = client.get( CloudControllerV3Endpoints.APPS + "/" + applicationGuid + CloudControllerV3Endpoints.PROCESSES + Constants.WEB_PROCESS_TYPE, V3Process.class); @@ -48,7 +48,7 @@ public CloudProcess getApplicationProcess(UUID applicationGuid) { public InstancesInfo getApplicationInstances(CloudApplication application) { if (application.getState() .equals(CloudApplication.State.STARTED)) { - return findApplicationInstances(application.getGuid()); + return getApplicationInstances(application.getGuid()); } return ImmutableInstancesInfo.builder() @@ -57,21 +57,23 @@ public InstancesInfo getApplicationInstances(CloudApplication application) { } public InstancesInfo getApplicationInstances(UUID applicationGuid) { - return findApplicationInstances(applicationGuid); - } - - private InstancesInfo findApplicationInstances(UUID applicationGuid) { - V3Process.V3ProcessStats stats = cc.get( + V3Process.V3ProcessStats stats = client.get( CloudControllerV3Endpoints.APPS + "/" + applicationGuid + CloudControllerV3Endpoints.PROCESSES + Constants.WEB_PROCESS_TYPE + "/stats", V3Process.V3ProcessStats.class); + if (stats == null) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + MessageFormat.format(Messages.STATISTICS_FOR_APPLICATION_INSTANCES_WITH_GUID_0_NOT_FOUND, + applicationGuid)); + } + return V3InstancesInfoMapper.toInstancesInfo(stats); } public boolean getApplicationSshEnabled(UUID applicationGuid) { - V3Process.V3SshEnabled sshEnabled = cc.get(CloudControllerV3Endpoints.APPS + "/" + applicationGuid + "/ssh_enabled", - V3Process.V3SshEnabled.class); + V3Process.V3SshEnabled sshEnabled = client.get(CloudControllerV3Endpoints.APPS + "/" + applicationGuid + "/ssh_enabled", + V3Process.V3SshEnabled.class); if (sshEnabled == null || sshEnabled.enabled() == null) { return false; @@ -81,11 +83,11 @@ public boolean getApplicationSshEnabled(UUID applicationGuid) { } public Map getApplicationFeatures(UUID applicationGuid) { - List applicationFeatures = cc.list(CloudControllerV3Endpoints.APPS + "/" + applicationGuid + "/features" - + CloudControllerV3Endpoints.QUERY_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE, - new ParameterizedTypeReference>() { - }); + List applicationFeatures = client.list(CloudControllerV3Endpoints.APPS + "/" + applicationGuid + "/features" + + CloudControllerV3Endpoints.QUERY_PER_PAGE + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE, + new ParameterizedTypeReference>() { + }); Map result = new HashMap<>(); for (V3Process.V3AppFeature feature : applicationFeatures) { @@ -96,7 +98,8 @@ public Map getApplicationFeatures(UUID applicationGuid) { } public DropletInfo getCurrentDropletForApplication(UUID applicationGuid) { - V3Droplet currentDroplet = cc.get(CloudControllerV3Endpoints.APPS + "/" + applicationGuid + "/droplets/current", V3Droplet.class); + V3Droplet currentDroplet = client.get(CloudControllerV3Endpoints.APPS + "/" + applicationGuid + "/droplets/current", + V3Droplet.class); if (currentDroplet == null) { throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, @@ -126,18 +129,18 @@ private DropletInfo parseDropletInfo(V3Droplet droplet) { public void updateApplicationStaging(String applicationName, Staging staging) { UUID applicationGuid = getRequiredApplicationGuid(applicationName); - cc.getRestClient() - .patch() - .uri(CloudControllerV3Endpoints.APP_BY_GUID, applicationGuid) - .body(Map.of("lifecycle", buildApplicationLifecycle(staging))) - .retrieve() - .toBodilessEntity(); + client.getRestClient() + .patch() + .uri(CloudControllerV3Endpoints.APP_BY_GUID, applicationGuid) + .body(Map.of("lifecycle", buildApplicationLifecycle(staging))) + .retrieve() + .toBodilessEntity(); updateApplicationProcess(applicationGuid, staging); } private Map buildApplicationLifecycle(Staging staging) { if (staging.getDockerInfo() != null) { - return Map.of("type", "docker", "data", Map.of()); + return Map.of(Constants.TYPE, "docker", Constants.DATA, Map.of()); } String lifecycleType = staging.getLifecycleType() != null ? staging.getLifecycleType() @@ -145,9 +148,9 @@ private Map buildApplicationLifecycle(Staging staging) { .toLowerCase() : "buildpack"; - if ("cnb".equals(lifecycleType) && (staging.getBuildpacks() == null || staging.getBuildpacks() - .isEmpty())) { - throw new IllegalArgumentException("Buildpacks are required for the CNB lifecycle type"); + if (Constants.CLOUD_NATIVE_BUILDPACK.equals(lifecycleType) && (staging.getBuildpacks() == null || staging.getBuildpacks() + .isEmpty())) { + throw new IllegalArgumentException(Messages.BUILDPACKS_ARE_REQUIRED_FOR_THE_BUILDPACK_LIFECYCLE_TYPE); } Map data = new HashMap<>(); @@ -159,17 +162,23 @@ private Map buildApplicationLifecycle(Staging staging) { data.put("buildpacks", staging.getBuildpacks()); } - return Map.of("type", lifecycleType, "data", data); + return Map.of(Constants.TYPE, lifecycleType, Constants.DATA, data); } private void updateApplicationProcess(UUID applicationGuid, Staging staging) { staging.getAppFeatures() .forEach((featureName, enabled) -> updateAppFeature(applicationGuid, featureName, enabled)); - V3Process process = cc.get( + V3Process process = client.get( CloudControllerV3Endpoints.APPS + "/" + applicationGuid + CloudControllerV3Endpoints.PROCESSES + Constants.WEB_PROCESS_TYPE, V3Process.class); + if (process == null) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + MessageFormat.format(Messages.APPLICATION_PROCESS_FOR_APPLICATION_WITH_GUID_0_NOT_FOUND, + applicationGuid)); + } + Map updateProcessBody = new HashMap<>(); updateProcessBody.put("command", staging.getCommand()); if (staging.getHealthCheckType() != null) { @@ -180,21 +189,21 @@ private void updateApplicationProcess(UUID applicationGuid, Staging staging) { updateProcessBody.put("readiness_health_check", buildReadinessHealthCheck(staging)); } - cc.getRestClient() - .patch() - .uri(CloudControllerV3Endpoints.PROCESS_BY_GUID, process.guid()) - .body(updateProcessBody) - .retrieve() - .toBodilessEntity(); + client.getRestClient() + .patch() + .uri(CloudControllerV3Endpoints.PROCESS_BY_GUID, process.guid()) + .body(updateProcessBody) + .retrieve() + .toBodilessEntity(); } private void updateAppFeature(UUID applicationGuid, String featureName, boolean enabled) { - cc.getRestClient() - .patch() - .uri(CloudControllerV3Endpoints.APP_FEATURE, applicationGuid, featureName) - .body(Map.of("enabled", enabled)) - .retrieve() - .toBodilessEntity(); + client.getRestClient() + .patch() + .uri(CloudControllerV3Endpoints.APP_FEATURE, applicationGuid, featureName) + .body(Map.of("enabled", enabled)) + .retrieve() + .toBodilessEntity(); } private Map buildHealthCheck(Staging staging) { @@ -205,8 +214,8 @@ private Map buildHealthCheck(Staging staging) { putIfNotNull(data, "interval", staging.getHealthCheckInterval()); Map healthCheck = new HashMap<>(); - healthCheck.put("type", staging.getHealthCheckType()); - healthCheck.put("data", data); + healthCheck.put(Constants.TYPE, staging.getHealthCheckType()); + healthCheck.put(Constants.DATA, data); return healthCheck; } @@ -217,8 +226,8 @@ private Map buildReadinessHealthCheck(Staging staging) { putIfNotNull(data, "interval", staging.getReadinessHealthCheckInterval()); Map readinessHealthCheck = new HashMap<>(); - readinessHealthCheck.put("type", staging.getReadinessHealthCheckType()); - readinessHealthCheck.put("data", data); + readinessHealthCheck.put(Constants.TYPE, staging.getReadinessHealthCheckType()); + readinessHealthCheck.put(Constants.DATA, data); return readinessHealthCheck; } @@ -232,17 +241,17 @@ private UUID getRequiredApplicationGuid(String applicationName) { StringBuilder query = new StringBuilder(CloudControllerV3Endpoints.APPS + CloudControllerV3Endpoints.QUERY_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE); - if (target != null && target.getGuid() != null) { + if (targetSpace != null && targetSpace.getGuid() != null) { query.append(CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) - .append(target.getGuid()); + .append(targetSpace.getGuid()); } query.append(CloudControllerV3Endpoints.AMPERSAND_NAMES) .append(applicationName); - List apps = cc.list(query.toString(), - new ParameterizedTypeReference>() { - }); + List apps = client.list(query.toString(), + new ParameterizedTypeReference>() { + }); if (apps.isEmpty()) { throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RolesV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RolesV3Operations.java index 4bf20566af..e4170ad8c5 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RolesV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RolesV3Operations.java @@ -16,10 +16,10 @@ public class RolesV3Operations { private static final ParameterizedTypeReference> ROLE_PAGE = new ParameterizedTypeReference<>() { }; - private final CloudControllerV3Client cc; + private final CloudControllerV3Client client; - public RolesV3Operations(CloudControllerV3Client cc) { - this.cc = cc; + public RolesV3Operations(CloudControllerV3Client client) { + this.client = client; } public Set getUserRolesBySpaceAndUser(UUID spaceGuid, UUID userGuid) { @@ -27,10 +27,10 @@ public Set getUserRolesBySpaceAndUser(UUID spaceGuid, UUID userGuid) { + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE + CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS + spaceGuid + CloudControllerV3Endpoints.AMPERSAND_USER_GUIDS + userGuid; - return cc.list(uri, ROLE_PAGE) - .stream() - .map(V3RoleMapper::toUserRole) - .collect(Collectors.toCollection(() -> EnumSet.noneOf(UserRole.class))); + return client.list(uri, ROLE_PAGE) + .stream() + .map(V3RoleMapper::toUserRole) + .collect(Collectors.toCollection(() -> EnumSet.noneOf(UserRole.class))); } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3Operations.java index 4ff3bce492..da034949d5 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3Operations.java @@ -34,12 +34,12 @@ public class RoutesV3Operations { private static final ParameterizedTypeReference> DOMAIN_PAGE = new ParameterizedTypeReference<>() { }; - private final CloudControllerV3Client cc; - private final CloudSpace target; + private final CloudControllerV3Client client; + private final CloudSpace targetSpace; - public RoutesV3Operations(CloudControllerV3Client cc, CloudSpace target) { - this.cc = cc; - this.target = target; + public RoutesV3Operations(CloudControllerV3Client client, CloudSpace targetSpace) { + this.client = client; + this.targetSpace = targetSpace; } public void addRoute(String host, String domainName, String path) { @@ -61,13 +61,13 @@ public void deleteRoute(String host, String domainName, String path) { } public void deleteOrphanedRoutes() { - ResponseEntity response = cc.getRestClient() - .delete() - .uri(CloudControllerV3Endpoints.SPACE_UNMAPPED_ROUTES, getTargetSpaceGuid()) - .retrieve() - .toEntity(Void.class); + ResponseEntity response = client.getRestClient() + .delete() + .uri(CloudControllerV3Endpoints.SPACE_UNMAPPED_ROUTES, getTargetSpaceGuid()) + .retrieve() + .toEntity(Void.class); - cc.followAsyncJob(response, Constants.DELETE_JOB_TIMEOUT); + client.followAsyncJob(response, Constants.DELETE_JOB_TIMEOUT); } public List getRoutes(String domainName) { @@ -102,21 +102,22 @@ public void updateApplicationRoutes(String applicationName, Set upda } private List findRoutesByDomainGuid(UUID domainGuid) { - StringBuilder query = new StringBuilder( - CloudControllerV3Endpoints.ROUTES + CloudControllerV3Endpoints.QUERY_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE); - - query.append(CloudControllerV3Endpoints.AMPERSAND_DOMAIN_GUIDS) - .append(domainGuid); + return listRoutes(buildRouteQuery(domainGuid, null, null)).stream() + .map(V3RouteMapper::toCloudRoute) + .toList(); + } - query.append(CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) - .append(getTargetSpaceGuid()); + private UUID getRouteGuid(UUID domainGuid, String host, String path) { + List routes = listRoutes(buildRouteQuery(domainGuid, host, path)); + if (CollectionUtils.isEmpty(routes)) { + return null; + } - return listRoutes(query.toString()).stream() - .map(V3RouteMapper::toCloudRoute) - .toList(); + return UUID.fromString(routes.getFirst() + .guid()); } - private UUID getRouteGuid(UUID domainGuid, String host, String path) { + private String buildRouteQuery(UUID domainGuid, String host, String path) { StringBuilder query = new StringBuilder( CloudControllerV3Endpoints.ROUTES + CloudControllerV3Endpoints.QUERY_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE); @@ -136,17 +137,11 @@ private UUID getRouteGuid(UUID domainGuid, String host, String path) { .append(path); } - List routes = listRoutes(query.toString()); - if (CollectionUtils.isEmpty(routes)) { - return null; - } - - return UUID.fromString(routes.getFirst() - .guid()); + return query.toString(); } private List listRoutes(String query) { - return cc.list(query, ROUTE_PAGE); + return client.list(query, ROUTE_PAGE); } private void addRoutes(Set routes, UUID applicationGuid) { @@ -186,43 +181,50 @@ private UUID getOrAddRoute(UUID domainGuid, String host, String path) { private UUID doAddRoute(UUID domainGuid, String host, String path) { assertSpaceProvided("add route"); - V3Route created = cc.getRestClient() - .post() - .uri(CloudControllerV3Endpoints.ROUTES) - .body(Map.of("host", host == null ? "" : host, "path", path == null ? "" : path, "relationships", - Map.of("domain", toOneRelationship(domainGuid), "space", - toOneRelationship(getTargetSpaceGuid())))) - .retrieve() - .body(V3Route.class); + V3Route created = client.getRestClient() + .post() + .uri(CloudControllerV3Endpoints.ROUTES) + .body(buildCreateRouteBody(domainGuid, host, path)) + .retrieve() + .body(V3Route.class); return UUID.fromString(created.guid()); } + private Map buildCreateRouteBody(UUID domainGuid, String host, String path) { + Map relationships = Map.of("domain", buildSingleResourceRelationship(domainGuid), + "space", buildSingleResourceRelationship(getTargetSpaceGuid())); + + return Map.of("host", host == null ? "" : host, + "path", path == null ? "" : path, + "relationships", relationships); + } + private void doDeleteRoute(UUID guid) { - ResponseEntity response = cc.getRestClient() - .delete() - .uri(CloudControllerV3Endpoints.ROUTE_BY_GUID, guid) - .retrieve() - .toEntity(Void.class); + ResponseEntity response = client.getRestClient() + .delete() + .uri(CloudControllerV3Endpoints.ROUTE_BY_GUID, guid) + .retrieve() + .toEntity(Void.class); - cc.followAsyncJob(response, Constants.DELETE_JOB_TIMEOUT); + client.followAsyncJob(response, Constants.DELETE_JOB_TIMEOUT); } private void bindRoute(UUID routeGuid, UUID applicationGuid, String protocol) { - cc.getRestClient() - .post() - .uri(CloudControllerV3Endpoints.ROUTE_DESTINATIONS, routeGuid) - .body(Map.of("destinations", List.of(createDestination(applicationGuid, protocol)))) - .retrieve() - .toBodilessEntity(); + client.getRestClient() + .post() + .uri(CloudControllerV3Endpoints.ROUTE_DESTINATIONS, routeGuid) + .body(Map.of("destinations", List.of(createDestination(applicationGuid, protocol)))) + .retrieve() + .toBodilessEntity(); } private void unbindRoute(UUID routeGuid, UUID destinationGuid) { - cc.getRestClient() - .delete() - .uri(CloudControllerV3Endpoints.ROUTE_DESTINATION_BY_GUID, routeGuid, destinationGuid) - .retrieve() - .toBodilessEntity(); + client.getRestClient() + .delete() + .uri(CloudControllerV3Endpoints.ROUTE_DESTINATION_BY_GUID, routeGuid, destinationGuid) + .retrieve() + .toBodilessEntity(); } private Map createDestination(UUID applicationGuid, String protocol) { @@ -233,7 +235,7 @@ private Map createDestination(UUID applicationGuid, String proto return Map.of("app", Map.of("guid", applicationGuid.toString()), "protocol", protocol); } - private static Map toOneRelationship(UUID guid) { + private Map buildSingleResourceRelationship(UUID guid) { return Map.of("data", Map.of("guid", guid.toString())); } @@ -246,11 +248,9 @@ private Set getOutdatedRoutes(UUID applicationGuid, List private boolean isRouteOutdated(UUID applicationGuid, CloudRoute currentRoute, Set updatedRoutes) { Optional updatedRoute = findRoute(currentRoute.getUrl(), updatedRoutes); - if (updatedRoute.isEmpty()) { - return true; - } + return updatedRoute.map(cloudRoute -> isProtocolChanged(applicationGuid, currentRoute, cloudRoute)) + .orElse(true); - return isProtocolChanged(applicationGuid, currentRoute, updatedRoute.get()); } private Set getNewRoutes(UUID applicationGuid, List currentRoutes, Set updatedRoutes) { @@ -262,11 +262,9 @@ private Set getNewRoutes(UUID applicationGuid, List curr private boolean isRouteUpdated(UUID applicationGuid, CloudRoute updatedRoute, List currentRoutes) { Optional currentRoute = findRoute(updatedRoute.getUrl(), currentRoutes); - if (currentRoute.isEmpty()) { - return true; - } + return currentRoute.map(cloudRoute -> isProtocolChanged(applicationGuid, cloudRoute, updatedRoute)) + .orElse(true); - return isProtocolChanged(applicationGuid, currentRoute.get(), updatedRoute); } private Optional findRoute(String url, java.util.Collection routes) { @@ -310,9 +308,10 @@ private UUID getRequiredDomainGuid(String name) { } private UUID findDomainGuidByName(String name) { - List domains = cc.list(CloudControllerV3Endpoints.DOMAINS + CloudControllerV3Endpoints.QUERY_NAMES + name - + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE, - DOMAIN_PAGE); + List domains = client.list(CloudControllerV3Endpoints.DOMAINS + CloudControllerV3Endpoints.QUERY_NAMES + name + + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE, + DOMAIN_PAGE); if (CollectionUtils.isEmpty(domains)) { return null; @@ -334,42 +333,47 @@ private Map getDomainsFromRoutes(Set routes) { } String names = String.join(",", domainNames); - return cc.list(CloudControllerV3Endpoints.DOMAINS + CloudControllerV3Endpoints.QUERY_NAMES + names - + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE, DOMAIN_PAGE) - .stream() - .collect(Collectors.toMap(V3Domain::name, domain -> UUID.fromString(domain.guid()))); + return client.list(CloudControllerV3Endpoints.DOMAINS + CloudControllerV3Endpoints.QUERY_NAMES + names + + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE, DOMAIN_PAGE) + .stream() + .collect(Collectors.toMap(V3Domain::name, domain -> UUID.fromString(domain.guid()))); } private UUID getRequiredApplicationGuid(String applicationName) { + List apps = client.list(buildApplicationQuery(applicationName), + new ParameterizedTypeReference>() { + }); + + if (CollectionUtils.isEmpty(apps)) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + MessageFormat.format(Messages.APPLICATION_0_NOT_FOUND, applicationName)); + } + + return UUID.fromString(apps.getFirst() + .guid()); + } + + private String buildApplicationQuery(String applicationName) { StringBuilder query = new StringBuilder( CloudControllerV3Endpoints.APPS + CloudControllerV3Endpoints.QUERY_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE); query.append(CloudControllerV3Endpoints.AMPERSAND_NAMES) .append(applicationName); - if (target != null && target.getGuid() != null) { + if (targetSpace != null && targetSpace.getGuid() != null) { query.append(CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) - .append(target.getGuid()); + .append(targetSpace.getGuid()); } - List apps = cc.list(query.toString(), new ParameterizedTypeReference>() { - }); - - if (CollectionUtils.isEmpty(apps)) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, - MessageFormat.format(Messages.APPLICATION_0_NOT_FOUND, applicationName)); - } - - return UUID.fromString(apps.getFirst() - .guid()); + return query.toString(); } private UUID getTargetSpaceGuid() { - return target.getGuid(); + return targetSpace.getGuid(); } private void assertSpaceProvided(String operation) { - if (target == null) { + if (targetSpace == null) { throw new IllegalArgumentException( MessageFormat.format(Messages.UNABLE_TO_0_WITHOUT_SPECIFYING_ORGANIZATION_AND_SPACE_TO_USE, operation)); } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3Operations.java index 5124d1aec4..5690bcd575 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3Operations.java @@ -32,12 +32,12 @@ public class ServiceBindingsV3Operations { private static final ParameterizedTypeReference> APPLICATION_LIST_TYPE = new ParameterizedTypeReference<>() { }; - private final CloudControllerV3Client cc; - private final CloudSpace target; + private final CloudControllerV3Client client; + private final CloudSpace targetSpace; - public ServiceBindingsV3Operations(CloudControllerV3Client cc, CloudSpace target) { - this.cc = cc; - this.target = target; + public ServiceBindingsV3Operations(CloudControllerV3Client client, CloudSpace targetSpace) { + this.client = client; + this.targetSpace = targetSpace; } public Optional bindServiceInstance(String bindingName, String applicationName, String serviceInstanceName) { @@ -49,22 +49,12 @@ public Optional bindServiceInstance(String bindingName, String applicati UUID applicationGuid = getRequiredApplicationGuid(applicationName); UUID serviceInstanceGuid = getRequiredServiceInstanceGuid(serviceInstanceName); - Map body = new HashMap<>(); - body.put("name", bindingName); - body.put("type", "app"); - body.put("relationships", Map.of("app", toOneRelationship(applicationGuid), "service_instance", - toOneRelationship(serviceInstanceGuid))); - - if (!CollectionUtils.isEmpty(parameters)) { - body.put("parameters", parameters); - } - - ResponseEntity response = cc.getRestClient() - .post() - .uri(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS) - .body(body) - .retrieve() - .toBodilessEntity(); + ResponseEntity response = client.getRestClient() + .post() + .uri(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS) + .body(buildServiceInstanceBody(bindingName, applicationGuid, serviceInstanceGuid, parameters)) + .retrieve() + .toBodilessEntity(); return extractJobGuidFromResponseHeader(response); } @@ -88,11 +78,11 @@ public CloudServiceBinding getServiceBinding(UUID serviceBindingGuid) { String uri = CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS + CloudControllerV3Endpoints.QUERY_GUIDS + serviceBindingGuid + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; - return cc.list(uri, BINDING_LIST_TYPE) - .stream() - .findFirst() - .map(V3ServiceBindingMapper::toCloudServiceBinding) - .orElse(null); + return client.list(uri, BINDING_LIST_TYPE) + .stream() + .findFirst() + .map(V3ServiceBindingMapper::toCloudServiceBinding) + .orElse(null); } public List getServiceAppBindings(UUID serviceInstanceGuid) { @@ -121,8 +111,14 @@ public List getServiceBindingsForApplication(UUID applicati public Map getServiceBindingParameters(UUID guid) { @SuppressWarnings("unchecked") - Map parameters = cc.get(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS + "/" + guid + "/parameters", - Map.class); + Map parameters = client.get(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS + "/" + guid + "/parameters", + Map.class); + + if (parameters == null) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + MessageFormat.format(Messages.PARAMETERS_OF_SERVICE_BINDING_WITH_GUID_0_NOT_FOUND, + guid)); + } return parameters; } @@ -132,19 +128,19 @@ public void updateServiceBindingMetadata(UUID guid, Metadata metadata) { metadataBody.put("labels", metadata.getLabels()); metadataBody.put("annotations", metadata.getAnnotations()); - cc.getRestClient() - .patch() - .uri(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDING_BY_GUID, guid) - .body(Map.of("metadata", metadataBody)) - .retrieve() - .toBodilessEntity(); + client.getRestClient() + .patch() + .uri(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDING_BY_GUID, guid) + .body(Map.of("metadata", metadataBody)) + .retrieve() + .toBodilessEntity(); } private List listBindings(String uri) { - return cc.list(uri, BINDING_LIST_TYPE) - .stream() - .map(V3ServiceBindingMapper::toCloudServiceBinding) - .toList(); + return client.list(uri, BINDING_LIST_TYPE) + .stream() + .map(V3ServiceBindingMapper::toCloudServiceBinding) + .toList(); } private List doUnbindServiceInstance(UUID applicationGuid, UUID serviceInstanceGuid) { @@ -165,10 +161,10 @@ private List getServiceBindingGuids(UUID applicationGuid, UUID serviceInst + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; - return cc.list(uri, BINDING_LIST_TYPE) - .stream() - .map(binding -> UUID.fromString(binding.guid())) - .toList(); + return client.list(uri, BINDING_LIST_TYPE) + .stream() + .map(binding -> UUID.fromString(binding.guid())) + .toList(); } private List doDeleteServiceBindings(List guids) { @@ -188,11 +184,11 @@ private List doDeleteServiceBindings(List guids) { } private Optional deleteSingleServiceBinding(UUID guid) { - ResponseEntity response = cc.getRestClient() - .delete() - .uri(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDING_BY_GUID, guid) - .retrieve() - .toBodilessEntity(); + ResponseEntity response = client.getRestClient() + .delete() + .uri(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDING_BY_GUID, guid) + .retrieve() + .toBodilessEntity(); return extractJobGuidFromResponseHeader(response); } @@ -229,15 +225,15 @@ private UUID getRequiredApplicationGuid(String applicationName) { StringBuilder query = new StringBuilder( CloudControllerV3Endpoints.APPS + CloudControllerV3Endpoints.QUERY_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE); - if (target != null && target.getGuid() != null) { + if (targetSpace != null && targetSpace.getGuid() != null) { query.append(CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) - .append(target.getGuid()); + .append(targetSpace.getGuid()); } query.append(CloudControllerV3Endpoints.AMPERSAND_NAMES) .append(applicationName); - List apps = cc.list(query.toString(), APPLICATION_LIST_TYPE); + List apps = client.list(query.toString(), APPLICATION_LIST_TYPE); if (apps.isEmpty()) { throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, @@ -252,16 +248,16 @@ private UUID getRequiredServiceInstanceGuid(String name) { StringBuilder query = new StringBuilder(CloudControllerV3Endpoints.SERVICE_INSTANCES + CloudControllerV3Endpoints.QUERY_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE); - if (target != null && target.getGuid() != null) { + if (targetSpace != null && targetSpace.getGuid() != null) { query.append(CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) - .append(target.getGuid()); + .append(targetSpace.getGuid()); } query.append(CloudControllerV3Endpoints.AMPERSAND_NAMES) .append(name); - List instances = cc.list(query.toString(), - new ParameterizedTypeReference>() { - }); + List instances = client.list(query.toString(), + new ParameterizedTypeReference>() { + }); if (instances.isEmpty()) { throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, @@ -272,6 +268,21 @@ private UUID getRequiredServiceInstanceGuid(String name) { .guid()); } + private Map buildServiceInstanceBody(String bindingName, UUID applicationGuid, UUID serviceInstanceGuid, + Map parameters) { + Map body = new HashMap<>(); + body.put("name", bindingName); + body.put("type", "app"); + body.put("relationships", Map.of("app", toOneRelationship(applicationGuid), "service_instance", + toOneRelationship(serviceInstanceGuid))); + + if (!CollectionUtils.isEmpty(parameters)) { + body.put("parameters", parameters); + } + + return body; + } + @JsonIgnoreProperties(ignoreUnknown = true) private record V3ServiceInstanceRef(@JsonProperty("guid") String guid) { } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBrokersV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBrokersV3Operations.java index 6b1db34c3e..ead8031401 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBrokersV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBrokersV3Operations.java @@ -31,12 +31,12 @@ public class ServiceBrokersV3Operations { private static final ParameterizedTypeReference> PLAN_PAGE = new ParameterizedTypeReference<>() { }; - private final CloudControllerV3Client cc; - private final CloudSpace target; + private final CloudControllerV3Client client; + private final CloudSpace targetSpace; - public ServiceBrokersV3Operations(CloudControllerV3Client cc, CloudSpace target) { - this.cc = cc; - this.target = target; + public ServiceBrokersV3Operations(CloudControllerV3Client client, CloudSpace targetSpace) { + this.client = client; + this.targetSpace = targetSpace; } public String createServiceBroker(CloudServiceBroker serviceBroker) { @@ -51,12 +51,12 @@ public String createServiceBroker(CloudServiceBroker serviceBroker) { body.put("relationships", Map.of("space", Map.of("data", Map.of("guid", serviceBroker.getSpaceGuid())))); } - ResponseEntity response = cc.getRestClient() - .post() - .uri(CloudControllerV3Endpoints.SERVICE_BROKERS) - .body(body) - .retrieve() - .toBodilessEntity(); + ResponseEntity response = client.getRestClient() + .post() + .uri(CloudControllerV3Endpoints.SERVICE_BROKERS) + .body(body) + .retrieve() + .toBodilessEntity(); return extractJobGuid(response); } @@ -65,11 +65,11 @@ public String deleteServiceBroker(String name) { UUID guid = broker.getMetadata() .getGuid(); - ResponseEntity response = cc.getRestClient() - .delete() - .uri(CloudControllerV3Endpoints.SERVICE_BROKER_BY_GUID, guid.toString()) - .retrieve() - .toBodilessEntity(); + ResponseEntity response = client.getRestClient() + .delete() + .uri(CloudControllerV3Endpoints.SERVICE_BROKER_BY_GUID, guid.toString()) + .retrieve() + .toBodilessEntity(); return extractJobGuid(response); } @@ -90,11 +90,11 @@ public CloudServiceBroker getServiceBroker(String name, boolean required) { } public List getServiceBrokers() { - return cc.list(CloudControllerV3Endpoints.SERVICE_BROKERS + CloudControllerV3Endpoints.QUERY_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE, BROKER_PAGE) - .stream() - .map(V3ServiceBrokerMapper::toCloudServiceBroker) - .toList(); + return client.list(CloudControllerV3Endpoints.SERVICE_BROKERS + CloudControllerV3Endpoints.QUERY_PER_PAGE + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE, BROKER_PAGE) + .stream() + .map(V3ServiceBrokerMapper::toCloudServiceBroker) + .toList(); } public String updateServiceBroker(CloudServiceBroker serviceBroker) { @@ -107,12 +107,12 @@ public String updateServiceBroker(CloudServiceBroker serviceBroker) { Map body = Map.of("name", serviceBroker.getName(), "url", serviceBroker.getUrl(), "authentication", basicAuthentication(serviceBroker)); - ResponseEntity response = cc.getRestClient() - .patch() - .uri(CloudControllerV3Endpoints.SERVICE_BROKER_BY_GUID, brokerGuid.toString()) - .body(body) - .retrieve() - .toBodilessEntity(); + ResponseEntity response = client.getRestClient() + .patch() + .uri(CloudControllerV3Endpoints.SERVICE_BROKER_BY_GUID, brokerGuid.toString()) + .body(body) + .retrieve() + .toBodilessEntity(); return extractJobGuid(response); } @@ -128,14 +128,14 @@ public void updateServicePlanVisibilityForBroker(String name, ServicePlanVisibil } private CloudServiceBroker findServiceBrokerByName(String name) { - return cc.list( - CloudControllerV3Endpoints.SERVICE_BROKERS + CloudControllerV3Endpoints.QUERY_NAMES + name - + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE, BROKER_PAGE) - .stream() - .findFirst() - .map(V3ServiceBrokerMapper::toCloudServiceBroker) - .orElse(null); + return client.list( + CloudControllerV3Endpoints.SERVICE_BROKERS + CloudControllerV3Endpoints.QUERY_NAMES + name + + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE, BROKER_PAGE) + .stream() + .findFirst() + .map(V3ServiceBrokerMapper::toCloudServiceBroker) + .orElse(null); } private List findServicePlanGuidsByBrokerGuid(UUID brokerGuid) { @@ -150,11 +150,11 @@ private List findServiceOfferingGuidsByBrokerGuid(UUID brokerGuid) { + CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS + getTargetSpaceGuid() + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; - return cc.list(uri, OFFERING_PAGE) - .stream() - .map(ServiceOfferingRef::guid) - .map(UUID::fromString) - .toList(); + return client.list(uri, OFFERING_PAGE) + .stream() + .map(ServiceOfferingRef::guid) + .map(UUID::fromString) + .toList(); } private List findServicePlanGuidsByOfferingGuid(UUID serviceOfferingGuid) { @@ -162,20 +162,20 @@ private List findServicePlanGuidsByOfferingGuid(UUID serviceOfferingGuid) CloudControllerV3Endpoints.SERVICE_PLANS + CloudControllerV3Endpoints.QUERY_SERVICE_OFFERING_GUIDS + serviceOfferingGuid + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; - return cc.list(uri, PLAN_PAGE) - .stream() - .map(ServicePlanRef::guid) - .map(UUID::fromString) - .toList(); + return client.list(uri, PLAN_PAGE) + .stream() + .map(ServicePlanRef::guid) + .map(UUID::fromString) + .toList(); } private void updateServicePlanVisibility(UUID servicePlanGuid, ServicePlanVisibility visibility) { - cc.getRestClient() - .patch() - .uri(CloudControllerV3Endpoints.SERVICE_PLAN_VISIBILITY, servicePlanGuid.toString()) - .body(Map.of("type", visibility.toString())) - .retrieve() - .toBodilessEntity(); + client.getRestClient() + .patch() + .uri(CloudControllerV3Endpoints.SERVICE_PLAN_VISIBILITY, servicePlanGuid.toString()) + .body(Map.of("type", visibility.toString())) + .retrieve() + .toBodilessEntity(); } private static Map basicAuthentication(CloudServiceBroker serviceBroker) { @@ -188,9 +188,9 @@ private static String nullToEmpty(String value) { } private String getTargetSpaceGuid() { - return target.getMetadata() - .getGuid() - .toString(); + return targetSpace.getMetadata() + .getGuid() + .toString(); } private static String extractJobGuid(ResponseEntity response) { diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3Operations.java index aef28801ee..c8f2b6c74e 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3Operations.java @@ -29,12 +29,12 @@ public class ServiceInstancesV3Operations { private static final ParameterizedTypeReference> SERVICE_INSTANCE_LIST_TYPE = new ParameterizedTypeReference<>() { }; - private final CloudControllerV3Client cc; - private final CloudSpace target; + private final CloudControllerV3Client client; + private final CloudSpace targetSpace; - public ServiceInstancesV3Operations(CloudControllerV3Client cc, CloudSpace target) { - this.cc = cc; - this.target = target; + public ServiceInstancesV3Operations(CloudControllerV3Client client, CloudSpace targetSpace) { + this.client = client; + this.targetSpace = targetSpace; } public void createServiceInstance(CloudServiceInstance serviceInstance) { @@ -51,12 +51,12 @@ public void createServiceInstance(CloudServiceInstance serviceInstance) { body.put("relationships", Map.of("service_plan", toOneRelationship(servicePlanGuid.toString()), "space", toOneRelationship(getTargetSpaceGuid().toString()))); - cc.getRestClient() - .post() - .uri(CloudControllerV3Endpoints.SERVICE_INSTANCES) - .body(body) - .retrieve() - .toBodilessEntity(); + client.getRestClient() + .post() + .uri(CloudControllerV3Endpoints.SERVICE_INSTANCES) + .body(body) + .retrieve() + .toBodilessEntity(); } public void createUserProvidedServiceInstance(CloudServiceInstance serviceInstance) { @@ -73,12 +73,12 @@ public void createUserProvidedServiceInstance(CloudServiceInstance serviceInstan putIfNotNull(body, "tags", serviceInstance.getTags()); body.put("relationships", Map.of("space", toOneRelationship(getTargetSpaceGuid().toString()))); - cc.getRestClient() - .post() - .uri(CloudControllerV3Endpoints.SERVICE_INSTANCES) - .body(body) - .retrieve() - .toBodilessEntity(); + client.getRestClient() + .post() + .uri(CloudControllerV3Endpoints.SERVICE_INSTANCES) + .body(body) + .retrieve() + .toBodilessEntity(); } public void deleteServiceInstance(String serviceInstanceName) { @@ -112,8 +112,8 @@ public CloudServiceInstance getServiceInstance(String serviceInstanceName, boole } public String getServiceInstanceName(UUID serviceInstanceGuid) { - V3ServiceInstance serviceInstance = cc.get(CloudControllerV3Endpoints.SERVICE_INSTANCES + "/" + serviceInstanceGuid, - V3ServiceInstance.class); + V3ServiceInstance serviceInstance = client.get(CloudControllerV3Endpoints.SERVICE_INSTANCES + "/" + serviceInstanceGuid, + V3ServiceInstance.class); return serviceInstance == null ? null : serviceInstance.name(); } @@ -132,11 +132,27 @@ public CloudServiceInstance getServiceInstanceWithoutAuxiliaryContent(String ser } public Map getServiceInstanceParameters(UUID guid) { - return getParameters(CloudControllerV3Endpoints.SERVICE_INSTANCES + "/" + guid + "/parameters"); + Map parameters = client.get(CloudControllerV3Endpoints.SERVICE_INSTANCES + "/" + guid + "/parameters", Map.class); + + if (parameters == null) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + MessageFormat.format(Messages.PARAMETERS_OF_SERVICE_INSTANCE_WITH_GUID_0_NOT_FOUND, + guid)); + } + + return parameters; } public Map getUserProvidedServiceInstanceParameters(UUID guid) { - return getParameters(CloudControllerV3Endpoints.SERVICE_INSTANCES + "/" + guid + "/credentials"); + Map credentials = client.get(CloudControllerV3Endpoints.SERVICE_INSTANCES + "/" + guid + "/credentials", Map.class); + + if (credentials == null) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + MessageFormat.format(Messages.CREDENTIALS_OF_SERVICE_INSTANCE_WITH_GUID_0_NOT_FOUND, + guid)); + } + + return credentials; } public List getServiceInstancesWithoutAuxiliaryContentByNames(List names) { @@ -147,10 +163,10 @@ public List getServiceInstancesWithoutAuxiliaryContentByNa + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE + CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS + getTargetSpaceGuid() + CloudControllerV3Endpoints.AMPERSAND_NAMES + String.join(",", batch); - cc.list(uri, SERVICE_INSTANCE_LIST_TYPE) - .stream() - .map(V3ServiceInstanceMapper::toCloudServiceInstanceWithoutAuxiliaryContent) - .forEach(allServiceInstances::add); + client.list(uri, SERVICE_INSTANCE_LIST_TYPE) + .stream() + .map(V3ServiceInstanceMapper::toCloudServiceInstanceWithoutAuxiliaryContent) + .forEach(allServiceInstances::add); } return allServiceInstances; @@ -161,7 +177,7 @@ public List getServiceInstancesByMetadataLabelSelector(Str + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE + CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS + getTargetSpaceGuid() + CloudControllerV3Endpoints.AMPERSAND_LABEL_SELECTOR + labelSelector; - return ReactiveFanOut.mapConcurrently(cc.list(uri, SERVICE_INSTANCE_LIST_TYPE), this::mapWithSupportingContent); + return ReactiveFanOut.mapConcurrently(client.list(uri, SERVICE_INSTANCE_LIST_TYPE), this::mapWithSupportingContent); } public List getServiceInstancesWithoutAuxiliaryContentByMetadataLabelSelector(String labelSelector) { @@ -169,10 +185,10 @@ public List getServiceInstancesWithoutAuxiliaryContentByMe + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE + CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS + getTargetSpaceGuid() + CloudControllerV3Endpoints.AMPERSAND_LABEL_SELECTOR + labelSelector; - return cc.list(uri, SERVICE_INSTANCE_LIST_TYPE) - .stream() - .map(resource -> V3ServiceInstanceMapper.toCloudServiceInstance(resource, null, null)) - .toList(); + return client.list(uri, SERVICE_INSTANCE_LIST_TYPE) + .stream() + .map(resource -> V3ServiceInstanceMapper.toCloudServiceInstance(resource, null, null)) + .toList(); } public void updateServicePlan(String serviceName, String planName) { @@ -252,10 +268,10 @@ private V3ServiceInstance findServiceInstanceResourceByName(String name) { + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE + CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS + getTargetSpaceGuid() + CloudControllerV3Endpoints.AMPERSAND_NAMES + name; - return cc.list(uri, SERVICE_INSTANCE_LIST_TYPE) - .stream() - .findFirst() - .orElse(null); + return client.list(uri, SERVICE_INSTANCE_LIST_TYPE) + .stream() + .findFirst() + .orElse(null); } private boolean isUserProvided(V3ServiceInstance resource) { @@ -297,45 +313,36 @@ private ServicePlanNames resolvePlanAndOfferingNames(String servicePlanGuid, Str private V3ServicePlan getServicePlanForNameResolution(String servicePlanGuid, String serviceInstanceName) { try { - return cc.get(CloudControllerV3Endpoints.SERVICE_PLANS + "/" + servicePlanGuid, V3ServicePlan.class); + return client.get(CloudControllerV3Endpoints.SERVICE_PLANS + "/" + servicePlanGuid, V3ServicePlan.class); } catch (CloudOperationException e) { - if (e.getStatusCode() == HttpStatus.FORBIDDEN) { - throw new CloudOperationException(HttpStatus.FORBIDDEN, Messages.FORBIDDEN, - MessageFormat.format( - Messages.SERVICE_PLAN_WITH_GUID_0_NOT_AVAILABLE_FOR_SERVICE_INSTANCE_1, - servicePlanGuid, serviceInstanceName), - e); - } - - if (e.getStatusCode() == HttpStatus.NOT_FOUND) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, - MessageFormat.format(Messages.NO_SERVICE_PLAN_FOUND, servicePlanGuid, - serviceInstanceName), - e); - } - - throw e; + throw determineNameResolutionError(e, + MessageFormat.format(Messages.SERVICE_PLAN_WITH_GUID_0_NOT_AVAILABLE_FOR_SERVICE_INSTANCE_1, + servicePlanGuid, serviceInstanceName), + MessageFormat.format(Messages.NO_SERVICE_PLAN_FOUND, servicePlanGuid, serviceInstanceName)); } } private V3ServiceOffering getServiceOfferingForNameResolution(String offeringGuid) { try { - return cc.get(CloudControllerV3Endpoints.SERVICE_OFFERINGS + "/" + offeringGuid, V3ServiceOffering.class); + return client.get(CloudControllerV3Endpoints.SERVICE_OFFERINGS + "/" + offeringGuid, V3ServiceOffering.class); } catch (CloudOperationException e) { - if (e.getStatusCode() == HttpStatus.FORBIDDEN) { - throw new CloudOperationException(HttpStatus.FORBIDDEN, Messages.FORBIDDEN, - MessageFormat.format(Messages.SERVICE_OFFERING_WITH_GUID_0_IS_NOT_AVAILABLE, - offeringGuid), - e); - } + throw determineNameResolutionError(e, + MessageFormat.format(Messages.SERVICE_OFFERING_WITH_GUID_0_IS_NOT_AVAILABLE, offeringGuid), + MessageFormat.format(Messages.SERVICE_OFFERING_WITH_GUID_0_NOT_FOUND, offeringGuid)); + } + } - if (e.getStatusCode() == HttpStatus.NOT_FOUND) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, - MessageFormat.format(Messages.SERVICE_OFFERING_WITH_GUID_0_NOT_FOUND, offeringGuid), e); - } + private CloudOperationException determineNameResolutionError(CloudOperationException e, String forbiddenMessage, + String notFoundMessage) { + if (e.getStatusCode() == HttpStatus.FORBIDDEN) { + return new CloudOperationException(HttpStatus.FORBIDDEN, Messages.FORBIDDEN, forbiddenMessage, e); + } - throw e; + if (e.getStatusCode() == HttpStatus.NOT_FOUND) { + return new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, notFoundMessage, e); } + + return e; } private String offeringGuidOf(V3ServicePlan plan) { @@ -361,11 +368,11 @@ private UUID findPlanGuidForService(CloudServiceInstance service, String planNam + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE + CloudControllerV3Endpoints.AMPERSAND_SERVICE_OFFERING_GUIDS + offeringGuid + CloudControllerV3Endpoints.AMPERSAND_NAMES + planName; - V3ServicePlan plan = cc.list(uri, new ParameterizedTypeReference>() { - }) - .stream() - .findFirst() - .orElse(null); + V3ServicePlan plan = client.list(uri, new ParameterizedTypeReference>() { + }) + .stream() + .findFirst() + .orElse(null); if (plan != null) { return UUID.fromString(plan.guid()); @@ -394,33 +401,28 @@ private List findServiceOfferingGuids(String label, String broker) { .append(broker); } - return cc.list(uri.toString(), new ParameterizedTypeReference>() { - }) - .stream() - .map(V3ServiceOffering::guid) - .toList(); + return client.list(uri.toString(), new ParameterizedTypeReference>() { + }) + .stream() + .map(V3ServiceOffering::guid) + .toList(); } private void doDeleteServiceInstance(UUID serviceInstanceGuid) { - cc.getRestClient() - .delete() - .uri(CloudControllerV3Endpoints.SERVICE_INSTANCE_BY_GUID, serviceInstanceGuid.toString()) - .retrieve() - .toBodilessEntity(); + client.getRestClient() + .delete() + .uri(CloudControllerV3Endpoints.SERVICE_INSTANCE_BY_GUID, serviceInstanceGuid.toString()) + .retrieve() + .toBodilessEntity(); } private void patchServiceInstance(UUID guid, Map body) { - cc.getRestClient() - .patch() - .uri(CloudControllerV3Endpoints.SERVICE_INSTANCE_BY_GUID, guid.toString()) - .body(body) - .retrieve() - .toBodilessEntity(); - } - - @SuppressWarnings("unchecked") - private Map getParameters(String uri) { - return cc.get(uri, Map.class); + client.getRestClient() + .patch() + .uri(CloudControllerV3Endpoints.SERVICE_INSTANCE_BY_GUID, guid.toString()) + .body(body) + .retrieve() + .toBodilessEntity(); } private Map toOneRelationship(String guid) { @@ -478,7 +480,7 @@ private List> toBatches(Collection largeList, int maxCharLength) } private UUID getTargetSpaceGuid() { - return getGuid(target); + return getGuid(targetSpace); } private UUID getGuid(CloudEntity entity) { @@ -491,7 +493,7 @@ private UUID getGuid(CloudEntity entity) { } private void assertSpaceProvided(String operation) { - Assert.notNull(target, "Unable to " + operation + " without specifying organization and space to use."); + Assert.notNull(targetSpace, "Unable to " + operation + " without specifying organization and space to use."); } private record ServicePlanNames(String planName, String offeringName) { diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3Operations.java index 08b74ae624..dfe598fc73 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3Operations.java @@ -23,17 +23,17 @@ public class ServiceKeysV3Operations { - private final CloudControllerV3Client cc; + private final CloudControllerV3Client client; - public ServiceKeysV3Operations(CloudControllerV3Client cc) { - this.cc = cc; + public ServiceKeysV3Operations(CloudControllerV3Client client) { + this.client = client; } public CloudServiceKey createAndFetchServiceKey(CloudServiceKey keyModel, CloudServiceInstance serviceInstance) { ResponseEntity acceptedResponse = postServiceKey(keyModel.getName(), keyModel.getCredentials(), keyModel.getV3Metadata(), serviceInstance); - cc.followAsyncJob(acceptedResponse, Constants.BINDING_OPERATIONS_TIMEOUT); + client.followAsyncJob(acceptedResponse, Constants.BINDING_OPERATIONS_TIMEOUT); V3ServiceBinding createdServiceKey = getServiceKeyResourceByNameAndServiceInstanceGuid(keyModel.getName(), serviceInstance.getGuid()); @@ -87,12 +87,12 @@ private ResponseEntity postServiceKey(String name, Map par String.format(Messages.CANT_CREATE_SERVICE_KEY_FOR_USER_PROVIDED_SERVICE, serviceInstance.getName())); } - return cc.getRestClient() - .post() - .uri(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS) - .body(buildCreateServiceKeyBody(name, parameters, metadata, serviceInstance.getGuid())) - .retrieve() - .toBodilessEntity(); + return client.getRestClient() + .post() + .uri(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS) + .body(buildCreateServiceKeyBody(name, parameters, metadata, serviceInstance.getGuid())) + .retrieve() + .toBodilessEntity(); } private Map buildCreateServiceKeyBody(String name, Map parameters, Metadata metadata, @@ -137,7 +137,7 @@ private V3ServiceBinding getServiceKeyResourceByNameAndServiceInstanceGuid(Strin + CloudControllerV3Endpoints.AMPERSAND_SERVICE_INSTANCE_GUIDS + serviceInstanceGuid + CloudControllerV3Endpoints.AMPERSAND_NAMES + name; - List keys = cc.list(query, new ParameterizedTypeReference>() { + List keys = client.list(query, new ParameterizedTypeReference>() { }); return keys.isEmpty() ? null : keys.getFirst(); @@ -148,14 +148,14 @@ private List listServiceKeyResources(UUID serviceInstanceGuid) + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE + CloudControllerV3Endpoints.AMPERSAND_TYPE + "key" + CloudControllerV3Endpoints.AMPERSAND_SERVICE_INSTANCE_GUIDS + serviceInstanceGuid; - return cc.list(query, new ParameterizedTypeReference>() { + return client.list(query, new ParameterizedTypeReference>() { }); } private Map getServiceKeyCredentials(String keyGuid) { - Optional details = cc.getOptional(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS + "/" + keyGuid - + "/details", - V3ServiceKeyDetails.class); + Optional details = client.getOptional(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS + "/" + keyGuid + + "/details", + V3ServiceKeyDetails.class); return details.map(V3ServiceKeyDetails::credentials) .orElse(Collections.emptyMap()); diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3Operations.java index 8d6cfe3cab..5ca510f314 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3Operations.java @@ -19,12 +19,12 @@ public class ServiceOfferingsV3Operations { private static final ParameterizedTypeReference> PLAN_LIST_TYPE = new ParameterizedTypeReference<>() { }; - private final CloudControllerV3Client cc; - private final CloudSpace target; + private final CloudControllerV3Client client; + private final CloudSpace targetSpace; - public ServiceOfferingsV3Operations(CloudControllerV3Client cc, CloudSpace target) { - this.cc = cc; - this.target = target; + public ServiceOfferingsV3Operations(CloudControllerV3Client client, CloudSpace targetSpace) { + this.client = client; + this.targetSpace = targetSpace; } public List getServiceOfferings() { @@ -37,12 +37,12 @@ private List getServiceOfferingResources() { StringBuilder query = new StringBuilder(CloudControllerV3Endpoints.SERVICE_OFFERINGS + CloudControllerV3Endpoints.QUERY_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE); - if (target != null && target.getGuid() != null) { + if (targetSpace != null && targetSpace.getGuid() != null) { query.append(CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) - .append(target.getGuid()); + .append(targetSpace.getGuid()); } - return cc.list(query.toString(), OFFERING_LIST_TYPE); + return client.list(query.toString(), OFFERING_LIST_TYPE); } private CloudServiceOffering toCloudServiceOfferingWithPlans(V3ServiceOffering serviceOffering) { @@ -55,11 +55,11 @@ private List getServicePlans(String serviceOfferingGuid) { String uri = CloudControllerV3Endpoints.SERVICE_PLANS + CloudControllerV3Endpoints.QUERY_SERVICE_OFFERING_GUIDS + serviceOfferingGuid + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; - - return cc.list(uri, PLAN_LIST_TYPE) - .stream() - .map(V3ServicePlanMapper::toCloudServicePlan) - .toList(); + + return client.list(uri, PLAN_LIST_TYPE) + .stream() + .map(V3ServicePlanMapper::toCloudServicePlan) + .toList(); } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3Operations.java index a6e8f8741b..ff5251c1ba 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3Operations.java @@ -25,12 +25,12 @@ public class ServicePlansV3Operations { private static final ParameterizedTypeReference> PLAN_LIST_TYPE = new ParameterizedTypeReference<>() { }; - private final CloudControllerV3Client cc; - private final CloudSpace target; + private final CloudControllerV3Client client; + private final CloudSpace targetSpace; - public ServicePlansV3Operations(CloudControllerV3Client cc, CloudSpace target) { - this.cc = cc; - this.target = target; + public ServicePlansV3Operations(CloudControllerV3Client client, CloudSpace targetSpace) { + this.client = client; + this.targetSpace = targetSpace; } public void updateServicePlanVisibilityForBroker(String name, ServicePlanVisibility visibility) { @@ -46,12 +46,12 @@ private UUID getRequiredServiceBrokerGuid(String name) { String uri = CloudControllerV3Endpoints.SERVICE_BROKERS + CloudControllerV3Endpoints.QUERY_NAMES + name + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; - return cc.list(uri, BROKER_LIST_TYPE) - .stream() - .findFirst() - .map(broker -> UUID.fromString(broker.guid())) - .orElseThrow(() -> new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, - MessageFormat.format(Messages.SERVICE_BROKER_0_NOT_FOUND, name))); + return client.list(uri, BROKER_LIST_TYPE) + .stream() + .findFirst() + .map(broker -> UUID.fromString(broker.guid())) + .orElseThrow(() -> new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + MessageFormat.format(Messages.SERVICE_BROKER_0_NOT_FOUND, name))); } private List findServicePlanGuidsByBrokerGuid(UUID brokerGuid) { @@ -68,10 +68,10 @@ private List findServicePlanGuidsByBrokerGuid(UUID brokerGuid) { String uri = CloudControllerV3Endpoints.SERVICE_PLANS + CloudControllerV3Endpoints.QUERY_SERVICE_OFFERING_GUIDS + offeringGuidsFilter + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; - return cc.list(uri, PLAN_LIST_TYPE) - .stream() - .map(plan -> UUID.fromString(plan.guid())) - .toList(); + return client.list(uri, PLAN_LIST_TYPE) + .stream() + .map(plan -> UUID.fromString(plan.guid())) + .toList(); } private List findServiceOfferingGuidsByBrokerGuid(UUID brokerGuid) { @@ -80,24 +80,24 @@ private List findServiceOfferingGuidsByBrokerGuid(UUID brokerGuid) { + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE); - if (target != null && target.getGuid() != null) { + if (targetSpace != null && targetSpace.getGuid() != null) { query.append(CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) - .append(target.getGuid()); + .append(targetSpace.getGuid()); } - return cc.list(query.toString(), OFFERING_LIST_TYPE) - .stream() - .map(offering -> UUID.fromString(offering.guid())) - .toList(); + return client.list(query.toString(), OFFERING_LIST_TYPE) + .stream() + .map(offering -> UUID.fromString(offering.guid())) + .toList(); } private void updateServicePlanVisibility(UUID servicePlanGuid, ServicePlanVisibility visibility) { - cc.getRestClient() - .patch() - .uri(CloudControllerV3Endpoints.SERVICE_PLAN_VISIBILITY, servicePlanGuid) - .body(Map.of("type", visibility.toString())) - .retrieve() - .toBodilessEntity(); + client.getRestClient() + .patch() + .uri(CloudControllerV3Endpoints.SERVICE_PLAN_VISIBILITY, servicePlanGuid) + .body(Map.of("type", visibility.toString())) + .retrieve() + .toBodilessEntity(); } @JsonIgnoreProperties(ignoreUnknown = true) diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/StacksV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/StacksV3Operations.java index d8cc570f5a..2fef87b663 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/StacksV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/StacksV3Operations.java @@ -17,10 +17,10 @@ public class StacksV3Operations { private static final ParameterizedTypeReference> STACK_PAGE = new ParameterizedTypeReference<>() { }; - private final CloudControllerV3Client cc; + private final CloudControllerV3Client client; - public StacksV3Operations(CloudControllerV3Client cc) { - this.cc = cc; + public StacksV3Operations(CloudControllerV3Client client) { + this.client = client; } public CloudStack getStack(String name) { @@ -39,20 +39,20 @@ public CloudStack getStack(String name, boolean required) { } public List getStacks() { - return cc.list(CloudControllerV3Endpoints.STACKS + CloudControllerV3Endpoints.QUERY_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE, STACK_PAGE) - .stream() - .map(V3StackMapper::toCloudStack) - .toList(); + return client.list(CloudControllerV3Endpoints.STACKS + CloudControllerV3Endpoints.QUERY_PER_PAGE + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE, STACK_PAGE) + .stream() + .map(V3StackMapper::toCloudStack) + .toList(); } private CloudStack findStackByName(String name) { - return cc.list(CloudControllerV3Endpoints.STACKS + CloudControllerV3Endpoints.QUERY_NAMES + name - + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE, STACK_PAGE) - .stream() - .map(V3StackMapper::toCloudStack) - .findFirst() - .orElse(null); + return client.list(CloudControllerV3Endpoints.STACKS + CloudControllerV3Endpoints.QUERY_NAMES + name + + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE, STACK_PAGE) + .stream() + .map(V3StackMapper::toCloudStack) + .findFirst() + .orElse(null); } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/TasksV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/TasksV3Operations.java index 486480dd58..aa2baa44f5 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/TasksV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/TasksV3Operations.java @@ -19,16 +19,16 @@ public class TasksV3Operations { - private final CloudControllerV3Client cc; - private final CloudSpace target; + private final CloudControllerV3Client client; + private final CloudSpace targetSpace; - public TasksV3Operations(CloudControllerV3Client cc, CloudSpace target) { - this.cc = cc; - this.target = target; + public TasksV3Operations(CloudControllerV3Client client, CloudSpace targetSpace) { + this.client = client; + this.targetSpace = targetSpace; } public CloudTask getTask(UUID taskGuid) { - V3Task task = cc.get(CloudControllerV3Endpoints.TASKS + "/" + taskGuid, V3Task.class); + V3Task task = client.get(CloudControllerV3Endpoints.TASKS + "/" + taskGuid, V3Task.class); return task == null ? null : V3TaskMapper.toCloudTask(task); } @@ -39,7 +39,7 @@ public List getTasks(String applicationName) { String query = CloudControllerV3Endpoints.TASKS + CloudControllerV3Endpoints.QUERY_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE + CloudControllerV3Endpoints.AMPERSAND_APP_GUIDS + applicationGuid; - List tasks = cc.list(query, new ParameterizedTypeReference>() { + List tasks = client.list(query, new ParameterizedTypeReference>() { }); return tasks.stream() @@ -50,22 +50,22 @@ public List getTasks(String applicationName) { public CloudTask runTask(String applicationName, CloudTask task) { UUID applicationGuid = getRequiredApplicationGuid(applicationName); - V3Task created = cc.getRestClient() - .post() - .uri(CloudControllerV3Endpoints.APP_TASKS, applicationGuid) - .body(buildCreateTaskBody(task)) - .retrieve() - .body(V3Task.class); + V3Task created = client.getRestClient() + .post() + .uri(CloudControllerV3Endpoints.APP_TASKS, applicationGuid) + .body(buildCreateTaskBody(task)) + .retrieve() + .body(V3Task.class); return created == null ? null : V3TaskMapper.toCloudTask(created); } public CloudTask cancelTask(UUID taskGuid) { - V3Task cancelled = cc.getRestClient() - .post() - .uri(CloudControllerV3Endpoints.TASK_CANCEL, taskGuid) - .retrieve() - .body(V3Task.class); + V3Task cancelled = client.getRestClient() + .post() + .uri(CloudControllerV3Endpoints.TASK_CANCEL, taskGuid) + .retrieve() + .body(V3Task.class); return cancelled == null ? null : V3TaskMapper.toCloudTask(cancelled); } @@ -93,14 +93,14 @@ private UUID getRequiredApplicationGuid(String applicationName) { StringBuilder query = new StringBuilder(CloudControllerV3Endpoints.APPS + CloudControllerV3Endpoints.QUERY_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE); - if (target != null && target.getGuid() != null) { + if (targetSpace != null && targetSpace.getGuid() != null) { query.append(CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) - .append(target.getGuid()); + .append(targetSpace.getGuid()); } query.append(CloudControllerV3Endpoints.AMPERSAND_NAMES) .append(applicationName); - List apps = cc.list(query.toString(), new ParameterizedTypeReference>() { + List apps = client.list(query.toString(), new ParameterizedTypeReference>() { }); if (apps.isEmpty()) { diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Application.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Application.java index 9c821a7066..2b91f846de 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Application.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Application.java @@ -17,13 +17,14 @@ * */ @JsonIgnoreProperties(ignoreUnknown = true) -public record V3Application(@JsonProperty("guid") String guid, @JsonProperty("name") String name, @JsonProperty("state") String state, - @JsonProperty("created_at") String createdAt, @JsonProperty("updated_at") String updatedAt, - @JsonProperty("lifecycle") V3Lifecycle lifecycle, @JsonProperty("metadata") V3Metadata metadata, - @JsonProperty("relationships") V3Relationships relationships) { +public record V3Application(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.NAME) String name, + @JsonProperty(V3Fields.STATE) String state, + @JsonProperty(V3Fields.CREATED_AT) String createdAt, @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, + @JsonProperty("lifecycle") V3Lifecycle lifecycle, @JsonProperty(V3Fields.METADATA) V3Metadata metadata, + @JsonProperty(V3Fields.RELATIONSHIPS) V3Relationships relationships) { @JsonIgnoreProperties(ignoreUnknown = true) - public record V3Lifecycle(@JsonProperty("type") String type, @JsonProperty("data") V3LifecycleData data) { + public record V3Lifecycle(@JsonProperty(V3Fields.TYPE) String type, @JsonProperty(V3Fields.DATA) V3LifecycleData data) { } @JsonIgnoreProperties(ignoreUnknown = true) @@ -31,15 +32,15 @@ public record V3LifecycleData(@JsonProperty("buildpacks") List buildpack } @JsonIgnoreProperties(ignoreUnknown = true) - public record V3Relationships(@JsonProperty("space") V3ToOneRelationship space) { + public record V3Relationships(@JsonProperty(V3Fields.SPACE) V3ToOneRelationship space) { } @JsonIgnoreProperties(ignoreUnknown = true) - public record V3ToOneRelationship(@JsonProperty("data") V3RelationshipData data) { + public record V3ToOneRelationship(@JsonProperty(V3Fields.DATA) V3RelationshipData data) { } @JsonIgnoreProperties(ignoreUnknown = true) - public record V3RelationshipData(@JsonProperty("guid") String guid) { + public record V3RelationshipData(@JsonProperty(V3Fields.GUID) String guid) { } @JsonIgnoreProperties(ignoreUnknown = true) diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ApplicationMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ApplicationMapper.java index cfab780cd7..71d0436615 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ApplicationMapper.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ApplicationMapper.java @@ -1,6 +1,6 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; -import java.util.Collections; +import java.util.List; import java.util.Map; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; @@ -47,10 +47,10 @@ private static Lifecycle parseLifecycle(V3Application.V3Lifecycle lifecycle) { private static Map extractLifecycleData(V3Application.V3LifecycleData data) { if (data == null) { - return Collections.emptyMap(); + return Map.of(); } - return Map.of(BUILDPACKS, data.buildpacks() == null ? Collections.emptyList() : data.buildpacks(), + return Map.of(BUILDPACKS, data.buildpacks() == null ? List.of() : data.buildpacks(), STACK, data.stack() == null ? "" : data.stack()); } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AuditEvent.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AuditEvent.java index 95907cb759..0004909f3c 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AuditEvent.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AuditEvent.java @@ -14,13 +14,13 @@ * */ @JsonIgnoreProperties(ignoreUnknown = true) -public record V3AuditEvent(@JsonProperty("guid") String guid, @JsonProperty("created_at") String createdAt, - @JsonProperty("updated_at") String updatedAt, @JsonProperty("type") String type, +public record V3AuditEvent(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.CREATED_AT) String createdAt, + @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, @JsonProperty(V3Fields.TYPE) String type, @JsonProperty("actor") V3Participant actor, @JsonProperty("target") V3Participant target) { @JsonIgnoreProperties(ignoreUnknown = true) - public record V3Participant(@JsonProperty("guid") String guid, @JsonProperty("type") String type, - @JsonProperty("name") String name) { + public record V3Participant(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.TYPE) String type, + @JsonProperty(V3Fields.NAME) String name) { } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Build.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Build.java index 15fd5ec357..1c83866a49 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Build.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Build.java @@ -15,21 +15,21 @@ * */ @JsonIgnoreProperties(ignoreUnknown = true) -public record V3Build(@JsonProperty("guid") String guid, @JsonProperty("created_at") String createdAt, - @JsonProperty("updated_at") String updatedAt, @JsonProperty("state") String state, +public record V3Build(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.CREATED_AT) String createdAt, + @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, @JsonProperty(V3Fields.STATE) String state, @JsonProperty("error") String error, @JsonProperty("created_by") V3CreatedBy createdBy, @JsonProperty("package") V3PackageReference inputPackage, @JsonProperty("droplet") V3DropletReference droplet) { @JsonIgnoreProperties(ignoreUnknown = true) - public record V3CreatedBy(@JsonProperty("guid") String guid, @JsonProperty("name") String name) { + public record V3CreatedBy(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.NAME) String name) { } @JsonIgnoreProperties(ignoreUnknown = true) - public record V3PackageReference(@JsonProperty("guid") String guid) { + public record V3PackageReference(@JsonProperty(V3Fields.GUID) String guid) { } @JsonIgnoreProperties(ignoreUnknown = true) - public record V3DropletReference(@JsonProperty("guid") String guid) { + public record V3DropletReference(@JsonProperty(V3Fields.GUID) String guid) { } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Domain.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Domain.java index 2234f4c698..dac0d3242f 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Domain.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Domain.java @@ -14,20 +14,21 @@ * */ @JsonIgnoreProperties(ignoreUnknown = true) -public record V3Domain(@JsonProperty("guid") String guid, @JsonProperty("name") String name, - @JsonProperty("created_at") String createdAt, @JsonProperty("updated_at") String updatedAt, - @JsonProperty("metadata") V3Metadata metadata, @JsonProperty("relationships") V3DomainRelationships relationships) { +public record V3Domain(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.NAME) String name, + @JsonProperty(V3Fields.CREATED_AT) String createdAt, @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, + @JsonProperty(V3Fields.METADATA) V3Metadata metadata, + @JsonProperty(V3Fields.RELATIONSHIPS) V3DomainRelationships relationships) { @JsonIgnoreProperties(ignoreUnknown = true) - public record V3DomainRelationships(@JsonProperty("organization") V3ToOneRelationship organization) { + public record V3DomainRelationships(@JsonProperty(V3Fields.ORGANIZATION) V3ToOneRelationship organization) { } @JsonIgnoreProperties(ignoreUnknown = true) - public record V3ToOneRelationship(@JsonProperty("data") V3RelationshipData data) { + public record V3ToOneRelationship(@JsonProperty(V3Fields.DATA) V3RelationshipData data) { } @JsonIgnoreProperties(ignoreUnknown = true) - public record V3RelationshipData(@JsonProperty("guid") String guid) { + public record V3RelationshipData(@JsonProperty(V3Fields.GUID) String guid) { } public boolean isPrivate() { diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Droplet.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Droplet.java index f69486e983..2c320e70a2 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Droplet.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Droplet.java @@ -13,7 +13,7 @@ * */ @JsonIgnoreProperties(ignoreUnknown = true) -public record V3Droplet(@JsonProperty("guid") String guid, @JsonProperty("links") Map links) { +public record V3Droplet(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty("links") Map links) { @JsonIgnoreProperties(ignoreUnknown = true) public record V3Link(@JsonProperty("href") String href) { diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Fields.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Fields.java new file mode 100644 index 0000000000..a99a3675e1 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Fields.java @@ -0,0 +1,27 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +public class V3Fields { + + public static final String GUID = "guid"; + public static final String NAME = "name"; + public static final String TYPE = "type"; + public static final String STATE = "state"; + public static final String DESCRIPTION = "description"; + public static final String CREATED_AT = "created_at"; + public static final String UPDATED_AT = "updated_at"; + public static final String METADATA = "metadata"; + public static final String DATA = "data"; + public static final String RELATIONSHIPS = "relationships"; + public static final String APP = "app"; + public static final String SPACE = "space"; + public static final String ORGANIZATION = "organization"; + public static final String COMMAND = "command"; + public static final String SERVICE_INSTANCE = "service_instance"; + public static final String PORT = "port"; + public static final String DETAIL = "detail"; + public static final String ENABLED = "enabled"; + + private V3Fields() { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Job.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Job.java index 2354a2134c..74d80d9437 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Job.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Job.java @@ -14,8 +14,8 @@ * */ @JsonIgnoreProperties(ignoreUnknown = true) -public record V3Job(@JsonProperty("guid") String guid, @JsonProperty("created_at") String createdAt, - @JsonProperty("updated_at") String updatedAt, @JsonProperty("state") String state, +public record V3Job(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.CREATED_AT) String createdAt, + @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, @JsonProperty(V3Fields.STATE) String state, @JsonProperty("operation") String operation, @JsonProperty("errors") List errors, @JsonProperty("warnings") List warnings) { @@ -32,11 +32,12 @@ public boolean isTerminal() { } @JsonIgnoreProperties(ignoreUnknown = true) - public record V3Error(@JsonProperty("detail") String detail, @JsonProperty("title") String title, @JsonProperty("code") Integer code) { + public record V3Error(@JsonProperty(V3Fields.DETAIL) String detail, @JsonProperty("title") String title, + @JsonProperty("code") Integer code) { } @JsonIgnoreProperties(ignoreUnknown = true) - public record V3Warning(@JsonProperty("detail") String detail) { + public record V3Warning(@JsonProperty(V3Fields.DETAIL) String detail) { } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Organization.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Organization.java index e7b9a14729..4f83a6f0b0 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Organization.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Organization.java @@ -7,6 +7,6 @@ * Thin Jackson wire-model of a CF v3 organization resource ({@code GET /v3/organizations/{guid}} / {@code GET /v3/organizations?...}). */ @JsonIgnoreProperties(ignoreUnknown = true) -public record V3Organization(@JsonProperty("guid") String guid, @JsonProperty("name") String name, - @JsonProperty("created_at") String createdAt, @JsonProperty("updated_at") String updatedAt) { +public record V3Organization(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.NAME) String name, + @JsonProperty(V3Fields.CREATED_AT) String createdAt, @JsonProperty(V3Fields.UPDATED_AT) String updatedAt) { } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Package.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Package.java index c67ff92e0b..320a89ce41 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Package.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Package.java @@ -19,9 +19,9 @@ * */ @JsonIgnoreProperties(ignoreUnknown = true) -public record V3Package(@JsonProperty("guid") String guid, @JsonProperty("type") String type, @JsonProperty("state") String state, - @JsonProperty("created_at") String createdAt, @JsonProperty("updated_at") String updatedAt, - @JsonProperty("data") V3PackageData data) { +public record V3Package(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.TYPE) String type, @JsonProperty(V3Fields.STATE) String state, + @JsonProperty(V3Fields.CREATED_AT) String createdAt, @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, + @JsonProperty(V3Fields.DATA) V3PackageData data) { @JsonIgnoreProperties(ignoreUnknown = true) public record V3PackageData(@JsonProperty("checksum") V3Checksum checksum, @JsonProperty("error") String error, @@ -30,7 +30,7 @@ public record V3PackageData(@JsonProperty("checksum") V3Checksum checksum, @Json } @JsonIgnoreProperties(ignoreUnknown = true) - public record V3Checksum(@JsonProperty("type") String type, @JsonProperty("value") String value) { + public record V3Checksum(@JsonProperty(V3Fields.TYPE) String type, @JsonProperty("value") String value) { } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Process.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Process.java index d010a950ee..97585100b4 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Process.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Process.java @@ -16,13 +16,13 @@ * */ @JsonIgnoreProperties(ignoreUnknown = true) -public record V3Process(@JsonProperty("guid") String guid, @JsonProperty("command") String command, +public record V3Process(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.COMMAND) String command, @JsonProperty("instances") Integer instances, @JsonProperty("memory_in_mb") Integer memoryInMb, @JsonProperty("disk_in_mb") Integer diskInMb, @JsonProperty("health_check") V3HealthCheck healthCheck, @JsonProperty("readiness_health_check") V3HealthCheck readinessHealthCheck) { @JsonIgnoreProperties(ignoreUnknown = true) - public record V3HealthCheck(@JsonProperty("type") String type, @JsonProperty("data") V3HealthCheckData data) { + public record V3HealthCheck(@JsonProperty(V3Fields.TYPE) String type, @JsonProperty(V3Fields.DATA) V3HealthCheckData data) { } @JsonIgnoreProperties(ignoreUnknown = true) @@ -36,16 +36,16 @@ public record V3ProcessStats(@JsonProperty("resources") List */ @JsonIgnoreProperties(ignoreUnknown = true) -public record V3Role(@JsonProperty("guid") String guid, @JsonProperty("created_at") String createdAt, - @JsonProperty("updated_at") String updatedAt, @JsonProperty("type") String type) { +public record V3Role(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.CREATED_AT) String createdAt, + @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, @JsonProperty(V3Fields.TYPE) String type) { } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Route.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Route.java index b29aa562d7..89134cc01b 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Route.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Route.java @@ -18,37 +18,37 @@ * */ @JsonIgnoreProperties(ignoreUnknown = true) -public record V3Route(@JsonProperty("guid") String guid, @JsonProperty("host") String host, @JsonProperty("path") String path, - @JsonProperty("port") Integer port, @JsonProperty("url") String url, - @JsonProperty("created_at") String createdAt, @JsonProperty("updated_at") String updatedAt, - @JsonProperty("destinations") List destinations, @JsonProperty("metadata") V3Metadata metadata, - @JsonProperty("relationships") V3RouteRelationships relationships) { +public record V3Route(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty("host") String host, @JsonProperty("path") String path, + @JsonProperty(V3Fields.PORT) Integer port, @JsonProperty("url") String url, + @JsonProperty(V3Fields.CREATED_AT) String createdAt, @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, + @JsonProperty("destinations") List destinations, @JsonProperty(V3Fields.METADATA) V3Metadata metadata, + @JsonProperty(V3Fields.RELATIONSHIPS) V3RouteRelationships relationships) { public List destinations() { return destinations == null ? Collections.emptyList() : destinations; } @JsonIgnoreProperties(ignoreUnknown = true) - public record V3Destination(@JsonProperty("guid") String guid, @JsonProperty("app") V3DestinationApp app, - @JsonProperty("port") Integer port, @JsonProperty("weight") Integer weight, + public record V3Destination(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.APP) V3DestinationApp app, + @JsonProperty(V3Fields.PORT) Integer port, @JsonProperty("weight") Integer weight, @JsonProperty("protocol") String protocol) { } @JsonIgnoreProperties(ignoreUnknown = true) - public record V3DestinationApp(@JsonProperty("guid") String guid) { + public record V3DestinationApp(@JsonProperty(V3Fields.GUID) String guid) { } @JsonIgnoreProperties(ignoreUnknown = true) - public record V3RouteRelationships(@JsonProperty("space") V3ToOneRelationship space, + public record V3RouteRelationships(@JsonProperty(V3Fields.SPACE) V3ToOneRelationship space, @JsonProperty("domain") V3ToOneRelationship domain) { } @JsonIgnoreProperties(ignoreUnknown = true) - public record V3ToOneRelationship(@JsonProperty("data") V3RelationshipData data) { + public record V3ToOneRelationship(@JsonProperty(V3Fields.DATA) V3RelationshipData data) { } @JsonIgnoreProperties(ignoreUnknown = true) - public record V3RelationshipData(@JsonProperty("guid") String guid) { + public record V3RelationshipData(@JsonProperty(V3Fields.GUID) String guid) { } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBinding.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBinding.java index 9c24571233..55c03bd0a1 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBinding.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBinding.java @@ -4,9 +4,9 @@ import com.fasterxml.jackson.annotation.JsonProperty; /** - * Thin Jackson wire-model of a CF v3 service credential binding resource ({@code GET/POST/PATCH/DELETE - * /v3/service_credential_bindings}). A binding is either of {@code type=app} (an application bound to a service instance) or - * {@code type=key} (a service key); the {@code relationships.app} is absent for keys. + * Thin Jackson wire-model of a CF v3 service credential binding resource ({@code GET/POST/PATCH/DELETE /v3/service_credential_bindings}). A + * binding is either of {@code type=app} (an application bound to a service instance) or {@code type=key} (a service key); the + * {@code relationships.app} is absent for keys. * *
  * { "guid": "...", "name": "...", "type": "app|key", "created_at": "...", "updated_at": "...",
@@ -18,29 +18,31 @@
  * 
*/ @JsonIgnoreProperties(ignoreUnknown = true) -public record V3ServiceBinding(@JsonProperty("guid") String guid, @JsonProperty("name") String name, @JsonProperty("type") String type, - @JsonProperty("created_at") String createdAt, @JsonProperty("updated_at") String updatedAt, +public record V3ServiceBinding(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.NAME) String name, + @JsonProperty(V3Fields.TYPE) String type, + @JsonProperty(V3Fields.CREATED_AT) String createdAt, @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, @JsonProperty("last_operation") V3LastOperation lastOperation, - @JsonProperty("metadata") V3Metadata metadata, - @JsonProperty("relationships") V3Relationships relationships) { + @JsonProperty(V3Fields.METADATA) V3Metadata metadata, + @JsonProperty(V3Fields.RELATIONSHIPS) V3Relationships relationships) { @JsonIgnoreProperties(ignoreUnknown = true) - public record V3LastOperation(@JsonProperty("type") String type, @JsonProperty("state") String state, - @JsonProperty("description") String description, @JsonProperty("created_at") String createdAt, - @JsonProperty("updated_at") String updatedAt) { + public record V3LastOperation(@JsonProperty(V3Fields.TYPE) String type, @JsonProperty(V3Fields.STATE) String state, + @JsonProperty(V3Fields.DESCRIPTION) String description, + @JsonProperty(V3Fields.CREATED_AT) String createdAt, + @JsonProperty(V3Fields.UPDATED_AT) String updatedAt) { } @JsonIgnoreProperties(ignoreUnknown = true) - public record V3Relationships(@JsonProperty("app") V3ToOneRelationship application, - @JsonProperty("service_instance") V3ToOneRelationship serviceInstance) { + public record V3Relationships(@JsonProperty(V3Fields.APP) V3ToOneRelationship application, + @JsonProperty(V3Fields.SERVICE_INSTANCE) V3ToOneRelationship serviceInstance) { } @JsonIgnoreProperties(ignoreUnknown = true) - public record V3ToOneRelationship(@JsonProperty("data") V3RelationshipData data) { + public record V3ToOneRelationship(@JsonProperty(V3Fields.DATA) V3RelationshipData data) { } @JsonIgnoreProperties(ignoreUnknown = true) - public record V3RelationshipData(@JsonProperty("guid") String guid) { + public record V3RelationshipData(@JsonProperty(V3Fields.GUID) String guid) { } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBroker.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBroker.java index 2fe6d5d6d8..df0022545b 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBroker.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBroker.java @@ -13,21 +13,22 @@ * */ @JsonIgnoreProperties(ignoreUnknown = true) -public record V3ServiceBroker(@JsonProperty("guid") String guid, @JsonProperty("name") String name, @JsonProperty("url") String url, - @JsonProperty("created_at") String createdAt, @JsonProperty("updated_at") String updatedAt, - @JsonProperty("metadata") V3Metadata metadata, - @JsonProperty("relationships") V3Relationships relationships) { +public record V3ServiceBroker(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.NAME) String name, + @JsonProperty("url") String url, + @JsonProperty(V3Fields.CREATED_AT) String createdAt, @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, + @JsonProperty(V3Fields.METADATA) V3Metadata metadata, + @JsonProperty(V3Fields.RELATIONSHIPS) V3Relationships relationships) { @JsonIgnoreProperties(ignoreUnknown = true) - public record V3Relationships(@JsonProperty("space") V3ToOneRelationship space) { + public record V3Relationships(@JsonProperty(V3Fields.SPACE) V3ToOneRelationship space) { } @JsonIgnoreProperties(ignoreUnknown = true) - public record V3ToOneRelationship(@JsonProperty("data") V3RelationshipData data) { + public record V3ToOneRelationship(@JsonProperty(V3Fields.DATA) V3RelationshipData data) { } @JsonIgnoreProperties(ignoreUnknown = true) - public record V3RelationshipData(@JsonProperty("guid") String guid) { + public record V3RelationshipData(@JsonProperty(V3Fields.GUID) String guid) { } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstance.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstance.java index 1f01ad0c34..5a8c9c046e 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstance.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstance.java @@ -18,29 +18,30 @@ * */ @JsonIgnoreProperties(ignoreUnknown = true) -public record V3ServiceInstance(@JsonProperty("guid") String guid, @JsonProperty("name") String name, @JsonProperty("type") String type, - @JsonProperty("created_at") String createdAt, @JsonProperty("updated_at") String updatedAt, +public record V3ServiceInstance(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.NAME) String name, + @JsonProperty(V3Fields.TYPE) String type, + @JsonProperty(V3Fields.CREATED_AT) String createdAt, @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, @JsonProperty("tags") List tags, @JsonProperty("syslog_drain_url") String syslogDrainUrl, @JsonProperty("last_operation") V3LastOperation lastOperation, - @JsonProperty("metadata") V3Metadata metadata, - @JsonProperty("relationships") V3Relationships relationships) { + @JsonProperty(V3Fields.METADATA) V3Metadata metadata, + @JsonProperty(V3Fields.RELATIONSHIPS) V3Relationships relationships) { @JsonIgnoreProperties(ignoreUnknown = true) - public record V3LastOperation(@JsonProperty("type") String type, @JsonProperty("state") String state, - @JsonProperty("description") String description) { + public record V3LastOperation(@JsonProperty(V3Fields.TYPE) String type, @JsonProperty(V3Fields.STATE) String state, + @JsonProperty(V3Fields.DESCRIPTION) String description) { } @JsonIgnoreProperties(ignoreUnknown = true) - public record V3Relationships(@JsonProperty("space") V3ToOneRelationship space, + public record V3Relationships(@JsonProperty(V3Fields.SPACE) V3ToOneRelationship space, @JsonProperty("service_plan") V3ToOneRelationship servicePlan) { } @JsonIgnoreProperties(ignoreUnknown = true) - public record V3ToOneRelationship(@JsonProperty("data") V3RelationshipData data) { + public record V3ToOneRelationship(@JsonProperty(V3Fields.DATA) V3RelationshipData data) { } @JsonIgnoreProperties(ignoreUnknown = true) - public record V3RelationshipData(@JsonProperty("guid") String guid) { + public record V3RelationshipData(@JsonProperty(V3Fields.GUID) String guid) { } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstanceMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstanceMapper.java index 2746c0cb29..7e5ee5a9f4 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstanceMapper.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstanceMapper.java @@ -1,10 +1,14 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; +import java.util.List; + import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceInstance; import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType; import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceOperation; +import static org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType.mapValue; + public final class V3ServiceInstanceMapper { private V3ServiceInstanceMapper() { @@ -34,7 +38,7 @@ public static CloudServiceInstance toCloudServiceInstanceWithoutAuxiliaryContent serviceInstance.updatedAt())) .v3Metadata(V3ResourceMappers.toV3Metadata(serviceInstance.metadata())) .name(serviceInstance.name()) - .tags(serviceInstance.tags() == null ? java.util.Collections.emptyList() + .tags(serviceInstance.tags() == null ? List.of() : serviceInstance.tags()) .build(); } @@ -44,14 +48,7 @@ private static ServiceInstanceType parseType(String type) { return null; } - for (ServiceInstanceType serviceInstanceType : ServiceInstanceType.values()) { - if (serviceInstanceType.getValue() - .equals(type)) { - return serviceInstanceType; - } - } - - return null; + return mapValue(type); } private static ServiceOperation parseLastOperation(V3ServiceInstance.V3LastOperation lastOperation) { diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceOffering.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceOffering.java index f0e64df11f..484576db44 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceOffering.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceOffering.java @@ -17,17 +17,17 @@ * */ @JsonIgnoreProperties(ignoreUnknown = true) -public record V3ServiceOffering(@JsonProperty("guid") String guid, @JsonProperty("name") String name, - @JsonProperty("description") String description, @JsonProperty("available") Boolean available, +public record V3ServiceOffering(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.NAME) String name, + @JsonProperty(V3Fields.DESCRIPTION) String description, @JsonProperty("available") Boolean available, @JsonProperty("shareable") Boolean shareable, @JsonProperty("documentation_url") String documentationUrl, - @JsonProperty("created_at") String createdAt, @JsonProperty("updated_at") String updatedAt, + @JsonProperty(V3Fields.CREATED_AT) String createdAt, @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, @JsonProperty("broker_catalog") V3BrokerCatalog brokerCatalog, - @JsonProperty("relationships") V3Relationships relationships, - @JsonProperty("metadata") V3Metadata metadata) { + @JsonProperty(V3Fields.RELATIONSHIPS) V3Relationships relationships, + @JsonProperty(V3Fields.METADATA) V3Metadata metadata) { @JsonIgnoreProperties(ignoreUnknown = true) - public record V3BrokerCatalog(@JsonProperty("id") String id, @JsonProperty("metadata") Map metadata, + public record V3BrokerCatalog(@JsonProperty("id") String id, @JsonProperty(V3Fields.METADATA) Map metadata, @JsonProperty("features") V3Features features) { } @@ -36,7 +36,7 @@ public record V3Features(@JsonProperty("bindable") Boolean bindable, @JsonProperty("allow_context_updates") Boolean allowContextUpdates, @JsonProperty("bindings_retrievable") Boolean bindingsRetrievable, @JsonProperty("instances_retrievable") Boolean instancesRetrievable, - @JsonProperty("plan_updateable") Boolean planUpdateable) { + @JsonProperty("plan_updatable") Boolean planUpdatable) { } @JsonIgnoreProperties(ignoreUnknown = true) @@ -44,11 +44,11 @@ public record V3Relationships(@JsonProperty("service_broker") V3ToOneRelationshi } @JsonIgnoreProperties(ignoreUnknown = true) - public record V3ToOneRelationship(@JsonProperty("data") V3RelationshipData data) { + public record V3ToOneRelationship(@JsonProperty(V3Fields.DATA) V3RelationshipData data) { } @JsonIgnoreProperties(ignoreUnknown = true) - public record V3RelationshipData(@JsonProperty("guid") String guid) { + public record V3RelationshipData(@JsonProperty(V3Fields.GUID) String guid) { } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServicePlan.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServicePlan.java index f7da724b1c..62e0654e52 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServicePlan.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServicePlan.java @@ -18,14 +18,14 @@ * */ @JsonIgnoreProperties(ignoreUnknown = true) -public record V3ServicePlan(@JsonProperty("guid") String guid, @JsonProperty("name") String name, - @JsonProperty("description") String description, @JsonProperty("free") Boolean free, - @JsonProperty("visibility_type") String visibilityType, @JsonProperty("created_at") String createdAt, - @JsonProperty("updated_at") String updatedAt, @JsonProperty("broker_catalog") V3BrokerCatalog brokerCatalog, - @JsonProperty("relationships") V3Relationships relationships, @JsonProperty("metadata") V3Metadata metadata) { +public record V3ServicePlan(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.NAME) String name, + @JsonProperty(V3Fields.DESCRIPTION) String description, @JsonProperty("free") Boolean free, + @JsonProperty("visibility_type") String visibilityType, @JsonProperty(V3Fields.CREATED_AT) String createdAt, + @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, @JsonProperty("broker_catalog") V3BrokerCatalog brokerCatalog, + @JsonProperty(V3Fields.RELATIONSHIPS) V3Relationships relationships, @JsonProperty(V3Fields.METADATA) V3Metadata metadata) { @JsonIgnoreProperties(ignoreUnknown = true) - public record V3BrokerCatalog(@JsonProperty("id") String id, @JsonProperty("metadata") Map metadata) { + public record V3BrokerCatalog(@JsonProperty("id") String id, @JsonProperty(V3Fields.METADATA) Map metadata) { } @JsonIgnoreProperties(ignoreUnknown = true) @@ -33,11 +33,11 @@ public record V3Relationships(@JsonProperty("service_offering") V3ToOneRelations } @JsonIgnoreProperties(ignoreUnknown = true) - public record V3ToOneRelationship(@JsonProperty("data") V3RelationshipData data) { + public record V3ToOneRelationship(@JsonProperty(V3Fields.DATA) V3RelationshipData data) { } @JsonIgnoreProperties(ignoreUnknown = true) - public record V3RelationshipData(@JsonProperty("guid") String guid) { + public record V3RelationshipData(@JsonProperty(V3Fields.GUID) String guid) { } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Space.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Space.java index 70e8c3f4bd..e62062173b 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Space.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Space.java @@ -12,8 +12,10 @@ * */ @JsonIgnoreProperties(ignoreUnknown = true) -public record V3Space(@JsonProperty("guid") String guid, @JsonProperty("name") String name, @JsonProperty("created_at") String createdAt, - @JsonProperty("updated_at") String updatedAt, @JsonProperty("relationships") V3SpaceRelationships relationships) { +public record V3Space(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.NAME) String name, + @JsonProperty(V3Fields.CREATED_AT) String createdAt, + @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, + @JsonProperty(V3Fields.RELATIONSHIPS) V3SpaceRelationships relationships) { public String organizationGuid() { if (relationships == null || relationships.organization() == null || relationships.organization() @@ -27,15 +29,15 @@ public String organizationGuid() { } @JsonIgnoreProperties(ignoreUnknown = true) - public record V3SpaceRelationships(@JsonProperty("organization") V3ToOne organization) { + public record V3SpaceRelationships(@JsonProperty(V3Fields.ORGANIZATION) V3ToOne organization) { } @JsonIgnoreProperties(ignoreUnknown = true) - public record V3ToOne(@JsonProperty("data") V3Data data) { + public record V3ToOne(@JsonProperty(V3Fields.DATA) V3Data data) { } @JsonIgnoreProperties(ignoreUnknown = true) - public record V3Data(@JsonProperty("guid") String guid) { + public record V3Data(@JsonProperty(V3Fields.GUID) String guid) { } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Stack.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Stack.java index d1ba56ffe3..e3823f4e91 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Stack.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Stack.java @@ -13,8 +13,8 @@ * */ @JsonIgnoreProperties(ignoreUnknown = true) -public record V3Stack(@JsonProperty("guid") String guid, @JsonProperty("name") String name, - @JsonProperty("description") String description, @JsonProperty("created_at") String createdAt, - @JsonProperty("updated_at") String updatedAt, @JsonProperty("metadata") V3Metadata metadata) { +public record V3Stack(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.NAME) String name, + @JsonProperty(V3Fields.DESCRIPTION) String description, @JsonProperty(V3Fields.CREATED_AT) String createdAt, + @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, @JsonProperty(V3Fields.METADATA) V3Metadata metadata) { } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Task.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Task.java index bddbd82ec6..918d711236 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Task.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Task.java @@ -15,11 +15,12 @@ * */ @JsonIgnoreProperties(ignoreUnknown = true) -public record V3Task(@JsonProperty("guid") String guid, @JsonProperty("name") String name, @JsonProperty("command") String command, - @JsonProperty("state") String state, @JsonProperty("memory_in_mb") Integer memoryInMb, +public record V3Task(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.NAME) String name, + @JsonProperty(V3Fields.COMMAND) String command, + @JsonProperty(V3Fields.STATE) String state, @JsonProperty("memory_in_mb") Integer memoryInMb, @JsonProperty("disk_in_mb") Integer diskInMb, @JsonProperty("result") V3Result result, - @JsonProperty("created_at") String createdAt, @JsonProperty("updated_at") String updatedAt, - @JsonProperty("metadata") V3Metadata metadata) { + @JsonProperty(V3Fields.CREATED_AT) String createdAt, @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, + @JsonProperty(V3Fields.METADATA) V3Metadata metadata) { @JsonIgnoreProperties(ignoreUnknown = true) public record V3Result(@JsonProperty("failure_reason") String failureReason) { diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/util/UriUtil.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/util/UriUtil.java index fb1d4a9647..be8f03d3be 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/util/UriUtil.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/util/UriUtil.java @@ -1,9 +1,18 @@ package org.cloudfoundry.multiapps.controller.client.facade.util; -import org.springframework.web.util.UriUtils; - +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; import java.nio.charset.Charset; +import java.text.MessageFormat; import java.util.List; +import java.util.Map; + +import org.cloudfoundry.multiapps.common.util.MiscUtil; +import org.cloudfoundry.multiapps.controller.Constants; +import org.cloudfoundry.multiapps.controller.Messages; +import org.cloudfoundry.multiapps.controller.client.facade.CloudException; +import org.springframework.web.util.UriUtils; public class UriUtil { @@ -12,9 +21,71 @@ private UriUtil() { } public static String encodeChars(String queryParam, List charsToEncode) { - for (String charToEncode: charsToEncode) { + for (String charToEncode : charsToEncode) { queryParam = queryParam.replaceAll(charToEncode, UriUtils.encode(charToEncode, Charset.defaultCharset())); } return queryParam; } + + public static URL extractBaseUrl(URL url) { + try { + int port = url.getPort(); + String origin = url.getProtocol() + Constants.PROTOCOL_SEPARATOR + url.getHost(); + + if (port != Constants.UNDEFINED_PORT) { + origin = origin + Constants.COLON + port; + } + + return URI.create(origin) + .toURL(); + } catch (MalformedURLException e) { + throw new IllegalArgumentException(MessageFormat.format(Messages.COULD_NOT_DETERMINE_API_ORIGIN_FROM_0, url), e); + } + } + + private static URL logCacheFallbackUrl(URL controllerUrl) { + String host = controllerUrl.getHost(); + + String logCacheHost = host.startsWith(Constants.API_HOST_PREFIX) ? Constants.LOG_CACHE_PREFIX + host.substring( + Constants.API_HOST_PREFIX.length()) : Constants.LOG_CACHE_PREFIX + host; + try { + return URI.create(controllerUrl.getProtocol() + Constants.PROTOCOL_SEPARATOR + logCacheHost) + .toURL(); + } catch (MalformedURLException me) { + throw new CloudException(MessageFormat.format(Messages.COULD_NOT_RESOLVE_LOG_CACHE_URL_FROM_0, controllerUrl), me); + } + } + + private static URL apiFallbackUrl(URL controllerUrl, String pathSuffix) { + try { + return URI.create(controllerUrl.toString() + pathSuffix) + .toURL(); + } catch (MalformedURLException e) { + throw new IllegalArgumentException( + MessageFormat.format(Messages.COULD_NOT_RESOLVE_URL_FROM_0_1, controllerUrl, pathSuffix), e); + } + } + + public static URL resolveV3ApiUrl(URL controllerUrl, Map rootDocumentLinks) { + try { + Map cloudControllerUrlV3 = MiscUtil.cast(rootDocumentLinks.get( + Constants.CLOUD_CONTROLLER_CF_ROOT_DOCUMENT_NAME)); + return URI.create((String) cloudControllerUrlV3.get(Constants.HREF)) + .toURL(); + } catch (Exception e) { + return apiFallbackUrl(controllerUrl, Constants.CF_API_V3); + } + } + + public static URL resolveLogCacheUrl(URL controllerUrl, Map rootDocumentLinks) { + try { + Map logCacheUrl = MiscUtil.cast(rootDocumentLinks.get(Constants.LOG_CACHE_CF_ROOT_DOCUMENT_NAME)); + + return URI.create((String) logCacheUrl.get(Constants.HREF)) + .toURL(); + } catch (Exception e) { + return logCacheFallbackUrl(controllerUrl); + } + } + } diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/ApplicationsCloudControllerClientIntegrationTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/ApplicationsCloudControllerClientIntegrationTest.java index 95ca5e50bd..88961bcc18 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/ApplicationsCloudControllerClientIntegrationTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/ApplicationsCloudControllerClientIntegrationTest.java @@ -1,7 +1,14 @@ package org.cloudfoundry.multiapps.controller.client.facade; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; -import org.cloudfoundry.multiapps.controller.client.facade.domain.HealthCheckType; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudBuild; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudMetadata; @@ -9,6 +16,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudProcess; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudRoute; import org.cloudfoundry.multiapps.controller.client.facade.domain.DockerInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.HealthCheckType; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudApplication; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudDomain; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudRoute; @@ -16,6 +24,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableStaging; import org.cloudfoundry.multiapps.controller.client.facade.domain.InstancesInfo; import org.cloudfoundry.multiapps.controller.client.facade.domain.LifecycleType; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.Staging; import org.cloudfoundry.multiapps.controller.client.facade.domain.Status; import org.cloudfoundry.multiapps.controller.client.facade.dto.ApplicationToCreateDto; @@ -26,15 +35,6 @@ import org.junit.jupiter.api.Test; import org.springframework.http.HttpStatus; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; - import static org.cloudfoundry.multiapps.controller.client.facade.IntegrationTestConstants.APPLICATION_HOST; import static org.cloudfoundry.multiapps.controller.client.facade.IntegrationTestConstants.DEFAULT_DOMAIN; import static org.cloudfoundry.multiapps.controller.client.facade.IntegrationTestConstants.DISK_IN_MB; @@ -85,7 +85,7 @@ void createApplication() { String applicationName = "test-app-1"; Staging staging = ImmutableStaging.builder() .addBuildpack(JAVA_BUILDPACK) - .healthCheckType(HealthCheckType.PROCESS.getValue()) + .healthCheckType(HealthCheckType.PROCESS.toString()) .healthCheckHttpEndpoint(HEALTH_CHECK_ENDPOINT) .healthCheckTimeout(HEALTH_CHECK_TIMEMOUT) .build(); @@ -158,7 +158,7 @@ void updateApplicationHealthcheckType() { try { createAndVerifyDefaultApplication(applicationName); client.updateApplicationStaging(applicationName, ImmutableStaging.builder() - .healthCheckType(HealthCheckType.PROCESS.getValue()) + .healthCheckType(HealthCheckType.PROCESS.toString()) .build()); UUID applicationGuid = client.getApplicationGuid(applicationName); CloudProcess cloudProcess = client.getApplicationProcess(applicationGuid); diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3OperationsTest.java index 7e66681a35..424ab98053 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3OperationsTest.java @@ -39,9 +39,9 @@ class DomainsV3OperationsTest { private static final String DOMAIN_NAME = "example.com"; @Mock - private CloudControllerV3Client cc; + private CloudControllerV3Client client; @Mock - private CloudSpace target; + private CloudSpace targetSpace; private DomainsV3Operations operations; @@ -52,9 +52,9 @@ class DomainsV3OperationsTest { void setUp() throws Exception { MockitoAnnotations.openMocks(this) .close(); - operations = new DomainsV3Operations(cc, target); + operations = new DomainsV3Operations(client, targetSpace); factory = MockControllerClientFactory.create(); - serverOperations = new DomainsV3Operations(factory.client(), target); + serverOperations = new DomainsV3Operations(factory.client(), targetSpace); } @Test @@ -98,7 +98,7 @@ void testGetSharedDomainsReturnsEmptyWhenNoDomains() { @Test void testAddDomainCreatesWhenAbsent() { - when(target.getOrganization()) + when(targetSpace.getOrganization()) .thenReturn(getOrganizationWithGuid(ORG_GUID)); stubEmptyDomainLookup(DOMAIN_NAME); @@ -173,7 +173,7 @@ void testDeleteDomainThrowsWhenSpaceNotProvided() { @Test void testGetDefaultDomainReturnsMappedDomain() { - when(target.getOrganization()) + when(targetSpace.getOrganization()) .thenReturn(getOrganizationWithGuid(ORG_GUID)); factory.server() @@ -191,7 +191,7 @@ void testGetDefaultDomainReturnsMappedDomain() { @Test void testGetDomainsForOrganizationReturnsMappedDomains() { - when(target.getOrganization()) + when(targetSpace.getOrganization()) .thenReturn(getOrganizationWithGuid(ORG_GUID)); factory.server() @@ -211,7 +211,7 @@ void testGetDomainsForOrganizationReturnsMappedDomains() { @Test void testGetDomainsForOrganizationReturnsEmptyWhenNoDomains() { - when(target.getOrganization()) + when(targetSpace.getOrganization()) .thenReturn(getOrganizationWithGuid(ORG_GUID)); factory.server() @@ -261,8 +261,8 @@ private static String getDomainResourceJson(String guid, String name) { } private void mockDomainList(V3Domain... domains) { - when(cc.list(ArgumentMatchers.anyString(), - ArgumentMatchers.>> any())) + when(client.list(ArgumentMatchers.anyString(), + ArgumentMatchers.>> any())) .thenReturn(List.of(domains)); } diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/EventsV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/EventsV3OperationsTest.java index 26f565f5da..a520836beb 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/EventsV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/EventsV3OperationsTest.java @@ -30,9 +30,9 @@ class EventsV3OperationsTest { private static final UUID TARGET_GUID = UUID.fromString("99999999-8888-7777-6666-555555555555"); @Mock - private CloudControllerV3Client cc; + private CloudControllerV3Client client; @Mock - private CloudSpace target; + private CloudSpace targetSpace; private EventsV3Operations operations; @@ -40,13 +40,13 @@ class EventsV3OperationsTest { void setUp() throws Exception { MockitoAnnotations.openMocks(this) .close(); - operations = new EventsV3Operations(cc, target); + operations = new EventsV3Operations(client, targetSpace); } @Test void testGetEventsMapsResults() { - when(cc.list(ArgumentMatchers.anyString(), - ArgumentMatchers.>> any())).thenReturn( + when(client.list(ArgumentMatchers.anyString(), + ArgumentMatchers.>> any())).thenReturn( List.of(getAuditEvent("audit.app.update"), getAuditEvent("audit.app.create"))); List result = operations.getEvents(); @@ -59,13 +59,13 @@ ArgumentMatchers.>> any( @Test void testGetEventsByTargetIncludesTargetGuidInQuery() { when( - cc.list(ArgumentMatchers.anyString(), ArgumentMatchers.>> any())) + client.list(ArgumentMatchers.anyString(), ArgumentMatchers.>> any())) .thenReturn(List.of()); operations.getEventsByTarget(TARGET_GUID); ArgumentCaptor uriCaptor = ArgumentCaptor.forClass(String.class); - verify(cc) + verify(client) .list(uriCaptor.capture(), ArgumentMatchers.>> any()); Assertions.assertTrue(uriCaptor.getValue() .contains("/v3/audit_events"), uriCaptor.getValue()); @@ -75,11 +75,11 @@ void testGetEventsByTargetIncludesTargetGuidInQuery() { @Test void testGetApplicationEventsThrowsWhenApplicationNotFound() { - when(target.getGuid()) + when(targetSpace.getGuid()) .thenReturn(SPACE_GUID); - when(cc.list(ArgumentMatchers.contains("/v3/apps"), - ArgumentMatchers.>> any())) + when(client.list(ArgumentMatchers.contains("/v3/apps"), + ArgumentMatchers.>> any())) .thenReturn(List.of()); CloudOperationException thrown = Assertions.assertThrows(CloudOperationException.class, @@ -90,19 +90,19 @@ ArgumentMatchers.>> any @Test void testGetApplicationEventsQueryScopesAppsToSpaceWhenTargetPresent() { - when(target.getGuid()) + when(targetSpace.getGuid()) .thenReturn(SPACE_GUID); - when(cc.list(ArgumentMatchers.contains("/v3/apps"), - ArgumentMatchers.>> any())) + when(client.list(ArgumentMatchers.contains("/v3/apps"), + ArgumentMatchers.>> any())) .thenReturn(List.of(new V3Application(GUID_STRING, "my-app", "STARTED", null, null, null, null, null))); - when(cc.list(ArgumentMatchers.contains("/v3/audit_events"), - ArgumentMatchers.>> any())) + when(client.list(ArgumentMatchers.contains("/v3/audit_events"), + ArgumentMatchers.>> any())) .thenReturn(List.of()); operations.getApplicationEvents("my-app"); ArgumentCaptor uriCaptor = ArgumentCaptor.forClass(String.class); - verify(cc, times(2)) + verify(client, times(2)) .list(uriCaptor.capture(), ArgumentMatchers.>> any()); String appsQuery = uriCaptor.getAllValues() .stream() diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3OperationsTest.java index 5b7c9367cf..91046db235 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3OperationsTest.java @@ -27,9 +27,9 @@ class ServiceOfferingsV3OperationsTest { private static final UUID SPACE_GUID = UUID.fromString("11111111-2222-3333-4444-555555555555"); @Mock - private CloudControllerV3Client cc; + private CloudControllerV3Client client; @Mock - private CloudSpace target; + private CloudSpace targetSpace; private ServiceOfferingsV3Operations operations; @@ -37,16 +37,16 @@ class ServiceOfferingsV3OperationsTest { void setUp() throws Exception { MockitoAnnotations.openMocks(this) .close(); - operations = new ServiceOfferingsV3Operations(cc, target); + operations = new ServiceOfferingsV3Operations(client, targetSpace); } @Test void testGetServiceOfferingsMapsOfferingWithItsPlans() { - when(cc.list(ArgumentMatchers.contains("/v3/service_offerings"), - ArgumentMatchers.>> any())) + when(client.list(ArgumentMatchers.contains("/v3/service_offerings"), + ArgumentMatchers.>> any())) .thenReturn(List.of(getOffering())); - when(cc.list(ArgumentMatchers.contains("/v3/service_plans"), - ArgumentMatchers.>> any())) + when(client.list(ArgumentMatchers.contains("/v3/service_plans"), + ArgumentMatchers.>> any())) .thenReturn(List.of(getPlan("plan-a"), getPlan("plan-b"))); List result = operations.getServiceOfferings(); @@ -61,17 +61,17 @@ ArgumentMatchers.>> any @Test void testGetServiceOfferingsScopesQueryToSpaceWhenTargetHasGuid() { - when(target.getGuid()) + when(targetSpace.getGuid()) .thenReturn(SPACE_GUID); - when(cc.list(ArgumentMatchers.contains("/v3/service_offerings"), - ArgumentMatchers.>> any())) + when(client.list(ArgumentMatchers.contains("/v3/service_offerings"), + ArgumentMatchers.>> any())) .thenReturn(List.of()); operations.getServiceOfferings(); ArgumentCaptor uriCaptor = ArgumentCaptor.forClass(String.class); - verify(cc) + verify(client) .list(uriCaptor.capture(), ArgumentMatchers.>> any()); Assertions.assertTrue(uriCaptor.getValue() .contains("space_guids=" + SPACE_GUID), uriCaptor.getValue()); @@ -79,17 +79,17 @@ ArgumentMatchers.>> @Test void testGetServiceOfferingsExcludesSpaceGuidWhenTargetGuidNull() { - when(target.getGuid()) + when(targetSpace.getGuid()) .thenReturn(null); - when(cc.list(ArgumentMatchers.contains("/v3/service_offerings"), - ArgumentMatchers.>> any())) + when(client.list(ArgumentMatchers.contains("/v3/service_offerings"), + ArgumentMatchers.>> any())) .thenReturn(List.of()); operations.getServiceOfferings(); ArgumentCaptor uriCaptor = ArgumentCaptor.forClass(String.class); - verify(cc) + verify(client) .list(uriCaptor.capture(), ArgumentMatchers.>> any()); Assertions.assertFalse(uriCaptor.getValue() .contains("space_guids="), uriCaptor.getValue()); diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/StacksV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/StacksV3OperationsTest.java index 230b32c968..bac9f68266 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/StacksV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/StacksV3OperationsTest.java @@ -24,7 +24,7 @@ class StacksV3OperationsTest { private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; @Mock - private CloudControllerV3Client cc; + private CloudControllerV3Client client; private StacksV3Operations operations; @@ -32,7 +32,7 @@ class StacksV3OperationsTest { void setUp() throws Exception { MockitoAnnotations.openMocks(this) .close(); - operations = new StacksV3Operations(cc); + operations = new StacksV3Operations(client); } @Test @@ -80,7 +80,7 @@ void testGetStacksQueriesStacksEndpointWithPageSize() { operations.getStacks(); ArgumentCaptor uriCaptor = ArgumentCaptor.forClass(String.class); - verify(cc) + verify(client) .list(uriCaptor.capture(), ArgumentMatchers.>> any()); Assertions.assertTrue(uriCaptor.getValue() .contains("/v3/stacks"), uriCaptor.getValue()); @@ -89,8 +89,8 @@ void testGetStacksQueriesStacksEndpointWithPageSize() { } private void mockStackList(V3Stack... stacks) { - when(cc.list(ArgumentMatchers.anyString(), - ArgumentMatchers.>> any())) + when(client.list(ArgumentMatchers.anyString(), + ArgumentMatchers.>> any())) .thenReturn(List.of(stacks)); } diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/BuildApplicationDeployModelStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/BuildApplicationDeployModelStep.java index 1bcfd82f39..47ea04dc72 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/BuildApplicationDeployModelStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/BuildApplicationDeployModelStep.java @@ -8,8 +8,8 @@ import jakarta.inject.Inject; import jakarta.inject.Named; import org.apache.commons.lang3.StringUtils; -import org.cloudfoundry.multiapps.controller.client.facade.domain.HealthCheckType; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudRoute; +import org.cloudfoundry.multiapps.controller.client.facade.domain.HealthCheckType; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableStaging; import org.cloudfoundry.multiapps.controller.client.facade.domain.Staging; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudApplicationExtended; @@ -85,16 +85,16 @@ private AdditionalModuleParametersReporter buildAdditionalModuleParametersReport private Staging modifyHealthCheckType(Staging staging) { String healthCheckType = staging.getHealthCheckType(); if (StringUtils.isEmpty(healthCheckType)) { - getStepLogger().debug(Messages.NOT_SPECIFIED_HEALTH_CHECK_TYPE, HealthCheckType.PORT.getValue()); + getStepLogger().debug(Messages.NOT_SPECIFIED_HEALTH_CHECK_TYPE, HealthCheckType.PORT.toString()); return ImmutableStaging.copyOf(staging) - .withHealthCheckType(HealthCheckType.PORT.getValue()); + .withHealthCheckType(HealthCheckType.PORT.toString()); } - if (HealthCheckType.NONE.getValue() + if (HealthCheckType.NONE.toString() .equalsIgnoreCase(healthCheckType)) { getStepLogger().info(Messages.USING_DEPRECATED_HEALTH_CHECK_TYPE_0_SETTING_TO_1, healthCheckType, - HealthCheckType.PROCESS.getValue()); + HealthCheckType.PROCESS.toString()); return ImmutableStaging.copyOf(staging) - .withHealthCheckType(HealthCheckType.PROCESS.getValue()); + .withHealthCheckType(HealthCheckType.PROCESS.toString()); } return staging; } From 7da4e024dd3a73a8398686e5a515f5e7968b242a Mon Sep 17 00:00:00 2001 From: Krasimir Kargov Date: Tue, 6 Oct 2026 11:12:21 +0300 Subject: [PATCH 11/15] Fixing comments from Yavor LMCROSSITXSADEPLOY-3563 --- .../src/main/java/module-info.java | 17 +- .../multiapps/controller/Constants.java | 2 + .../client/facade/domain/CloudAsyncJob.java | 1 - .../facade/domain/ServiceInstanceType.java | 4 +- .../facade/rest/ApplicationsV3Operations.java | 159 ++---- .../facade/rest/BuildsV3Operations.java | 18 +- .../CloudControllerRestClientBuilder.java | 38 +- .../CloudControllerRestClientFactory.java | 8 +- .../facade/rest/CloudControllerV3Client.java | 35 ++ .../facade/rest/DomainsV3Operations.java | 92 ++-- .../facade/rest/EventsV3Operations.java | 22 +- .../facade/rest/OperationsQueryUtil.java | 69 +++ .../client/facade/rest/OperationsUtil.java | 497 ++++++++++++++++++ .../facade/rest/PackagesV3Operations.java | 71 +-- .../facade/rest/ProcessesV3Operations.java | 137 +---- .../facade/rest/RoutesV3Operations.java | 219 ++------ .../rest/ServiceBindingsV3Operations.java | 154 ++---- .../rest/ServiceBrokersV3Operations.java | 114 +--- .../rest/ServiceInstancesV3Operations.java | 254 +++------ .../facade/rest/ServiceKeysV3Operations.java | 83 +-- .../rest/ServiceOfferingsV3Operations.java | 8 +- .../facade/rest/ServicePlansV3Operations.java | 37 +- .../client/facade/rest/TasksV3Operations.java | 60 +-- .../facade/rest/resources/V3Fields.java | 36 +- .../rest/resources/V3GuidReference.java | 8 + .../facade/rest/resources/V3JobMapper.java | 2 +- .../facade/rest/resources/V3RouteMapper.java | 10 +- .../resources/V3ServiceInstanceMapper.java | 4 +- .../rest/ApplicationsV3OperationsTest.java | 137 ++--- .../facade/rest/BuildsV3OperationsTest.java | 35 +- .../CloudControllerRestClientV3ImplTest.java | 191 ++----- .../rest/CloudControllerV3ClientTest.java | 52 +- .../facade/rest/CloudSpaceClientTest.java | 41 +- .../facade/rest/DomainsV3OperationsTest.java | 45 +- .../facade/rest/EventsV3OperationsTest.java | 10 +- .../rest/MockControllerClientFactory.java | 19 + .../facade/rest/PackagesV3OperationsTest.java | 45 +- .../rest/ProcessesV3OperationsTest.java | 110 +--- .../facade/rest/RolesV3OperationsTest.java | 21 +- .../facade/rest/RoutesV3OperationsTest.java | 106 +--- .../rest/ServiceBindingsV3OperationsTest.java | 48 +- .../rest/ServiceBrokersV3OperationsTest.java | 43 +- .../ServiceInstancesV3OperationsTest.java | 117 ++--- .../rest/ServiceKeysV3OperationsTest.java | 26 +- .../rest/ServicePlansV3OperationsTest.java | 20 +- .../facade/rest/TasksV3OperationsTest.java | 5 +- .../resources/V3ApplicationMapperTest.java | 18 +- .../rest/resources/V3JobMapperTest.java | 5 +- 48 files changed, 1304 insertions(+), 1949 deletions(-) create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/OperationsQueryUtil.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/OperationsUtil.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3GuidReference.java diff --git a/multiapps-controller-client/src/main/java/module-info.java b/multiapps-controller-client/src/main/java/module-info.java index dfd1045713..3aafdc0b6c 100644 --- a/multiapps-controller-client/src/main/java/module-info.java +++ b/multiapps-controller-client/src/main/java/module-info.java @@ -16,29 +16,30 @@ requires transitive spring.web; requires com.fasterxml.jackson.databind; + requires java.desktop; + requires java.net.http; + requires io.netty.handler; + requires io.netty.transport; requires org.apache.commons.collections4; requires org.apache.commons.io; requires org.apache.commons.logging; requires org.cloudfoundry.multiapps.common; + requires org.reactivestreams; requires org.slf4j; - requires java.net.http; - requires spring.core; - requires spring.webflux; requires reactor.core; + requires reactor.netty; requires reactor.netty.core; requires reactor.netty.http; - requires org.reactivestreams; - requires io.netty.handler; - requires io.netty.transport; - requires reactor.netty; + requires spring.core; requires spring.security.core; requires spring.security.oauth2.client; + requires spring.webflux; requires static com.fasterxml.jackson.annotation; requires static java.compiler; requires static jakarta.inject; requires static org.immutables.value; requires io.netty.codec; - requires java.desktop; + requires org.apache.logging.log4j; } \ No newline at end of file diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Constants.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Constants.java index 6d883ecb72..253ac729aa 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Constants.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Constants.java @@ -42,6 +42,8 @@ public class Constants { public static final String PACKAGE_LINK = "package"; public static final String WEB_PROCESS_TYPE = "web"; + public static final String USER_PROVIDED = "user-provided"; + private Constants() { } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudAsyncJob.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudAsyncJob.java index d458160350..20b52febc8 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudAsyncJob.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudAsyncJob.java @@ -11,7 +11,6 @@ @JsonDeserialize(as = ImmutableCloudAsyncJob.class) public abstract class CloudAsyncJob extends CloudEntity implements Derivable { - @Nullable public abstract JobState getState(); @Nullable diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/ServiceInstanceType.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/ServiceInstanceType.java index ac93bcb13e..1e3bbefcc2 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/ServiceInstanceType.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/ServiceInstanceType.java @@ -23,7 +23,7 @@ public String getValue() { @JsonCreator public static ServiceInstanceType from(String value) { - ServiceInstanceType serviceInstanceType = mapValue(value); + ServiceInstanceType serviceInstanceType = mapTypeToServiceInstanceType(value); if (serviceInstanceType != null) { return serviceInstanceType; } @@ -31,7 +31,7 @@ public static ServiceInstanceType from(String value) { throw new IllegalArgumentException(MessageFormat.format(Messages.UNKNOWN_SERVICE_INSTANCE_TYPE_0, value)); } - public static ServiceInstanceType mapValue(String value) { + public static ServiceInstanceType mapTypeToServiceInstanceType(String value) { for (ServiceInstanceType type : values()) { if (type.value.equals(value)) { return type; diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3Operations.java index ac4f318cac..9e040e2caf 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3Operations.java @@ -12,17 +12,18 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Staging; import org.cloudfoundry.multiapps.controller.client.facade.dto.ApplicationToCreateDto; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Application; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ApplicationMapper; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Fields; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; public class ApplicationsV3Operations { - private static final ParameterizedTypeReference> APPLICATION_PAGE = new ParameterizedTypeReference<>() { + private static final ParameterizedTypeReference> APP_PAGE = new ParameterizedTypeReference<>() { }; private static final Duration DELETE_JOB_TIMEOUT = Duration.ofMinutes(5); @@ -38,7 +39,10 @@ public ApplicationsV3Operations(CloudControllerV3Client client, CloudSpace targe public UUID createApplication(ApplicationToCreateDto dto) { validateTargetSpace(); - Map body = buildApplicationBody(dto); + String targetSpaceGuid = targetSpace.getGuid() + .toString(); + Map body = OperationsUtil.buildApplicationBody(dto, targetSpaceGuid); + UUID appGuid = sendCreateApplicationRequest(body); scaleApplicationIfNeeded(appGuid, dto); @@ -53,44 +57,19 @@ private void validateTargetSpace() { } } - private Map buildApplicationBody(ApplicationToCreateDto dto) { - Map body = new HashMap<>(); - body.put("name", dto.getName()); - body.put("lifecycle", buildLifecycle(dto.getStaging())); - body.put("relationships", Map.of("space", Map.of("data", Map.of("guid", targetSpace.getGuid() - .toString())))); - if (dto.getEnv() != null) { - body.put("environment_variables", dto.getEnv()); - } - - if (dto.getMetadata() != null) { - body.put("metadata", Map.of("labels", dto.getMetadata() - .getLabels(), - "annotations", dto.getMetadata() - .getAnnotations())); - } - - return body; - } - private UUID sendCreateApplicationRequest(Map body) { - V3Application created = client.getRestClient() - .post() - .uri(CloudControllerV3Endpoints.APPS) - .body(body) - .retrieve() - .body(V3Application.class); + V3Application created = client.postForObject(CloudControllerV3Endpoints.APPS, body, V3Application.class); return UUID.fromString(created.guid()); } private void scaleApplicationIfNeeded(UUID appGuid, ApplicationToCreateDto dto) { Map scale = new HashMap<>(); if (dto.getMemoryInMb() != null) { - scale.put("memory_in_mb", dto.getMemoryInMb()); + scale.put(V3Fields.MEMORY_IN_MB, dto.getMemoryInMb()); } if (dto.getDiskQuotaInMb() != null) { - scale.put("disk_in_mb", dto.getDiskQuotaInMb()); + scale.put(V3Fields.DISK_IN_MB, dto.getDiskQuotaInMb()); } if (!scale.isEmpty()) { @@ -98,40 +77,10 @@ private void scaleApplicationIfNeeded(UUID appGuid, ApplicationToCreateDto dto) } } - private Map buildLifecycle(Staging staging) { - if (staging == null) { - return Map.of("type", "buildpack", "data", Map.of()); - } - - if (staging.getDockerInfo() != null) { - return Map.of("type", "docker", "data", Map.of()); - } - - String type = staging.getLifecycleType() != null ? staging.getLifecycleType() - .name() - .toLowerCase() - : "buildpack"; - - Map data = new HashMap<>(); - if (staging.getBuildpacks() != null) { - data.put("buildpacks", staging.getBuildpacks()); - } - - if (staging.getStackName() != null) { - data.put("stack", staging.getStackName()); - } - - return Map.of("type", type, "data", data); - } - public void deleteApplication(String applicationName) { UUID applicationGuid = getApplicationGuid(applicationName); - var response = client.getRestClient() - .delete() - .uri(CloudControllerV3Endpoints.APP_BY_GUID, applicationGuid) - .retrieve() - .toBodilessEntity(); + ResponseEntity response = client.delete(CloudControllerV3Endpoints.APP_BY_GUID, applicationGuid); client.followAsyncJob(response, DELETE_JOB_TIMEOUT); } @@ -180,13 +129,15 @@ public Map getApplicationEnvironment(String applicationName) { } public List getApplications() { - return listApplications(applicationsQuery(null)).stream() - .map(app -> V3ApplicationMapper.toCloudApplication(app, targetSpace)) - .toList(); + String applicationsQuery = OperationsQueryUtil.buildApplicationsQuery(null, targetSpace); + + return listApplications(applicationsQuery).stream() + .map(app -> V3ApplicationMapper.toCloudApplication(app, targetSpace)) + .toList(); } public List getApplicationsByMetadataLabelSelector(String labelSelector) { - String query = applicationsQuery(null); + String query = OperationsQueryUtil.buildApplicationsQuery(null, targetSpace); if (labelSelector != null) { query = query + CloudControllerV3Endpoints.AMPERSAND_LABEL_SELECTOR + labelSelector; @@ -200,33 +151,20 @@ public List getApplicationsByMetadataLabelSelector(String labe public void startApplication(String applicationName) { UUID guid = getApplicationGuid(applicationName); - client.getRestClient() - .post() - .uri(CloudControllerV3Endpoints.APP_START, guid) - .retrieve() - .toBodilessEntity(); + client.post(CloudControllerV3Endpoints.APP_START, null, guid); } public void stopApplication(String applicationName) { UUID guid = getApplicationGuid(applicationName); - client.getRestClient() - .post() - .uri(CloudControllerV3Endpoints.APP_STOP, guid) - .retrieve() - .toBodilessEntity(); + client.post(CloudControllerV3Endpoints.APP_STOP, null, guid); } public void rename(String applicationName, String newName) { UUID guid = getApplicationGuid(applicationName); try { - client.getRestClient() - .patch() - .uri(CloudControllerV3Endpoints.APP_BY_GUID, guid) - .body(Map.of("name", newName)) - .retrieve() - .toBodilessEntity(); + client.patch(CloudControllerV3Endpoints.APP_BY_GUID, Map.of(V3Fields.NAME, newName), guid); } catch (CloudOperationException e) { //the Cloud Controller can return 503 but the rename might have happened already if (e.getStatusCode() == HttpStatus.SERVICE_UNAVAILABLE && newName.equals(getApplicationName(guid))) { @@ -237,80 +175,45 @@ public void rename(String applicationName, String newName) { } public void updateApplicationInstances(String applicationName, int instances) { - scaleWebProcess(getApplicationGuid(applicationName), Map.of("instances", instances)); + scaleWebProcess(getApplicationGuid(applicationName), Map.of(V3Fields.INSTANCES, instances)); } public void updateApplicationMemory(String applicationName, int memory) { - scaleWebProcess(getApplicationGuid(applicationName), Map.of("memory_in_mb", memory)); + scaleWebProcess(getApplicationGuid(applicationName), Map.of(V3Fields.MEMORY_IN_MB, memory)); } public void updateApplicationDiskQuota(String applicationName, int disk) { - scaleWebProcess(getApplicationGuid(applicationName), Map.of("disk_in_mb", disk)); + scaleWebProcess(getApplicationGuid(applicationName), Map.of(V3Fields.DISK_IN_MB, disk)); } public void updateApplicationEnv(String applicationName, Map env) { UUID guid = getApplicationGuid(applicationName); - client.getRestClient() - .patch() - .uri(CloudControllerV3Endpoints.APP_ENV_VARS, guid) - .body(Map.of("var", env)) - .retrieve() - .toBodilessEntity(); + client.patch(CloudControllerV3Endpoints.APP_ENV_VARS, Map.of(V3Fields.VAR, env), guid); } public void bindDropletToApp(UUID dropletGuid, UUID applicationGuid) { - client.getRestClient() - .patch() - .uri(CloudControllerV3Endpoints.APP_CURRENT_DROPLET, applicationGuid) - .body(Map.of("data", Map.of("guid", dropletGuid.toString()))) - .retrieve() - .toBodilessEntity(); + client.patch(CloudControllerV3Endpoints.APP_CURRENT_DROPLET, Map.of(V3Fields.DATA, Map.of(V3Fields.GUID, dropletGuid.toString())), + applicationGuid); } public void updateApplicationMetadata(UUID guid, Metadata metadata) { - client.getRestClient() - .patch() - .uri(CloudControllerV3Endpoints.APP_BY_GUID, guid) - .body(Map.of("metadata", Map.of("labels", metadata.getLabels(), "annotations", metadata.getAnnotations()))) - .retrieve() - .toBodilessEntity(); + client.patch(CloudControllerV3Endpoints.APP_BY_GUID, Map.of(V3Fields.METADATA, OperationsUtil.toMetadataMap(metadata)), guid); } private void scaleWebProcess(UUID applicationGuid, Map scaleBody) { - client.getRestClient() - .post() - .uri(CloudControllerV3Endpoints.APP_WEB_PROCESS_SCALE, applicationGuid) - .body(scaleBody) - .retrieve() - .toBodilessEntity(); + client.post(CloudControllerV3Endpoints.APP_WEB_PROCESS_SCALE, scaleBody, applicationGuid); } private V3Application findApplicationByName(String applicationName) { - List apps = listApplications(applicationsQuery(applicationName)); + String applicationsQuery = OperationsQueryUtil.buildApplicationsQuery(applicationName, targetSpace); + List apps = listApplications(applicationsQuery); return apps.isEmpty() ? null : apps.getFirst(); } private List listApplications(String query) { - return client.list(query, APPLICATION_PAGE); - } - - private String applicationsQuery(String name) { - StringBuilder query = new StringBuilder(CloudControllerV3Endpoints.APPS + CloudControllerV3Endpoints.QUERY_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE); - - if (targetSpace != null && targetSpace.getGuid() != null) { - query.append(CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) - .append(targetSpace.getGuid()); - } - - if (name != null) { - query.append(CloudControllerV3Endpoints.AMPERSAND_NAMES) - .append(name); - } - - return query.toString(); + return client.list(query, APP_PAGE); } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3Operations.java index 8f752b29bf..5e2243d23c 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3Operations.java @@ -10,12 +10,16 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudBuild; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Build; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3BuildMapper; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Fields; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpStatus; public class BuildsV3Operations { + private static final ParameterizedTypeReference> BUILD_PAGE = new ParameterizedTypeReference<>() { + }; + private final CloudControllerV3Client client; public BuildsV3Operations(CloudControllerV3Client client) { @@ -23,12 +27,8 @@ public BuildsV3Operations(CloudControllerV3Client client) { } public CloudBuild createBuild(UUID packageGuid) { - V3Build build = client.getRestClient() - .post() - .uri(CloudControllerV3Endpoints.BUILDS) - .body(Map.of("package", Map.of("guid", packageGuid.toString()))) - .retrieve() - .body(V3Build.class); + V3Build build = client.postForObject(CloudControllerV3Endpoints.BUILDS, + Map.of(V3Fields.PACKAGE, Map.of(V3Fields.GUID, packageGuid.toString())), V3Build.class); return V3BuildMapper.toCloudBuild(build); } @@ -48,8 +48,7 @@ public List getBuildsForApplication(UUID applicationGuid) { String uri = CloudControllerV3Endpoints.APPS + "/" + applicationGuid + "/builds" + CloudControllerV3Endpoints.QUERY_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; - return client.list(uri, new ParameterizedTypeReference>() { - }) + return client.list(uri, BUILD_PAGE) .stream() .map(V3BuildMapper::toCloudBuild) .toList(); @@ -59,8 +58,7 @@ public List getBuildsForPackage(UUID packageGuid) { String uri = CloudControllerV3Endpoints.BUILDS + CloudControllerV3Endpoints.QUERY_PACKAGE_GUIDS + packageGuid + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; - return client.list(uri, new ParameterizedTypeReference>() { - }) + return client.list(uri, BUILD_PAGE) .stream() .map(V3BuildMapper::toCloudBuild) .toList(); diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientBuilder.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientBuilder.java index 1ea4e993e1..ea447bf6a7 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientBuilder.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientBuilder.java @@ -17,6 +17,7 @@ import org.cloudfoundry.multiapps.controller.Messages; import org.cloudfoundry.multiapps.controller.client.facade.oauth2.OAuthClient; import org.springframework.http.HttpHeaders; +import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ReactorClientHttpRequestFactory; import org.springframework.http.client.reactive.ReactorClientHttpConnector; import org.springframework.http.converter.ByteArrayHttpMessageConverter; @@ -39,11 +40,6 @@ public final class CloudControllerRestClientBuilder { private CloudControllerRestClientBuilder() { } - public static RestClient build(URL v3ApiUrl, OAuthClient oAuthClient, Map requestTags, - ClientConfigurationOptions configOptions) { - return buildRestClient(buildHttpClient(configOptions), v3ApiUrl, oAuthClient, requestTags); - } - public static RestClient buildRestClient(HttpClient httpClient, URL baseUrl, OAuthClient oAuthClient, Map requestTags) { return RestClient.builder() @@ -53,21 +49,25 @@ public static RestClient buildRestClient(HttpClient httpClient, URL baseUrl, OAu new StringHttpMessageConverter(), new FormHttpMessageConverter(), new MappingJackson2HttpMessageConverter(OBJECT_MAPPER))) - .requestInterceptor((request, body, execution) -> { - String authorization = oAuthClient.getAuthorizationHeaderValue(); + .requestInterceptor(restClientAuthAndTagsInterceptor(oAuthClient, requestTags)) + .defaultStatusHandler(new CloudControllerResponseErrorHandler()) + .build(); + } - if (authorization != null) { - request.getHeaders() - .set(HttpHeaders.AUTHORIZATION, authorization); - } + private static ClientHttpRequestInterceptor restClientAuthAndTagsInterceptor(OAuthClient oAuthClient, Map requestTags) { + return (request, body, execution) -> { + String authorization = oAuthClient.getAuthorizationHeaderValue(); - requestTags.forEach((String key, String value) -> request.getHeaders() - .set(key, value)); + if (authorization != null) { + request.getHeaders() + .set(HttpHeaders.AUTHORIZATION, authorization); + } - return execution.execute(request, body); - }) - .defaultStatusHandler(new CloudControllerResponseErrorHandler()) - .build(); + requestTags.forEach((String key, String value) -> request.getHeaders() + .set(key, value)); + + return execution.execute(request, body); + }; } public static WebClient buildWebClient(HttpClient httpClient, URL baseUrl, OAuthClient oAuthClient, @@ -75,11 +75,11 @@ public static WebClient buildWebClient(HttpClient httpClient, URL baseUrl, OAuth return WebClient.builder() .baseUrl(baseUrl.toString()) .clientConnector(new ReactorClientHttpConnector(httpClient)) - .filter(authAndTagsFilter(oAuthClient, requestTags)) + .filter(webClientAuthAndTagsFilter(oAuthClient, requestTags)) .build(); } - private static ExchangeFilterFunction authAndTagsFilter(OAuthClient oAuthClient, Map requestTags) { + private static ExchangeFilterFunction webClientAuthAndTagsFilter(OAuthClient oAuthClient, Map requestTags) { return (request, next) -> { ClientRequest.Builder requestBuilder = ClientRequest.from(request); String authorizationValue = oAuthClient.getAuthorizationHeaderValue(); diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientFactory.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientFactory.java index 70053e1236..78e8a2f782 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientFactory.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientFactory.java @@ -64,12 +64,8 @@ public CloudControllerRestClient createClient(URL controllerUrl, CloudCredential CloudControllerV3Client client = buildCloudControllerV3Client(httpClient, baseUrl, oAuthClient, requestTags); Function uploadRestClientFactory = uploadTimeout -> { - ClientConfigurationOptions uploadOptions = new ClientConfigurationOptions(getConnectTimeout(), Optional.of(uploadTimeout), - getSslHandshakeTimeout(), getConnectionPoolSize(), - Optional.empty(), - shouldTrustSelfSignedCertificates()); - - return CloudControllerRestClientBuilder.build(baseUrl, oAuthClient, requestTags, uploadOptions); + HttpClient uploadHttpClient = httpClient.responseTimeout(uploadTimeout); + return CloudControllerRestClientBuilder.buildRestClient(uploadHttpClient, baseUrl, oAuthClient, requestTags); }; return new CloudControllerRestClientV3Impl(target, client, uploadRestClientFactory); diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Client.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Client.java index 3a2df48acd..7b323b36aa 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Client.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Client.java @@ -45,6 +45,41 @@ public T get(String uri, Class responseType) { .body(responseType); } + public T postForObject(String uri, Object body, Class responseType, Object... uriVars) { + RestClient.RequestBodySpec spec = restClient.post() + .uri(uri, uriVars); + if (body != null) { + spec.body(body); + } + return spec.retrieve() + .body(responseType); + } + + public ResponseEntity post(String uri, Object body, Object... uriVars) { + RestClient.RequestBodySpec spec = restClient.post() + .uri(uri, uriVars); + if (body != null) { + spec.body(body); + } + return spec.retrieve() + .toBodilessEntity(); + } + + public ResponseEntity patch(String uri, Object body, Object... uriVars) { + return restClient.patch() + .uri(uri, uriVars) + .body(body) + .retrieve() + .toBodilessEntity(); + } + + public ResponseEntity delete(String uri, Object... uriVars) { + return restClient.delete() + .uri(uri, uriVars) + .retrieve() + .toBodilessEntity(); + } + private T get(String uri, ParameterizedTypeReference responseType) { return restClient.get() .uri(uri) diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3Operations.java index 448d191bdb..cf3b88b42e 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3Operations.java @@ -4,15 +4,16 @@ import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.function.Predicate; import org.cloudfoundry.multiapps.controller.Constants; import org.cloudfoundry.multiapps.controller.Messages; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudDomain; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEntity; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Domain; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3DomainMapper; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Fields; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpStatus; @@ -21,7 +22,7 @@ public class DomainsV3Operations { - private static final ParameterizedTypeReference> DOMAIN_LIST_TYPE = new ParameterizedTypeReference<>() { + private static final ParameterizedTypeReference> DOMAIN_PAGE = new ParameterizedTypeReference<>() { }; private final CloudControllerV3Client client; @@ -37,19 +38,25 @@ public void addDomain(String domainName) { CloudDomain domain = findDomainByName(domainName); if (domain == null) { - doCreateDomain(domainName); + createDomain(domainName); } } public void deleteDomain(String domainName) { assertSpaceProvided("delete domain"); - CloudDomain domain = findDomainByName(domainName, true); - doDeleteDomain(domain.getGuid()); + CloudDomain domain = findDomainByName(domainName); + + if (domain == null) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + MessageFormat.format(Messages.DOMAIN_0_NOT_FOUND, domainName)); + } + + deleteDomain(domain.getGuid()); } public CloudDomain getDefaultDomain() { - UUID organizationGuid = getTargetOrganizationGuid(); + UUID organizationGuid = OperationsUtil.getGuid(targetSpace.getOrganization()); V3Domain domain = client.get(CloudControllerV3Endpoints.ORGANIZATIONS + "/" + organizationGuid + "/domains/default", V3Domain.class); @@ -62,31 +69,32 @@ public CloudDomain getDefaultDomain() { } public List getDomains() { - return getAllDomains().stream() - .map(V3DomainMapper::toCloudDomain) - .toList(); + return filterAndMapDomains(domain -> true); } public List getSharedDomains() { - return getAllDomains().stream() - .filter(domain -> !domain.isPrivate()) - .map(V3DomainMapper::toCloudDomain) - .toList(); + return filterAndMapDomains(domain -> !domain.isPrivate()); } public List getPrivateDomains() { + return filterAndMapDomains(domain -> domain.isPrivate()); + } + + private List filterAndMapDomains(Predicate filterPredicate) { return getAllDomains().stream() - .filter(V3Domain::isPrivate) + .filter(filterPredicate) .map(V3DomainMapper::toCloudDomain) .toList(); } public List getDomainsForOrganization() { assertSpaceProvided("access organization domains"); - String uri = CloudControllerV3Endpoints.ORGANIZATIONS + "/" + getTargetOrganizationGuid() + "/domains" + UUID targetOrganizationGuid = OperationsUtil.getGuid(targetSpace.getOrganization()); + + String uri = CloudControllerV3Endpoints.ORGANIZATIONS + "/" + targetOrganizationGuid + "/domains" + CloudControllerV3Endpoints.QUERY_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; - return client.list(uri, DOMAIN_LIST_TYPE) + return client.list(uri, DOMAIN_PAGE) .stream() .map(V3DomainMapper::toCloudDomain) .toList(); @@ -94,72 +102,36 @@ public List getDomainsForOrganization() { private List getAllDomains() { return client.list(CloudControllerV3Endpoints.DOMAINS + CloudControllerV3Endpoints.QUERY_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE, DOMAIN_LIST_TYPE); - } - - private CloudDomain findDomainByName(String name, boolean required) { - CloudDomain domain = findDomainByName(name); - - if (domain == null && required) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, - MessageFormat.format(Messages.DOMAIN_0_NOT_FOUND, name)); - } - - return domain; + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE, DOMAIN_PAGE); } private CloudDomain findDomainByName(String name) { String uri = CloudControllerV3Endpoints.DOMAINS + CloudControllerV3Endpoints.QUERY_NAMES + name + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; - return client.list(uri, DOMAIN_LIST_TYPE) + return client.list(uri, DOMAIN_PAGE) .stream() .findFirst() .map(V3DomainMapper::toCloudDomain) .orElse(null); } - private void doCreateDomain(String name) { - UUID organizationGuid = getTargetOrganizationGuid(); + private void createDomain(String name) { + UUID organizationGuid = OperationsUtil.getGuid(targetSpace.getOrganization()); if (organizationGuid == null) { throw new CloudOperationException(HttpStatus.BAD_REQUEST, Messages.BAD_REQUEST, MessageFormat.format(Messages.CANNOT_CREATE_DOMAIN_0_WITHOUT_ORGANIZATION, name)); } - client.getRestClient() - .post() - .uri(CloudControllerV3Endpoints.DOMAINS) - .body(Map.of("name", name, "relationships", organizationRelationship(organizationGuid))) - .retrieve() - .toBodilessEntity(); - } - - private Map organizationRelationship(UUID organizationGuid) { - return Map.of("organization", Map.of("data", Map.of("guid", organizationGuid.toString()))); + Map organizationRelationship = OperationsUtil.organizationRelationship(organizationGuid); + client.post(CloudControllerV3Endpoints.DOMAINS, Map.of(V3Fields.NAME, name, V3Fields.RELATIONSHIPS, organizationRelationship)); } - private void doDeleteDomain(UUID guid) { - ResponseEntity response = client.getRestClient() - .delete() - .uri(CloudControllerV3Endpoints.DOMAIN_BY_GUID, guid.toString()) - .retrieve() - .toEntity(Void.class); + private void deleteDomain(UUID guid) { + ResponseEntity response = client.delete(CloudControllerV3Endpoints.DOMAIN_BY_GUID, guid.toString()); client.followAsyncJob(response, Constants.DELETE_JOB_TIMEOUT); } - private UUID getTargetOrganizationGuid() { - return getGuid(targetSpace.getOrganization()); - } - - private UUID getGuid(CloudEntity entity) { - if (entity == null || entity.getMetadata() == null) { - return null; - } - - return entity.getMetadata() - .getGuid(); - } - private void assertSpaceProvided(String operation) { Assert.notNull(targetSpace, "Unable to " + operation + " without specifying organization and space to use."); } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/EventsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/EventsV3Operations.java index abb177ef21..0ab6741bdf 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/EventsV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/EventsV3Operations.java @@ -8,9 +8,9 @@ import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEvent; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; -import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Application; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3AuditEvent; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3AuditEventMapper; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3GuidReference; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpStatus; @@ -19,7 +19,7 @@ public class EventsV3Operations { private static final ParameterizedTypeReference> EVENT_PAGE = new ParameterizedTypeReference<>() { }; - private static final ParameterizedTypeReference> APPLICATION_PAGE = new ParameterizedTypeReference<>() { + private static final ParameterizedTypeReference> APP_PAGE = new ParameterizedTypeReference<>() { }; private final CloudControllerV3Client client; @@ -55,7 +55,8 @@ public List getApplicationEvents(String applicationName) { } private UUID getRequiredApplicationGuid(String applicationName) { - List apps = client.list(buildApplicationQuery(applicationName), APPLICATION_PAGE); + String applicationsQuery = OperationsQueryUtil.buildApplicationsQuery(applicationName, targetSpace); + List apps = client.list(applicationsQuery, APP_PAGE); if (apps.isEmpty()) { throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, @@ -66,19 +67,4 @@ private UUID getRequiredApplicationGuid(String applicationName) { .guid()); } - private String buildApplicationQuery(String applicationName) { - StringBuilder query = new StringBuilder(CloudControllerV3Endpoints.APPS + CloudControllerV3Endpoints.QUERY_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE); - - if (targetSpace != null && targetSpace.getGuid() != null) { - query.append(CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) - .append(targetSpace.getGuid()); - } - - query.append(CloudControllerV3Endpoints.AMPERSAND_NAMES) - .append(applicationName); - - return query.toString(); - } - } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/OperationsQueryUtil.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/OperationsQueryUtil.java new file mode 100644 index 0000000000..d56cbe0766 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/OperationsQueryUtil.java @@ -0,0 +1,69 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; + +public class OperationsQueryUtil { + + private OperationsQueryUtil() { + } + + public static String buildApplicationsQuery(String name, CloudSpace targetSpace) { + StringBuilder query = new StringBuilder(CloudControllerV3Endpoints.APPS + CloudControllerV3Endpoints.QUERY_PER_PAGE + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE); + + if (targetSpace != null && targetSpace.getGuid() != null) { + query.append(CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) + .append(targetSpace.getGuid()); + } + + if (name != null) { + query.append(CloudControllerV3Endpoints.AMPERSAND_NAMES) + .append(name); + } + + return query.toString(); + } + + public static String buildRoutesQuery(UUID domainGuid, String host, String path, UUID targetSpaceGuid) { + StringBuilder query = new StringBuilder( + CloudControllerV3Endpoints.ROUTES + CloudControllerV3Endpoints.QUERY_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE); + + query.append(CloudControllerV3Endpoints.AMPERSAND_DOMAIN_GUIDS) + .append(domainGuid); + + query.append(CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) + .append(targetSpaceGuid); + + if (host != null) { + query.append(CloudControllerV3Endpoints.AMPERSAND_HOSTS) + .append(host); + } + + if (path != null) { + query.append(CloudControllerV3Endpoints.AMPERSAND_PATHS) + .append(path); + } + + return query.toString(); + } + + public static String buildServiceInstancesQuery(String name, CloudSpace targetSpace) { + StringBuilder query = new StringBuilder(CloudControllerV3Endpoints.SERVICE_INSTANCES + CloudControllerV3Endpoints.QUERY_PER_PAGE + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE); + + if (targetSpace != null && targetSpace.getGuid() != null) { + query.append(CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) + .append(targetSpace.getGuid()); + } + + if (name != null) { + query.append(CloudControllerV3Endpoints.AMPERSAND_NAMES) + .append(name); + } + + return query.toString(); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/OperationsUtil.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/OperationsUtil.java new file mode 100644 index 0000000000..41fd8531b0 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/OperationsUtil.java @@ -0,0 +1,497 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.net.URI; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +import org.cloudfoundry.multiapps.common.util.MapUtil; +import org.cloudfoundry.multiapps.controller.Constants; +import org.cloudfoundry.multiapps.controller.Messages; +import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEntity; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudRoute; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBroker; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudTask; +import org.cloudfoundry.multiapps.controller.client.facade.domain.DropletInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableDropletInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Staging; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Status; +import org.cloudfoundry.multiapps.controller.client.facade.dto.ApplicationToCreateDto; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Droplet; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Fields; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServicePlan; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +public class OperationsUtil { + + private OperationsUtil() { + } + + public static Map buildApplicationBody(ApplicationToCreateDto dto, String targetSpaceGuid) { + Map body = new HashMap<>(); + body.put(V3Fields.NAME, dto.getName()); + body.put(V3Fields.LIFECYCLE, buildLifecycle(dto.getStaging())); + body.put(V3Fields.RELATIONSHIPS, + java.util.Map.of(V3Fields.SPACE, java.util.Map.of(V3Fields.DATA, java.util.Map.of(V3Fields.GUID, targetSpaceGuid)))); + if (dto.getEnv() != null) { + body.put(V3Fields.ENVIRONMENT_VARIABLES, dto.getEnv()); + } + + if (dto.getMetadata() != null) { + body.put(V3Fields.METADATA, java.util.Map.of(V3Fields.LABELS, dto.getMetadata() + .getLabels(), + V3Fields.ANNOTATIONS, dto.getMetadata() + .getAnnotations())); + } + + return body; + } + + private static Map buildLifecycle(Staging staging) { + if (staging == null) { + return java.util.Map.of(V3Fields.TYPE, "buildpack", V3Fields.DATA, java.util.Map.of()); + } + + if (staging.getDockerInfo() != null) { + return Map.of(V3Fields.TYPE, "docker", V3Fields.DATA, Map.of()); + } + + String type = staging.getLifecycleType() != null ? staging.getLifecycleType() + .name() + .toLowerCase() + : "buildpack"; + + Map data = new HashMap<>(); + if (staging.getBuildpacks() != null) { + data.put(V3Fields.BUILDPACKS, staging.getBuildpacks()); + } + + if (staging.getStackName() != null) { + data.put(V3Fields.STACK, staging.getStackName()); + } + + return Map.of(V3Fields.TYPE, type, V3Fields.DATA, data); + } + + public static DropletInfo parseDropletInfo(V3Droplet droplet) { + String packageUrl = droplet.links() + .get(Constants.PACKAGE_LINK) + .href(); + + if (packageUrl.endsWith("/")) { + packageUrl = packageUrl.substring(0, packageUrl.lastIndexOf("/")); + } + + String packageGuid = packageUrl.substring(packageUrl.lastIndexOf("/") + 1); + return ImmutableDropletInfo.builder() + .guid(UUID.fromString(droplet.guid())) + .packageGuid(UUID.fromString(packageGuid)) + .build(); + } + + public static Map buildApplicationLifecycle(Staging staging) { + if (staging.getDockerInfo() != null) { + return Map.of(V3Fields.TYPE, "docker", V3Fields.DATA, Map.of()); + } + + String lifecycleType = staging.getLifecycleType() != null ? staging.getLifecycleType() + .name() + .toLowerCase() + : "buildpack"; + + if (Constants.CLOUD_NATIVE_BUILDPACK.equals(lifecycleType) && (staging.getBuildpacks() == null || staging.getBuildpacks() + .isEmpty())) { + throw new IllegalArgumentException(Messages.BUILDPACKS_ARE_REQUIRED_FOR_THE_BUILDPACK_LIFECYCLE_TYPE); + } + + Map data = new HashMap<>(); + if (staging.getStackName() != null) { + data.put(V3Fields.STACK, staging.getStackName()); + } + + if (staging.getBuildpacks() != null) { + data.put(V3Fields.BUILDPACKS, staging.getBuildpacks()); + } + + return Map.of(V3Fields.TYPE, lifecycleType, V3Fields.DATA, data); + } + + private static Map buildHealthCheck(Staging staging) { + Map data = new HashMap<>(); + MapUtil.addNonNull(data, V3Fields.ENDPOINT, staging.getHealthCheckHttpEndpoint()); + MapUtil.addNonNull(data, V3Fields.TIMEOUT, staging.getHealthCheckTimeout()); + MapUtil.addNonNull(data, V3Fields.INVOCATION_TIMEOUT, staging.getInvocationTimeout()); + MapUtil.addNonNull(data, V3Fields.INTERVAL, staging.getHealthCheckInterval()); + + Map healthCheck = new HashMap<>(); + healthCheck.put(V3Fields.TYPE, staging.getHealthCheckType()); + healthCheck.put(V3Fields.DATA, data); + return healthCheck; + } + + private static Map buildReadinessHealthCheck(Staging staging) { + Map data = new HashMap<>(); + MapUtil.addNonNull(data, V3Fields.INVOCATION_TIMEOUT, staging.getReadinessHealthCheckInvocationTimeout()); + MapUtil.addNonNull(data, V3Fields.ENDPOINT, staging.getReadinessHealthCheckHttpEndpoint()); + MapUtil.addNonNull(data, V3Fields.INTERVAL, staging.getReadinessHealthCheckInterval()); + + Map readinessHealthCheck = new HashMap<>(); + readinessHealthCheck.put(V3Fields.TYPE, staging.getReadinessHealthCheckType()); + readinessHealthCheck.put(V3Fields.DATA, data); + return readinessHealthCheck; + } + + public static Map buildUpdatedProcessBody(Staging staging) { + Map updatedProcessBody = new HashMap<>(); + updatedProcessBody.put(V3Fields.COMMAND, staging.getCommand()); + + if (staging.getHealthCheckType() != null) { + updatedProcessBody.put(V3Fields.HEALTH_CHECK, buildHealthCheck(staging)); + } + + if (staging.getReadinessHealthCheckType() != null) { + updatedProcessBody.put(V3Fields.READINESS_HEALTH_CHECK, buildReadinessHealthCheck(staging)); + } + + return updatedProcessBody; + } + + public static Map buildCreateRouteBody(UUID domainGuid, String host, String path, UUID targetSpaceGuid) { + Map relationships = java.util.Map.of(V3Fields.DOMAIN, buildSingleResourceRelationship(domainGuid), + V3Fields.SPACE, buildSingleResourceRelationship(targetSpaceGuid)); + + return Map.of(V3Fields.HOST, host == null ? "" : host, + V3Fields.PATH, path == null ? "" : path, + V3Fields.RELATIONSHIPS, relationships); + } + + private static Map buildSingleResourceRelationship(UUID guid) { + return Map.of(V3Fields.DATA, Map.of(V3Fields.GUID, guid.toString())); + } + + public static Map createDestination(UUID applicationGuid, String protocol) { + if (protocol == null) { + return Map.of(V3Fields.APP, Map.of(V3Fields.GUID, applicationGuid.toString())); + } + + return Map.of(V3Fields.APP, Map.of(V3Fields.GUID, applicationGuid.toString()), V3Fields.PROTOCOL, protocol); + } + + public static Set getOutdatedRoutes(UUID applicationGuid, List currentRoutes, Set updatedRoutes) { + return currentRoutes.stream() + .filter(currentRoute -> isRouteOutdated(applicationGuid, currentRoute, updatedRoutes)) + .collect(Collectors.toSet()); + } + + private static boolean isRouteOutdated(UUID applicationGuid, CloudRoute currentRoute, Set updatedRoutes) { + Optional updatedRoute = findRoute(currentRoute.getUrl(), updatedRoutes); + + return updatedRoute.map(cloudRoute -> isProtocolChanged(applicationGuid, currentRoute, cloudRoute)) + .orElse(true); + + } + + public static Set getNewRoutes(UUID applicationGuid, List currentRoutes, Set updatedRoutes) { + return updatedRoutes.stream() + .filter(updatedRoute -> isRouteUpdated(applicationGuid, updatedRoute, currentRoutes)) + .collect(Collectors.toSet()); + } + + private static boolean isRouteUpdated(UUID applicationGuid, CloudRoute updatedRoute, List currentRoutes) { + Optional currentRoute = findRoute(updatedRoute.getUrl(), currentRoutes); + + return currentRoute.map(cloudRoute -> isProtocolChanged(applicationGuid, cloudRoute, updatedRoute)) + .orElse(true); + + } + + private static Optional findRoute(String url, Collection routes) { + return routes.stream() + .filter(route -> Objects.equals(url, route.getUrl())) + .findFirst(); + } + + private static boolean isProtocolChanged(UUID applicationGuid, CloudRoute currentRoute, CloudRoute updatedRoute) { + if (updatedRoute.getRequestedProtocol() == null) { + return false; + } + + return currentRoute.getDestinations() + .stream() + .filter(routeDestination -> Objects.equals(routeDestination.getApplicationGuid(), applicationGuid)) + .noneMatch(routeDestination -> Objects.equals(routeDestination.getProtocol(), + updatedRoute.getRequestedProtocol())); + } + + public static void validateDomainForRoute(CloudRoute route, Map existingDomains) { + String domain = route.getDomain() + .getName(); + + if (!StringUtils.hasLength(domain) || !existingDomains.containsKey(domain)) { + throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + MessageFormat.format(Messages.DOMAIN_0_NOT_FOUND_FOR_URI_1, domain, route.getUrl())); + } + + } + + public static Map buildServiceInstanceBody(String bindingName, UUID applicationGuid, UUID serviceInstanceGuid, + Map parameters) { + Map body = new HashMap<>(); + body.put(V3Fields.NAME, bindingName); + body.put(V3Fields.TYPE, "app"); + body.put(V3Fields.RELATIONSHIPS, Map.of(V3Fields.APP, toOneRelationship(applicationGuid), V3Fields.SERVICE_INSTANCE, + toOneRelationship(serviceInstanceGuid))); + + if (!CollectionUtils.isEmpty(parameters)) { + body.put(V3Fields.PARAMETERS, parameters); + } + + return body; + } + + private static Map toOneRelationship(UUID guid) { + return Map.of(V3Fields.DATA, Map.of(V3Fields.GUID, guid.toString())); + } + + public static UUID getGuid(CloudEntity entity) { + if (entity == null || entity.getMetadata() == null) { + return null; + } + + return entity.getMetadata() + .getGuid(); + } + + public static Map organizationRelationship(UUID organizationGuid) { + return Map.of(V3Fields.ORGANIZATION, Map.of(V3Fields.DATA, Map.of(V3Fields.GUID, organizationGuid.toString()))); + } + + public static boolean isUploadReady(Status status) { + return status == Status.READY; + } + + public static boolean hasUploadFailed(Status status) { + return status == Status.EXPIRED || status == Status.FAILED; + } + + public static void throwOnErrors(List errors) { + if (errors.isEmpty()) { + return; + } + + CloudOperationException firstException = errors.getFirst(); + errors.subList(1, errors.size()) + .forEach(firstException::addSuppressed); + + throw firstException; + } + + public static Optional extractJobGuid(ResponseEntity response) { + URI location = response.getHeaders() + .getLocation(); + + if (location == null) { + return Optional.empty(); + } + + String path = location.getPath(); + return Optional.of(path.substring(path.lastIndexOf('/') + 1)); + } + + public static Map basicAuthentication(CloudServiceBroker serviceBroker) { + return Map.of(V3Fields.TYPE, "basic", V3Fields.CREDENTIALS, + Map.of(V3Fields.USERNAME, nullToEmpty(serviceBroker.getUsername()), V3Fields.PASSWORD, + nullToEmpty(serviceBroker.getPassword()))); + } + + private static String nullToEmpty(String value) { + return value == null ? "" : value; + } + + public static Map buildCreateServiceBrokerBody(CloudServiceBroker serviceBroker) { + Map body = new HashMap<>(); + Map basicAuthentication = OperationsUtil.basicAuthentication(serviceBroker); + + body.put(V3Fields.NAME, serviceBroker.getName()); + body.put(V3Fields.URL, serviceBroker.getUrl()); + body.put(V3Fields.AUTHENTICATION, basicAuthentication); + + if (serviceBroker.getSpaceGuid() != null) { + body.put(V3Fields.RELATIONSHIPS, + Map.of(V3Fields.SPACE, Map.of(V3Fields.DATA, Map.of(V3Fields.GUID, serviceBroker.getSpaceGuid())))); + } + + return body; + } + + public static Map buildCreateServiceInstanceBody(CloudServiceInstance serviceInstance, UUID servicePlanGuid, + CloudSpace targetSpace) { + Map body = new HashMap<>(); + body.put(V3Fields.TYPE, "managed"); + body.put(V3Fields.NAME, serviceInstance.getName()); + MapUtil.addNonNull(body, V3Fields.METADATA, toMetadataMap(serviceInstance.getV3Metadata())); + MapUtil.addNonNull(body, V3Fields.TAGS, serviceInstance.getTags()); + MapUtil.addNonNull(body, V3Fields.PARAMETERS, serviceInstance.getCredentials()); + body.put(V3Fields.RELATIONSHIPS, Map.of(V3Fields.SERVICE_PLAN, toOneRelationship(servicePlanGuid.toString()), V3Fields.SPACE, + toOneRelationship(getGuid(targetSpace).toString()))); + + return body; + } + + public static Map toMetadataMap(Metadata metadata) { + if (metadata == null) { + return null; + } + + Map metadataMapResult = new HashMap<>(); + metadataMapResult.put(V3Fields.LABELS, metadata.getLabels() == null ? Map.of() : metadata.getLabels()); + metadataMapResult.put(V3Fields.ANNOTATIONS, metadata.getAnnotations() == null ? Map.of() : metadata.getAnnotations()); + + return metadataMapResult; + } + + public static Map toOneRelationship(String guid) { + return Map.of(V3Fields.DATA, Map.of(V3Fields.GUID, guid)); + } + + public static Map buildCreateUserProvidedServiceInstance(CloudServiceInstance serviceInstance, String syslogDrainUrl, + CloudSpace targetSpace) { + Map body = new HashMap<>(); + body.put(V3Fields.TYPE, "user-provided"); + body.put(V3Fields.NAME, serviceInstance.getName()); + MapUtil.addNonNull(body, V3Fields.METADATA, toMetadataMap(serviceInstance.getV3Metadata())); + MapUtil.addNonNull(body, V3Fields.CREDENTIALS, serviceInstance.getCredentials()); + body.put(V3Fields.SYSLOG_DRAIN_URL, syslogDrainUrl); + MapUtil.addNonNull(body, V3Fields.TAGS, serviceInstance.getTags()); + body.put(V3Fields.RELATIONSHIPS, Map.of(V3Fields.SPACE, toOneRelationship(getGuid(targetSpace).toString()))); + return body; + } + + public static boolean isUserProvided(V3ServiceInstance resource) { + return Constants.USER_PROVIDED.equals(resource.type()); + } + + public static String servicePlanGuidOf(V3ServiceInstance resource) { + if (resource.relationships() == null || resource.relationships() + .servicePlan() == null + || resource.relationships() + .servicePlan() + .data() == null) { + return null; + } + + return resource.relationships() + .servicePlan() + .data() + .guid(); + } + + public static CloudOperationException determineNameResolutionError(CloudOperationException e, String forbiddenMessage, + String notFoundMessage) { + if (e.getStatusCode() == HttpStatus.FORBIDDEN) { + return new CloudOperationException(HttpStatus.FORBIDDEN, Messages.FORBIDDEN, forbiddenMessage, e); + } + + if (e.getStatusCode() == HttpStatus.NOT_FOUND) { + return new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, notFoundMessage, e); + } + + return e; + } + + public static String getServiceOfferingGuidFromServicePlan(V3ServicePlan plan) { + if (plan.relationships() == null || plan.relationships() + .serviceOffering() == null + || plan.relationships() + .serviceOffering() + .data() == null) { + return null; + } + + return plan.relationships() + .serviceOffering() + .data() + .guid(); + } + + public static List> toBatches(Collection largeList, int maxCharLength) { + if (largeList.isEmpty()) { + return List.of(); + } + + List> batches = new ArrayList<>(); + int currentBatchLength = 0; + int currentBatchIndex = 0; + batches.add(new ArrayList<>()); + + for (T element : largeList) { + int elementLength = element.toString() + .length(); + + if (elementLength + currentBatchLength >= maxCharLength) { + batches.add(new ArrayList<>()); + currentBatchIndex++; + currentBatchLength = 0; + } + + batches.get(currentBatchIndex) + .add(element); + currentBatchLength += elementLength; + } + + return batches; + } + + public static Map buildCreateServiceKeyBody(String name, Map parameters, Metadata metadata, + UUID serviceInstanceGuid) { + Map resultBody = new HashMap<>(); + resultBody.put(V3Fields.TYPE, "key"); + resultBody.put(V3Fields.NAME, name); + resultBody.put(V3Fields.RELATIONSHIPS, + Map.of(V3Fields.SERVICE_INSTANCE, Map.of(V3Fields.DATA, Map.of(V3Fields.GUID, serviceInstanceGuid.toString())))); + + if (parameters != null && !parameters.isEmpty()) { + resultBody.put(V3Fields.PARAMETERS, parameters); + } + + MapUtil.addNonNull(resultBody, V3Fields.METADATA, toMetadataMap(metadata)); + + return resultBody; + } + + public static Map buildCreateTaskBody(CloudTask task) { + Map body = new HashMap<>(); + body.put(V3Fields.COMMAND, task.getCommand()); + body.put(V3Fields.NAME, task.getName()); + + CloudTask.Limits limits = task.getLimits(); + if (limits != null) { + if (limits.getMemory() != null) { + body.put(V3Fields.MEMORY_IN_MB, limits.getMemory()); + } + + if (limits.getDisk() != null) { + body.put(V3Fields.DISK_IN_MB, limits.getDisk()); + } + } + + return body; + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3Operations.java index a9da042d7d..45ce0bd716 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3Operations.java @@ -12,8 +12,6 @@ import java.util.concurrent.ThreadFactory; import java.util.function.Function; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; import org.cloudfoundry.multiapps.common.util.MiscUtil; import org.cloudfoundry.multiapps.controller.Constants; import org.cloudfoundry.multiapps.controller.Messages; @@ -29,6 +27,8 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableUpload; import org.cloudfoundry.multiapps.controller.client.facade.domain.Status; import org.cloudfoundry.multiapps.controller.client.facade.domain.Upload; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Fields; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3GuidReference; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Package; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3PackageMapper; @@ -46,6 +46,9 @@ public class PackagesV3Operations { private static final ParameterizedTypeReference> PACKAGE_PAGE = new ParameterizedTypeReference<>() { }; + private static final ParameterizedTypeReference> APP_PAGE = new ParameterizedTypeReference<>() { + }; + private final CloudControllerV3Client client; private final CloudSpace targetSpace; @@ -86,21 +89,18 @@ public List getPackagesForApplication(UUID applicationGuid) { public CloudPackage createDockerPackage(UUID applicationGuid, DockerInfo dockerInfo) { Map data = new HashMap<>(); - data.put("image", dockerInfo.getImage()); + data.put(V3Fields.IMAGE, dockerInfo.getImage()); DockerCredentials credentials = dockerInfo.getCredentials(); if (credentials != null && credentials.getUsername() != null && credentials.getPassword() != null) { - data.put("username", credentials.getUsername()); - data.put("password", credentials.getPassword()); + data.put(V3Fields.USERNAME, credentials.getUsername()); + data.put(V3Fields.PASSWORD, credentials.getPassword()); } - V3Package created = client.getRestClient() - .post() - .uri(CloudControllerV3Endpoints.PACKAGES) - .body(Map.of("type", "docker", "data", data, "relationships", - applicationRelationship(applicationGuid))) - .retrieve() - .body(V3Package.class); + V3Package created = client.postForObject(CloudControllerV3Endpoints.PACKAGES, + Map.of(V3Fields.TYPE, "docker", V3Fields.DATA, data, V3Fields.RELATIONSHIPS, + applicationRelationship(applicationGuid)), + V3Package.class); return getPackage(UUID.fromString(created.guid())); } @@ -157,23 +157,21 @@ private RestClient resolveUploadClient(Duration uploadTimeout) { } private CloudPackage createBitsPackage(UUID applicationGuid) { - V3Package created = client.getRestClient() - .post() - .uri(CloudControllerV3Endpoints.PACKAGES) - .body(Map.of("type", "bits", "relationships", applicationRelationship(applicationGuid))) - .retrieve() - .body(V3Package.class); + V3Package created = client.postForObject(CloudControllerV3Endpoints.PACKAGES, + Map.of(V3Fields.TYPE, "bits", V3Fields.RELATIONSHIPS, + applicationRelationship(applicationGuid)), + V3Package.class); return getPackage(UUID.fromString(created.guid())); } private static Map applicationRelationship(UUID applicationGuid) { - return Map.of("app", Map.of("data", Map.of("guid", applicationGuid.toString()))); + return Map.of(V3Fields.APP, Map.of(V3Fields.DATA, Map.of(V3Fields.GUID, applicationGuid.toString()))); } private UUID getRequiredApplicationGuid(String applicationName) { - List apps = client.list(buildApplicationQuery(applicationName), new ParameterizedTypeReference>() { - }); + String applicationsQuery = OperationsQueryUtil.buildApplicationsQuery(applicationName, targetSpace); + List apps = client.list(applicationsQuery, APP_PAGE); if (apps.isEmpty() || apps.getFirst() .guid() == null) { @@ -185,21 +183,6 @@ private UUID getRequiredApplicationGuid(String applicationName) { .guid()); } - private String buildApplicationQuery(String applicationName) { - StringBuilder query = new StringBuilder(CloudControllerV3Endpoints.APPS + CloudControllerV3Endpoints.QUERY_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE); - - if (targetSpace != null && targetSpace.getGuid() != null) { - query.append(CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) - .append(targetSpace.getGuid()); - } - - query.append(CloudControllerV3Endpoints.AMPERSAND_NAMES) - .append(applicationName); - - return query.toString(); - } - private void processAsyncUploadInBackground(CloudPackage cloudPackage, UploadStatusCallback callback) { uploadMonitorExecutor.execute(() -> processAsyncUpload(cloudPackage, callback)); } @@ -210,11 +193,11 @@ private void processAsyncUpload(CloudPackage cloudPackage, UploadStatusCallback Status uploadStatus = upload.getStatus(); boolean unsubscribe = callback.onProgress(uploadStatus.toString()); - if (unsubscribe || isUploadReady(uploadStatus)) { + if (unsubscribe || OperationsUtil.isUploadReady(uploadStatus)) { return; } - if (hasUploadFailed(uploadStatus)) { + if (OperationsUtil.hasUploadFailed(uploadStatus)) { callback.onError(upload.getErrorDetails() .getDescription()); return; @@ -228,14 +211,6 @@ private void processAsyncUpload(CloudPackage cloudPackage, UploadStatusCallback } } - private boolean isUploadReady(Status status) { - return status == Status.READY; - } - - private boolean hasUploadFailed(Status status) { - return status == Status.EXPIRED || status == Status.FAILED; - } - private ExecutorService createUploadMonitorExecutor() { ThreadFactory threadFactory = Thread.ofVirtual() .name(Constants.UPLOAD_MONITOR_THREAD_NAME_PREFIX, 0) @@ -244,8 +219,4 @@ private ExecutorService createUploadMonitorExecutor() { return Executors.newThreadPerTaskExecutor(threadFactory); } - @JsonIgnoreProperties(ignoreUnknown = true) - private record V3App(@JsonProperty("guid") String guid) { - } - } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3Operations.java index a3b210eec3..6685fa5b46 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3Operations.java @@ -14,12 +14,12 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudProcess; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; import org.cloudfoundry.multiapps.controller.client.facade.domain.DropletInfo; -import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableDropletInfo; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableInstancesInfo; import org.cloudfoundry.multiapps.controller.client.facade.domain.InstancesInfo; import org.cloudfoundry.multiapps.controller.client.facade.domain.Staging; -import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Application; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Droplet; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Fields; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3GuidReference; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3InstancesInfoMapper; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Process; @@ -29,7 +29,14 @@ public class ProcessesV3Operations { + private static final ParameterizedTypeReference> APP_FEATURE_PAGE = new ParameterizedTypeReference<>() { + }; + + private static final ParameterizedTypeReference> APP_PAGE = new ParameterizedTypeReference<>() { + }; + private final CloudControllerV3Client client; + private final CloudSpace targetSpace; public ProcessesV3Operations(CloudControllerV3Client client, CloudSpace targetSpace) { @@ -86,8 +93,7 @@ public Map getApplicationFeatures(UUID applicationGuid) { List applicationFeatures = client.list(CloudControllerV3Endpoints.APPS + "/" + applicationGuid + "/features" + CloudControllerV3Endpoints.QUERY_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE, - new ParameterizedTypeReference>() { - }); + APP_FEATURE_PAGE); Map result = new HashMap<>(); for (V3Process.V3AppFeature feature : applicationFeatures) { @@ -107,64 +113,17 @@ public DropletInfo getCurrentDropletForApplication(UUID applicationGuid) { applicationGuid)); } - return parseDropletInfo(currentDroplet); - } - - private DropletInfo parseDropletInfo(V3Droplet droplet) { - String packageUrl = droplet.links() - .get(Constants.PACKAGE_LINK) - .href(); - - if (packageUrl.endsWith("/")) { - packageUrl = packageUrl.substring(0, packageUrl.lastIndexOf("/")); - } - - String packageGuid = packageUrl.substring(packageUrl.lastIndexOf("/") + 1); - return ImmutableDropletInfo.builder() - .guid(UUID.fromString(droplet.guid())) - .packageGuid(UUID.fromString(packageGuid)) - .build(); + return OperationsUtil.parseDropletInfo(currentDroplet); } public void updateApplicationStaging(String applicationName, Staging staging) { UUID applicationGuid = getRequiredApplicationGuid(applicationName); + Map applicationLifecycle = OperationsUtil.buildApplicationLifecycle(staging); - client.getRestClient() - .patch() - .uri(CloudControllerV3Endpoints.APP_BY_GUID, applicationGuid) - .body(Map.of("lifecycle", buildApplicationLifecycle(staging))) - .retrieve() - .toBodilessEntity(); + client.patch(CloudControllerV3Endpoints.APP_BY_GUID, Map.of(V3Fields.LIFECYCLE, applicationLifecycle), applicationGuid); updateApplicationProcess(applicationGuid, staging); } - private Map buildApplicationLifecycle(Staging staging) { - if (staging.getDockerInfo() != null) { - return Map.of(Constants.TYPE, "docker", Constants.DATA, Map.of()); - } - - String lifecycleType = staging.getLifecycleType() != null ? staging.getLifecycleType() - .name() - .toLowerCase() - : "buildpack"; - - if (Constants.CLOUD_NATIVE_BUILDPACK.equals(lifecycleType) && (staging.getBuildpacks() == null || staging.getBuildpacks() - .isEmpty())) { - throw new IllegalArgumentException(Messages.BUILDPACKS_ARE_REQUIRED_FOR_THE_BUILDPACK_LIFECYCLE_TYPE); - } - - Map data = new HashMap<>(); - if (staging.getStackName() != null) { - data.put("stack", staging.getStackName()); - } - - if (staging.getBuildpacks() != null) { - data.put("buildpacks", staging.getBuildpacks()); - } - - return Map.of(Constants.TYPE, lifecycleType, Constants.DATA, data); - } - private void updateApplicationProcess(UUID applicationGuid, Staging staging) { staging.getAppFeatures() .forEach((featureName, enabled) -> updateAppFeature(applicationGuid, featureName, enabled)); @@ -179,79 +138,19 @@ private void updateApplicationProcess(UUID applicationGuid, Staging staging) { applicationGuid)); } - Map updateProcessBody = new HashMap<>(); - updateProcessBody.put("command", staging.getCommand()); - if (staging.getHealthCheckType() != null) { - updateProcessBody.put("health_check", buildHealthCheck(staging)); - } - - if (staging.getReadinessHealthCheckType() != null) { - updateProcessBody.put("readiness_health_check", buildReadinessHealthCheck(staging)); - } + Map updateProcessBody = OperationsUtil.buildUpdatedProcessBody(staging); - client.getRestClient() - .patch() - .uri(CloudControllerV3Endpoints.PROCESS_BY_GUID, process.guid()) - .body(updateProcessBody) - .retrieve() - .toBodilessEntity(); + client.patch(CloudControllerV3Endpoints.PROCESS_BY_GUID, updateProcessBody, process.guid()); } private void updateAppFeature(UUID applicationGuid, String featureName, boolean enabled) { - client.getRestClient() - .patch() - .uri(CloudControllerV3Endpoints.APP_FEATURE, applicationGuid, featureName) - .body(Map.of("enabled", enabled)) - .retrieve() - .toBodilessEntity(); - } - - private Map buildHealthCheck(Staging staging) { - Map data = new HashMap<>(); - putIfNotNull(data, "endpoint", staging.getHealthCheckHttpEndpoint()); - putIfNotNull(data, "timeout", staging.getHealthCheckTimeout()); - putIfNotNull(data, "invocation_timeout", staging.getInvocationTimeout()); - putIfNotNull(data, "interval", staging.getHealthCheckInterval()); - - Map healthCheck = new HashMap<>(); - healthCheck.put(Constants.TYPE, staging.getHealthCheckType()); - healthCheck.put(Constants.DATA, data); - return healthCheck; - } - - private Map buildReadinessHealthCheck(Staging staging) { - Map data = new HashMap<>(); - putIfNotNull(data, "invocation_timeout", staging.getReadinessHealthCheckInvocationTimeout()); - putIfNotNull(data, "endpoint", staging.getReadinessHealthCheckHttpEndpoint()); - putIfNotNull(data, "interval", staging.getReadinessHealthCheckInterval()); - - Map readinessHealthCheck = new HashMap<>(); - readinessHealthCheck.put(Constants.TYPE, staging.getReadinessHealthCheckType()); - readinessHealthCheck.put(Constants.DATA, data); - return readinessHealthCheck; - } - - private static void putIfNotNull(Map map, String key, Object value) { - if (value != null) { - map.put(key, value); - } + client.patch(CloudControllerV3Endpoints.APP_FEATURE, Map.of(V3Fields.ENABLED, enabled), applicationGuid, featureName); } private UUID getRequiredApplicationGuid(String applicationName) { - StringBuilder query = new StringBuilder(CloudControllerV3Endpoints.APPS + CloudControllerV3Endpoints.QUERY_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE); - - if (targetSpace != null && targetSpace.getGuid() != null) { - query.append(CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) - .append(targetSpace.getGuid()); - } - - query.append(CloudControllerV3Endpoints.AMPERSAND_NAMES) - .append(applicationName); + String applicationsQuery = OperationsQueryUtil.buildApplicationsQuery(applicationName, targetSpace); - List apps = client.list(query.toString(), - new ParameterizedTypeReference>() { - }); + List apps = client.list(applicationsQuery, APP_PAGE); if (apps.isEmpty()) { throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3Operations.java index da034949d5..481dca4e3b 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3Operations.java @@ -3,14 +3,10 @@ import java.text.MessageFormat; import java.util.List; import java.util.Map; -import java.util.Objects; -import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; import org.cloudfoundry.multiapps.controller.Constants; import org.cloudfoundry.multiapps.controller.Messages; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; @@ -18,6 +14,8 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; import org.cloudfoundry.multiapps.controller.client.facade.domain.RouteDestination; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Domain; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Fields; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3GuidReference; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Route; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3RouteMapper; @@ -31,10 +29,15 @@ public class RoutesV3Operations { private static final ParameterizedTypeReference> ROUTE_PAGE = new ParameterizedTypeReference<>() { }; + private static final ParameterizedTypeReference> DOMAIN_PAGE = new ParameterizedTypeReference<>() { }; + private static final ParameterizedTypeReference> APP_PAGE = new ParameterizedTypeReference<>() { + }; + private final CloudControllerV3Client client; + private final CloudSpace targetSpace; public RoutesV3Operations(CloudControllerV3Client client, CloudSpace targetSpace) { @@ -45,7 +48,7 @@ public RoutesV3Operations(CloudControllerV3Client client, CloudSpace targetSpace public void addRoute(String host, String domainName, String path) { assertSpaceProvided("add route for domain"); UUID domainGuid = getRequiredDomainGuid(domainName); - doAddRoute(domainGuid, host, path); + addRoute(domainGuid, host, path); } public void deleteRoute(String host, String domainName, String path) { @@ -57,15 +60,11 @@ public void deleteRoute(String host, String domainName, String path) { MessageFormat.format(Messages.HOST_0_NOT_FOUND_FOR_DOMAIN_1, host, domainName)); } - doDeleteRoute(routeGuid); + deleteRoute(routeGuid); } public void deleteOrphanedRoutes() { - ResponseEntity response = client.getRestClient() - .delete() - .uri(CloudControllerV3Endpoints.SPACE_UNMAPPED_ROUTES, getTargetSpaceGuid()) - .retrieve() - .toEntity(Void.class); + ResponseEntity response = client.delete(CloudControllerV3Endpoints.SPACE_UNMAPPED_ROUTES, getTargetSpaceGuid()); client.followAsyncJob(response, Constants.DELETE_JOB_TIMEOUT); } @@ -94,21 +93,27 @@ public void updateApplicationRoutes(String applicationName, Set upda .map(V3RouteMapper::toCloudRoute) .toList(); - Set outdatedRoutes = getOutdatedRoutes(applicationGuid, appRoutes, updatedRoutes); - Set newRoutes = getNewRoutes(applicationGuid, appRoutes, updatedRoutes); + Set outdatedRoutes = OperationsUtil.getOutdatedRoutes(applicationGuid, appRoutes, updatedRoutes); + Set newRoutes = OperationsUtil.getNewRoutes(applicationGuid, appRoutes, updatedRoutes); removeRoutes(outdatedRoutes, applicationGuid); addRoutes(newRoutes, applicationGuid); } private List findRoutesByDomainGuid(UUID domainGuid) { - return listRoutes(buildRouteQuery(domainGuid, null, null)).stream() - .map(V3RouteMapper::toCloudRoute) - .toList(); + UUID targetSpaceGuid = getTargetSpaceGuid(); + String routeQuery = OperationsQueryUtil.buildRoutesQuery(domainGuid, null, null, targetSpaceGuid); + + return listRoutes(routeQuery).stream() + .map(V3RouteMapper::toCloudRoute) + .toList(); } private UUID getRouteGuid(UUID domainGuid, String host, String path) { - List routes = listRoutes(buildRouteQuery(domainGuid, host, path)); + UUID targetSpaceGuid = getTargetSpaceGuid(); + String routeQuery = OperationsQueryUtil.buildRoutesQuery(domainGuid, host, path, targetSpaceGuid); + + List routes = listRoutes(routeQuery); if (CollectionUtils.isEmpty(routes)) { return null; } @@ -117,29 +122,6 @@ private UUID getRouteGuid(UUID domainGuid, String host, String path) { .guid()); } - private String buildRouteQuery(UUID domainGuid, String host, String path) { - StringBuilder query = new StringBuilder( - CloudControllerV3Endpoints.ROUTES + CloudControllerV3Endpoints.QUERY_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE); - - query.append(CloudControllerV3Endpoints.AMPERSAND_DOMAIN_GUIDS) - .append(domainGuid); - - query.append(CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) - .append(getTargetSpaceGuid()); - - if (host != null) { - query.append(CloudControllerV3Endpoints.AMPERSAND_HOSTS) - .append(host); - } - - if (path != null) { - query.append(CloudControllerV3Endpoints.AMPERSAND_PATHS) - .append(path); - } - - return query.toString(); - } - private List listRoutes(String query) { return client.list(query, ROUTE_PAGE); } @@ -148,7 +130,7 @@ private void addRoutes(Set routes, UUID applicationGuid) { Map domains = getDomainsFromRoutes(routes); for (CloudRoute route : routes) { - validateDomainForRoute(route, domains); + OperationsUtil.validateDomainForRoute(route, domains); UUID domainGuid = domains.get(route.getDomain() .getName()); @@ -160,11 +142,17 @@ private void addRoutes(Set routes, UUID applicationGuid) { private void removeRoutes(Set routes, UUID applicationGuid) { for (CloudRoute route : routes) { - for (RouteDestination destination : route.getDestinations()) { - if (destination.getApplicationGuid() - .equals(applicationGuid)) { - unbindRoute(route.getGuid(), destination.getGuid()); - } + unbindRouteFromDestinationApplications(route, applicationGuid); + } + } + + private void unbindRouteFromDestinationApplications(CloudRoute route, UUID applicationGuid) { + List destinationsForCurrentRoute = route.getDestinations(); + + for (RouteDestination destination : destinationsForCurrentRoute) { + if (destination.getApplicationGuid() + .equals(applicationGuid)) { + unbindRoute(route.getGuid(), destination.getGuid()); } } } @@ -173,127 +161,35 @@ private UUID getOrAddRoute(UUID domainGuid, String host, String path) { UUID routeGuid = getRouteGuid(domainGuid, host, path); if (routeGuid == null) { - routeGuid = doAddRoute(domainGuid, host, path); + routeGuid = addRoute(domainGuid, host, path); } return routeGuid; } - private UUID doAddRoute(UUID domainGuid, String host, String path) { + private UUID addRoute(UUID domainGuid, String host, String path) { assertSpaceProvided("add route"); - V3Route created = client.getRestClient() - .post() - .uri(CloudControllerV3Endpoints.ROUTES) - .body(buildCreateRouteBody(domainGuid, host, path)) - .retrieve() - .body(V3Route.class); - - return UUID.fromString(created.guid()); - } + UUID targetSpaceGuid = getTargetSpaceGuid(); + Map createRouteBody = OperationsUtil.buildCreateRouteBody(domainGuid, host, path, targetSpaceGuid); - private Map buildCreateRouteBody(UUID domainGuid, String host, String path) { - Map relationships = Map.of("domain", buildSingleResourceRelationship(domainGuid), - "space", buildSingleResourceRelationship(getTargetSpaceGuid())); + V3Route created = client.postForObject(CloudControllerV3Endpoints.ROUTES, createRouteBody, V3Route.class); - return Map.of("host", host == null ? "" : host, - "path", path == null ? "" : path, - "relationships", relationships); + return UUID.fromString(created.guid()); } - private void doDeleteRoute(UUID guid) { - ResponseEntity response = client.getRestClient() - .delete() - .uri(CloudControllerV3Endpoints.ROUTE_BY_GUID, guid) - .retrieve() - .toEntity(Void.class); + private void deleteRoute(UUID guid) { + ResponseEntity response = client.delete(CloudControllerV3Endpoints.ROUTE_BY_GUID, guid); client.followAsyncJob(response, Constants.DELETE_JOB_TIMEOUT); } private void bindRoute(UUID routeGuid, UUID applicationGuid, String protocol) { - client.getRestClient() - .post() - .uri(CloudControllerV3Endpoints.ROUTE_DESTINATIONS, routeGuid) - .body(Map.of("destinations", List.of(createDestination(applicationGuid, protocol)))) - .retrieve() - .toBodilessEntity(); + client.post(CloudControllerV3Endpoints.ROUTE_DESTINATIONS, + Map.of(V3Fields.DESTINATIONS, List.of(OperationsUtil.createDestination(applicationGuid, protocol))), routeGuid); } private void unbindRoute(UUID routeGuid, UUID destinationGuid) { - client.getRestClient() - .delete() - .uri(CloudControllerV3Endpoints.ROUTE_DESTINATION_BY_GUID, routeGuid, destinationGuid) - .retrieve() - .toBodilessEntity(); - } - - private Map createDestination(UUID applicationGuid, String protocol) { - if (protocol == null) { - return Map.of("app", Map.of("guid", applicationGuid.toString())); - } - - return Map.of("app", Map.of("guid", applicationGuid.toString()), "protocol", protocol); - } - - private Map buildSingleResourceRelationship(UUID guid) { - return Map.of("data", Map.of("guid", guid.toString())); - } - - private Set getOutdatedRoutes(UUID applicationGuid, List currentRoutes, Set updatedRoutes) { - return currentRoutes.stream() - .filter(currentRoute -> isRouteOutdated(applicationGuid, currentRoute, updatedRoutes)) - .collect(Collectors.toSet()); - } - - private boolean isRouteOutdated(UUID applicationGuid, CloudRoute currentRoute, Set updatedRoutes) { - Optional updatedRoute = findRoute(currentRoute.getUrl(), updatedRoutes); - - return updatedRoute.map(cloudRoute -> isProtocolChanged(applicationGuid, currentRoute, cloudRoute)) - .orElse(true); - - } - - private Set getNewRoutes(UUID applicationGuid, List currentRoutes, Set updatedRoutes) { - return updatedRoutes.stream() - .filter(updatedRoute -> isRouteUpdated(applicationGuid, updatedRoute, currentRoutes)) - .collect(Collectors.toSet()); - } - - private boolean isRouteUpdated(UUID applicationGuid, CloudRoute updatedRoute, List currentRoutes) { - Optional currentRoute = findRoute(updatedRoute.getUrl(), currentRoutes); - - return currentRoute.map(cloudRoute -> isProtocolChanged(applicationGuid, cloudRoute, updatedRoute)) - .orElse(true); - - } - - private Optional findRoute(String url, java.util.Collection routes) { - return routes.stream() - .filter(route -> Objects.equals(url, route.getUrl())) - .findFirst(); - } - - private boolean isProtocolChanged(UUID applicationGuid, CloudRoute currentRoute, CloudRoute updatedRoute) { - if (updatedRoute.getRequestedProtocol() == null) { - return false; - } - - return currentRoute.getDestinations() - .stream() - .filter(routeDestination -> Objects.equals(routeDestination.getApplicationGuid(), applicationGuid)) - .noneMatch(routeDestination -> Objects.equals(routeDestination.getProtocol(), - updatedRoute.getRequestedProtocol())); - } - - private void validateDomainForRoute(CloudRoute route, Map existingDomains) { - String domain = route.getDomain() - .getName(); - - if (!StringUtils.hasLength(domain) || !existingDomains.containsKey(domain)) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, - MessageFormat.format(Messages.DOMAIN_0_NOT_FOUND_FOR_URI_1, domain, route.getUrl())); - } - + client.delete(CloudControllerV3Endpoints.ROUTE_DESTINATION_BY_GUID, routeGuid, destinationGuid); } private UUID getRequiredDomainGuid(String name) { @@ -340,9 +236,9 @@ private Map getDomainsFromRoutes(Set routes) { } private UUID getRequiredApplicationGuid(String applicationName) { - List apps = client.list(buildApplicationQuery(applicationName), - new ParameterizedTypeReference>() { - }); + String applicationsQuery = OperationsQueryUtil.buildApplicationsQuery(applicationName, targetSpace); + List apps = client.list(applicationsQuery, + APP_PAGE); if (CollectionUtils.isEmpty(apps)) { throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, @@ -353,21 +249,6 @@ private UUID getRequiredApplicationGuid(String applicationName) { .guid()); } - private String buildApplicationQuery(String applicationName) { - StringBuilder query = new StringBuilder( - CloudControllerV3Endpoints.APPS + CloudControllerV3Endpoints.QUERY_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE); - - query.append(CloudControllerV3Endpoints.AMPERSAND_NAMES) - .append(applicationName); - - if (targetSpace != null && targetSpace.getGuid() != null) { - query.append(CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) - .append(targetSpace.getGuid()); - } - - return query.toString(); - } - private UUID getTargetSpaceGuid() { return targetSpace.getGuid(); } @@ -379,8 +260,4 @@ private void assertSpaceProvided(String operation) { } } - @JsonIgnoreProperties(ignoreUnknown = true) - private record V3AppRef(@JsonProperty("guid") String guid) { - } - } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3Operations.java index 5690bcd575..fe9d99d2a8 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3Operations.java @@ -1,6 +1,5 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest; -import java.net.URI; import java.text.MessageFormat; import java.util.ArrayList; import java.util.HashMap; @@ -9,30 +8,31 @@ import java.util.Optional; import java.util.UUID; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; +import org.cloudfoundry.multiapps.common.util.MiscUtil; import org.cloudfoundry.multiapps.controller.Messages; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBinding; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; -import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Application; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Fields; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3GuidReference; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceBinding; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceBindingMapper; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.util.CollectionUtils; public class ServiceBindingsV3Operations { - private static final ParameterizedTypeReference> BINDING_LIST_TYPE = new ParameterizedTypeReference<>() { + private static final ParameterizedTypeReference> BINDING_PAGE = new ParameterizedTypeReference<>() { }; - private static final ParameterizedTypeReference> APPLICATION_LIST_TYPE = new ParameterizedTypeReference<>() { + + private static final ParameterizedTypeReference> GUID_REFERENCE_PAGE = new ParameterizedTypeReference<>() { }; private final CloudControllerV3Client client; + private final CloudSpace targetSpace; public ServiceBindingsV3Operations(CloudControllerV3Client client, CloudSpace targetSpace) { @@ -48,37 +48,26 @@ public Optional bindServiceInstance(String bindingName, String applicati Map parameters) { UUID applicationGuid = getRequiredApplicationGuid(applicationName); UUID serviceInstanceGuid = getRequiredServiceInstanceGuid(serviceInstanceName); + Map serviceInstanceBody = OperationsUtil.buildServiceInstanceBody(bindingName, applicationGuid, serviceInstanceGuid, + parameters); - ResponseEntity response = client.getRestClient() - .post() - .uri(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS) - .body(buildServiceInstanceBody(bindingName, applicationGuid, serviceInstanceGuid, parameters)) - .retrieve() - .toBodilessEntity(); + ResponseEntity response = client.post(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS, serviceInstanceBody); - return extractJobGuidFromResponseHeader(response); + return OperationsUtil.extractJobGuid(response); } public List unbindServiceInstance(String applicationName, String serviceInstanceName) { UUID applicationGuid = getRequiredApplicationGuid(applicationName); UUID serviceInstanceGuid = getRequiredServiceInstanceGuid(serviceInstanceName); - return doUnbindServiceInstance(applicationGuid, serviceInstanceGuid); - } - - public List unbindServiceInstance(UUID applicationGuid, UUID serviceInstanceGuid) { - return doUnbindServiceInstance(applicationGuid, serviceInstanceGuid); - } - - public Optional deleteServiceBinding(UUID bindingGuid) { - return deleteSingleServiceBinding(bindingGuid); + return unbindServiceInstance(applicationGuid, serviceInstanceGuid); } public CloudServiceBinding getServiceBinding(UUID serviceBindingGuid) { String uri = CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS + CloudControllerV3Endpoints.QUERY_GUIDS + serviceBindingGuid + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; - return client.list(uri, BINDING_LIST_TYPE) + return client.list(uri, BINDING_PAGE) .stream() .findFirst() .map(V3ServiceBindingMapper::toCloudServiceBinding) @@ -110,9 +99,9 @@ public List getServiceBindingsForApplication(UUID applicati } public Map getServiceBindingParameters(UUID guid) { - @SuppressWarnings("unchecked") - Map parameters = client.get(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS + "/" + guid + "/parameters", - Map.class); + Map parameters = MiscUtil.cast( + client.get(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS + "/" + guid + "/parameters", + Map.class)); if (parameters == null) { throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, @@ -124,26 +113,18 @@ public Map getServiceBindingParameters(UUID guid) { } public void updateServiceBindingMetadata(UUID guid, Metadata metadata) { - Map metadataBody = new HashMap<>(); - metadataBody.put("labels", metadata.getLabels()); - metadataBody.put("annotations", metadata.getAnnotations()); - - client.getRestClient() - .patch() - .uri(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDING_BY_GUID, guid) - .body(Map.of("metadata", metadataBody)) - .retrieve() - .toBodilessEntity(); + client.patch(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDING_BY_GUID, + Map.of(V3Fields.METADATA, OperationsUtil.toMetadataMap(metadata)), guid); } private List listBindings(String uri) { - return client.list(uri, BINDING_LIST_TYPE) + return client.list(uri, BINDING_PAGE) .stream() .map(V3ServiceBindingMapper::toCloudServiceBinding) .toList(); } - private List doUnbindServiceInstance(UUID applicationGuid, UUID serviceInstanceGuid) { + public List unbindServiceInstance(UUID applicationGuid, UUID serviceInstanceGuid) { List serviceBindingGuids = getServiceBindingGuids(applicationGuid, serviceInstanceGuid); if (serviceBindingGuids.isEmpty()) { @@ -152,7 +133,7 @@ private List doUnbindServiceInstance(UUID applicationGuid, UUID serviceI applicationGuid)); } - return doDeleteServiceBindings(serviceBindingGuids); + return deleteServiceBindings(serviceBindingGuids); } private List getServiceBindingGuids(UUID applicationGuid, UUID serviceInstanceGuid) { @@ -161,79 +142,38 @@ private List getServiceBindingGuids(UUID applicationGuid, UUID serviceInst + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; - return client.list(uri, BINDING_LIST_TYPE) + return client.list(uri, BINDING_PAGE) .stream() .map(binding -> UUID.fromString(binding.guid())) .toList(); } - private List doDeleteServiceBindings(List guids) { + private List deleteServiceBindings(List guids) { List jobIds = new ArrayList<>(); List errors = new ArrayList<>(); for (UUID guid : guids) { try { - deleteSingleServiceBinding(guid).ifPresent(jobIds::add); + deleteServiceBinding(guid).ifPresent(jobIds::add); } catch (CloudOperationException e) { errors.add(e); } } - throwOnErrors(errors); + OperationsUtil.throwOnErrors(errors); return jobIds; } - private Optional deleteSingleServiceBinding(UUID guid) { - ResponseEntity response = client.getRestClient() - .delete() - .uri(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDING_BY_GUID, guid) - .retrieve() - .toBodilessEntity(); + public Optional deleteServiceBinding(UUID guid) { + ResponseEntity response = client.delete(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDING_BY_GUID, guid); - return extractJobGuidFromResponseHeader(response); - } - - private void throwOnErrors(List errors) { - if (errors.isEmpty()) { - return; - } - - CloudOperationException firstException = errors.getFirst(); - errors.subList(1, errors.size()) - .forEach(firstException::addSuppressed); - - throw firstException; - } - - private Optional extractJobGuidFromResponseHeader(ResponseEntity response) { - URI location = response.getHeaders() - .getLocation(); - - if (location == null) { - return Optional.empty(); - } - - String path = location.getPath(); - return Optional.of(path.substring(path.lastIndexOf('/') + 1)); - } - - private Map toOneRelationship(UUID guid) { - return Map.of("data", Map.of("guid", guid.toString())); + return OperationsUtil.extractJobGuid(response); } private UUID getRequiredApplicationGuid(String applicationName) { - StringBuilder query = new StringBuilder( - CloudControllerV3Endpoints.APPS + CloudControllerV3Endpoints.QUERY_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE); - - if (targetSpace != null && targetSpace.getGuid() != null) { - query.append(CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) - .append(targetSpace.getGuid()); - } - - query.append(CloudControllerV3Endpoints.AMPERSAND_NAMES) - .append(applicationName); + String applicationsQuery = OperationsQueryUtil.buildApplicationsQuery(applicationName, targetSpace); - List apps = client.list(query.toString(), APPLICATION_LIST_TYPE); + List apps = client.list(applicationsQuery, GUID_REFERENCE_PAGE); if (apps.isEmpty()) { throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, @@ -245,19 +185,8 @@ private UUID getRequiredApplicationGuid(String applicationName) { } private UUID getRequiredServiceInstanceGuid(String name) { - StringBuilder query = new StringBuilder(CloudControllerV3Endpoints.SERVICE_INSTANCES + CloudControllerV3Endpoints.QUERY_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE); - - if (targetSpace != null && targetSpace.getGuid() != null) { - query.append(CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) - .append(targetSpace.getGuid()); - } - - query.append(CloudControllerV3Endpoints.AMPERSAND_NAMES) - .append(name); - List instances = client.list(query.toString(), - new ParameterizedTypeReference>() { - }); + String serviceInstancesQuery = OperationsQueryUtil.buildServiceInstancesQuery(name, targetSpace); + List instances = client.list(serviceInstancesQuery, GUID_REFERENCE_PAGE); if (instances.isEmpty()) { throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, @@ -268,23 +197,4 @@ private UUID getRequiredServiceInstanceGuid(String name) { .guid()); } - private Map buildServiceInstanceBody(String bindingName, UUID applicationGuid, UUID serviceInstanceGuid, - Map parameters) { - Map body = new HashMap<>(); - body.put("name", bindingName); - body.put("type", "app"); - body.put("relationships", Map.of("app", toOneRelationship(applicationGuid), "service_instance", - toOneRelationship(serviceInstanceGuid))); - - if (!CollectionUtils.isEmpty(parameters)) { - body.put("parameters", parameters); - } - - return body; - } - - @JsonIgnoreProperties(ignoreUnknown = true) - private record V3ServiceInstanceRef(@JsonProperty("guid") String guid) { - } - } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBrokersV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBrokersV3Operations.java index ead8031401..8eb573f6a4 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBrokersV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBrokersV3Operations.java @@ -1,19 +1,17 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest; -import java.net.URI; import java.text.MessageFormat; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; import org.cloudfoundry.multiapps.controller.Messages; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBroker; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; import org.cloudfoundry.multiapps.controller.client.facade.domain.ServicePlanVisibility; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Fields; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3GuidReference; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceBroker; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceBrokerMapper; @@ -26,9 +24,8 @@ public class ServiceBrokersV3Operations { private static final ParameterizedTypeReference> BROKER_PAGE = new ParameterizedTypeReference<>() { }; - private static final ParameterizedTypeReference> OFFERING_PAGE = new ParameterizedTypeReference<>() { - }; - private static final ParameterizedTypeReference> PLAN_PAGE = new ParameterizedTypeReference<>() { + + private static final ParameterizedTypeReference> GUID_REFERENCE_PAGE = new ParameterizedTypeReference<>() { }; private final CloudControllerV3Client client; @@ -42,22 +39,11 @@ public ServiceBrokersV3Operations(CloudControllerV3Client client, CloudSpace tar public String createServiceBroker(CloudServiceBroker serviceBroker) { Assert.notNull(serviceBroker, "Service broker must not be null."); - Map body = new HashMap<>(); - body.put("name", serviceBroker.getName()); - body.put("url", serviceBroker.getUrl()); - body.put("authentication", basicAuthentication(serviceBroker)); - - if (serviceBroker.getSpaceGuid() != null) { - body.put("relationships", Map.of("space", Map.of("data", Map.of("guid", serviceBroker.getSpaceGuid())))); - } + Map body = OperationsUtil.buildCreateServiceBrokerBody(serviceBroker); + ResponseEntity response = client.post(CloudControllerV3Endpoints.SERVICE_BROKERS, body); - ResponseEntity response = client.getRestClient() - .post() - .uri(CloudControllerV3Endpoints.SERVICE_BROKERS) - .body(body) - .retrieve() - .toBodilessEntity(); - return extractJobGuid(response); + return OperationsUtil.extractJobGuid(response) + .orElse(null); } public String deleteServiceBroker(String name) { @@ -65,13 +51,10 @@ public String deleteServiceBroker(String name) { UUID guid = broker.getMetadata() .getGuid(); - ResponseEntity response = client.getRestClient() - .delete() - .uri(CloudControllerV3Endpoints.SERVICE_BROKER_BY_GUID, guid.toString()) - .retrieve() - .toBodilessEntity(); + ResponseEntity response = client.delete(CloudControllerV3Endpoints.SERVICE_BROKER_BY_GUID, guid.toString()); - return extractJobGuid(response); + return OperationsUtil.extractJobGuid(response) + .orElse(null); } public CloudServiceBroker getServiceBroker(String name) { @@ -104,17 +87,14 @@ public String updateServiceBroker(CloudServiceBroker serviceBroker) { UUID brokerGuid = existingBroker.getMetadata() .getGuid(); - Map body = Map.of("name", serviceBroker.getName(), "url", serviceBroker.getUrl(), "authentication", - basicAuthentication(serviceBroker)); + Map basicAuthentication = OperationsUtil.basicAuthentication(serviceBroker); + Map body = Map.of(V3Fields.NAME, serviceBroker.getName(), V3Fields.URL, serviceBroker.getUrl(), + V3Fields.AUTHENTICATION, basicAuthentication); - ResponseEntity response = client.getRestClient() - .patch() - .uri(CloudControllerV3Endpoints.SERVICE_BROKER_BY_GUID, brokerGuid.toString()) - .body(body) - .retrieve() - .toBodilessEntity(); + ResponseEntity response = client.patch(CloudControllerV3Endpoints.SERVICE_BROKER_BY_GUID, body, brokerGuid.toString()); - return extractJobGuid(response); + return OperationsUtil.extractJobGuid(response) + .orElse(null); } public void updateServicePlanVisibilityForBroker(String name, ServicePlanVisibility visibility) { @@ -147,12 +127,13 @@ private List findServicePlanGuidsByBrokerGuid(UUID brokerGuid) { private List findServiceOfferingGuidsByBrokerGuid(UUID brokerGuid) { String uri = CloudControllerV3Endpoints.SERVICE_OFFERINGS + CloudControllerV3Endpoints.QUERY_SERVICE_BROKER_GUIDS + brokerGuid - + CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS + getTargetSpaceGuid() + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + + CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS + OperationsUtil.getGuid(targetSpace) + + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; - return client.list(uri, OFFERING_PAGE) + return client.list(uri, GUID_REFERENCE_PAGE) .stream() - .map(ServiceOfferingRef::guid) + .map(V3GuidReference::guid) .map(UUID::fromString) .toList(); } @@ -162,61 +143,16 @@ private List findServicePlanGuidsByOfferingGuid(UUID serviceOfferingGuid) CloudControllerV3Endpoints.SERVICE_PLANS + CloudControllerV3Endpoints.QUERY_SERVICE_OFFERING_GUIDS + serviceOfferingGuid + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; - return client.list(uri, PLAN_PAGE) + return client.list(uri, GUID_REFERENCE_PAGE) .stream() - .map(ServicePlanRef::guid) + .map(V3GuidReference::guid) .map(UUID::fromString) .toList(); } private void updateServicePlanVisibility(UUID servicePlanGuid, ServicePlanVisibility visibility) { - client.getRestClient() - .patch() - .uri(CloudControllerV3Endpoints.SERVICE_PLAN_VISIBILITY, servicePlanGuid.toString()) - .body(Map.of("type", visibility.toString())) - .retrieve() - .toBodilessEntity(); - } - - private static Map basicAuthentication(CloudServiceBroker serviceBroker) { - return Map.of("type", "basic", "credentials", - Map.of("username", nullToEmpty(serviceBroker.getUsername()), "password", nullToEmpty(serviceBroker.getPassword()))); - } - - private static String nullToEmpty(String value) { - return value == null ? "" : value; - } - - private String getTargetSpaceGuid() { - return targetSpace.getMetadata() - .getGuid() - .toString(); - } - - private static String extractJobGuid(ResponseEntity response) { - URI location = response.getHeaders() - .getLocation(); - - if (location == null) { - return null; - } - - String value = location.toString(); - int index = value.lastIndexOf("/v3/jobs/"); - - if (index < 0) { - return value.substring(value.lastIndexOf('/') + 1); - } - - return value.substring(index + "/v3/jobs/".length()); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - private record ServiceOfferingRef(@JsonProperty("guid") String guid) { - } - - @JsonIgnoreProperties(ignoreUnknown = true) - private record ServicePlanRef(@JsonProperty("guid") String guid) { + client.patch(CloudControllerV3Endpoints.SERVICE_PLAN_VISIBILITY, Map.of(V3Fields.TYPE, visibility.toString()), + servicePlanGuid.toString()); } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3Operations.java index c8f2b6c74e..340f996b50 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3Operations.java @@ -2,8 +2,6 @@ import java.text.MessageFormat; import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; @@ -11,10 +9,11 @@ import org.cloudfoundry.multiapps.controller.Constants; import org.cloudfoundry.multiapps.controller.Messages; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; -import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEntity; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Fields; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3GuidReference; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceInstance; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceInstanceMapper; @@ -23,13 +22,18 @@ import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpStatus; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; public class ServiceInstancesV3Operations { private static final ParameterizedTypeReference> SERVICE_INSTANCE_LIST_TYPE = new ParameterizedTypeReference<>() { }; + private static final ParameterizedTypeReference> GUID_REFERENCE_PAGE = new ParameterizedTypeReference<>() { + }; + private final CloudControllerV3Client client; + private final CloudSpace targetSpace; public ServiceInstancesV3Operations(CloudControllerV3Client client, CloudSpace targetSpace) { @@ -42,52 +46,28 @@ public void createServiceInstance(CloudServiceInstance serviceInstance) { Assert.notNull(serviceInstance, "Service instance must not be null."); UUID servicePlanGuid = findPlanGuidForService(serviceInstance, serviceInstance.getPlan()); - Map body = new HashMap<>(); - body.put("type", "managed"); - body.put("name", serviceInstance.getName()); - putIfNotNull(body, "metadata", toMetadataMap(serviceInstance.getV3Metadata())); - putIfNotNull(body, "tags", serviceInstance.getTags()); - putIfNotNull(body, "parameters", serviceInstance.getCredentials()); - body.put("relationships", Map.of("service_plan", toOneRelationship(servicePlanGuid.toString()), "space", - toOneRelationship(getTargetSpaceGuid().toString()))); + Map body = OperationsUtil.buildCreateServiceInstanceBody(serviceInstance, servicePlanGuid, targetSpace); - client.getRestClient() - .post() - .uri(CloudControllerV3Endpoints.SERVICE_INSTANCES) - .body(body) - .retrieve() - .toBodilessEntity(); + client.post(CloudControllerV3Endpoints.SERVICE_INSTANCES, body); } public void createUserProvidedServiceInstance(CloudServiceInstance serviceInstance) { assertSpaceProvided("create service instance"); Assert.notNull(serviceInstance, "Service instance must not be null."); - String syslogDrainUrl = hasText(serviceInstance.getSyslogDrainUrl()) ? serviceInstance.getSyslogDrainUrl() : ""; + String syslogDrainUrl = StringUtils.hasText(serviceInstance.getSyslogDrainUrl()) ? serviceInstance.getSyslogDrainUrl() : ""; - Map body = new HashMap<>(); - body.put("type", "user-provided"); - body.put("name", serviceInstance.getName()); - putIfNotNull(body, "metadata", toMetadataMap(serviceInstance.getV3Metadata())); - putIfNotNull(body, "credentials", serviceInstance.getCredentials()); - body.put("syslog_drain_url", syslogDrainUrl); - putIfNotNull(body, "tags", serviceInstance.getTags()); - body.put("relationships", Map.of("space", toOneRelationship(getTargetSpaceGuid().toString()))); + Map body = OperationsUtil.buildCreateUserProvidedServiceInstance(serviceInstance, syslogDrainUrl, targetSpace); - client.getRestClient() - .post() - .uri(CloudControllerV3Endpoints.SERVICE_INSTANCES) - .body(body) - .retrieve() - .toBodilessEntity(); + client.post(CloudControllerV3Endpoints.SERVICE_INSTANCES, body); } public void deleteServiceInstance(String serviceInstanceName) { CloudServiceInstance serviceInstance = getServiceInstanceWithoutAuxiliaryContent(serviceInstanceName); - doDeleteServiceInstance(serviceInstance.getGuid()); + deleteServiceInstance(serviceInstance.getGuid()); } public void deleteServiceInstance(CloudServiceInstance serviceInstance) { - doDeleteServiceInstance(serviceInstance.getGuid()); + deleteServiceInstance(serviceInstance.getGuid()); } public UUID getRequiredServiceInstanceGuid(String name) { @@ -158,9 +138,10 @@ public Map getUserProvidedServiceInstanceParameters(UUID guid) { public List getServiceInstancesWithoutAuxiliaryContentByNames(List names) { List allServiceInstances = new ArrayList<>(); - for (List batch : toBatches(names, Constants.MAX_CHAR_LENGTH_FOR_PARAMS_IN_REQUEST)) { + for (List batch : OperationsUtil.toBatches(names, Constants.MAX_CHAR_LENGTH_FOR_PARAMS_IN_REQUEST)) { String uri = CloudControllerV3Endpoints.SERVICE_INSTANCES + CloudControllerV3Endpoints.QUERY_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE + CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS + getTargetSpaceGuid() + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE + CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS + + OperationsUtil.getGuid(targetSpace) + CloudControllerV3Endpoints.AMPERSAND_NAMES + String.join(",", batch); client.list(uri, SERVICE_INSTANCE_LIST_TYPE) @@ -174,7 +155,8 @@ public List getServiceInstancesWithoutAuxiliaryContentByNa public List getServiceInstancesByMetadataLabelSelector(String labelSelector) { String uri = CloudControllerV3Endpoints.SERVICE_INSTANCES + CloudControllerV3Endpoints.QUERY_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE + CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS + getTargetSpaceGuid() + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE + CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS + + OperationsUtil.getGuid(targetSpace) + CloudControllerV3Endpoints.AMPERSAND_LABEL_SELECTOR + labelSelector; return ReactiveFanOut.mapConcurrently(client.list(uri, SERVICE_INSTANCE_LIST_TYPE), this::mapWithSupportingContent); @@ -182,7 +164,8 @@ public List getServiceInstancesByMetadataLabelSelector(Str public List getServiceInstancesWithoutAuxiliaryContentByMetadataLabelSelector(String labelSelector) { String uri = CloudControllerV3Endpoints.SERVICE_INSTANCES + CloudControllerV3Endpoints.QUERY_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE + CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS + getTargetSpaceGuid() + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE + CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS + + OperationsUtil.getGuid(targetSpace) + CloudControllerV3Endpoints.AMPERSAND_LABEL_SELECTOR + labelSelector; return client.list(uri, SERVICE_INSTANCE_LIST_TYPE) @@ -199,18 +182,20 @@ public void updateServicePlan(String serviceName, String planName) { } UUID planGuid = findPlanGuidForService(service, planName); - patchServiceInstance(service.getGuid(), Map.of("relationships", Map.of("service_plan", toOneRelationship(planGuid.toString())))); + patchServiceInstance(service.getGuid(), + Map.of(V3Fields.RELATIONSHIPS, + Map.of(V3Fields.SERVICE_PLAN, OperationsUtil.toOneRelationship(planGuid.toString())))); } public void updateServiceParameters(String serviceName, Map parameters) { CloudServiceInstance service = getServiceInstanceWithoutAuxiliaryContent(serviceName); - String key = service.isUserProvided() ? "credentials" : "parameters"; + String key = service.isUserProvided() ? V3Fields.CREDENTIALS : V3Fields.PARAMETERS; patchServiceInstance(service.getGuid(), Map.of(key, parameters)); } public void updateServiceTags(String serviceName, List tags) { UUID serviceInstanceGuid = getRequiredServiceInstanceGuid(serviceName); - patchServiceInstance(serviceInstanceGuid, Map.of("tags", tags)); + patchServiceInstance(serviceInstanceGuid, Map.of(V3Fields.TAGS, tags)); } public void updateServiceSyslogDrainUrl(String serviceName, String syslogDrainUrl) { @@ -220,13 +205,13 @@ public void updateServiceSyslogDrainUrl(String serviceName, String syslogDrainUr return; } - String updatedSyslogDrain = hasText(syslogDrainUrl) ? syslogDrainUrl : ""; - patchServiceInstance(service.getGuid(), Map.of("syslog_drain_url", updatedSyslogDrain)); + String updatedSyslogDrain = StringUtils.hasText(syslogDrainUrl) ? syslogDrainUrl : ""; + patchServiceInstance(service.getGuid(), Map.of(V3Fields.SYSLOG_DRAIN_URL, updatedSyslogDrain)); } public void updateServiceInstanceMetadata(UUID guid, Metadata metadata) { - Map metadataMap = toMetadataMap(metadata); - patchServiceInstance(guid, Map.of("metadata", metadataMap == null ? Map.of() : metadataMap)); + Map metadataMap = OperationsUtil.toMetadataMap(metadata); + patchServiceInstance(guid, Map.of(V3Fields.METADATA, metadataMap == null ? Map.of() : metadataMap)); } private CloudServiceInstance getServiceInstanceIfRequired(String serviceInstanceName, CloudServiceInstance serviceInstance, @@ -250,11 +235,11 @@ private CloudServiceInstance findServiceInstanceByName(String name) { } private CloudServiceInstance mapWithSupportingContent(V3ServiceInstance resource) { - if (isUserProvided(resource)) { + if (OperationsUtil.isUserProvided(resource)) { return V3ServiceInstanceMapper.toCloudServiceInstance(resource, null, null); } - String servicePlanGuid = servicePlanGuidOf(resource); + String servicePlanGuid = OperationsUtil.servicePlanGuidOf(resource); if (servicePlanGuid == null) { return V3ServiceInstanceMapper.toCloudServiceInstance(resource, null, null); } @@ -265,7 +250,8 @@ private CloudServiceInstance mapWithSupportingContent(V3ServiceInstance resource private V3ServiceInstance findServiceInstanceResourceByName(String name) { String uri = CloudControllerV3Endpoints.SERVICE_INSTANCES + CloudControllerV3Endpoints.QUERY_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE + CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS + getTargetSpaceGuid() + + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE + CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS + + OperationsUtil.getGuid(targetSpace) + CloudControllerV3Endpoints.AMPERSAND_NAMES + name; return client.list(uri, SERVICE_INSTANCE_LIST_TYPE) @@ -274,34 +260,15 @@ private V3ServiceInstance findServiceInstanceResourceByName(String name) { .orElse(null); } - private boolean isUserProvided(V3ServiceInstance resource) { - return "user-provided".equals(resource.type()); - } - - private String servicePlanGuidOf(V3ServiceInstance resource) { - if (resource.relationships() == null || resource.relationships() - .servicePlan() == null - || resource.relationships() - .servicePlan() - .data() == null) { - return null; - } - - return resource.relationships() - .servicePlan() - .data() - .guid(); - } - private ServicePlanNames resolvePlanAndOfferingNames(String servicePlanGuid, String serviceInstanceName) { - V3ServicePlan plan = getServicePlanForNameResolution(servicePlanGuid, serviceInstanceName); + V3ServicePlan plan = getServicePlanForServiceInstance(servicePlanGuid, serviceInstanceName); if (plan == null) { return new ServicePlanNames(null, null); } String offeringName = null; - String offeringGuid = offeringGuidOf(plan); + String offeringGuid = OperationsUtil.getServiceOfferingGuidFromServicePlan(plan); if (offeringGuid != null) { V3ServiceOffering offering = getServiceOfferingForNameResolution(offeringGuid); @@ -311,14 +278,16 @@ private ServicePlanNames resolvePlanAndOfferingNames(String servicePlanGuid, Str return new ServicePlanNames(plan.name(), offeringName); } - private V3ServicePlan getServicePlanForNameResolution(String servicePlanGuid, String serviceInstanceName) { + private V3ServicePlan getServicePlanForServiceInstance(String servicePlanGuid, String serviceInstanceName) { try { return client.get(CloudControllerV3Endpoints.SERVICE_PLANS + "/" + servicePlanGuid, V3ServicePlan.class); } catch (CloudOperationException e) { - throw determineNameResolutionError(e, - MessageFormat.format(Messages.SERVICE_PLAN_WITH_GUID_0_NOT_AVAILABLE_FOR_SERVICE_INSTANCE_1, - servicePlanGuid, serviceInstanceName), - MessageFormat.format(Messages.NO_SERVICE_PLAN_FOUND, servicePlanGuid, serviceInstanceName)); + throw OperationsUtil.determineNameResolutionError(e, + MessageFormat.format( + Messages.SERVICE_PLAN_WITH_GUID_0_NOT_AVAILABLE_FOR_SERVICE_INSTANCE_1, + servicePlanGuid, serviceInstanceName), + MessageFormat.format(Messages.NO_SERVICE_PLAN_FOUND, servicePlanGuid, + serviceInstanceName)); } } @@ -326,38 +295,12 @@ private V3ServiceOffering getServiceOfferingForNameResolution(String offeringGui try { return client.get(CloudControllerV3Endpoints.SERVICE_OFFERINGS + "/" + offeringGuid, V3ServiceOffering.class); } catch (CloudOperationException e) { - throw determineNameResolutionError(e, - MessageFormat.format(Messages.SERVICE_OFFERING_WITH_GUID_0_IS_NOT_AVAILABLE, offeringGuid), - MessageFormat.format(Messages.SERVICE_OFFERING_WITH_GUID_0_NOT_FOUND, offeringGuid)); - } - } - - private CloudOperationException determineNameResolutionError(CloudOperationException e, String forbiddenMessage, - String notFoundMessage) { - if (e.getStatusCode() == HttpStatus.FORBIDDEN) { - return new CloudOperationException(HttpStatus.FORBIDDEN, Messages.FORBIDDEN, forbiddenMessage, e); + throw OperationsUtil.determineNameResolutionError(e, + MessageFormat.format(Messages.SERVICE_OFFERING_WITH_GUID_0_IS_NOT_AVAILABLE, + offeringGuid), + MessageFormat.format(Messages.SERVICE_OFFERING_WITH_GUID_0_NOT_FOUND, + offeringGuid)); } - - if (e.getStatusCode() == HttpStatus.NOT_FOUND) { - return new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, notFoundMessage, e); - } - - return e; - } - - private String offeringGuidOf(V3ServicePlan plan) { - if (plan.relationships() == null || plan.relationships() - .serviceOffering() == null - || plan.relationships() - .serviceOffering() - .data() == null) { - return null; - } - - return plan.relationships() - .serviceOffering() - .data() - .guid(); } private UUID findPlanGuidForService(CloudServiceInstance service, String planName) { @@ -368,11 +311,10 @@ private UUID findPlanGuidForService(CloudServiceInstance service, String planNam + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE + CloudControllerV3Endpoints.AMPERSAND_SERVICE_OFFERING_GUIDS + offeringGuid + CloudControllerV3Endpoints.AMPERSAND_NAMES + planName; - V3ServicePlan plan = client.list(uri, new ParameterizedTypeReference>() { - }) - .stream() - .findFirst() - .orElse(null); + V3GuidReference plan = client.list(uri, GUID_REFERENCE_PAGE) + .stream() + .findFirst() + .orElse(null); if (plan != null) { return UUID.fromString(plan.guid()); @@ -390,106 +332,30 @@ private List findServiceOfferingGuids(String label, String broker) { .append( CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) .append( - getTargetSpaceGuid()) + OperationsUtil.getGuid( + targetSpace)) .append( CloudControllerV3Endpoints.AMPERSAND_NAMES) .append( label); - if (hasText(broker)) { + if (StringUtils.hasText(broker)) { uri.append(CloudControllerV3Endpoints.AMPERSAND_SERVICE_BROKER_NAMES) .append(broker); } - return client.list(uri.toString(), new ParameterizedTypeReference>() { - }) + return client.list(uri.toString(), GUID_REFERENCE_PAGE) .stream() - .map(V3ServiceOffering::guid) + .map(V3GuidReference::guid) .toList(); } - private void doDeleteServiceInstance(UUID serviceInstanceGuid) { - client.getRestClient() - .delete() - .uri(CloudControllerV3Endpoints.SERVICE_INSTANCE_BY_GUID, serviceInstanceGuid.toString()) - .retrieve() - .toBodilessEntity(); + private void deleteServiceInstance(UUID serviceInstanceGuid) { + client.delete(CloudControllerV3Endpoints.SERVICE_INSTANCE_BY_GUID, serviceInstanceGuid.toString()); } private void patchServiceInstance(UUID guid, Map body) { - client.getRestClient() - .patch() - .uri(CloudControllerV3Endpoints.SERVICE_INSTANCE_BY_GUID, guid.toString()) - .body(body) - .retrieve() - .toBodilessEntity(); - } - - private Map toOneRelationship(String guid) { - return Map.of("data", Map.of("guid", guid)); - } - - private Map toMetadataMap(Metadata metadata) { - if (metadata == null) { - return null; - } - - Map metadataMapResult = new HashMap<>(); - metadataMapResult.put("labels", metadata.getLabels() == null ? Map.of() : metadata.getLabels()); - metadataMapResult.put("annotations", metadata.getAnnotations() == null ? Map.of() : metadata.getAnnotations()); - - return metadataMapResult; - } - - private static void putIfNotNull(Map body, String key, Object value) { - if (value != null) { - body.put(key, value); - } - } - - private static boolean hasText(String value) { - return value != null && !value.isBlank(); - } - - private List> toBatches(Collection largeList, int maxCharLength) { - if (largeList.isEmpty()) { - return List.of(); - } - - List> batches = new ArrayList<>(); - int currentBatchLength = 0; - int currentBatchIndex = 0; - batches.add(new ArrayList<>()); - - for (T element : largeList) { - int elementLength = element.toString() - .length(); - - if (elementLength + currentBatchLength >= maxCharLength) { - batches.add(new ArrayList<>()); - currentBatchIndex++; - currentBatchLength = 0; - } - - batches.get(currentBatchIndex) - .add(element); - currentBatchLength += elementLength; - } - - return batches; - } - - private UUID getTargetSpaceGuid() { - return getGuid(targetSpace); - } - - private UUID getGuid(CloudEntity entity) { - if (entity == null || entity.getMetadata() == null) { - return null; - } - - return entity.getMetadata() - .getGuid(); + client.patch(CloudControllerV3Endpoints.SERVICE_INSTANCE_BY_GUID, body, guid.toString()); } private void assertSpaceProvided(String operation) { diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3Operations.java index dfe598fc73..b93f4f74fa 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3Operations.java @@ -1,8 +1,6 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest; -import java.net.URI; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -23,6 +21,9 @@ public class ServiceKeysV3Operations { + private static final ParameterizedTypeReference> SERVICE_KEY_PAGE = new ParameterizedTypeReference<>() { + }; + private final CloudControllerV3Client client; public ServiceKeysV3Operations(CloudControllerV3Client client) { @@ -34,28 +35,20 @@ public CloudServiceKey createAndFetchServiceKey(CloudServiceKey keyModel, CloudS serviceInstance); client.followAsyncJob(acceptedResponse, Constants.BINDING_OPERATIONS_TIMEOUT); - - V3ServiceBinding createdServiceKey = getServiceKeyResourceByNameAndServiceInstanceGuid(keyModel.getName(), - serviceInstance.getGuid()); - if (createdServiceKey == null) { - return null; - } - - Map credentials = getServiceKeyCredentials(createdServiceKey.guid()); - return V3ServiceKeyMapper.toCloudServiceKey(createdServiceKey, serviceInstance, credentials); + return getServiceKey(serviceInstance, keyModel.getName()); } public Optional createServiceKey(CloudServiceKey keyModel, CloudServiceInstance serviceInstance) { ResponseEntity acceptedResponse = postServiceKey(keyModel.getName(), keyModel.getCredentials(), keyModel.getV3Metadata(), serviceInstance); - return extractJobGuid(acceptedResponse); + return OperationsUtil.extractJobGuid(acceptedResponse); } public Optional createServiceKey(CloudServiceInstance serviceInstance, String serviceKeyName, Map parameters) { ResponseEntity acceptedResponse = postServiceKey(serviceKeyName, parameters, null, serviceInstance); - return extractJobGuid(acceptedResponse); + return OperationsUtil.extractJobGuid(acceptedResponse); } public CloudServiceKey getServiceKey(CloudServiceInstance serviceInstance, String serviceKeyName) { @@ -87,48 +80,8 @@ private ResponseEntity postServiceKey(String name, Map par String.format(Messages.CANT_CREATE_SERVICE_KEY_FOR_USER_PROVIDED_SERVICE, serviceInstance.getName())); } - return client.getRestClient() - .post() - .uri(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS) - .body(buildCreateServiceKeyBody(name, parameters, metadata, serviceInstance.getGuid())) - .retrieve() - .toBodilessEntity(); - } - - private Map buildCreateServiceKeyBody(String name, Map parameters, Metadata metadata, - UUID serviceInstanceGuid) { - Map resultBody = new HashMap<>(); - resultBody.put("type", "key"); - resultBody.put("name", name); - resultBody.put("relationships", Map.of("service_instance", Map.of("data", Map.of("guid", serviceInstanceGuid.toString())))); - - if (parameters != null && !parameters.isEmpty()) { - resultBody.put("parameters", parameters); - } - - Map metadataBody = buildMetadataBody(metadata); - if (metadataBody != null) { - resultBody.put("metadata", metadataBody); - } - - return resultBody; - } - - private Map buildMetadataBody(Metadata metadata) { - if (metadata == null) { - return null; - } - - Map metadataBody = new HashMap<>(); - if (metadata.getLabels() != null) { - metadataBody.put("labels", metadata.getLabels()); - } - - if (metadata.getAnnotations() != null) { - metadataBody.put("annotations", metadata.getAnnotations()); - } - - return metadataBody.isEmpty() ? null : metadataBody; + return client.post(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS, + OperationsUtil.buildCreateServiceKeyBody(name, parameters, metadata, serviceInstance.getGuid())); } private V3ServiceBinding getServiceKeyResourceByNameAndServiceInstanceGuid(String name, UUID serviceInstanceGuid) { @@ -137,8 +90,7 @@ private V3ServiceBinding getServiceKeyResourceByNameAndServiceInstanceGuid(Strin + CloudControllerV3Endpoints.AMPERSAND_SERVICE_INSTANCE_GUIDS + serviceInstanceGuid + CloudControllerV3Endpoints.AMPERSAND_NAMES + name; - List keys = client.list(query, new ParameterizedTypeReference>() { - }); + List keys = client.list(query, SERVICE_KEY_PAGE); return keys.isEmpty() ? null : keys.getFirst(); } @@ -148,8 +100,7 @@ private List listServiceKeyResources(UUID serviceInstanceGuid) + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE + CloudControllerV3Endpoints.AMPERSAND_TYPE + "key" + CloudControllerV3Endpoints.AMPERSAND_SERVICE_INSTANCE_GUIDS + serviceInstanceGuid; - return client.list(query, new ParameterizedTypeReference>() { - }); + return client.list(query, SERVICE_KEY_PAGE); } private Map getServiceKeyCredentials(String keyGuid) { @@ -161,18 +112,4 @@ private Map getServiceKeyCredentials(String keyGuid) { .orElse(Collections.emptyMap()); } - private Optional extractJobGuid(ResponseEntity accepted) { - URI location = accepted.getHeaders() - .getLocation(); - - if (location == null) { - return Optional.empty(); - } - - String href = location.toString(); - String jobGuid = href.substring(href.lastIndexOf('/') + 1); - - return Optional.of(jobGuid); - } - } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3Operations.java index 5ca510f314..6c7a9d740c 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3Operations.java @@ -14,9 +14,9 @@ public class ServiceOfferingsV3Operations { - private static final ParameterizedTypeReference> OFFERING_LIST_TYPE = new ParameterizedTypeReference<>() { + private static final ParameterizedTypeReference> OFFERING_PAGE = new ParameterizedTypeReference<>() { }; - private static final ParameterizedTypeReference> PLAN_LIST_TYPE = new ParameterizedTypeReference<>() { + private static final ParameterizedTypeReference> PLAN_PAGE = new ParameterizedTypeReference<>() { }; private final CloudControllerV3Client client; @@ -42,7 +42,7 @@ private List getServiceOfferingResources() { .append(targetSpace.getGuid()); } - return client.list(query.toString(), OFFERING_LIST_TYPE); + return client.list(query.toString(), OFFERING_PAGE); } private CloudServiceOffering toCloudServiceOfferingWithPlans(V3ServiceOffering serviceOffering) { @@ -56,7 +56,7 @@ private List getServicePlans(String serviceOfferingGuid) { CloudControllerV3Endpoints.SERVICE_PLANS + CloudControllerV3Endpoints.QUERY_SERVICE_OFFERING_GUIDS + serviceOfferingGuid + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; - return client.list(uri, PLAN_LIST_TYPE) + return client.list(uri, PLAN_PAGE) .stream() .map(V3ServicePlanMapper::toCloudServicePlan) .toList(); diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3Operations.java index ff5251c1ba..e88ddea48f 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3Operations.java @@ -4,28 +4,25 @@ import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.stream.Collectors; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; import org.cloudfoundry.multiapps.controller.Messages; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; import org.cloudfoundry.multiapps.controller.client.facade.domain.ServicePlanVisibility; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Fields; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3GuidReference; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; -import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServicePlan; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpStatus; public class ServicePlansV3Operations { - private static final ParameterizedTypeReference> BROKER_LIST_TYPE = new ParameterizedTypeReference<>() { - }; - private static final ParameterizedTypeReference> OFFERING_LIST_TYPE = new ParameterizedTypeReference<>() { - }; - private static final ParameterizedTypeReference> PLAN_LIST_TYPE = new ParameterizedTypeReference<>() { + private static final ParameterizedTypeReference> GUID_REFERENCE_PAGE = new ParameterizedTypeReference<>() { }; private final CloudControllerV3Client client; + private final CloudSpace targetSpace; public ServicePlansV3Operations(CloudControllerV3Client client, CloudSpace targetSpace) { @@ -46,7 +43,7 @@ private UUID getRequiredServiceBrokerGuid(String name) { String uri = CloudControllerV3Endpoints.SERVICE_BROKERS + CloudControllerV3Endpoints.QUERY_NAMES + name + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; - return client.list(uri, BROKER_LIST_TYPE) + return client.list(uri, GUID_REFERENCE_PAGE) .stream() .findFirst() .map(broker -> UUID.fromString(broker.guid())) @@ -62,13 +59,12 @@ private List findServicePlanGuidsByBrokerGuid(UUID brokerGuid) { String offeringGuidsFilter = offeredServicesGuids.stream() .map(UUID::toString) - .reduce((a, b) -> a + "," + b) - .orElse(""); + .collect(Collectors.joining(",")); String uri = CloudControllerV3Endpoints.SERVICE_PLANS + CloudControllerV3Endpoints.QUERY_SERVICE_OFFERING_GUIDS + offeringGuidsFilter + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; - return client.list(uri, PLAN_LIST_TYPE) + return client.list(uri, GUID_REFERENCE_PAGE) .stream() .map(plan -> UUID.fromString(plan.guid())) .toList(); @@ -85,27 +81,14 @@ private List findServiceOfferingGuidsByBrokerGuid(UUID brokerGuid) { .append(targetSpace.getGuid()); } - return client.list(query.toString(), OFFERING_LIST_TYPE) + return client.list(query.toString(), GUID_REFERENCE_PAGE) .stream() .map(offering -> UUID.fromString(offering.guid())) .toList(); } private void updateServicePlanVisibility(UUID servicePlanGuid, ServicePlanVisibility visibility) { - client.getRestClient() - .patch() - .uri(CloudControllerV3Endpoints.SERVICE_PLAN_VISIBILITY, servicePlanGuid) - .body(Map.of("type", visibility.toString())) - .retrieve() - .toBodilessEntity(); - } - - @JsonIgnoreProperties(ignoreUnknown = true) - private record V3ServiceBrokerRef(@JsonProperty("guid") String guid) { - } - - @JsonIgnoreProperties(ignoreUnknown = true) - private record V3ServiceOfferingRef(@JsonProperty("guid") String guid) { + client.patch(CloudControllerV3Endpoints.SERVICE_PLAN_VISIBILITY, Map.of(V3Fields.TYPE, visibility.toString()), servicePlanGuid); } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/TasksV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/TasksV3Operations.java index aa2baa44f5..8ac0c26308 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/TasksV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/TasksV3Operations.java @@ -1,7 +1,6 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest; import java.text.MessageFormat; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; @@ -10,7 +9,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudTask; -import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Application; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3GuidReference; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Task; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3TaskMapper; @@ -19,7 +18,14 @@ public class TasksV3Operations { + private static final ParameterizedTypeReference> TASK_PAGE = new ParameterizedTypeReference<>() { + }; + + private static final ParameterizedTypeReference> APP_PAGE = new ParameterizedTypeReference<>() { + }; + private final CloudControllerV3Client client; + private final CloudSpace targetSpace; public TasksV3Operations(CloudControllerV3Client client, CloudSpace targetSpace) { @@ -39,8 +45,7 @@ public List getTasks(String applicationName) { String query = CloudControllerV3Endpoints.TASKS + CloudControllerV3Endpoints.QUERY_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE + CloudControllerV3Endpoints.AMPERSAND_APP_GUIDS + applicationGuid; - List tasks = client.list(query, new ParameterizedTypeReference>() { - }); + List tasks = client.list(query, TASK_PAGE); return tasks.stream() .map(V3TaskMapper::toCloudTask) @@ -49,59 +54,22 @@ public List getTasks(String applicationName) { public CloudTask runTask(String applicationName, CloudTask task) { UUID applicationGuid = getRequiredApplicationGuid(applicationName); + Map createTaskBody = OperationsUtil.buildCreateTaskBody(task); - V3Task created = client.getRestClient() - .post() - .uri(CloudControllerV3Endpoints.APP_TASKS, applicationGuid) - .body(buildCreateTaskBody(task)) - .retrieve() - .body(V3Task.class); + V3Task created = client.postForObject(CloudControllerV3Endpoints.APP_TASKS, createTaskBody, V3Task.class, applicationGuid); return created == null ? null : V3TaskMapper.toCloudTask(created); } public CloudTask cancelTask(UUID taskGuid) { - V3Task cancelled = client.getRestClient() - .post() - .uri(CloudControllerV3Endpoints.TASK_CANCEL, taskGuid) - .retrieve() - .body(V3Task.class); + V3Task cancelled = client.postForObject(CloudControllerV3Endpoints.TASK_CANCEL, null, V3Task.class, taskGuid); return cancelled == null ? null : V3TaskMapper.toCloudTask(cancelled); } - private Map buildCreateTaskBody(CloudTask task) { - Map body = new HashMap<>(); - body.put("command", task.getCommand()); - body.put("name", task.getName()); - - CloudTask.Limits limits = task.getLimits(); - if (limits != null) { - if (limits.getMemory() != null) { - body.put("memory_in_mb", limits.getMemory()); - } - - if (limits.getDisk() != null) { - body.put("disk_in_mb", limits.getDisk()); - } - } - - return body; - } - private UUID getRequiredApplicationGuid(String applicationName) { - StringBuilder query = new StringBuilder(CloudControllerV3Endpoints.APPS + CloudControllerV3Endpoints.QUERY_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE); - - if (targetSpace != null && targetSpace.getGuid() != null) { - query.append(CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) - .append(targetSpace.getGuid()); - } - - query.append(CloudControllerV3Endpoints.AMPERSAND_NAMES) - .append(applicationName); - List apps = client.list(query.toString(), new ParameterizedTypeReference>() { - }); + String applicationsQuery = OperationsQueryUtil.buildApplicationsQuery(applicationName, targetSpace); + List apps = client.list(applicationsQuery, APP_PAGE); if (apps.isEmpty()) { throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Fields.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Fields.java index a99a3675e1..422e1cb169 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Fields.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Fields.java @@ -15,9 +15,43 @@ public class V3Fields { public static final String APP = "app"; public static final String SPACE = "space"; public static final String ORGANIZATION = "organization"; - public static final String COMMAND = "command"; public static final String SERVICE_INSTANCE = "service_instance"; + public static final String SERVICE_PLAN = "service_plan"; + public static final String DOMAIN = "domain"; + public static final String LABELS = "labels"; + public static final String ANNOTATIONS = "annotations"; + public static final String COMMAND = "command"; + public static final String INSTANCES = "instances"; + public static final String MEMORY_IN_MB = "memory_in_mb"; + public static final String DISK_IN_MB = "disk_in_mb"; + public static final String LIFECYCLE = "lifecycle"; + public static final String BUILDPACKS = "buildpacks"; + public static final String STACK = "stack"; + public static final String ENVIRONMENT_VARIABLES = "environment_variables"; + public static final String VAR = "var"; + public static final String HEALTH_CHECK = "health_check"; + public static final String READINESS_HEALTH_CHECK = "readiness_health_check"; + public static final String ENDPOINT = "endpoint"; + public static final String INTERVAL = "interval"; + public static final String INVOCATION_TIMEOUT = "invocation_timeout"; + public static final String TIMEOUT = "timeout"; + public static final String TAGS = "tags"; + public static final String PARAMETERS = "parameters"; + public static final String CREDENTIALS = "credentials"; + public static final String SYSLOG_DRAIN_URL = "syslog_drain_url"; + public static final String DOCKER = "docker"; + public static final String BITS = "bits"; + public static final String IMAGE = "image"; + public static final String USERNAME = "username"; + public static final String PASSWORD = "password"; + public static final String PACKAGE = "package"; + public static final String HOST = "host"; + public static final String PATH = "path"; public static final String PORT = "port"; + public static final String PROTOCOL = "protocol"; + public static final String URL = "url"; + public static final String DESTINATIONS = "destinations"; + public static final String AUTHENTICATION = "authentication"; public static final String DETAIL = "detail"; public static final String ENABLED = "enabled"; diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3GuidReference.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3GuidReference.java new file mode 100644 index 0000000000..e039088e9d --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3GuidReference.java @@ -0,0 +1,8 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3GuidReference(@JsonProperty(V3Fields.GUID) String guid) { +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3JobMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3JobMapper.java index b643ed0130..67df0d4cbb 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3JobMapper.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3JobMapper.java @@ -23,7 +23,7 @@ public static CloudAsyncJob toCloudAsyncJob(V3Job job) { } private static JobState parseState(String state) { - return state == null ? null : JobState.valueOf(state.toUpperCase()); + return JobState.valueOf(state.toUpperCase()); } private static String getWarnings(V3Job job) { diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RouteMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RouteMapper.java index 6c8691d6e4..9aad0dee9b 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RouteMapper.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RouteMapper.java @@ -11,6 +11,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudRoute; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableRouteDestination; import org.cloudfoundry.multiapps.controller.client.facade.domain.RouteDestination; +import org.springframework.util.StringUtils; public final class V3RouteMapper { @@ -47,14 +48,13 @@ public static CloudRoute toCloudRoute(V3Route route, UUID applicationGuid) { private static String computeDomain(V3Route route) { String domain = route.url(); - String host = route.host() == null ? "" : route.host(); - String path = route.path() == null ? "" : route.path(); - if (!host.isEmpty()) { - domain = domain.substring(host.length() + 1); + if (StringUtils.hasText(route.host())) { + domain = domain.substring(route.host() + .length() + 1); } - if (!path.isEmpty()) { + if (StringUtils.hasText(route.path())) { domain = domain.substring(0, domain.indexOf('/')); } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstanceMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstanceMapper.java index 7e5ee5a9f4..5953c331d3 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstanceMapper.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstanceMapper.java @@ -7,8 +7,6 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType; import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceOperation; -import static org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType.mapValue; - public final class V3ServiceInstanceMapper { private V3ServiceInstanceMapper() { @@ -48,7 +46,7 @@ private static ServiceInstanceType parseType(String type) { return null; } - return mapValue(type); + return ServiceInstanceType.mapTypeToServiceInstanceType(type); } private static ServiceOperation parseLastOperation(V3ServiceInstance.V3LastOperation lastOperation) { diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3OperationsTest.java index fd361be660..89904a661e 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3OperationsTest.java @@ -75,11 +75,8 @@ void testCreateApplicationWithScaleAlsoScalesWebProcess() { .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) .andRespond(MockRestResponseCreators.withSuccess(appJson(APP_GUID, "my-app", "STOPPED"), MediaType.APPLICATION_JSON)); - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID - + "/processes/web/actions/scale")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) - .andRespond(MockRestResponseCreators.withSuccess()); + factory.stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/processes/web/actions/scale", HttpMethod.POST); ApplicationToCreateDto dto = ImmutableApplicationToCreateDto.builder() .name("my-app") @@ -120,10 +117,7 @@ void testDeleteApplicationLooksUpGuidAndDeletes() { stubAppLookupByName("my-app", APP_GUID); - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.DELETE)) - .andRespond(MockRestResponseCreators.withSuccess()); + factory.stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID, HttpMethod.DELETE); operations.deleteApplication("my-app"); @@ -145,11 +139,7 @@ void testGetApplicationReturnsMappedApplication() { when(target.getGuid()) .thenReturn(null); - factory.server() - .expect(MockRestRequestMatchers.requestTo(APPS_BASE + "&names=my-app")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(listJson(appJson(APP_GUID, "my-app", "STARTED")), - MediaType.APPLICATION_JSON)); + factory.stubGet(APPS_BASE + "&names=my-app", listJson(appJson(APP_GUID, "my-app", "STARTED"))); CloudApplication result = operations.getApplication("my-app"); @@ -199,11 +189,7 @@ void testGetApplicationGuidReturnsGuid() { void testGetApplicationQueryIncludesSpaceGuidWhenPresent() { when(target.getGuid()) .thenReturn(UUID.fromString(SPACE_GUID)); - factory.server() - .expect(MockRestRequestMatchers.requestTo(APPS_BASE + "&space_guids=" + SPACE_GUID + "&names=my-app")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(listJson(appJson(APP_GUID, "my-app", "STARTED")), - MediaType.APPLICATION_JSON)); + factory.stubGet(APPS_BASE + "&space_guids=" + SPACE_GUID + "&names=my-app", listJson(appJson(APP_GUID, "my-app", "STARTED"))); CloudApplication result = operations.getApplication("my-app"); @@ -213,10 +199,7 @@ void testGetApplicationQueryIncludesSpaceGuidWhenPresent() { @Test void testGetApplicationNameReturnsName() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(appJson(APP_GUID, "my-app", "STARTED"), MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID, appJson(APP_GUID, "my-app", "STARTED")); String result = operations.getApplicationName(UUID.fromString(APP_GUID)); @@ -226,11 +209,8 @@ void testGetApplicationNameReturnsName() { @Test void testGetApplicationEnvironmentByGuidReturnsVariables() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID - + "/environment_variables")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"var\":{\"FOO\":\"bar\"}}", MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/environment_variables", "{\"var\":{\"FOO\":\"bar\"}}"); Map result = operations.getApplicationEnvironment(UUID.fromString(APP_GUID)); @@ -240,11 +220,8 @@ void testGetApplicationEnvironmentByGuidReturnsVariables() { @Test void testGetApplicationEnvironmentByGuidReturnsEmptyWhenNoVars() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID - + "/environment_variables")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{}", MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/environment_variables", "{}"); Map result = operations.getApplicationEnvironment(UUID.fromString(APP_GUID)); @@ -259,11 +236,8 @@ void testGetApplicationEnvironmentByNameResolvesGuidFirst() { stubAppLookupByName("my-app", APP_GUID); - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID - + "/environment_variables")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"var\":{\"A\":\"B\"}}", MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/environment_variables", "{\"var\":{\"A\":\"B\"}}"); Map result = operations.getApplicationEnvironment("my-app"); @@ -276,11 +250,7 @@ void testGetApplicationsReturnsMappedList() { when(target.getGuid()) .thenReturn(null); - factory.server() - .expect(MockRestRequestMatchers.requestTo(APPS_BASE)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(listJson(appJson(APP_GUID, "my-app", "STARTED")), - MediaType.APPLICATION_JSON)); + factory.stubGet(APPS_BASE, listJson(appJson(APP_GUID, "my-app", "STARTED"))); List result = operations.getApplications(); @@ -295,10 +265,7 @@ void testGetApplicationsReturnsEmptyList() { when(target.getGuid()) .thenReturn(null); - factory.server() - .expect(MockRestRequestMatchers.requestTo(APPS_BASE)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + factory.stubGet(APPS_BASE, "{\"resources\":[]}"); List result = operations.getApplications(); @@ -311,11 +278,7 @@ void testGetApplicationsByMetadataLabelSelectorAppendsSelector() { when(target.getGuid()) .thenReturn(null); - factory.server() - .expect(MockRestRequestMatchers.requestTo(APPS_BASE + "&label_selector=env")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(listJson(appJson(APP_GUID, "my-app", "STARTED")), - MediaType.APPLICATION_JSON)); + factory.stubGet(APPS_BASE + "&label_selector=env", listJson(appJson(APP_GUID, "my-app", "STARTED"))); List result = operations.getApplicationsByMetadataLabelSelector("env"); @@ -328,10 +291,7 @@ void testGetApplicationsByMetadataLabelSelectorWithNullSelector() { when(target.getGuid()) .thenReturn(null); - factory.server() - .expect(MockRestRequestMatchers.requestTo(APPS_BASE)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + factory.stubGet(APPS_BASE, "{\"resources\":[]}"); List result = operations.getApplicationsByMetadataLabelSelector(null); @@ -346,11 +306,8 @@ void testStartApplicationPostsToStartAction() { stubAppLookupByName("my-app", APP_GUID); - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID - + "/actions/start")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) - .andRespond(MockRestResponseCreators.withSuccess()); + factory.stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/actions/start", HttpMethod.POST); operations.startApplication("my-app"); @@ -364,11 +321,8 @@ void testStopApplicationPostsToStopAction() { stubAppLookupByName("my-app", APP_GUID); - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID - + "/actions/stop")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) - .andRespond(MockRestResponseCreators.withSuccess()); + factory.stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/actions/stop", HttpMethod.POST); operations.stopApplication("my-app"); @@ -382,10 +336,7 @@ void testRenameUpdatesName() { stubAppLookupByName("my-app", APP_GUID); - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) - .andRespond(MockRestResponseCreators.withSuccess()); + factory.stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID, HttpMethod.PATCH); operations.rename("my-app", "new-name"); @@ -404,10 +355,7 @@ void testRenameSwallowsServiceUnavailableWhenAlreadyRenamed() { .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) .andRespond(MockRestResponseCreators.withStatus(HttpStatus.SERVICE_UNAVAILABLE)); - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(appJson(APP_GUID, "new-name", "STOPPED"), MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID, appJson(APP_GUID, "new-name", "STOPPED")); operations.rename("my-app", "new-name"); @@ -426,10 +374,7 @@ void testRenameRethrowsServiceUnavailableWhenNotRenamed() { .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) .andRespond(MockRestResponseCreators.withStatus(HttpStatus.SERVICE_UNAVAILABLE)); - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(appJson(APP_GUID, "still-old", "STOPPED"), MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID, appJson(APP_GUID, "still-old", "STOPPED")); Assertions.assertThrows(CloudOperationException.class, () -> operations.rename("my-app", "new-name")); } @@ -468,11 +413,8 @@ void testUpdateApplicationEnvUpdatesEnvVars() { stubAppLookupByName("my-app", APP_GUID); - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID - + "/environment_variables")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) - .andRespond(MockRestResponseCreators.withSuccess()); + factory.stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/environment_variables", HttpMethod.PATCH); operations.updateApplicationEnv("my-app", Map.of("FOO", "bar")); @@ -483,11 +425,8 @@ void testUpdateApplicationEnvUpdatesEnvVars() { void testBindDropletToAppUpdatesCurrentDroplet() { UUID dropletGuid = UUID.fromString("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"); - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID - + "/relationships/current_droplet")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) - .andRespond(MockRestResponseCreators.withSuccess()); + factory.stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/relationships/current_droplet", HttpMethod.PATCH); operations.bindDropletToApp(dropletGuid, UUID.fromString(APP_GUID)); @@ -496,10 +435,7 @@ void testBindDropletToAppUpdatesCurrentDroplet() { @Test void testUpdateApplicationMetadataUpdatesApp() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) - .andRespond(MockRestResponseCreators.withSuccess()); + factory.stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID, HttpMethod.PATCH); Metadata metadata = Metadata.builder() .label("k", "v") @@ -517,25 +453,16 @@ private void stubScaleFlow() { stubAppLookupByName("my-app", APP_GUID); - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID - + "/processes/web/actions/scale")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) - .andRespond(MockRestResponseCreators.withSuccess()); + factory.stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/processes/web/actions/scale", HttpMethod.POST); } private void stubAppLookupByName(String name, String guid) { - factory.server() - .expect(MockRestRequestMatchers.requestTo(APPS_BASE + "&names=" + name)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(listJson(appJson(guid, name, "STARTED")), MediaType.APPLICATION_JSON)); + factory.stubGet(APPS_BASE + "&names=" + name, listJson(appJson(guid, name, "STARTED"))); } private void stubEmptyAppLookupByName(String name) { - factory.server() - .expect(MockRestRequestMatchers.requestTo(APPS_BASE + "&names=" + name)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + factory.stubGet(APPS_BASE + "&names=" + name, "{\"resources\":[]}"); } private static String appJson(String guid, String name, String state) { diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3OperationsTest.java index e2b5dbe687..e220947b48 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3OperationsTest.java @@ -49,10 +49,7 @@ void testCreateBuildPostsPackageAndReturnsMappedBuild() { @Test void testGetBuildReturnsMappedBuild() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/builds/" + BUILD_GUID)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(getBuildJson("STAGED"), MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/builds/" + BUILD_GUID, getBuildJson("STAGED")); CloudBuild result = operations.getBuild(UUID.fromString(BUILD_GUID)); @@ -75,12 +72,8 @@ void testGetBuildThrowsWhenNotFound() { @Test void testGetBuildsForApplicationReturnsMappedList() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID - + "/builds?per_page=5000")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[" + getBuildJson("STAGED") + "]}", - MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/builds?per_page=5000", "{\"resources\":[" + getBuildJson("STAGED") + "]}"); List result = operations.getBuildsForApplication(UUID.fromString(APP_GUID)); @@ -92,11 +85,8 @@ void testGetBuildsForApplicationReturnsMappedList() { @Test void testGetBuildsForApplicationReturnsEmptyList() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID - + "/builds?per_page=5000")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/builds?per_page=5000", "{\"resources\":[]}"); List result = operations.getBuildsForApplication(UUID.fromString(APP_GUID)); @@ -106,12 +96,8 @@ void testGetBuildsForApplicationReturnsEmptyList() { @Test void testGetBuildsForPackageReturnsMappedList() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/builds?package_guids=" - + PACKAGE_GUID + "&per_page=5000")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[" + getBuildJson("FAILED") + "]}", - MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/builds?package_guids=" + + PACKAGE_GUID + "&per_page=5000", "{\"resources\":[" + getBuildJson("FAILED") + "]}"); List result = operations.getBuildsForPackage(UUID.fromString(PACKAGE_GUID)); @@ -125,11 +111,8 @@ void testGetBuildsForPackageReturnsMappedList() { @Test void testGetBuildsForPackageReturnsEmptyList() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/builds?package_guids=" - + PACKAGE_GUID + "&per_page=5000")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/builds?package_guids=" + + PACKAGE_GUID + "&per_page=5000", "{\"resources\":[]}"); List result = operations.getBuildsForPackage(UUID.fromString(PACKAGE_GUID)); diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientV3ImplTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientV3ImplTest.java index ec501cd039..2e5344b3e1 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientV3ImplTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientV3ImplTest.java @@ -98,11 +98,7 @@ void testGetTargetReturnsInjectedSpace() { @Test void testGetStackReturnsMappedStack() { - factory.server() - .expect(MockRestRequestMatchers.requestTo( - MockControllerClientFactory.BASE_URL + "/v3/stacks?names=cflinuxfs4&per_page=5000")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(getStackListJson(), MediaType.APPLICATION_JSON)); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/stacks?names=cflinuxfs4&per_page=5000", getStackListJson()); CloudStack result = client.getStack("cflinuxfs4"); @@ -112,10 +108,7 @@ void testGetStackReturnsMappedStack() { @Test void testGetStacksReturnsMappedList() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/stacks?per_page=5000")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(getStackListJson(), MediaType.APPLICATION_JSON)); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/stacks?per_page=5000", getStackListJson()); List result = client.getStacks(); @@ -125,10 +118,7 @@ void testGetStacksReturnsMappedList() { @Test void testGetDomainsReturnsMappedList() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/domains?per_page=5000")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(getDomainListJson(), MediaType.APPLICATION_JSON)); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?per_page=5000", getDomainListJson()); List result = client.getDomains(); @@ -140,10 +130,7 @@ void testGetDomainsReturnsMappedList() { @Test void testGetSharedDomainsReturnsMappedList() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/domains?per_page=5000")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(getDomainListJson(), MediaType.APPLICATION_JSON)); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?per_page=5000", getDomainListJson()); List result = client.getSharedDomains(); @@ -153,13 +140,7 @@ void testGetSharedDomainsReturnsMappedList() { @Test void testGetApplicationReturnsMappedApplication() { - when(target.getGuid()) - .thenReturn(null); - - factory.server() - .expect(MockRestRequestMatchers.requestTo(APPS_BASE + "&names=my-app")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(getAppListJson(), MediaType.APPLICATION_JSON)); + stubApplicationLookup(); CloudApplication result = client.getApplication("my-app"); @@ -173,10 +154,7 @@ void testGetApplicationsReturnsMappedList() { when(target.getGuid()) .thenReturn(null); - factory.server() - .expect(MockRestRequestMatchers.requestTo(APPS_BASE)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(getAppListJson(), MediaType.APPLICATION_JSON)); + stubGet(APPS_BASE, getAppListJson()); List result = client.getApplications(); @@ -186,13 +164,7 @@ void testGetApplicationsReturnsMappedList() { @Test void testGetApplicationGuidReturnsGuid() { - when(target.getGuid()) - .thenReturn(null); - - factory.server() - .expect(MockRestRequestMatchers.requestTo(APPS_BASE + "&names=my-app")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(getAppListJson(), MediaType.APPLICATION_JSON)); + stubApplicationLookup(); UUID result = client.getApplicationGuid("my-app"); @@ -202,10 +174,7 @@ void testGetApplicationGuidReturnsGuid() { @Test void testGetApplicationNameReturnsName() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(getAppJson(), MediaType.APPLICATION_JSON)); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID, getAppJson()); String result = client.getApplicationName(UUID.fromString(APP_GUID)); @@ -215,11 +184,8 @@ void testGetApplicationNameReturnsName() { @Test void testGetApplicationEnvironmentReturnsVariables() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID - + "/environment_variables")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"var\":{\"FOO\":\"bar\"}}", MediaType.APPLICATION_JSON)); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/environment_variables", "{\"var\":{\"FOO\":\"bar\"}}"); Map result = client.getApplicationEnvironment(UUID.fromString(APP_GUID)); @@ -256,21 +222,15 @@ void testCreateApplicationWithStagingUpdatesStagingThroughProcess() { .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) .andRespond(MockRestResponseCreators.withSuccess(getAppJson(), MediaType.APPLICATION_JSON)); - factory.server() - .expect(MockRestRequestMatchers.requestTo(APPS_BASE + "&space_guids=" + SPACE_GUID + "&names=my-app")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(getAppListJson(), MediaType.APPLICATION_JSON)); + stubGet(APPS_BASE + "&space_guids=" + SPACE_GUID + "&names=my-app", getAppListJson()); factory.server() .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID)) .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) .andRespond(MockRestResponseCreators.withSuccess()); - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID - + "/processes/web")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(getWebProcessJson(), MediaType.APPLICATION_JSON)); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/processes/web", getWebProcessJson()); factory.server() .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/processes/" + APP_GUID)) @@ -293,11 +253,7 @@ void testCreateApplicationWithStagingUpdatesStagingThroughProcess() { @Test void testGetEventsReturnsMappedList() { - factory.server() - .expect(MockRestRequestMatchers.requestTo( - MockControllerClientFactory.BASE_URL + "/v3/audit_events?per_page=5000")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(getAuditEventListJson(), MediaType.APPLICATION_JSON)); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/audit_events?per_page=5000", getAuditEventListJson()); List result = client.getEvents(); @@ -309,11 +265,8 @@ void testGetEventsReturnsMappedList() { @Test void testGetEventsByTargetReturnsMappedList() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/audit_events?per_page=5000" - + "&target_guids=" + APP_GUID)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(getAuditEventListJson(), MediaType.APPLICATION_JSON)); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/audit_events?per_page=5000" + + "&target_guids=" + APP_GUID, getAuditEventListJson()); List result = client.getEventsByTarget(UUID.fromString(APP_GUID)); @@ -333,11 +286,7 @@ void testGetServiceInstanceReturnsMappedInstance() { @Test void testGetServiceInstanceNameReturnsName() { - factory.server() - .expect(MockRestRequestMatchers.requestTo( - MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + SERVICE_INSTANCE_GUID)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(getServiceInstanceJson(), MediaType.APPLICATION_JSON)); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + SERVICE_INSTANCE_GUID, getServiceInstanceJson()); String result = client.getServiceInstanceName(UUID.fromString(SERVICE_INSTANCE_GUID)); @@ -369,10 +318,7 @@ void testGetRequiredServiceInstanceGuidReturnsGuid() { @Test void testGetTaskReturnsMappedTask() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/tasks/" + TASK_GUID)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(getTaskJson(), MediaType.APPLICATION_JSON)); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/tasks/" + TASK_GUID, getTaskJson()); CloudTask result = client.getTask(UUID.fromString(TASK_GUID)); @@ -383,19 +329,10 @@ void testGetTaskReturnsMappedTask() { @Test void testGetTasksReturnsMappedList() { - when(target.getGuid()) - .thenReturn(null); + stubApplicationLookup(); - factory.server() - .expect(MockRestRequestMatchers.requestTo(APPS_BASE + "&names=my-app")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(getAppListJson(), MediaType.APPLICATION_JSON)); - - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/tasks?per_page=5000&app_guids=" - + APP_GUID)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[" + getTaskJson() + "]}", MediaType.APPLICATION_JSON)); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/tasks?per_page=5000&app_guids=" + + APP_GUID, "{\"resources\":[" + getTaskJson() + "]}"); List result = client.getTasks("my-app"); @@ -405,10 +342,7 @@ void testGetTasksReturnsMappedList() { @Test void testGetBuildReturnsMappedBuild() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/builds/" + BUILD_GUID)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(getBuildJson(), MediaType.APPLICATION_JSON)); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/builds/" + BUILD_GUID, getBuildJson()); CloudBuild result = client.getBuild(UUID.fromString(BUILD_GUID)); @@ -418,11 +352,8 @@ void testGetBuildReturnsMappedBuild() { @Test void testGetBuildsForApplicationReturnsMappedList() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID - + "/builds?per_page=5000")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[" + getBuildJson() + "]}", MediaType.APPLICATION_JSON)); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/builds?per_page=5000", "{\"resources\":[" + getBuildJson() + "]}"); List result = client.getBuildsForApplication(UUID.fromString(APP_GUID)); @@ -432,11 +363,8 @@ void testGetBuildsForApplicationReturnsMappedList() { @Test void testGetBuildsForPackageReturnsMappedList() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/builds?package_guids=" + PACKAGE_GUID - + "&per_page=5000")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[" + getBuildJson() + "]}", MediaType.APPLICATION_JSON)); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/builds?package_guids=" + PACKAGE_GUID + + "&per_page=5000", "{\"resources\":[" + getBuildJson() + "]}"); List result = client.getBuildsForPackage(UUID.fromString(PACKAGE_GUID)); @@ -446,11 +374,8 @@ void testGetBuildsForPackageReturnsMappedList() { @Test void testGetApplicationRoutesReturnsMappedList() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID - + "/routes?per_page=5000")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[" + getRouteJson() + "]}", MediaType.APPLICATION_JSON)); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + + "/routes?per_page=5000", "{\"resources\":[" + getRouteJson() + "]}"); List result = client.getApplicationRoutes(UUID.fromString(APP_GUID)); @@ -471,8 +396,7 @@ void testGetBuildPropagatesCloudOperationExceptionOnNotFound() { @Test void testGetApplicationWithRequiredFlag() { - when(target.getGuid()).thenReturn(null); - stubGet(APPS_BASE + "&names=my-app", getAppListJson()); + stubApplicationLookup(); CloudApplication result = client.getApplication("my-app", false); @@ -482,8 +406,7 @@ void testGetApplicationWithRequiredFlag() { @Test void testGetApplicationEnvironmentByName() { - when(target.getGuid()).thenReturn(null); - stubGet(APPS_BASE + "&names=my-app", getAppListJson()); + stubApplicationLookup(); stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/environment_variables", "{\"var\":{\"FOO\":\"bar\"}}"); @@ -506,8 +429,7 @@ void testGetApplicationsByMetadataLabelSelector() { @Test void testStartApplication() { - when(target.getGuid()).thenReturn(null); - stubGet(APPS_BASE + "&names=my-app", getAppListJson()); + stubApplicationLookup(); stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/actions/start", HttpMethod.POST); client.startApplication("my-app"); @@ -517,8 +439,7 @@ void testStartApplication() { @Test void testStopApplication() { - when(target.getGuid()).thenReturn(null); - stubGet(APPS_BASE + "&names=my-app", getAppListJson()); + stubApplicationLookup(); stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/actions/stop", HttpMethod.POST); client.stopApplication("my-app"); @@ -528,8 +449,7 @@ void testStopApplication() { @Test void testRestartApplication() { - when(target.getGuid()).thenReturn(null); - stubGet(APPS_BASE + "&names=my-app", getAppListJson()); + stubApplicationLookup(); stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/actions/stop", HttpMethod.POST); stubGet(APPS_BASE + "&names=my-app", getAppListJson()); stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/actions/start", HttpMethod.POST); @@ -541,8 +461,7 @@ void testRestartApplication() { @Test void testRenameApplication() { - when(target.getGuid()).thenReturn(null); - stubGet(APPS_BASE + "&names=my-app", getAppListJson()); + stubApplicationLookup(); stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID, HttpMethod.PATCH); client.rename("my-app", "new-name"); @@ -552,8 +471,7 @@ void testRenameApplication() { @Test void testUpdateApplicationInstances() { - when(target.getGuid()).thenReturn(null); - stubGet(APPS_BASE + "&names=my-app", getAppListJson()); + stubApplicationLookup(); stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/processes/web/actions/scale", HttpMethod.POST); client.updateApplicationInstances("my-app", 3); @@ -563,8 +481,7 @@ void testUpdateApplicationInstances() { @Test void testUpdateApplicationMemory() { - when(target.getGuid()).thenReturn(null); - stubGet(APPS_BASE + "&names=my-app", getAppListJson()); + stubApplicationLookup(); stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/processes/web/actions/scale", HttpMethod.POST); client.updateApplicationMemory("my-app", 512); @@ -574,8 +491,7 @@ void testUpdateApplicationMemory() { @Test void testUpdateApplicationDiskQuota() { - when(target.getGuid()).thenReturn(null); - stubGet(APPS_BASE + "&names=my-app", getAppListJson()); + stubApplicationLookup(); stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/processes/web/actions/scale", HttpMethod.POST); client.updateApplicationDiskQuota("my-app", 2048); @@ -585,8 +501,7 @@ void testUpdateApplicationDiskQuota() { @Test void testUpdateApplicationEnv() { - when(target.getGuid()).thenReturn(null); - stubGet(APPS_BASE + "&names=my-app", getAppListJson()); + stubApplicationLookup(); stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/environment_variables", HttpMethod.PATCH); client.updateApplicationEnv("my-app", Map.of("FOO", "bar")); @@ -616,8 +531,7 @@ void testUpdateApplicationMetadata() { @Test void testDeleteApplication() { - when(target.getGuid()).thenReturn(null); - stubGet(APPS_BASE + "&names=my-app", getAppListJson()); + stubApplicationLookup(); stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID, HttpMethod.DELETE); client.deleteApplication("my-app"); @@ -679,8 +593,7 @@ void testGetCurrentDropletForApplication() { @Test void testUpdateApplicationStaging() { - when(target.getGuid()).thenReturn(null); - stubGet(APPS_BASE + "&names=my-app", getAppListJson()); + stubApplicationLookup(); stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID, HttpMethod.PATCH); stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/processes/web", getWebProcessJson()); stubMethod(MockControllerClientFactory.BASE_URL + "/v3/processes/" + APP_GUID, HttpMethod.PATCH); @@ -694,8 +607,7 @@ void testUpdateApplicationStaging() { @Test void testGetApplicationEvents() { - when(target.getGuid()).thenReturn(null); - stubGet(APPS_BASE + "&names=my-app", getAppListJson()); + stubApplicationLookup(); stubGet(MockControllerClientFactory.BASE_URL + "/v3/audit_events?per_page=5000&target_guids=" + APP_GUID, getAuditEventListJson()); @@ -817,7 +729,7 @@ void testDeleteRoute() { @Test void testUpdateApplicationRoutesAddsRoute() { when(target.getGuid()).thenReturn(UUID.fromString(SPACE_GUID)); - stubGet(APPS_BASE + "&names=my-app&space_guids=" + SPACE_GUID, "{\"resources\":[{\"guid\":\"" + APP_GUID + "\"}]}"); + stubGet(APPS_BASE + "&space_guids=" + SPACE_GUID + "&names=my-app", "{\"resources\":[{\"guid\":\"" + APP_GUID + "\"}]}"); stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/routes?per_page=5000", "{\"resources\":[]}"); stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + DOMAIN_NAME + "&per_page=5000", "{\"resources\":[{\"guid\":\"" + APP_GUID + "\",\"name\":\"" + DOMAIN_NAME + "\"}]}"); @@ -1304,8 +1216,7 @@ void testGetServiceOfferings() { @Test void testRunTask() { - when(target.getGuid()).thenReturn(null); - stubGet(APPS_BASE + "&names=my-app", getAppListJson()); + stubApplicationLookup(); factory.server() .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/tasks")) .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) @@ -1431,20 +1342,16 @@ private void stubTargetOrganization() { } private void stubGet(String uri, String responseJson) { - var response = responseJson == null ? MockRestResponseCreators.withSuccess() - : MockRestResponseCreators.withSuccess(responseJson, MediaType.APPLICATION_JSON); + factory.stubGet(uri, responseJson); + } - factory.server() - .expect(MockRestRequestMatchers.requestTo(uri)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(response); + private void stubApplicationLookup() { + when(target.getGuid()).thenReturn(null); + stubGet(APPS_BASE + "&names=my-app", getAppListJson()); } private void stubMethod(String uri, HttpMethod method) { - factory.server() - .expect(MockRestRequestMatchers.requestTo(uri)) - .andExpect(MockRestRequestMatchers.method(method)) - .andRespond(MockRestResponseCreators.withSuccess()); + factory.stubMethod(uri, method); } private static String getStackListJson() { diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3ClientTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3ClientTest.java index 0be1b7f6fd..18402e7021 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3ClientTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3ClientTest.java @@ -12,7 +12,6 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.test.web.client.match.MockRestRequestMatchers; import org.springframework.test.web.client.response.MockRestResponseCreators; @@ -33,10 +32,7 @@ void setUp() { @Test void testGetReturnsMappedBody() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(getJobJson("COMPLETE"), MediaType.APPLICATION_JSON)); + stubJobGet(getJobJson("COMPLETE")); V3Job job = client.get("/v3/jobs/" + JOB_GUID, V3Job.class); @@ -47,20 +43,14 @@ void testGetReturnsMappedBody() { @Test void testGetThrowsOnNotFound() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withStatus(HttpStatus.NOT_FOUND)); + stubJobGetStatus(HttpStatus.NOT_FOUND); Assertions.assertThrows(CloudOperationException.class, () -> client.get("/v3/jobs/" + JOB_GUID, V3Job.class)); } @Test void testGetOptionalReturnsPresentOn200() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(getJobJson("COMPLETE"), MediaType.APPLICATION_JSON)); + stubJobGet(getJobJson("COMPLETE")); Optional job = client.getOptional("/v3/jobs/" + JOB_GUID, V3Job.class); @@ -72,10 +62,7 @@ void testGetOptionalReturnsPresentOn200() { @Test void testGetOptionalReturnsEmptyOn404() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withStatus(HttpStatus.NOT_FOUND)); + stubJobGetStatus(HttpStatus.NOT_FOUND); Optional job = client.getOptional("/v3/jobs/" + JOB_GUID, V3Job.class); @@ -85,20 +72,14 @@ void testGetOptionalReturnsEmptyOn404() { @Test void testGetOptionalRethrowsNonNotFound() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withStatus(HttpStatus.INTERNAL_SERVER_ERROR)); + stubJobGetStatus(HttpStatus.INTERNAL_SERVER_ERROR); Assertions.assertThrows(CloudOperationException.class, () -> client.getOptional("/v3/jobs/" + JOB_GUID, V3Job.class)); } @Test void testWaitForAsyncJobReturnsWhenComplete() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(getJobJson("COMPLETE"), MediaType.APPLICATION_JSON)); + stubJobGet(getJobJson("COMPLETE")); V3Job job = client.waitForAsyncJob("/" + JOB_GUID, TIMEOUT); @@ -109,10 +90,7 @@ void testWaitForAsyncJobReturnsWhenComplete() { @Test void testWaitForAsyncJobThrowsWhenFailed() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(getFailedJobJson(), MediaType.APPLICATION_JSON)); + stubJobGet(getFailedJobJson()); CloudOperationException exception = Assertions.assertThrows(CloudOperationException.class, () -> client.waitForAsyncJob("/" + JOB_GUID, TIMEOUT)); @@ -127,10 +105,7 @@ void testFollowAsyncJobFollowsLocation() { headers.setLocation(URI.create(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID)); ResponseEntity accepted = new ResponseEntity<>(headers, HttpStatus.ACCEPTED); - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(getJobJson("COMPLETE"), MediaType.APPLICATION_JSON)); + stubJobGet(getJobJson("COMPLETE")); client.followAsyncJob(accepted, TIMEOUT); @@ -146,6 +121,17 @@ void testFollowAsyncJobReturnsImmediatelyWhenNoLocation() { factory.verify(); } + private void stubJobGet(String jobJson) { + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID, jobJson); + } + + private void stubJobGetStatus(HttpStatus status) { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(MockRestResponseCreators.withStatus(status)); + } + private static String getJobJson(String state) { return "{\"guid\":\"" + JOB_GUID + "\",\"state\":\"" + state + "\"}"; } diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudSpaceClientTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudSpaceClientTest.java index 0a164751f8..d7c5145396 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudSpaceClientTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudSpaceClientTest.java @@ -9,7 +9,6 @@ import org.junit.jupiter.api.Test; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; import org.springframework.test.web.client.match.MockRestRequestMatchers; import org.springframework.test.web.client.response.MockRestResponseCreators; @@ -31,15 +30,9 @@ void setUp() { @Test void testGetSpaceByGuidReturnsMappedSpace() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/spaces/" + SPACE_GUID)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(getSpaceJson(), MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/spaces/" + SPACE_GUID, getSpaceJson()); - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/organizations/" + ORG_GUID)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(getOrgJson(), MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/organizations/" + ORG_GUID, getOrgJson()); CloudSpace result = client.getSpace(UUID.fromString(SPACE_GUID)); @@ -65,10 +58,7 @@ void testGetSpaceByGuidThrowsWhenSpaceNotFound() { @Test void testGetSpaceByGuidThrowsWhenOrganizationNotFound() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/spaces/" + SPACE_GUID)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(getSpaceJson(), MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/spaces/" + SPACE_GUID, getSpaceJson()); factory.server() .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/organizations/" + ORG_GUID)) .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) @@ -82,11 +72,8 @@ void testGetSpaceByGuidThrowsWhenOrganizationNotFound() { void testGetSpaceByNameReturnsMappedSpace() { stubOrgLookupWithResult(); - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/spaces?organization_guids=" + ORG_GUID - + "&names=" + SPACE_NAME)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[" + getSpaceJson() + "]}", MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/spaces?organization_guids=" + ORG_GUID + + "&names=" + SPACE_NAME, "{\"resources\":[" + getSpaceJson() + "]}"); CloudSpace result = client.getSpace(ORG_NAME, SPACE_NAME); @@ -98,10 +85,7 @@ void testGetSpaceByNameReturnsMappedSpace() { @Test void testGetSpaceByNameThrowsWhenOrganizationNotFound() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/organizations?names=" + ORG_NAME)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/organizations?names=" + ORG_NAME, "{\"resources\":[]}"); Assertions.assertThrows(CloudOperationException.class, () -> client.getSpace(ORG_NAME, SPACE_NAME)); } @@ -110,20 +94,15 @@ void testGetSpaceByNameThrowsWhenOrganizationNotFound() { void testGetSpaceByNameThrowsWhenSpaceNotFound() { stubOrgLookupWithResult(); - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/spaces?organization_guids=" + ORG_GUID - + "&names=" + SPACE_NAME)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/spaces?organization_guids=" + ORG_GUID + + "&names=" + SPACE_NAME, "{\"resources\":[]}"); Assertions.assertThrows(CloudOperationException.class, () -> client.getSpace(ORG_NAME, SPACE_NAME)); } private void stubOrgLookupWithResult() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/organizations?names=" + ORG_NAME)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[" + getOrgJson() + "]}", MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/organizations?names=" + ORG_NAME, + "{\"resources\":[" + getOrgJson() + "]}"); } private static String getSpaceJson() { diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3OperationsTest.java index 424ab98053..8ded132d8c 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3OperationsTest.java @@ -146,11 +146,8 @@ void testDeleteDomainDeletesFoundDomain() { .andRespond(MockRestResponseCreators.withStatus(HttpStatus.ACCEPTED) .location(URI.create(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID))); - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"guid\":\"" + JOB_GUID + "\",\"state\":\"COMPLETE\"}", - MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID, + "{\"guid\":\"" + JOB_GUID + "\",\"state\":\"COMPLETE\"}"); serverOperations.deleteDomain(DOMAIN_NAME); @@ -176,12 +173,8 @@ void testGetDefaultDomainReturnsMappedDomain() { when(targetSpace.getOrganization()) .thenReturn(getOrganizationWithGuid(ORG_GUID)); - factory.server() - .expect(MockRestRequestMatchers.requestTo( - MockControllerClientFactory.BASE_URL + "/v3/organizations/" + ORG_GUID + "/domains/default")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(getDomainResourceJson(DOMAIN_GUID, DOMAIN_NAME), - MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/organizations/" + ORG_GUID + "/domains/default", + getDomainResourceJson(DOMAIN_GUID, DOMAIN_NAME)); CloudDomain result = serverOperations.getDefaultDomain(); @@ -194,12 +187,8 @@ void testGetDomainsForOrganizationReturnsMappedDomains() { when(targetSpace.getOrganization()) .thenReturn(getOrganizationWithGuid(ORG_GUID)); - factory.server() - .expect(MockRestRequestMatchers.requestTo( - MockControllerClientFactory.BASE_URL + "/v3/organizations/" + ORG_GUID + "/domains?per_page=5000")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess( - "{\"resources\":[" + getDomainResourceJson(DOMAIN_GUID, DOMAIN_NAME) + "]}", MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/organizations/" + ORG_GUID + "/domains?per_page=5000", + "{\"resources\":[" + getDomainResourceJson(DOMAIN_GUID, DOMAIN_NAME) + "]}"); List result = serverOperations.getDomainsForOrganization(); @@ -214,11 +203,8 @@ void testGetDomainsForOrganizationReturnsEmptyWhenNoDomains() { when(targetSpace.getOrganization()) .thenReturn(getOrganizationWithGuid(ORG_GUID)); - factory.server() - .expect(MockRestRequestMatchers.requestTo( - MockControllerClientFactory.BASE_URL + "/v3/organizations/" + ORG_GUID + "/domains?per_page=5000")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/organizations/" + ORG_GUID + "/domains?per_page=5000", + "{\"resources\":[]}"); Assertions.assertTrue(serverOperations.getDomainsForOrganization() .isEmpty()); @@ -233,20 +219,13 @@ void testGetDomainsForOrganizationThrowsWhenSpaceNotProvided() { } private void stubEmptyDomainLookup(String name) { - factory.server() - .expect(MockRestRequestMatchers.requestTo( - MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + name + "&per_page=5000")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + name + "&per_page=5000", + "{\"resources\":[]}"); } private void stubDomainLookupWithResult(String name, String domainGuid) { - factory.server() - .expect(MockRestRequestMatchers.requestTo( - MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + name + "&per_page=5000")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess( - "{\"resources\":[" + getDomainResourceJson(domainGuid, name) + "]}", MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + name + "&per_page=5000", + "{\"resources\":[" + getDomainResourceJson(domainGuid, name) + "]}"); } private static CloudOrganization getOrganizationWithGuid(String guid) { diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/EventsV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/EventsV3OperationsTest.java index a520836beb..1d8c98c075 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/EventsV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/EventsV3OperationsTest.java @@ -6,8 +6,8 @@ import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEvent; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; -import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Application; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3AuditEvent; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3GuidReference; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -79,7 +79,7 @@ void testGetApplicationEventsThrowsWhenApplicationNotFound() { .thenReturn(SPACE_GUID); when(client.list(ArgumentMatchers.contains("/v3/apps"), - ArgumentMatchers.>> any())) + ArgumentMatchers.>> any())) .thenReturn(List.of()); CloudOperationException thrown = Assertions.assertThrows(CloudOperationException.class, @@ -93,8 +93,8 @@ void testGetApplicationEventsQueryScopesAppsToSpaceWhenTargetPresent() { when(targetSpace.getGuid()) .thenReturn(SPACE_GUID); when(client.list(ArgumentMatchers.contains("/v3/apps"), - ArgumentMatchers.>> any())) - .thenReturn(List.of(new V3Application(GUID_STRING, "my-app", "STARTED", null, null, null, null, null))); + ArgumentMatchers.>> any())) + .thenReturn(List.of(new V3GuidReference(GUID_STRING))); when(client.list(ArgumentMatchers.contains("/v3/audit_events"), ArgumentMatchers.>> any())) .thenReturn(List.of()); @@ -103,7 +103,7 @@ ArgumentMatchers.>> any( ArgumentCaptor uriCaptor = ArgumentCaptor.forClass(String.class); verify(client, times(2)) - .list(uriCaptor.capture(), ArgumentMatchers.>> any()); + .list(uriCaptor.capture(), ArgumentMatchers.>> any()); String appsQuery = uriCaptor.getAllValues() .stream() .filter(uri -> uri.contains("/v3/apps")) diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/MockControllerClientFactory.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/MockControllerClientFactory.java index a75217ae8d..8f0a71354f 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/MockControllerClientFactory.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/MockControllerClientFactory.java @@ -1,10 +1,14 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest; import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; import org.springframework.http.converter.ByteArrayHttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.test.web.client.match.MockRestRequestMatchers; +import org.springframework.test.web.client.response.MockRestResponseCreators; import org.springframework.web.client.RestClient; final class MockControllerClientFactory { @@ -44,6 +48,21 @@ MockRestServiceServer server() { return server; } + void stubGet(String uri, String responseJson) { + var response = responseJson == null ? MockRestResponseCreators.withSuccess() + : MockRestResponseCreators.withSuccess(responseJson, MediaType.APPLICATION_JSON); + + server.expect(MockRestRequestMatchers.requestTo(uri)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andRespond(response); + } + + void stubMethod(String uri, HttpMethod method) { + server.expect(MockRestRequestMatchers.requestTo(uri)) + .andExpect(MockRestRequestMatchers.method(method)) + .andRespond(MockRestResponseCreators.withSuccess()); + } + void verify() { server.verify(); } diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3OperationsTest.java index 7d636d729c..1dff0adf1a 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3OperationsTest.java @@ -43,10 +43,7 @@ void setUp() throws Exception { @Test void testGetPackageReturnsMappedPackage() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/packages/" + PACKAGE_GUID)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(bitsPackageJson("READY", null), MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/packages/" + PACKAGE_GUID, bitsPackageJson("READY", null)); CloudPackage result = operations.getPackage(UUID.fromString(PACKAGE_GUID)); @@ -59,10 +56,7 @@ void testGetPackageReturnsMappedPackage() { @Test void testGetPackageThrowsWhenResponseIsNull() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/packages/" + PACKAGE_GUID)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("", MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/packages/" + PACKAGE_GUID, ""); UUID packageGuid = UUID.fromString(PACKAGE_GUID); Assertions.assertThrows(CloudOperationException.class, () -> operations.getPackage(packageGuid)); @@ -81,12 +75,8 @@ void testGetPackageThrowsOnNotFoundStatus() { @Test void testGetPackagesForApplicationReturnsMappedList() { - factory.server() - .expect(MockRestRequestMatchers.requestTo( - MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/packages?per_page=5000")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[" + bitsPackageJson("READY", null) + "]}", - MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/packages?per_page=5000", + "{\"resources\":[" + bitsPackageJson("READY", null) + "]}"); List result = operations.getPackagesForApplication(UUID.fromString(APP_GUID)); @@ -98,11 +88,8 @@ void testGetPackagesForApplicationReturnsMappedList() { @Test void testGetPackagesForApplicationReturnsEmptyList() { - factory.server() - .expect(MockRestRequestMatchers.requestTo( - MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/packages?per_page=5000")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/packages?per_page=5000", + "{\"resources\":[]}"); List result = operations.getPackagesForApplication(UUID.fromString(APP_GUID)); @@ -117,10 +104,7 @@ void testCreateDockerPackagePostsAndReturnsCreatedPackage() { .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) .andRespond(MockRestResponseCreators.withSuccess(dockerPackageJson(), MediaType.APPLICATION_JSON)); - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/packages/" + PACKAGE_GUID)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(dockerPackageJson(), MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/packages/" + PACKAGE_GUID, dockerPackageJson()); DockerInfo dockerInfo = ImmutableDockerInfo.builder() .image("my/image:latest") @@ -141,10 +125,7 @@ void testCreateDockerPackageWithCredentials() { .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) .andRespond(MockRestResponseCreators.withSuccess(dockerPackageJson(), MediaType.APPLICATION_JSON)); - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/packages/" + PACKAGE_GUID)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(dockerPackageJson(), MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/packages/" + PACKAGE_GUID, dockerPackageJson()); DockerInfo dockerInfo = ImmutableDockerInfo.builder() .image("my/image:latest") @@ -162,10 +143,7 @@ void testCreateDockerPackageWithCredentials() { @Test void testGetUploadStatusForBitsPackageIncludesErrorDetails() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/packages/" + PACKAGE_GUID)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(bitsPackageJson("FAILED", "boom"), MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/packages/" + PACKAGE_GUID, bitsPackageJson("FAILED", "boom")); Upload result = operations.getUploadStatus(UUID.fromString(PACKAGE_GUID)); @@ -177,10 +155,7 @@ void testGetUploadStatusForBitsPackageIncludesErrorDetails() { @Test void testGetUploadStatusForDockerPackageHasNoErrorDetails() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/packages/" + PACKAGE_GUID)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(dockerPackageJson(), MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/packages/" + PACKAGE_GUID, dockerPackageJson()); Upload result = operations.getUploadStatus(UUID.fromString(PACKAGE_GUID)); diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3OperationsTest.java index ab7cc6969c..798a9c6857 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3OperationsTest.java @@ -52,11 +52,7 @@ void setUp() throws Exception { @Test void testGetApplicationProcessReturnsMappedProcess() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID - + "/processes/web")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(getProcessJson(), MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/processes/web", getProcessJson()); CloudProcess result = operations.getApplicationProcess(UUID.fromString(APP_GUID)); @@ -85,11 +81,7 @@ void testGetApplicationProcessReturnsNullWhenNoBody() { @Test void testGetApplicationInstancesByGuidReturnsMappedInstances() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID - + "/processes/web/stats")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(getStatsJson(), MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/processes/web/stats", getStatsJson()); InstancesInfo result = operations.getApplicationInstances(UUID.fromString(APP_GUID)); @@ -106,11 +98,7 @@ void testGetApplicationInstancesByGuidReturnsMappedInstances() { @Test void testGetApplicationInstancesForStartedApplicationQueriesStats() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID - + "/processes/web/stats")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(getStatsJson(), MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/processes/web/stats", getStatsJson()); CloudApplication application = ImmutableCloudApplication.builder() .metadata(ImmutableCloudMetadata.builder() @@ -146,10 +134,7 @@ void testGetApplicationInstancesForStoppedApplicationReturnsEmptyWithoutHttp() { @Test void testGetApplicationSshEnabledReturnsTrue() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/ssh_enabled")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"enabled\":true,\"reason\":\"\"}", MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/ssh_enabled", "{\"enabled\":true,\"reason\":\"\"}"); Assertions.assertTrue(operations.getApplicationSshEnabled(UUID.fromString(APP_GUID))); factory.verify(); @@ -157,10 +142,7 @@ void testGetApplicationSshEnabledReturnsTrue() { @Test void testGetApplicationSshEnabledReturnsFalseWhenEnabledIsNull() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/ssh_enabled")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"reason\":\"disabled globally\"}", MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/ssh_enabled", "{\"reason\":\"disabled globally\"}"); Assertions.assertFalse(operations.getApplicationSshEnabled(UUID.fromString(APP_GUID))); factory.verify(); @@ -168,13 +150,8 @@ void testGetApplicationSshEnabledReturnsFalseWhenEnabledIsNull() { @Test void testGetApplicationFeaturesReturnsMap() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID - + "/features?per_page=5000")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess( - "{\"resources\":[{\"name\":\"ssh\",\"enabled\":true},{\"name\":\"revisions\",\"enabled\":false}]}", - MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/features?per_page=5000", + "{\"resources\":[{\"name\":\"ssh\",\"enabled\":true},{\"name\":\"revisions\",\"enabled\":false}]}"); Map result = operations.getApplicationFeatures(UUID.fromString(APP_GUID)); @@ -186,11 +163,7 @@ void testGetApplicationFeaturesReturnsMap() { @Test void testGetApplicationFeaturesReturnsEmptyMap() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID - + "/features?per_page=5000")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/features?per_page=5000", "{\"resources\":[]}"); Map result = operations.getApplicationFeatures(UUID.fromString(APP_GUID)); @@ -200,12 +173,8 @@ void testGetApplicationFeaturesReturnsEmptyMap() { @Test void testGetCurrentDropletForApplicationReturnsDropletInfo() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID - + "/droplets/current")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(getDropletJson(MockControllerClientFactory.BASE_URL + "/v3/packages/" - + PACKAGE_GUID), MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/droplets/current", + getDropletJson(MockControllerClientFactory.BASE_URL + "/v3/packages/" + PACKAGE_GUID)); DropletInfo result = operations.getCurrentDropletForApplication(UUID.fromString(APP_GUID)); @@ -216,12 +185,8 @@ void testGetCurrentDropletForApplicationReturnsDropletInfo() { @Test void testGetCurrentDropletForApplicationHandlesTrailingSlashInPackageLink() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID - + "/droplets/current")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(getDropletJson(MockControllerClientFactory.BASE_URL + "/v3/packages/" - + PACKAGE_GUID + "/"), MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/droplets/current", + getDropletJson(MockControllerClientFactory.BASE_URL + "/v3/packages/" + PACKAGE_GUID + "/")); DropletInfo result = operations.getCurrentDropletForApplication(UUID.fromString(APP_GUID)); @@ -247,22 +212,14 @@ void testUpdateApplicationStagingUpdatesAppAndProcess() { when(target.getGuid()) .thenReturn(null); - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000&names=my-app")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[{\"guid\":\"" + APP_GUID + "\"}]}", - MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000&names=my-app", "{\"resources\":[{\"guid\":\"" + APP_GUID + "\"}]}"); factory.server() .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID)) .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) .andRespond(MockRestResponseCreators.withSuccess("{}", MediaType.APPLICATION_JSON)); - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID - + "/processes/web")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(getProcessJson(), MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/processes/web", getProcessJson()); factory.server() .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/processes/" + PROCESS_GUID)) @@ -290,11 +247,7 @@ void testUpdateApplicationStagingUpdatesAppFeatures() { when(target.getGuid()) .thenReturn(null); - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000&names=my-app")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[{\"guid\":\"" + APP_GUID + "\"}]}", - MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000&names=my-app", "{\"resources\":[{\"guid\":\"" + APP_GUID + "\"}]}"); factory.server() .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID)) .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) @@ -306,11 +259,7 @@ void testUpdateApplicationStagingUpdatesAppFeatures() { .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) .andRespond(MockRestResponseCreators.withSuccess("{}", MediaType.APPLICATION_JSON)); - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID - + "/processes/web")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(getProcessJson(), MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/processes/web", getProcessJson()); factory.server() .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/processes/" + PROCESS_GUID)) @@ -333,23 +282,15 @@ void testUpdateApplicationStagingIncludesSpaceGuidInLookupQuery() { when(target.getGuid()) .thenReturn(spaceGuid); - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000&space_guids=" - + spaceGuid + "&names=my-app")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[{\"guid\":\"" + APP_GUID + "\"}]}", - MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000&space_guids=" + + spaceGuid + "&names=my-app", "{\"resources\":[{\"guid\":\"" + APP_GUID + "\"}]}"); factory.server() .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID)) .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) .andRespond(MockRestResponseCreators.withSuccess("{}", MediaType.APPLICATION_JSON)); - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID - + "/processes/web")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(getProcessJson(), MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/processes/web", getProcessJson()); factory.server() .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/processes/" + PROCESS_GUID)) @@ -370,11 +311,8 @@ void testUpdateApplicationStagingThrowsWhenApplicationNotFound() { when(target.getGuid()) .thenReturn(null); - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL - + "/v3/apps?per_page=5000&names=missing-app")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + + "/v3/apps?per_page=5000&names=missing-app", "{\"resources\":[]}"); Staging staging = ImmutableStaging.builder() .command("cmd") @@ -388,11 +326,7 @@ void testUpdateApplicationStagingWithCnbLifecycleWithoutBuildpacksThrows() { when(target.getGuid()) .thenReturn(null); - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000&names=my-app")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[{\"guid\":\"" + APP_GUID + "\"}]}", - MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000&names=my-app", "{\"resources\":[{\"guid\":\"" + APP_GUID + "\"}]}"); Staging staging = ImmutableStaging.builder() .command("cmd") diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RolesV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RolesV3OperationsTest.java index 0cda8c936a..d1d2ccc194 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RolesV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RolesV3OperationsTest.java @@ -13,7 +13,6 @@ import org.mockito.MockitoAnnotations; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; import org.springframework.test.web.client.match.MockRestRequestMatchers; import org.springframework.test.web.client.response.MockRestResponseCreators; @@ -42,10 +41,7 @@ void testGetUserRolesReturnsMappedRoles() { String json = "{\"resources\":[{\"guid\":\"r1\",\"type\":\"space_developer\"}," + "{\"guid\":\"r2\",\"type\":\"space_manager\"}]}"; - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + ROLES_URI)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(json, MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + ROLES_URI, json); Set result = operations.getUserRolesBySpaceAndUser(SPACE_GUID, USER_GUID); @@ -60,10 +56,7 @@ void testGetUserRolesDeduplicatesRepeatedRoleTypes() { String json = "{\"resources\":[{\"guid\":\"r1\",\"type\":\"space_developer\"}," + "{\"guid\":\"r2\",\"type\":\"space_developer\"}]}"; - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + ROLES_URI)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(json, MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + ROLES_URI, json); Set result = operations.getUserRolesBySpaceAndUser(SPACE_GUID, USER_GUID); @@ -74,10 +67,7 @@ void testGetUserRolesDeduplicatesRepeatedRoleTypes() { @Test void testGetUserRolesReturnsEmptySetWhenNoRoles() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + ROLES_URI)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + ROLES_URI, "{\"resources\":[]}"); Set result = operations.getUserRolesBySpaceAndUser(SPACE_GUID, USER_GUID); @@ -88,10 +78,7 @@ void testGetUserRolesReturnsEmptySetWhenNoRoles() { @Test void testGetUserRolesThrowsOnUnknownRoleType() { String json = "{\"resources\":[{\"guid\":\"r1\",\"type\":\"galactic_overlord\"}]}"; - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + ROLES_URI)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(json, MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + ROLES_URI, json); Assertions.assertThrows(IllegalArgumentException.class, () -> operations.getUserRolesBySpaceAndUser(SPACE_GUID, USER_GUID)); diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3OperationsTest.java index f3ba3c1f47..e3279c3a1c 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3OperationsTest.java @@ -131,11 +131,8 @@ void testGetRoutesReturnsMappedRoutes() { .thenReturn(UUID.fromString(SPACE_GUID)); stubDomainLookup(DOMAIN_NAME, DOMAIN_GUID); - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/routes?per_page=5000&domain_guids=" - + DOMAIN_GUID + "&space_guids=" + SPACE_GUID)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[" + getRouteJson() + "]}", MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/routes?per_page=5000&domain_guids=" + + DOMAIN_GUID + "&space_guids=" + SPACE_GUID, "{\"resources\":[" + getRouteJson() + "]}"); List routes = operations.getRoutes(DOMAIN_NAME); @@ -164,11 +161,8 @@ void testGetRoutesThrowsWhenSpaceNotProvided() { @Test void testGetApplicationRoutesReturnsMappedRoutes() { - factory.server() - .expect(MockRestRequestMatchers.requestTo( - MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/routes?per_page=5000")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[" + getRouteJson() + "]}", MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/routes?per_page=5000", + "{\"resources\":[" + getRouteJson() + "]}"); List routes = operations.getApplicationRoutes(UUID.fromString(APP_GUID)); @@ -180,11 +174,8 @@ void testGetApplicationRoutesReturnsMappedRoutes() { @Test void testGetApplicationRoutesReturnsEmptyList() { - factory.server() - .expect(MockRestRequestMatchers.requestTo( - MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/routes?per_page=5000")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/routes?per_page=5000", + "{\"resources\":[]}"); List routes = operations.getApplicationRoutes(UUID.fromString(APP_GUID)); @@ -199,34 +190,21 @@ void testUpdateApplicationRoutesAddsNewRoute() { stubAppLookup("my-app"); - factory.server() - .expect(MockRestRequestMatchers.requestTo( - MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/routes?per_page=5000")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/routes?per_page=5000", + "{\"resources\":[]}"); - factory.server() - .expect(MockRestRequestMatchers.requestTo( - MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + DOMAIN_NAME + "&per_page=5000")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(getDomainJson(DOMAIN_GUID), MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + DOMAIN_NAME + "&per_page=5000", + getDomainJson(DOMAIN_GUID)); - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/routes?per_page=5000&domain_guids=" - + DOMAIN_GUID + "&space_guids=" + SPACE_GUID + "&hosts=myhost&paths=/mypath")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/routes?per_page=5000&domain_guids=" + + DOMAIN_GUID + "&space_guids=" + SPACE_GUID + "&hosts=myhost&paths=/mypath", "{\"resources\":[]}"); factory.server() .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/routes")) .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) .andRespond(MockRestResponseCreators.withSuccess(getRouteJson(), MediaType.APPLICATION_JSON)); - factory.server() - .expect(MockRestRequestMatchers.requestTo( - MockControllerClientFactory.BASE_URL + "/v3/routes/" + ROUTE_GUID + "/destinations")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) - .andRespond(MockRestResponseCreators.withSuccess()); + factory.stubMethod(MockControllerClientFactory.BASE_URL + "/v3/routes/" + ROUTE_GUID + "/destinations", HttpMethod.POST); CloudRoute newRoute = ImmutableCloudRoute.builder() .domain(ImmutableCloudDomain.builder() @@ -249,18 +227,11 @@ void testUpdateApplicationRoutesRemovesOutdatedRoute() { stubAppLookup("my-app"); - factory.server() - .expect(MockRestRequestMatchers.requestTo( - MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/routes?per_page=5000")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[" + getRouteWithDestinationJson() + "]}", - MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/routes?per_page=5000", + "{\"resources\":[" + getRouteWithDestinationJson() + "]}"); - factory.server() - .expect(MockRestRequestMatchers.requestTo( - MockControllerClientFactory.BASE_URL + "/v3/routes/" + ROUTE_GUID + "/destinations/" + DESTINATION_GUID)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.DELETE)) - .andRespond(MockRestResponseCreators.withSuccess()); + factory.stubMethod(MockControllerClientFactory.BASE_URL + "/v3/routes/" + ROUTE_GUID + "/destinations/" + DESTINATION_GUID, + HttpMethod.DELETE); operations.updateApplicationRoutes("my-app", Set.of()); @@ -272,46 +243,30 @@ void testUpdateApplicationRoutesThrowsWhenApplicationNotFound() { when(target.getGuid()) .thenReturn(UUID.fromString(SPACE_GUID)); - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000&names=missing-app" - + "&space_guids=" + SPACE_GUID)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000" + + "&space_guids=" + SPACE_GUID + "&names=missing-app", "{\"resources\":[]}"); Assertions.assertThrows(CloudOperationException.class, () -> operations.updateApplicationRoutes("missing-app", Set.of())); } private void stubDomainLookup(String name, String domainGuid) { - factory.server() - .expect(MockRestRequestMatchers.requestTo( - MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + name + "&per_page=5000")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(getDomainJson(domainGuid), MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + name + "&per_page=5000", + getDomainJson(domainGuid)); } private void stubEmptyDomainLookup(String name) { - factory.server() - .expect(MockRestRequestMatchers.requestTo( - MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + name + "&per_page=5000")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + name + "&per_page=5000", + "{\"resources\":[]}"); } private void stubRouteLookupWithResult() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/routes?per_page=5000&domain_guids=" - + DOMAIN_GUID + "&space_guids=" + SPACE_GUID + "&hosts=myhost&paths=/mypath")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[" + getRouteJson() + "]}", MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/routes?per_page=5000&domain_guids=" + + DOMAIN_GUID + "&space_guids=" + SPACE_GUID + "&hosts=myhost&paths=/mypath", "{\"resources\":[" + getRouteJson() + "]}"); } private void stubAppLookup(String appName) { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000&names=" + appName - + "&space_guids=" + SPACE_GUID)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[{\"guid\":\"" + APP_GUID + "\"}]}", - MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000" + + "&space_guids=" + SPACE_GUID + "&names=" + appName, "{\"resources\":[{\"guid\":\"" + APP_GUID + "\"}]}"); } private void stubAsyncDelete(String uri) { @@ -320,11 +275,8 @@ private void stubAsyncDelete(String uri) { .andExpect(MockRestRequestMatchers.method(HttpMethod.DELETE)) .andRespond(MockRestResponseCreators.withStatus(HttpStatus.ACCEPTED) .location(URI.create(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID))); - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"guid\":\"" + JOB_GUID + "\",\"state\":\"COMPLETE\"}", - MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID, + "{\"guid\":\"" + JOB_GUID + "\",\"state\":\"COMPLETE\"}"); } private static String getRouteJson() { diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3OperationsTest.java index af8707d934..e69b24ab1d 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3OperationsTest.java @@ -227,11 +227,8 @@ void testDeleteServiceBindingReturnsJobGuid() { @Test void testGetServiceBindingReturnsMappedBinding() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings?guids=" - + BINDING_GUID + "&per_page=5000")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(getBindingListJson(), MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings?guids=" + + BINDING_GUID + "&per_page=5000", getBindingListJson()); CloudServiceBinding result = operations.getServiceBinding(UUID.fromString(BINDING_GUID)); @@ -242,11 +239,8 @@ void testGetServiceBindingReturnsMappedBinding() { @Test void testGetServiceBindingReturnsNullWhenNoBindingFound() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings?guids=" - + BINDING_GUID + "&per_page=5000")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings?guids=" + + BINDING_GUID + "&per_page=5000", "{\"resources\":[]}"); CloudServiceBinding result = operations.getServiceBinding(UUID.fromString(BINDING_GUID)); @@ -256,12 +250,9 @@ void testGetServiceBindingReturnsNullWhenNoBindingFound() { @Test void testGetServiceAppBindingsReturnsMappedList() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL - + "/v3/service_credential_bindings?service_instance_guids=" - + SERVICE_INSTANCE_GUID + "&type=app&per_page=5000")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(getBindingListJson(), MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + + "/v3/service_credential_bindings?service_instance_guids=" + + SERVICE_INSTANCE_GUID + "&type=app&per_page=5000", getBindingListJson()); List result = operations.getServiceAppBindings(UUID.fromString(SERVICE_INSTANCE_GUID)); @@ -273,11 +264,8 @@ void testGetServiceAppBindingsReturnsMappedList() { @Test void testGetAppBindingsReturnsEmptyList() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL - + "/v3/service_credential_bindings?app_guids=" + APP_GUID + "&per_page=5000")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + + "/v3/service_credential_bindings?app_guids=" + APP_GUID + "&per_page=5000", "{\"resources\":[]}"); List result = operations.getAppBindings(UUID.fromString(APP_GUID)); @@ -287,13 +275,10 @@ void testGetAppBindingsReturnsEmptyList() { @Test void testGetServiceBindingsForApplicationReturnsMappedList() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL - + "/v3/service_credential_bindings?app_guids=" + APP_GUID - + "&service_instance_guids=" + SERVICE_INSTANCE_GUID - + "&per_page=5000")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(getBindingListJson(), MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + + "/v3/service_credential_bindings?app_guids=" + APP_GUID + + "&service_instance_guids=" + SERVICE_INSTANCE_GUID + + "&per_page=5000", getBindingListJson()); List result = operations.getServiceBindingsForApplication(UUID.fromString(APP_GUID), UUID.fromString(SERVICE_INSTANCE_GUID)); @@ -304,11 +289,8 @@ void testGetServiceBindingsForApplicationReturnsMappedList() { @Test void testGetServiceBindingParametersReturnsMap() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings/" - + BINDING_GUID + "/parameters")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"foo\":\"bar\"}", MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings/" + + BINDING_GUID + "/parameters", "{\"foo\":\"bar\"}"); Map result = operations.getServiceBindingParameters(UUID.fromString(BINDING_GUID)); diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBrokersV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBrokersV3OperationsTest.java index 02204f9125..0f15e77353 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBrokersV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBrokersV3OperationsTest.java @@ -51,11 +51,7 @@ void setUp() throws Exception { @Test void testGetServiceBrokersReturnsMappedList() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(BROKERS_URI + "?per_page=5000")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[" + getBrokerJson("broker-a") + "]}", - MediaType.APPLICATION_JSON)); + factory.stubGet(BROKERS_URI + "?per_page=5000", "{\"resources\":[" + getBrokerJson("broker-a") + "]}"); List result = operations.getServiceBrokers(); @@ -69,10 +65,7 @@ void testGetServiceBrokersReturnsMappedList() { @Test void testGetServiceBrokersReturnsEmptyList() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(BROKERS_URI + "?per_page=5000")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + factory.stubGet(BROKERS_URI + "?per_page=5000", "{\"resources\":[]}"); List result = operations.getServiceBrokers(); @@ -226,20 +219,13 @@ void testUpdateServicePlanVisibilityForBrokerUpdatesEachPlan() { stubBrokerLookup("broker-a", "{\"resources\":[" + getBrokerJson("broker-a") + "]}"); - factory.server() - .expect( - MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_offerings?service_broker_guids=" - + BROKER_GUID + "&space_guids=" + SPACE_GUID + "&per_page=5000")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[{\"guid\":\"" + OFFERING_GUID + "\"}]}", - MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_offerings?service_broker_guids=" + + BROKER_GUID + "&space_guids=" + SPACE_GUID + "&per_page=5000", + "{\"resources\":[{\"guid\":\"" + OFFERING_GUID + "\"}]}"); - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_plans?service_offering_guids=" - + OFFERING_GUID + "&per_page=5000")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[{\"guid\":\"" + PLAN_GUID + "\"}]}", - MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_plans?service_offering_guids=" + + OFFERING_GUID + "&per_page=5000", + "{\"resources\":[{\"guid\":\"" + PLAN_GUID + "\"}]}"); factory.server() .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_plans/" + PLAN_GUID @@ -262,12 +248,8 @@ void testUpdateServicePlanVisibilityForBrokerWithNoOfferingsUpdatesNothing() { stubBrokerLookup("broker-a", "{\"resources\":[" + getBrokerJson("broker-a") + "]}"); - factory.server() - .expect( - MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_offerings?service_broker_guids=" - + BROKER_GUID + "&space_guids=" + SPACE_GUID + "&per_page=5000")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_offerings?service_broker_guids=" + + BROKER_GUID + "&space_guids=" + SPACE_GUID + "&per_page=5000", "{\"resources\":[]}"); operations.updateServicePlanVisibilityForBroker("broker-a", ServicePlanVisibility.ADMIN); @@ -284,10 +266,7 @@ void testUpdateServicePlanVisibilityForBrokerThrowsWhenBrokerMissing() { } private void stubBrokerLookup(String name, String responseJson) { - factory.server() - .expect(MockRestRequestMatchers.requestTo(BROKERS_URI + "?names=" + name + "&per_page=5000")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(responseJson, MediaType.APPLICATION_JSON)); + factory.stubGet(BROKERS_URI + "?names=" + name + "&per_page=5000", responseJson); } private static String getBrokerJson(String name) { diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3OperationsTest.java index 60451abbe4..e7fe1f6af1 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3OperationsTest.java @@ -99,10 +99,7 @@ void testGetServiceInstanceWithoutAuxiliaryContentMapsWhenFound() { @Test void testGetServiceInstanceNameReturnsNameFromGet() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + SPACE_GUID)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(getManagedInstanceJson("my-service"), MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + SPACE_GUID, getManagedInstanceJson("my-service")); Assertions.assertEquals("my-service", operations.getServiceInstanceName(SPACE_GUID)); factory.verify(); @@ -110,10 +107,7 @@ void testGetServiceInstanceNameReturnsNameFromGet() { @Test void testGetServiceInstanceNameReturnsNullWhenGetReturnsNull() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + SPACE_GUID)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess()); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + SPACE_GUID, null); Assertions.assertNull(operations.getServiceInstanceName(SPACE_GUID)); factory.verify(); @@ -124,10 +118,7 @@ void testCreateServiceInstancePostsManagedInstance() { stubOfferingLookup("my-offering", null); stubPlanLookup("my-plan"); - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_instances")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) - .andRespond(MockRestResponseCreators.withSuccess()); + factory.stubMethod(MockControllerClientFactory.BASE_URL + "/v3/service_instances", HttpMethod.POST); CloudServiceInstance serviceInstance = ImmutableCloudServiceInstance.builder() .name("my-service") @@ -171,10 +162,7 @@ void testCreateServiceInstanceThrowsWhenSpaceNotProvided() { @Test void testCreateUserProvidedServiceInstancePosts() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_instances")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) - .andRespond(MockRestResponseCreators.withSuccess()); + factory.stubMethod(MockControllerClientFactory.BASE_URL + "/v3/service_instances", HttpMethod.POST); CloudServiceInstance serviceInstance = ImmutableCloudServiceInstance.builder() .name("my-ups") @@ -202,10 +190,7 @@ void testCreateUserProvidedServiceInstanceThrowsWhenSpaceNotProvided() { void testDeleteServiceInstanceByNameDeletesFoundInstance() { stubServiceInstanceLookup("my-service", getManagedInstanceJson("my-service")); - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + GUID_STRING)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.DELETE)) - .andRespond(MockRestResponseCreators.withSuccess()); + factory.stubMethod(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + GUID_STRING, HttpMethod.DELETE); operations.deleteServiceInstance("my-service"); @@ -221,10 +206,7 @@ void testDeleteServiceInstanceByNameThrowsWhenNotFound() { @Test void testDeleteServiceInstanceByInstanceDeletes() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + GUID_STRING)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.DELETE)) - .andRespond(MockRestResponseCreators.withSuccess()); + factory.stubMethod(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + GUID_STRING, HttpMethod.DELETE); CloudServiceInstance serviceInstance = ImmutableCloudServiceInstance.builder() .metadata( @@ -325,10 +307,7 @@ void testUpdateServiceSyslogDrainUrlSkipsManaged() { @Test void testUpdateServiceInstanceMetadataUpdates() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + GUID_STRING)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) - .andRespond(MockRestResponseCreators.withSuccess()); + factory.stubMethod(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + GUID_STRING, HttpMethod.PATCH); Metadata metadata = Metadata.builder() .label("key", "value") @@ -341,10 +320,7 @@ void testUpdateServiceInstanceMetadataUpdates() { @Test void testUpdateServiceInstanceMetadataUpdatesWithNullMetadata() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + GUID_STRING)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) - .andRespond(MockRestResponseCreators.withSuccess()); + factory.stubMethod(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + GUID_STRING, HttpMethod.PATCH); operations.updateServiceInstanceMetadata(UUID.fromString(GUID_STRING), null); @@ -353,12 +329,9 @@ void testUpdateServiceInstanceMetadataUpdatesWithNullMetadata() { @Test void testGetServiceInstancesByMetadataLabelSelectorMapsUserProvided() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=5000" - + "&space_guids=" + SPACE_GUID + "&label_selector=env")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[" + getUserProvidedInstanceJson("ups-1") + "]}", - MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=5000" + + "&space_guids=" + SPACE_GUID + "&label_selector=env", + "{\"resources\":[" + getUserProvidedInstanceJson("ups-1") + "]}"); List result = operations.getServiceInstancesByMetadataLabelSelector("env"); @@ -370,11 +343,8 @@ void testGetServiceInstancesByMetadataLabelSelectorMapsUserProvided() { @Test void testGetServiceInstancesByMetadataLabelSelectorReturnsEmpty() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=5000" - + "&space_guids=" + SPACE_GUID + "&label_selector=env")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=5000" + + "&space_guids=" + SPACE_GUID + "&label_selector=env", "{\"resources\":[]}"); List result = operations.getServiceInstancesByMetadataLabelSelector("env"); @@ -384,12 +354,9 @@ void testGetServiceInstancesByMetadataLabelSelectorReturnsEmpty() { @Test void testGetServiceInstancesWithoutAuxiliaryContentByMetadataLabelSelectorMaps() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=5000" - + "&space_guids=" + SPACE_GUID + "&label_selector=env")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[" + getManagedInstanceJson("mi-1") + "]}", - MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=5000" + + "&space_guids=" + SPACE_GUID + "&label_selector=env", + "{\"resources\":[" + getManagedInstanceJson("mi-1") + "]}"); List result = operations.getServiceInstancesWithoutAuxiliaryContentByMetadataLabelSelector("env"); @@ -403,12 +370,9 @@ void testGetServiceInstancesWithoutAuxiliaryContentByMetadataLabelSelectorMaps() @Test void testGetServiceInstancesWithoutAuxiliaryContentByNames() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=5000" - + "&space_guids=" + SPACE_GUID + "&names=my-service")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[" + getManagedInstanceJson("my-service") + "]}", - MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=5000" + + "&space_guids=" + SPACE_GUID + "&names=my-service", + "{\"resources\":[" + getManagedInstanceJson("my-service") + "]}"); List result = operations.getServiceInstancesWithoutAuxiliaryContentByNames(List.of("my-service")); @@ -429,55 +393,36 @@ void testGetServiceInstancesWithoutAuxiliaryContentByNamesReturnsEmptyForEmptyIn private void stubServiceInstanceLookup(String name, String instanceJson) { String body = instanceJson == null ? "{\"resources\":[]}" : "{\"resources\":[" + instanceJson + "]}"; - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=5000" - + "&space_guids=" + SPACE_GUID + "&names=" + name)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(body, MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=5000" + + "&space_guids=" + SPACE_GUID + "&names=" + name, body); } private void stubOfferingLookup(String label, String broker) { String uri = MockControllerClientFactory.BASE_URL + "/v3/service_offerings?per_page=5000&space_guids=" + SPACE_GUID + "&names=" + label + (broker == null ? "" : "&service_broker_names=" + broker); - factory.server() - .expect(MockRestRequestMatchers.requestTo(uri)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[{\"guid\":\"" + OFFERING_GUID + "\",\"name\":\"" + label - + "\"}]}", MediaType.APPLICATION_JSON)); + factory.stubGet(uri, "{\"resources\":[{\"guid\":\"" + OFFERING_GUID + "\",\"name\":\"" + label + "\"}]}"); } private void stubPlanLookup(String planName) { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_plans?per_page=5000" - + "&service_offering_guids=" + OFFERING_GUID + "&names=" + planName)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[{\"guid\":\"" + PLAN_GUID + "\",\"name\":\"" + planName - + "\"}]}", MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_plans?per_page=5000" + + "&service_offering_guids=" + OFFERING_GUID + "&names=" + planName, + "{\"resources\":[{\"guid\":\"" + PLAN_GUID + "\",\"name\":\"" + planName + "\"}]}"); } private void stubPlanForNameResolution() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_plans/" + PLAN_GUID)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"guid\":\"" + PLAN_GUID + "\",\"name\":\"my-plan\",\"relationships\":" - + "{\"service_offering\":{\"data\":{\"guid\":\"" + OFFERING_GUID - + "\"}}}}", MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_plans/" + PLAN_GUID, + "{\"guid\":\"" + PLAN_GUID + "\",\"name\":\"my-plan\",\"relationships\":" + + "{\"service_offering\":{\"data\":{\"guid\":\"" + OFFERING_GUID + "\"}}}}"); } private void stubOfferingForNameResolution() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_offerings/" + OFFERING_GUID)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"guid\":\"" + OFFERING_GUID + "\",\"name\":\"my-offering\"}", - MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_offerings/" + OFFERING_GUID, + "{\"guid\":\"" + OFFERING_GUID + "\",\"name\":\"my-offering\"}"); } private void stubPatch() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + GUID_STRING)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) - .andRespond(MockRestResponseCreators.withSuccess()); + factory.stubMethod(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + GUID_STRING, HttpMethod.PATCH); } private static String getManagedInstanceJson(String name) { diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3OperationsTest.java index e61bce6f38..01b177bc71 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3OperationsTest.java @@ -17,7 +17,6 @@ import org.mockito.MockitoAnnotations; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; import org.springframework.test.web.client.match.MockRestRequestMatchers; import org.springframework.test.web.client.response.MockRestResponseCreators; @@ -174,26 +173,16 @@ void testCreateAndFetchServiceKeyReturnsNullWhenKeyNotFoundAfterJob() { } private void stubKeyLookup(String responseJson) { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + LOOKUP_KEY_QUERY)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(responseJson, MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + LOOKUP_KEY_QUERY, responseJson); } private void stubKeyList(String responseJson) { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + LIST_KEYS_QUERY)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(responseJson, MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + LIST_KEYS_QUERY, responseJson); } private void stubCredentials() { - factory.server() - .expect(MockRestRequestMatchers.requestTo( - MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings/" + KEY_GUID + "/details")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"credentials\":{\"password\":\"secret\"}}", - MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings/" + KEY_GUID + "/details", + "{\"credentials\":{\"password\":\"secret\"}}"); } private void stubAcceptedPost() { @@ -207,11 +196,8 @@ private void stubAcceptedPost() { } private void stubJobComplete() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess("{\"guid\":\"" + JOB_GUID + "\",\"state\":\"COMPLETE\"}", - MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID, + "{\"guid\":\"" + JOB_GUID + "\",\"state\":\"COMPLETE\"}"); } private static String getServiceKeyResourcesJson() { diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3OperationsTest.java index 2f2bdabfdc..c83364bba6 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3OperationsTest.java @@ -12,7 +12,6 @@ import org.mockito.MockitoAnnotations; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; import org.springframework.test.web.client.match.MockRestRequestMatchers; import org.springframework.test.web.client.response.MockRestResponseCreators; @@ -106,11 +105,8 @@ void testUpdateServicePlanVisibilityForBrokerDoesNothingWhenNoPlans() { } private void stubBrokerLookup(String responseJson) { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_brokers?names=" - + BROKER_NAME + "&per_page=5000")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(responseJson, MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_brokers?names=" + BROKER_NAME + "&per_page=5000", + responseJson); } private void stubOfferingLookup(UUID spaceGuid, String responseJson) { @@ -121,18 +117,12 @@ private void stubOfferingLookup(UUID spaceGuid, String responseJson) { uri = uri + "&space_guids=" + spaceGuid; } - factory.server() - .expect(MockRestRequestMatchers.requestTo(uri)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(responseJson, MediaType.APPLICATION_JSON)); + factory.stubGet(uri, responseJson); } private void stubPlanLookup(String responseJson) { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_plans?service_offering_guids=" - + OFFERING_GUID + "&per_page=5000")) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(responseJson, MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_plans?service_offering_guids=" + OFFERING_GUID + + "&per_page=5000", responseJson); } private void stubVisibilityPatch(String planGuid) { diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/TasksV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/TasksV3OperationsTest.java index fc8df33bbf..71b88e492e 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/TasksV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/TasksV3OperationsTest.java @@ -39,10 +39,7 @@ void setUp() throws Exception { @Test void testGetTaskReturnsMappedTask() { - factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/tasks/" + TASK_GUID)) - .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) - .andRespond(MockRestResponseCreators.withSuccess(getTaskJson("SUCCEEDED"), MediaType.APPLICATION_JSON)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/tasks/" + TASK_GUID, getTaskJson("SUCCEEDED")); CloudTask result = operations.getTask(UUID.fromString(TASK_GUID)); diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ApplicationMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ApplicationMapperTest.java index 516b66daa8..1686dfae6f 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ApplicationMapperTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ApplicationMapperTest.java @@ -17,7 +17,7 @@ class V3ApplicationMapperTest { @Test void testToCloudApplicationMapsAllFields() { V3Lifecycle lifecycle = new V3Lifecycle("buildpack", new V3LifecycleData(List.of("java_buildpack"), "cflinuxfs4")); - V3Application app = new V3Application(GUID_STRING, "my-app", "STARTED", null, null, lifecycle, null, null); + V3Application app = application("STARTED", lifecycle); CloudApplication result = V3ApplicationMapper.toCloudApplication(app, null); @@ -31,7 +31,7 @@ void testToCloudApplicationMapsAllFields() { @Test void testToCloudApplicationNullStateReturnsNullState() { - V3Application app = new V3Application(GUID_STRING, "my-app", null, null, null, null, null, null); + V3Application app = application(null, null); Assertions.assertNull(V3ApplicationMapper.toCloudApplication(app, null) .getState()); @@ -39,7 +39,7 @@ void testToCloudApplicationNullStateReturnsNullState() { @Test void testToCloudApplicationNullLifecycleReturnsNullLifecycle() { - V3Application app = new V3Application(GUID_STRING, "my-app", "STOPPED", null, null, null, null, null); + V3Application app = application("STOPPED", null); Assertions.assertNull(V3ApplicationMapper.toCloudApplication(app, null) .getLifecycle()); @@ -48,7 +48,7 @@ void testToCloudApplicationNullLifecycleReturnsNullLifecycle() { @Test void testToCloudApplicationLifecycleWithNullTypeReturnsNullLifecycle() { V3Lifecycle lifecycle = new V3Lifecycle(null, new V3LifecycleData(List.of(), "cflinuxfs4")); - V3Application app = new V3Application(GUID_STRING, "my-app", "STARTED", null, null, lifecycle, null, null); + V3Application app = application("STARTED", lifecycle); Assertions.assertNull(V3ApplicationMapper.toCloudApplication(app, null) .getLifecycle()); @@ -57,7 +57,7 @@ void testToCloudApplicationLifecycleWithNullTypeReturnsNullLifecycle() { @Test void testToCloudApplicationLifecycleTypeIsUpperCased() { V3Lifecycle lifecycle = new V3Lifecycle("docker", null); - V3Application app = new V3Application(GUID_STRING, "my-app", "STARTED", null, null, lifecycle, null, null); + V3Application app = application("STARTED", lifecycle); Assertions.assertEquals(LifecycleType.DOCKER, V3ApplicationMapper.toCloudApplication(app, null) .getLifecycle() @@ -67,7 +67,7 @@ void testToCloudApplicationLifecycleTypeIsUpperCased() { @Test void testToCloudApplicationNullLifecycleDataReturnsEmptyDataMap() { V3Lifecycle lifecycle = new V3Lifecycle("buildpack", null); - V3Application app = new V3Application(GUID_STRING, "my-app", "STARTED", null, null, lifecycle, null, null); + V3Application app = application("STARTED", lifecycle); Assertions.assertEquals(Map.of(), V3ApplicationMapper.toCloudApplication(app, null) .getLifecycle() @@ -77,11 +77,15 @@ void testToCloudApplicationNullLifecycleDataReturnsEmptyDataMap() { @Test void testToCloudApplicationLifecycleDataWithNullFieldsReturnsDefaultedMap() { V3Lifecycle lifecycle = new V3Lifecycle("buildpack", new V3LifecycleData(null, null)); - V3Application app = new V3Application(GUID_STRING, "my-app", "STARTED", null, null, lifecycle, null, null); + V3Application app = application("STARTED", lifecycle); Assertions.assertEquals(Map.of("buildpacks", List.of(), "stack", ""), V3ApplicationMapper.toCloudApplication(app, null) .getLifecycle() .getData()); } + private V3Application application(String state, V3Lifecycle lifecycle) { + return new V3Application(GUID_STRING, "my-app", state, null, null, lifecycle, null, null); + } + } diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3JobMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3JobMapperTest.java index 38f4c8b0b2..9b4d710a6e 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3JobMapperTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3JobMapperTest.java @@ -36,11 +36,10 @@ void testToCloudAsyncJobParseStateIsCaseInsensitive() { } @Test - void testToCloudAsyncJobParseStateWithNullReturnsNullState() { + void testToCloudAsyncJobParseStateWithNullThrows() { V3Job job = buildJobWithState(null); - Assertions.assertNull(V3JobMapper.toCloudAsyncJob(job) - .getState()); + Assertions.assertThrows(NullPointerException.class, () -> V3JobMapper.toCloudAsyncJob(job)); } @Test From e7fa4e632208b45643ba277c0fbf538d2c1f4d2f Mon Sep 17 00:00:00 2001 From: Krasimir Kargov Date: Mon, 12 Oct 2026 16:51:06 +0300 Subject: [PATCH 12/15] Fixing final comments LMCROSSITXSADEPLOY-3563 --- multiapps-controller-client/pom.xml | 10 +- .../src/main/java/module-info.java | 1 - .../multiapps/controller/Constants.java | 20 +- .../multiapps/controller/Messages.java | 6 +- .../ResilientCloudControllerClient.java | 5 - .../client/facade/CloudControllerClient.java | 4 +- .../facade/CloudControllerClientImpl.java | 5 - .../controller/client/facade/Messages.java | 1 + .../facade/adapters/LogCacheClient.java | 17 +- .../client/facade/domain/CloudProcess.java | 1 + .../client/facade/domain/Metadata.java | 108 ++--------- .../facade/domain/RouteDestination.java | 1 + .../client/facade/domain/UserRole.java | 2 +- .../facade/rest/ApplicationsV3Operations.java | 26 ++- .../facade/rest/BuildsV3Operations.java | 20 +- .../rest/CloudControllerRestClient.java | 4 +- .../CloudControllerRestClientBuilder.java | 28 +-- .../rest/CloudControllerRestClientV3Impl.java | 8 +- .../rest/CloudControllerV3Endpoints.java | 51 +++-- .../client/facade/rest/CloudSpaceClient.java | 40 ++-- .../facade/rest/DomainsV3Operations.java | 27 ++- .../facade/rest/EventsV3Operations.java | 16 +- .../client/facade/rest/JobsV3Operations.java | 6 +- .../facade/rest/OperationsQueryUtil.java | 71 +++---- .../client/facade/rest/OperationsUtil.java | 50 +++-- .../facade/rest/PackagesV3Operations.java | 42 ++-- .../facade/rest/ProcessesV3Operations.java | 49 ++--- .../client/facade/rest/RolesV3Operations.java | 9 +- .../facade/rest/RoutesV3Operations.java | 41 ++-- .../rest/ServiceBindingsV3Operations.java | 51 +++-- .../rest/ServiceBrokersV3Operations.java | 31 +-- .../rest/ServiceInstancesV3Operations.java | 107 ++++++----- .../facade/rest/ServiceKeysV3Operations.java | 25 ++- .../rest/ServiceOfferingsV3Operations.java | 23 +-- .../facade/rest/ServicePlansV3Operations.java | 36 ++-- .../facade/rest/StacksV3Operations.java | 14 +- .../client/facade/rest/TasksV3Operations.java | 12 +- .../controller/client/facade/rest/V3Uri.java | 95 +++++++++ .../facade/rest/resources/LogCacheFields.java | 17 ++ .../rest/resources/LogCacheReadResponse.java | 12 +- .../facade/rest/resources/V3AppFeature.java | 12 ++ .../facade/rest/resources/V3Application.java | 2 +- .../rest/resources/V3ApplicationMapper.java | 8 +- .../facade/rest/resources/V3AuditEvent.java | 2 +- .../client/facade/rest/resources/V3Build.java | 22 ++- .../facade/rest/resources/V3Domain.java | 3 +- .../facade/rest/resources/V3Droplet.java | 42 +++- .../facade/rest/resources/V3Fields.java | 56 ++++++ .../rest/resources/V3InstancesInfoMapper.java | 4 +- .../client/facade/rest/resources/V3Job.java | 13 +- .../facade/rest/resources/V3JobMapper.java | 11 +- .../facade/rest/resources/V3ListResponse.java | 10 +- .../facade/rest/resources/V3Metadata.java | 4 +- .../facade/rest/resources/V3Organization.java | 3 +- .../facade/rest/resources/V3Package.java | 29 ++- .../rest/resources/V3PackageMapper.java | 2 +- .../facade/rest/resources/V3Process.java | 47 +++-- .../rest/resources/V3ProcessMapper.java | 2 +- .../rest/resources/V3QueryParameters.java | 26 +++ .../rest/resources/V3ResourceMappers.java | 3 +- .../client/facade/rest/resources/V3Role.java | 18 +- .../client/facade/rest/resources/V3Route.java | 14 +- .../facade/rest/resources/V3RouteMapper.java | 28 ++- .../rest/resources/V3ServiceBinding.java | 2 +- .../resources/V3ServiceBindingMapper.java | 24 +-- .../rest/resources/V3ServiceBroker.java | 2 +- .../rest/resources/V3ServiceInstance.java | 10 +- .../rest/resources/V3ServiceOffering.java | 5 +- .../facade/rest/resources/V3ServicePlan.java | 88 ++++++++- .../rest/resources/V3ServicePlanMapper.java | 8 +- .../client/facade/rest/resources/V3Space.java | 20 +- .../facade/rest/resources/V3SshEnabled.java | 12 ++ .../client/facade/rest/resources/V3Stack.java | 4 +- .../client/facade/rest/resources/V3Task.java | 20 ++ .../client/facade/util/RestUtil.java | 2 +- .../ResilientCloudControllerClientTest.java | 12 -- ...sCloudControllerClientIntegrationTest.java | 5 +- .../rest/ApplicationsV3OperationsTest.java | 31 ++- .../facade/rest/BuildsV3OperationsTest.java | 8 +- .../CloudControllerRestClientV3ImplTest.java | 180 +++++++++++------- .../facade/rest/DomainsV3OperationsTest.java | 12 +- .../facade/rest/PackagesV3OperationsTest.java | 4 +- .../rest/ProcessesV3OperationsTest.java | 41 ++-- .../facade/rest/ReactiveFanOutTest.java | 8 +- .../facade/rest/RolesV3OperationsTest.java | 2 +- .../facade/rest/RoutesV3OperationsTest.java | 97 +++++++--- .../rest/ServiceBindingsV3OperationsTest.java | 35 ++-- .../rest/ServiceBrokersV3OperationsTest.java | 12 +- .../ServiceInstancesV3OperationsTest.java | 19 +- .../rest/ServiceKeysV3OperationsTest.java | 31 ++- .../ServiceOfferingsV3OperationsTest.java | 9 +- .../rest/ServicePlansV3OperationsTest.java | 11 +- .../facade/rest/StacksV3OperationsTest.java | 6 +- .../facade/rest/TasksV3OperationsTest.java | 4 +- .../client/facade/rest/V3UriTest.java | 149 +++++++++++++++ .../rest/resources/V3BuildMapperTest.java | 8 +- .../rest/resources/V3DomainMapperTest.java | 10 +- .../resources/V3InstancesInfoMapperTest.java | 14 ++ .../rest/resources/V3PackageMapperTest.java | 23 ++- .../rest/resources/V3ProcessMapperTest.java | 34 +++- .../rest/resources/V3RoleMapperTest.java | 4 +- .../rest/resources/V3RouteMapperTest.java | 32 ++++ .../V3ServiceInstanceMapperTest.java | 12 +- .../V3ServiceOfferingMapperTest.java | 8 +- .../resources/V3ServicePlanMapperTest.java | 14 +- .../rest/resources/V3StackMapperTest.java | 5 +- .../rest/resources/V3TaskMapperTest.java | 6 +- multiapps-controller-core/pom.xml | 8 - .../cf/clients/CloudEntityResourceMapper.java | 3 +- .../cf/metadata/util/MtaMetadataUtil.java | 19 +- .../cf/v2/ApplicationMetadataBuilder.java | 9 +- .../core/cf/v2/MtaMetadataBuilder.java | 16 +- .../core/cf/v2/ServiceKeyMetadataBuilder.java | 4 +- .../core/cf/v2/ServiceMetadataBuilder.java | 4 +- .../cf/detect/DeployedMtaDetectorTest.java | 6 +- .../util/ApplicationConfigurationTest.java | 6 +- .../client/LoggingCloudControllerClient.java | 6 - .../PrepareApplicationForBackupStep.java | 14 +- .../steps/RemoveMtaBackupMetadataStep.java | 8 +- .../controller/process/steps/StepsUtil.java | 9 +- ...CreateOrUpdateStepWithExistingAppTest.java | 1 - .../DeleteServiceMtaMetadataStepTest.java | 6 +- .../DetachServiceKeysFromMtaStepTest.java | 29 +-- .../steps/DetachServicesFromMtaStepTest.java | 27 +-- .../steps/DetectDeployedMtaStepTest.java | 10 +- ...ServiceDeleteActionsToExecuteStepTest.java | 6 +- ...etermineServiceKeysToRecreateStepTest.java | 18 +- .../PrepareApplicationForBackupStepTest.java | 10 +- ...PrepareBackupMtaForDeploymentStepTest.java | 6 +- .../RemoveMtaBackupMetadataStepTest.java | 20 +- .../process/steps/StepsTestUtil.java | 5 +- .../process/steps/SyncFlowableStepTest.java | 6 +- .../UpdateServiceKeysMetadataStepTest.java | 13 +- .../ExistingAppsToBackupCalculatorTest.java | 32 ++-- .../process/util/ModuleDeterminerTest.java | 3 +- .../OperationInFinalStateHandlerTest.java | 6 +- pom.xml | 16 +- 137 files changed, 1775 insertions(+), 1047 deletions(-) create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/V3Uri.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/LogCacheFields.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AppFeature.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3QueryParameters.java create mode 100644 multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3SshEnabled.java create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/V3UriTest.java diff --git a/multiapps-controller-client/pom.xml b/multiapps-controller-client/pom.xml index fbf6c87a5e..19dfc96ea3 100644 --- a/multiapps-controller-client/pom.xml +++ b/multiapps-controller-client/pom.xml @@ -118,7 +118,7 @@ org.hamcrest hamcrest - 2.2 + ${hamcrest.version} test @@ -157,14 +157,6 @@ - - - - commons-logging - commons-logging - ${commons-logging.version} - -
\ No newline at end of file diff --git a/multiapps-controller-client/src/main/java/module-info.java b/multiapps-controller-client/src/main/java/module-info.java index 3aafdc0b6c..1600dc2c0b 100644 --- a/multiapps-controller-client/src/main/java/module-info.java +++ b/multiapps-controller-client/src/main/java/module-info.java @@ -16,7 +16,6 @@ requires transitive spring.web; requires com.fasterxml.jackson.databind; - requires java.desktop; requires java.net.http; requires io.netty.handler; requires io.netty.transport; diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Constants.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Constants.java index 253ac729aa..4f8ab5ea56 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Constants.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Constants.java @@ -5,7 +5,7 @@ public class Constants { - public static final Duration DEFAULT_CONNECT_TIMEOUT = Duration.ofMinutes(1); + public static final Duration DEFAULT_CONNECT_TIMEOUT = Duration.ofMinutes(2); public static final Duration JOB_POLL_MIN_INTERVAL = Duration.ofSeconds(1); public static final Duration JOB_POLL_MAX_INTERVAL = Duration.ofSeconds(15); public static final Duration DELETE_JOB_TIMEOUT = Duration.ofMinutes(5); @@ -14,9 +14,9 @@ public class Constants { public static final int DEFAULT_CONNECTION_POOL_SIZE = 192; public static final int UNDEFINED_PORT = -1; - public static final int MAX_CONCURRENT_PAGES = 256; + public static final int MAX_CONCURRENT_PAGES = 4; //leaving it like that for testing purposes public static final int SECOND_PAGE = 2; - public static final int DEFAULT_CONCURRENT_TASKS = 256; + public static final int DEFAULT_CONCURRENT_TASKS = 8; //this one can be a bit more, but first tests will be like that public static final int MAX_CHAR_LENGTH_FOR_PARAMS_IN_REQUEST = 4000; public static final int JOB_POLL_INTERVAL_MULTIPLIER = 2; @@ -36,14 +36,28 @@ public class Constants { public static final String EMPTY_STRING = ""; public static final String COLON = ":"; + public static final String COMMA = ","; + public static final String SPACE_SYMBOL = " "; + public static final String EQUALS = "="; public static final String PROTOCOL_SEPARATOR = "://"; + public static final String QUESTION_MARK = "?"; + public static final String AMPERSAND = "&"; public static final String NEW_LINE = "\n"; + public static final String SLASH = "/"; public static final String PACKAGE_LINK = "package"; public static final String WEB_PROCESS_TYPE = "web"; public static final String USER_PROVIDED = "user-provided"; + public static final String JOB_STATE_COMPLETE = "COMPLETE"; + public static final String JOB_STATE_FAILED = "FAILED"; + + public static final String PER_PAGE = "per_page="; + + public static final char OPEN_BRACE_CHAR = '{'; + public static final char CLOSE_BRACE_CHAR = '}'; + private Constants() { } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Messages.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Messages.java index 2e8e38ecc1..4c081e732f 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Messages.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Messages.java @@ -54,19 +54,15 @@ public class Messages { public static final String INTERRUPTED_WHILE_POLLING_ASYNC_JOB_0 = "Interrupted while polling an async job: \"{0}\""; public static final String UNKNOWN_JOB_STATE_0 = "Unknown job state: \"{0}\""; public static final String UNKNOWN_SERVICE_INSTANCE_TYPE_0 = "Unknown service instance type: \"{0}\""; - public static final String UNKNOWN_USER_ROLE_0 = "Unknown user role: \"{0}\""; public static final String CANNOT_CREATE_DOMAIN_0_WITHOUT_ORGANIZATION = "Cannot create domain \"{0}\" without specifying an organization."; public static final String PACKAGE_0_NOT_FOUND = "Package \"{0}\" not found."; public static final String BUILDPACKS_ARE_REQUIRED_FOR_THE_BUILDPACK_LIFECYCLE_TYPE = "Buildpacks are required for the buildpack (Cloud Native Buildpack) lifecycle type."; //Status texts - public static final String NOT_FOUND = "Not Found"; public static final String JOB_TIMEOUT = "Job Timeout"; public static final String JOB_FAILED = "Job Failed"; public static final String JOB_FAILED_WITHOUT_DETAILS = "Job failed without any error details"; public static final String JOB_0_WITH_OPERATION_1_FAILED_WITH_2 = "Job \"{0}\" (operation \"{1}\") failed with: {2}"; public static final String JOB_0_RELATED_TO_OPERATION_1_FAILED_WITH_2 = "Job \"{0}\" related to operation \"{1}\" failed with: \"{2}\""; - public static final String BAD_REQUEST = "Bad Request"; - public static final String FORBIDDEN = "Forbidden"; - + } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/ResilientCloudControllerClient.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/ResilientCloudControllerClient.java index d23bafd7ce..8c648a9266 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/ResilientCloudControllerClient.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/ResilientCloudControllerClient.java @@ -192,11 +192,6 @@ public List getApplicationRoutes(UUID applicationGuid) { return executeWithRetry(() -> delegate.getApplicationRoutes(applicationGuid)); } - @Override - public boolean getApplicationSshEnabled(UUID applicationGuid) { - return executeWithRetry(() -> delegate.getApplicationSshEnabled(applicationGuid)); - } - @Override public Map getApplicationFeatures(UUID applicationGuid) { return executeWithRetry(() -> delegate.getApplicationFeatures(applicationGuid)); diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/CloudControllerClient.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/CloudControllerClient.java index 89f8dec346..ac5670a127 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/CloudControllerClient.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/CloudControllerClient.java @@ -8,7 +8,6 @@ import java.util.Set; import java.util.UUID; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudAsyncJob; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudBuild; @@ -28,6 +27,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.DockerInfo; import org.cloudfoundry.multiapps.controller.client.facade.domain.DropletInfo; import org.cloudfoundry.multiapps.controller.client.facade.domain.InstancesInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ServicePlanVisibility; import org.cloudfoundry.multiapps.controller.client.facade.domain.Staging; import org.cloudfoundry.multiapps.controller.client.facade.domain.Upload; @@ -280,8 +280,6 @@ Optional bindServiceInstance(String bindingName, String applicationName, List getApplicationRoutes(UUID applicationGuid); - boolean getApplicationSshEnabled(UUID applicationGuid); - Map getApplicationFeatures(UUID applicationGuid); /** diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/CloudControllerClientImpl.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/CloudControllerClientImpl.java index e016da176c..51c9dca804 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/CloudControllerClientImpl.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/CloudControllerClientImpl.java @@ -257,11 +257,6 @@ public List getApplicationRoutes(UUID applicationGuid) { return handleExceptions(() -> delegate.getApplicationRoutes(applicationGuid)); } - @Override - public boolean getApplicationSshEnabled(UUID applicationGuid) { - return handleExceptions(() -> delegate.getApplicationSshEnabled(applicationGuid)); - } - @Override public Map getApplicationFeatures(UUID applicationGuid) { return handleExceptions(() -> delegate.getApplicationFeatures(applicationGuid)); diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/Messages.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/Messages.java index e4cda12b8a..af19d79b3d 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/Messages.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/Messages.java @@ -26,5 +26,6 @@ private Messages() { public static final String FAILED_TO_FETCH_APP_LOGS_FOR_APP = "Failed to fetch app logs for app: {0}"; public static final String BUILDPACKS_ARE_REQUIRED_FOR_CNB_LIFECYCLE_TYPE = "Buildpacks are required for CNB lifecycle type."; + public static final String UNKNOWN_USER_ROLE_0 = "Unknown user role: \"{0}\""; } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/LogCacheClient.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/LogCacheClient.java index 304a80dff8..bba82b70ea 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/LogCacheClient.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/LogCacheClient.java @@ -27,6 +27,13 @@ public class LogCacheClient { private static final String SOURCE_TYPE_KEY_NAME = "source_type"; private static final int MAX_LOG_COUNT = 1000; + private static final String READ_LOGS_PATH = "/api/v1/read/{sourceId}"; + private static final String ENVELOPE_TYPES_QUERY_PARAM = "envelope_types"; + private static final String LOG_ENVELOPE_TYPE = "LOG"; + private static final String DESCENDING_QUERY_PARAM = "descending"; + private static final String LIMIT_QUERY_PARAM = "limit"; + private static final String START_TIME_QUERY_PARAM = "start_time"; + private final RestClient restClient; public LogCacheClient(RestClient restClient) { @@ -57,11 +64,11 @@ private LogCacheReadResponse readApplicationLogs(UUID applicationGuid, LocalDate long startTime = secondsInNanos + instant.getNano() + 1; return restClient.get() - .uri(uriBuilder -> uriBuilder.path("/api/v1/read/{sourceId}") - .queryParam("envelope_types", "LOG") - .queryParam("descending", Boolean.TRUE) - .queryParam("limit", MAX_LOG_COUNT) - .queryParam("start_time", startTime) + .uri(uriBuilder -> uriBuilder.path(READ_LOGS_PATH) + .queryParam(ENVELOPE_TYPES_QUERY_PARAM, LOG_ENVELOPE_TYPE) + .queryParam(DESCENDING_QUERY_PARAM, Boolean.TRUE) + .queryParam(LIMIT_QUERY_PARAM, MAX_LOG_COUNT) + .queryParam(START_TIME_QUERY_PARAM, startTime) .build(applicationGuid.toString())) .retrieve() .body(LogCacheReadResponse.class); diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudProcess.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudProcess.java index ef141ae3fe..ff053f4e5d 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudProcess.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudProcess.java @@ -10,6 +10,7 @@ @JsonDeserialize(as = ImmutableCloudProcess.class) public abstract class CloudProcess extends CloudEntity implements Derivable { + @Nullable public abstract String getCommand(); public abstract Integer getDiskInMb(); diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/Metadata.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/Metadata.java index 8555421f22..d2ebb31503 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/Metadata.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/Metadata.java @@ -1,107 +1,25 @@ package org.cloudfoundry.multiapps.controller.client.facade.domain; -import java.util.Collections; -import java.util.LinkedHashMap; import java.util.Map; -import java.util.Objects; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; - -public final class Metadata { +import org.cloudfoundry.multiapps.controller.client.facade.AllowNulls; +import org.immutables.value.Value; - private final Map labels; - private final Map annotations; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; - @JsonCreator - Metadata(@JsonProperty("labels") Map labels, @JsonProperty("annotations") Map annotations) { - this.labels = labels == null ? Collections.emptyMap() : Collections.unmodifiableMap(new LinkedHashMap<>(labels)); - this.annotations = annotations == null ? Collections.emptyMap() : Collections.unmodifiableMap(new LinkedHashMap<>(annotations)); - } +@Value.Immutable +@JsonSerialize(as = ImmutableMetadata.class) +@JsonDeserialize(as = ImmutableMetadata.class) +public abstract class Metadata { + @AllowNulls @JsonProperty("labels") - public Map getLabels() { - return labels; - } + public abstract Map getLabels(); + @AllowNulls @JsonProperty("annotations") - public Map getAnnotations() { - return annotations; - } - - public static Builder builder() { - return new Builder(); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - - if (o == null || getClass() != o.getClass()) { - return false; - } - - Metadata metadata = (Metadata) o; - return labels.equals(metadata.labels) && annotations.equals(metadata.annotations); - } - - @Override - public int hashCode() { - return Objects.hash(labels, annotations); - } - - @Override - public String toString() { - return "Metadata{annotations=" + annotations + ", labels=" + labels + "}"; - } - - public static final class Builder { - - private final Map labels = new LinkedHashMap<>(); - private final Map annotations = new LinkedHashMap<>(); - - private Builder() { - } - - public Builder from(Metadata metadata) { - if (metadata != null) { - this.labels.putAll(metadata.getLabels()); - this.annotations.putAll(metadata.getAnnotations()); - } - - return this; - } - - public Builder label(String key, String value) { - this.labels.put(key, value); - return this; - } - - public Builder labels(Map labels) { - if (labels != null) { - this.labels.putAll(labels); - } - - return this; - } - - public Builder annotation(String key, String value) { - this.annotations.put(key, value); - return this; - } - - public Builder annotations(Map annotations) { - if (annotations != null) { - this.annotations.putAll(annotations); - } - - return this; - } + public abstract Map getAnnotations(); - public Metadata build() { - return new Metadata(labels, annotations); - } - } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/RouteDestination.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/RouteDestination.java index b8b23d5093..0245919e03 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/RouteDestination.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/RouteDestination.java @@ -21,6 +21,7 @@ public abstract class RouteDestination extends CloudEntity implements Derivable< @Nullable public abstract Integer getWeight(); + @Nullable public abstract String getProtocol(); @Override diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/UserRole.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/UserRole.java index 8644a5cdf2..225e918ed5 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/UserRole.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/UserRole.java @@ -5,7 +5,7 @@ import java.util.Map; import java.util.stream.Collectors; -import org.cloudfoundry.multiapps.controller.Messages; +import org.cloudfoundry.multiapps.controller.client.facade.Messages; public enum UserRole { diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3Operations.java index 9e040e2caf..2126f5baf4 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3Operations.java @@ -1,12 +1,12 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest; import java.text.MessageFormat; -import java.time.Duration; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; +import org.cloudfoundry.multiapps.controller.Constants; import org.cloudfoundry.multiapps.controller.Messages; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; @@ -26,9 +26,8 @@ public class ApplicationsV3Operations { private static final ParameterizedTypeReference> APP_PAGE = new ParameterizedTypeReference<>() { }; - private static final Duration DELETE_JOB_TIMEOUT = Duration.ofMinutes(5); - private final CloudControllerV3Client client; + private final CloudSpace targetSpace; public ApplicationsV3Operations(CloudControllerV3Client client, CloudSpace targetSpace) { @@ -52,7 +51,7 @@ public UUID createApplication(ApplicationToCreateDto dto) { private void validateTargetSpace() { if (targetSpace == null || targetSpace.getGuid() == null) { - throw new CloudOperationException(HttpStatus.BAD_REQUEST, Messages.BAD_REQUEST, + throw new CloudOperationException(HttpStatus.BAD_REQUEST, HttpStatus.BAD_REQUEST.getReasonPhrase(), Messages.TARGET_SPACE_REQUIRED_TO_CREATE_AN_APPLICATION); } } @@ -82,7 +81,7 @@ public void deleteApplication(String applicationName) { ResponseEntity response = client.delete(CloudControllerV3Endpoints.APP_BY_GUID, applicationGuid); - client.followAsyncJob(response, DELETE_JOB_TIMEOUT); + client.followAsyncJob(response, Constants.DELETE_JOB_TIMEOUT); } public CloudApplication getApplication(String applicationName) { @@ -97,7 +96,7 @@ public CloudApplication getApplication(String applicationName, boolean required) } if (required) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), MessageFormat.format(Messages.APPLICATION_0_NOT_FOUND, applicationName)); } @@ -109,14 +108,17 @@ public UUID getApplicationGuid(String applicationName) { } public String getApplicationName(UUID applicationGuid) { - V3Application app = client.get(CloudControllerV3Endpoints.APPS + "/" + applicationGuid, V3Application.class); + V3Application app = client.get(V3Uri.path(CloudControllerV3Endpoints.APP_BY_GUID, applicationGuid) + .build(), + V3Application.class); return app == null ? null : app.name(); } public Map getApplicationEnvironment(UUID applicationGuid) { - V3Application.V3EnvironmentVariables environmentVariablesJson = client.get(CloudControllerV3Endpoints.APPS + "/" + applicationGuid - + "/environment_variables", + V3Application.V3EnvironmentVariables environmentVariablesJson = client.get(V3Uri.path(CloudControllerV3Endpoints.APP_ENV_VARS, + applicationGuid) + .build(), V3Application.V3EnvironmentVariables.class); return environmentVariablesJson == null || environmentVariablesJson.environmentVariables() == null @@ -137,11 +139,7 @@ public List getApplications() { } public List getApplicationsByMetadataLabelSelector(String labelSelector) { - String query = OperationsQueryUtil.buildApplicationsQuery(null, targetSpace); - - if (labelSelector != null) { - query = query + CloudControllerV3Endpoints.AMPERSAND_LABEL_SELECTOR + labelSelector; - } + String query = OperationsQueryUtil.buildApplicationsQuery(null, targetSpace, labelSelector); return listApplications(query).stream() .map(app -> V3ApplicationMapper.toCloudApplication(app, targetSpace)) diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3Operations.java index 5e2243d23c..8fae3619db 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3Operations.java @@ -12,6 +12,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3BuildMapper; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Fields; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3QueryParameters; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpStatus; @@ -28,16 +29,18 @@ public BuildsV3Operations(CloudControllerV3Client client) { public CloudBuild createBuild(UUID packageGuid) { V3Build build = client.postForObject(CloudControllerV3Endpoints.BUILDS, - Map.of(V3Fields.PACKAGE, Map.of(V3Fields.GUID, packageGuid.toString())), V3Build.class); + Map.of(V3Fields.PACKAGE, Map.of(V3Fields.GUID, packageGuid.toString())), V3Build.class); return V3BuildMapper.toCloudBuild(build); } public CloudBuild getBuild(UUID buildGuid) { - V3Build build = client.get(CloudControllerV3Endpoints.BUILDS + "/" + buildGuid, V3Build.class); + V3Build build = client.get(V3Uri.path(CloudControllerV3Endpoints.BUILD_BY_GUID, buildGuid) + .build(), + V3Build.class); if (build == null) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), MessageFormat.format(Messages.BUILD_WITH_GUID_0_NOT_FOUND, buildGuid)); } @@ -45,8 +48,9 @@ public CloudBuild getBuild(UUID buildGuid) { } public List getBuildsForApplication(UUID applicationGuid) { - String uri = CloudControllerV3Endpoints.APPS + "/" + applicationGuid + "/builds" + CloudControllerV3Endpoints.QUERY_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; + String uri = V3Uri.path(CloudControllerV3Endpoints.APP_BUILDS, applicationGuid) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(); return client.list(uri, BUILD_PAGE) .stream() @@ -55,8 +59,10 @@ public List getBuildsForApplication(UUID applicationGuid) { } public List getBuildsForPackage(UUID packageGuid) { - String uri = CloudControllerV3Endpoints.BUILDS + CloudControllerV3Endpoints.QUERY_PACKAGE_GUIDS + packageGuid - + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; + String uri = V3Uri.path(CloudControllerV3Endpoints.BUILDS) + .query(V3QueryParameters.PACKAGE_GUIDS, packageGuid) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(); return client.list(uri, BUILD_PAGE) .stream() diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClient.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClient.java index 4ed03f71d4..5eebfa0cbc 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClient.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClient.java @@ -8,7 +8,6 @@ import java.util.Set; import java.util.UUID; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.UploadStatusCallback; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudAsyncJob; @@ -29,6 +28,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.DockerInfo; import org.cloudfoundry.multiapps.controller.client.facade.domain.DropletInfo; import org.cloudfoundry.multiapps.controller.client.facade.domain.InstancesInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ServicePlanVisibility; import org.cloudfoundry.multiapps.controller.client.facade.domain.Staging; import org.cloudfoundry.multiapps.controller.client.facade.domain.Upload; @@ -107,8 +107,6 @@ Optional bindServiceInstance(String bindingName, String applicationName, List getApplicationRoutes(UUID applicationGuid); - boolean getApplicationSshEnabled(UUID applicationGuid); - Map getApplicationFeatures(UUID applicationGuid); List getApplications(); diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientBuilder.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientBuilder.java index ea447bf6a7..ec4daf0dc3 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientBuilder.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientBuilder.java @@ -1,14 +1,13 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest; import java.net.URL; -import java.security.cert.X509Certificate; import java.time.Duration; import java.util.List; import java.util.Map; import java.util.Optional; import javax.net.ssl.SSLException; -import javax.net.ssl.X509TrustManager; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import io.netty.channel.ChannelOption; import io.netty.handler.ssl.SslContext; @@ -16,6 +15,7 @@ import org.cloudfoundry.multiapps.controller.Constants; import org.cloudfoundry.multiapps.controller.Messages; import org.cloudfoundry.multiapps.controller.client.facade.oauth2.OAuthClient; +import org.cloudfoundry.multiapps.controller.client.facade.util.RestUtil; import org.springframework.http.HttpHeaders; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ReactorClientHttpRequestFactory; @@ -35,7 +35,7 @@ public final class CloudControllerRestClientBuilder { - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectMapper OBJECT_MAPPER = configureObjectMapper(); private CloudControllerRestClientBuilder() { } @@ -173,30 +173,16 @@ private static SslContext buildDefaultClientSslContext() { private static SslContext buildTrustAllSslContext() { try { return SslContextBuilder.forClient() - .trustManager(getTrustAllCertificatesManager()) + .trustManager(RestUtil.createDummyTrustManager()) .build(); } catch (SSLException e) { throw new IllegalStateException(Messages.ERROR_OCCURRED_SETTING_UP_ALWAYS_APPROVING_SSL_CONTEXT, e); } } - private static X509TrustManager getTrustAllCertificatesManager() { - return new X509TrustManager() { - - @Override - public void checkClientTrusted(X509Certificate[] chain, String authType) { - } - - @Override - public void checkServerTrusted(X509Certificate[] chain, String authType) { - } - - @Override - public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[] {}; - } - - }; + private static ObjectMapper configureObjectMapper() { + return new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY); } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientV3Impl.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientV3Impl.java index 5d666f9c78..116a936d3f 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientV3Impl.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientV3Impl.java @@ -88,7 +88,6 @@ public CloudSpace getTarget() { return targetSpace; } - @Override public void addDomain(String domainName) { domainsOperations.addDomain(domainName); @@ -194,7 +193,7 @@ public Optional deleteServiceBinding(String serviceInstanceName, String CloudServiceKey serviceKey = getServiceKey(serviceInstanceName, serviceKeyName); if (serviceKey == null) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), MessageFormat.format(Messages.SERVICE_KEY_0_NOT_FOUND, serviceKeyName)); } @@ -276,11 +275,6 @@ public List getApplicationRoutes(UUID applicationGuid) { return routesOperations.getApplicationRoutes(applicationGuid); } - @Override - public boolean getApplicationSshEnabled(UUID applicationGuid) { - return processesOperations.getApplicationSshEnabled(applicationGuid); - } - @Override public Map getApplicationFeatures(UUID applicationGuid) { return processesOperations.getApplicationFeatures(applicationGuid); diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Endpoints.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Endpoints.java index 911b21aefb..835bbf9a8c 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Endpoints.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerV3Endpoints.java @@ -2,7 +2,8 @@ public class CloudControllerV3Endpoints { - public static final int DEFAULT_PAGE_SIZE = 5000; + //leaving it like that for testing, then if all is alright with the performance, we can remove it and just not pass it since the default is 50 from cf controller side + public static final int DEFAULT_PAGE_SIZE = 50; public static final String APPS = "/v3/apps"; public static final String ROUTES = "/v3/routes"; @@ -15,7 +16,6 @@ public class CloudControllerV3Endpoints { public static final String PACKAGES = "/v3/packages"; public static final String BUILDS = "/v3/builds"; public static final String TASKS = "/v3/tasks"; - public static final String PROCESSES = "/processes/"; public static final String STACKS = "/v3/stacks"; public static final String AUDIT_EVENTS = "/v3/audit_events"; public static final String JOBS = "/v3/jobs"; @@ -43,31 +43,28 @@ public class CloudControllerV3Endpoints { public static final String TASK_CANCEL = "/v3/tasks/{guid}/actions/cancel"; public static final String PACKAGE_UPLOAD = "/v3/packages/{guid}/upload"; public static final String SPACE_UNMAPPED_ROUTES = "/v3/spaces/{guid}/routes?unmapped=true"; - - public static final String QUERY_PER_PAGE = "?per_page="; - public static final String QUERY_NAMES = "?names="; - public static final String QUERY_PACKAGE_GUIDS = "?package_guids="; - public static final String QUERY_APP_GUIDS = "?app_guids="; - public static final String QUERY_GUIDS = "?guids="; - public static final String QUERY_SERVICE_INSTANCE_GUIDS = "?service_instance_guids="; - public static final String QUERY_SERVICE_OFFERING_GUIDS = "?service_offering_guids="; - public static final String QUERY_SERVICE_BROKER_GUIDS = "?service_broker_guids="; - public static final String QUERY_ORGANIZATION_GUIDS = "?organization_guids="; - - public static final String AMPERSAND_PER_PAGE = "&per_page="; - public static final String AMPERSAND_SPACE_GUIDS = "&space_guids="; - public static final String AMPERSAND_NAMES = "&names="; - public static final String AMPERSAND_TYPE = "&type="; - public static final String AMPERSAND_LABEL_SELECTOR = "&label_selector="; - public static final String AMPERSAND_DOMAIN_GUIDS = "&domain_guids="; - public static final String AMPERSAND_SERVICE_INSTANCE_GUIDS = "&service_instance_guids="; - public static final String AMPERSAND_SERVICE_OFFERING_GUIDS = "&service_offering_guids="; - public static final String AMPERSAND_SERVICE_BROKER_NAMES = "&service_broker_names="; - public static final String AMPERSAND_APP_GUIDS = "&app_guids="; - public static final String AMPERSAND_USER_GUIDS = "&user_guids="; - public static final String AMPERSAND_TARGET_GUIDS = "&target_guids="; - public static final String AMPERSAND_HOSTS = "&hosts="; - public static final String AMPERSAND_PATHS = "&paths="; + public static final String ORGANIZATION_DEFAULT_DOMAIN = "/v3/organizations/{guid}/domains/default"; + public static final String ORGANIZATION_DOMAINS = "/v3/organizations/{guid}/domains"; + public static final String ORGANIZATION_BY_GUID = "/v3/organizations/{guid}"; + public static final String SPACE_BY_GUID = "/v3/spaces/{guid}"; + public static final String JOB_BY_GUID = "/v3/jobs/{guid}"; + public static final String BUILD_BY_GUID = "/v3/builds/{guid}"; + public static final String PACKAGE_BY_GUID = "/v3/packages/{guid}"; + public static final String TASK_BY_GUID = "/v3/tasks/{guid}"; + public static final String SERVICE_PLAN_BY_GUID = "/v3/service_plans/{guid}"; + public static final String SERVICE_OFFERING_BY_GUID = "/v3/service_offerings/{guid}"; + public static final String SERVICE_INSTANCE_PARAMETERS = "/v3/service_instances/{guid}/parameters"; + public static final String SERVICE_INSTANCE_CREDENTIALS = "/v3/service_instances/{guid}/credentials"; + public static final String SERVICE_CREDENTIAL_BINDING_PARAMETERS = "/v3/service_credential_bindings/{guid}/parameters"; + public static final String SERVICE_CREDENTIAL_BINDING_DETAILS = "/v3/service_credential_bindings/{guid}/details"; + public static final String APP_WEB_PROCESS = "/v3/apps/{guid}/processes/web"; + public static final String APP_WEB_PROCESS_STATS = "/v3/apps/{guid}/processes/web/stats"; + public static final String APP_SSH_ENABLED = "/v3/apps/{guid}/ssh_enabled"; + public static final String APP_FEATURES = "/v3/apps/{guid}/features"; + public static final String APP_DROPLET_CURRENT = "/v3/apps/{guid}/droplets/current"; + public static final String APP_BUILDS = "/v3/apps/{guid}/builds"; + public static final String APP_PACKAGES = "/v3/apps/{guid}/packages"; + public static final String APP_ROUTES = "/v3/apps/{guid}/routes"; private CloudControllerV3Endpoints() { diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudSpaceClient.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudSpaceClient.java index f1cc684535..b97a881557 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudSpaceClient.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudSpaceClient.java @@ -11,16 +11,14 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudSpace; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Organization; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3QueryParameters; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ResourceMappers; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Space; -import org.cloudfoundry.multiapps.controller.client.facade.util.UriUtil; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpStatus; public class CloudSpaceClient { - private static final List CHARS_TO_ENCODE = List.of(","); - private final CloudControllerV3Client client; public CloudSpaceClient(CloudControllerV3Client client) { @@ -28,17 +26,20 @@ public CloudSpaceClient(CloudControllerV3Client client) { } public CloudSpace getSpace(UUID spaceGuid) { - V3Space space = client.getOptional(CloudControllerV3Endpoints.SPACES + "/" + spaceGuid, V3Space.class) - .orElseThrow(() -> new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + V3Space space = client.getOptional(V3Uri.path(CloudControllerV3Endpoints.SPACE_BY_GUID, spaceGuid) + .build(), V3Space.class) + .orElseThrow(() -> new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), MessageFormat.format(Messages.SPACE_WITH_GUID_0_NOT_FOUND, spaceGuid))); String orgGuid = space.organizationGuid(); - V3Organization org = client.getOptional(CloudControllerV3Endpoints.ORGANIZATIONS + "/" + orgGuid, V3Organization.class) - .orElseThrow(() -> new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, - MessageFormat.format( - Messages.ORGANIZATION_WITH_GUID_0_NOT_FOUND, - orgGuid))); + V3Organization org = client.getOptional(V3Uri.path(CloudControllerV3Endpoints.ORGANIZATION_BY_GUID, orgGuid) + .build(), V3Organization.class) + .orElseThrow( + () -> new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), + MessageFormat.format( + Messages.ORGANIZATION_WITH_GUID_0_NOT_FOUND, + orgGuid))); return mapToCloudSpace(space, org); } @@ -51,13 +52,14 @@ public CloudSpace getSpace(String organizationName, String spaceName) { } private V3Organization findOrganizationByName(String organizationName) { - List organizations = client.list(CloudControllerV3Endpoints.ORGANIZATIONS + CloudControllerV3Endpoints.QUERY_NAMES - + encodeAsQueryParam(organizationName), + List organizations = client.list(V3Uri.path(CloudControllerV3Endpoints.ORGANIZATIONS) + .queryEncoded(V3QueryParameters.NAMES, organizationName) + .build(), new ParameterizedTypeReference>() { }); if (organizations.isEmpty()) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), MessageFormat.format(Messages.ORGANISATION_0_NOT_FOUND, organizationName)); } @@ -66,13 +68,15 @@ private V3Organization findOrganizationByName(String organizationName) { private V3Space findSpaceByName(String spaceName, V3Organization org) { List spaces = client.list( - CloudControllerV3Endpoints.SPACES + CloudControllerV3Endpoints.QUERY_ORGANIZATION_GUIDS + org.guid() - + CloudControllerV3Endpoints.AMPERSAND_NAMES + encodeAsQueryParam(spaceName), + V3Uri.path(CloudControllerV3Endpoints.SPACES) + .query(V3QueryParameters.ORGANIZATION_GUIDS, org.guid()) + .queryEncoded(V3QueryParameters.NAMES, spaceName) + .build(), new ParameterizedTypeReference>() { }); if (spaces.isEmpty()) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), MessageFormat.format(Messages.SPACE_0_NOT_FOUND_IN_ORGANIZATION_1, spaceName, org.name())); } @@ -80,10 +84,6 @@ private V3Space findSpaceByName(String spaceName, V3Organization org) { return spaces.getFirst(); } - private String encodeAsQueryParam(String param) { - return UriUtil.encodeChars(param, CHARS_TO_ENCODE); - } - private CloudSpace mapToCloudSpace(V3Space space, V3Organization org) { return ImmutableCloudSpace.builder() .metadata(V3ResourceMappers.parseMetadata(space.guid(), space.createdAt(), space.updatedAt())) diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3Operations.java index cf3b88b42e..fd8f18eef4 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3Operations.java @@ -15,6 +15,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3DomainMapper; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Fields; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3QueryParameters; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -26,6 +27,7 @@ public class DomainsV3Operations { }; private final CloudControllerV3Client client; + private final CloudSpace targetSpace; public DomainsV3Operations(CloudControllerV3Client client, CloudSpace targetSpace) { @@ -48,7 +50,7 @@ public void deleteDomain(String domainName) { CloudDomain domain = findDomainByName(domainName); if (domain == null) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), MessageFormat.format(Messages.DOMAIN_0_NOT_FOUND, domainName)); } @@ -57,11 +59,12 @@ public void deleteDomain(String domainName) { public CloudDomain getDefaultDomain() { UUID organizationGuid = OperationsUtil.getGuid(targetSpace.getOrganization()); - V3Domain domain = client.get(CloudControllerV3Endpoints.ORGANIZATIONS + "/" + organizationGuid + "/domains/default", + V3Domain domain = client.get(V3Uri.path(CloudControllerV3Endpoints.ORGANIZATION_DEFAULT_DOMAIN, organizationGuid) + .build(), V3Domain.class); if (domain == null) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), MessageFormat.format(Messages.DOMAIN_WITH_GUID_0_NOT_FOUND, organizationGuid)); } @@ -91,8 +94,9 @@ public List getDomainsForOrganization() { assertSpaceProvided("access organization domains"); UUID targetOrganizationGuid = OperationsUtil.getGuid(targetSpace.getOrganization()); - String uri = CloudControllerV3Endpoints.ORGANIZATIONS + "/" + targetOrganizationGuid + "/domains" - + CloudControllerV3Endpoints.QUERY_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; + String uri = V3Uri.path(CloudControllerV3Endpoints.ORGANIZATION_DOMAINS, targetOrganizationGuid) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(); return client.list(uri, DOMAIN_PAGE) .stream() @@ -101,13 +105,16 @@ public List getDomainsForOrganization() { } private List getAllDomains() { - return client.list(CloudControllerV3Endpoints.DOMAINS + CloudControllerV3Endpoints.QUERY_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE, DOMAIN_PAGE); + return client.list(V3Uri.path(CloudControllerV3Endpoints.DOMAINS) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(), DOMAIN_PAGE); } private CloudDomain findDomainByName(String name) { - String uri = CloudControllerV3Endpoints.DOMAINS + CloudControllerV3Endpoints.QUERY_NAMES + name - + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; + String uri = V3Uri.path(CloudControllerV3Endpoints.DOMAINS) + .query(V3QueryParameters.NAMES, name) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(); return client.list(uri, DOMAIN_PAGE) .stream() @@ -119,7 +126,7 @@ private CloudDomain findDomainByName(String name) { private void createDomain(String name) { UUID organizationGuid = OperationsUtil.getGuid(targetSpace.getOrganization()); if (organizationGuid == null) { - throw new CloudOperationException(HttpStatus.BAD_REQUEST, Messages.BAD_REQUEST, + throw new CloudOperationException(HttpStatus.BAD_REQUEST, HttpStatus.BAD_REQUEST.getReasonPhrase(), MessageFormat.format(Messages.CANNOT_CREATE_DOMAIN_0_WITHOUT_ORGANIZATION, name)); } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/EventsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/EventsV3Operations.java index 0ab6741bdf..978177ff19 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/EventsV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/EventsV3Operations.java @@ -12,6 +12,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3AuditEventMapper; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3GuidReference; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3QueryParameters; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpStatus; @@ -19,10 +20,12 @@ public class EventsV3Operations { private static final ParameterizedTypeReference> EVENT_PAGE = new ParameterizedTypeReference<>() { }; + private static final ParameterizedTypeReference> APP_PAGE = new ParameterizedTypeReference<>() { }; private final CloudControllerV3Client client; + private final CloudSpace targetSpace; public EventsV3Operations(CloudControllerV3Client client, CloudSpace targetSpace) { @@ -31,16 +34,19 @@ public EventsV3Operations(CloudControllerV3Client client, CloudSpace targetSpace } public List getEvents() { - return client.list(CloudControllerV3Endpoints.AUDIT_EVENTS + CloudControllerV3Endpoints.QUERY_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE, EVENT_PAGE) + return client.list(V3Uri.path(CloudControllerV3Endpoints.AUDIT_EVENTS) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(), EVENT_PAGE) .stream() .map(V3AuditEventMapper::toCloudEvent) .toList(); } public List getEventsByTarget(UUID uuid) { - String query = CloudControllerV3Endpoints.AUDIT_EVENTS + CloudControllerV3Endpoints.QUERY_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE + CloudControllerV3Endpoints.AMPERSAND_TARGET_GUIDS + uuid; + String query = V3Uri.path(CloudControllerV3Endpoints.AUDIT_EVENTS) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .query(V3QueryParameters.TARGET_GUIDS, uuid) + .build(); return client.list(query, EVENT_PAGE) .stream() @@ -59,7 +65,7 @@ private UUID getRequiredApplicationGuid(String applicationName) { List apps = client.list(applicationsQuery, APP_PAGE); if (apps.isEmpty()) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), MessageFormat.format(Messages.APPLICATION_0_NOT_FOUND, applicationName)); } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/JobsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/JobsV3Operations.java index 048b58152f..884890f031 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/JobsV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/JobsV3Operations.java @@ -18,10 +18,12 @@ public JobsV3Operations(CloudControllerV3Client client) { } public CloudAsyncJob getAsyncJob(String jobId) { - V3Job job = client.get(CloudControllerV3Endpoints.JOBS + "/" + jobId, V3Job.class); + V3Job job = client.get(V3Uri.path(CloudControllerV3Endpoints.JOB_BY_GUID, jobId) + .build(), + V3Job.class); if (job == null) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), MessageFormat.format(Messages.JOB_WITH_GUID_0_NOT_FOUND, jobId)); } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/OperationsQueryUtil.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/OperationsQueryUtil.java index d56cbe0766..aada44b1b8 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/OperationsQueryUtil.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/OperationsQueryUtil.java @@ -3,6 +3,7 @@ import java.util.UUID; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3QueryParameters; public class OperationsQueryUtil { @@ -10,60 +11,38 @@ private OperationsQueryUtil() { } public static String buildApplicationsQuery(String name, CloudSpace targetSpace) { - StringBuilder query = new StringBuilder(CloudControllerV3Endpoints.APPS + CloudControllerV3Endpoints.QUERY_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE); - - if (targetSpace != null && targetSpace.getGuid() != null) { - query.append(CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) - .append(targetSpace.getGuid()); - } - - if (name != null) { - query.append(CloudControllerV3Endpoints.AMPERSAND_NAMES) - .append(name); - } + return buildApplicationsQuery(name, targetSpace, null); + } - return query.toString(); + public static String buildApplicationsQuery(String name, CloudSpace targetSpace, String labelSelector) { + return V3Uri.path(CloudControllerV3Endpoints.APPS) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .query(V3QueryParameters.SPACE_GUIDS, spaceGuidOrNull(targetSpace)) + .queryEncoded(V3QueryParameters.NAMES, name) + .query(V3QueryParameters.LABEL_SELECTOR, labelSelector) + .build(); } public static String buildRoutesQuery(UUID domainGuid, String host, String path, UUID targetSpaceGuid) { - StringBuilder query = new StringBuilder( - CloudControllerV3Endpoints.ROUTES + CloudControllerV3Endpoints.QUERY_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE); - - query.append(CloudControllerV3Endpoints.AMPERSAND_DOMAIN_GUIDS) - .append(domainGuid); - - query.append(CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) - .append(targetSpaceGuid); - - if (host != null) { - query.append(CloudControllerV3Endpoints.AMPERSAND_HOSTS) - .append(host); - } - - if (path != null) { - query.append(CloudControllerV3Endpoints.AMPERSAND_PATHS) - .append(path); - } - - return query.toString(); + return V3Uri.path(CloudControllerV3Endpoints.ROUTES) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .query(V3QueryParameters.DOMAIN_GUIDS, domainGuid) + .query(V3QueryParameters.SPACE_GUIDS, targetSpaceGuid) + .query(V3QueryParameters.HOSTS, host) + .query(V3QueryParameters.PATHS, path) + .build(); } public static String buildServiceInstancesQuery(String name, CloudSpace targetSpace) { - StringBuilder query = new StringBuilder(CloudControllerV3Endpoints.SERVICE_INSTANCES + CloudControllerV3Endpoints.QUERY_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE); - - if (targetSpace != null && targetSpace.getGuid() != null) { - query.append(CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) - .append(targetSpace.getGuid()); - } - - if (name != null) { - query.append(CloudControllerV3Endpoints.AMPERSAND_NAMES) - .append(name); - } + return V3Uri.path(CloudControllerV3Endpoints.SERVICE_INSTANCES) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .query(V3QueryParameters.SPACE_GUIDS, spaceGuidOrNull(targetSpace)) + .queryEncoded(V3QueryParameters.NAMES, name) + .build(); + } - return query.toString(); + private static UUID spaceGuidOrNull(CloudSpace targetSpace) { + return targetSpace == null ? null : targetSpace.getGuid(); } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/OperationsUtil.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/OperationsUtil.java index 41fd8531b0..30f670e6ed 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/OperationsUtil.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/OperationsUtil.java @@ -47,17 +47,13 @@ public static Map buildApplicationBody(ApplicationToCreateDto dt Map body = new HashMap<>(); body.put(V3Fields.NAME, dto.getName()); body.put(V3Fields.LIFECYCLE, buildLifecycle(dto.getStaging())); - body.put(V3Fields.RELATIONSHIPS, - java.util.Map.of(V3Fields.SPACE, java.util.Map.of(V3Fields.DATA, java.util.Map.of(V3Fields.GUID, targetSpaceGuid)))); + body.put(V3Fields.RELATIONSHIPS, Map.of(V3Fields.SPACE, Map.of(V3Fields.DATA, Map.of(V3Fields.GUID, targetSpaceGuid)))); if (dto.getEnv() != null) { body.put(V3Fields.ENVIRONMENT_VARIABLES, dto.getEnv()); } if (dto.getMetadata() != null) { - body.put(V3Fields.METADATA, java.util.Map.of(V3Fields.LABELS, dto.getMetadata() - .getLabels(), - V3Fields.ANNOTATIONS, dto.getMetadata() - .getAnnotations())); + body.put(V3Fields.METADATA, toMetadataMap(dto.getMetadata())); } return body; @@ -65,17 +61,17 @@ public static Map buildApplicationBody(ApplicationToCreateDto dt private static Map buildLifecycle(Staging staging) { if (staging == null) { - return java.util.Map.of(V3Fields.TYPE, "buildpack", V3Fields.DATA, java.util.Map.of()); + return Map.of(V3Fields.TYPE, V3Fields.BUILDPACK, V3Fields.DATA, Map.of()); } if (staging.getDockerInfo() != null) { - return Map.of(V3Fields.TYPE, "docker", V3Fields.DATA, Map.of()); + return Map.of(V3Fields.TYPE, V3Fields.DOCKER, V3Fields.DATA, Map.of()); } String type = staging.getLifecycleType() != null ? staging.getLifecycleType() .name() .toLowerCase() - : "buildpack"; + : V3Fields.BUILDPACK; Map data = new HashMap<>(); if (staging.getBuildpacks() != null) { @@ -94,11 +90,11 @@ public static DropletInfo parseDropletInfo(V3Droplet droplet) { .get(Constants.PACKAGE_LINK) .href(); - if (packageUrl.endsWith("/")) { - packageUrl = packageUrl.substring(0, packageUrl.lastIndexOf("/")); + if (packageUrl.endsWith(Constants.SLASH)) { + packageUrl = packageUrl.substring(0, packageUrl.lastIndexOf(Constants.SLASH)); } - String packageGuid = packageUrl.substring(packageUrl.lastIndexOf("/") + 1); + String packageGuid = packageUrl.substring(packageUrl.lastIndexOf(Constants.SLASH) + 1); return ImmutableDropletInfo.builder() .guid(UUID.fromString(droplet.guid())) .packageGuid(UUID.fromString(packageGuid)) @@ -107,13 +103,13 @@ public static DropletInfo parseDropletInfo(V3Droplet droplet) { public static Map buildApplicationLifecycle(Staging staging) { if (staging.getDockerInfo() != null) { - return Map.of(V3Fields.TYPE, "docker", V3Fields.DATA, Map.of()); + return Map.of(V3Fields.TYPE, V3Fields.DOCKER, V3Fields.DATA, Map.of()); } String lifecycleType = staging.getLifecycleType() != null ? staging.getLifecycleType() .name() .toLowerCase() - : "buildpack"; + : V3Fields.BUILDPACK; if (Constants.CLOUD_NATIVE_BUILDPACK.equals(lifecycleType) && (staging.getBuildpacks() == null || staging.getBuildpacks() .isEmpty())) { @@ -173,11 +169,11 @@ public static Map buildUpdatedProcessBody(Staging staging) { } public static Map buildCreateRouteBody(UUID domainGuid, String host, String path, UUID targetSpaceGuid) { - Map relationships = java.util.Map.of(V3Fields.DOMAIN, buildSingleResourceRelationship(domainGuid), - V3Fields.SPACE, buildSingleResourceRelationship(targetSpaceGuid)); + Map relationships = Map.of(V3Fields.DOMAIN, buildSingleResourceRelationship(domainGuid), + V3Fields.SPACE, buildSingleResourceRelationship(targetSpaceGuid)); - return Map.of(V3Fields.HOST, host == null ? "" : host, - V3Fields.PATH, path == null ? "" : path, + return Map.of(V3Fields.HOST, host == null ? Constants.EMPTY_STRING : host, + V3Fields.PATH, path == null ? Constants.EMPTY_STRING : path, V3Fields.RELATIONSHIPS, relationships); } @@ -244,7 +240,7 @@ public static void validateDomainForRoute(CloudRoute route, Map ex .getName(); if (!StringUtils.hasLength(domain) || !existingDomains.containsKey(domain)) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), MessageFormat.format(Messages.DOMAIN_0_NOT_FOUND_FOR_URI_1, domain, route.getUrl())); } @@ -254,7 +250,7 @@ public static Map buildServiceInstanceBody(String bindingName, U Map parameters) { Map body = new HashMap<>(); body.put(V3Fields.NAME, bindingName); - body.put(V3Fields.TYPE, "app"); + body.put(V3Fields.TYPE, V3Fields.APP); body.put(V3Fields.RELATIONSHIPS, Map.of(V3Fields.APP, toOneRelationship(applicationGuid), V3Fields.SERVICE_INSTANCE, toOneRelationship(serviceInstanceGuid))); @@ -315,13 +311,13 @@ public static Optional extractJobGuid(ResponseEntity response) { } public static Map basicAuthentication(CloudServiceBroker serviceBroker) { - return Map.of(V3Fields.TYPE, "basic", V3Fields.CREDENTIALS, + return Map.of(V3Fields.TYPE, V3Fields.BASIC, V3Fields.CREDENTIALS, Map.of(V3Fields.USERNAME, nullToEmpty(serviceBroker.getUsername()), V3Fields.PASSWORD, nullToEmpty(serviceBroker.getPassword()))); } private static String nullToEmpty(String value) { - return value == null ? "" : value; + return value == null ? Constants.EMPTY_STRING : value; } public static Map buildCreateServiceBrokerBody(CloudServiceBroker serviceBroker) { @@ -343,7 +339,7 @@ public static Map buildCreateServiceBrokerBody(CloudServiceBroke public static Map buildCreateServiceInstanceBody(CloudServiceInstance serviceInstance, UUID servicePlanGuid, CloudSpace targetSpace) { Map body = new HashMap<>(); - body.put(V3Fields.TYPE, "managed"); + body.put(V3Fields.TYPE, V3Fields.MANAGED); body.put(V3Fields.NAME, serviceInstance.getName()); MapUtil.addNonNull(body, V3Fields.METADATA, toMetadataMap(serviceInstance.getV3Metadata())); MapUtil.addNonNull(body, V3Fields.TAGS, serviceInstance.getTags()); @@ -373,7 +369,7 @@ public static Map toOneRelationship(String guid) { public static Map buildCreateUserProvidedServiceInstance(CloudServiceInstance serviceInstance, String syslogDrainUrl, CloudSpace targetSpace) { Map body = new HashMap<>(); - body.put(V3Fields.TYPE, "user-provided"); + body.put(V3Fields.TYPE, V3Fields.USER_PROVIDED); body.put(V3Fields.NAME, serviceInstance.getName()); MapUtil.addNonNull(body, V3Fields.METADATA, toMetadataMap(serviceInstance.getV3Metadata())); MapUtil.addNonNull(body, V3Fields.CREDENTIALS, serviceInstance.getCredentials()); @@ -405,11 +401,11 @@ public static String servicePlanGuidOf(V3ServiceInstance resource) { public static CloudOperationException determineNameResolutionError(CloudOperationException e, String forbiddenMessage, String notFoundMessage) { if (e.getStatusCode() == HttpStatus.FORBIDDEN) { - return new CloudOperationException(HttpStatus.FORBIDDEN, Messages.FORBIDDEN, forbiddenMessage, e); + return new CloudOperationException(HttpStatus.FORBIDDEN, HttpStatus.FORBIDDEN.getReasonPhrase(), forbiddenMessage, e); } if (e.getStatusCode() == HttpStatus.NOT_FOUND) { - return new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, notFoundMessage, e); + return new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), notFoundMessage, e); } return e; @@ -461,7 +457,7 @@ public static List> toBatches(Collection largeList, int maxCharLe public static Map buildCreateServiceKeyBody(String name, Map parameters, Metadata metadata, UUID serviceInstanceGuid) { Map resultBody = new HashMap<>(); - resultBody.put(V3Fields.TYPE, "key"); + resultBody.put(V3Fields.TYPE, V3Fields.KEY); resultBody.put(V3Fields.NAME, name); resultBody.put(V3Fields.RELATIONSHIPS, Map.of(V3Fields.SERVICE_INSTANCE, Map.of(V3Fields.DATA, Map.of(V3Fields.GUID, serviceInstanceGuid.toString())))); diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3Operations.java index 45ce0bd716..1443af8a11 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3Operations.java @@ -69,10 +69,11 @@ public PackagesV3Operations(CloudControllerV3Client client, CloudSpace targetSpa } public CloudPackage getPackage(UUID packageGuid) { - V3Package packageResource = client.get(CloudControllerV3Endpoints.PACKAGES + "/" + packageGuid, V3Package.class); + V3Package packageResource = client.get(V3Uri.path(CloudControllerV3Endpoints.PACKAGE_BY_GUID, packageGuid) + .build(), V3Package.class); if (packageResource == null) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), MessageFormat.format(Messages.PACKAGE_0_NOT_FOUND, packageGuid)); } @@ -80,8 +81,9 @@ public CloudPackage getPackage(UUID packageGuid) { } public List getPackagesForApplication(UUID applicationGuid) { - return client.list(CloudControllerV3Endpoints.APPS + "/" + applicationGuid + "/packages" + CloudControllerV3Endpoints.QUERY_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE, PACKAGE_PAGE) + return client.list(V3Uri.path(CloudControllerV3Endpoints.APP_PACKAGES, applicationGuid) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(), PACKAGE_PAGE) .stream() .map(V3PackageMapper::toCloudPackage) .toList(); @@ -98,7 +100,7 @@ public CloudPackage createDockerPackage(UUID applicationGuid, DockerInfo dockerI } V3Package created = client.postForObject(CloudControllerV3Endpoints.PACKAGES, - Map.of(V3Fields.TYPE, "docker", V3Fields.DATA, data, V3Fields.RELATIONSHIPS, + Map.of(V3Fields.TYPE, V3Fields.DOCKER, V3Fields.DATA, data, V3Fields.RELATIONSHIPS, applicationRelationship(applicationGuid)), V3Package.class); @@ -135,7 +137,7 @@ private CloudPackage startUpload(String applicationName, Path file, Duration upl UUID packageGuid = createBitsPackage(applicationGuid).getGuid(); MultiValueMap body = new LinkedMultiValueMap<>(); - body.add("bits", new FileSystemResource(file)); + body.add(V3Fields.BITS, new FileSystemResource(file)); RestClient uploadClient = resolveUploadClient(uploadTimeout); uploadClient.post() @@ -158,7 +160,7 @@ private RestClient resolveUploadClient(Duration uploadTimeout) { private CloudPackage createBitsPackage(UUID applicationGuid) { V3Package created = client.postForObject(CloudControllerV3Endpoints.PACKAGES, - Map.of(V3Fields.TYPE, "bits", V3Fields.RELATIONSHIPS, + Map.of(V3Fields.TYPE, V3Fields.BITS, V3Fields.RELATIONSHIPS, applicationRelationship(applicationGuid)), V3Package.class); @@ -175,7 +177,7 @@ private UUID getRequiredApplicationGuid(String applicationName) { if (apps.isEmpty() || apps.getFirst() .guid() == null) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), MessageFormat.format(Messages.APPLICATION_0_NOT_FOUND, applicationName)); } @@ -188,25 +190,25 @@ private void processAsyncUploadInBackground(CloudPackage cloudPackage, UploadSta } private void processAsyncUpload(CloudPackage cloudPackage, UploadStatusCallback callback) { - while (true) { + boolean keepPolling = true; + + while (keepPolling) { Upload upload = getUploadStatus(cloudPackage.getGuid()); Status uploadStatus = upload.getStatus(); boolean unsubscribe = callback.onProgress(uploadStatus.toString()); if (unsubscribe || OperationsUtil.isUploadReady(uploadStatus)) { - return; - } - - if (OperationsUtil.hasUploadFailed(uploadStatus)) { + keepPolling = false; + } else if (OperationsUtil.hasUploadFailed(uploadStatus)) { callback.onError(upload.getErrorDetails() .getDescription()); - return; - } - - try { - MiscUtil.sleep(Constants.PACKAGE_UPLOAD_JOB_POLLING_PERIOD); - } catch (IllegalStateException e) { - return; + keepPolling = false; + } else { + try { + MiscUtil.sleep(Constants.PACKAGE_UPLOAD_JOB_POLLING_PERIOD); + } catch (IllegalStateException e) { + keepPolling = false; + } } } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3Operations.java index 6685fa5b46..2d4a680215 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3Operations.java @@ -7,7 +7,6 @@ import java.util.Map; import java.util.UUID; -import org.cloudfoundry.multiapps.controller.Constants; import org.cloudfoundry.multiapps.controller.Messages; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; @@ -17,6 +16,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableInstancesInfo; import org.cloudfoundry.multiapps.controller.client.facade.domain.InstancesInfo; import org.cloudfoundry.multiapps.controller.client.facade.domain.Staging; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3AppFeature; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Droplet; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Fields; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3GuidReference; @@ -29,7 +29,7 @@ public class ProcessesV3Operations { - private static final ParameterizedTypeReference> APP_FEATURE_PAGE = new ParameterizedTypeReference<>() { + private static final ParameterizedTypeReference> APP_FEATURE_PAGE = new ParameterizedTypeReference<>() { }; private static final ParameterizedTypeReference> APP_PAGE = new ParameterizedTypeReference<>() { @@ -46,7 +46,8 @@ public ProcessesV3Operations(CloudControllerV3Client client, CloudSpace targetSp public CloudProcess getApplicationProcess(UUID applicationGuid) { V3Process applicationProcess = client.get( - CloudControllerV3Endpoints.APPS + "/" + applicationGuid + CloudControllerV3Endpoints.PROCESSES + Constants.WEB_PROCESS_TYPE, + V3Uri.path(CloudControllerV3Endpoints.APP_WEB_PROCESS, applicationGuid) + .build(), V3Process.class); return applicationProcess == null ? null : V3ProcessMapper.toCloudProcess(applicationProcess); @@ -64,13 +65,12 @@ public InstancesInfo getApplicationInstances(CloudApplication application) { } public InstancesInfo getApplicationInstances(UUID applicationGuid) { - V3Process.V3ProcessStats stats = client.get( - CloudControllerV3Endpoints.APPS + "/" + applicationGuid + CloudControllerV3Endpoints.PROCESSES + Constants.WEB_PROCESS_TYPE - + "/stats", - V3Process.V3ProcessStats.class); + V3Process.V3ProcessStats stats = client.get(V3Uri.path(CloudControllerV3Endpoints.APP_WEB_PROCESS_STATS, applicationGuid) + .build(), + V3Process.V3ProcessStats.class); if (stats == null) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), MessageFormat.format(Messages.STATISTICS_FOR_APPLICATION_INSTANCES_WITH_GUID_0_NOT_FOUND, applicationGuid)); } @@ -78,25 +78,14 @@ public InstancesInfo getApplicationInstances(UUID applicationGuid) { return V3InstancesInfoMapper.toInstancesInfo(stats); } - public boolean getApplicationSshEnabled(UUID applicationGuid) { - V3Process.V3SshEnabled sshEnabled = client.get(CloudControllerV3Endpoints.APPS + "/" + applicationGuid + "/ssh_enabled", - V3Process.V3SshEnabled.class); - - if (sshEnabled == null || sshEnabled.enabled() == null) { - return false; - } - - return sshEnabled.enabled(); - } - public Map getApplicationFeatures(UUID applicationGuid) { - List applicationFeatures = client.list(CloudControllerV3Endpoints.APPS + "/" + applicationGuid + "/features" - + CloudControllerV3Endpoints.QUERY_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE, - APP_FEATURE_PAGE); + List applicationFeatures = client.list(V3Uri.path(CloudControllerV3Endpoints.APP_FEATURES, applicationGuid) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(), + APP_FEATURE_PAGE); Map result = new HashMap<>(); - for (V3Process.V3AppFeature feature : applicationFeatures) { + for (V3AppFeature feature : applicationFeatures) { result.put(feature.name(), feature.enabled()); } @@ -104,11 +93,12 @@ public Map getApplicationFeatures(UUID applicationGuid) { } public DropletInfo getCurrentDropletForApplication(UUID applicationGuid) { - V3Droplet currentDroplet = client.get(CloudControllerV3Endpoints.APPS + "/" + applicationGuid + "/droplets/current", + V3Droplet currentDroplet = client.get(V3Uri.path(CloudControllerV3Endpoints.APP_DROPLET_CURRENT, applicationGuid) + .build(), V3Droplet.class); if (currentDroplet == null) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), MessageFormat.format(Messages.APPLICATION_WITH_GUID_0_DOES_NOT_HAVE_A_DROPLET, applicationGuid)); } @@ -129,11 +119,12 @@ private void updateApplicationProcess(UUID applicationGuid, Staging staging) { .forEach((featureName, enabled) -> updateAppFeature(applicationGuid, featureName, enabled)); V3Process process = client.get( - CloudControllerV3Endpoints.APPS + "/" + applicationGuid + CloudControllerV3Endpoints.PROCESSES + Constants.WEB_PROCESS_TYPE, + V3Uri.path(CloudControllerV3Endpoints.APP_WEB_PROCESS, applicationGuid) + .build(), V3Process.class); if (process == null) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), MessageFormat.format(Messages.APPLICATION_PROCESS_FOR_APPLICATION_WITH_GUID_0_NOT_FOUND, applicationGuid)); } @@ -153,7 +144,7 @@ private UUID getRequiredApplicationGuid(String applicationName) { List apps = client.list(applicationsQuery, APP_PAGE); if (apps.isEmpty()) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), MessageFormat.format(Messages.APPLICATION_0_NOT_FOUND, applicationName)); } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RolesV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RolesV3Operations.java index e4170ad8c5..4aece63b19 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RolesV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RolesV3Operations.java @@ -7,6 +7,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.UserRole; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3QueryParameters; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Role; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3RoleMapper; import org.springframework.core.ParameterizedTypeReference; @@ -23,9 +24,11 @@ public RolesV3Operations(CloudControllerV3Client client) { } public Set getUserRolesBySpaceAndUser(UUID spaceGuid, UUID userGuid) { - String uri = CloudControllerV3Endpoints.ROLES + CloudControllerV3Endpoints.QUERY_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE + CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS + spaceGuid - + CloudControllerV3Endpoints.AMPERSAND_USER_GUIDS + userGuid; + String uri = V3Uri.path(CloudControllerV3Endpoints.ROLES) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .query(V3QueryParameters.SPACE_GUIDS, spaceGuid) + .query(V3QueryParameters.USER_GUIDS, userGuid) + .build(); return client.list(uri, ROLE_PAGE) .stream() diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3Operations.java index 481dca4e3b..832c7adbe6 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3Operations.java @@ -17,6 +17,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Fields; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3GuidReference; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3QueryParameters; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Route; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3RouteMapper; import org.springframework.core.ParameterizedTypeReference; @@ -56,7 +57,7 @@ public void deleteRoute(String host, String domainName, String path) { UUID routeGuid = getRouteGuid(getRequiredDomainGuid(domainName), host, path); if (routeGuid == null) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), MessageFormat.format(Messages.HOST_0_NOT_FOUND_FOR_DOMAIN_1, host, domainName)); } @@ -77,21 +78,23 @@ public List getRoutes(String domainName) { } public List getApplicationRoutes(UUID applicationGuid) { - return listRoutes(CloudControllerV3Endpoints.APPS + "/" + applicationGuid + "/routes" + CloudControllerV3Endpoints.QUERY_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE).stream() - .map(route -> V3RouteMapper.toCloudRoute( - route, applicationGuid)) - .toList(); + return listRoutes(V3Uri.path(CloudControllerV3Endpoints.APP_ROUTES, applicationGuid) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build()).stream() + .map(route -> V3RouteMapper.toCloudRoute( + route, applicationGuid)) + .toList(); } public void updateApplicationRoutes(String applicationName, Set updatedRoutes) { UUID applicationGuid = getRequiredApplicationGuid(applicationName); List appRoutes = listRoutes( - CloudControllerV3Endpoints.APPS + "/" + applicationGuid + "/routes" + CloudControllerV3Endpoints.QUERY_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE).stream() - .map(V3RouteMapper::toCloudRoute) - .toList(); + V3Uri.path(CloudControllerV3Endpoints.APP_ROUTES, applicationGuid) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build()).stream() + .map(V3RouteMapper::toCloudRoute) + .toList(); Set outdatedRoutes = OperationsUtil.getOutdatedRoutes(applicationGuid, appRoutes, updatedRoutes); Set newRoutes = OperationsUtil.getNewRoutes(applicationGuid, appRoutes, updatedRoutes); @@ -196,7 +199,7 @@ private UUID getRequiredDomainGuid(String name) { UUID guid = findDomainGuidByName(name); if (guid == null) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), MessageFormat.format(Messages.DOMAIN_0_NOT_FOUND, name)); } @@ -204,9 +207,10 @@ private UUID getRequiredDomainGuid(String name) { } private UUID findDomainGuidByName(String name) { - List domains = client.list(CloudControllerV3Endpoints.DOMAINS + CloudControllerV3Endpoints.QUERY_NAMES + name - + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE, + List domains = client.list(V3Uri.path(CloudControllerV3Endpoints.DOMAINS) + .query(V3QueryParameters.NAMES, name) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(), DOMAIN_PAGE); if (CollectionUtils.isEmpty(domains)) { @@ -228,9 +232,10 @@ private Map getDomainsFromRoutes(Set routes) { return Map.of(); } - String names = String.join(",", domainNames); - return client.list(CloudControllerV3Endpoints.DOMAINS + CloudControllerV3Endpoints.QUERY_NAMES + names - + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE, DOMAIN_PAGE) + return client.list(V3Uri.path(CloudControllerV3Endpoints.DOMAINS) + .query(V3QueryParameters.NAMES, domainNames) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(), DOMAIN_PAGE) .stream() .collect(Collectors.toMap(V3Domain::name, domain -> UUID.fromString(domain.guid()))); } @@ -241,7 +246,7 @@ private UUID getRequiredApplicationGuid(String applicationName) { APP_PAGE); if (CollectionUtils.isEmpty(apps)) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), MessageFormat.format(Messages.APPLICATION_0_NOT_FOUND, applicationName)); } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3Operations.java index fe9d99d2a8..e0fe99408b 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3Operations.java @@ -2,7 +2,6 @@ import java.text.MessageFormat; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -17,6 +16,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Fields; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3GuidReference; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3QueryParameters; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceBinding; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceBindingMapper; import org.springframework.core.ParameterizedTypeReference; @@ -64,8 +64,10 @@ public List unbindServiceInstance(String applicationName, String service } public CloudServiceBinding getServiceBinding(UUID serviceBindingGuid) { - String uri = CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS + CloudControllerV3Endpoints.QUERY_GUIDS + serviceBindingGuid - + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; + String uri = V3Uri.path(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS) + .query(V3QueryParameters.GUIDS, serviceBindingGuid) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(); return client.list(uri, BINDING_PAGE) .stream() @@ -75,36 +77,42 @@ public CloudServiceBinding getServiceBinding(UUID serviceBindingGuid) { } public List getServiceAppBindings(UUID serviceInstanceGuid) { - String uri = CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS + CloudControllerV3Endpoints.QUERY_SERVICE_INSTANCE_GUIDS - + serviceInstanceGuid + CloudControllerV3Endpoints.AMPERSAND_TYPE + "app" + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; + String uri = V3Uri.path(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS) + .query(V3QueryParameters.SERVICE_INSTANCE_GUIDS, serviceInstanceGuid) + .query(V3QueryParameters.TYPE, V3Fields.APP) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(); return listBindings(uri); } public List getAppBindings(UUID applicationGuid) { - String uri = CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS + CloudControllerV3Endpoints.QUERY_APP_GUIDS + applicationGuid - + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; + String uri = V3Uri.path(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS) + .query(V3QueryParameters.APP_GUIDS, applicationGuid) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(); return listBindings(uri); } public List getServiceBindingsForApplication(UUID applicationId, UUID serviceInstanceGuid) { - String uri = CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS + CloudControllerV3Endpoints.QUERY_APP_GUIDS + applicationId - + CloudControllerV3Endpoints.AMPERSAND_SERVICE_INSTANCE_GUIDS + serviceInstanceGuid - + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; + String uri = V3Uri.path(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS) + .query(V3QueryParameters.APP_GUIDS, applicationId) + .query(V3QueryParameters.SERVICE_INSTANCE_GUIDS, serviceInstanceGuid) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(); return listBindings(uri); } public Map getServiceBindingParameters(UUID guid) { Map parameters = MiscUtil.cast( - client.get(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS + "/" + guid + "/parameters", + client.get(V3Uri.path(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDING_PARAMETERS, guid) + .build(), Map.class)); if (parameters == null) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), MessageFormat.format(Messages.PARAMETERS_OF_SERVICE_BINDING_WITH_GUID_0_NOT_FOUND, guid)); } @@ -128,7 +136,7 @@ public List unbindServiceInstance(UUID applicationGuid, UUID serviceInst List serviceBindingGuids = getServiceBindingGuids(applicationGuid, serviceInstanceGuid); if (serviceBindingGuids.isEmpty()) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, MessageFormat.format( + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), MessageFormat.format( Messages.SERVICE_BINDING_BETWEEN_SERVICE_WITH_GUID_0_AND_APPLICATION_WITH_GUID_1_NOT_FOUND, serviceInstanceGuid, applicationGuid)); } @@ -137,10 +145,11 @@ public List unbindServiceInstance(UUID applicationGuid, UUID serviceInst } private List getServiceBindingGuids(UUID applicationGuid, UUID serviceInstanceGuid) { - String uri = CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS + CloudControllerV3Endpoints.QUERY_APP_GUIDS + applicationGuid - + CloudControllerV3Endpoints.AMPERSAND_SERVICE_INSTANCE_GUIDS + serviceInstanceGuid - + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; + String uri = V3Uri.path(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS) + .query(V3QueryParameters.APP_GUIDS, applicationGuid) + .query(V3QueryParameters.SERVICE_INSTANCE_GUIDS, serviceInstanceGuid) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(); return client.list(uri, BINDING_PAGE) .stream() @@ -176,7 +185,7 @@ private UUID getRequiredApplicationGuid(String applicationName) { List apps = client.list(applicationsQuery, GUID_REFERENCE_PAGE); if (apps.isEmpty()) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), MessageFormat.format(Messages.APPLICATION_0_NOT_FOUND, applicationName)); } @@ -189,7 +198,7 @@ private UUID getRequiredServiceInstanceGuid(String name) { List instances = client.list(serviceInstancesQuery, GUID_REFERENCE_PAGE); if (instances.isEmpty()) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), MessageFormat.format(Messages.SERVICE_INSTANCE_0_NOT_FOUND, name)); } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBrokersV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBrokersV3Operations.java index 8eb573f6a4..e57323eb02 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBrokersV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBrokersV3Operations.java @@ -13,6 +13,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Fields; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3GuidReference; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3QueryParameters; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceBroker; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceBrokerMapper; import org.springframework.core.ParameterizedTypeReference; @@ -29,6 +30,7 @@ public class ServiceBrokersV3Operations { }; private final CloudControllerV3Client client; + private final CloudSpace targetSpace; public ServiceBrokersV3Operations(CloudControllerV3Client client, CloudSpace targetSpace) { @@ -65,7 +67,7 @@ public CloudServiceBroker getServiceBroker(String name, boolean required) { CloudServiceBroker serviceBroker = findServiceBrokerByName(name); if (serviceBroker == null && required) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), MessageFormat.format(Messages.SERVICE_BROKER_0_NOT_FOUND, name)); } @@ -73,8 +75,9 @@ public CloudServiceBroker getServiceBroker(String name, boolean required) { } public List getServiceBrokers() { - return client.list(CloudControllerV3Endpoints.SERVICE_BROKERS + CloudControllerV3Endpoints.QUERY_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE, BROKER_PAGE) + return client.list(V3Uri.path(CloudControllerV3Endpoints.SERVICE_BROKERS) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(), BROKER_PAGE) .stream() .map(V3ServiceBrokerMapper::toCloudServiceBroker) .toList(); @@ -109,9 +112,10 @@ public void updateServicePlanVisibilityForBroker(String name, ServicePlanVisibil private CloudServiceBroker findServiceBrokerByName(String name) { return client.list( - CloudControllerV3Endpoints.SERVICE_BROKERS + CloudControllerV3Endpoints.QUERY_NAMES + name - + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE, BROKER_PAGE) + V3Uri.path(CloudControllerV3Endpoints.SERVICE_BROKERS) + .query(V3QueryParameters.NAMES, name) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(), BROKER_PAGE) .stream() .findFirst() .map(V3ServiceBrokerMapper::toCloudServiceBroker) @@ -126,10 +130,11 @@ private List findServicePlanGuidsByBrokerGuid(UUID brokerGuid) { } private List findServiceOfferingGuidsByBrokerGuid(UUID brokerGuid) { - String uri = CloudControllerV3Endpoints.SERVICE_OFFERINGS + CloudControllerV3Endpoints.QUERY_SERVICE_BROKER_GUIDS + brokerGuid - + CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS + OperationsUtil.getGuid(targetSpace) - + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; + String uri = V3Uri.path(CloudControllerV3Endpoints.SERVICE_OFFERINGS) + .query(V3QueryParameters.SERVICE_BROKER_GUIDS, brokerGuid) + .query(V3QueryParameters.SPACE_GUIDS, OperationsUtil.getGuid(targetSpace)) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(); return client.list(uri, GUID_REFERENCE_PAGE) .stream() @@ -140,8 +145,10 @@ private List findServiceOfferingGuidsByBrokerGuid(UUID brokerGuid) { private List findServicePlanGuidsByOfferingGuid(UUID serviceOfferingGuid) { String uri = - CloudControllerV3Endpoints.SERVICE_PLANS + CloudControllerV3Endpoints.QUERY_SERVICE_OFFERING_GUIDS + serviceOfferingGuid - + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; + V3Uri.path(CloudControllerV3Endpoints.SERVICE_PLANS) + .query(V3QueryParameters.SERVICE_OFFERING_GUIDS, serviceOfferingGuid) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(); return client.list(uri, GUID_REFERENCE_PAGE) .stream() diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3Operations.java index 340f996b50..33a483cd5c 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3Operations.java @@ -15,6 +15,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Fields; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3GuidReference; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3QueryParameters; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceInstance; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceInstanceMapper; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceOffering; @@ -54,7 +55,9 @@ public void createServiceInstance(CloudServiceInstance serviceInstance) { public void createUserProvidedServiceInstance(CloudServiceInstance serviceInstance) { assertSpaceProvided("create service instance"); Assert.notNull(serviceInstance, "Service instance must not be null."); - String syslogDrainUrl = StringUtils.hasText(serviceInstance.getSyslogDrainUrl()) ? serviceInstance.getSyslogDrainUrl() : ""; + String syslogDrainUrl = StringUtils.hasText(serviceInstance.getSyslogDrainUrl()) + ? serviceInstance.getSyslogDrainUrl() + : Constants.EMPTY_STRING; Map body = OperationsUtil.buildCreateUserProvidedServiceInstance(serviceInstance, syslogDrainUrl, targetSpace); @@ -74,7 +77,7 @@ public UUID getRequiredServiceInstanceGuid(String name) { V3ServiceInstance resource = findServiceInstanceResourceByName(name); if (resource == null) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), MessageFormat.format(Messages.SERVICE_INSTANCE_0_NOT_FOUND, name)); } @@ -92,7 +95,8 @@ public CloudServiceInstance getServiceInstance(String serviceInstanceName, boole } public String getServiceInstanceName(UUID serviceInstanceGuid) { - V3ServiceInstance serviceInstance = client.get(CloudControllerV3Endpoints.SERVICE_INSTANCES + "/" + serviceInstanceGuid, + V3ServiceInstance serviceInstance = client.get(V3Uri.path(CloudControllerV3Endpoints.SERVICE_INSTANCE_BY_GUID, serviceInstanceGuid) + .build(), V3ServiceInstance.class); return serviceInstance == null ? null : serviceInstance.name(); @@ -112,10 +116,12 @@ public CloudServiceInstance getServiceInstanceWithoutAuxiliaryContent(String ser } public Map getServiceInstanceParameters(UUID guid) { - Map parameters = client.get(CloudControllerV3Endpoints.SERVICE_INSTANCES + "/" + guid + "/parameters", Map.class); + Map parameters = client.get(V3Uri.path(CloudControllerV3Endpoints.SERVICE_INSTANCE_PARAMETERS, guid) + .build(), + Map.class); if (parameters == null) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), MessageFormat.format(Messages.PARAMETERS_OF_SERVICE_INSTANCE_WITH_GUID_0_NOT_FOUND, guid)); } @@ -124,10 +130,12 @@ public Map getServiceInstanceParameters(UUID guid) { } public Map getUserProvidedServiceInstanceParameters(UUID guid) { - Map credentials = client.get(CloudControllerV3Endpoints.SERVICE_INSTANCES + "/" + guid + "/credentials", Map.class); + Map credentials = client.get(V3Uri.path(CloudControllerV3Endpoints.SERVICE_INSTANCE_CREDENTIALS, guid) + .build(), + Map.class); if (credentials == null) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), MessageFormat.format(Messages.CREDENTIALS_OF_SERVICE_INSTANCE_WITH_GUID_0_NOT_FOUND, guid)); } @@ -139,10 +147,11 @@ public List getServiceInstancesWithoutAuxiliaryContentByNa List allServiceInstances = new ArrayList<>(); for (List batch : OperationsUtil.toBatches(names, Constants.MAX_CHAR_LENGTH_FOR_PARAMS_IN_REQUEST)) { - String uri = CloudControllerV3Endpoints.SERVICE_INSTANCES + CloudControllerV3Endpoints.QUERY_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE + CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS - + OperationsUtil.getGuid(targetSpace) - + CloudControllerV3Endpoints.AMPERSAND_NAMES + String.join(",", batch); + String uri = V3Uri.path(CloudControllerV3Endpoints.SERVICE_INSTANCES) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .query(V3QueryParameters.SPACE_GUIDS, OperationsUtil.getGuid(targetSpace)) + .query(V3QueryParameters.NAMES, batch) + .build(); client.list(uri, SERVICE_INSTANCE_LIST_TYPE) .stream() @@ -154,19 +163,21 @@ public List getServiceInstancesWithoutAuxiliaryContentByNa } public List getServiceInstancesByMetadataLabelSelector(String labelSelector) { - String uri = CloudControllerV3Endpoints.SERVICE_INSTANCES + CloudControllerV3Endpoints.QUERY_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE + CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS - + OperationsUtil.getGuid(targetSpace) - + CloudControllerV3Endpoints.AMPERSAND_LABEL_SELECTOR + labelSelector; + String uri = V3Uri.path(CloudControllerV3Endpoints.SERVICE_INSTANCES) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .query(V3QueryParameters.SPACE_GUIDS, OperationsUtil.getGuid(targetSpace)) + .query(V3QueryParameters.LABEL_SELECTOR, labelSelector) + .build(); return ReactiveFanOut.mapConcurrently(client.list(uri, SERVICE_INSTANCE_LIST_TYPE), this::mapWithSupportingContent); } public List getServiceInstancesWithoutAuxiliaryContentByMetadataLabelSelector(String labelSelector) { - String uri = CloudControllerV3Endpoints.SERVICE_INSTANCES + CloudControllerV3Endpoints.QUERY_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE + CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS - + OperationsUtil.getGuid(targetSpace) - + CloudControllerV3Endpoints.AMPERSAND_LABEL_SELECTOR + labelSelector; + String uri = V3Uri.path(CloudControllerV3Endpoints.SERVICE_INSTANCES) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .query(V3QueryParameters.SPACE_GUIDS, OperationsUtil.getGuid(targetSpace)) + .query(V3QueryParameters.LABEL_SELECTOR, labelSelector) + .build(); return client.list(uri, SERVICE_INSTANCE_LIST_TYPE) .stream() @@ -205,7 +216,7 @@ public void updateServiceSyslogDrainUrl(String serviceName, String syslogDrainUr return; } - String updatedSyslogDrain = StringUtils.hasText(syslogDrainUrl) ? syslogDrainUrl : ""; + String updatedSyslogDrain = StringUtils.hasText(syslogDrainUrl) ? syslogDrainUrl : Constants.EMPTY_STRING; patchServiceInstance(service.getGuid(), Map.of(V3Fields.SYSLOG_DRAIN_URL, updatedSyslogDrain)); } @@ -217,7 +228,7 @@ public void updateServiceInstanceMetadata(UUID guid, Metadata metadata) { private CloudServiceInstance getServiceInstanceIfRequired(String serviceInstanceName, CloudServiceInstance serviceInstance, boolean required) { if (serviceInstance == null && required) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), MessageFormat.format(Messages.SERVICE_INSTANCE_0_NOT_FOUND, serviceInstanceName)); } @@ -249,10 +260,11 @@ private CloudServiceInstance mapWithSupportingContent(V3ServiceInstance resource } private V3ServiceInstance findServiceInstanceResourceByName(String name) { - String uri = CloudControllerV3Endpoints.SERVICE_INSTANCES + CloudControllerV3Endpoints.QUERY_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE + CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS - + OperationsUtil.getGuid(targetSpace) - + CloudControllerV3Endpoints.AMPERSAND_NAMES + name; + String uri = V3Uri.path(CloudControllerV3Endpoints.SERVICE_INSTANCES) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .query(V3QueryParameters.SPACE_GUIDS, OperationsUtil.getGuid(targetSpace)) + .query(V3QueryParameters.NAMES, name) + .build(); return client.list(uri, SERVICE_INSTANCE_LIST_TYPE) .stream() @@ -280,7 +292,9 @@ private ServicePlanNames resolvePlanAndOfferingNames(String servicePlanGuid, Str private V3ServicePlan getServicePlanForServiceInstance(String servicePlanGuid, String serviceInstanceName) { try { - return client.get(CloudControllerV3Endpoints.SERVICE_PLANS + "/" + servicePlanGuid, V3ServicePlan.class); + return client.get(V3Uri.path(CloudControllerV3Endpoints.SERVICE_PLAN_BY_GUID, servicePlanGuid) + .build(), + V3ServicePlan.class); } catch (CloudOperationException e) { throw OperationsUtil.determineNameResolutionError(e, MessageFormat.format( @@ -293,7 +307,9 @@ private V3ServicePlan getServicePlanForServiceInstance(String servicePlanGuid, S private V3ServiceOffering getServiceOfferingForNameResolution(String offeringGuid) { try { - return client.get(CloudControllerV3Endpoints.SERVICE_OFFERINGS + "/" + offeringGuid, V3ServiceOffering.class); + return client.get(V3Uri.path(CloudControllerV3Endpoints.SERVICE_OFFERING_BY_GUID, offeringGuid) + .build(), + V3ServiceOffering.class); } catch (CloudOperationException e) { throw OperationsUtil.determineNameResolutionError(e, MessageFormat.format(Messages.SERVICE_OFFERING_WITH_GUID_0_IS_NOT_AVAILABLE, @@ -307,9 +323,11 @@ private UUID findPlanGuidForService(CloudServiceInstance service, String planNam List offeringGuids = findServiceOfferingGuids(service.getLabel(), service.getBroker()); for (String offeringGuid : offeringGuids) { - String uri = CloudControllerV3Endpoints.SERVICE_PLANS + CloudControllerV3Endpoints.QUERY_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE + CloudControllerV3Endpoints.AMPERSAND_SERVICE_OFFERING_GUIDS + offeringGuid - + CloudControllerV3Endpoints.AMPERSAND_NAMES + planName; + String uri = V3Uri.path(CloudControllerV3Endpoints.SERVICE_PLANS) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .query(V3QueryParameters.SERVICE_OFFERING_GUIDS, offeringGuid) + .query(V3QueryParameters.NAMES, planName) + .build(); V3GuidReference plan = client.list(uri, GUID_REFERENCE_PAGE) .stream() @@ -321,30 +339,19 @@ private UUID findPlanGuidForService(CloudServiceInstance service, String planNam } } - throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), MessageFormat.format(Messages.SERVICE_PLAN_0_NOT_FOUND, planName)); } private List findServiceOfferingGuids(String label, String broker) { - StringBuilder uri = new StringBuilder( - CloudControllerV3Endpoints.SERVICE_OFFERINGS + CloudControllerV3Endpoints.QUERY_PER_PAGE).append( - CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) - .append( - CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) - .append( - OperationsUtil.getGuid( - targetSpace)) - .append( - CloudControllerV3Endpoints.AMPERSAND_NAMES) - .append( - label); - - if (StringUtils.hasText(broker)) { - uri.append(CloudControllerV3Endpoints.AMPERSAND_SERVICE_BROKER_NAMES) - .append(broker); - } - - return client.list(uri.toString(), GUID_REFERENCE_PAGE) + String uri = V3Uri.path(CloudControllerV3Endpoints.SERVICE_OFFERINGS) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .query(V3QueryParameters.SPACE_GUIDS, OperationsUtil.getGuid(targetSpace)) + .query(V3QueryParameters.NAMES, label) + .query(V3QueryParameters.SERVICE_BROKER_NAMES, StringUtils.hasText(broker) ? broker : null) + .build(); + + return client.list(uri, GUID_REFERENCE_PAGE) .stream() .map(V3GuidReference::guid) .toList(); diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3Operations.java index b93f4f74fa..b87bb38e01 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3Operations.java @@ -12,7 +12,9 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Fields; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3QueryParameters; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceBinding; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceKeyDetails; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceKeyMapper; @@ -85,10 +87,12 @@ private ResponseEntity postServiceKey(String name, Map par } private V3ServiceBinding getServiceKeyResourceByNameAndServiceInstanceGuid(String name, UUID serviceInstanceGuid) { - String query = CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS + CloudControllerV3Endpoints.QUERY_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE + CloudControllerV3Endpoints.AMPERSAND_TYPE + "key" - + CloudControllerV3Endpoints.AMPERSAND_SERVICE_INSTANCE_GUIDS + serviceInstanceGuid + CloudControllerV3Endpoints.AMPERSAND_NAMES - + name; + String query = V3Uri.path(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .query(V3QueryParameters.TYPE, V3Fields.KEY) + .query(V3QueryParameters.SERVICE_INSTANCE_GUIDS, serviceInstanceGuid) + .query(V3QueryParameters.NAMES, name) + .build(); List keys = client.list(query, SERVICE_KEY_PAGE); @@ -96,16 +100,19 @@ private V3ServiceBinding getServiceKeyResourceByNameAndServiceInstanceGuid(Strin } private List listServiceKeyResources(UUID serviceInstanceGuid) { - String query = CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS + CloudControllerV3Endpoints.QUERY_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE + CloudControllerV3Endpoints.AMPERSAND_TYPE + "key" - + CloudControllerV3Endpoints.AMPERSAND_SERVICE_INSTANCE_GUIDS + serviceInstanceGuid; + String query = V3Uri.path(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .query(V3QueryParameters.TYPE, V3Fields.KEY) + .query(V3QueryParameters.SERVICE_INSTANCE_GUIDS, serviceInstanceGuid) + .build(); return client.list(query, SERVICE_KEY_PAGE); } private Map getServiceKeyCredentials(String keyGuid) { - Optional details = client.getOptional(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDINGS + "/" + keyGuid - + "/details", + Optional details = client.getOptional(V3Uri.path(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDING_DETAILS, + keyGuid) + .build(), V3ServiceKeyDetails.class); return details.map(V3ServiceKeyDetails::credentials) diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3Operations.java index 6c7a9d740c..3c8a6c8840 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3Operations.java @@ -6,6 +6,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServicePlan; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3QueryParameters; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceOffering; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceOfferingMapper; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServicePlan; @@ -16,10 +17,12 @@ public class ServiceOfferingsV3Operations { private static final ParameterizedTypeReference> OFFERING_PAGE = new ParameterizedTypeReference<>() { }; + private static final ParameterizedTypeReference> PLAN_PAGE = new ParameterizedTypeReference<>() { }; private final CloudControllerV3Client client; + private final CloudSpace targetSpace; public ServiceOfferingsV3Operations(CloudControllerV3Client client, CloudSpace targetSpace) { @@ -34,15 +37,12 @@ public List getServiceOfferings() { } private List getServiceOfferingResources() { - StringBuilder query = new StringBuilder(CloudControllerV3Endpoints.SERVICE_OFFERINGS + CloudControllerV3Endpoints.QUERY_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE); - - if (targetSpace != null && targetSpace.getGuid() != null) { - query.append(CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) - .append(targetSpace.getGuid()); - } + String uri = V3Uri.path(CloudControllerV3Endpoints.SERVICE_OFFERINGS) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .query(V3QueryParameters.SPACE_GUIDS, OperationsUtil.getGuid(targetSpace)) + .build(); - return client.list(query.toString(), OFFERING_PAGE); + return client.list(uri, OFFERING_PAGE); } private CloudServiceOffering toCloudServiceOfferingWithPlans(V3ServiceOffering serviceOffering) { @@ -52,9 +52,10 @@ private CloudServiceOffering toCloudServiceOfferingWithPlans(V3ServiceOffering s } private List getServicePlans(String serviceOfferingGuid) { - String uri = - CloudControllerV3Endpoints.SERVICE_PLANS + CloudControllerV3Endpoints.QUERY_SERVICE_OFFERING_GUIDS + serviceOfferingGuid - + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; + String uri = V3Uri.path(CloudControllerV3Endpoints.SERVICE_PLANS) + .query(V3QueryParameters.SERVICE_OFFERING_GUIDS, serviceOfferingGuid) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(); return client.list(uri, PLAN_PAGE) .stream() diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3Operations.java index e88ddea48f..35d902cc36 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3Operations.java @@ -4,7 +4,6 @@ import java.util.List; import java.util.Map; import java.util.UUID; -import java.util.stream.Collectors; import org.cloudfoundry.multiapps.controller.Messages; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; @@ -13,6 +12,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Fields; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3GuidReference; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3QueryParameters; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpStatus; @@ -40,14 +40,16 @@ public void updateServicePlanVisibilityForBroker(String name, ServicePlanVisibil } private UUID getRequiredServiceBrokerGuid(String name) { - String uri = CloudControllerV3Endpoints.SERVICE_BROKERS + CloudControllerV3Endpoints.QUERY_NAMES + name - + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; + String uri = V3Uri.path(CloudControllerV3Endpoints.SERVICE_BROKERS) + .query(V3QueryParameters.NAMES, name) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(); return client.list(uri, GUID_REFERENCE_PAGE) .stream() .findFirst() .map(broker -> UUID.fromString(broker.guid())) - .orElseThrow(() -> new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + .orElseThrow(() -> new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), MessageFormat.format(Messages.SERVICE_BROKER_0_NOT_FOUND, name))); } @@ -57,12 +59,10 @@ private List findServicePlanGuidsByBrokerGuid(UUID brokerGuid) { return List.of(); } - String offeringGuidsFilter = offeredServicesGuids.stream() - .map(UUID::toString) - .collect(Collectors.joining(",")); - - String uri = CloudControllerV3Endpoints.SERVICE_PLANS + CloudControllerV3Endpoints.QUERY_SERVICE_OFFERING_GUIDS - + offeringGuidsFilter + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE; + String uri = V3Uri.path(CloudControllerV3Endpoints.SERVICE_PLANS) + .query(V3QueryParameters.SERVICE_OFFERING_GUIDS, offeredServicesGuids) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(); return client.list(uri, GUID_REFERENCE_PAGE) .stream() @@ -71,17 +71,13 @@ private List findServicePlanGuidsByBrokerGuid(UUID brokerGuid) { } private List findServiceOfferingGuidsByBrokerGuid(UUID brokerGuid) { - StringBuilder query = new StringBuilder(CloudControllerV3Endpoints.SERVICE_OFFERINGS - + CloudControllerV3Endpoints.QUERY_SERVICE_BROKER_GUIDS + brokerGuid - + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE); - - if (targetSpace != null && targetSpace.getGuid() != null) { - query.append(CloudControllerV3Endpoints.AMPERSAND_SPACE_GUIDS) - .append(targetSpace.getGuid()); - } + String uri = V3Uri.path(CloudControllerV3Endpoints.SERVICE_OFFERINGS) + .query(V3QueryParameters.SERVICE_BROKER_GUIDS, brokerGuid) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .query(V3QueryParameters.SPACE_GUIDS, OperationsUtil.getGuid(targetSpace)) + .build(); - return client.list(query.toString(), GUID_REFERENCE_PAGE) + return client.list(uri, GUID_REFERENCE_PAGE) .stream() .map(offering -> UUID.fromString(offering.guid())) .toList(); diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/StacksV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/StacksV3Operations.java index 2fef87b663..9e788e4302 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/StacksV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/StacksV3Operations.java @@ -7,6 +7,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudStack; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3QueryParameters; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Stack; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3StackMapper; import org.springframework.core.ParameterizedTypeReference; @@ -31,7 +32,7 @@ public CloudStack getStack(String name, boolean required) { CloudStack resultStack = findStackByName(name); if (resultStack == null && required) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), MessageFormat.format(Messages.STACK_0_NOT_FOUND, name)); } @@ -39,16 +40,19 @@ public CloudStack getStack(String name, boolean required) { } public List getStacks() { - return client.list(CloudControllerV3Endpoints.STACKS + CloudControllerV3Endpoints.QUERY_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE, STACK_PAGE) + return client.list(V3Uri.path(CloudControllerV3Endpoints.STACKS) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(), STACK_PAGE) .stream() .map(V3StackMapper::toCloudStack) .toList(); } private CloudStack findStackByName(String name) { - return client.list(CloudControllerV3Endpoints.STACKS + CloudControllerV3Endpoints.QUERY_NAMES + name - + CloudControllerV3Endpoints.AMPERSAND_PER_PAGE + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE, STACK_PAGE) + return client.list(V3Uri.path(CloudControllerV3Endpoints.STACKS) + .query(V3QueryParameters.NAMES, name) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .build(), STACK_PAGE) .stream() .map(V3StackMapper::toCloudStack) .findFirst() diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/TasksV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/TasksV3Operations.java index 8ac0c26308..587e1282ae 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/TasksV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/TasksV3Operations.java @@ -11,6 +11,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudTask; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3GuidReference; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; +import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3QueryParameters; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3Task; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3TaskMapper; import org.springframework.core.ParameterizedTypeReference; @@ -34,7 +35,8 @@ public TasksV3Operations(CloudControllerV3Client client, CloudSpace targetSpace) } public CloudTask getTask(UUID taskGuid) { - V3Task task = client.get(CloudControllerV3Endpoints.TASKS + "/" + taskGuid, V3Task.class); + V3Task task = client.get(V3Uri.path(CloudControllerV3Endpoints.TASK_BY_GUID, taskGuid) + .build(), V3Task.class); return task == null ? null : V3TaskMapper.toCloudTask(task); } @@ -42,8 +44,10 @@ public CloudTask getTask(UUID taskGuid) { public List getTasks(String applicationName) { UUID applicationGuid = getRequiredApplicationGuid(applicationName); - String query = CloudControllerV3Endpoints.TASKS + CloudControllerV3Endpoints.QUERY_PER_PAGE - + CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE + CloudControllerV3Endpoints.AMPERSAND_APP_GUIDS + applicationGuid; + String query = V3Uri.path(CloudControllerV3Endpoints.TASKS) + .perPage(CloudControllerV3Endpoints.DEFAULT_PAGE_SIZE) + .query(V3QueryParameters.APP_GUIDS, applicationGuid) + .build(); List tasks = client.list(query, TASK_PAGE); @@ -72,7 +76,7 @@ private UUID getRequiredApplicationGuid(String applicationName) { List apps = client.list(applicationsQuery, APP_PAGE); if (apps.isEmpty()) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, Messages.NOT_FOUND, + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), MessageFormat.format(Messages.APPLICATION_0_NOT_FOUND, applicationName)); } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/V3Uri.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/V3Uri.java new file mode 100644 index 0000000000..8a1f1ad14d --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/V3Uri.java @@ -0,0 +1,95 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +import org.cloudfoundry.multiapps.controller.Constants; +import org.cloudfoundry.multiapps.controller.client.facade.util.UriUtil; + +public final class V3Uri { + + private static final List CHARS_TO_ENCODE = List.of(Constants.COMMA); + + private final String path; + + private final List queryParameters = new ArrayList<>(); + + private V3Uri(String path) { + this.path = path; + } + + public static V3Uri path(String template, Object... args) { + return new V3Uri(fillPlaceholders(template, args)); + } + + public V3Uri perPage(int size) { + queryParameters.add(Constants.PER_PAGE + size); + return this; + } + + public V3Uri query(String key, Object value) { + if (value != null) { + queryParameters.add(key + Constants.EQUALS + value); + } + + return this; + } + + public V3Uri queryEncoded(String key, Object value) { + if (value != null) { + queryParameters.add(key + Constants.EQUALS + encodeCommas(String.valueOf(value))); + } + + return this; + } + + public V3Uri query(String key, Collection values) { + if (values != null && !values.isEmpty()) { + queryParameters.add(key + Constants.EQUALS + values.stream() + .map(String::valueOf) + .map(V3Uri::encodeCommas) + .collect(Collectors.joining(Constants.COMMA))); + } + + return this; + } + + public String build() { + if (queryParameters.isEmpty()) { + return path; + } + + return path + Constants.QUESTION_MARK + String.join(Constants.AMPERSAND, queryParameters); + } + + private static String encodeCommas(String value) { + return UriUtil.encodeChars(value, CHARS_TO_ENCODE); + } + + private static String fillPlaceholders(String template, Object... args) { + StringBuilder result = new StringBuilder(); + + int argumentIndex = 0; + int nextSearchStart = 0; + int openBraceIndex; + + while ((openBraceIndex = template.indexOf(Constants.OPEN_BRACE_CHAR, nextSearchStart)) >= 0) { + int closeBraceIndex = template.indexOf(Constants.CLOSE_BRACE_CHAR, openBraceIndex); + + if (closeBraceIndex < 0) { + break; + } + + result.append(template, nextSearchStart, openBraceIndex) + .append(args[argumentIndex++]); + + nextSearchStart = closeBraceIndex + 1; + } + + result.append(template, nextSearchStart, template.length()); + return result.toString(); + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/LogCacheFields.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/LogCacheFields.java new file mode 100644 index 0000000000..4779017247 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/LogCacheFields.java @@ -0,0 +1,17 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +public class LogCacheFields { + + public static final String ENVELOPES = "envelopes"; + public static final String BATCH = "batch"; + public static final String SOURCE_ID = "source_id"; + public static final String TIMESTAMP = "timestamp"; + public static final String TAGS = "tags"; + public static final String LOG = "log"; + public static final String PAYLOAD = "payload"; + public static final String TYPE = "type"; + + private LogCacheFields() { + } + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/LogCacheReadResponse.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/LogCacheReadResponse.java index 010e348dea..469c646463 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/LogCacheReadResponse.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/LogCacheReadResponse.java @@ -8,27 +8,27 @@ import com.fasterxml.jackson.annotation.JsonProperty; @JsonIgnoreProperties(ignoreUnknown = true) -public record LogCacheReadResponse(@JsonProperty("envelopes") EnvelopeBatch envelopes) { +public record LogCacheReadResponse(@JsonProperty(LogCacheFields.ENVELOPES) EnvelopeBatch envelopes) { public List batch() { if (envelopes == null || envelopes.batch() == null) { return Collections.emptyList(); } - + return envelopes.batch(); } @JsonIgnoreProperties(ignoreUnknown = true) - public record EnvelopeBatch(@JsonProperty("batch") List batch) { + public record EnvelopeBatch(@JsonProperty(LogCacheFields.BATCH) List batch) { } @JsonIgnoreProperties(ignoreUnknown = true) - public record Envelope(@JsonProperty("source_id") String sourceId, @JsonProperty("timestamp") Long timestamp, - @JsonProperty("tags") Map tags, @JsonProperty("log") Log log) { + public record Envelope(@JsonProperty(LogCacheFields.SOURCE_ID) String sourceId, @JsonProperty(LogCacheFields.TIMESTAMP) Long timestamp, + @JsonProperty(LogCacheFields.TAGS) Map tags, @JsonProperty(LogCacheFields.LOG) Log log) { } @JsonIgnoreProperties(ignoreUnknown = true) - public record Log(@JsonProperty("payload") String payload, @JsonProperty("type") String type) { + public record Log(@JsonProperty(LogCacheFields.PAYLOAD) String payload, @JsonProperty(LogCacheFields.TYPE) String type) { } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AppFeature.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AppFeature.java new file mode 100644 index 0000000000..1a535ac1a5 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AppFeature.java @@ -0,0 +1,12 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of a CF v3 app feature resource ({@code GET /v3/apps/{guid}/features}). App features belong to the + * application entity. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3AppFeature(@JsonProperty(V3Fields.NAME) String name, @JsonProperty(V3Fields.ENABLED) Boolean enabled) { +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Application.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Application.java index 2b91f846de..381c7320bf 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Application.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Application.java @@ -28,7 +28,7 @@ public record V3Lifecycle(@JsonProperty(V3Fields.TYPE) String type, @JsonPropert } @JsonIgnoreProperties(ignoreUnknown = true) - public record V3LifecycleData(@JsonProperty("buildpacks") List buildpacks, @JsonProperty("stack") String stack) { + public record V3LifecycleData(@JsonProperty(V3Fields.BUILDPACKS) List buildpacks, @JsonProperty(V3Fields.STACK) String stack) { } @JsonIgnoreProperties(ignoreUnknown = true) diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ApplicationMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ApplicationMapper.java index 71d0436615..be823aa926 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ApplicationMapper.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ApplicationMapper.java @@ -3,6 +3,7 @@ import java.util.List; import java.util.Map; +import org.cloudfoundry.multiapps.controller.Constants; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudApplication; @@ -12,9 +13,6 @@ public final class V3ApplicationMapper { - private static final String BUILDPACKS = "buildpacks"; - private static final String STACK = "stack"; - private V3ApplicationMapper() { } @@ -50,8 +48,8 @@ private static Map extractLifecycleData(V3Application.V3Lifecycl return Map.of(); } - return Map.of(BUILDPACKS, data.buildpacks() == null ? List.of() : data.buildpacks(), - STACK, data.stack() == null ? "" : data.stack()); + return Map.of(V3Fields.BUILDPACKS, data.buildpacks() == null ? List.of() : data.buildpacks(), V3Fields.STACK, + data.stack() == null ? Constants.EMPTY_STRING : data.stack()); } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AuditEvent.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AuditEvent.java index 0004909f3c..85e330b0ab 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AuditEvent.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3AuditEvent.java @@ -16,7 +16,7 @@ @JsonIgnoreProperties(ignoreUnknown = true) public record V3AuditEvent(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.CREATED_AT) String createdAt, @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, @JsonProperty(V3Fields.TYPE) String type, - @JsonProperty("actor") V3Participant actor, @JsonProperty("target") V3Participant target) { + @JsonProperty(V3Fields.ACTOR) V3Participant actor, @JsonProperty(V3Fields.TARGET) V3Participant target) { @JsonIgnoreProperties(ignoreUnknown = true) public record V3Participant(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.TYPE) String type, diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Build.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Build.java index 1c83866a49..5866cfa952 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Build.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Build.java @@ -11,14 +11,18 @@ * "state": "STAGING|STAGED|FAILED", "error": "...", * "created_by": { "guid": "...", "name": "..." }, * "package": { "guid": "..." }, - * "droplet": { "guid": "..." } } + * "droplet": { "guid": "..." }, + * "metadata": { "labels": {...}, "annotations": {...} }, + * "relationships": { "app": { "data": { "guid": "..." } } } } * */ @JsonIgnoreProperties(ignoreUnknown = true) public record V3Build(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.CREATED_AT) String createdAt, @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, @JsonProperty(V3Fields.STATE) String state, - @JsonProperty("error") String error, @JsonProperty("created_by") V3CreatedBy createdBy, - @JsonProperty("package") V3PackageReference inputPackage, @JsonProperty("droplet") V3DropletReference droplet) { + @JsonProperty(V3Fields.ERROR) String error, @JsonProperty(V3Fields.CREATED_BY) V3CreatedBy createdBy, + @JsonProperty(V3Fields.PACKAGE) V3PackageReference inputPackage, + @JsonProperty(V3Fields.DROPLET) V3DropletReference droplet, @JsonProperty(V3Fields.METADATA) V3Metadata metadata, + @JsonProperty(V3Fields.RELATIONSHIPS) V3Relationships relationships) { @JsonIgnoreProperties(ignoreUnknown = true) public record V3CreatedBy(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.NAME) String name) { @@ -32,4 +36,16 @@ public record V3PackageReference(@JsonProperty(V3Fields.GUID) String guid) { public record V3DropletReference(@JsonProperty(V3Fields.GUID) String guid) { } + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Relationships(@JsonProperty(V3Fields.APP) V3ToOneRelationship app) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3ToOneRelationship(@JsonProperty(V3Fields.DATA) V3RelationshipData data) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3RelationshipData(@JsonProperty(V3Fields.GUID) String guid) { + } + } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Domain.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Domain.java index dac0d3242f..460627b04f 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Domain.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Domain.java @@ -8,13 +8,14 @@ * {@code GET /v3/organizations/{guid}/domains/default}). * *
- * { "guid": "...", "name": "example.com", "created_at": "...", "updated_at": "...",
+ * { "guid": "...", "name": "example.com", "internal": false, "created_at": "...", "updated_at": "...",
  *   "metadata": { "labels": {...}, "annotations": {...} },
  *   "relationships": { "organization": { "data": { "guid": "..." } | null } } }
  * 
*/ @JsonIgnoreProperties(ignoreUnknown = true) public record V3Domain(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.NAME) String name, + @JsonProperty(V3Fields.INTERNAL) Boolean internal, @JsonProperty(V3Fields.CREATED_AT) String createdAt, @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, @JsonProperty(V3Fields.METADATA) V3Metadata metadata, @JsonProperty(V3Fields.RELATIONSHIPS) V3DomainRelationships relationships) { diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Droplet.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Droplet.java index 2c320e70a2..561b2c7e1c 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Droplet.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Droplet.java @@ -1,22 +1,56 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; +import java.util.List; import java.util.Map; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; /** - * Thin Jackson wire-model of the CF v3 current-droplet response ({@code GET /v3/apps/{guid}/droplets/current}): + * Thin Jackson wire-model of a CF v3 droplet resource ({@code GET /v3/droplets/{guid}}, + * {@code GET /v3/apps/{guid}/droplets/current}). * *
- * { "guid": "...", "links": { "package": { "href": "https://.../v3/packages/{guid}" }, ... } }
+ * { "guid": "...", "created_at": "...", "updated_at": "...",
+ *   "state": "AWAITING_UPLOAD|PROCESSING_UPLOAD|STAGED|COPYING|FAILED|EXPIRED", "error": "...",
+ *   "lifecycle": { "type": "buildpack|docker|cnb", "data": { "buildpacks": [...], "stack": "..." } },
+ *   "execution_metadata": "...", "process_types": { "web": "..." },
+ *   "relationships": { "app": { "data": { "guid": "..." } } },
+ *   "metadata": { "labels": {...}, "annotations": {...} },
+ *   "links": { "package": { "href": "https://.../v3/packages/{guid}" }, ... } }
  * 
*/ @JsonIgnoreProperties(ignoreUnknown = true) -public record V3Droplet(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty("links") Map links) { +public record V3Droplet(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.CREATED_AT) String createdAt, + @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, @JsonProperty(V3Fields.STATE) String state, + @JsonProperty(V3Fields.ERROR) String error, @JsonProperty(V3Fields.LIFECYCLE) V3Lifecycle lifecycle, + @JsonProperty(V3Fields.EXECUTION_METADATA) String executionMetadata, + @JsonProperty(V3Fields.PROCESS_TYPES) Map processTypes, + @JsonProperty(V3Fields.RELATIONSHIPS) V3Relationships relationships, + @JsonProperty(V3Fields.METADATA) V3Metadata metadata, @JsonProperty(V3Fields.LINKS) Map links) { @JsonIgnoreProperties(ignoreUnknown = true) - public record V3Link(@JsonProperty("href") String href) { + public record V3Lifecycle(@JsonProperty(V3Fields.TYPE) String type, @JsonProperty(V3Fields.DATA) V3LifecycleData data) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3LifecycleData(@JsonProperty(V3Fields.BUILDPACKS) List buildpacks, @JsonProperty(V3Fields.STACK) String stack) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Relationships(@JsonProperty(V3Fields.APP) V3ToOneRelationship app) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3ToOneRelationship(@JsonProperty(V3Fields.DATA) V3RelationshipData data) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3RelationshipData(@JsonProperty(V3Fields.GUID) String guid) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Link(@JsonProperty(V3Fields.HREF) String href) { } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Fields.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Fields.java index 422e1cb169..387290aac3 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Fields.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Fields.java @@ -2,6 +2,7 @@ public class V3Fields { + public static final String ID = "id"; public static final String GUID = "guid"; public static final String NAME = "name"; public static final String TYPE = "type"; @@ -26,6 +27,7 @@ public class V3Fields { public static final String DISK_IN_MB = "disk_in_mb"; public static final String LIFECYCLE = "lifecycle"; public static final String BUILDPACKS = "buildpacks"; + public static final String BUILDPACK = "buildpack"; public static final String STACK = "stack"; public static final String ENVIRONMENT_VARIABLES = "environment_variables"; public static final String VAR = "var"; @@ -54,6 +56,60 @@ public class V3Fields { public static final String AUTHENTICATION = "authentication"; public static final String DETAIL = "detail"; public static final String ENABLED = "enabled"; + public static final String ACTOR = "actor"; + public static final String TARGET = "target"; + public static final String PUBLIC_VISIBILITY = "public"; + public static final String ERROR = "error"; + public static final String CREATED_BY = "created_by"; + public static final String DROPLET = "droplet"; + public static final String HREF = "href"; + public static final String LINKS = "links"; + public static final String OPERATION = "operation"; + public static final String WARNINGS = "warnings"; + public static final String ERRORS = "errors"; + public static final String CODE = "code"; + public static final String TITLE = "title"; + public static final String PAGINATION = "pagination"; + public static final String RESOURCES = "resources"; + public static final String TOTAL_PAGES = "total_pages"; + public static final String TOTAL_RESULTS = "total_results"; + public static final String NEXT = "next"; + public static final String PREVIOUS = "previous"; + public static final String CHECKSUM = "checksum"; + public static final String VALUE = "value"; + public static final String INDEX = "index"; + public static final String ROUTABLE = "routable"; + public static final String REASON = "reason"; + public static final String INTERNAL = "internal"; + public static final String EXECUTION_METADATA = "execution_metadata"; + public static final String PROCESS_TYPES = "process_types"; + public static final String USER = "user"; + public static final String VERSION = "version"; + public static final String LOG_RATE_LIMIT_IN_BYTES_PER_SECOND = "log_rate_limit_in_bytes_per_second"; + public static final String REVISION = "revision"; + public static final String WEIGHT = "weight"; + public static final String LAST_OPERATION = "last_operation"; + public static final String ROUTE_SERVICE_URL = "route_service_url"; + public static final String SERVICE_OFFERING = "service_offering"; + public static final String FREE = "free"; + public static final String VISIBILITY_TYPE = "visibility_type"; + public static final String BROKER_CATALOG = "broker_catalog"; + public static final String AVAILABLE = "available"; + public static final String AMOUNT = "amount"; + public static final String CURRENCY = "currency"; + public static final String UNIT = "unit"; + public static final String COSTS = "costs"; + public static final String MAINTENANCE_INFO = "maintenance_info"; + public static final String CREATE = "create"; + public static final String UPDATE = "update"; + public static final String SERVICE_BINDING = "service_binding"; + public static final String SCHEMAS = "schemas"; + public static final String QUOTA = "quota"; + public static final String SUSPENDED = "suspended"; + public static final String BASIC = "basic"; + public static final String MANAGED = "managed"; + public static final String USER_PROVIDED = "user-provided"; + public static final String KEY = "key"; private V3Fields() { } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3InstancesInfoMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3InstancesInfoMapper.java index a1cc4f16bb..a5931a7eaa 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3InstancesInfoMapper.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3InstancesInfoMapper.java @@ -32,9 +32,9 @@ private static List parseProcessStatistics(List errors, - @JsonProperty("warnings") List warnings) { + @JsonProperty(V3Fields.OPERATION) String operation, @JsonProperty(V3Fields.ERRORS) List errors, + @JsonProperty(V3Fields.WARNINGS) List warnings) { public boolean isComplete() { - return "COMPLETE".equals(state); + return Constants.JOB_STATE_COMPLETE.equals(state); } public boolean isFailed() { - return "FAILED".equals(state); + return Constants.JOB_STATE_FAILED.equals(state); } public boolean isTerminal() { @@ -32,8 +33,8 @@ public boolean isTerminal() { } @JsonIgnoreProperties(ignoreUnknown = true) - public record V3Error(@JsonProperty(V3Fields.DETAIL) String detail, @JsonProperty("title") String title, - @JsonProperty("code") Integer code) { + public record V3Error(@JsonProperty(V3Fields.DETAIL) String detail, @JsonProperty(V3Fields.TITLE) String title, + @JsonProperty(V3Fields.CODE) Integer code) { } @JsonIgnoreProperties(ignoreUnknown = true) diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3JobMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3JobMapper.java index 67df0d4cbb..551b1f6589 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3JobMapper.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3JobMapper.java @@ -3,6 +3,7 @@ import java.util.List; import java.util.stream.Collectors; +import org.cloudfoundry.multiapps.controller.Constants; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudAsyncJob; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudAsyncJob; import org.cloudfoundry.multiapps.controller.client.facade.domain.JobState; @@ -29,27 +30,27 @@ private static JobState parseState(String state) { private static String getWarnings(V3Job job) { List warnings = job.warnings(); if (warnings == null) { - return ""; + return Constants.EMPTY_STRING; } return warnings.stream() .map(V3Job.V3Warning::detail) - .collect(Collectors.joining(",")); + .collect(Collectors.joining(Constants.COMMA)); } private static String getErrors(V3Job job) { List errors = job.errors(); if (errors == null) { - return ""; + return Constants.EMPTY_STRING; } return errors.stream() .map(V3JobMapper::joinErrorDetails) - .collect(Collectors.joining(",")); + .collect(Collectors.joining(Constants.COMMA)); } private static String joinErrorDetails(V3Job.V3Error error) { - return String.join(" ", String.valueOf(error.code()), error.title(), error.detail()); + return String.join(Constants.SPACE_SYMBOL, String.valueOf(error.code()), error.title(), error.detail()); } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ListResponse.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ListResponse.java index 5476df61e6..5fb490078c 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ListResponse.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ListResponse.java @@ -17,7 +17,8 @@ * */ @JsonIgnoreProperties(ignoreUnknown = true) -public record V3ListResponse(@JsonProperty("pagination") Pagination pagination, @JsonProperty("resources") List resources) { +public record V3ListResponse(@JsonProperty(V3Fields.PAGINATION) Pagination pagination, + @JsonProperty(V3Fields.RESOURCES) List resources) { public List resources() { return resources == null ? Collections.emptyList() : resources; @@ -33,12 +34,13 @@ public String nextPageHref() { } @JsonIgnoreProperties(ignoreUnknown = true) - public record Pagination(@JsonProperty("total_results") Integer totalResults, @JsonProperty("total_pages") Integer totalPages, - @JsonProperty("next") Link next, @JsonProperty("previous") Link previous) { + public record Pagination(@JsonProperty(V3Fields.TOTAL_RESULTS) Integer totalResults, + @JsonProperty(V3Fields.TOTAL_PAGES) Integer totalPages, + @JsonProperty(V3Fields.NEXT) Link next, @JsonProperty(V3Fields.PREVIOUS) Link previous) { } @JsonIgnoreProperties(ignoreUnknown = true) - public record Link(@JsonProperty("href") String href) { + public record Link(@JsonProperty(V3Fields.HREF) String href) { } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Metadata.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Metadata.java index ef50b44d5c..9dc0fb375a 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Metadata.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Metadata.java @@ -10,8 +10,8 @@ * Thin Jackson wire-model of the CF v3 {@code metadata} object (labels + annotations) attached to most resources. */ @JsonIgnoreProperties(ignoreUnknown = true) -public record V3Metadata(@JsonProperty("labels") Map labels, - @JsonProperty("annotations") Map annotations) { +public record V3Metadata(@JsonProperty(V3Fields.LABELS) Map labels, + @JsonProperty(V3Fields.ANNOTATIONS) Map annotations) { public Map labels() { return labels == null ? Collections.emptyMap() : labels; diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Organization.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Organization.java index 4f83a6f0b0..b70e8b6124 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Organization.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Organization.java @@ -8,5 +8,6 @@ */ @JsonIgnoreProperties(ignoreUnknown = true) public record V3Organization(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.NAME) String name, - @JsonProperty(V3Fields.CREATED_AT) String createdAt, @JsonProperty(V3Fields.UPDATED_AT) String updatedAt) { + @JsonProperty(V3Fields.CREATED_AT) String createdAt, @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, + @JsonProperty(V3Fields.METADATA) V3Metadata metadata) { } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Package.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Package.java index 320a89ce41..44fa45865e 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Package.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Package.java @@ -15,22 +15,37 @@ * // docker: * "image": "...", "username": "...", "password": "..." * }, - * "relationships": { "app": { "data": { "guid": "..." } } } } + * "relationships": { "app": { "data": { "guid": "..." } } } + * "metadata": { "labels": {...}, "annotations": {...} }} * */ @JsonIgnoreProperties(ignoreUnknown = true) -public record V3Package(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.TYPE) String type, @JsonProperty(V3Fields.STATE) String state, +public record V3Package(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.TYPE) String type, + @JsonProperty(V3Fields.STATE) String state, @JsonProperty(V3Fields.CREATED_AT) String createdAt, @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, - @JsonProperty(V3Fields.DATA) V3PackageData data) { + @JsonProperty(V3Fields.DATA) V3PackageData data, @JsonProperty(V3Fields.METADATA) V3Metadata metadata, + @JsonProperty(V3Fields.RELATIONSHIPS) V3Relationships relationships) { @JsonIgnoreProperties(ignoreUnknown = true) - public record V3PackageData(@JsonProperty("checksum") V3Checksum checksum, @JsonProperty("error") String error, - @JsonProperty("image") String image, @JsonProperty("username") String username, - @JsonProperty("password") String password) { + public record V3PackageData(@JsonProperty(V3Fields.CHECKSUM) V3Checksum checksum, @JsonProperty(V3Fields.ERROR) String error, + @JsonProperty(V3Fields.IMAGE) String image, @JsonProperty(V3Fields.USERNAME) String username, + @JsonProperty(V3Fields.PASSWORD) String password) { } @JsonIgnoreProperties(ignoreUnknown = true) - public record V3Checksum(@JsonProperty(V3Fields.TYPE) String type, @JsonProperty("value") String value) { + public record V3Checksum(@JsonProperty(V3Fields.TYPE) String type, @JsonProperty(V3Fields.VALUE) String value) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Relationships(@JsonProperty(V3Fields.APP) V3ToOneRelationship app) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3ToOneRelationship(@JsonProperty(V3Fields.DATA) V3RelationshipData data) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3RelationshipData(@JsonProperty(V3Fields.GUID) String guid) { } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3PackageMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3PackageMapper.java index 2a966ca5c6..98b8c24a9d 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3PackageMapper.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3PackageMapper.java @@ -63,7 +63,7 @@ private static CloudPackage.PackageData parseBitsData(V3Package.V3PackageData da } private static CloudPackage.PackageData parseDockerData(V3Package.V3PackageData data) { - if (data == null) { + if (data == null || data.image() == null) { return null; } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Process.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Process.java index 97585100b4..def5e566d1 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Process.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Process.java @@ -1,6 +1,7 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; import java.util.List; +import java.util.Map; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; @@ -10,42 +11,62 @@ * *
  * // process
- * { "guid": "...", "command": "...", "instances": 1, "memory_in_mb": 256, "disk_in_mb": 1024,
+ * { "guid": "...", "type": "web", "command": "...", "user": "vcap", "instances": 1,
+ *   "memory_in_mb": 256, "disk_in_mb": 1024, "log_rate_limit_in_bytes_per_second": 1024,
  *   "health_check": { "type": "port|http|process", "data": { "timeout": N, "invocation_timeout": N, "endpoint": "...", "interval": N } },
- *   "readiness_health_check": { "type": "process|http|port", "data": { "invocation_timeout": N, "endpoint": "...", "interval": N } } }
+ *   "readiness_health_check": { "type": "process|http|port", "data": { "invocation_timeout": N, "endpoint": "...", "interval": N } },
+ *   "relationships": { "app": { "data": { "guid": "..." } }, "revision": { "data": { "guid": "..." } } },
+ *   "metadata": { "labels": {...}, "annotations": {...} },
+ *   "created_at": "...", "updated_at": "...", "version": "...",
+ *   "links": { "self": { "href": "..." }, "scale": { "href": "..." }, ... } }
  * 
*/ @JsonIgnoreProperties(ignoreUnknown = true) public record V3Process(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.COMMAND) String command, - @JsonProperty("instances") Integer instances, @JsonProperty("memory_in_mb") Integer memoryInMb, - @JsonProperty("disk_in_mb") Integer diskInMb, @JsonProperty("health_check") V3HealthCheck healthCheck, - @JsonProperty("readiness_health_check") V3HealthCheck readinessHealthCheck) { + @JsonProperty(V3Fields.INSTANCES) Integer instances, @JsonProperty(V3Fields.MEMORY_IN_MB) Integer memoryInMb, + @JsonProperty(V3Fields.DISK_IN_MB) Integer diskInMb, @JsonProperty(V3Fields.HEALTH_CHECK) V3HealthCheck healthCheck, + @JsonProperty(V3Fields.READINESS_HEALTH_CHECK) V3HealthCheck readinessHealthCheck, + @JsonProperty(V3Fields.TYPE) String type, @JsonProperty(V3Fields.USER) String user, + @JsonProperty(V3Fields.LOG_RATE_LIMIT_IN_BYTES_PER_SECOND) Integer logRateLimitInBytesPerSecond, + @JsonProperty(V3Fields.RELATIONSHIPS) V3Relationships relationships, + @JsonProperty(V3Fields.METADATA) V3Metadata metadata, @JsonProperty(V3Fields.CREATED_AT) String createdAt, + @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, @JsonProperty(V3Fields.VERSION) String version, + @JsonProperty(V3Fields.LINKS) Map links) { @JsonIgnoreProperties(ignoreUnknown = true) public record V3HealthCheck(@JsonProperty(V3Fields.TYPE) String type, @JsonProperty(V3Fields.DATA) V3HealthCheckData data) { } @JsonIgnoreProperties(ignoreUnknown = true) - public record V3HealthCheckData(@JsonProperty("timeout") Integer timeout, - @JsonProperty("invocation_timeout") Integer invocationTimeout, - @JsonProperty("endpoint") String endpoint, @JsonProperty("interval") Integer interval) { + public record V3HealthCheckData(@JsonProperty(V3Fields.TIMEOUT) Integer timeout, + @JsonProperty(V3Fields.INVOCATION_TIMEOUT) Integer invocationTimeout, + @JsonProperty(V3Fields.ENDPOINT) String endpoint, @JsonProperty(V3Fields.INTERVAL) Integer interval) { } @JsonIgnoreProperties(ignoreUnknown = true) - public record V3ProcessStats(@JsonProperty("resources") List resources) { + public record V3Relationships(@JsonProperty(V3Fields.APP) V3ToOneRelationship app, + @JsonProperty(V3Fields.REVISION) V3ToOneRelationship revision) { } @JsonIgnoreProperties(ignoreUnknown = true) - public record V3ProcessStatsResource(@JsonProperty("index") Integer index, @JsonProperty(V3Fields.STATE) String state, - @JsonProperty("routable") String routable) { + public record V3ToOneRelationship(@JsonProperty(V3Fields.DATA) V3RelationshipData data) { } @JsonIgnoreProperties(ignoreUnknown = true) - public record V3AppFeature(@JsonProperty(V3Fields.NAME) String name, @JsonProperty(V3Fields.ENABLED) Boolean enabled) { + public record V3RelationshipData(@JsonProperty(V3Fields.GUID) String guid) { } @JsonIgnoreProperties(ignoreUnknown = true) - public record V3SshEnabled(@JsonProperty(V3Fields.ENABLED) Boolean enabled, @JsonProperty("reason") String reason) { + public record V3Link(@JsonProperty(V3Fields.HREF) String href) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3ProcessStats(@JsonProperty(V3Fields.RESOURCES) List resources) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3ProcessStatsResource(@JsonProperty(V3Fields.INDEX) Integer index, @JsonProperty(V3Fields.STATE) String state, + @JsonProperty(V3Fields.ROUTABLE) String routable) { } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ProcessMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ProcessMapper.java index 063412b278..65864fee8c 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ProcessMapper.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ProcessMapper.java @@ -42,7 +42,7 @@ public static CloudProcess toCloudProcess(V3Process process) { .instances(process.instances()) .memoryInMb(process.memoryInMb()) .diskInMb(process.diskInMb()) - .healthCheckType(healthCheck == null ? HealthCheckType.PORT + .healthCheckType(healthCheck == null || healthCheck.type() == null ? HealthCheckType.PORT : HealthCheckType.valueOf(healthCheck.type() .toUpperCase())) .healthCheckHttpEndpoint(healthCheckHttpEndpoint) diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3QueryParameters.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3QueryParameters.java new file mode 100644 index 0000000000..96ea2c073c --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3QueryParameters.java @@ -0,0 +1,26 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +public class V3QueryParameters { + + private V3QueryParameters() { + } + + public static final String NAMES = "names"; + public static final String HOSTS = "hosts"; + public static final String PATHS = "paths"; + public static final String GUIDS = "guids"; + public static final String TYPE = "type"; + public static final String APP_GUIDS = "app_guids"; + public static final String SERVICE_INSTANCE_GUIDS = "service_instance_guids"; + public static final String SERVICE_BROKER_GUIDS = "service_broker_guids"; + public static final String SERVICE_OFFERING_GUIDS = "service_offering_guids"; + public static final String SERVICE_BROKER_NAMES = "service_broker_names"; + public static final String PACKAGE_GUIDS = "package_guids"; + public static final String ORGANIZATION_GUIDS = "organization_guids"; + public static final String TARGET_GUIDS = "target_guids"; + public static final String SPACE_GUIDS = "space_guids"; + public static final String DOMAIN_GUIDS = "domain_guids"; + public static final String USER_GUIDS = "user_guids"; + public static final String LABEL_SELECTOR = "label_selector"; + +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ResourceMappers.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ResourceMappers.java index 2e0975a070..ad60ae6273 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ResourceMappers.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ResourceMappers.java @@ -9,6 +9,7 @@ import org.cloudfoundry.multiapps.controller.Messages; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,7 +34,7 @@ public static Metadata toV3Metadata(V3Metadata metadata) { return null; } - return Metadata.builder() + return ImmutableMetadata.builder() .labels(metadata.labels()) .annotations(metadata.annotations()) .build(); diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Role.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Role.java index d82e87e95c..bdd6230dd6 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Role.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Role.java @@ -15,5 +15,21 @@ */ @JsonIgnoreProperties(ignoreUnknown = true) public record V3Role(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.CREATED_AT) String createdAt, - @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, @JsonProperty(V3Fields.TYPE) String type) { + @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, @JsonProperty(V3Fields.TYPE) String type, + @JsonProperty(V3Fields.RELATIONSHIPS) V3Relationships relationships) { + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Relationships(@JsonProperty(V3Fields.USER) V3ToOneRelationship user, + @JsonProperty(V3Fields.SPACE) V3ToOneRelationship space, + @JsonProperty(V3Fields.ORGANIZATION) V3ToOneRelationship organization) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3ToOneRelationship(@JsonProperty(V3Fields.DATA) V3RelationshipData data) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3RelationshipData(@JsonProperty(V3Fields.GUID) String guid) { + } + } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Route.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Route.java index 89134cc01b..57fef8b9a4 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Route.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Route.java @@ -18,10 +18,12 @@ * */ @JsonIgnoreProperties(ignoreUnknown = true) -public record V3Route(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty("host") String host, @JsonProperty("path") String path, - @JsonProperty(V3Fields.PORT) Integer port, @JsonProperty("url") String url, +public record V3Route(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.HOST) String host, + @JsonProperty(V3Fields.PATH) String path, + @JsonProperty(V3Fields.PORT) Integer port, @JsonProperty(V3Fields.URL) String url, @JsonProperty(V3Fields.CREATED_AT) String createdAt, @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, - @JsonProperty("destinations") List destinations, @JsonProperty(V3Fields.METADATA) V3Metadata metadata, + @JsonProperty(V3Fields.DESTINATIONS) List destinations, + @JsonProperty(V3Fields.METADATA) V3Metadata metadata, @JsonProperty(V3Fields.RELATIONSHIPS) V3RouteRelationships relationships) { public List destinations() { @@ -30,8 +32,8 @@ public List destinations() { @JsonIgnoreProperties(ignoreUnknown = true) public record V3Destination(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.APP) V3DestinationApp app, - @JsonProperty(V3Fields.PORT) Integer port, @JsonProperty("weight") Integer weight, - @JsonProperty("protocol") String protocol) { + @JsonProperty(V3Fields.PORT) Integer port, @JsonProperty(V3Fields.WEIGHT) Integer weight, + @JsonProperty(V3Fields.PROTOCOL) String protocol) { } @JsonIgnoreProperties(ignoreUnknown = true) @@ -40,7 +42,7 @@ public record V3DestinationApp(@JsonProperty(V3Fields.GUID) String guid) { @JsonIgnoreProperties(ignoreUnknown = true) public record V3RouteRelationships(@JsonProperty(V3Fields.SPACE) V3ToOneRelationship space, - @JsonProperty("domain") V3ToOneRelationship domain) { + @JsonProperty(V3Fields.DOMAIN) V3ToOneRelationship domain) { } @JsonIgnoreProperties(ignoreUnknown = true) diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RouteMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RouteMapper.java index 9aad0dee9b..94b0877dad 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RouteMapper.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RouteMapper.java @@ -24,10 +24,7 @@ public static CloudRoute toCloudRoute(V3Route route) { public static CloudRoute toCloudRoute(V3Route route, UUID applicationGuid) { List destinations = mapDestinations(route); - String domainGuid = route.relationships() - .domain() - .data() - .guid(); + String domainGuid = extractDomainGuid(route); return ImmutableCloudRoute.builder() .metadata(V3ResourceMappers.parseMetadata(route.guid(), route.createdAt(), route.updatedAt())) @@ -37,7 +34,8 @@ public static CloudRoute toCloudRoute(V3Route route, UUID applicationGuid) { .port(route.port()) .domain(ImmutableCloudDomain.builder() .name(computeDomain(route)) - .metadata(ImmutableCloudMetadata.of(UUID.fromString(domainGuid))) + .metadata(ImmutableCloudMetadata.of( + V3ResourceMappers.parseNullableGuid(domainGuid))) .build()) .path(route.path()) .url(route.url()) @@ -46,6 +44,21 @@ public static CloudRoute toCloudRoute(V3Route route, UUID applicationGuid) { .build(); } + private static String extractDomainGuid(V3Route route) { + if (route.relationships() == null || route.relationships() + .domain() == null + || route.relationships() + .domain() + .data() == null) { + return null; + } + + return route.relationships() + .domain() + .data() + .guid(); + } + private static String computeDomain(V3Route route) { String domain = route.url(); @@ -68,9 +81,12 @@ private static String computeDomain(V3Route route) { private static List mapDestinations(V3Route route) { return route.destinations() .stream() + .filter(destination -> destination.app() != null && destination.app() + .guid() != null) .map(destination -> ImmutableRouteDestination.builder() .metadata(ImmutableCloudMetadata.builder() - .guid(UUID.fromString(destination.guid())) + .guid(V3ResourceMappers.parseNullableGuid( + destination.guid())) .build()) .applicationGuid(UUID.fromString(destination.app() .guid())) diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBinding.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBinding.java index 55c03bd0a1..cb25d419b9 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBinding.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBinding.java @@ -21,7 +21,7 @@ public record V3ServiceBinding(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.NAME) String name, @JsonProperty(V3Fields.TYPE) String type, @JsonProperty(V3Fields.CREATED_AT) String createdAt, @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, - @JsonProperty("last_operation") V3LastOperation lastOperation, + @JsonProperty(V3Fields.LAST_OPERATION) V3LastOperation lastOperation, @JsonProperty(V3Fields.METADATA) V3Metadata metadata, @JsonProperty(V3Fields.RELATIONSHIPS) V3Relationships relationships) { diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBindingMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBindingMapper.java index 96a3bf82c6..a2a61b16a3 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBindingMapper.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBindingMapper.java @@ -1,7 +1,5 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; import java.util.UUID; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBinding; @@ -38,10 +36,16 @@ private static UUID parseApplicationGuid(V3ServiceBinding binding) { } private static UUID parseServiceInstanceGuid(V3ServiceBinding binding) { - return UUID.fromString(binding.relationships() - .serviceInstance() - .data() - .guid()); + V3ServiceBinding.V3ToOneRelationship serviceInstance = binding.relationships() == null ? null + : binding.relationships() + .serviceInstance(); + + if (serviceInstance == null || serviceInstance.data() == null) { + return null; + } + + return V3ResourceMappers.parseNullableGuid(serviceInstance.data() + .guid()); } private static ServiceCredentialBindingOperation parseServiceBindingOperation(V3ServiceBinding.V3LastOperation lastOperation) { @@ -53,13 +57,9 @@ private static ServiceCredentialBindingOperation parseServiceBindingOperation(V3 .type(ServiceCredentialBindingOperation.Type.fromString(lastOperation.type())) .state(ServiceCredentialBindingOperation.State.fromString(lastOperation.state())) .description(lastOperation.description()) - .createdAt(parseDate(lastOperation.createdAt())) - .updatedAt(parseDate(lastOperation.updatedAt())) + .createdAt(V3ResourceMappers.parseNullableDate(lastOperation.createdAt())) + .updatedAt(V3ResourceMappers.parseNullableDate(lastOperation.updatedAt())) .build(); } - private static LocalDateTime parseDate(String date) { - return date == null ? null : LocalDateTime.parse(date, DateTimeFormatter.ISO_DATE_TIME); - } - } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBroker.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBroker.java index df0022545b..5680a99484 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBroker.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceBroker.java @@ -14,7 +14,7 @@ */ @JsonIgnoreProperties(ignoreUnknown = true) public record V3ServiceBroker(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.NAME) String name, - @JsonProperty("url") String url, + @JsonProperty(V3Fields.URL) String url, @JsonProperty(V3Fields.CREATED_AT) String createdAt, @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, @JsonProperty(V3Fields.METADATA) V3Metadata metadata, @JsonProperty(V3Fields.RELATIONSHIPS) V3Relationships relationships) { diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstance.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstance.java index 5a8c9c046e..640834f08c 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstance.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstance.java @@ -11,7 +11,7 @@ *
  * { "guid": "...", "name": "...", "type": "managed|user-provided",
  *   "created_at": "...", "updated_at": "...",
- *   "tags": [...], "syslog_drain_url": "...",
+ *   "tags": [...], "syslog_drain_url": "...", "route_service_url": "...",
  *   "last_operation": { "type": "create|update|delete", "state": "succeeded|failed|in progress|initial", "description": "..." },
  *   "metadata": { "labels": {...}, "annotations": {...} },
  *   "relationships": { "space": { "data": { "guid": "..." } }, "service_plan": { "data": { "guid": "..." } } } }
@@ -21,8 +21,10 @@
 public record V3ServiceInstance(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.NAME) String name,
                                 @JsonProperty(V3Fields.TYPE) String type,
                                 @JsonProperty(V3Fields.CREATED_AT) String createdAt, @JsonProperty(V3Fields.UPDATED_AT) String updatedAt,
-                                @JsonProperty("tags") List tags, @JsonProperty("syslog_drain_url") String syslogDrainUrl,
-                                @JsonProperty("last_operation") V3LastOperation lastOperation,
+                                @JsonProperty(V3Fields.TAGS) List tags,
+                                @JsonProperty(V3Fields.SYSLOG_DRAIN_URL) String syslogDrainUrl,
+                                @JsonProperty(V3Fields.ROUTE_SERVICE_URL) String routeServiceUrl,
+                                @JsonProperty(V3Fields.LAST_OPERATION) V3LastOperation lastOperation,
                                 @JsonProperty(V3Fields.METADATA) V3Metadata metadata,
                                 @JsonProperty(V3Fields.RELATIONSHIPS) V3Relationships relationships) {
 
@@ -33,7 +35,7 @@ public record V3LastOperation(@JsonProperty(V3Fields.TYPE) String type, @JsonPro
 
     @JsonIgnoreProperties(ignoreUnknown = true)
     public record V3Relationships(@JsonProperty(V3Fields.SPACE) V3ToOneRelationship space,
-                                  @JsonProperty("service_plan") V3ToOneRelationship servicePlan) {
+                                  @JsonProperty(V3Fields.SERVICE_PLAN) V3ToOneRelationship servicePlan) {
     }
 
     @JsonIgnoreProperties(ignoreUnknown = true)
diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceOffering.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceOffering.java
index 484576db44..94f61d0358 100644
--- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceOffering.java
+++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceOffering.java
@@ -1,5 +1,6 @@
 package org.cloudfoundry.multiapps.controller.client.facade.rest.resources;
 
+import java.util.List;
 import java.util.Map;
 
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@@ -10,7 +11,7 @@
  *
  * 
  * { "guid": "...", "name": "...", "description": "...", "available": true, "shareable": true,
- *   "documentation_url": "...", "created_at": "...", "updated_at": "...",
+ *   "tags": [...], "documentation_url": "...", "created_at": "...", "updated_at": "...",
  *   "broker_catalog": { "id": "...", "metadata": {...}, "features": { "bindable": true, ... } },
  *   "relationships": { "service_broker": { "data": { "guid": "..." } } },
  *   "metadata": { "labels": {...}, "annotations": {...} } }
@@ -24,7 +25,7 @@ public record V3ServiceOffering(@JsonProperty(V3Fields.GUID) String guid, @JsonP
                                 @JsonProperty(V3Fields.CREATED_AT) String createdAt, @JsonProperty(V3Fields.UPDATED_AT) String updatedAt,
                                 @JsonProperty("broker_catalog") V3BrokerCatalog brokerCatalog,
                                 @JsonProperty(V3Fields.RELATIONSHIPS) V3Relationships relationships,
-                                @JsonProperty(V3Fields.METADATA) V3Metadata metadata) {
+                                @JsonProperty(V3Fields.METADATA) V3Metadata metadata, @JsonProperty(V3Fields.TAGS) List tags) {
 
     @JsonIgnoreProperties(ignoreUnknown = true)
     public record V3BrokerCatalog(@JsonProperty("id") String id, @JsonProperty(V3Fields.METADATA) Map metadata,
diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServicePlan.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServicePlan.java
index 62e0654e52..959c7c91eb 100644
--- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServicePlan.java
+++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServicePlan.java
@@ -1,5 +1,6 @@
 package org.cloudfoundry.multiapps.controller.client.facade.rest.resources;
 
+import java.util.List;
 import java.util.Map;
 
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@@ -11,25 +12,70 @@
  * 
  * { "guid": "...", "name": "...", "description": "...", "free": true|false,
  *   "visibility_type": "public|admin|organization|space",
+ *   "available": true|false,
+ *   "costs": [
+ *     {
+ *       "currency": "USD",
+ *       "amount": 199.99,
+ *       "unit": "..."
+ *     },
+ *   "maintenance_info": {
+ *     "version": "1.0.0+dev4",
+ *     "description": "Database version 7.8.0"
+ *   },
+ *   "schemas": {
+ *     "service_instance": {
+ *       "create": {
+ *         "parameters": {
+ *           "$schema": "...",
+ *           "type": "object",
+ *           "properties": {
+ *             "billing-account": {
+ *               "description": "Billing account number used to charge use of shared fake server.",
+ *               "type": "string"
+ *             }
+ *           }
+ *         }
+ *       },
+ *       "update": {
+ *         "parameters": {}
+ *       }
+ *     },
+ *     "service_binding": {
+ *       "create": {
+ *         "parameters": {}
+ *       }
+ *     }
+ *   }
  *   "created_at": "...", "updated_at": "...",
  *   "broker_catalog": { "id": "...", "metadata": { ... } },
  *   "relationships": { "service_offering": { "data": { "guid": "..." } } },
- *   "metadata": { "labels": {...}, "annotations": {...} } }
+ *   "metadata": { "labels": {...}, "annotations": {...} }
+ *   "links": { "self": { "href": "..." }, "scale": { "href": "..." }, ... }}
  * 
*/ @JsonIgnoreProperties(ignoreUnknown = true) public record V3ServicePlan(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.NAME) String name, - @JsonProperty(V3Fields.DESCRIPTION) String description, @JsonProperty("free") Boolean free, - @JsonProperty("visibility_type") String visibilityType, @JsonProperty(V3Fields.CREATED_AT) String createdAt, - @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, @JsonProperty("broker_catalog") V3BrokerCatalog brokerCatalog, - @JsonProperty(V3Fields.RELATIONSHIPS) V3Relationships relationships, @JsonProperty(V3Fields.METADATA) V3Metadata metadata) { + @JsonProperty(V3Fields.DESCRIPTION) String description, @JsonProperty(V3Fields.FREE) Boolean free, + @JsonProperty(V3Fields.VISIBILITY_TYPE) String visibilityType, + @JsonProperty(V3Fields.AVAILABLE) Boolean available, + @JsonProperty(V3Fields.COSTS) List costs, + @JsonProperty(V3Fields.MAINTENANCE_INFO) V3ServicePlanMaintenanceInfo maintenanceInfo, + @JsonProperty(V3Fields.SCHEMAS) V3ServicePlanSchemas schemas, + @JsonProperty(V3Fields.CREATED_AT) String createdAt, + @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, + @JsonProperty(V3Fields.BROKER_CATALOG) V3BrokerCatalog brokerCatalog, + @JsonProperty(V3Fields.RELATIONSHIPS) V3Relationships relationships, + @JsonProperty(V3Fields.METADATA) V3Metadata metadata, + @JsonProperty(V3Fields.LINKS) Map links) { @JsonIgnoreProperties(ignoreUnknown = true) - public record V3BrokerCatalog(@JsonProperty("id") String id, @JsonProperty(V3Fields.METADATA) Map metadata) { + public record V3BrokerCatalog(@JsonProperty(V3Fields.ID) String id, @JsonProperty(V3Fields.METADATA) Map metadata) { } @JsonIgnoreProperties(ignoreUnknown = true) - public record V3Relationships(@JsonProperty("service_offering") V3ToOneRelationship serviceOffering) { + public record V3Relationships(@JsonProperty(V3Fields.SERVICE_OFFERING) V3ToOneRelationship serviceOffering, + @JsonProperty(V3Fields.SPACE) V3ToOneRelationship space) { } @JsonIgnoreProperties(ignoreUnknown = true) @@ -40,4 +86,32 @@ public record V3ToOneRelationship(@JsonProperty(V3Fields.DATA) V3RelationshipDat public record V3RelationshipData(@JsonProperty(V3Fields.GUID) String guid) { } + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3ServicePlanCost(@JsonProperty(V3Fields.AMOUNT) float amount, @JsonProperty(V3Fields.CURRENCY) String currency, + @JsonProperty(V3Fields.UNIT) String unit) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3ServicePlanMaintenanceInfo(@JsonProperty(V3Fields.VERSION) String version, + @JsonProperty(V3Fields.DESCRIPTION) String description) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3ServicePlanSchemas(@JsonProperty(V3Fields.SERVICE_INSTANCE) V3SchemaParameters serviceInstance, + @JsonProperty(V3Fields.SERVICE_BINDING) V3SchemaParameters serviceBinding) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3SchemaParameters(@JsonProperty(V3Fields.CREATE) V3SchemaParametersDefinition create, + @JsonProperty(V3Fields.UPDATE) V3SchemaParametersDefinition update) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3SchemaParametersDefinition(@JsonProperty(V3Fields.PARAMETERS) Map parameters) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Link(@JsonProperty(V3Fields.HREF) String href) { + } + } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServicePlanMapper.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServicePlanMapper.java index 9b6711fa94..763159fb05 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServicePlanMapper.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServicePlanMapper.java @@ -1,11 +1,11 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; +import java.util.Map; + import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServicePlan; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServicePlan; public final class V3ServicePlanMapper { - - private static final String PUBLIC_VISIBILITY = "public"; private V3ServicePlanMapper() { } @@ -25,7 +25,7 @@ public static CloudServicePlan toCloudServicePlan(V3ServicePlan servicePlan) { .build(); } - private static java.util.Map extractBrokerCatalogMetadata(V3ServicePlan.V3BrokerCatalog brokerCatalog) { + private static Map extractBrokerCatalogMetadata(V3ServicePlan.V3BrokerCatalog brokerCatalog) { return brokerCatalog == null ? null : brokerCatalog.metadata(); } @@ -45,7 +45,7 @@ private static String extractServiceOfferingId(V3ServicePlan.V3Relationships rel } private static Boolean isPublic(String visibilityType) { - return visibilityType == null ? null : PUBLIC_VISIBILITY.equals(visibilityType); + return visibilityType == null ? null : V3Fields.PUBLIC_VISIBILITY.equals(visibilityType); } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Space.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Space.java index e62062173b..7b475ffddc 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Space.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Space.java @@ -1,5 +1,7 @@ package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; +import java.util.Map; + import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; @@ -8,14 +10,21 @@ * *
  * { "guid": "...", "name": "...", "created_at": "...", "updated_at": "...",
- *   "relationships": { "organization": { "data": { "guid": "..." } } } }
+ *   "relationships": { "organization": { "data": { "guid": "..." } }, "quota": { "data": { "guid": "..." } } },
+ *   "suspended": false,
+ *   "metadata": { "labels": {...}, "annotations": {...} }
+ *   "links": { "self": { "href": "..." }, "scale": { "href": "..." }, ... },
+ *   }
  * 
*/ @JsonIgnoreProperties(ignoreUnknown = true) public record V3Space(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.NAME) String name, @JsonProperty(V3Fields.CREATED_AT) String createdAt, @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, - @JsonProperty(V3Fields.RELATIONSHIPS) V3SpaceRelationships relationships) { + @JsonProperty(V3Fields.SUSPENDED) Boolean suspended, + @JsonProperty(V3Fields.RELATIONSHIPS) V3SpaceRelationships relationships, + @JsonProperty(V3Fields.METADATA) V3Metadata metadata, + @JsonProperty(V3Fields.LINKS) Map links) { public String organizationGuid() { if (relationships == null || relationships.organization() == null || relationships.organization() @@ -29,7 +38,8 @@ public String organizationGuid() { } @JsonIgnoreProperties(ignoreUnknown = true) - public record V3SpaceRelationships(@JsonProperty(V3Fields.ORGANIZATION) V3ToOne organization) { + public record V3SpaceRelationships(@JsonProperty(V3Fields.ORGANIZATION) V3ToOne organization, + @JsonProperty(V3Fields.QUOTA) V3ToOne quota) { } @JsonIgnoreProperties(ignoreUnknown = true) @@ -40,4 +50,8 @@ public record V3ToOne(@JsonProperty(V3Fields.DATA) V3Data data) { public record V3Data(@JsonProperty(V3Fields.GUID) String guid) { } + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Link(@JsonProperty(V3Fields.HREF) String href) { + } + } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3SshEnabled.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3SshEnabled.java new file mode 100644 index 0000000000..0a4d2b8103 --- /dev/null +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3SshEnabled.java @@ -0,0 +1,12 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest.resources; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Thin Jackson wire-model of the CF v3 SSH-enabled resource ({@code GET /v3/apps/{guid}/ssh_enabled}). This belongs to the + * application entity. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record V3SshEnabled(@JsonProperty(V3Fields.ENABLED) Boolean enabled, @JsonProperty(V3Fields.REASON) String reason) { +} diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Stack.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Stack.java index e3823f4e91..815b670d9c 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Stack.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Stack.java @@ -9,12 +9,14 @@ *
  * { "guid": "...", "name": "cflinuxfs4", "description": "...",
  *   "created_at": "...", "updated_at": "...",
+ *   "state": "ACTIVE",
  *   "metadata": { "labels": {...}, "annotations": {...} } }
  * 
*/ @JsonIgnoreProperties(ignoreUnknown = true) public record V3Stack(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3Fields.NAME) String name, @JsonProperty(V3Fields.DESCRIPTION) String description, @JsonProperty(V3Fields.CREATED_AT) String createdAt, - @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, @JsonProperty(V3Fields.METADATA) V3Metadata metadata) { + @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, @JsonProperty(V3Fields.STATE) String state, + @JsonProperty(V3Fields.METADATA) V3Metadata metadata) { } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Task.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Task.java index 918d711236..7ed98abff8 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Task.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3Task.java @@ -11,6 +11,13 @@ * "memory_in_mb": 256, "disk_in_mb": 1024, * "result": { "failure_reason": "..." | null }, * "created_at": "...", "updated_at": "...", + * "relationships": { + * "app": { + * "data": { + * "guid": "ccc25a0f-c8f4-4b39-9f1b-de9f328d0ee5" + * } + * } + * }, * "metadata": { "labels": {...}, "annotations": {...} } } *
*/ @@ -20,10 +27,23 @@ public record V3Task(@JsonProperty(V3Fields.GUID) String guid, @JsonProperty(V3F @JsonProperty(V3Fields.STATE) String state, @JsonProperty("memory_in_mb") Integer memoryInMb, @JsonProperty("disk_in_mb") Integer diskInMb, @JsonProperty("result") V3Result result, @JsonProperty(V3Fields.CREATED_AT) String createdAt, @JsonProperty(V3Fields.UPDATED_AT) String updatedAt, + @JsonProperty(V3Fields.RELATIONSHIPS) V3TaskRelationships relationships, @JsonProperty(V3Fields.METADATA) V3Metadata metadata) { @JsonIgnoreProperties(ignoreUnknown = true) public record V3Result(@JsonProperty("failure_reason") String failureReason) { } + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3TaskRelationships(@JsonProperty(V3Fields.APP) V3Task.V3ToOne app) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3ToOne(@JsonProperty(V3Fields.DATA) V3Task.V3Data data) { + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record V3Data(@JsonProperty(V3Fields.GUID) String guid) { + } + } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/util/RestUtil.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/util/RestUtil.java index 8b22d95fb3..161d966a28 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/util/RestUtil.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/util/RestUtil.java @@ -83,7 +83,7 @@ private SslContext buildSslContext() { } } - private X509ExtendedTrustManager createDummyTrustManager() { + public static X509ExtendedTrustManager createDummyTrustManager() { return new X509ExtendedTrustManager() { @Override diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/ResilientCloudControllerClientTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/ResilientCloudControllerClientTest.java index d2deb2c952..c8285710c2 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/ResilientCloudControllerClientTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/ResilientCloudControllerClientTest.java @@ -199,18 +199,6 @@ void testBindServiceInstanceReturnsOptional() { Assertions.assertEquals(Optional.of("job-1"), client.bindServiceInstance("binding", "app", "service")); } - @Test - void testGetApplicationSshEnabledRetriesAndReturnsBoolean() { - UUID guid = UUID.randomUUID(); - Mockito.when(restClient.getApplicationSshEnabled(guid)) - .thenThrow(new CloudOperationException(HttpStatus.BAD_GATEWAY)) - .thenReturn(true); - - Assertions.assertTrue(client.getApplicationSshEnabled(guid)); - Mockito.verify(restClient, Mockito.times(2)) - .getApplicationSshEnabled(guid); - } - @Test void testAsyncUploadWithOverrideTimeoutUsesPlainRetry() { Path file = Paths.get("/tmp/some.mtar"); diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/ApplicationsCloudControllerClientIntegrationTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/ApplicationsCloudControllerClientIntegrationTest.java index 88961bcc18..6216ab339d 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/ApplicationsCloudControllerClientIntegrationTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/ApplicationsCloudControllerClientIntegrationTest.java @@ -21,6 +21,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudDomain; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudRoute; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableDockerInfo; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableStaging; import org.cloudfoundry.multiapps.controller.client.facade.domain.InstancesInfo; import org.cloudfoundry.multiapps.controller.client.facade.domain.LifecycleType; @@ -382,8 +383,8 @@ void getBuildsForApplication() { @DisplayName("Create application and update its metadata") void updateApplicationMetadata() { String applicationName = "test-application-15"; - Metadata metadata = Metadata.builder() - .label("test-app", "test-app") + Metadata metadata = ImmutableMetadata.builder() + .putLabel("test-app", "test-app") .build(); try { createAndVerifyDefaultApplication(applicationName); diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3OperationsTest.java index 89904a661e..b1774404fd 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3OperationsTest.java @@ -7,6 +7,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableStaging; import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.dto.ApplicationToCreateDto; @@ -28,7 +29,7 @@ class ApplicationsV3OperationsTest { private static final String SPACE_GUID = "11111111-2222-3333-4444-555555555555"; private static final String APP_GUID = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; - private static final String APPS_BASE = MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000"; + private static final String APPS_BASE = MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=50"; @Mock private CloudSpace target; @@ -87,8 +88,8 @@ void testCreateApplicationWithScaleAlsoScalesWebProcess() { .stackName("cflinuxfs4") .build()) .env(Map.of("FOO", "bar")) - .metadata(Metadata.builder() - .label("k", "v") + .metadata(ImmutableMetadata.builder() + .putLabel("k", "v") .build()) .build(); @@ -437,9 +438,9 @@ void testBindDropletToAppUpdatesCurrentDroplet() { void testUpdateApplicationMetadataUpdatesApp() { factory.stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID, HttpMethod.PATCH); - Metadata metadata = Metadata.builder() - .label("k", "v") - .annotation("a", "b") + Metadata metadata = ImmutableMetadata.builder() + .putLabel("k", "v") + .putAnnotation("a", "b") .build(); operations.updateApplicationMetadata(UUID.fromString(APP_GUID), metadata); @@ -447,6 +448,24 @@ void testUpdateApplicationMetadataUpdatesApp() { factory.verify(); } + @Test + void testUpdateApplicationMetadataSendsNullLabelValueToDeleteLabel() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andExpect(MockRestRequestMatchers.content() + .string(org.hamcrest.Matchers.containsString("\"obsolete\":null"))) + .andRespond(MockRestResponseCreators.withSuccess()); + + Metadata metadata = ImmutableMetadata.builder() + .putLabel("obsolete", null) + .build(); + + operations.updateApplicationMetadata(UUID.fromString(APP_GUID), metadata); + + factory.verify(); + } + private void stubScaleFlow() { when(target.getGuid()) .thenReturn(null); diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3OperationsTest.java index e220947b48..2838eb3e1a 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/BuildsV3OperationsTest.java @@ -73,7 +73,7 @@ void testGetBuildThrowsWhenNotFound() { @Test void testGetBuildsForApplicationReturnsMappedList() { factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID - + "/builds?per_page=5000", "{\"resources\":[" + getBuildJson("STAGED") + "]}"); + + "/builds?per_page=50", "{\"resources\":[" + getBuildJson("STAGED") + "]}"); List result = operations.getBuildsForApplication(UUID.fromString(APP_GUID)); @@ -86,7 +86,7 @@ void testGetBuildsForApplicationReturnsMappedList() { @Test void testGetBuildsForApplicationReturnsEmptyList() { factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID - + "/builds?per_page=5000", "{\"resources\":[]}"); + + "/builds?per_page=50", "{\"resources\":[]}"); List result = operations.getBuildsForApplication(UUID.fromString(APP_GUID)); @@ -97,7 +97,7 @@ void testGetBuildsForApplicationReturnsEmptyList() { @Test void testGetBuildsForPackageReturnsMappedList() { factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/builds?package_guids=" - + PACKAGE_GUID + "&per_page=5000", "{\"resources\":[" + getBuildJson("FAILED") + "]}"); + + PACKAGE_GUID + "&per_page=50", "{\"resources\":[" + getBuildJson("FAILED") + "]}"); List result = operations.getBuildsForPackage(UUID.fromString(PACKAGE_GUID)); @@ -112,7 +112,7 @@ void testGetBuildsForPackageReturnsMappedList() { @Test void testGetBuildsForPackageReturnsEmptyList() { factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/builds?package_guids=" - + PACKAGE_GUID + "&per_page=5000", "{\"resources\":[]}"); + + PACKAGE_GUID + "&per_page=50", "{\"resources\":[]}"); List result = operations.getBuildsForPackage(UUID.fromString(PACKAGE_GUID)); diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientV3ImplTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientV3ImplTest.java index 2e5344b3e1..3273433876 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientV3ImplTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/CloudControllerRestClientV3ImplTest.java @@ -34,13 +34,14 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceBroker; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceInstance; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudTask; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableStaging; import org.cloudfoundry.multiapps.controller.client.facade.domain.InstancesInfo; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ServicePlanVisibility; import org.cloudfoundry.multiapps.controller.client.facade.domain.UserRole; import org.cloudfoundry.multiapps.controller.client.facade.dto.ApplicationToCreateDto; import org.cloudfoundry.multiapps.controller.client.facade.dto.ImmutableApplicationToCreateDto; +import org.hamcrest.Matchers; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -69,8 +70,8 @@ class CloudControllerRestClientV3ImplTest { private static final String OFFERING_GUID = "a1a1a1a1-b2b2-c3c3-d4d4-e5e5e5e5e5e5"; private static final String PLAN_GUID = "f6f6f6f6-a7a7-b8b8-c9c9-d0d0d0d0d0d0"; private static final String DOMAIN_NAME = "example.com"; - private static final String APPS_BASE = MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000"; - private static final String SI_BASE = MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=5000"; + private static final String APPS_BASE = MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=50"; + private static final String SI_BASE = MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=50"; private static final String BINDINGS = MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings"; @Mock @@ -98,7 +99,7 @@ void testGetTargetReturnsInjectedSpace() { @Test void testGetStackReturnsMappedStack() { - stubGet(MockControllerClientFactory.BASE_URL + "/v3/stacks?names=cflinuxfs4&per_page=5000", getStackListJson()); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/stacks?names=cflinuxfs4&per_page=50", getStackListJson()); CloudStack result = client.getStack("cflinuxfs4"); @@ -108,7 +109,7 @@ void testGetStackReturnsMappedStack() { @Test void testGetStacksReturnsMappedList() { - stubGet(MockControllerClientFactory.BASE_URL + "/v3/stacks?per_page=5000", getStackListJson()); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/stacks?per_page=50", getStackListJson()); List result = client.getStacks(); @@ -118,7 +119,7 @@ void testGetStacksReturnsMappedList() { @Test void testGetDomainsReturnsMappedList() { - stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?per_page=5000", getDomainListJson()); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?per_page=50", getDomainListJson()); List result = client.getDomains(); @@ -130,7 +131,7 @@ void testGetDomainsReturnsMappedList() { @Test void testGetSharedDomainsReturnsMappedList() { - stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?per_page=5000", getDomainListJson()); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?per_page=50", getDomainListJson()); List result = client.getSharedDomains(); @@ -253,7 +254,7 @@ void testCreateApplicationWithStagingUpdatesStagingThroughProcess() { @Test void testGetEventsReturnsMappedList() { - stubGet(MockControllerClientFactory.BASE_URL + "/v3/audit_events?per_page=5000", getAuditEventListJson()); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/audit_events?per_page=50", getAuditEventListJson()); List result = client.getEvents(); @@ -265,7 +266,7 @@ void testGetEventsReturnsMappedList() { @Test void testGetEventsByTargetReturnsMappedList() { - stubGet(MockControllerClientFactory.BASE_URL + "/v3/audit_events?per_page=5000" + stubGet(MockControllerClientFactory.BASE_URL + "/v3/audit_events?per_page=50" + "&target_guids=" + APP_GUID, getAuditEventListJson()); List result = client.getEventsByTarget(UUID.fromString(APP_GUID)); @@ -331,7 +332,7 @@ void testGetTaskReturnsMappedTask() { void testGetTasksReturnsMappedList() { stubApplicationLookup(); - stubGet(MockControllerClientFactory.BASE_URL + "/v3/tasks?per_page=5000&app_guids=" + stubGet(MockControllerClientFactory.BASE_URL + "/v3/tasks?per_page=50&app_guids=" + APP_GUID, "{\"resources\":[" + getTaskJson() + "]}"); List result = client.getTasks("my-app"); @@ -353,7 +354,7 @@ void testGetBuildReturnsMappedBuild() { @Test void testGetBuildsForApplicationReturnsMappedList() { stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID - + "/builds?per_page=5000", "{\"resources\":[" + getBuildJson() + "]}"); + + "/builds?per_page=50", "{\"resources\":[" + getBuildJson() + "]}"); List result = client.getBuildsForApplication(UUID.fromString(APP_GUID)); @@ -364,7 +365,7 @@ void testGetBuildsForApplicationReturnsMappedList() { @Test void testGetBuildsForPackageReturnsMappedList() { stubGet(MockControllerClientFactory.BASE_URL + "/v3/builds?package_guids=" + PACKAGE_GUID - + "&per_page=5000", "{\"resources\":[" + getBuildJson() + "]}"); + + "&per_page=50", "{\"resources\":[" + getBuildJson() + "]}"); List result = client.getBuildsForPackage(UUID.fromString(PACKAGE_GUID)); @@ -375,7 +376,7 @@ void testGetBuildsForPackageReturnsMappedList() { @Test void testGetApplicationRoutesReturnsMappedList() { stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID - + "/routes?per_page=5000", "{\"resources\":[" + getRouteJson() + "]}"); + + "/routes?per_page=50", "{\"resources\":[" + getRouteJson() + "]}"); List result = client.getApplicationRoutes(UUID.fromString(APP_GUID)); @@ -522,9 +523,25 @@ void testBindDropletToApp() { void testUpdateApplicationMetadata() { stubMethod(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID, HttpMethod.PATCH); - client.updateApplicationMetadata(UUID.fromString(APP_GUID), Metadata.builder() - .label("key", "value") - .build()); + client.updateApplicationMetadata(UUID.fromString(APP_GUID), ImmutableMetadata.builder() + .putLabel("key", "value") + .build()); + + factory.verify(); + } + + @Test + void testUpdateApplicationMetadataUnsetsLabelWhenValueIsNull() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andExpect(MockRestRequestMatchers.content() + .string(Matchers.containsString("\"obsolete\":null"))) + .andRespond(MockRestResponseCreators.withSuccess()); + + client.updateApplicationMetadata(UUID.fromString(APP_GUID), ImmutableMetadata.builder() + .putLabel("obsolete", null) + .build()); factory.verify(); } @@ -560,17 +577,9 @@ void testGetApplicationInstancesByGuid() { factory.verify(); } - @Test - void testGetApplicationSshEnabled() { - stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/ssh_enabled", "{\"enabled\":true}"); - - Assertions.assertTrue(client.getApplicationSshEnabled(UUID.fromString(APP_GUID))); - factory.verify(); - } - @Test void testGetApplicationFeatures() { - stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/features?per_page=5000", + stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/features?per_page=50", "{\"resources\":[{\"name\":\"ssh\",\"enabled\":true}]}"); Map result = client.getApplicationFeatures(UUID.fromString(APP_GUID)); @@ -608,7 +617,7 @@ void testUpdateApplicationStaging() { @Test void testGetApplicationEvents() { stubApplicationLookup(); - stubGet(MockControllerClientFactory.BASE_URL + "/v3/audit_events?per_page=5000&target_guids=" + APP_GUID, + stubGet(MockControllerClientFactory.BASE_URL + "/v3/audit_events?per_page=50&target_guids=" + APP_GUID, getAuditEventListJson()); List result = client.getApplicationEvents("my-app"); @@ -631,7 +640,7 @@ void testGetDefaultDomain() { @Test void testGetPrivateDomains() { - stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?per_page=5000", + stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?per_page=50", "{\"resources\":[{\"guid\":\"" + APP_GUID + "\",\"name\":\"" + DOMAIN_NAME + "\",\"relationships\":{\"organization\":{\"data\":{\"guid\":\"" + SPACE_GUID + "\"}}}}]}"); @@ -644,7 +653,7 @@ void testGetPrivateDomains() { @Test void testGetDomainsForOrganization() { stubTargetOrganization(); - stubGet(MockControllerClientFactory.BASE_URL + "/v3/organizations/" + SPACE_GUID + "/domains?per_page=5000", + stubGet(MockControllerClientFactory.BASE_URL + "/v3/organizations/" + SPACE_GUID + "/domains?per_page=50", getDomainListJson()); List result = client.getDomainsForOrganization(); @@ -656,7 +665,7 @@ void testGetDomainsForOrganization() { @Test void testAddDomainCreatesWhenMissing() { stubTargetOrganization(); - stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + DOMAIN_NAME + "&per_page=5000", + stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + DOMAIN_NAME + "&per_page=50", "{\"resources\":[]}"); stubMethod(MockControllerClientFactory.BASE_URL + "/v3/domains", HttpMethod.POST); @@ -668,7 +677,7 @@ void testAddDomainCreatesWhenMissing() { @Test void testDeleteDomain() { stubTargetOrganization(); - stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + DOMAIN_NAME + "&per_page=5000", getDomainListJson()); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + DOMAIN_NAME + "&per_page=50", getDomainListJson()); stubMethod(MockControllerClientFactory.BASE_URL + "/v3/domains/" + APP_GUID, HttpMethod.DELETE); client.deleteDomain(DOMAIN_NAME); @@ -679,7 +688,7 @@ void testDeleteDomain() { @Test void testAddRoute() { when(target.getGuid()).thenReturn(UUID.fromString(SPACE_GUID)); - stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + DOMAIN_NAME + "&per_page=5000", getDomainListJson()); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + DOMAIN_NAME + "&per_page=50", getDomainListJson()); factory.server() .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/routes")) .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) @@ -693,8 +702,8 @@ void testAddRoute() { @Test void testGetRoutes() { when(target.getGuid()).thenReturn(UUID.fromString(SPACE_GUID)); - stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + DOMAIN_NAME + "&per_page=5000", getDomainListJson()); - stubGet(MockControllerClientFactory.BASE_URL + "/v3/routes?per_page=5000&domain_guids=" + APP_GUID + "&space_guids=" + SPACE_GUID, + stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + DOMAIN_NAME + "&per_page=50", getDomainListJson()); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/routes?per_page=50&domain_guids=" + APP_GUID + "&space_guids=" + SPACE_GUID, "{\"resources\":[" + getRouteJson() + "]}"); List result = client.getRoutes(DOMAIN_NAME); @@ -716,8 +725,8 @@ void testDeleteOrphanedRoutes() { @Test void testDeleteRoute() { when(target.getGuid()).thenReturn(UUID.fromString(SPACE_GUID)); - stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + DOMAIN_NAME + "&per_page=5000", getDomainListJson()); - stubGet(MockControllerClientFactory.BASE_URL + "/v3/routes?per_page=5000&domain_guids=" + APP_GUID + "&space_guids=" + SPACE_GUID + stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + DOMAIN_NAME + "&per_page=50", getDomainListJson()); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/routes?per_page=50&domain_guids=" + APP_GUID + "&space_guids=" + SPACE_GUID + "&hosts=myhost&paths=/mypath", "{\"resources\":[" + getRouteJson() + "]}"); stubMethod(MockControllerClientFactory.BASE_URL + "/v3/routes/" + APP_GUID, HttpMethod.DELETE); @@ -730,10 +739,10 @@ void testDeleteRoute() { void testUpdateApplicationRoutesAddsRoute() { when(target.getGuid()).thenReturn(UUID.fromString(SPACE_GUID)); stubGet(APPS_BASE + "&space_guids=" + SPACE_GUID + "&names=my-app", "{\"resources\":[{\"guid\":\"" + APP_GUID + "\"}]}"); - stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/routes?per_page=5000", "{\"resources\":[]}"); - stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + DOMAIN_NAME + "&per_page=5000", + stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/routes?per_page=50", "{\"resources\":[]}"); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + DOMAIN_NAME + "&per_page=50", "{\"resources\":[{\"guid\":\"" + APP_GUID + "\",\"name\":\"" + DOMAIN_NAME + "\"}]}"); - stubGet(MockControllerClientFactory.BASE_URL + "/v3/routes?per_page=5000&domain_guids=" + APP_GUID + "&space_guids=" + SPACE_GUID + stubGet(MockControllerClientFactory.BASE_URL + "/v3/routes?per_page=50&domain_guids=" + APP_GUID + "&space_guids=" + SPACE_GUID + "&hosts=myhost&paths=/mypath", "{\"resources\":[]}"); factory.server() @@ -783,9 +792,9 @@ void testGetServiceInstanceWithoutAuxiliaryContentRequiredFlag() { @Test void testCreateServiceInstance() { stubTargetSpaceMetadata(); - stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_offerings?per_page=5000&space_guids=" + SPACE_GUID + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_offerings?per_page=50&space_guids=" + SPACE_GUID + "&names=my-offering", "{\"resources\":[{\"guid\":\"" + OFFERING_GUID + "\",\"name\":\"my-offering\"}]}"); - stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_plans?per_page=5000&service_offering_guids=" + OFFERING_GUID + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_plans?per_page=50&service_offering_guids=" + OFFERING_GUID + "&names=my-plan", "{\"resources\":[{\"guid\":\"" + PLAN_GUID + "\",\"name\":\"my-plan\"}]}"); stubMethod(MockControllerClientFactory.BASE_URL + "/v3/service_instances", HttpMethod.POST); @@ -937,9 +946,26 @@ void testUpdateServiceSyslogDrainUrlSkipsManaged() { void testUpdateServiceInstanceMetadata() { stubMethod(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + SERVICE_INSTANCE_GUID, HttpMethod.PATCH); - client.updateServiceInstanceMetadata(UUID.fromString(SERVICE_INSTANCE_GUID), Metadata.builder() - .label("k", "v") - .build()); + client.updateServiceInstanceMetadata(UUID.fromString(SERVICE_INSTANCE_GUID), ImmutableMetadata.builder() + .putLabel("k", "v") + .build()); + + factory.verify(); + } + + @Test + void testUpdateServiceInstanceMetadataUnsetsAnnotationWhenValueIsNull() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + + SERVICE_INSTANCE_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andExpect(MockRestRequestMatchers.content() + .string(Matchers.containsString("\"obsolete\":null"))) + .andRespond(MockRestResponseCreators.withSuccess()); + + client.updateServiceInstanceMetadata(UUID.fromString(SERVICE_INSTANCE_GUID), ImmutableMetadata.builder() + .putAnnotation("obsolete", null) + .build()); factory.verify(); } @@ -973,7 +999,7 @@ void testBindServiceInstanceWithParameters() { @Test void testGetServiceBinding() { - stubGet(BINDINGS + "?guids=" + BINDING_GUID + "&per_page=5000", + stubGet(BINDINGS + "?guids=" + BINDING_GUID + "&per_page=50", "{\"resources\":[" + getBindingJson() + "]}"); CloudServiceBinding result = client.getServiceBinding(UUID.fromString(BINDING_GUID)); @@ -984,7 +1010,7 @@ void testGetServiceBinding() { @Test void testGetServiceAppBindings() { - stubGet(BINDINGS + "?service_instance_guids=" + SERVICE_INSTANCE_GUID + "&type=app&per_page=5000", + stubGet(BINDINGS + "?service_instance_guids=" + SERVICE_INSTANCE_GUID + "&type=app&per_page=50", "{\"resources\":[" + getBindingJson() + "]}"); List result = client.getServiceAppBindings(UUID.fromString(SERVICE_INSTANCE_GUID)); @@ -995,7 +1021,7 @@ void testGetServiceAppBindings() { @Test void testGetAppBindings() { - stubGet(BINDINGS + "?app_guids=" + APP_GUID + "&per_page=5000", "{\"resources\":[" + getBindingJson() + "]}"); + stubGet(BINDINGS + "?app_guids=" + APP_GUID + "&per_page=50", "{\"resources\":[" + getBindingJson() + "]}"); List result = client.getAppBindings(UUID.fromString(APP_GUID)); @@ -1005,7 +1031,7 @@ void testGetAppBindings() { @Test void testGetServiceBindingsForApplication() { - stubGet(BINDINGS + "?app_guids=" + APP_GUID + "&service_instance_guids=" + SERVICE_INSTANCE_GUID + "&per_page=5000", + stubGet(BINDINGS + "?app_guids=" + APP_GUID + "&service_instance_guids=" + SERVICE_INSTANCE_GUID + "&per_page=50", "{\"resources\":[" + getBindingJson() + "]}"); List result = client.getServiceBindingsForApplication(UUID.fromString(APP_GUID), @@ -1029,9 +1055,25 @@ void testGetServiceBindingParameters() { void testUpdateServiceBindingMetadata() { stubMethod(BINDINGS + "/" + BINDING_GUID, HttpMethod.PATCH); - client.updateServiceBindingMetadata(UUID.fromString(BINDING_GUID), Metadata.builder() - .label("k", "v") - .build()); + client.updateServiceBindingMetadata(UUID.fromString(BINDING_GUID), ImmutableMetadata.builder() + .putLabel("k", "v") + .build()); + + factory.verify(); + } + + @Test + void testUpdateServiceBindingMetadataUnsetsLabelWhenValueIsNull() { + factory.server() + .expect(MockRestRequestMatchers.requestTo(BINDINGS + "/" + BINDING_GUID)) + .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) + .andExpect(MockRestRequestMatchers.content() + .string(Matchers.containsString("\"obsolete\":null"))) + .andRespond(MockRestResponseCreators.withSuccess()); + + client.updateServiceBindingMetadata(UUID.fromString(BINDING_GUID), ImmutableMetadata.builder() + .putLabel("obsolete", null) + .build()); factory.verify(); } @@ -1052,7 +1094,7 @@ void testUnbindServiceInstanceByName() { stubGet(APPS_BASE + "&space_guids=" + SPACE_GUID + "&names=my-app", getAppListJson()); stubGet(SI_BASE + "&space_guids=" + SPACE_GUID + "&names=my-service", "{\"resources\":[" + getServiceInstanceJson() + "]}"); - stubGet(BINDINGS + "?app_guids=" + APP_GUID + "&service_instance_guids=" + SERVICE_INSTANCE_GUID + "&per_page=5000", + stubGet(BINDINGS + "?app_guids=" + APP_GUID + "&service_instance_guids=" + SERVICE_INSTANCE_GUID + "&per_page=50", "{\"resources\":[" + getBindingJson() + "]}"); stubMethod(BINDINGS + "/" + BINDING_GUID, HttpMethod.DELETE); @@ -1064,7 +1106,7 @@ void testUnbindServiceInstanceByName() { @Test void testUnbindServiceInstanceByGuid() { - stubGet(BINDINGS + "?app_guids=" + APP_GUID + "&service_instance_guids=" + SERVICE_INSTANCE_GUID + "&per_page=5000", + stubGet(BINDINGS + "?app_guids=" + APP_GUID + "&service_instance_guids=" + SERVICE_INSTANCE_GUID + "&per_page=50", "{\"resources\":[" + getBindingJson() + "]}"); stubMethod(BINDINGS + "/" + BINDING_GUID, HttpMethod.DELETE); @@ -1079,7 +1121,7 @@ void testGetServiceKey() { stubServiceInstanceLookupWithPlan(); stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_plans/" + PLAN_GUID, "{\"guid\":\"" + PLAN_GUID + "\",\"name\":\"my-plan\"}"); - stubGet(BINDINGS + "?per_page=5000&type=key&service_instance_guids=" + SERVICE_INSTANCE_GUID + "&names=my-key", + stubGet(BINDINGS + "?per_page=50&type=key&service_instance_guids=" + SERVICE_INSTANCE_GUID + "&names=my-key", "{\"resources\":[" + getServiceKeyJson() + "]}"); stubGet(BINDINGS + "/" + SERVICE_KEY_GUID + "/details", "{\"credentials\":{\"user\":\"admin\"}}"); @@ -1095,7 +1137,7 @@ void testGetServiceKeysByName() { stubServiceInstanceLookupWithPlan(); stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_plans/" + PLAN_GUID, "{\"guid\":\"" + PLAN_GUID + "\",\"name\":\"my-plan\"}"); - stubGet(BINDINGS + "?per_page=5000&type=key&service_instance_guids=" + SERVICE_INSTANCE_GUID, + stubGet(BINDINGS + "?per_page=50&type=key&service_instance_guids=" + SERVICE_INSTANCE_GUID, "{\"resources\":[" + getServiceKeyJson() + "]}"); List result = client.getServiceKeys("my-service"); @@ -1135,7 +1177,7 @@ void testCreateServiceBroker() { @Test void testGetServiceBroker() { - stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_brokers?names=my-broker&per_page=5000", getBrokerListJson()); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_brokers?names=my-broker&per_page=50", getBrokerListJson()); CloudServiceBroker result = client.getServiceBroker("my-broker"); @@ -1145,7 +1187,7 @@ void testGetServiceBroker() { @Test void testGetServiceBrokerWithRequiredFlag() { - stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_brokers?names=my-broker&per_page=5000", getBrokerListJson()); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_brokers?names=my-broker&per_page=50", getBrokerListJson()); Assertions.assertNotNull(client.getServiceBroker("my-broker", false)); factory.verify(); @@ -1153,7 +1195,7 @@ void testGetServiceBrokerWithRequiredFlag() { @Test void testGetServiceBrokers() { - stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_brokers?per_page=5000", getBrokerListJson()); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_brokers?per_page=50", getBrokerListJson()); List result = client.getServiceBrokers(); @@ -1163,7 +1205,7 @@ void testGetServiceBrokers() { @Test void testUpdateServiceBroker() { - stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_brokers?names=my-broker&per_page=5000", getBrokerListJson()); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_brokers?names=my-broker&per_page=50", getBrokerListJson()); stubMethod(MockControllerClientFactory.BASE_URL + "/v3/service_brokers/" + BROKER_GUID, HttpMethod.PATCH); CloudServiceBroker broker = ImmutableCloudServiceBroker.builder() @@ -1178,7 +1220,7 @@ void testUpdateServiceBroker() { @Test void testDeleteServiceBroker() { - stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_brokers?names=my-broker&per_page=5000", getBrokerListJson()); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_brokers?names=my-broker&per_page=50", getBrokerListJson()); stubMethod(MockControllerClientFactory.BASE_URL + "/v3/service_brokers/" + BROKER_GUID, HttpMethod.DELETE); Assertions.assertNull(client.deleteServiceBroker("my-broker")); @@ -1188,10 +1230,10 @@ void testDeleteServiceBroker() { @Test void testUpdateServicePlanVisibilityForBroker() { stubTargetSpaceMetadata(); - stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_brokers?names=my-broker&per_page=5000", getBrokerListJson()); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_brokers?names=my-broker&per_page=50", getBrokerListJson()); stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_offerings?service_broker_guids=" + BROKER_GUID + "&space_guids=" - + SPACE_GUID + "&per_page=5000", "{\"resources\":[{\"guid\":\"" + OFFERING_GUID + "\"}]}"); - stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_plans?service_offering_guids=" + OFFERING_GUID + "&per_page=5000", + + SPACE_GUID + "&per_page=50", "{\"resources\":[{\"guid\":\"" + OFFERING_GUID + "\"}]}"); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_plans?service_offering_guids=" + OFFERING_GUID + "&per_page=50", "{\"resources\":[{\"guid\":\"" + PLAN_GUID + "\"}]}"); stubMethod(MockControllerClientFactory.BASE_URL + "/v3/service_plans/" + PLAN_GUID + "/visibility", HttpMethod.PATCH); @@ -1202,10 +1244,10 @@ void testUpdateServicePlanVisibilityForBroker() { @Test void testGetServiceOfferings() { - when(target.getGuid()).thenReturn(UUID.fromString(SPACE_GUID)); - stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_offerings?per_page=5000&space_guids=" + SPACE_GUID, + when(target.getMetadata()).thenReturn(ImmutableCloudMetadata.of(UUID.fromString(SPACE_GUID))); + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_offerings?per_page=50&space_guids=" + SPACE_GUID, "{\"resources\":[{\"guid\":\"" + OFFERING_GUID + "\",\"name\":\"my-offering\"}]}"); - stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_plans?service_offering_guids=" + OFFERING_GUID + "&per_page=5000", + stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_plans?service_offering_guids=" + OFFERING_GUID + "&per_page=50", "{\"resources\":[{\"guid\":\"" + PLAN_GUID + "\",\"name\":\"my-plan\"}]}"); List result = client.getServiceOfferings(); @@ -1272,7 +1314,7 @@ void testGetPackage() { @Test void testGetPackagesForApplication() { - stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/packages?per_page=5000", + stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/packages?per_page=50", "{\"resources\":[" + getPackageJson() + "]}"); List result = client.getPackagesForApplication(UUID.fromString(APP_GUID)); @@ -1283,7 +1325,7 @@ void testGetPackagesForApplication() { @Test void testGetUserRolesBySpaceAndUser() { - stubGet(MockControllerClientFactory.BASE_URL + "/v3/roles?per_page=5000&space_guids=" + SPACE_GUID + "&user_guids=" + APP_GUID, + stubGet(MockControllerClientFactory.BASE_URL + "/v3/roles?per_page=50&space_guids=" + SPACE_GUID + "&user_guids=" + APP_GUID, "{\"resources\":[{\"guid\":\"" + BINDING_GUID + "\",\"type\":\"space_developer\"}]}"); Set result = client.getUserRolesBySpaceAndUser(UUID.fromString(SPACE_GUID), UUID.fromString(APP_GUID)); @@ -1311,7 +1353,7 @@ private void stubTargetSpaceMetadata() { private void stubServiceInstanceLookup() { factory.server() .expect(MockRestRequestMatchers.requestTo( - MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=5000&space_guids=" + SPACE_GUID + MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=50&space_guids=" + SPACE_GUID + "&names=my-service")) .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[" + getServiceInstanceJson() + "]}", @@ -1321,7 +1363,7 @@ private void stubServiceInstanceLookup() { private void stubServiceInstanceLookupWithPlan() { factory.server() .expect(MockRestRequestMatchers.requestTo( - MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=5000&space_guids=" + SPACE_GUID + MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=50&space_guids=" + SPACE_GUID + "&names=my-service")) .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[" + getServiceInstanceWithPlanJson() + "]}", diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3OperationsTest.java index 8ded132d8c..9e7fb1d13d 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/DomainsV3OperationsTest.java @@ -187,7 +187,7 @@ void testGetDomainsForOrganizationReturnsMappedDomains() { when(targetSpace.getOrganization()) .thenReturn(getOrganizationWithGuid(ORG_GUID)); - factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/organizations/" + ORG_GUID + "/domains?per_page=5000", + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/organizations/" + ORG_GUID + "/domains?per_page=50", "{\"resources\":[" + getDomainResourceJson(DOMAIN_GUID, DOMAIN_NAME) + "]}"); List result = serverOperations.getDomainsForOrganization(); @@ -203,7 +203,7 @@ void testGetDomainsForOrganizationReturnsEmptyWhenNoDomains() { when(targetSpace.getOrganization()) .thenReturn(getOrganizationWithGuid(ORG_GUID)); - factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/organizations/" + ORG_GUID + "/domains?per_page=5000", + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/organizations/" + ORG_GUID + "/domains?per_page=50", "{\"resources\":[]}"); Assertions.assertTrue(serverOperations.getDomainsForOrganization() @@ -219,12 +219,12 @@ void testGetDomainsForOrganizationThrowsWhenSpaceNotProvided() { } private void stubEmptyDomainLookup(String name) { - factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + name + "&per_page=5000", + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + name + "&per_page=50", "{\"resources\":[]}"); } private void stubDomainLookupWithResult(String name, String domainGuid) { - factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + name + "&per_page=5000", + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + name + "&per_page=50", "{\"resources\":[" + getDomainResourceJson(domainGuid, name) + "]}"); } @@ -246,13 +246,13 @@ ArgumentMatchers.>> any())) } private static V3Domain getSharedDomain(String name) { - return new V3Domain(GUID_STRING, name, null, null, null, null); + return new V3Domain(GUID_STRING, name, null, null, null, null, null); } private static V3Domain getPrivateDomain(String name) { V3DomainRelationships relationships = new V3DomainRelationships(new V3ToOneRelationship(new V3RelationshipData(ORG_GUID))); - return new V3Domain(GUID_STRING, name, null, null, null, relationships); + return new V3Domain(GUID_STRING, name, null, null, null, null, relationships); } } diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3OperationsTest.java index 1dff0adf1a..9fa0cf7e8c 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/PackagesV3OperationsTest.java @@ -75,7 +75,7 @@ void testGetPackageThrowsOnNotFoundStatus() { @Test void testGetPackagesForApplicationReturnsMappedList() { - factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/packages?per_page=5000", + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/packages?per_page=50", "{\"resources\":[" + bitsPackageJson("READY", null) + "]}"); List result = operations.getPackagesForApplication(UUID.fromString(APP_GUID)); @@ -88,7 +88,7 @@ void testGetPackagesForApplicationReturnsMappedList() { @Test void testGetPackagesForApplicationReturnsEmptyList() { - factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/packages?per_page=5000", + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/packages?per_page=50", "{\"resources\":[]}"); List result = operations.getPackagesForApplication(UUID.fromString(APP_GUID)); diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3OperationsTest.java index 798a9c6857..4f538824d3 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ProcessesV3OperationsTest.java @@ -132,26 +132,10 @@ void testGetApplicationInstancesForStoppedApplicationReturnsEmptyWithoutHttp() { factory.verify(); } - @Test - void testGetApplicationSshEnabledReturnsTrue() { - factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/ssh_enabled", "{\"enabled\":true,\"reason\":\"\"}"); - - Assertions.assertTrue(operations.getApplicationSshEnabled(UUID.fromString(APP_GUID))); - factory.verify(); - } - - @Test - void testGetApplicationSshEnabledReturnsFalseWhenEnabledIsNull() { - factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/ssh_enabled", "{\"reason\":\"disabled globally\"}"); - - Assertions.assertFalse(operations.getApplicationSshEnabled(UUID.fromString(APP_GUID))); - factory.verify(); - } - @Test void testGetApplicationFeaturesReturnsMap() { - factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/features?per_page=5000", - "{\"resources\":[{\"name\":\"ssh\",\"enabled\":true},{\"name\":\"revisions\",\"enabled\":false}]}"); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/features?per_page=50", + "{\"resources\":[{\"name\":\"ssh\",\"enabled\":true},{\"name\":\"revisions\",\"enabled\":false}]}"); Map result = operations.getApplicationFeatures(UUID.fromString(APP_GUID)); @@ -163,7 +147,7 @@ void testGetApplicationFeaturesReturnsMap() { @Test void testGetApplicationFeaturesReturnsEmptyMap() { - factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/features?per_page=5000", "{\"resources\":[]}"); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/features?per_page=50", "{\"resources\":[]}"); Map result = operations.getApplicationFeatures(UUID.fromString(APP_GUID)); @@ -174,7 +158,7 @@ void testGetApplicationFeaturesReturnsEmptyMap() { @Test void testGetCurrentDropletForApplicationReturnsDropletInfo() { factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/droplets/current", - getDropletJson(MockControllerClientFactory.BASE_URL + "/v3/packages/" + PACKAGE_GUID)); + getDropletJson(MockControllerClientFactory.BASE_URL + "/v3/packages/" + PACKAGE_GUID)); DropletInfo result = operations.getCurrentDropletForApplication(UUID.fromString(APP_GUID)); @@ -186,7 +170,7 @@ void testGetCurrentDropletForApplicationReturnsDropletInfo() { @Test void testGetCurrentDropletForApplicationHandlesTrailingSlashInPackageLink() { factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/droplets/current", - getDropletJson(MockControllerClientFactory.BASE_URL + "/v3/packages/" + PACKAGE_GUID + "/")); + getDropletJson(MockControllerClientFactory.BASE_URL + "/v3/packages/" + PACKAGE_GUID + "/")); DropletInfo result = operations.getCurrentDropletForApplication(UUID.fromString(APP_GUID)); @@ -212,7 +196,8 @@ void testUpdateApplicationStagingUpdatesAppAndProcess() { when(target.getGuid()) .thenReturn(null); - factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000&names=my-app", "{\"resources\":[{\"guid\":\"" + APP_GUID + "\"}]}"); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=50&names=my-app", + "{\"resources\":[{\"guid\":\"" + APP_GUID + "\"}]}"); factory.server() .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID)) @@ -247,7 +232,8 @@ void testUpdateApplicationStagingUpdatesAppFeatures() { when(target.getGuid()) .thenReturn(null); - factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000&names=my-app", "{\"resources\":[{\"guid\":\"" + APP_GUID + "\"}]}"); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=50&names=my-app", + "{\"resources\":[{\"guid\":\"" + APP_GUID + "\"}]}"); factory.server() .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID)) .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) @@ -282,8 +268,8 @@ void testUpdateApplicationStagingIncludesSpaceGuidInLookupQuery() { when(target.getGuid()) .thenReturn(spaceGuid); - factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000&space_guids=" - + spaceGuid + "&names=my-app", "{\"resources\":[{\"guid\":\"" + APP_GUID + "\"}]}"); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=50&space_guids=" + + spaceGuid + "&names=my-app", "{\"resources\":[{\"guid\":\"" + APP_GUID + "\"}]}"); factory.server() .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID)) @@ -312,7 +298,7 @@ void testUpdateApplicationStagingThrowsWhenApplicationNotFound() { .thenReturn(null); factory.stubGet(MockControllerClientFactory.BASE_URL - + "/v3/apps?per_page=5000&names=missing-app", "{\"resources\":[]}"); + + "/v3/apps?per_page=50&names=missing-app", "{\"resources\":[]}"); Staging staging = ImmutableStaging.builder() .command("cmd") @@ -326,7 +312,8 @@ void testUpdateApplicationStagingWithCnbLifecycleWithoutBuildpacksThrows() { when(target.getGuid()) .thenReturn(null); - factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000&names=my-app", "{\"resources\":[{\"guid\":\"" + APP_GUID + "\"}]}"); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=50&names=my-app", + "{\"resources\":[{\"guid\":\"" + APP_GUID + "\"}]}"); Staging staging = ImmutableStaging.builder() .command("cmd") diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ReactiveFanOutTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ReactiveFanOutTest.java index 192acd89a1..d9c3fcdc1b 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ReactiveFanOutTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ReactiveFanOutTest.java @@ -32,14 +32,14 @@ void testMapConcurrentlyResultPreserveInputOrderEvenWhenTasksFinishOutOfOrder() List input = IntStream.rangeClosed(1, 20) .boxed() .toList(); - CountDownLatch lastItemDone = new CountDownLatch(1); + CountDownLatch secondItemDone = new CountDownLatch(1); List result = ReactiveFanOut.mapConcurrently(input, i -> { - if (i == 20) { - lastItemDone.countDown(); + if (i == 2) { + secondItemDone.countDown(); } else if (i == 1) { try { - lastItemDone.await(); + secondItemDone.await(); } catch (InterruptedException e) { Thread.currentThread() .interrupt(); diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RolesV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RolesV3OperationsTest.java index d1d2ccc194..345c31b01c 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RolesV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RolesV3OperationsTest.java @@ -20,7 +20,7 @@ class RolesV3OperationsTest { private static final UUID SPACE_GUID = UUID.fromString("11111111-2222-3333-4444-555555555555"); private static final UUID USER_GUID = UUID.fromString("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"); - private static final String ROLES_URI = "/v3/roles?per_page=5000&space_guids=" + SPACE_GUID + "&user_guids=" + USER_GUID; + private static final String ROLES_URI = "/v3/roles?per_page=50&space_guids=" + SPACE_GUID + "&user_guids=" + USER_GUID; @Mock private CloudSpace target; diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3OperationsTest.java index e3279c3a1c..d0df606a2a 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/RoutesV3OperationsTest.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.Set; import java.util.UUID; +import java.util.stream.Collectors; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudRoute; @@ -28,6 +29,7 @@ class RoutesV3OperationsTest { private static final String SPACE_GUID = "11111111-1111-1111-1111-111111111111"; private static final String DOMAIN_GUID = "22222222-2222-2222-2222-222222222222"; private static final String ROUTE_GUID = "33333333-3333-3333-3333-333333333333"; + private static final String SECOND_ROUTE_GUID = "77777777-7777-7777-7777-777777777777"; private static final String APP_GUID = "44444444-4444-4444-4444-444444444444"; private static final String DESTINATION_GUID = "55555555-5555-5555-5555-555555555555"; private static final String JOB_GUID = "66666666-6666-6666-6666-666666666666"; @@ -52,11 +54,13 @@ void testAddRoutePostsToRoutes() { when(target.getGuid()) .thenReturn(UUID.fromString(SPACE_GUID)); + String route = getRouteJson(ROUTE_GUID, "myhost", "/mypath", "myhost.example.com/mypath"); + stubDomainLookup(DOMAIN_NAME, DOMAIN_GUID); factory.server() .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/routes")) .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) - .andRespond(MockRestResponseCreators.withSuccess(getRouteJson(), MediaType.APPLICATION_JSON)); + .andRespond(MockRestResponseCreators.withSuccess(route, MediaType.APPLICATION_JSON)); operations.addRoute("myhost", DOMAIN_NAME, "/mypath"); @@ -130,9 +134,11 @@ void testGetRoutesReturnsMappedRoutes() { when(target.getGuid()) .thenReturn(UUID.fromString(SPACE_GUID)); + String firstRoute = getRouteJson(ROUTE_GUID, "myhost", "/mypath", "myhost.example.com/mypath"); + stubDomainLookup(DOMAIN_NAME, DOMAIN_GUID); - factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/routes?per_page=5000&domain_guids=" - + DOMAIN_GUID + "&space_guids=" + SPACE_GUID, "{\"resources\":[" + getRouteJson() + "]}"); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/routes?per_page=50&domain_guids=" + + DOMAIN_GUID + "&space_guids=" + SPACE_GUID, "{\"resources\":[" + firstRoute + "]}"); List routes = operations.getRoutes(DOMAIN_NAME); @@ -142,6 +148,31 @@ void testGetRoutesReturnsMappedRoutes() { factory.verify(); } + @Test + void testGetRoutesReturnsMultipleMappedRoutes() { + when(target.getGuid()) + .thenReturn(UUID.fromString(SPACE_GUID)); + + String firstRoute = getRouteJson(ROUTE_GUID, "myhost", "/mypath", "myhost.example.com/mypath"); + String secondRoute = getRouteJson(SECOND_ROUTE_GUID, "otherhost", "/otherpath", "otherhost.example.com/otherpath"); + + stubDomainLookup(DOMAIN_NAME, DOMAIN_GUID); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/routes?per_page=50&domain_guids=" + + DOMAIN_GUID + "&space_guids=" + SPACE_GUID, "{\"resources\":[" + firstRoute + "," + secondRoute + "]}"); + + List routes = operations.getRoutes(DOMAIN_NAME); + + Assertions.assertEquals(2, routes.size()); + Assertions.assertEquals(Set.of("myhost", "otherhost"), routes.stream() + .map(CloudRoute::getHost) + .collect(Collectors.toSet())); + Assertions.assertEquals(Set.of("myhost.example.com/mypath", "otherhost.example.com/otherpath"), + routes.stream() + .map(CloudRoute::getUrl) + .collect(Collectors.toSet())); + factory.verify(); + } + @Test void testGetRoutesThrowsWhenDomainNotFound() { when(target.getGuid()) @@ -161,8 +192,10 @@ void testGetRoutesThrowsWhenSpaceNotProvided() { @Test void testGetApplicationRoutesReturnsMappedRoutes() { - factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/routes?per_page=5000", - "{\"resources\":[" + getRouteJson() + "]}"); + String route = getRouteJson(ROUTE_GUID, "myhost", "/mypath", "myhost.example.com/mypath"); + + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/routes?per_page=50", + "{\"resources\":[" + route + "]}"); List routes = operations.getApplicationRoutes(UUID.fromString(APP_GUID)); @@ -174,8 +207,8 @@ void testGetApplicationRoutesReturnsMappedRoutes() { @Test void testGetApplicationRoutesReturnsEmptyList() { - factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/routes?per_page=5000", - "{\"resources\":[]}"); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/routes?per_page=50", + "{\"resources\":[]}"); List routes = operations.getApplicationRoutes(UUID.fromString(APP_GUID)); @@ -188,21 +221,23 @@ void testUpdateApplicationRoutesAddsNewRoute() { when(target.getGuid()) .thenReturn(UUID.fromString(SPACE_GUID)); + String route = getRouteJson(ROUTE_GUID, "myhost", "/mypath", "myhost.example.com/mypath"); + stubAppLookup("my-app"); - factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/routes?per_page=5000", - "{\"resources\":[]}"); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/routes?per_page=50", + "{\"resources\":[]}"); - factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + DOMAIN_NAME + "&per_page=5000", - getDomainJson(DOMAIN_GUID)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + DOMAIN_NAME + "&per_page=50", + getDomainJson(DOMAIN_GUID)); - factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/routes?per_page=5000&domain_guids=" - + DOMAIN_GUID + "&space_guids=" + SPACE_GUID + "&hosts=myhost&paths=/mypath", "{\"resources\":[]}"); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/routes?per_page=50&domain_guids=" + + DOMAIN_GUID + "&space_guids=" + SPACE_GUID + "&hosts=myhost&paths=/mypath", "{\"resources\":[]}"); factory.server() .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/routes")) .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) - .andRespond(MockRestResponseCreators.withSuccess(getRouteJson(), MediaType.APPLICATION_JSON)); + .andRespond(MockRestResponseCreators.withSuccess(route, MediaType.APPLICATION_JSON)); factory.stubMethod(MockControllerClientFactory.BASE_URL + "/v3/routes/" + ROUTE_GUID + "/destinations", HttpMethod.POST); @@ -227,11 +262,11 @@ void testUpdateApplicationRoutesRemovesOutdatedRoute() { stubAppLookup("my-app"); - factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/routes?per_page=5000", - "{\"resources\":[" + getRouteWithDestinationJson() + "]}"); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps/" + APP_GUID + "/routes?per_page=50", + "{\"resources\":[" + getRouteWithDestinationJson() + "]}"); factory.stubMethod(MockControllerClientFactory.BASE_URL + "/v3/routes/" + ROUTE_GUID + "/destinations/" + DESTINATION_GUID, - HttpMethod.DELETE); + HttpMethod.DELETE); operations.updateApplicationRoutes("my-app", Set.of()); @@ -243,30 +278,32 @@ void testUpdateApplicationRoutesThrowsWhenApplicationNotFound() { when(target.getGuid()) .thenReturn(UUID.fromString(SPACE_GUID)); - factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000" - + "&space_guids=" + SPACE_GUID + "&names=missing-app", "{\"resources\":[]}"); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=50" + + "&space_guids=" + SPACE_GUID + "&names=missing-app", "{\"resources\":[]}"); Assertions.assertThrows(CloudOperationException.class, () -> operations.updateApplicationRoutes("missing-app", Set.of())); } private void stubDomainLookup(String name, String domainGuid) { - factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + name + "&per_page=5000", - getDomainJson(domainGuid)); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + name + "&per_page=50", + getDomainJson(domainGuid)); } private void stubEmptyDomainLookup(String name) { - factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + name + "&per_page=5000", - "{\"resources\":[]}"); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/domains?names=" + name + "&per_page=50", + "{\"resources\":[]}"); } private void stubRouteLookupWithResult() { - factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/routes?per_page=5000&domain_guids=" - + DOMAIN_GUID + "&space_guids=" + SPACE_GUID + "&hosts=myhost&paths=/mypath", "{\"resources\":[" + getRouteJson() + "]}"); + String route = getRouteJson(ROUTE_GUID, "myhost", "/mypath", "myhost.example.com/mypath"); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/routes?per_page=50&domain_guids=" + + DOMAIN_GUID + "&space_guids=" + SPACE_GUID + "&hosts=myhost&paths=/mypath", + "{\"resources\":[" + route + "]}"); } private void stubAppLookup(String appName) { - factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000" - + "&space_guids=" + SPACE_GUID + "&names=" + appName, "{\"resources\":[{\"guid\":\"" + APP_GUID + "\"}]}"); + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=50" + + "&space_guids=" + SPACE_GUID + "&names=" + appName, "{\"resources\":[{\"guid\":\"" + APP_GUID + "\"}]}"); } private void stubAsyncDelete(String uri) { @@ -276,11 +313,11 @@ private void stubAsyncDelete(String uri) { .andRespond(MockRestResponseCreators.withStatus(HttpStatus.ACCEPTED) .location(URI.create(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID))); factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/jobs/" + JOB_GUID, - "{\"guid\":\"" + JOB_GUID + "\",\"state\":\"COMPLETE\"}"); + "{\"guid\":\"" + JOB_GUID + "\",\"state\":\"COMPLETE\"}"); } - private static String getRouteJson() { - return "{\"guid\":\"" + ROUTE_GUID + "\",\"host\":\"myhost\",\"path\":\"/mypath\",\"url\":\"myhost.example.com/mypath\"," + private static String getRouteJson(String guid, String host, String path, String url) { + return "{\"guid\":\"" + guid + "\",\"host\":\"" + host + "\",\"path\":\"" + path + "\",\"url\":\"" + url + "\"," + "\"destinations\":[],\"relationships\":{\"space\":{\"data\":{\"guid\":\"" + SPACE_GUID + "\"}}," + "\"domain\":{\"data\":{\"guid\":\"" + DOMAIN_GUID + "\"}}}}"; } diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3OperationsTest.java index e69b24ab1d..a1d63faefa 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3OperationsTest.java @@ -9,6 +9,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBinding; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -91,13 +92,13 @@ void testBindServiceInstanceUsesSpaceGuidInLookupWhenTargetHasGuid() { .thenReturn(spaceGuid); factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000&space_guids=" + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=50&space_guids=" + spaceGuid + "&names=my-app")) .andRespond(MockRestResponseCreators.withSuccess(getResourcesJson(APP_GUID), MediaType.APPLICATION_JSON)); factory.server() .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL - + "/v3/service_instances?per_page=5000&space_guids=" + spaceGuid + + "/v3/service_instances?per_page=50&space_guids=" + spaceGuid + "&names=my-service")) .andRespond(MockRestResponseCreators.withSuccess(getResourcesJson(SERVICE_INSTANCE_GUID), MediaType.APPLICATION_JSON)); @@ -118,7 +119,7 @@ void testBindServiceInstanceThrowsWhenApplicationNotFound() { .thenReturn(null); factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000&names=missing-app")) + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=50&names=missing-app")) .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); Assertions.assertThrows(CloudOperationException.class, @@ -133,7 +134,7 @@ void testBindServiceInstanceThrowsWhenServiceInstanceNotFound() { stubAppLookup("my-app"); factory.server() .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL - + "/v3/service_instances?per_page=5000&names=missing-service")) + + "/v3/service_instances?per_page=50&names=missing-service")) .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); Assertions.assertThrows(CloudOperationException.class, @@ -152,7 +153,7 @@ void testUnbindServiceInstanceByNameDeletesFoundBindings() { .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings?app_guids=" + APP_GUID + "&service_instance_guids=" + SERVICE_INSTANCE_GUID - + "&per_page=5000")) + + "&per_page=50")) .andRespond(MockRestResponseCreators.withSuccess(getResourcesJson(BINDING_GUID), MediaType.APPLICATION_JSON)); factory.server() @@ -177,7 +178,7 @@ void testUnbindServiceInstanceByGuidDeletesFoundBindings() { .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings?app_guids=" + APP_GUID + "&service_instance_guids=" + SERVICE_INSTANCE_GUID - + "&per_page=5000")) + + "&per_page=50")) .andRespond(MockRestResponseCreators.withSuccess(getResourcesJson(BINDING_GUID), MediaType.APPLICATION_JSON)); factory.server() @@ -201,7 +202,7 @@ void testUnbindServiceInstanceThrowsWhenNoBindingFound() { .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings?app_guids=" + APP_GUID + "&service_instance_guids=" + SERVICE_INSTANCE_GUID - + "&per_page=5000")) + + "&per_page=50")) .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); Assertions.assertThrows(CloudOperationException.class, @@ -228,7 +229,7 @@ void testDeleteServiceBindingReturnsJobGuid() { @Test void testGetServiceBindingReturnsMappedBinding() { factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings?guids=" - + BINDING_GUID + "&per_page=5000", getBindingListJson()); + + BINDING_GUID + "&per_page=50", getBindingListJson()); CloudServiceBinding result = operations.getServiceBinding(UUID.fromString(BINDING_GUID)); @@ -240,7 +241,7 @@ void testGetServiceBindingReturnsMappedBinding() { @Test void testGetServiceBindingReturnsNullWhenNoBindingFound() { factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings?guids=" - + BINDING_GUID + "&per_page=5000", "{\"resources\":[]}"); + + BINDING_GUID + "&per_page=50", "{\"resources\":[]}"); CloudServiceBinding result = operations.getServiceBinding(UUID.fromString(BINDING_GUID)); @@ -252,7 +253,7 @@ void testGetServiceBindingReturnsNullWhenNoBindingFound() { void testGetServiceAppBindingsReturnsMappedList() { factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings?service_instance_guids=" - + SERVICE_INSTANCE_GUID + "&type=app&per_page=5000", getBindingListJson()); + + SERVICE_INSTANCE_GUID + "&type=app&per_page=50", getBindingListJson()); List result = operations.getServiceAppBindings(UUID.fromString(SERVICE_INSTANCE_GUID)); @@ -265,7 +266,7 @@ void testGetServiceAppBindingsReturnsMappedList() { @Test void testGetAppBindingsReturnsEmptyList() { factory.stubGet(MockControllerClientFactory.BASE_URL - + "/v3/service_credential_bindings?app_guids=" + APP_GUID + "&per_page=5000", "{\"resources\":[]}"); + + "/v3/service_credential_bindings?app_guids=" + APP_GUID + "&per_page=50", "{\"resources\":[]}"); List result = operations.getAppBindings(UUID.fromString(APP_GUID)); @@ -278,7 +279,7 @@ void testGetServiceBindingsForApplicationReturnsMappedList() { factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_credential_bindings?app_guids=" + APP_GUID + "&service_instance_guids=" + SERVICE_INSTANCE_GUID - + "&per_page=5000", getBindingListJson()); + + "&per_page=50", getBindingListJson()); List result = operations.getServiceBindingsForApplication(UUID.fromString(APP_GUID), UUID.fromString(SERVICE_INSTANCE_GUID)); @@ -319,8 +320,8 @@ void testUpdateServiceBindingMetadataUpdates() { .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) .andRespond(MockRestResponseCreators.withSuccess("{\"guid\":\"" + BINDING_GUID + "\"}", MediaType.APPLICATION_JSON)); - Metadata metadata = Metadata.builder() - .label("key", "value") + Metadata metadata = ImmutableMetadata.builder() + .putLabel("key", "value") .build(); Assertions.assertDoesNotThrow(() -> operations.updateServiceBindingMetadata(UUID.fromString(BINDING_GUID), metadata)); @@ -335,7 +336,7 @@ void testUpdateServiceBindingMetadataThrowsOnError() { .andExpect(MockRestRequestMatchers.method(HttpMethod.PATCH)) .andRespond(MockRestResponseCreators.withStatus(HttpStatus.UNPROCESSABLE_ENTITY)); - Metadata metadata = Metadata.builder() + Metadata metadata = ImmutableMetadata.builder() .build(); UUID bindingGuid = UUID.fromString(BINDING_GUID); @@ -345,13 +346,13 @@ void testUpdateServiceBindingMetadataThrowsOnError() { private void stubAppLookup(String appName) { factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000&names=" + appName)) + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=50&names=" + appName)) .andRespond(MockRestResponseCreators.withSuccess(getResourcesJson(APP_GUID), MediaType.APPLICATION_JSON)); } private void stubServiceInstanceLookup(String serviceName) { factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=5000&names=" + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=50&names=" + serviceName)) .andRespond(MockRestResponseCreators.withSuccess(getResourcesJson(SERVICE_INSTANCE_GUID), MediaType.APPLICATION_JSON)); } diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBrokersV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBrokersV3OperationsTest.java index 0f15e77353..83fb192b09 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBrokersV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBrokersV3OperationsTest.java @@ -51,7 +51,7 @@ void setUp() throws Exception { @Test void testGetServiceBrokersReturnsMappedList() { - factory.stubGet(BROKERS_URI + "?per_page=5000", "{\"resources\":[" + getBrokerJson("broker-a") + "]}"); + factory.stubGet(BROKERS_URI + "?per_page=50", "{\"resources\":[" + getBrokerJson("broker-a") + "]}"); List result = operations.getServiceBrokers(); @@ -65,7 +65,7 @@ void testGetServiceBrokersReturnsMappedList() { @Test void testGetServiceBrokersReturnsEmptyList() { - factory.stubGet(BROKERS_URI + "?per_page=5000", "{\"resources\":[]}"); + factory.stubGet(BROKERS_URI + "?per_page=50", "{\"resources\":[]}"); List result = operations.getServiceBrokers(); @@ -220,11 +220,11 @@ void testUpdateServicePlanVisibilityForBrokerUpdatesEachPlan() { stubBrokerLookup("broker-a", "{\"resources\":[" + getBrokerJson("broker-a") + "]}"); factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_offerings?service_broker_guids=" - + BROKER_GUID + "&space_guids=" + SPACE_GUID + "&per_page=5000", + + BROKER_GUID + "&space_guids=" + SPACE_GUID + "&per_page=50", "{\"resources\":[{\"guid\":\"" + OFFERING_GUID + "\"}]}"); factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_plans?service_offering_guids=" - + OFFERING_GUID + "&per_page=5000", + + OFFERING_GUID + "&per_page=50", "{\"resources\":[{\"guid\":\"" + PLAN_GUID + "\"}]}"); factory.server() @@ -249,7 +249,7 @@ void testUpdateServicePlanVisibilityForBrokerWithNoOfferingsUpdatesNothing() { stubBrokerLookup("broker-a", "{\"resources\":[" + getBrokerJson("broker-a") + "]}"); factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_offerings?service_broker_guids=" - + BROKER_GUID + "&space_guids=" + SPACE_GUID + "&per_page=5000", "{\"resources\":[]}"); + + BROKER_GUID + "&space_guids=" + SPACE_GUID + "&per_page=50", "{\"resources\":[]}"); operations.updateServicePlanVisibilityForBroker("broker-a", ServicePlanVisibility.ADMIN); @@ -266,7 +266,7 @@ void testUpdateServicePlanVisibilityForBrokerThrowsWhenBrokerMissing() { } private void stubBrokerLookup(String name, String responseJson) { - factory.stubGet(BROKERS_URI + "?names=" + name + "&per_page=5000", responseJson); + factory.stubGet(BROKERS_URI + "?names=" + name + "&per_page=50", responseJson); } private static String getBrokerJson(String name) { diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3OperationsTest.java index e7fe1f6af1..f88f8a8cef 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3OperationsTest.java @@ -9,6 +9,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceInstanceType; import org.junit.jupiter.api.Assertions; @@ -309,8 +310,8 @@ void testUpdateServiceSyslogDrainUrlSkipsManaged() { void testUpdateServiceInstanceMetadataUpdates() { factory.stubMethod(MockControllerClientFactory.BASE_URL + "/v3/service_instances/" + GUID_STRING, HttpMethod.PATCH); - Metadata metadata = Metadata.builder() - .label("key", "value") + Metadata metadata = ImmutableMetadata.builder() + .putLabel("key", "value") .build(); operations.updateServiceInstanceMetadata(UUID.fromString(GUID_STRING), metadata); @@ -329,7 +330,7 @@ void testUpdateServiceInstanceMetadataUpdatesWithNullMetadata() { @Test void testGetServiceInstancesByMetadataLabelSelectorMapsUserProvided() { - factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=5000" + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=50" + "&space_guids=" + SPACE_GUID + "&label_selector=env", "{\"resources\":[" + getUserProvidedInstanceJson("ups-1") + "]}"); @@ -343,7 +344,7 @@ void testGetServiceInstancesByMetadataLabelSelectorMapsUserProvided() { @Test void testGetServiceInstancesByMetadataLabelSelectorReturnsEmpty() { - factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=5000" + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=50" + "&space_guids=" + SPACE_GUID + "&label_selector=env", "{\"resources\":[]}"); List result = operations.getServiceInstancesByMetadataLabelSelector("env"); @@ -354,7 +355,7 @@ void testGetServiceInstancesByMetadataLabelSelectorReturnsEmpty() { @Test void testGetServiceInstancesWithoutAuxiliaryContentByMetadataLabelSelectorMaps() { - factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=5000" + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=50" + "&space_guids=" + SPACE_GUID + "&label_selector=env", "{\"resources\":[" + getManagedInstanceJson("mi-1") + "]}"); @@ -370,7 +371,7 @@ void testGetServiceInstancesWithoutAuxiliaryContentByMetadataLabelSelectorMaps() @Test void testGetServiceInstancesWithoutAuxiliaryContentByNames() { - factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=5000" + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=50" + "&space_guids=" + SPACE_GUID + "&names=my-service", "{\"resources\":[" + getManagedInstanceJson("my-service") + "]}"); @@ -393,19 +394,19 @@ void testGetServiceInstancesWithoutAuxiliaryContentByNamesReturnsEmptyForEmptyIn private void stubServiceInstanceLookup(String name, String instanceJson) { String body = instanceJson == null ? "{\"resources\":[]}" : "{\"resources\":[" + instanceJson + "]}"; - factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=5000" + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_instances?per_page=50" + "&space_guids=" + SPACE_GUID + "&names=" + name, body); } private void stubOfferingLookup(String label, String broker) { - String uri = MockControllerClientFactory.BASE_URL + "/v3/service_offerings?per_page=5000&space_guids=" + SPACE_GUID + "&names=" + String uri = MockControllerClientFactory.BASE_URL + "/v3/service_offerings?per_page=50&space_guids=" + SPACE_GUID + "&names=" + label + (broker == null ? "" : "&service_broker_names=" + broker); factory.stubGet(uri, "{\"resources\":[{\"guid\":\"" + OFFERING_GUID + "\",\"name\":\"" + label + "\"}]}"); } private void stubPlanLookup(String planName) { - factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_plans?per_page=5000" + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_plans?per_page=50" + "&service_offering_guids=" + OFFERING_GUID + "&names=" + planName, "{\"resources\":[{\"guid\":\"" + PLAN_GUID + "\",\"name\":\"" + planName + "\"}]}"); } diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3OperationsTest.java index 01b177bc71..6b14755df4 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceKeysV3OperationsTest.java @@ -3,7 +3,9 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.UUID; +import java.util.stream.Collectors; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; @@ -24,10 +26,12 @@ class ServiceKeysV3OperationsTest { private static final String SERVICE_INSTANCE_GUID = "11111111-2222-3333-4444-555555555555"; private static final String KEY_GUID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"; + private static final String SECOND_KEY_GUID = "bbbbbbbb-cccc-dddd-eeee-ffffffffffff"; private static final String JOB_GUID = "99999999-8888-7777-6666-555555555555"; private static final String KEY_NAME = "my-key"; + private static final String SECOND_KEY_NAME = "my-other-key"; - private static final String LIST_KEYS_QUERY = "/v3/service_credential_bindings?per_page=5000&type=key&service_instance_guids=" + private static final String LIST_KEYS_QUERY = "/v3/service_credential_bindings?per_page=50&type=key&service_instance_guids=" + SERVICE_INSTANCE_GUID; private static final String LOOKUP_KEY_QUERY = LIST_KEYS_QUERY + "&names=" + KEY_NAME; @@ -79,6 +83,25 @@ void testGetServiceKeysReturnsMappedKeysWithoutCredentials() { factory.verify(); } + @Test + void testGetServiceKeysReturnsMultipleMappedKeysWithoutCredentials() { + stubKeyList("{\"resources\":[" + getServiceKeyJson(KEY_GUID, KEY_NAME) + "," + getServiceKeyJson(SECOND_KEY_GUID, SECOND_KEY_NAME) + + "]}"); + + List result = operations.getServiceKeys(getManagedServiceInstance()); + + Assertions.assertEquals(2, result.size()); + Assertions.assertEquals(Set.of(KEY_NAME, SECOND_KEY_NAME), result.stream() + .map(CloudServiceKey::getName) + .collect(Collectors.toSet())); + Assertions.assertEquals(Set.of(UUID.fromString(KEY_GUID), UUID.fromString(SECOND_KEY_GUID)), + result.stream() + .map(key -> key.getMetadata() + .getGuid()) + .collect(Collectors.toSet())); + factory.verify(); + } + @Test void testGetServiceKeysReturnsEmptyListWhenNoKeys() { stubKeyList("{\"resources\":[]}"); @@ -140,7 +163,7 @@ void testCreateServiceKeyReturnsEmptyWhenNoLocationHeader() { @Test void testCreateServiceKeyThrowsForUserProvidedService() { CloudServiceInstance userProvidedServiceInstance = getUserProvidedServiceInstance(); - + Assertions.assertThrows(IllegalArgumentException.class, () -> operations.createServiceKey(userProvidedServiceInstance, KEY_NAME, null)); } @@ -204,6 +227,10 @@ private static String getServiceKeyResourcesJson() { return "{\"resources\":[{\"guid\":\"" + KEY_GUID + "\",\"name\":\"" + KEY_NAME + "\",\"type\":\"key\"}]}"; } + private static String getServiceKeyJson(String guid, String name) { + return "{\"guid\":\"" + guid + "\",\"name\":\"" + name + "\",\"type\":\"key\"}"; + } + private static CloudServiceInstance getManagedServiceInstance() { return ImmutableCloudServiceInstance.builder() .name("my-service") diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3OperationsTest.java index 91046db235..b0d2b1d984 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceOfferingsV3OperationsTest.java @@ -5,6 +5,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceOffering; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ListResponse; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServiceOffering; import org.cloudfoundry.multiapps.controller.client.facade.rest.resources.V3ServicePlan; @@ -61,8 +62,8 @@ ArgumentMatchers.>> any @Test void testGetServiceOfferingsScopesQueryToSpaceWhenTargetHasGuid() { - when(targetSpace.getGuid()) - .thenReturn(SPACE_GUID); + when(targetSpace.getMetadata()) + .thenReturn(ImmutableCloudMetadata.of(SPACE_GUID)); when(client.list(ArgumentMatchers.contains("/v3/service_offerings"), ArgumentMatchers.>> any())) @@ -96,11 +97,11 @@ ArgumentMatchers.>> } private static V3ServiceOffering getOffering() { - return new V3ServiceOffering(OFFERING_GUID, "my-offering", "desc", true, false, null, null, null, null, null, null); + return new V3ServiceOffering(OFFERING_GUID, "my-offering", "desc", true, false, null, null, null, null, null, null, null); } private static V3ServicePlan getPlan(String name) { - return new V3ServicePlan(PLAN_GUID, name, null, true, "public", null, null, null, null, null); + return new V3ServicePlan(PLAN_GUID, name, null, true, "public", null, null, null, null, null, null, null, null, null, null); } } diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3OperationsTest.java index c83364bba6..467dfd49a0 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServicePlansV3OperationsTest.java @@ -4,6 +4,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudSpace; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ServicePlanVisibility; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -56,8 +57,8 @@ void testUpdateServicePlanVisibilityForBrokerUpdatesAllPlansWhenNoSpaceFilter() @Test void testUpdateServicePlanVisibilityForBrokerAppendsSpaceGuidWhenTargetHasGuid() { - when(target.getGuid()) - .thenReturn(SPACE_GUID); + when(target.getMetadata()) + .thenReturn(ImmutableCloudMetadata.of(SPACE_GUID)); stubBrokerLookup("{\"resources\":[{\"guid\":\"" + BROKER_GUID + "\"}]}"); stubOfferingLookup(SPACE_GUID, "{\"resources\":[{\"guid\":\"" + OFFERING_GUID + "\"}]}"); stubPlanLookup("{\"resources\":[{\"guid\":\"" + PLAN_GUID + "\"}]}"); @@ -105,13 +106,13 @@ void testUpdateServicePlanVisibilityForBrokerDoesNothingWhenNoPlans() { } private void stubBrokerLookup(String responseJson) { - factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_brokers?names=" + BROKER_NAME + "&per_page=5000", + factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_brokers?names=" + BROKER_NAME + "&per_page=50", responseJson); } private void stubOfferingLookup(UUID spaceGuid, String responseJson) { String uri = MockControllerClientFactory.BASE_URL + "/v3/service_offerings?service_broker_guids=" + BROKER_GUID - + "&per_page=5000"; + + "&per_page=50"; if (spaceGuid != null) { uri = uri + "&space_guids=" + spaceGuid; @@ -122,7 +123,7 @@ private void stubOfferingLookup(UUID spaceGuid, String responseJson) { private void stubPlanLookup(String responseJson) { factory.stubGet(MockControllerClientFactory.BASE_URL + "/v3/service_plans?service_offering_guids=" + OFFERING_GUID - + "&per_page=5000", responseJson); + + "&per_page=50", responseJson); } private void stubVisibilityPatch(String planGuid) { diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/StacksV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/StacksV3OperationsTest.java index bac9f68266..7b3a467bf4 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/StacksV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/StacksV3OperationsTest.java @@ -37,7 +37,7 @@ void setUp() throws Exception { @Test void testGetStackReturnsMappedStackWhenFound() { - mockStackList(new V3Stack(GUID_STRING, "cflinuxfs4", "Ubuntu-based stack", null, null, null)); + mockStackList(new V3Stack(GUID_STRING, "cflinuxfs4", "Ubuntu-based stack", null, null, null, null)); CloudStack result = operations.getStack("cflinuxfs4"); @@ -63,8 +63,8 @@ void testGetStackNotRequiredReturnsNullWhenNotFound() { @Test void testGetStacksMapsAllResults() { - mockStackList(new V3Stack(GUID_STRING, "cflinuxfs4", null, null, null, null), - new V3Stack(GUID_STRING, "cflinuxfs3", null, null, null, null)); + mockStackList(new V3Stack(GUID_STRING, "cflinuxfs4", null, null, null, null, null), + new V3Stack(GUID_STRING, "cflinuxfs3", null, null, null, null, null)); List result = operations.getStacks(); diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/TasksV3OperationsTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/TasksV3OperationsTest.java index 71b88e492e..5d05638dc2 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/TasksV3OperationsTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/TasksV3OperationsTest.java @@ -87,7 +87,7 @@ void testRunTaskThrowsWhenApplicationNotFound() { .thenReturn(null); factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000&names=missing-app")) + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=50&names=missing-app")) .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[]}", MediaType.APPLICATION_JSON)); CloudTask task = ImmutableCloudTask.builder() @@ -103,7 +103,7 @@ private void stubAppLookup() { .thenReturn(null); factory.server() - .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=5000&names=my-app")) + .expect(MockRestRequestMatchers.requestTo(MockControllerClientFactory.BASE_URL + "/v3/apps?per_page=50&names=my-app")) .andRespond(MockRestResponseCreators.withSuccess("{\"resources\":[{\"guid\":\"" + APP_GUID + "\"}]}", MediaType.APPLICATION_JSON)); } diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/V3UriTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/V3UriTest.java new file mode 100644 index 0000000000..1c8c8c4af8 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/V3UriTest.java @@ -0,0 +1,149 @@ +package org.cloudfoundry.multiapps.controller.client.facade.rest; + +import java.util.List; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class V3UriTest { + + @Test + void testPathWithoutPlaceholdersOrQueryReturnsPathAsIs() { + Assertions.assertEquals("/v3/domains", V3Uri.path("/v3/domains") + .build()); + } + + @Test + void testPathFillsSinglePlaceholder() { + Assertions.assertEquals("/v3/jobs/job-guid", V3Uri.path("/v3/jobs/{guid}", "job-guid") + .build()); + } + + @Test + void testPathFillsMultiplePlaceholdersInOrder() { + Assertions.assertEquals("/v3/apps/app-guid/features/ssh", V3Uri.path("/v3/apps/{guid}/features/{name}", "app-guid", "ssh") + .build()); + } + + @Test + void testPathFillsPlaceholdersInsideAndQueryAfter() { + Assertions.assertEquals("/v3/service_instances/si-guid/parameters", + V3Uri.path("/v3/service_instances/{guid}/parameters", "si-guid") + .build()); + } + + @Test + void testPerPageAddedAsFirstQueryParameter() { + Assertions.assertEquals("/v3/domains?per_page=5000", V3Uri.path("/v3/domains") + .perPage(5000) + .build()); + } + + @Test + void testMultipleQueryParametersUseAmpersandSeparators() { + String uri = V3Uri.path("/v3/service_instances") + .perPage(5000) + .query("space_guids", "space-guid") + .query("type", "key") + .build(); + + Assertions.assertEquals("/v3/service_instances?per_page=5000&space_guids=space-guid&type=key", uri); + } + + @Test + void testNullQueryValueIsSkipped() { + String uri = V3Uri.path("/v3/apps") + .perPage(5000) + .query("names", (Object) null) + .build(); + + Assertions.assertEquals("/v3/apps?per_page=5000", uri); + } + + @Test + void testCollectionQueryValueIsCommaJoined() { + String uri = V3Uri.path("/v3/service_instances") + .perPage(5000) + .query("names", List.of("a", "b", "c")) + .build(); + + Assertions.assertEquals("/v3/service_instances?per_page=5000&names=a,b,c", uri); + } + + @Test + void testCollectionQueryEncodesCommaWithinAnElement() { + String uri = V3Uri.path("/v3/service_instances") + .perPage(5000) + .query("names", List.of("a,b", "c")) + .build(); + + Assertions.assertEquals("/v3/service_instances?per_page=5000&names=a%2Cb,c", uri); + } + + @Test + void testQueryEncodedEscapesCommaInSingleValue() { + String uri = V3Uri.path("/v3/apps") + .perPage(5000) + .queryEncoded("names", "my,app") + .build(); + + Assertions.assertEquals("/v3/apps?per_page=5000&names=my%2Capp", uri); + } + + @Test + void testQueryEncodedLeavesCommaFreeValueUnchanged() { + String uri = V3Uri.path("/v3/apps") + .queryEncoded("names", "my-app") + .build(); + + Assertions.assertEquals("/v3/apps?names=my-app", uri); + } + + @Test + void testQueryEncodedSkipsNullValue() { + String uri = V3Uri.path("/v3/apps") + .perPage(5000) + .queryEncoded("names", null) + .build(); + + Assertions.assertEquals("/v3/apps?per_page=5000", uri); + } + + @Test + void testNullCollectionQueryValueIsSkipped() { + String uri = V3Uri.path("/v3/service_instances") + .perPage(5000) + .query("names", null) + .build(); + + Assertions.assertEquals("/v3/service_instances?per_page=5000", uri); + } + + @Test + void testEmptyCollectionQueryValueIsSkipped() { + String uri = V3Uri.path("/v3/service_instances") + .perPage(5000) + .query("names", List.of()) + .build(); + + Assertions.assertEquals("/v3/service_instances?per_page=5000", uri); + } + + @Test + void testPlaceholderValueContainingDollarAndBackslashIsNotInterpretedAsRegex() { + String uri = V3Uri.path("/v3/apps/{guid}", "weird$name\\value") + .build(); + + Assertions.assertEquals("/v3/apps/weird$name\\value", uri); + } + + @Test + void testQueryValueContainingSpecialCharsIsKeptRaw() { + String uri = V3Uri.path("/v3/apps") + .query("label_selector", "a=b,c in (d)") + .build(); + + Assertions.assertEquals("/v3/apps?label_selector=a=b,c in (d)", uri); + } + +} diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3BuildMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3BuildMapperTest.java index f4dbeaa9b2..4242909aff 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3BuildMapperTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3BuildMapperTest.java @@ -17,7 +17,7 @@ class V3BuildMapperTest { @Test void testToCloudBuildMapsAllFields() { V3Build build = new V3Build(GUID_STRING, null, null, "STAGED", null, new V3CreatedBy(USER_GUID, "admin"), - new V3PackageReference(PACKAGE_GUID), new V3DropletReference(DROPLET_GUID)); + new V3PackageReference(PACKAGE_GUID), new V3DropletReference(DROPLET_GUID), null, null); CloudBuild result = V3BuildMapper.toCloudBuild(build); @@ -37,7 +37,7 @@ void testToCloudBuildMapsAllFields() { @Test void testToCloudBuildNullCreatedByPackageAndDropletReturnNulls() { - V3Build build = new V3Build(GUID_STRING, null, null, "STAGING", "boom", null, null, null); + V3Build build = new V3Build(GUID_STRING, null, null, "STAGING", "boom", null, null, null, null, null); CloudBuild result = V3BuildMapper.toCloudBuild(build); @@ -49,7 +49,7 @@ void testToCloudBuildNullCreatedByPackageAndDropletReturnNulls() { @Test void testToCLoudBuildDropletWithNullGuidReturnsNullDropletInfo() { - V3Build build = new V3Build(GUID_STRING, null, null, "FAILED", null, null, null, new V3DropletReference(null)); + V3Build build = new V3Build(GUID_STRING, null, null, "FAILED", null, null, null, new V3DropletReference(null), null, null); Assertions.assertNull(V3BuildMapper.toCloudBuild(build) .getDropletInfo()); @@ -57,7 +57,7 @@ void testToCLoudBuildDropletWithNullGuidReturnsNullDropletInfo() { @Test void testToCloudBuildNullStateReturnsNullState() { - V3Build build = new V3Build(GUID_STRING, null, null, null, null, null, null, null); + V3Build build = new V3Build(GUID_STRING, null, null, null, null, null, null, null, null, null); Assertions.assertNull(V3BuildMapper.toCloudBuild(build) .getState()); diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3DomainMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3DomainMapperTest.java index c18bdffd23..104ba0f175 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3DomainMapperTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3DomainMapperTest.java @@ -14,7 +14,7 @@ class V3DomainMapperTest { @Test void testToCloudDomainMapsNameAndGuid() { - V3Domain domain = new V3Domain(GUID_STRING, "example.com", null, null, null, null); + V3Domain domain = new V3Domain(GUID_STRING, "example.com", null, null, null, null, null); CloudDomain result = V3DomainMapper.toCloudDomain(domain); @@ -25,7 +25,7 @@ void testToCloudDomainMapsNameAndGuid() { @Test void testToCloudDomainWithNullGuidReturnsNullGuid() { - V3Domain domain = new V3Domain(null, "example.com", null, null, null, null); + V3Domain domain = new V3Domain(null, "example.com", null, null, null, null, null); Assertions.assertNull(V3DomainMapper.toCloudDomain(domain) .getMetadata() @@ -35,14 +35,14 @@ void testToCloudDomainWithNullGuidReturnsNullGuid() { @Test void testToCloudDomainIsPrivateWhenOrganizationDataPresent() { V3DomainRelationships relationships = new V3DomainRelationships(new V3ToOneRelationship(new V3RelationshipData(ORG_GUID))); - V3Domain domain = new V3Domain(GUID_STRING, "private.example.com", null, null, null, relationships); + V3Domain domain = new V3Domain(GUID_STRING, "private.example.com", null, null, null, null, relationships); Assertions.assertTrue(domain.isPrivate()); } @Test void testToCloudDomainIsNotPrivateWhenRelationshipsNull() { - V3Domain domain = new V3Domain(GUID_STRING, "shared.example.com", null, null, null, null); + V3Domain domain = new V3Domain(GUID_STRING, "shared.example.com", null, null, null, null, null); Assertions.assertFalse(domain.isPrivate()); } @@ -50,7 +50,7 @@ void testToCloudDomainIsNotPrivateWhenRelationshipsNull() { @Test void testToCloudDomainIsNotPrivateWhenOrganizationDataNull() { V3DomainRelationships relationships = new V3DomainRelationships(new V3ToOneRelationship(null)); - V3Domain domain = new V3Domain(GUID_STRING, "shared.example.com", null, null, null, relationships); + V3Domain domain = new V3Domain(GUID_STRING, "shared.example.com", null, null, null, null, relationships); Assertions.assertFalse(domain.isPrivate()); } diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3InstancesInfoMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3InstancesInfoMapperTest.java index 0df54cd4f7..569ea6d0ea 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3InstancesInfoMapperTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3InstancesInfoMapperTest.java @@ -57,4 +57,18 @@ void testToInstancesInfoDefaultsUnknownStateAndNonBooleanRoutable() { .isRoutable()); } + @Test + void testToInstancesInfoWhenIndexStateAndRoutableNullDoesNotThrow() { + V3Process.V3ProcessStats stats = new V3Process.V3ProcessStats(List.of(new V3Process.V3ProcessStatsResource(null, null, null))); + + InstancesInfo result = Assertions.assertDoesNotThrow(() -> V3InstancesInfoMapper.toInstancesInfo(stats)); + + Assertions.assertEquals(0, result.getInstances() + .getFirst() + .getIndex()); + Assertions.assertFalse(result.getInstances() + .getFirst() + .isRoutable()); + } + } diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3PackageMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3PackageMapperTest.java index 660bd0ad73..d1b648d571 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3PackageMapperTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3PackageMapperTest.java @@ -16,7 +16,7 @@ class V3PackageMapperTest { @Test void testToCloudPackageMapsBitsPackage() { V3PackageData data = new V3PackageData(new V3Checksum("sha256", "abc123"), "no error", null, null, null); - V3Package resource = new V3Package(GUID_STRING, "bits", "READY", null, null, data); + V3Package resource = new V3Package(GUID_STRING, "bits", "READY", null, null, data, null, null); CloudPackage result = V3PackageMapper.toCloudPackage(resource); @@ -33,7 +33,7 @@ void testToCloudPackageMapsBitsPackage() { @Test void testToCloudPackageMapsDockerPackage() { V3PackageData data = new V3PackageData(null, null, "my/image:latest", "user", "pass"); - V3Package resource = new V3Package(GUID_STRING, "docker", "READY", null, null, data); + V3Package resource = new V3Package(GUID_STRING, "docker", "READY", null, null, data, null, null); CloudPackage result = V3PackageMapper.toCloudPackage(resource); @@ -45,7 +45,7 @@ void testToCloudPackageMapsDockerPackage() { @Test void testToCloudPackageNullStateReturnsNullStatus() { - V3Package resource = new V3Package(GUID_STRING, "bits", null, null, null, null); + V3Package resource = new V3Package(GUID_STRING, "bits", null, null, null, null, null, null); Assertions.assertNull(V3PackageMapper.toCloudPackage(resource) .getStatus()); @@ -53,7 +53,7 @@ void testToCloudPackageNullStateReturnsNullStatus() { @Test void testToCloudPackageNullTypeReturnsNullType() { - V3Package resource = new V3Package(GUID_STRING, null, "READY", null, null, null); + V3Package resource = new V3Package(GUID_STRING, null, "READY", null, null, null, null, null); Assertions.assertNull(V3PackageMapper.toCloudPackage(resource) .getType()); @@ -61,7 +61,7 @@ void testToCloudPackageNullTypeReturnsNullType() { @Test void testToCloudPackageBitsPackageWithNullDataReturnsEmptyBitsData() { - V3Package resource = new V3Package(GUID_STRING, "bits", "READY", null, null, null); + V3Package resource = new V3Package(GUID_STRING, "bits", "READY", null, null, null, null, null); BitsData bitsData = (BitsData) V3PackageMapper.toCloudPackage(resource) .getData(); @@ -73,7 +73,7 @@ void testToCloudPackageBitsPackageWithNullDataReturnsEmptyBitsData() { @Test void testToCloudPackageBitsPackageWithNullChecksumReturnsNullChecksum() { V3PackageData data = new V3PackageData(null, "err", null, null, null); - V3Package resource = new V3Package(GUID_STRING, "bits", "READY", null, null, data); + V3Package resource = new V3Package(GUID_STRING, "bits", "READY", null, null, data, null, null); BitsData bitsData = (BitsData) V3PackageMapper.toCloudPackage(resource) .getData(); @@ -82,4 +82,15 @@ void testToCloudPackageBitsPackageWithNullChecksumReturnsNullChecksum() { Assertions.assertEquals("err", bitsData.getError()); } + @Test + void testToCloudPackageDockerPackageWithNullImageDoesNotThrow() { + V3PackageData data = new V3PackageData(null, null, null, "user", "pass"); + V3Package resource = new V3Package(GUID_STRING, "docker", "READY", null, null, data, null, null); + + CloudPackage result = Assertions.assertDoesNotThrow(() -> V3PackageMapper.toCloudPackage(resource)); + + Assertions.assertEquals(CloudPackage.Type.DOCKER, result.getType()); + Assertions.assertNull(result.getData()); + } + } diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ProcessMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ProcessMapperTest.java index 3abefbf940..ad1033660c 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ProcessMapperTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ProcessMapperTest.java @@ -16,7 +16,8 @@ void testToCloudProcessMapsAllFields() { V3HealthCheckData healthCheckData = new V3HealthCheckData(60, 10, "/health", 5); V3HealthCheckData readinessData = new V3HealthCheckData(null, 8, "/ready", 3); V3Process process = new V3Process(GUID_STRING, "start.sh", 3, 512, 2048, new V3HealthCheck("http", healthCheckData), - new V3HealthCheck("http", readinessData)); + new V3HealthCheck("http", readinessData), null, null, null, null, null, null, null, null, + null); CloudProcess result = V3ProcessMapper.toCloudProcess(process); @@ -31,7 +32,8 @@ void testToCloudProcessMapsAllFields() { @Test void testToCloudProcessNullHealthCheckDefaultsToPort() { - V3Process process = new V3Process(GUID_STRING, "start.sh", 1, 256, 1024, null, null); + V3Process process = new V3Process(GUID_STRING, "start.sh", 1, 256, 1024, null, null, null, null, null, null, null, null, null, + null, null); CloudProcess result = V3ProcessMapper.toCloudProcess(process); @@ -41,7 +43,8 @@ void testToCloudProcessNullHealthCheckDefaultsToPort() { @Test void testToCloudProcessHealthCheckTypeIsUpperCased() { - V3Process process = new V3Process(GUID_STRING, "s", 1, 256, 1024, new V3HealthCheck("process", null), null); + V3Process process = new V3Process(GUID_STRING, "s", 1, 256, 1024, new V3HealthCheck("process", null), null, null, null, null, + null, null, null, null, null, null); Assertions.assertEquals(HealthCheckType.PROCESS, V3ProcessMapper.toCloudProcess(process) .getHealthCheckType()); @@ -49,7 +52,8 @@ void testToCloudProcessHealthCheckTypeIsUpperCased() { @Test void testToCloudProcessNullHealthCheckDataLeavesEndpointFieldsNull() { - V3Process process = new V3Process(GUID_STRING, "s", 1, 256, 1024, new V3HealthCheck("port", null), null); + V3Process process = new V3Process(GUID_STRING, "s", 1, 256, 1024, new V3HealthCheck("port", null), null, null, null, null, null, + null, null, null, null, null); CloudProcess result = V3ProcessMapper.toCloudProcess(process); @@ -58,4 +62,26 @@ void testToCloudProcessNullHealthCheckDataLeavesEndpointFieldsNull() { Assertions.assertNull(result.getHealthCheckTimeout()); } + @Test + void testToCloudProcessWhenCommandNullAndHealthCheckTypeNullDoesNotThrow() { + V3Process process = new V3Process(GUID_STRING, null, 1, 256, 1024, new V3HealthCheck(null, null), null, null, null, null, null, + null, null, null, null, null); + + CloudProcess result = Assertions.assertDoesNotThrow(() -> V3ProcessMapper.toCloudProcess(process)); + + Assertions.assertNull(result.getCommand()); + Assertions.assertEquals(HealthCheckType.PORT, result.getHealthCheckType()); + } + + @Test + void testToCloudProcessWhenCommandNullAndHealthCheckNullDoesNotThrow() { + V3Process process = new V3Process(GUID_STRING, null, 1, 256, 1024, null, null, null, null, null, null, null, null, null, null, + null); + + CloudProcess result = Assertions.assertDoesNotThrow(() -> V3ProcessMapper.toCloudProcess(process)); + + Assertions.assertNull(result.getCommand()); + Assertions.assertEquals(HealthCheckType.PORT, result.getHealthCheckType()); + } + } diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RoleMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RoleMapperTest.java index 7b156b67dc..d658ebce3b 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RoleMapperTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RoleMapperTest.java @@ -13,14 +13,14 @@ class V3RoleMapperTest { "organization_manager,ORGANIZATION_MANAGER", "organization_user,ORGANIZATION_USER", "space_auditor,SPACE_AUDITOR", "space_developer,SPACE_DEVELOPER", "space_manager,SPACE_MANAGER" }) void testToUserRoleMapsEachType(String wireType, UserRole expected) { - V3Role role = new V3Role("guid", null, null, wireType); + V3Role role = new V3Role("guid", null, null, wireType, null); Assertions.assertEquals(expected, V3RoleMapper.toUserRole(role)); } @Test void testToUserRoleWithUnknownTypeThrows() { - V3Role role = new V3Role("guid", null, null, "galactic_overlord"); + V3Role role = new V3Role("guid", null, null, "galactic_overlord", null); Assertions.assertThrows(IllegalArgumentException.class, () -> V3RoleMapper.toUserRole(role)); } diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RouteMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RouteMapperTest.java index 83c937ea09..98b6fd68b5 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RouteMapperTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3RouteMapperTest.java @@ -92,6 +92,38 @@ void testToCloudRouteRequestedProtocolIsNullWhenApplicationGuidDoesNotMatch() { Assertions.assertNull(result.getRequestedProtocol()); } + @Test + void testToCloudRouteWhenRelationshipsNullDoesNotThrow() { + V3Route route = new V3Route(ROUTE_GUID, null, null, null, "example.com", null, null, List.of(), null, null); + + CloudRoute result = Assertions.assertDoesNotThrow(() -> V3RouteMapper.toCloudRoute(route)); + + Assertions.assertEquals("example.com", result.getDomain() + .getName()); + Assertions.assertNull(result.getDomain() + .getMetadata() + .getGuid()); + } + + @Test + void testToCloudRouteWhenDestinationAppOrProtocolNullDoesNotThrow() { + V3Destination destinationWithNullApp = new V3Destination(DESTINATION_GUID, null, 8080, 1, "http1"); + V3Destination destinationWithNullProtocol = new V3Destination(DESTINATION_GUID, new V3DestinationApp(APP_GUID), 8080, 1, null); + V3Route route = buildRoute("myhost", "", null, "myhost.example.com", + List.of(destinationWithNullApp, destinationWithNullProtocol)); + + CloudRoute result = Assertions.assertDoesNotThrow(() -> V3RouteMapper.toCloudRoute(route)); + + Assertions.assertEquals(1, result.getDestinations() + .size()); + Assertions.assertEquals(UUID.fromString(APP_GUID), result.getDestinations() + .get(0) + .getApplicationGuid()); + Assertions.assertNull(result.getDestinations() + .get(0) + .getProtocol()); + } + private static V3Route buildRoute(String host, String path, Integer port, String url, List destinations) { V3RouteRelationships relationships = new V3RouteRelationships(new V3ToOneRelationship(new V3RelationshipData(DOMAIN_GUID)), new V3ToOneRelationship(new V3RelationshipData(DOMAIN_GUID))); diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstanceMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstanceMapperTest.java index 43181d182d..828d3fe6ea 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstanceMapperTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceInstanceMapperTest.java @@ -16,7 +16,7 @@ class V3ServiceInstanceMapperTest { @Test void testToCloudServiceInstanceMapsAllFields() { V3ServiceInstance instance = new V3ServiceInstance(GUID_STRING, "my-service", "managed", null, null, List.of("tag1", "tag2"), - "syslog://drain", new V3LastOperation("create", "succeeded", "done"), null, + "syslog://drain", null, new V3LastOperation("create", "succeeded", "done"), null, null); CloudServiceInstance result = V3ServiceInstanceMapper.toCloudServiceInstance(instance, "my-plan", "my-label"); @@ -59,7 +59,7 @@ void testToCloudServiceInstanceWithUnknownTypeReturnsNullType() { @Test void testToCloudServiceInstanceNullTagsReturnEmptyList() { - V3ServiceInstance instance = new V3ServiceInstance(GUID_STRING, "s", "managed", null, null, null, null, null, null, null); + V3ServiceInstance instance = new V3ServiceInstance(GUID_STRING, "s", "managed", null, null, null, null, null, null, null, null); Assertions.assertTrue(V3ServiceInstanceMapper.toCloudServiceInstance(instance, null, null) .getTags() @@ -68,7 +68,7 @@ void testToCloudServiceInstanceNullTagsReturnEmptyList() { @Test void testToCloudServiceInstanceNullLastOperationReturnsNullOperation() { - V3ServiceInstance instance = new V3ServiceInstance(GUID_STRING, "s", "managed", null, null, null, null, null, null, null); + V3ServiceInstance instance = new V3ServiceInstance(GUID_STRING, "s", "managed", null, null, null, null, null, null, null, null); Assertions.assertNull(V3ServiceInstanceMapper.toCloudServiceInstance(instance, null, null) .getLastOperation()); @@ -76,7 +76,7 @@ void testToCloudServiceInstanceNullLastOperationReturnsNullOperation() { @Test void testToCloudServiceInstanceLastOperationWithNullTypeReturnsNullOperation() { - V3ServiceInstance instance = new V3ServiceInstance(GUID_STRING, "s", "managed", null, null, null, null, + V3ServiceInstance instance = new V3ServiceInstance(GUID_STRING, "s", "managed", null, null, null, null, null, new V3LastOperation(null, "succeeded", "d"), null, null); Assertions.assertNull(V3ServiceInstanceMapper.toCloudServiceInstance(instance, null, null) @@ -85,7 +85,7 @@ void testToCloudServiceInstanceLastOperationWithNullTypeReturnsNullOperation() { @Test void testToCloudServiceInstanceWithoutAuxiliaryContentExcludesPlanAndOperation() { - V3ServiceInstance instance = new V3ServiceInstance(GUID_STRING, "my-service", "managed", null, null, List.of("t"), "syslog", + V3ServiceInstance instance = new V3ServiceInstance(GUID_STRING, "my-service", "managed", null, null, List.of("t"), "syslog", null, new V3LastOperation("create", "succeeded", "d"), null, null); CloudServiceInstance result = V3ServiceInstanceMapper.toCloudServiceInstanceWithoutAuxiliaryContent(instance); @@ -98,7 +98,7 @@ void testToCloudServiceInstanceWithoutAuxiliaryContentExcludesPlanAndOperation() } private static V3ServiceInstance buildInstanceWithType(String type) { - return new V3ServiceInstance(GUID_STRING, "s", type, null, null, null, null, null, null, null); + return new V3ServiceInstance(GUID_STRING, "s", type, null, null, null, null, null, null, null, null); } } diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceOfferingMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceOfferingMapperTest.java index 8e01652ea3..77a49e5a05 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceOfferingMapperTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServiceOfferingMapperTest.java @@ -23,7 +23,7 @@ void testToCloudServiceOfferingMapsAllFields() { V3BrokerCatalog brokerCatalog = new V3BrokerCatalog("catalog-id", Map.of("displayName", "My Service"), features); V3Relationships relationships = new V3Relationships(new V3ToOneRelationship(new V3RelationshipData(BROKER_GUID))); V3ServiceOffering offering = new V3ServiceOffering(GUID_STRING, "my-offering", "An offering", true, true, "https://docs", - null, null, brokerCatalog, relationships, null); + null, null, brokerCatalog, relationships, null, null); CloudServiceOffering result = V3ServiceOfferingMapper.toCloudServiceOffering(offering, List.of()); @@ -40,7 +40,7 @@ void testToCloudServiceOfferingMapsAllFields() { @Test void testToCloudServiceOfferingNullBrokerCatalogReturnsNullDerivedFields() { - V3ServiceOffering offering = new V3ServiceOffering(GUID_STRING, "o", null, true, false, null, null, null, null, null, null); + V3ServiceOffering offering = new V3ServiceOffering(GUID_STRING, "o", null, true, false, null, null, null, null, null, null, null); CloudServiceOffering result = V3ServiceOfferingMapper.toCloudServiceOffering(offering, List.of()); @@ -54,7 +54,7 @@ void testToCloudServiceOfferingNullBrokerCatalogReturnsNullDerivedFields() { void testToCloudServiceOfferingNullFeaturesReturnsNullBindable() { V3BrokerCatalog brokerCatalog = new V3BrokerCatalog("catalog-id", null, null); V3ServiceOffering offering = new V3ServiceOffering(GUID_STRING, "o", null, true, false, null, null, null, brokerCatalog, null, - null); + null, null); Assertions.assertNull(V3ServiceOfferingMapper.toCloudServiceOffering(offering, List.of()) .isBindable()); @@ -64,7 +64,7 @@ void testToCloudServiceOfferingNullFeaturesReturnsNullBindable() { void testToCloudServiceOfferingNullServiceBrokerRelationshipReturnsNullBrokerId() { V3Relationships relationships = new V3Relationships(null); V3ServiceOffering offering = new V3ServiceOffering(GUID_STRING, "o", null, true, false, null, null, null, null, relationships, - null); + null, null); Assertions.assertNull(V3ServiceOfferingMapper.toCloudServiceOffering(offering, List.of()) .getBrokerId()); diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServicePlanMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServicePlanMapperTest.java index fa2a0b11d6..f075591063 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServicePlanMapperTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3ServicePlanMapperTest.java @@ -14,13 +14,16 @@ class V3ServicePlanMapperTest { private static final String GUID_STRING = "3725a721-6e3f-4b6e-8f2b-1c2d3e4f5a6b"; private static final String OFFERING_GUID = "11111111-2222-3333-4444-555555555555"; + public static final String SERVICE_BROKER_SPACE_GUID = "11111111-2222-3333-4444-555555555555"; @Test void testToCloudServicePlanMapsAllFields() { V3BrokerCatalog brokerCatalog = new V3BrokerCatalog("catalog-id", Map.of("k", "v")); - V3Relationships relationships = new V3Relationships(new V3ToOneRelationship(new V3RelationshipData(OFFERING_GUID))); - V3ServicePlan plan = new V3ServicePlan(GUID_STRING, "my-plan", "A plan", true, "public", null, null, brokerCatalog, - relationships, null); + V3Relationships relationships = new V3Relationships(new V3ToOneRelationship(new V3RelationshipData(OFFERING_GUID)), + new V3ToOneRelationship(new V3RelationshipData(SERVICE_BROKER_SPACE_GUID))); + V3ServicePlan plan = new V3ServicePlan(GUID_STRING, "my-plan", "A plan", true, "public", null, null, null, null, null, null, + brokerCatalog, + relationships, null, null); CloudServicePlan result = V3ServicePlanMapper.toCloudServicePlan(plan); @@ -51,7 +54,8 @@ void testToCloudServicePlanNullVisibilityReturnsNullIsPublic() { @Test void testToCloudServicePlanNullBrokerCatalogAndRelationshipsReturnNulls() { - V3ServicePlan plan = new V3ServicePlan(GUID_STRING, "p", null, false, "public", null, null, null, null, null); + V3ServicePlan plan = new V3ServicePlan(GUID_STRING, "p", null, false, "public", null, null, null, null, null, null, null, null, + null, null); CloudServicePlan result = V3ServicePlanMapper.toCloudServicePlan(plan); @@ -61,7 +65,7 @@ void testToCloudServicePlanNullBrokerCatalogAndRelationshipsReturnNulls() { } private static V3ServicePlan buildPlanWithVisibility(String visibilityType) { - return new V3ServicePlan(GUID_STRING, "p", null, false, visibilityType, null, null, null, null, null); + return new V3ServicePlan(GUID_STRING, "p", null, false, visibilityType, null, null, null, null, null, null, null, null, null, null); } } diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3StackMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3StackMapperTest.java index a25bb98836..39ef332ad3 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3StackMapperTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3StackMapperTest.java @@ -10,7 +10,8 @@ class V3StackMapperTest { @Test void testToCloudStackMapsAllFields() { - V3Stack stack = new V3Stack(GUID_STRING, "cflinuxfs4", "Ubuntu-based stack", "2026-08-04T10:15:30Z", "2026-08-04T10:16:30Z", null); + V3Stack stack = new V3Stack(GUID_STRING, "cflinuxfs4", "Ubuntu-based stack", "2026-08-04T10:15:30Z", "2026-08-04T10:16:30Z", null, + null); CloudStack result = V3StackMapper.toCloudStack(stack); @@ -22,7 +23,7 @@ void testToCloudStackMapsAllFields() { @Test void testToCloudStackWithNullGuidReturnsNullGuid() { - V3Stack stack = new V3Stack(null, "cflinuxfs4", null, null, null, null); + V3Stack stack = new V3Stack(null, "cflinuxfs4", null, null, null, null, null); Assertions.assertNull(V3StackMapper.toCloudStack(stack) .getMetadata() diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3TaskMapperTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3TaskMapperTest.java index b07d6b1eee..2b568e9b83 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3TaskMapperTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/rest/resources/V3TaskMapperTest.java @@ -11,7 +11,7 @@ class V3TaskMapperTest { @Test void testToCloudTaskMapsAllFields() { V3Task task = new V3Task(GUID_STRING, "migrate-db", "rails db:migrate", "SUCCEEDED", 256, 1024, - new V3Task.V3Result(null), "2026-08-04T10:15:30Z", "2026-08-04T10:16:30Z", null); + new V3Task.V3Result(null), "2026-08-04T10:15:30Z", "2026-08-04T10:16:30Z", null, null); CloudTask result = V3TaskMapper.toCloudTask(task); @@ -36,7 +36,7 @@ void testToCloudTaskWithNullResultReturnsNullResult() { @Test void testToCloudTaskMapsFailureReason() { - V3Task task = new V3Task(GUID_STRING, "t", "cmd", "FAILED", 256, 1024, new V3Task.V3Result("boom"), null, null, null); + V3Task task = new V3Task(GUID_STRING, "t", "cmd", "FAILED", 256, 1024, new V3Task.V3Result("boom"), null, null, null, null); Assertions.assertEquals("boom", V3TaskMapper.toCloudTask(task) .getResult() @@ -59,7 +59,7 @@ void testToCloudTaskWithUnknownStateThrows() { } private static V3Task buildTaskWithState(String state) { - return new V3Task(GUID_STRING, "t", "cmd", state, 256, 1024, null, null, null, null); + return new V3Task(GUID_STRING, "t", "cmd", state, 256, 1024, null, null, null, null, null); } } diff --git a/multiapps-controller-core/pom.xml b/multiapps-controller-core/pom.xml index f07caae358..797054b9d0 100644 --- a/multiapps-controller-core/pom.xml +++ b/multiapps-controller-core/pom.xml @@ -103,14 +103,6 @@ multiapps-controller-core-test test - - - io.jsonwebtoken - jjwt-api - ${jjwt.version} - test - org.cloudfoundry.multiapps multiapps-controller-client diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/clients/CloudEntityResourceMapper.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/clients/CloudEntityResourceMapper.java index 228cfaca71..2e73410bfe 100644 --- a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/clients/CloudEntityResourceMapper.java +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/clients/CloudEntityResourceMapper.java @@ -17,6 +17,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.controller.core.model.DeployedMtaService; import org.cloudfoundry.multiapps.controller.core.model.DeployedMtaServiceKey; import org.cloudfoundry.multiapps.controller.core.model.ImmutableDeployedMtaServiceKey; @@ -78,7 +79,7 @@ public static CloudMetadata getCloudMetadataV3(Map resource) { @SuppressWarnings("unchecked") public static Metadata getMetadataV3(Map resource) { Map metadata = (Map) resource.getOrDefault("metadata", Collections.emptyMap()); - return Metadata.builder() + return ImmutableMetadata.builder() .labels((Map) metadata.getOrDefault("labels", Collections.emptyMap())) .annotations((Map) metadata.getOrDefault("annotations", Collections.emptyMap())) .build(); diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/metadata/util/MtaMetadataUtil.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/metadata/util/MtaMetadataUtil.java index 4d6d69572c..2f1606a70f 100644 --- a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/metadata/util/MtaMetadataUtil.java +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/metadata/util/MtaMetadataUtil.java @@ -4,8 +4,9 @@ import java.util.Set; import org.apache.commons.lang3.StringUtils; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudEntity; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataAnnotations; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataLabels; import org.springframework.util.DigestUtils; @@ -42,15 +43,15 @@ public static String getHashedLabel(String mtaLabel) { } public static Metadata getMetadataWithoutMtaFields(Metadata metadata) { - return Metadata.builder() + return ImmutableMetadata.builder() .from(metadata) - .label(MtaMetadataLabels.MTA_ID, null) - .label(MtaMetadataLabels.MTA_NAMESPACE, null) - .label(MtaMetadataLabels.SPACE_GUID, null) - .annotation(MtaMetadataAnnotations.MTA_ID, null) - .annotation(MtaMetadataAnnotations.MTA_VERSION, null) - .annotation(MtaMetadataAnnotations.MTA_RESOURCE, null) - .annotation(MtaMetadataAnnotations.MTA_NAMESPACE, null) + .putLabel(MtaMetadataLabels.MTA_ID, null) + .putLabel(MtaMetadataLabels.MTA_NAMESPACE, null) + .putLabel(MtaMetadataLabels.SPACE_GUID, null) + .putAnnotation(MtaMetadataAnnotations.MTA_ID, null) + .putAnnotation(MtaMetadataAnnotations.MTA_VERSION, null) + .putAnnotation(MtaMetadataAnnotations.MTA_RESOURCE, null) + .putAnnotation(MtaMetadataAnnotations.MTA_NAMESPACE, null) .build(); } diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ApplicationMetadataBuilder.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ApplicationMetadataBuilder.java index aea8248cab..345f7b2725 100644 --- a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ApplicationMetadataBuilder.java +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ApplicationMetadataBuilder.java @@ -5,6 +5,7 @@ import java.util.TreeMap; import java.util.stream.Collectors; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.common.util.JsonUtil; import org.cloudfoundry.multiapps.common.util.MapUtil; @@ -21,11 +22,11 @@ public static Metadata build(DeploymentDescriptor deploymentDescriptor, String n String mtaModuleProvidedDependenciesAnnotation = buildMtaModuleProvidedDependenciesAnnotation(module); String mtaServicesAnnotation = buildBoundMtaServicesAnnotation(services); - Metadata.Builder builder = MtaMetadataBuilder.init(deploymentDescriptor, namespace) - .annotation(MtaMetadataAnnotations.MTA_MODULE, mtaModuleAnnotation) - .annotation(MtaMetadataAnnotations.MTA_MODULE_PUBLIC_PROVIDED_DEPENDENCIES, + ImmutableMetadata.Builder builder = MtaMetadataBuilder.init(deploymentDescriptor, namespace) + .putAnnotation(MtaMetadataAnnotations.MTA_MODULE, mtaModuleAnnotation) + .putAnnotation(MtaMetadataAnnotations.MTA_MODULE_PUBLIC_PROVIDED_DEPENDENCIES, mtaModuleProvidedDependenciesAnnotation) - .annotation(MtaMetadataAnnotations.MTA_MODULE_BOUND_SERVICES, mtaServicesAnnotation); + .putAnnotation(MtaMetadataAnnotations.MTA_MODULE_BOUND_SERVICES, mtaServicesAnnotation); return builder.build(); } diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/MtaMetadataBuilder.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/MtaMetadataBuilder.java index a392eafb31..3be51b4c5c 100644 --- a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/MtaMetadataBuilder.java +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/MtaMetadataBuilder.java @@ -1,8 +1,8 @@ package org.cloudfoundry.multiapps.controller.core.cf.v2; import org.apache.commons.lang3.StringUtils; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata.Builder; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata.Builder; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataAnnotations; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataLabels; import org.cloudfoundry.multiapps.controller.core.cf.metadata.util.MtaMetadataUtil; @@ -12,15 +12,15 @@ public class MtaMetadataBuilder { public static Builder init(DeploymentDescriptor deploymentDescriptor, String namespace) { String hashedMtaId = MtaMetadataUtil.getHashedLabel(deploymentDescriptor.getId()); - Builder builder = Metadata.builder() - .label(MtaMetadataLabels.MTA_ID, hashedMtaId) - .annotation(MtaMetadataAnnotations.MTA_ID, deploymentDescriptor.getId()) - .annotation(MtaMetadataAnnotations.MTA_VERSION, deploymentDescriptor.getVersion()); + Builder builder = ImmutableMetadata.builder() + .putLabel(MtaMetadataLabels.MTA_ID, hashedMtaId) + .putAnnotation(MtaMetadataAnnotations.MTA_ID, deploymentDescriptor.getId()) + .putAnnotation(MtaMetadataAnnotations.MTA_VERSION, deploymentDescriptor.getVersion()); if (StringUtils.isNotEmpty(namespace)) { String hashedMtaNamespace = MtaMetadataUtil.getHashedLabel(namespace); - builder.label(MtaMetadataLabels.MTA_NAMESPACE, hashedMtaNamespace) - .annotation(MtaMetadataAnnotations.MTA_NAMESPACE, namespace); + builder.putLabel(MtaMetadataLabels.MTA_NAMESPACE, hashedMtaNamespace) + .putAnnotation(MtaMetadataAnnotations.MTA_NAMESPACE, namespace); } return builder; } diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServiceKeyMetadataBuilder.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServiceKeyMetadataBuilder.java index e9ccf2fcc0..78817ec81c 100644 --- a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServiceKeyMetadataBuilder.java +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServiceKeyMetadataBuilder.java @@ -1,7 +1,7 @@ package org.cloudfoundry.multiapps.controller.core.cf.v2; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata.Builder; import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata.Builder; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataLabels; import org.cloudfoundry.multiapps.mta.model.DeploymentDescriptor; @@ -10,7 +10,7 @@ public class ServiceKeyMetadataBuilder { public static Metadata build(DeploymentDescriptor deploymentDescriptor, String namespace, String spaceGuid) { Builder builder = MtaMetadataBuilder.init(deploymentDescriptor, namespace) - .label(MtaMetadataLabels.SPACE_GUID, spaceGuid); + .putLabel(MtaMetadataLabels.SPACE_GUID, spaceGuid); return builder.build(); } diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServiceMetadataBuilder.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServiceMetadataBuilder.java index f80768cd03..b995e3fe74 100644 --- a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServiceMetadataBuilder.java +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServiceMetadataBuilder.java @@ -3,8 +3,8 @@ import java.util.Map; import java.util.TreeMap; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata.Builder; import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata.Builder; import org.cloudfoundry.multiapps.common.util.JsonUtil; import org.cloudfoundry.multiapps.common.util.MapUtil; import org.cloudfoundry.multiapps.controller.core.Constants; @@ -18,7 +18,7 @@ public static Metadata build(DeploymentDescriptor deploymentDescriptor, String n String mtaResourceAnnotation = buildMtaResourceAnnotation(resource); Builder builder = MtaMetadataBuilder.init(deploymentDescriptor, namespace) - .annotation(MtaMetadataAnnotations.MTA_RESOURCE, mtaResourceAnnotation); + .putAnnotation(MtaMetadataAnnotations.MTA_RESOURCE, mtaResourceAnnotation); return builder.build(); } diff --git a/multiapps-controller-core/src/test/java/org/cloudfoundry/multiapps/controller/core/cf/detect/DeployedMtaDetectorTest.java b/multiapps-controller-core/src/test/java/org/cloudfoundry/multiapps/controller/core/cf/detect/DeployedMtaDetectorTest.java index 8d5230fb38..69454f4973 100644 --- a/multiapps-controller-core/src/test/java/org/cloudfoundry/multiapps/controller/core/cf/detect/DeployedMtaDetectorTest.java +++ b/multiapps-controller-core/src/test/java/org/cloudfoundry/multiapps/controller/core/cf/detect/DeployedMtaDetectorTest.java @@ -9,7 +9,6 @@ import java.util.stream.Stream; import com.fasterxml.jackson.core.type.TypeReference; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.common.test.TestUtil; import org.cloudfoundry.multiapps.common.test.Tester; import org.cloudfoundry.multiapps.common.test.Tester.Expectation; @@ -20,6 +19,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudApplication; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.controller.core.cf.metadata.entity.processor.MtaMetadataApplicationCollector; import org.cloudfoundry.multiapps.controller.core.cf.metadata.entity.processor.MtaMetadataEntityAggregator; import org.cloudfoundry.multiapps.controller.core.cf.metadata.entity.processor.MtaMetadataEntityCollector; @@ -259,7 +259,7 @@ private CloudApplication toCloudApplication() { .guid(NameUtil.getUUID(name)) .build()) .name(name) - .v3Metadata(Metadata.builder() + .v3Metadata(ImmutableMetadata.builder() .annotations(annotations) .labels(labels) .build()) @@ -279,7 +279,7 @@ private CloudServiceInstance toCloudService() { .guid(NameUtil.getUUID(name)) .build()) .name(name) - .v3Metadata(Metadata.builder() + .v3Metadata(ImmutableMetadata.builder() .annotations(annotations) .labels(labels) .build()) diff --git a/multiapps-controller-core/src/test/java/org/cloudfoundry/multiapps/controller/core/util/ApplicationConfigurationTest.java b/multiapps-controller-core/src/test/java/org/cloudfoundry/multiapps/controller/core/util/ApplicationConfigurationTest.java index af8e25b17a..51c769f922 100644 --- a/multiapps-controller-core/src/test/java/org/cloudfoundry/multiapps/controller/core/util/ApplicationConfigurationTest.java +++ b/multiapps-controller-core/src/test/java/org/cloudfoundry/multiapps/controller/core/util/ApplicationConfigurationTest.java @@ -7,7 +7,7 @@ import java.util.List; import java.util.Map; -import io.jsonwebtoken.lang.Strings; +import org.apache.commons.lang3.StringUtils; import org.cloudfoundry.multiapps.common.test.TestUtil; import org.cloudfoundry.multiapps.common.util.JsonUtil; import org.cloudfoundry.multiapps.controller.core.Messages; @@ -613,8 +613,8 @@ void testIsOnStartFilesWithoutContentCleanerEnabledThroughEnvironmentWithEnv() { @Test void testLoad() { - when(environment.getString(ApplicationConfiguration.OBJECTSTORE_REGIONS, Strings.EMPTY)) - .thenReturn(Strings.EMPTY); + when(environment.getString(ApplicationConfiguration.OBJECTSTORE_REGIONS, StringUtils.EMPTY)) + .thenReturn(StringUtils.EMPTY); Map vcapApplication = injectFileInEnvironment(VCAP_APPLICATION, ApplicationConfiguration.CFG_VCAP_APPLICATION); configuration.load(); Assertions.assertEquals(vcapApplication.get("cf_api"), configuration.getControllerUrl() diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/client/LoggingCloudControllerClient.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/client/LoggingCloudControllerClient.java index 60f0909f3c..0685aa3a61 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/client/LoggingCloudControllerClient.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/client/LoggingCloudControllerClient.java @@ -275,12 +275,6 @@ public List getApplicationRoutes(UUID applicationGuid) { return delegate.getApplicationRoutes(applicationGuid); } - @Override - public boolean getApplicationSshEnabled(UUID applicationGuid) { - logger.debug(Messages.GETTING_SSH_ENABLED_FOR_APPLICATION_0, applicationGuid); - return delegate.getApplicationSshEnabled(applicationGuid); - } - @Override public Map getApplicationFeatures(UUID applicationGuid) { logger.debug(Messages.GETTING_FEATURES_FOR_APPLICATION_0, applicationGuid); diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/PrepareApplicationForBackupStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/PrepareApplicationForBackupStep.java index 3fcf4a2856..c44512a817 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/PrepareApplicationForBackupStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/PrepareApplicationForBackupStep.java @@ -3,9 +3,9 @@ import java.text.MessageFormat; import jakarta.inject.Named; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.CloudControllerClient; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataAnnotations; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataLabels; import org.cloudfoundry.multiapps.controller.core.cf.metadata.util.MtaMetadataUtil; @@ -35,12 +35,12 @@ protected StepPhase executeStep(ProcessContext context) throws Exception { getStepLogger().info(Messages.RENAMING_APPLICATION_0_TO_1_TO_BE_USED_FOR_ROLLBACK, cloudApplication.getName(), newApplicationName); client.rename(cloudApplication.getName(), newApplicationName); String hashedMtaNamespace = MtaMetadataUtil.getHashedLabel(mtaUserNamespaceWithSystemNamespace); - client.updateApplicationMetadata(cloudApplication.getGuid(), Metadata.builder() - .from(cloudApplication.getV3Metadata()) - .label(MtaMetadataLabels.MTA_NAMESPACE, hashedMtaNamespace) - .annotation(MtaMetadataAnnotations.MTA_NAMESPACE, - mtaUserNamespaceWithSystemNamespace) - .build()); + client.updateApplicationMetadata(cloudApplication.getGuid(), ImmutableMetadata.builder() + .from(cloudApplication.getV3Metadata()) + .putLabel(MtaMetadataLabels.MTA_NAMESPACE, hashedMtaNamespace) + .putAnnotation(MtaMetadataAnnotations.MTA_NAMESPACE, + mtaUserNamespaceWithSystemNamespace) + .build()); return StepPhase.DONE; } diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/RemoveMtaBackupMetadataStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/RemoveMtaBackupMetadataStep.java index 7d93f193a0..b4441df77d 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/RemoveMtaBackupMetadataStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/RemoveMtaBackupMetadataStep.java @@ -1,8 +1,8 @@ package org.cloudfoundry.multiapps.controller.process.steps; import jakarta.inject.Named; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.CloudControllerClient; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataAnnotations; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataLabels; import org.cloudfoundry.multiapps.controller.core.cf.metadata.util.MtaMetadataUtil; @@ -28,10 +28,10 @@ protected StepPhase executeStep(ProcessContext context) throws Exception { for (DeployedMtaApplication application : backupMta.getApplications()) { getStepLogger().debug(Messages.REMOVE_MTA_BACKUP_METADATA_FOR_APPLICATION_0, application.getName()); - client.updateApplicationMetadata(application.getGuid(), Metadata.builder() + client.updateApplicationMetadata(application.getGuid(), ImmutableMetadata.builder() .from(application.getV3Metadata()) - .label(MtaMetadataLabels.MTA_NAMESPACE, hashedMtaNamespace) - .annotation(MtaMetadataAnnotations.MTA_NAMESPACE, mtaNamespace) + .putLabel(MtaMetadataLabels.MTA_NAMESPACE, hashedMtaNamespace) + .putAnnotation(MtaMetadataAnnotations.MTA_NAMESPACE, mtaNamespace) .build()); } diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/StepsUtil.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/StepsUtil.java index d5058281c8..1efcd4b4ec 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/StepsUtil.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/StepsUtil.java @@ -23,6 +23,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBroker; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudTask; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudServiceInstanceExtended; import org.cloudfoundry.multiapps.controller.core.cf.CloudHandlerFactory; import org.cloudfoundry.multiapps.controller.core.cf.detect.AppSuffixDeterminer; @@ -294,8 +295,8 @@ public static void setAsJsonBinary(VariableScope scope, String name, Object valu public static void disableAutoscaling(ProcessContext context, CloudControllerClient client, UUID uuid) { String correlationId = context.getVariable(Variables.CORRELATION_ID); - Metadata metadata = Metadata.builder() - .label(MtaMetadataLabels.AUTOSCALER_LABEL, + Metadata metadata = ImmutableMetadata.builder() + .putLabel(MtaMetadataLabels.AUTOSCALER_LABEL, MessageFormat.format(Messages.DISABLE_AUTOSCALER_LABEL_CONTENT, correlationId)) .build(); client.updateApplicationMetadata(uuid, metadata); @@ -303,8 +304,8 @@ public static void disableAutoscaling(ProcessContext context, CloudControllerCli public static void enableAutoscaling(CloudControllerClient client, CloudApplication application) { UUID applicationId = client.getApplicationGuid(application.getName()); - Metadata metadata = Metadata.builder() - .label(MtaMetadataLabels.AUTOSCALER_LABEL, null) + Metadata metadata = ImmutableMetadata.builder() + .putLabel(MtaMetadataLabels.AUTOSCALER_LABEL, null) .build(); client.updateApplicationMetadata(applicationId, metadata); } diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/CreateOrUpdateStepWithExistingAppTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/CreateOrUpdateStepWithExistingAppTest.java index 78f20b5f65..fe6156d9c8 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/CreateOrUpdateStepWithExistingAppTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/CreateOrUpdateStepWithExistingAppTest.java @@ -227,7 +227,6 @@ private void prepareClient(CloudApplication application, Set routes, when(client.getApplicationEnvironment(application.getGuid())).thenReturn(env); application = prepareAppWithStaging(application, staging); when(client.getApplication(application.getName(), false)).thenReturn(application); - when(client.getApplicationSshEnabled(application.getGuid())).thenReturn(staging.isSshEnabled() != null && staging.isSshEnabled()); when(client.getApplicationRoutes(application.getGuid())).thenReturn(List.copyOf(routes)); String command = staging.getCommand() == null ? DEFAULT_COMMAND : staging.getCommand(); var hcType = staging.getHealthCheckType(); diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DeleteServiceMtaMetadataStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DeleteServiceMtaMetadataStepTest.java index 650b4faa3c..7413431144 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DeleteServiceMtaMetadataStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DeleteServiceMtaMetadataStepTest.java @@ -2,8 +2,8 @@ import java.util.UUID; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudServiceInstanceExtended; import org.cloudfoundry.multiapps.controller.client.lib.domain.ImmutableCloudServiceInstanceExtended; import org.cloudfoundry.multiapps.controller.process.variables.Variables; @@ -33,8 +33,8 @@ private CloudServiceInstanceExtended buildCloudServiceInstanceExtended() { return ImmutableCloudServiceInstanceExtended.builder() .name(SERVICE_INSTANCE_NAME) .metadata(ImmutableCloudMetadata.of(SERVICE_INSTANCE_GUID)) - .v3Metadata(Metadata.builder() - .annotation("key", "value") + .v3Metadata(ImmutableMetadata.builder() + .putAnnotation("key", "value") .build()) .build(); } diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetachServiceKeysFromMtaStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetachServiceKeysFromMtaStepTest.java index b3f581c732..8085e30558 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetachServiceKeysFromMtaStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetachServiceKeysFromMtaStepTest.java @@ -9,6 +9,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataAnnotations; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataLabels; import org.cloudfoundry.multiapps.controller.core.model.DeployedMtaServiceKey; @@ -61,14 +62,14 @@ private void validateServicesDetached(List serviceKeysToD } private static Metadata getMetadataWithoutMtaFields() { - return Metadata.builder() - .label(MtaMetadataLabels.MTA_ID, null) - .label(MtaMetadataLabels.MTA_NAMESPACE, null) - .label(MtaMetadataLabels.SPACE_GUID, null) - .annotation(MtaMetadataAnnotations.MTA_ID, null) - .annotation(MtaMetadataAnnotations.MTA_VERSION, null) - .annotation(MtaMetadataAnnotations.MTA_RESOURCE, null) - .annotation(MtaMetadataLabels.MTA_NAMESPACE, null) + return ImmutableMetadata.builder() + .putLabel(MtaMetadataLabels.MTA_ID, null) + .putLabel(MtaMetadataLabels.MTA_NAMESPACE, null) + .putLabel(MtaMetadataLabels.SPACE_GUID, null) + .putAnnotation(MtaMetadataAnnotations.MTA_ID, null) + .putAnnotation(MtaMetadataAnnotations.MTA_VERSION, null) + .putAnnotation(MtaMetadataAnnotations.MTA_RESOURCE, null) + .putAnnotation(MtaMetadataLabels.MTA_NAMESPACE, null) .build(); } @@ -87,14 +88,14 @@ private static DeployedMtaServiceKey createDeployedServiceKeyWithoutMetadata(Str private static DeployedMtaServiceKey createDeployedServiceKey(String name, boolean generateMetadata) { Metadata v3Metadata; if (generateMetadata) { - v3Metadata = Metadata.builder() - .label(MtaMetadataLabels.MTA_ID, "098f6bcd4621d373cade4e832627b4f6") - .annotation(MtaMetadataAnnotations.MTA_ID, "test") - .annotation(MtaMetadataAnnotations.MTA_VERSION, "1") - .annotation(MtaMetadataAnnotations.MTA_RESOURCE, "test") + v3Metadata = ImmutableMetadata.builder() + .putLabel(MtaMetadataLabels.MTA_ID, "098f6bcd4621d373cade4e832627b4f6") + .putAnnotation(MtaMetadataAnnotations.MTA_ID, "test") + .putAnnotation(MtaMetadataAnnotations.MTA_VERSION, "1") + .putAnnotation(MtaMetadataAnnotations.MTA_RESOURCE, "test") .build(); } else { - v3Metadata = Metadata.builder() + v3Metadata = ImmutableMetadata.builder() .labels(Collections.emptyMap()) .annotations(Collections.emptyMap()) .build(); diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetachServicesFromMtaStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetachServicesFromMtaStepTest.java index d3b8e2005e..169c20d8ef 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetachServicesFromMtaStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetachServicesFromMtaStepTest.java @@ -11,6 +11,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceInstance; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataAnnotations; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataLabels; import org.cloudfoundry.multiapps.controller.process.variables.Variables; @@ -53,14 +54,14 @@ private void validateServicesDetached(List servicesToDeta } private static Metadata getMetadataWithoutMtaFields() { - return Metadata.builder() - .label(MtaMetadataLabels.MTA_ID, null) - .label(MtaMetadataLabels.MTA_NAMESPACE, null) - .label(MtaMetadataLabels.SPACE_GUID, null) - .annotation(MtaMetadataAnnotations.MTA_ID, null) - .annotation(MtaMetadataAnnotations.MTA_VERSION, null) - .annotation(MtaMetadataAnnotations.MTA_RESOURCE, null) - .annotation(MtaMetadataLabels.MTA_NAMESPACE, null) + return ImmutableMetadata.builder() + .putLabel(MtaMetadataLabels.MTA_ID, null) + .putLabel(MtaMetadataLabels.MTA_NAMESPACE, null) + .putLabel(MtaMetadataLabels.SPACE_GUID, null) + .putAnnotation(MtaMetadataAnnotations.MTA_ID, null) + .putAnnotation(MtaMetadataAnnotations.MTA_VERSION, null) + .putAnnotation(MtaMetadataAnnotations.MTA_RESOURCE, null) + .putAnnotation(MtaMetadataLabels.MTA_NAMESPACE, null) .build(); } @@ -88,11 +89,11 @@ private void prepareClient(List servicesToDelete) { } private CloudServiceInstance createServiceInstance(SimpleServiceInstance serviceInstance) { - Metadata v3Metadata = Metadata.builder() - .label(MtaMetadataLabels.MTA_ID, "098f6bcd4621d373cade4e832627b4f6") - .annotation(MtaMetadataAnnotations.MTA_ID, "test") - .annotation(MtaMetadataAnnotations.MTA_VERSION, "1") - .annotation(MtaMetadataAnnotations.MTA_RESOURCE, "test") + Metadata v3Metadata = ImmutableMetadata.builder() + .putLabel(MtaMetadataLabels.MTA_ID, "098f6bcd4621d373cade4e832627b4f6") + .putAnnotation(MtaMetadataAnnotations.MTA_ID, "test") + .putAnnotation(MtaMetadataAnnotations.MTA_VERSION, "1") + .putAnnotation(MtaMetadataAnnotations.MTA_RESOURCE, "test") .build(); CloudMetadata metadata = ImmutableCloudMetadata.builder() .guid(serviceInstance.guid) 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 418341ae0d..6ce1f876e1 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 @@ -4,7 +4,7 @@ import java.util.List; import java.util.Optional; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.common.test.TestUtil; import org.cloudfoundry.multiapps.common.test.Tester.Expectation; import org.cloudfoundry.multiapps.common.util.JsonUtil; @@ -99,8 +99,8 @@ void testExecuteWithBackupdMta() { .addApplication(ImmutableDeployedMtaApplication.builder() .moduleName("test-module") .name("test-app") - .v3Metadata(Metadata.builder() - .annotation( + .v3Metadata(ImmutableMetadata.builder() + .putAnnotation( MtaMetadataAnnotations.MTA_VERSION, MTA_VERSION_2) .build()) @@ -116,8 +116,8 @@ void testExecuteWithBackupdMta() { .addApplication(ImmutableDeployedMtaApplication.builder() .moduleName("test-module") .name("mta-backup-test-app") - .v3Metadata(Metadata.builder() - .annotation( + .v3Metadata(ImmutableMetadata.builder() + .putAnnotation( MtaMetadataAnnotations.MTA_VERSION, MTA_VERSION_1) .build()) diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceDeleteActionsToExecuteStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceDeleteActionsToExecuteStepTest.java index 41a47a3cd8..9d8a8ffc32 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceDeleteActionsToExecuteStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceDeleteActionsToExecuteStepTest.java @@ -5,12 +5,12 @@ import java.util.Map; import java.util.UUID; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBinding; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceBinding; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceKey; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableServiceCredentialBindingOperation; import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceCredentialBindingOperation; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudServiceInstanceExtended; @@ -102,8 +102,8 @@ private CloudServiceInstanceExtended buildOptionalCloudServiceInstance() { return ImmutableCloudServiceInstanceExtended.builder() .isOptional(true) .name(SERVICE_TO_DELETE) - .v3Metadata(Metadata.builder() - .label("label-key", "label-value") + .v3Metadata(ImmutableMetadata.builder() + .putLabel("label-key", "label-value") .build()) .build(); } diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceKeysToRecreateStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceKeysToRecreateStepTest.java index be639d0bd6..9e8a328d58 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceKeysToRecreateStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceKeysToRecreateStepTest.java @@ -5,12 +5,12 @@ import java.util.Map; import java.util.UUID; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.common.SLException; import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceKey; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableServiceCredentialBindingOperation; import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceCredentialBindingOperation; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudServiceInstanceExtended; @@ -125,11 +125,11 @@ void testUpdateUnchangedServiceKeyMetadata() { CloudServiceInstanceExtended serviceInstance = buildCloudServiceInstanceExtended(false); CloudServiceKey serviceKeyToUpdate = buildCloudServiceKey(); CloudServiceKey existingVersionOfKey = ImmutableCloudServiceKey.copyOf(serviceKeyToUpdate) - .withV3Metadata(Metadata.builder() - .label(MtaMetadataLabels.MTA_ID, "aaaaa") - .annotation(MtaMetadataAnnotations.MTA_ID, + .withV3Metadata(ImmutableMetadata.builder() + .putLabel(MtaMetadataLabels.MTA_ID, "aaaaa") + .putAnnotation(MtaMetadataAnnotations.MTA_ID, "different-test-mta") - .annotation( + .putAnnotation( MtaMetadataAnnotations.MTA_VERSION, MTA_VERSION) .build()); @@ -156,10 +156,10 @@ private CloudServiceKey buildCloudServiceKey() { return ImmutableCloudServiceKey.builder() .name(SERVICE_KEY_NAME) .metadata(ImmutableCloudMetadata.of(SERVICE_KEY_GUID)) - .v3Metadata(Metadata.builder() - .label(MtaMetadataLabels.MTA_ID, MTA_ID_HASH) - .annotation(MtaMetadataAnnotations.MTA_ID, MTA_ID) - .annotation(MtaMetadataAnnotations.MTA_VERSION, MTA_VERSION) + .v3Metadata(ImmutableMetadata.builder() + .putLabel(MtaMetadataLabels.MTA_ID, MTA_ID_HASH) + .putAnnotation(MtaMetadataAnnotations.MTA_ID, MTA_ID) + .putAnnotation(MtaMetadataAnnotations.MTA_VERSION, MTA_VERSION) .build()) .serviceKeyOperation(ImmutableServiceCredentialBindingOperation.builder() .type( diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PrepareApplicationForBackupStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PrepareApplicationForBackupStepTest.java index c3c511a19d..e5b8211ab6 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PrepareApplicationForBackupStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PrepareApplicationForBackupStepTest.java @@ -3,8 +3,8 @@ import java.util.UUID; import java.util.stream.Stream; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudApplicationExtended; import org.cloudfoundry.multiapps.controller.client.lib.domain.ImmutableCloudApplicationExtended; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataAnnotations; @@ -45,10 +45,10 @@ void testStep(String applicationName, String namespace, String expectedNewApplic assertStepFinishedSuccessfully(); verify(client).rename(applicationName, expectedNewApplicationName); - verify(client).updateApplicationMetadata(applicationGuid, Metadata.builder() - .label(MtaMetadataLabels.MTA_NAMESPACE, + verify(client).updateApplicationMetadata(applicationGuid, ImmutableMetadata.builder() + .putLabel(MtaMetadataLabels.MTA_NAMESPACE, MtaMetadataUtil.getHashedLabel(expectedNamespace)) - .annotation(MtaMetadataAnnotations.MTA_NAMESPACE, + .putAnnotation(MtaMetadataAnnotations.MTA_NAMESPACE, expectedNamespace) .build()); } @@ -59,7 +59,7 @@ private void prepareContext(String applicationName, String namespace, UUID appli .metadata(ImmutableCloudMetadata.builder() .guid(applicationGuid) .build()) - .v3Metadata(Metadata.builder() + .v3Metadata(ImmutableMetadata.builder() .build()) .build(); context.setVariable(Variables.APP_TO_PROCESS, application); diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PrepareBackupMtaForDeploymentStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PrepareBackupMtaForDeploymentStepTest.java index 4d9262cec4..83cdd3eb13 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PrepareBackupMtaForDeploymentStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PrepareBackupMtaForDeploymentStepTest.java @@ -13,7 +13,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.common.ContentException; import org.cloudfoundry.multiapps.controller.core.cf.detect.DeployedMtaDetector; import org.cloudfoundry.multiapps.controller.core.cf.metadata.ImmutableMtaMetadata; @@ -129,8 +129,8 @@ private List createDeployedMtaApplications(List .map(testApp -> ImmutableDeployedMtaApplication.builder() .name(testApp.appName) .moduleName(testApp.appName) - .v3Metadata(Metadata.builder() - .annotation(MtaMetadataAnnotations.MTA_VERSION, + .v3Metadata(ImmutableMetadata.builder() + .putAnnotation(MtaMetadataAnnotations.MTA_VERSION, testApp.version) .build()) .build()) diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/RemoveMtaBackupMetadataStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/RemoveMtaBackupMetadataStepTest.java index a3e0442a32..7f1a47bc6f 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/RemoveMtaBackupMetadataStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/RemoveMtaBackupMetadataStepTest.java @@ -2,8 +2,8 @@ import java.util.UUID; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.controller.core.cf.metadata.ImmutableMtaMetadata; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataAnnotations; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataLabels; @@ -34,9 +34,9 @@ void testRemoveSystemNamespace() { step.execute(execution); assertStepFinishedSuccessfully(); - verify(client).updateApplicationMetadata(APP_GUID, Metadata.builder() - .label(MtaMetadataLabels.MTA_NAMESPACE, null) - .annotation(MtaMetadataAnnotations.MTA_NAMESPACE, null) + verify(client).updateApplicationMetadata(APP_GUID, ImmutableMetadata.builder() + .putLabel(MtaMetadataLabels.MTA_NAMESPACE, null) + .putAnnotation(MtaMetadataAnnotations.MTA_NAMESPACE, null) .build()); } @@ -50,10 +50,10 @@ void testRemoveSystemNamespaceAndKeepUserNamespace() { step.execute(execution); assertStepFinishedSuccessfully(); - verify(client).updateApplicationMetadata(APP_GUID, Metadata.builder() - .label(MtaMetadataLabels.MTA_NAMESPACE, + verify(client).updateApplicationMetadata(APP_GUID, ImmutableMetadata.builder() + .putLabel(MtaMetadataLabels.MTA_NAMESPACE, MtaMetadataUtil.getHashedLabel(userNamespace)) - .annotation(MtaMetadataAnnotations.MTA_NAMESPACE, userNamespace) + .putAnnotation(MtaMetadataAnnotations.MTA_NAMESPACE, userNamespace) .build()); } @@ -62,11 +62,11 @@ private DeployedMta createBackupMta(String namespace) { .addApplication(ImmutableDeployedMtaApplication.builder() .moduleName(MODULE_NAME) .name(BACKUP_APP_NAME) - .v3Metadata(Metadata.builder() - .label( + .v3Metadata(ImmutableMetadata.builder() + .putLabel( MtaMetadataLabels.MTA_NAMESPACE, namespace) - .annotation( + .putAnnotation( MtaMetadataAnnotations.MTA_NAMESPACE, namespace) .build()) diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/StepsTestUtil.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/StepsTestUtil.java index daf246a99c..2d5a604930 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/StepsTestUtil.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/StepsTestUtil.java @@ -3,6 +3,7 @@ import java.util.List; import java.util.UUID; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.common.util.JsonUtil; import org.cloudfoundry.multiapps.controller.client.facade.CloudControllerClient; @@ -34,8 +35,8 @@ public static void prepareDisablingAutoscaler(ProcessContext context, CloudContr } public static void testIfEnabledOrDisabledAutoscaler(CloudControllerClient client, String labelValue, UUID uid) { - Metadata metadata = Metadata.builder() - .label(MtaMetadataLabels.AUTOSCALER_LABEL, labelValue) + Metadata metadata = ImmutableMetadata.builder() + .putLabel(MtaMetadataLabels.AUTOSCALER_LABEL, labelValue) .build(); verify(client).updateApplicationMetadata(uid, metadata); } diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/SyncFlowableStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/SyncFlowableStepTest.java index 5a3065be72..aec0b06039 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/SyncFlowableStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/SyncFlowableStepTest.java @@ -8,10 +8,10 @@ import java.util.UUID; import java.util.stream.Stream; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.common.test.Tester; import org.cloudfoundry.multiapps.controller.client.facade.CloudControllerClient; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudApplicationExtended; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudServiceInstanceExtended; import org.cloudfoundry.multiapps.controller.client.lib.domain.ImmutableCloudApplicationExtended; @@ -194,8 +194,8 @@ protected CloudServiceInstanceExtended buildServiceToProcess(boolean isOptional) .guid(UUID.randomUUID()) .build()) .syslogDrainUrl(SYSLOG_DRAIN_URL) - .v3Metadata(Metadata.builder() - .label(METADATA_LABEL, METADATA_LABEL_VALUE) + .v3Metadata(ImmutableMetadata.builder() + .putLabel(METADATA_LABEL, METADATA_LABEL_VALUE) .build()) .isOptional(isOptional) .build(); diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/UpdateServiceKeysMetadataStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/UpdateServiceKeysMetadataStepTest.java index 7f9d40033c..ae69c46938 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/UpdateServiceKeysMetadataStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/UpdateServiceKeysMetadataStepTest.java @@ -11,6 +11,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceKey; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataAnnotations; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataLabels; import org.cloudfoundry.multiapps.controller.process.Messages; @@ -64,14 +65,14 @@ void testUpdateServiceMetadataFailure() { private CloudServiceKey buildTestServiceKey(boolean generateMetadata) { Metadata v3Metadata; if (generateMetadata) { - v3Metadata = Metadata.builder() - .label(MtaMetadataLabels.MTA_ID, "098f6bcd4621d373cade4e832627b4f6") - .annotation(MtaMetadataAnnotations.MTA_ID, "test") - .annotation(MtaMetadataAnnotations.MTA_VERSION, "1") - .annotation(MtaMetadataAnnotations.MTA_RESOURCE, "test") + v3Metadata = ImmutableMetadata.builder() + .putLabel(MtaMetadataLabels.MTA_ID, "098f6bcd4621d373cade4e832627b4f6") + .putAnnotation(MtaMetadataAnnotations.MTA_ID, "test") + .putAnnotation(MtaMetadataAnnotations.MTA_VERSION, "1") + .putAnnotation(MtaMetadataAnnotations.MTA_RESOURCE, "test") .build(); } else { - v3Metadata = Metadata.builder() + v3Metadata = ImmutableMetadata.builder() .labels(Collections.emptyMap()) .annotations(Collections.emptyMap()) .build(); diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/ExistingAppsToBackupCalculatorTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/ExistingAppsToBackupCalculatorTest.java index a4d2c66be7..ba262b726a 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/ExistingAppsToBackupCalculatorTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/ExistingAppsToBackupCalculatorTest.java @@ -7,9 +7,9 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudApplication; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.controller.core.cf.metadata.ImmutableMtaMetadata; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadataAnnotations; import org.cloudfoundry.multiapps.controller.core.model.DeployedMta; @@ -66,8 +66,8 @@ private static Stream testCalculateExistingAppsToBackup() { Collections.emptyList(), "2", true, List.of("app-1-live", "app-1-idle"), List.of(ImmutableCloudApplication.builder() .name("app-1-live") - .v3Metadata(Metadata.builder() - .annotation(MtaMetadataAnnotations.MTA_VERSION, + .v3Metadata(ImmutableMetadata.builder() + .putAnnotation(MtaMetadataAnnotations.MTA_VERSION, "1") .build()) .build())), @@ -83,8 +83,8 @@ private static Stream testCalculateExistingAppsToBackup() { "3", true, List.of("app-1-live", "mta-backup-app-1"), List.of(ImmutableCloudApplication.builder() .name("app-1-live") .v3Metadata( - Metadata.builder() - .annotation( + ImmutableMetadata.builder() + .putAnnotation( MtaMetadataAnnotations.MTA_VERSION, "2") .build()) @@ -106,8 +106,8 @@ private static Stream testCalculateExistingAppsToBackup() { List.of(new TestApplication("app-1", "mta-backup-app-1", "1")), "3", true, List.of("app-1-live"), List.of(ImmutableCloudApplication.builder() .name("app-1-live") - .v3Metadata(Metadata.builder() - .annotation(MtaMetadataAnnotations.MTA_VERSION, + .v3Metadata(ImmutableMetadata.builder() + .putAnnotation(MtaMetadataAnnotations.MTA_VERSION, "2") .build()) .build())), @@ -146,8 +146,8 @@ private static Stream testCalculateAppsToUndeploy() { Arguments.of(List.of(new TestApplication("app-1", "mta-backup-app-1", "1")), List.of("app-1-idle"), true, List.of(ImmutableCloudApplication.builder() .name("mta-backup-app-1") - .v3Metadata(Metadata.builder() - .annotation(MtaMetadataAnnotations.MTA_VERSION, + .v3Metadata(ImmutableMetadata.builder() + .putAnnotation(MtaMetadataAnnotations.MTA_VERSION, "1") .build()) .build())), @@ -166,16 +166,16 @@ private static Stream testCalculateAppsToUndeploy() { new TestApplication("app-2", "mta-backup-app-2", "2")), Collections.emptyList(), false, List.of(ImmutableCloudApplication.builder() .name("mta-backup-app-1") - .v3Metadata(Metadata.builder() - .annotation( + .v3Metadata(ImmutableMetadata.builder() + .putAnnotation( MtaMetadataAnnotations.MTA_VERSION, "2") .build()) .build(), ImmutableCloudApplication.builder() .name("mta-backup-app-2") - .v3Metadata(Metadata.builder() - .annotation( + .v3Metadata(ImmutableMetadata.builder() + .putAnnotation( MtaMetadataAnnotations.MTA_VERSION, "2") .build()) @@ -213,12 +213,12 @@ private DeployedMta getDeployedMta(List deployedApplications) { deployedMtaApplications.add(ImmutableDeployedMtaApplication.builder() .moduleName(application.moduleName) .name(application.appName) - .v3Metadata(application.mtaVersion != null ? Metadata.builder() - .annotation( + .v3Metadata(application.mtaVersion != null ? ImmutableMetadata.builder() + .putAnnotation( MtaMetadataAnnotations.MTA_VERSION, application.mtaVersion) .build() - : Metadata.builder() + : ImmutableMetadata.builder() .annotations(Collections.emptyMap()) .build()) .productizationState(application.productizationState) diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/ModuleDeterminerTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/ModuleDeterminerTest.java index dfa90198c3..de7d1cac2e 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/ModuleDeterminerTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/ModuleDeterminerTest.java @@ -3,6 +3,7 @@ import java.util.List; import java.util.Map; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudApplicationExtended; import org.cloudfoundry.multiapps.controller.client.lib.domain.ImmutableCloudApplicationExtended; @@ -97,7 +98,7 @@ private CloudApplicationExtended createApplication(String appName, String module } private Metadata createV3Metadata(Map labels, Map annotations) { - return Metadata.builder() + return ImmutableMetadata.builder() .labels(labels) .annotations(annotations) .build(); diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/OperationInFinalStateHandlerTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/OperationInFinalStateHandlerTest.java index fc6f8aab18..bb3fc3bd1d 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/OperationInFinalStateHandlerTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/OperationInFinalStateHandlerTest.java @@ -5,7 +5,7 @@ import java.util.List; import java.util.stream.Stream; -import org.cloudfoundry.multiapps.controller.client.facade.domain.Metadata; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableMetadata; import org.cloudfoundry.multiapps.controller.api.model.ImmutableOperation; import org.cloudfoundry.multiapps.controller.api.model.Operation; import org.cloudfoundry.multiapps.controller.api.model.Operation.State; @@ -489,8 +489,8 @@ private DeployedMta buildDeployedMta(String mtaVersion) { DeployedMtaApplication application = ImmutableDeployedMtaApplication.builder() .name("app-1") .moduleName("module-1") - .v3Metadata(Metadata.builder() - .annotation( + .v3Metadata(ImmutableMetadata.builder() + .putAnnotation( MtaMetadataAnnotations.MTA_VERSION, mtaVersion) .build()) diff --git a/pom.xml b/pom.xml index 2f6fcf00dd..3f52039a60 100644 --- a/pom.xml +++ b/pom.xml @@ -58,12 +58,12 @@ 1.28.0 3.3.2 1.3.6 - 0.12.6 2.70.0 0.134.0 12.35.0 1.0.4 4.0.1 + 2.2 multiapps-controller-client @@ -406,6 +406,20 @@ micrometer-registry-jmx ${micrometer.version} + + + org.springframework + spring-test + ${spring.version} + test + + + + org.hamcrest + hamcrest + ${hamcrest.version}} + test + io.swagger From e6453d473b8d04fae225587f0b7c214a5c9ddc6e Mon Sep 17 00:00:00 2001 From: Krasimir Kargov Date: Tue, 13 Oct 2026 08:47:50 +0300 Subject: [PATCH 13/15] Fix deleted dependency from rebasing, which is actually used for the operations limiting LMCROSSITXSADEPLOY-3563 --- pom.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pom.xml b/pom.xml index 3f52039a60..99f7358fb3 100644 --- a/pom.xml +++ b/pom.xml @@ -64,6 +64,7 @@ 1.0.4 4.0.1 2.2 + 8.20.0 multiapps-controller-client @@ -838,6 +839,12 @@ resilience4j-ratelimiter ${resilience4j.version} + + + com.bucket4j + bucket4j_jdk17-postgresql + ${bucket4j.version} + org.bouncycastle From b3f8aa7dcd159e71179b99916492e97308169fc6 Mon Sep 17 00:00:00 2001 From: Krasimir Kargov Date: Tue, 13 Oct 2026 13:18:43 +0300 Subject: [PATCH 14/15] Fix maven problem regarding 3.10 LMCROSSITXSADEPLOY-3563 --- multiapps-controller-persistence/pom.xml | 7 ++----- multiapps-controller-process/pom.xml | 4 ---- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/multiapps-controller-persistence/pom.xml b/multiapps-controller-persistence/pom.xml index 065c46b790..a45803f9f4 100644 --- a/multiapps-controller-persistence/pom.xml +++ b/multiapps-controller-persistence/pom.xml @@ -19,7 +19,8 @@ maven-surefire-plugin - ${project.basedir}/src/test/resources/logging.properties + ${project.basedir}/src/test/resources/logging.properties + @@ -90,10 +91,6 @@ org.glassfish.jaxb jaxb-core - - org.glassfish.jaxb - jaxb-runtime - com.h2database h2 diff --git a/multiapps-controller-process/pom.xml b/multiapps-controller-process/pom.xml index 86f9727179..664d4c4957 100644 --- a/multiapps-controller-process/pom.xml +++ b/multiapps-controller-process/pom.xml @@ -33,10 +33,6 @@ org.apache.commons commons-compress - - jakarta.xml.bind - jakarta.xml.bind-api - org.glassfish.jaxb jaxb-core From 8353fd9067fb3523464d1b80087dce1bf3efe267 Mon Sep 17 00:00:00 2001 From: Krasimir Kargov Date: Tue, 13 Oct 2026 14:52:02 +0300 Subject: [PATCH 15/15] Fixing critical findings from sonar after the refactoring and comments LMCROSSITXSADEPLOY-3563 --- .../cloudfoundry/multiapps/controller/Constants.java | 2 +- .../client/facade/rest/ApplicationsV3Operations.java | 4 +++- .../facade/rest/ServiceBindingsV3Operations.java | 3 ++- .../facade/rest/ServiceInstancesV3Operations.java | 3 ++- .../controller/client/facade/rest/V3Uri.java | 12 ++++++------ 5 files changed, 14 insertions(+), 10 deletions(-) diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Constants.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Constants.java index 4f8ab5ea56..7d06f44b7e 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Constants.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/Constants.java @@ -38,7 +38,7 @@ public class Constants { public static final String COLON = ":"; public static final String COMMA = ","; public static final String SPACE_SYMBOL = " "; - public static final String EQUALS = "="; + public static final String EQUALS_SYMBOL = "="; public static final String PROTOCOL_SEPARATOR = "://"; public static final String QUESTION_MARK = "?"; public static final String AMPERSAND = "&"; diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3Operations.java index 2126f5baf4..7b3f722445 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ApplicationsV3Operations.java @@ -196,7 +196,9 @@ public void bindDropletToApp(UUID dropletGuid, UUID applicationGuid) { } public void updateApplicationMetadata(UUID guid, Metadata metadata) { - client.patch(CloudControllerV3Endpoints.APP_BY_GUID, Map.of(V3Fields.METADATA, OperationsUtil.toMetadataMap(metadata)), guid); + Map metadataMap = OperationsUtil.toMetadataMap(metadata); + client.patch(CloudControllerV3Endpoints.APP_BY_GUID, Map.of(V3Fields.METADATA, metadataMap == null ? Map.of() : metadataMap), + guid); } private void scaleWebProcess(UUID applicationGuid, Map scaleBody) { diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3Operations.java index e0fe99408b..b20e70a7a8 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceBindingsV3Operations.java @@ -121,8 +121,9 @@ public Map getServiceBindingParameters(UUID guid) { } public void updateServiceBindingMetadata(UUID guid, Metadata metadata) { + Map metadataMap = OperationsUtil.toMetadataMap(metadata); client.patch(CloudControllerV3Endpoints.SERVICE_CREDENTIAL_BINDING_BY_GUID, - Map.of(V3Fields.METADATA, OperationsUtil.toMetadataMap(metadata)), guid); + Map.of(V3Fields.METADATA, metadataMap == null ? Map.of() : metadataMap), guid); } private List listBindings(String uri) { diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3Operations.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3Operations.java index 33a483cd5c..3298f7b4b0 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3Operations.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/ServiceInstancesV3Operations.java @@ -366,7 +366,8 @@ private void patchServiceInstance(UUID guid, Map body) { } private void assertSpaceProvided(String operation) { - Assert.notNull(targetSpace, "Unable to " + operation + " without specifying organization and space to use."); + Assert.notNull(OperationsUtil.getGuid(targetSpace), + "Unable to " + operation + " without specifying organization and space to use."); } private record ServicePlanNames(String planName, String offeringName) { diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/V3Uri.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/V3Uri.java index 8a1f1ad14d..6970f5ebb0 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/V3Uri.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/rest/V3Uri.java @@ -31,7 +31,7 @@ public V3Uri perPage(int size) { public V3Uri query(String key, Object value) { if (value != null) { - queryParameters.add(key + Constants.EQUALS + value); + queryParameters.add(key + Constants.EQUALS_SYMBOL + value); } return this; @@ -39,7 +39,7 @@ public V3Uri query(String key, Object value) { public V3Uri queryEncoded(String key, Object value) { if (value != null) { - queryParameters.add(key + Constants.EQUALS + encodeCommas(String.valueOf(value))); + queryParameters.add(key + Constants.EQUALS_SYMBOL + encodeCommas(String.valueOf(value))); } return this; @@ -47,10 +47,10 @@ public V3Uri queryEncoded(String key, Object value) { public V3Uri query(String key, Collection values) { if (values != null && !values.isEmpty()) { - queryParameters.add(key + Constants.EQUALS + values.stream() - .map(String::valueOf) - .map(V3Uri::encodeCommas) - .collect(Collectors.joining(Constants.COMMA))); + queryParameters.add(key + Constants.EQUALS_SYMBOL + values.stream() + .map(String::valueOf) + .map(V3Uri::encodeCommas) + .collect(Collectors.joining(Constants.COMMA))); } return this;