diff --git a/spring-cloud-app-broker-autoconfigure/src/main/java/org/springframework/cloud/appbroker/autoconfigure/AppBrokerAutoConfiguration.java b/spring-cloud-app-broker-autoconfigure/src/main/java/org/springframework/cloud/appbroker/autoconfigure/AppBrokerAutoConfiguration.java index 42cef81..d634fcc 100644 --- a/spring-cloud-app-broker-autoconfigure/src/main/java/org/springframework/cloud/appbroker/autoconfigure/AppBrokerAutoConfiguration.java +++ b/spring-cloud-app-broker-autoconfigure/src/main/java/org/springframework/cloud/appbroker/autoconfigure/AppBrokerAutoConfiguration.java @@ -162,43 +162,56 @@ public class AppBrokerAutoConfiguration { } @Bean - public CreateServiceInstanceWorkflow appDeploymentCreateServiceInstanceWorkflow(BrokeredServices brokeredServices, - BackingAppDeploymentService backingAppDeploymentService, - BackingApplicationsParametersTransformationService applicationsParametersTransformationService, - BackingServicesParametersTransformationService servicesParametersTransformationService, - CredentialProviderService credentialProviderService, - TargetService targetService, - BackingServicesProvisionService backingServicesProvisionService) { + public CreateServiceInstanceWorkflow appDeploymentCreateServiceInstanceWorkflow( + BrokeredServices brokeredServices, + BackingAppDeploymentService backingAppDeploymentService, + BackingApplicationsParametersTransformationService appsParametersTransformationService, + BackingServicesParametersTransformationService servicesParametersTransformationService, + CredentialProviderService credentialProviderService, + TargetService targetService, + BackingServicesProvisionService backingServicesProvisionService) { return new AppDeploymentCreateServiceInstanceWorkflow( brokeredServices, backingAppDeploymentService, - applicationsParametersTransformationService, + backingServicesProvisionService, + appsParametersTransformationService, servicesParametersTransformationService, credentialProviderService, - targetService, - backingServicesProvisionService); + targetService); } @Bean - public DeleteServiceInstanceWorkflow appDeploymentDeleteServiceInstanceWorkflow(BrokeredServices brokeredServices, - BackingAppDeploymentService backingAppDeploymentService, - CredentialProviderService credentialProviderService, - TargetService targetService, - BackingServicesProvisionService backingServicesProvisionService) { + public UpdateServiceInstanceWorkflow appDeploymentUpdateServiceInstanceWorkflow( + BrokeredServices brokeredServices, + BackingAppDeploymentService backingAppDeploymentService, + BackingServicesProvisionService backingServicesProvisionService, + BackingApplicationsParametersTransformationService appsParametersTransformationService, + BackingServicesParametersTransformationService servicesParametersTransformationService, + TargetService targetService) { + + return new AppDeploymentUpdateServiceInstanceWorkflow( + brokeredServices, + backingAppDeploymentService, + backingServicesProvisionService, + appsParametersTransformationService, + servicesParametersTransformationService, + targetService); + } + + @Bean + public DeleteServiceInstanceWorkflow appDeploymentDeleteServiceInstanceWorkflow( + BrokeredServices brokeredServices, + BackingAppDeploymentService backingAppDeploymentService, + BackingServicesProvisionService backingServicesProvisionService, + CredentialProviderService credentialProviderService, + TargetService targetService) { + return new AppDeploymentDeleteServiceInstanceWorkflow( brokeredServices, backingAppDeploymentService, - credentialProviderService, - targetService, - backingServicesProvisionService); - } - - @Bean - public UpdateServiceInstanceWorkflow updateServiceInstanceWorkflow(BrokeredServices brokeredServices, - BackingAppDeploymentService backingAppDeploymentService, - BackingApplicationsParametersTransformationService parametersTransformationService, - TargetService targetService) { - return new AppDeploymentUpdateServiceInstanceWorkflow(brokeredServices, backingAppDeploymentService, parametersTransformationService, targetService); + backingServicesProvisionService, credentialProviderService, + targetService + ); } @Bean diff --git a/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/deployer/BackingServicesProvisionService.java b/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/deployer/BackingServicesProvisionService.java index 30d9a88..75a0f69 100644 --- a/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/deployer/BackingServicesProvisionService.java +++ b/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/deployer/BackingServicesProvisionService.java @@ -45,6 +45,21 @@ public class BackingServicesProvisionService { .sequential(); } + public Flux updateServiceInstance(List backingServices) { + return Flux.fromIterable(backingServices) + .parallel() + .runOn(Schedulers.parallel()) + // service instances can be updated with a change to the plan or to parameters + // if the service instance has no parameters, don't update it + .filter(backingService -> !backingService.getParameters().isEmpty()) + .flatMap(deployerClient::updateServiceInstance) + .doOnRequest(l -> log.info("Updating backing services {}", backingServices)) + .doOnEach(d -> log.info("Finished updating backing service {}", d)) + .doOnComplete(() -> log.info("Finished updating backing service {}", backingServices)) + .doOnError(e -> log.info("Error updating backing services {} with error {}", backingServices, e)) + .sequential(); + } + public Flux deleteServiceInstance(List backingServices) { return Flux.fromIterable(backingServices) .parallel() @@ -56,5 +71,4 @@ public class BackingServicesProvisionService { .doOnError(exception -> log.error("Error deleting backing services {} with error {}", backingServices, exception)) .sequential(); } - } diff --git a/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/deployer/DeployerClient.java b/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/deployer/DeployerClient.java index f50581b..4db91ad 100644 --- a/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/deployer/DeployerClient.java +++ b/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/deployer/DeployerClient.java @@ -55,24 +55,39 @@ public class DeployerClient { } Mono createServiceInstance(BackingService backingService) { - return appDeployer.createServiceInstance(CreateServiceInstanceRequest.builder() - .serviceInstanceName(backingService.getServiceInstanceName()) - .name(backingService.getName()) - .plan(backingService.getPlan()) - .parameters(backingService.getParameters()) - .properties(backingService.getProperties()) - .build()) - .doOnRequest(l -> log.debug("Creating backing service {}", backingService)) - .doOnSuccess(response -> log.debug("Finished creating backing service {}", backingService)) - .doOnError(exception -> log.error("Error creating backing service {} with error {}", backingService, exception)) - .map(CreateServiceInstanceResponse::getName); + return appDeployer.createServiceInstance( + CreateServiceInstanceRequest.builder() + .serviceInstanceName(backingService.getServiceInstanceName()) + .name(backingService.getName()) + .plan(backingService.getPlan()) + .parameters(backingService.getParameters()) + .properties(backingService.getProperties()) + .build()) + .doOnRequest(l -> log.debug("Creating backing service {}", backingService.getName())) + .doOnSuccess(d -> log.debug("Finished creating backing service {}", backingService.getName())) + .doOnError(e -> log.error("Error creating backing service {} with error {}", backingService.getName(), e)) + .map(CreateServiceInstanceResponse::getName); + } + + Mono updateServiceInstance(BackingService backingService) { + return appDeployer.updateServiceInstance( + UpdateServiceInstanceRequest.builder() + .serviceInstanceName(backingService.getServiceInstanceName()) + .parameters(backingService.getParameters()) + .properties(backingService.getProperties()) + .build()) + .doOnRequest(l -> log.debug("Creating backing service {}", backingService.getName())) + .doOnSuccess(d -> log.debug("Finished creating backing service {}", backingService.getName())) + .doOnError(e -> log.error("Error creating backing service {} with error {}", backingService.getName(), e)) + .map(UpdateServiceInstanceResponse::getName); } Mono deleteServiceInstance(BackingService backingService) { - return appDeployer.deleteServiceInstance(DeleteServiceInstanceRequest.builder() - .name(backingService.getServiceInstanceName()) - .properties(backingService.getProperties()) - .build()) - .map(DeleteServiceInstanceResponse::getName); + return appDeployer.deleteServiceInstance( + DeleteServiceInstanceRequest.builder() + .serviceInstanceName(backingService.getServiceInstanceName()) + .properties(backingService.getProperties()) + .build()) + .map(DeleteServiceInstanceResponse::getName); } } diff --git a/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentCreateServiceInstanceWorkflow.java b/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentCreateServiceInstanceWorkflow.java index 8d7574a..651003c 100644 --- a/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentCreateServiceInstanceWorkflow.java +++ b/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentCreateServiceInstanceWorkflow.java @@ -41,40 +41,52 @@ public class AppDeploymentCreateServiceInstanceWorkflow private final Logger log = Loggers.getLogger(AppDeploymentCreateServiceInstanceWorkflow.class); private final BackingAppDeploymentService deploymentService; + private final BackingServicesProvisionService backingServicesProvisionService; private final BackingApplicationsParametersTransformationService appsParametersTransformationService; private final BackingServicesParametersTransformationService servicesParametersTransformationService; private final CredentialProviderService credentialProviderService; private final TargetService targetService; - private final BackingServicesProvisionService backingServicesProvisionService; public AppDeploymentCreateServiceInstanceWorkflow(BrokeredServices brokeredServices, BackingAppDeploymentService deploymentService, + BackingServicesProvisionService backingServicesProvisionService, BackingApplicationsParametersTransformationService appsParametersTransformationService, BackingServicesParametersTransformationService servicesParametersTransformationService, CredentialProviderService credentialProviderService, - TargetService targetService, - BackingServicesProvisionService backingServicesProvisionService) { + TargetService targetService) { super(brokeredServices); this.deploymentService = deploymentService; + this.backingServicesProvisionService = backingServicesProvisionService; this.appsParametersTransformationService = appsParametersTransformationService; this.servicesParametersTransformationService = servicesParametersTransformationService; this.credentialProviderService = credentialProviderService; this.targetService = targetService; - this.backingServicesProvisionService = backingServicesProvisionService; } @Override public Flux create(CreateServiceInstanceRequest request) { return getBackingServicesForService(request.getServiceDefinition(), request.getPlanId()) - .flatMap(backingService -> targetService.addToBackingServices(backingService, getTargetForService(request.getServiceDefinition(), request.getPlanId()) , request.getServiceInstanceId())) - .flatMap(backingServices -> servicesParametersTransformationService.transformParameters(backingServices, request.getParameters())) + .flatMap(backingService -> + targetService.addToBackingServices(backingService, + getTargetForService(request.getServiceDefinition(), request.getPlanId()) , + request.getServiceInstanceId())) + .flatMap(backingServices -> + servicesParametersTransformationService.transformParameters(backingServices, + request.getParameters())) .flatMapMany(backingServicesProvisionService::createServiceInstance) .thenMany( getBackingApplicationsForService(request.getServiceDefinition(), request.getPlanId()) - .flatMap(backingApps -> targetService.addToBackingApplications(backingApps, getTargetForService(request.getServiceDefinition(), request.getPlanId()) , request.getServiceInstanceId())) - .flatMap(backingApps -> appsParametersTransformationService.transformParameters(backingApps, request.getParameters())) - .flatMap(backingApplications -> credentialProviderService.addCredentials(backingApplications, request.getServiceInstanceId())) + .flatMap(backingApps -> + targetService.addToBackingApplications(backingApps, + getTargetForService(request.getServiceDefinition(), + request.getPlanId()) , request.getServiceInstanceId())) + .flatMap(backingApps -> + appsParametersTransformationService.transformParameters(backingApps, + request.getParameters())) + .flatMap(backingApplications -> + credentialProviderService.addCredentials(backingApplications, + request.getServiceInstanceId())) .flatMapMany(deploymentService::deploy) .doOnRequest(l -> log.debug("Deploying applications {}", brokeredServices)) .doOnEach(response -> log.debug("Finished deploying {}", response)) diff --git a/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentDeleteServiceInstanceWorkflow.java b/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentDeleteServiceInstanceWorkflow.java index 4cd6e72..5a2e941 100644 --- a/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentDeleteServiceInstanceWorkflow.java +++ b/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentDeleteServiceInstanceWorkflow.java @@ -45,9 +45,9 @@ public class AppDeploymentDeleteServiceInstanceWorkflow public AppDeploymentDeleteServiceInstanceWorkflow(BrokeredServices brokeredServices, BackingAppDeploymentService deploymentService, + BackingServicesProvisionService backingServicesProvisionService, CredentialProviderService credentialProviderService, - TargetService targetService, - BackingServicesProvisionService backingServicesProvisionService) { + TargetService targetService) { super(brokeredServices); this.deploymentService = deploymentService; this.credentialProviderService = credentialProviderService; diff --git a/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentUpdateServiceInstanceWorkflow.java b/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentUpdateServiceInstanceWorkflow.java index fb76b4d..50a8ed8 100644 --- a/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentUpdateServiceInstanceWorkflow.java +++ b/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentUpdateServiceInstanceWorkflow.java @@ -22,8 +22,10 @@ import reactor.util.Logger; import reactor.util.Loggers; import org.springframework.cloud.appbroker.deployer.BackingAppDeploymentService; +import org.springframework.cloud.appbroker.deployer.BackingServicesProvisionService; import org.springframework.cloud.appbroker.deployer.BrokeredServices; import org.springframework.cloud.appbroker.extensions.parameters.BackingApplicationsParametersTransformationService; +import org.springframework.cloud.appbroker.extensions.parameters.BackingServicesParametersTransformationService; import org.springframework.cloud.appbroker.extensions.targets.TargetService; import org.springframework.cloud.appbroker.service.UpdateServiceInstanceWorkflow; import org.springframework.cloud.servicebroker.model.instance.UpdateServiceInstanceRequest; @@ -38,30 +40,50 @@ public class AppDeploymentUpdateServiceInstanceWorkflow private final Logger log = Loggers.getLogger(AppDeploymentUpdateServiceInstanceWorkflow.class); private final BackingAppDeploymentService deploymentService; - private final BackingApplicationsParametersTransformationService parametersTransformationService; + private final BackingServicesProvisionService backingServicesProvisionService; + private final BackingApplicationsParametersTransformationService appsParametersTransformationService; + private final BackingServicesParametersTransformationService servicesParametersTransformationService; private final TargetService targetService; public AppDeploymentUpdateServiceInstanceWorkflow(BrokeredServices brokeredServices, BackingAppDeploymentService deploymentService, - BackingApplicationsParametersTransformationService parametersTransformationService, + BackingServicesProvisionService backingServicesProvisionService, + BackingApplicationsParametersTransformationService appsParametersTransformationService, + BackingServicesParametersTransformationService servicesParametersTransformationService, TargetService targetService) { super(brokeredServices); this.deploymentService = deploymentService; - this.parametersTransformationService = parametersTransformationService; + this.backingServicesProvisionService = backingServicesProvisionService; + this.appsParametersTransformationService = appsParametersTransformationService; + this.servicesParametersTransformationService = servicesParametersTransformationService; this.targetService = targetService; } public Flux update(UpdateServiceInstanceRequest request) { - return getBackingApplicationsForService(request.getServiceDefinition(), request.getPlanId()) - .flatMap(backingApps -> targetService.addToBackingApplications(backingApps, getTargetForService(request.getServiceDefinition(), request.getPlanId()), request.getServiceInstanceId())) - .flatMap(backingApps -> - parametersTransformationService.transformParameters(backingApps, request.getParameters())) - .flatMapMany(deploymentService::deploy) - .doOnRequest(l -> log.debug("Deploying applications {}", brokeredServices)) - .doOnEach(response -> log.debug("Finished deploying {}", response)) - .doOnComplete(() -> log.debug("Finished deploying applications {}", brokeredServices)) - .doOnError(exception -> log.error("Error deploying applications {} with error {}", brokeredServices, exception)) - .flatMap(apps -> Flux.empty()); + return + getBackingServicesForService(request.getServiceDefinition(), request.getPlanId()) + .flatMap(backingService -> + targetService.addToBackingServices(backingService, + getTargetForService(request.getServiceDefinition(), request.getPlanId()), + request.getServiceInstanceId())) + .flatMap(backingServices -> + servicesParametersTransformationService.transformParameters(backingServices, + request.getParameters())) + .flatMapMany(backingServicesProvisionService::updateServiceInstance) + .thenMany( + getBackingApplicationsForService(request.getServiceDefinition(), request.getPlanId()) + .flatMap(backingApps -> + targetService.addToBackingApplications(backingApps, + getTargetForService(request.getServiceDefinition(), request.getPlanId()), + request.getServiceInstanceId())) + .flatMap(backingApps -> + appsParametersTransformationService.transformParameters(backingApps, request.getParameters())) + .flatMapMany(deploymentService::deploy) + .doOnRequest(l -> log.info("Deploying applications {}", brokeredServices)) + .doOnEach(s -> log.info("Finished deploying {}", s)) + .doOnComplete(() -> log.info("Finished deploying applications {}", brokeredServices)) + .doOnError(e -> log.info("Error deploying applications {} with error {}", brokeredServices, e)) + .flatMap(apps -> Flux.empty())); } @Override diff --git a/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/deployer/BackingServicesProvisionServiceTest.java b/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/deployer/BackingServicesProvisionServiceTest.java index 2b1c97a..7f4c54b 100644 --- a/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/deployer/BackingServicesProvisionServiceTest.java +++ b/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/deployer/BackingServicesProvisionServiceTest.java @@ -12,7 +12,9 @@ import org.mockito.junit.jupiter.MockitoExtension; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verifyNoMoreInteractions; @ExtendWith(MockitoExtension.class) class BackingServicesProvisionServiceTest { @@ -37,14 +39,13 @@ class BackingServicesProvisionServiceTest { .serviceInstanceName("si2") .name("service2") .plan("free") - .parameters(Collections.singletonMap("key2", "value2")) .build()) .build(); } @Test @SuppressWarnings("UnassignedFluxMonoInstance") - void shouldCreateServiceInstance() { + void createServiceInstance() { doReturn(Mono.just("si1")) .when(deployerClient).createServiceInstance(backingServices.get(0)); doReturn(Mono.just("si2")) @@ -64,7 +65,20 @@ class BackingServicesProvisionServiceTest { @Test @SuppressWarnings("UnassignedFluxMonoInstance") - void shouldDeleteServiceInstance() { + void updateServiceInstance() { + doReturn(Mono.just("si1")) + .when(deployerClient).updateServiceInstance(backingServices.get(0)); + + StepVerifier.create(backingServicesProvisionService.updateServiceInstance(backingServices)) + .assertNext(value -> assertThat(value).isEqualTo("si1")) + .verifyComplete(); + + verifyNoMoreInteractions(deployerClient); + } + + @Test + @SuppressWarnings("UnassignedFluxMonoInstance") + void deleteServiceInstance() { doReturn(Mono.just("deleted1")) .when(deployerClient).deleteServiceInstance(backingServices.get(0)); doReturn(Mono.just("deleted2")) diff --git a/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/deployer/DeployerClientTest.java b/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/deployer/DeployerClientTest.java index 36a1834..90b0141 100644 --- a/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/deployer/DeployerClientTest.java +++ b/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/deployer/DeployerClientTest.java @@ -16,7 +16,6 @@ package org.springframework.cloud.appbroker.deployer; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -37,6 +36,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) +@SuppressWarnings("UnassignedFluxMonoInstance") class DeployerClientTest { private static final String APP_NAME = "helloworld"; @@ -122,7 +122,7 @@ class DeployerClientTest { .verifyComplete(); verify(appDeployer).deploy(argThat(matchesRequest(APP_NAME, APP_PATH, Collections.emptyMap(), - Collections.emptyMap(), Arrays.asList("my-db-service")))); + Collections.emptyMap(), Collections.singletonList("my-db-service")))); } @Test diff --git a/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentCreateServiceInstanceWorkflowTest.java b/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentCreateServiceInstanceWorkflowTest.java index 3c91d2e..74011a4 100644 --- a/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentCreateServiceInstanceWorkflowTest.java +++ b/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentCreateServiceInstanceWorkflowTest.java @@ -56,13 +56,16 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; class AppDeploymentCreateServiceInstanceWorkflowTest { @Mock - private BackingAppDeploymentService backingAppDeploymentService; + private BackingAppDeploymentService appDeploymentService; @Mock - private BackingApplicationsParametersTransformationService backingApplicationsParametersTransformationService; + private BackingServicesProvisionService servicesProvisionService; @Mock - private BackingServicesParametersTransformationService backingServicesParametersTransformationService; + private BackingApplicationsParametersTransformationService appsParametersTransformationService; + + @Mock + private BackingServicesParametersTransformationService servicesParametersTransformationService; @Mock private CredentialProviderService credentialProviderService; @@ -70,9 +73,6 @@ class AppDeploymentCreateServiceInstanceWorkflowTest { @Mock private TargetService targetService; - @Mock - private BackingServicesProvisionService backingServicesProvisionService; - private BackingApplications backingApps; private BackingServices backingServices; private TargetSpec targetSpec; @@ -105,7 +105,10 @@ class AppDeploymentCreateServiceInstanceWorkflowTest { .build()) .build(); - targetSpec = TargetSpec.builder().name("TargetSpace").build(); + targetSpec = TargetSpec.builder() + .name("TargetSpace") + .build(); + BrokeredServices brokeredServices = BrokeredServices .builder() .service(BrokeredService @@ -120,16 +123,16 @@ class AppDeploymentCreateServiceInstanceWorkflowTest { createServiceInstanceWorkflow = new AppDeploymentCreateServiceInstanceWorkflow( brokeredServices, - backingAppDeploymentService, - backingApplicationsParametersTransformationService, - backingServicesParametersTransformationService, + appDeploymentService, + servicesProvisionService, + appsParametersTransformationService, + servicesParametersTransformationService, credentialProviderService, - targetService, - backingServicesProvisionService); + targetService); } @Test - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "UnassignedFluxMonoInstance"}) void createServiceInstanceSucceeds() { CreateServiceInstanceRequest request = buildRequest("service1", "plan1"); @@ -141,43 +144,8 @@ class AppDeploymentCreateServiceInstanceWorkflowTest { .expectNext() .verifyComplete(); - verify(backingAppDeploymentService).deploy(backingApps); - verify(backingServicesProvisionService).createServiceInstance(backingServices); - - verifyNoMoreInteractionsWithServices(); - } - - @Test - void createServiceInstanceWithParametersSucceeds() { - CreateServiceInstanceRequest request = buildRequest("service1", "plan1", - singletonMap("ENV_VAR_1", "value from parameters")); - - setupMocks(request); - - StepVerifier - .create(createServiceInstanceWorkflow.create(request)) - .expectNext() - .expectNext() - .verifyComplete(); - - verify(backingApplicationsParametersTransformationService) - .transformParameters(backingApps, singletonMap("ENV_VAR_1", "value from parameters")); - - verifyNoMoreInteractionsWithServices(); - } - - @Test - void createServiceInstanceWithTargetSucceeds() { - CreateServiceInstanceRequest request = buildRequest("service1", "plan1", - singletonMap("ENV_VAR_1", "value from parameters")); - - setupMocks(request); - - StepVerifier - .create(createServiceInstanceWorkflow.create(request)) - .expectNext() - .expectNext() - .verifyComplete(); + verify(appDeploymentService).deploy(backingApps); + verify(servicesProvisionService).createServiceInstance(backingServices); final String expectedServiceId = "service-instance-id"; verify(targetService).addToBackingServices(backingServices, targetSpec, expectedServiceId); @@ -186,6 +154,34 @@ class AppDeploymentCreateServiceInstanceWorkflowTest { verifyNoMoreInteractionsWithServices(); } + @Test + @SuppressWarnings("UnassignedFluxMonoInstance") + void createServiceInstanceWithParametersSucceeds() { + Map parameters = singletonMap("ENV_VAR_1", "value from parameters"); + + CreateServiceInstanceRequest request = buildRequest("service1", "plan1", + parameters); + + setupMocks(request); + + StepVerifier + .create(createServiceInstanceWorkflow.create(request)) + .expectNext() + .expectNext() + .verifyComplete(); + + verify(appDeploymentService).deploy(backingApps); + verify(servicesProvisionService).createServiceInstance(backingServices); + + verify(appsParametersTransformationService) + .transformParameters(backingApps, parameters); + + verify(servicesParametersTransformationService) + .transformParameters(backingServices, parameters); + + verifyNoMoreInteractionsWithServices(); + } + @Test void createServiceInstanceWithNoAppsDoesNothing() { CreateServiceInstanceRequest request = buildRequest("unsupported-service", "plan1"); @@ -198,29 +194,32 @@ class AppDeploymentCreateServiceInstanceWorkflowTest { } private void setupMocks(CreateServiceInstanceRequest request) { - given(this.backingAppDeploymentService.deploy(eq(backingApps))) + given(this.appDeploymentService.deploy(eq(backingApps))) .willReturn(Flux.just("app1", "app2")); - given(this.backingApplicationsParametersTransformationService.transformParameters(eq(backingApps), eq(request.getParameters()))) + given(this.servicesProvisionService.createServiceInstance(eq(backingServices))) + .willReturn(Flux.just("my-service-instance")); + + given(this.appsParametersTransformationService.transformParameters(eq(backingApps), eq(request.getParameters()))) .willReturn(Mono.just(backingApps)); - given(this.backingServicesParametersTransformationService.transformParameters(eq(backingServices), eq(request.getParameters()))) + given(this.servicesParametersTransformationService.transformParameters(eq(backingServices), eq(request.getParameters()))) .willReturn(Mono.just(backingServices)); + given(this.credentialProviderService.addCredentials(eq(backingApps), eq(request.getServiceInstanceId()))) .willReturn(Mono.just(backingApps)); + given(this.targetService.addToBackingApplications(eq(backingApps), eq(targetSpec), eq(request.getServiceInstanceId()))) .willReturn(Mono.just(backingApps)); given(this.targetService.addToBackingServices(eq(backingServices), eq(targetSpec), eq(request.getServiceInstanceId()))) .willReturn(Mono.just(backingServices)); - given(this.backingServicesProvisionService.createServiceInstance(eq(backingServices))) - .willReturn(Flux.just("my-service-instance")); } private void verifyNoMoreInteractionsWithServices() { - verifyNoMoreInteractions(this.backingAppDeploymentService); - verifyNoMoreInteractions(this.backingApplicationsParametersTransformationService); - verifyNoMoreInteractions(this.backingServicesParametersTransformationService); + verifyNoMoreInteractions(this.appDeploymentService); + verifyNoMoreInteractions(this.servicesProvisionService); + verifyNoMoreInteractions(this.appsParametersTransformationService); + verifyNoMoreInteractions(this.servicesParametersTransformationService); verifyNoMoreInteractions(this.credentialProviderService); verifyNoMoreInteractions(this.targetService); - verifyNoMoreInteractions(this.backingServicesProvisionService); } private CreateServiceInstanceRequest buildRequest(String serviceName, String planName) { diff --git a/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentDeleteServiceInstanceWorkflowTest.java b/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentDeleteServiceInstanceWorkflowTest.java index fd0ef3d..a268760 100644 --- a/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentDeleteServiceInstanceWorkflowTest.java +++ b/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentDeleteServiceInstanceWorkflowTest.java @@ -109,9 +109,10 @@ class AppDeploymentDeleteServiceInstanceWorkflowTest { new AppDeploymentDeleteServiceInstanceWorkflow( brokeredServices, backingAppDeploymentService, + backingServicesProvisionService, credentialProviderService, - targetService, - backingServicesProvisionService); + targetService + ); } @Test diff --git a/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentUpdateServiceInstanceWorkflowTest.java b/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentUpdateServiceInstanceWorkflowTest.java index 1c99dae..8b8b50f 100644 --- a/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentUpdateServiceInstanceWorkflowTest.java +++ b/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentUpdateServiceInstanceWorkflowTest.java @@ -24,6 +24,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.cloud.appbroker.deployer.BackingService; +import org.springframework.cloud.appbroker.deployer.BackingServices; +import org.springframework.cloud.appbroker.deployer.BackingServicesProvisionService; +import org.springframework.cloud.appbroker.extensions.parameters.BackingServicesParametersTransformationService; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; @@ -43,22 +47,31 @@ import org.springframework.cloud.servicebroker.model.instance.UpdateServiceInsta import static java.util.Collections.singletonMap; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @ExtendWith(MockitoExtension.class) class AppDeploymentUpdateServiceInstanceWorkflowTest { @Mock - private BackingAppDeploymentService backingAppDeploymentService; + private BackingAppDeploymentService appDeploymentService; @Mock - private BackingApplicationsParametersTransformationService backingApplicationsParametersTransformationService; + private BackingServicesProvisionService servicesProvisionService; + + @Mock + private BackingApplicationsParametersTransformationService appsParametersTransformationService; + + @Mock + private BackingServicesParametersTransformationService servicesParametersTransformationService; @Mock private TargetService targetService; private BackingApplications backingApps; + private BackingServices backingServices; private TargetSpec targetSpec; + private AppDeploymentUpdateServiceInstanceWorkflow updateServiceInstanceWorkflow; @BeforeEach @@ -75,34 +88,45 @@ class AppDeploymentUpdateServiceInstanceWorkflowTest { .build()) .build(); - targetSpec = TargetSpec.builder().name("TargetSpace").build(); - BrokeredServices brokeredServices = BrokeredServices + backingServices = BackingServices .builder() - .service(BrokeredService.builder() - .serviceName("service1") - .planName("plan1") - .apps(backingApps) - .target(targetSpec) - .build()) + .backingService(BackingService + .builder() + .name("my-service") + .plan("a-plan") + .serviceInstanceName("my-service-instance") + .build()) .build(); - updateServiceInstanceWorkflow = new AppDeploymentUpdateServiceInstanceWorkflow(brokeredServices, - backingAppDeploymentService, - backingApplicationsParametersTransformationService, + targetSpec = TargetSpec.builder() + .name("TargetSpace") + .build(); + + BrokeredServices brokeredServices = BrokeredServices.builder() + .service(BrokeredService.builder() + .serviceName("service1") + .planName("plan1") + .apps(backingApps) + .services(backingServices) + .target(targetSpec) + .build()) + .build(); + + updateServiceInstanceWorkflow = new AppDeploymentUpdateServiceInstanceWorkflow( + brokeredServices, + appDeploymentService, + servicesProvisionService, + appsParametersTransformationService, + servicesParametersTransformationService, targetService); } @Test - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "UnassignedFluxMonoInstance"}) void updateServiceInstanceSucceeds() { UpdateServiceInstanceRequest request = buildRequest("service1", "plan1"); - given(this.backingAppDeploymentService.deploy(eq(backingApps))) - .willReturn(Flux.just("app1", "app2")); - given(this.targetService.addToBackingApplications(eq(backingApps), eq(targetSpec), eq("service-instance-id"))) - .willReturn(Mono.just(backingApps)); - given(this.backingApplicationsParametersTransformationService.transformParameters(eq(backingApps), eq(request.getParameters()))) - .willReturn(Mono.just(backingApps)); + setupMocks(request); StepVerifier .create(updateServiceInstanceWorkflow.update(request)) @@ -110,9 +134,14 @@ class AppDeploymentUpdateServiceInstanceWorkflowTest { .expectNext() .verifyComplete(); - verifyNoMoreInteractions(this.backingAppDeploymentService); - verifyNoMoreInteractions(this.backingApplicationsParametersTransformationService); - verifyNoMoreInteractions(this.targetService); + verify(appDeploymentService).deploy(backingApps); + verify(servicesProvisionService).updateServiceInstance(backingServices); + + final String expectedServiceId = "service-instance-id"; + verify(targetService).addToBackingServices(backingServices, targetSpec, expectedServiceId); + verify(targetService).addToBackingApplications(backingApps, targetSpec, expectedServiceId); + + verifyNoMoreInteractionsWithServices(); } @Test @@ -120,12 +149,7 @@ class AppDeploymentUpdateServiceInstanceWorkflowTest { UpdateServiceInstanceRequest request = buildRequest("service1", "plan1", singletonMap("ENV_VAR_1", "value from parameters")); - given(this.backingAppDeploymentService.deploy(eq(backingApps))) - .willReturn(Flux.just("app1", "app2")); - given(this.backingApplicationsParametersTransformationService.transformParameters(eq(backingApps), eq(request.getParameters()))) - .willReturn(Mono.just(backingApps)); - given(this.targetService.addToBackingApplications(eq(backingApps), eq(targetSpec), eq("service-instance-id"))) - .willReturn(Mono.just(backingApps)); + setupMocks(request); StepVerifier .create(updateServiceInstanceWorkflow.update(request)) @@ -133,9 +157,7 @@ class AppDeploymentUpdateServiceInstanceWorkflowTest { .expectNext() .verifyComplete(); - verifyNoMoreInteractions(this.backingAppDeploymentService); - verifyNoMoreInteractions(this.backingApplicationsParametersTransformationService); - verifyNoMoreInteractions(this.targetService); + verifyNoMoreInteractionsWithServices(); } @Test @@ -144,8 +166,31 @@ class AppDeploymentUpdateServiceInstanceWorkflowTest { .create(updateServiceInstanceWorkflow.update(buildRequest("unsupported-service", "plan1"))) .verifyComplete(); - verifyNoMoreInteractions(this.backingAppDeploymentService); - verifyNoMoreInteractions(this.backingApplicationsParametersTransformationService); + verifyNoMoreInteractionsWithServices(); + } + + private void setupMocks(UpdateServiceInstanceRequest request) { + given(this.appDeploymentService.deploy(eq(backingApps))) + .willReturn(Flux.just("app1", "app2")); + given(this.servicesProvisionService.updateServiceInstance(eq(backingServices))) + .willReturn(Flux.just("my-service-instance")); + + given(this.appsParametersTransformationService.transformParameters(eq(backingApps), eq(request.getParameters()))) + .willReturn(Mono.just(backingApps)); + given(this.servicesParametersTransformationService.transformParameters(eq(backingServices), eq(request.getParameters()))) + .willReturn(Mono.just(backingServices)); + + given(this.targetService.addToBackingApplications(eq(backingApps), eq(targetSpec), eq("service-instance-id"))) + .willReturn(Mono.just(backingApps)); + given(this.targetService.addToBackingServices(eq(backingServices), eq(targetSpec), eq(request.getServiceInstanceId()))) + .willReturn(Mono.just(backingServices)); + } + + private void verifyNoMoreInteractionsWithServices() { + verifyNoMoreInteractions(this.appDeploymentService); + verifyNoMoreInteractions(this.servicesProvisionService); + verifyNoMoreInteractions(this.appsParametersTransformationService); + verifyNoMoreInteractions(this.servicesParametersTransformationService); verifyNoMoreInteractions(this.targetService); } diff --git a/spring-cloud-app-broker-deployer-cloudfoundry/src/main/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/CloudFoundryAppDeployer.java b/spring-cloud-app-broker-deployer-cloudfoundry/src/main/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/CloudFoundryAppDeployer.java index 6752404..7e48858 100644 --- a/spring-cloud-app-broker-deployer-cloudfoundry/src/main/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/CloudFoundryAppDeployer.java +++ b/spring-cloud-app-broker-deployer-cloudfoundry/src/main/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/CloudFoundryAppDeployer.java @@ -68,6 +68,8 @@ import org.springframework.cloud.appbroker.deployer.DeployApplicationResponse; import org.springframework.cloud.appbroker.deployer.DeploymentProperties; import org.springframework.cloud.appbroker.deployer.UndeployApplicationRequest; import org.springframework.cloud.appbroker.deployer.UndeployApplicationResponse; +import org.springframework.cloud.appbroker.deployer.UpdateServiceInstanceRequest; +import org.springframework.cloud.appbroker.deployer.UpdateServiceInstanceResponse; import org.springframework.cloud.appbroker.deployer.util.ByteSizeUtils; import org.springframework.context.ResourceLoaderAware; import org.springframework.core.io.Resource; @@ -218,7 +220,7 @@ public class CloudFoundryAppDeployer implements AppDeployer, ResourceLoaderAware .name(spaceName) .build()) .doOnSuccess(response -> logger.info("Created space {}", spaceName)) - .doOnError(e -> logger.warn(String.format("Error creating space %s. Exception Message %s", spaceName, e.getMessage()))) + .doOnError(e -> logger.warn(String.format("Error creating space %s: %s", spaceName, e.getMessage()))) .onErrorResume(e -> Mono.empty()) .then(Mono.empty())); return getSpaceIdFromName(spaceName) @@ -517,7 +519,9 @@ public class CloudFoundryAppDeployer implements AppDeployer, ResourceLoaderAware .build(); Mono createServiceInstanceResponseMono = - Mono.just(CreateServiceInstanceResponse.builder().name(request.getServiceInstanceName()).build()); + Mono.just(CreateServiceInstanceResponse.builder() + .name(request.getServiceInstanceName()) + .build()); if (request.getProperties().containsKey(DeploymentProperties.TARGET_PROPERTY_KEY)) { return createSpace(request.getProperties().get(DeploymentProperties.TARGET_PROPERTY_KEY)) @@ -526,34 +530,49 @@ public class CloudFoundryAppDeployer implements AppDeployer, ResourceLoaderAware .services() .createInstance(createServiceInstanceRequest) .then(createServiceInstanceResponseMono)); + } else { + return operations + .services() + .createInstance(createServiceInstanceRequest) + .then(createServiceInstanceResponseMono); } - return operations + } + + @Override + public Mono updateServiceInstance(UpdateServiceInstanceRequest request) { + org.cloudfoundry.operations.services.UpdateServiceInstanceRequest updateServiceInstanceRequest = + org.cloudfoundry.operations.services.UpdateServiceInstanceRequest + .builder() + .serviceInstanceName(request.getServiceInstanceName()) + .parameters(request.getParameters()) + .build(); + + Mono updateServiceInstanceResponseMono = + Mono.just(UpdateServiceInstanceResponse.builder() + .name(request.getServiceInstanceName()) + .build()); + + return getOperations(request.getProperties()) .services() - .createInstance(createServiceInstanceRequest) - .then(createServiceInstanceResponseMono); + .updateInstance(updateServiceInstanceRequest) + .then(updateServiceInstanceResponseMono); } @Override public Mono deleteServiceInstance(DeleteServiceInstanceRequest request) { - final String serviceInstanceName = request.getName(); + final String serviceInstanceName = request.getServiceInstanceName(); - final CloudFoundryOperations operations; - if (request.getProperties().containsKey(DeploymentProperties.TARGET_PROPERTY_KEY)) { - operations = createCloudFoundryOperationsForSpace(request.getProperties().get(DeploymentProperties.TARGET_PROPERTY_KEY)); - } - else { - operations = this.operations; - } - - return operations + return getOperations(request.getProperties()) .services() - .getInstance(GetServiceInstanceRequest.builder().name(serviceInstanceName).build()) + .getInstance(GetServiceInstanceRequest.builder() + .name(serviceInstanceName) + .build()) .map(ServiceInstance::getApplications) .flatMap((Function, Mono>) applications -> Flux.fromIterable(applications) .flatMap( application -> - operations + getOperations(request.getProperties()) .services() .unbind( UnbindServiceInstanceRequest @@ -563,7 +582,7 @@ public class CloudFoundryAppDeployer implements AppDeployer, ResourceLoaderAware .build()) ).collectList()) .then( - operations + getOperations(request.getProperties()) .services() .deleteInstance( org.cloudfoundry.operations.services.DeleteServiceInstanceRequest @@ -573,4 +592,11 @@ public class CloudFoundryAppDeployer implements AppDeployer, ResourceLoaderAware .then(Mono.just(DeleteServiceInstanceResponse.builder().name(serviceInstanceName).build()))); } + private CloudFoundryOperations getOperations(Map properties) { + if (properties.containsKey(DeploymentProperties.TARGET_PROPERTY_KEY)) { + return createCloudFoundryOperationsForSpace(properties.get(DeploymentProperties.TARGET_PROPERTY_KEY)); + } else { + return this.operations; + } + } } \ No newline at end of file diff --git a/spring-cloud-app-broker-deployer-cloudfoundry/src/test/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/CloudFoundryAppDeployerTest.java b/spring-cloud-app-broker-deployer-cloudfoundry/src/test/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/CloudFoundryAppDeployerTest.java index f2649ce..b6dad65 100644 --- a/spring-cloud-app-broker-deployer-cloudfoundry/src/test/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/CloudFoundryAppDeployerTest.java +++ b/spring-cloud-app-broker-deployer-cloudfoundry/src/test/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/CloudFoundryAppDeployerTest.java @@ -47,6 +47,7 @@ import org.springframework.cloud.appbroker.deployer.CreateServiceInstanceRequest import org.springframework.cloud.appbroker.deployer.DeleteServiceInstanceRequest; import org.springframework.cloud.appbroker.deployer.DeployApplicationRequest; import org.springframework.cloud.appbroker.deployer.DeploymentProperties; +import org.springframework.cloud.appbroker.deployer.UpdateServiceInstanceRequest; import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.ResourceLoader; @@ -324,7 +325,7 @@ class CloudFoundryAppDeployerTest { DeleteServiceInstanceRequest request = DeleteServiceInstanceRequest.builder() - .name("service-instance-name") + .serviceInstanceName("service-instance-name") .properties(emptyMap()) .build(); @@ -360,6 +361,27 @@ class CloudFoundryAppDeployerTest { .verifyComplete(); } + @Test + void updateServiceInstance() { + when(services.updateInstance( + org.cloudfoundry.operations.services.UpdateServiceInstanceRequest.builder() + .serviceInstanceName("service-instance-name") + .parameters(emptyMap()) + .build())) + .thenReturn(Mono.empty()); + + UpdateServiceInstanceRequest request = + UpdateServiceInstanceRequest.builder() + .serviceInstanceName("service-instance-name") + .parameters(emptyMap()) + .build(); + + StepVerifier.create( + appDeployer.updateServiceInstance(request)) + .assertNext(response -> assertThat(response.getName()).isEqualTo("service-instance-name")) + .verifyComplete(); + } + private ApplicationManifest.Builder baseManifest() { return ApplicationManifest.builder() .environmentVariable("SPRING_APPLICATION_INDEX", "${vcap.application.instance_index}") diff --git a/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/AppDeployer.java b/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/AppDeployer.java index 5c7d9da..1fb4adf 100644 --- a/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/AppDeployer.java +++ b/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/AppDeployer.java @@ -32,8 +32,11 @@ public interface AppDeployer { return Mono.empty(); } - default Mono deleteServiceInstance(DeleteServiceInstanceRequest request) { + default Mono updateServiceInstance(UpdateServiceInstanceRequest request) { return Mono.empty(); } + default Mono deleteServiceInstance(DeleteServiceInstanceRequest request) { + return Mono.empty(); + } } \ No newline at end of file diff --git a/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/DeleteServiceInstanceRequest.java b/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/DeleteServiceInstanceRequest.java index 8b34f8b..4e15848 100644 --- a/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/DeleteServiceInstanceRequest.java +++ b/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/DeleteServiceInstanceRequest.java @@ -20,11 +20,11 @@ import java.util.Map; public class DeleteServiceInstanceRequest { - private final String name; + private final String serviceInstanceName; private final Map properties; - DeleteServiceInstanceRequest(String name, Map properties) { - this.name = name; + DeleteServiceInstanceRequest(String serviceInstanceName, Map properties) { + this.serviceInstanceName = serviceInstanceName; this.properties = properties; } @@ -32,8 +32,8 @@ public class DeleteServiceInstanceRequest { return new DeleteServiceInstanceRequestBuilder(); } - public String getName() { - return name; + public String getServiceInstanceName() { + return serviceInstanceName; } public Map getProperties() { @@ -42,14 +42,14 @@ public class DeleteServiceInstanceRequest { public static class DeleteServiceInstanceRequestBuilder { - private String name; + private String serviceInstanceName; private Map properties; DeleteServiceInstanceRequestBuilder() { } - public DeleteServiceInstanceRequestBuilder name(String name) { - this.name = name; + public DeleteServiceInstanceRequestBuilder serviceInstanceName(String name) { + this.serviceInstanceName = name; return this; } @@ -59,7 +59,7 @@ public class DeleteServiceInstanceRequest { } public DeleteServiceInstanceRequest build() { - return new DeleteServiceInstanceRequest(name, properties); + return new DeleteServiceInstanceRequest(serviceInstanceName, properties); } } diff --git a/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/UpdateServiceInstanceRequest.java b/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/UpdateServiceInstanceRequest.java new file mode 100644 index 0000000..93b5f24 --- /dev/null +++ b/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/UpdateServiceInstanceRequest.java @@ -0,0 +1,92 @@ +/* + * Copyright 2016-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.appbroker.deployer; + +import java.util.HashMap; +import java.util.Map; + +public class UpdateServiceInstanceRequest { + + private final String serviceInstanceName; + private final Map parameters; + private final Map properties; + + UpdateServiceInstanceRequest(String serviceInstanceName, + Map parameters, + Map properties) { + this.serviceInstanceName = serviceInstanceName; + this.parameters = parameters; + this.properties = properties; + } + + public static UpdateServiceInstanceRequestBuilder builder() { + return new UpdateServiceInstanceRequestBuilder(); + } + + public String getServiceInstanceName() { + return serviceInstanceName; + } + + public Map getParameters() { + return parameters; + } + + public Map getProperties() { + return properties; + } + + public static class UpdateServiceInstanceRequestBuilder { + + private String serviceInstanceName; + private final Map parameters = new HashMap<>(); + private final Map properties = new HashMap<>(); + + UpdateServiceInstanceRequestBuilder() { + } + + public UpdateServiceInstanceRequestBuilder serviceInstanceName(String serviceInstanceName) { + this.serviceInstanceName = serviceInstanceName; + return this; + } + + public UpdateServiceInstanceRequestBuilder parameters(String key, String value) { + this.parameters.put(key, value); + return this; + } + + public UpdateServiceInstanceRequestBuilder parameters(Map parameters) { + if (parameters == null) { + return this; + } + this.parameters.putAll(parameters); + return this; + } + + public UpdateServiceInstanceRequestBuilder properties(Map properties) { + if (properties == null) { + return this; + } + this.properties.putAll(properties); + return this; + } + + public UpdateServiceInstanceRequest build() { + return new UpdateServiceInstanceRequest(serviceInstanceName, parameters, properties); + } + } + +} diff --git a/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/UpdateServiceInstanceResponse.java b/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/UpdateServiceInstanceResponse.java new file mode 100644 index 0000000..84ed5bb --- /dev/null +++ b/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/UpdateServiceInstanceResponse.java @@ -0,0 +1,52 @@ +/* + * Copyright 2016-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.appbroker.deployer; + +public class UpdateServiceInstanceResponse { + + private final String name; + + UpdateServiceInstanceResponse(String name) { + this.name = name; + } + + public static UpdateServiceInstanceResponseBuilder builder() { + return new UpdateServiceInstanceResponseBuilder(); + } + + public String getName() { + return name; + } + + public static class UpdateServiceInstanceResponseBuilder { + + private String name; + + UpdateServiceInstanceResponseBuilder() { + } + + public UpdateServiceInstanceResponseBuilder name(String name) { + this.name = name; + return this; + } + + public UpdateServiceInstanceResponse build() { + return new UpdateServiceInstanceResponse(name); + } + + } +} diff --git a/spring-cloud-app-broker-sample/src/test/java/org.springframework.cloud.appbroker/sample/UpdateInstanceComponentTest.java b/spring-cloud-app-broker-sample/src/test/java/org.springframework.cloud.appbroker/sample/UpdateInstanceComponentTest.java index ceaac2f..cf9c522 100644 --- a/spring-cloud-app-broker-sample/src/test/java/org.springframework.cloud.appbroker/sample/UpdateInstanceComponentTest.java +++ b/spring-cloud-app-broker-sample/src/test/java/org.springframework.cloud.appbroker/sample/UpdateInstanceComponentTest.java @@ -41,8 +41,8 @@ import static org.springframework.cloud.appbroker.sample.UpdateInstanceComponent }) class UpdateInstanceComponentTest extends WiremockComponentTest { - static final String APP_NAME_1 = "first-app"; - static final String APP_NAME_2 = "second-app"; + static final String APP_NAME_1 = "update-first-app"; + static final String APP_NAME_2 = "update-second-app"; @Autowired private OpenServiceBrokerApiFixture brokerFixture; diff --git a/spring-cloud-app-broker-sample/src/test/java/org.springframework.cloud.appbroker/sample/UpdateInstanceWithServicesComponentTest.java b/spring-cloud-app-broker-sample/src/test/java/org.springframework.cloud.appbroker/sample/UpdateInstanceWithServicesComponentTest.java new file mode 100644 index 0000000..070773d --- /dev/null +++ b/spring-cloud-app-broker-sample/src/test/java/org.springframework.cloud.appbroker/sample/UpdateInstanceWithServicesComponentTest.java @@ -0,0 +1,84 @@ +/* + * Copyright 2016-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.appbroker.sample; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.appbroker.sample.fixtures.CloudControllerStubFixture; +import org.springframework.cloud.appbroker.sample.fixtures.OpenServiceBrokerApiFixture; +import org.springframework.cloud.servicebroker.model.instance.OperationState; +import org.springframework.http.HttpStatus; +import org.springframework.test.context.TestPropertySource; + +import static io.restassured.RestAssured.given; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.springframework.cloud.appbroker.sample.UpdateInstanceWithServicesComponentTest.APP_NAME; +import static org.springframework.cloud.appbroker.sample.UpdateInstanceWithServicesComponentTest.SERVICE_NAME; +import static org.springframework.cloud.appbroker.sample.UpdateInstanceWithServicesComponentTest.SERVICE_INSTANCE_NAME; + +@TestPropertySource(properties = { + "spring.cloud.appbroker.services[0].service-name=example", + "spring.cloud.appbroker.services[0].plan-name=standard", + "spring.cloud.appbroker.services[0].apps[0].path=classpath:demo.jar", + "spring.cloud.appbroker.services[0].apps[0].name=" + APP_NAME, + "spring.cloud.appbroker.services[0].apps[0].services[0].service-instance-name=" + SERVICE_INSTANCE_NAME, + "spring.cloud.appbroker.services[0].services[0].service-instance-name=" + SERVICE_INSTANCE_NAME, + "spring.cloud.appbroker.services[0].services[0].name=" + SERVICE_NAME, + "spring.cloud.appbroker.services[0].services[0].plan=standard" +}) +class UpdateInstanceWithServicesComponentTest extends WiremockComponentTest { + + static final String APP_NAME = "app-update-with-services"; + + static final String SERVICE_INSTANCE_NAME = "my-db-service"; + static final String SERVICE_NAME = "db-service"; + + @Autowired + private OpenServiceBrokerApiFixture brokerFixture; + + @Autowired + private CloudControllerStubFixture cloudControllerFixture; + + @Test + void updateAppWithServicesWhenServicesExist() { + cloudControllerFixture.stubAppExists(APP_NAME); + cloudControllerFixture.stubUpdateApp(APP_NAME); + + cloudControllerFixture.stubServiceInstanceExists(SERVICE_INSTANCE_NAME); + cloudControllerFixture.stubCreateServiceBinding(APP_NAME, SERVICE_INSTANCE_NAME); + + // when a service instance is created + given(brokerFixture.serviceInstanceRequest()) + .when() + .patch(brokerFixture.createServiceInstanceUrl(), "instance-id") + .then() + .statusCode(HttpStatus.ACCEPTED.value()); + + // when the "last_operation" API is polled + given(brokerFixture.serviceInstanceRequest()) + .when() + .get(brokerFixture.getLastInstanceOperationUrl(), "instance-id") + .then() + .statusCode(HttpStatus.OK.value()) + .body("state", is(equalTo(OperationState.IN_PROGRESS.toString()))); + + String state = brokerFixture.waitForAsyncOperationComplete("instance-id"); + assertThat(state).isEqualTo(OperationState.SUCCEEDED.toString()); + } +} \ No newline at end of file diff --git a/spring-cloud-app-broker-sample/src/test/java/org.springframework.cloud.appbroker/sample/UpdateInstanceWithServicesParametersComponentTest.java b/spring-cloud-app-broker-sample/src/test/java/org.springframework.cloud.appbroker/sample/UpdateInstanceWithServicesParametersComponentTest.java new file mode 100644 index 0000000..06fb518 --- /dev/null +++ b/spring-cloud-app-broker-sample/src/test/java/org.springframework.cloud.appbroker/sample/UpdateInstanceWithServicesParametersComponentTest.java @@ -0,0 +1,101 @@ +/* + * Copyright 2016-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.appbroker.sample; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.appbroker.sample.fixtures.CloudControllerStubFixture; +import org.springframework.cloud.appbroker.sample.fixtures.OpenServiceBrokerApiFixture; +import org.springframework.cloud.servicebroker.model.instance.OperationState; +import org.springframework.http.HttpStatus; +import org.springframework.test.context.TestPropertySource; + +import java.util.Collections; +import java.util.HashMap; + +import static io.restassured.RestAssured.given; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.springframework.cloud.appbroker.sample.UpdateInstanceWithServicesParametersComponentTest.APP_NAME; +import static org.springframework.cloud.appbroker.sample.UpdateInstanceWithServicesParametersComponentTest.SERVICE_NAME; +import static org.springframework.cloud.appbroker.sample.UpdateInstanceWithServicesParametersComponentTest.SERVICE_INSTANCE_NAME; + +@TestPropertySource(properties = { + "spring.cloud.appbroker.services[0].service-name=example", + "spring.cloud.appbroker.services[0].plan-name=standard", + "spring.cloud.appbroker.services[0].apps[0].path=classpath:demo.jar", + "spring.cloud.appbroker.services[0].apps[0].name=" + APP_NAME, + "spring.cloud.appbroker.services[0].apps[0].services[0].service-instance-name=" + SERVICE_INSTANCE_NAME, + "spring.cloud.appbroker.services[0].services[0].service-instance-name=" + SERVICE_INSTANCE_NAME, + "spring.cloud.appbroker.services[0].services[0].name=" + SERVICE_NAME, + "spring.cloud.appbroker.services[0].services[0].plan=standard", + "spring.cloud.appbroker.services[0].services[0].parameters-transformers[0].name=ParameterMapping", + "spring.cloud.appbroker.services[0].services[0].parameters-transformers[0].args.include=paramA,paramC" +}) +class UpdateInstanceWithServicesParametersComponentTest extends WiremockComponentTest { + + static final String APP_NAME = "app-update-services-param"; + + static final String SERVICE_INSTANCE_NAME = "my-db-service"; + static final String SERVICE_NAME = "db-service"; + + @Autowired + private OpenServiceBrokerApiFixture brokerFixture; + + @Autowired + private CloudControllerStubFixture cloudControllerFixture; + + @Test + void updateAppWithBackingServicesParameters() { + cloudControllerFixture.stubAppExists(APP_NAME); + cloudControllerFixture.stubUpdateApp(APP_NAME); + + cloudControllerFixture.stubServiceInstanceExists(SERVICE_INSTANCE_NAME); + + // will update with filtered parameters and bind the service instance + HashMap expectedCreationParameters = new HashMap<>(); + expectedCreationParameters.put("paramA", "valueA"); + expectedCreationParameters.put("paramC", Collections.singletonMap("paramC1", "valueC1")); + + cloudControllerFixture.stubUpdateServiceInstanceWithParameters(SERVICE_INSTANCE_NAME, expectedCreationParameters); + cloudControllerFixture.stubCreateServiceBinding(APP_NAME, SERVICE_INSTANCE_NAME); + + // when a service instance is created with parameters + HashMap creationParameters = new HashMap<>(); + creationParameters.put("paramA", "valueA"); + creationParameters.put("paramB", "valueB"); + creationParameters.put("paramC", Collections.singletonMap("paramC1", "valueC1")); + + given(brokerFixture.serviceInstanceRequest(creationParameters)) + .when() + .patch(brokerFixture.createServiceInstanceUrl(), "instance-id") + .then() + .statusCode(HttpStatus.ACCEPTED.value()); + + // when the "last_operation" API is polled + given(brokerFixture.serviceInstanceRequest()) + .when() + .get(brokerFixture.getLastInstanceOperationUrl(), "instance-id") + .then() + .statusCode(HttpStatus.OK.value()) + .body("state", is(equalTo(OperationState.IN_PROGRESS.toString()))); + + String state = brokerFixture.waitForAsyncOperationComplete("instance-id"); + assertThat(state).isEqualTo(OperationState.SUCCEEDED.toString()); + } +} \ No newline at end of file diff --git a/spring-cloud-app-broker-sample/src/test/java/org.springframework.cloud.appbroker/sample/fixtures/CloudControllerStubFixture.java b/spring-cloud-app-broker-sample/src/test/java/org.springframework.cloud.appbroker/sample/fixtures/CloudControllerStubFixture.java index b176bf9..f2d63fe 100644 --- a/spring-cloud-app-broker-sample/src/test/java/org.springframework.cloud.appbroker/sample/fixtures/CloudControllerStubFixture.java +++ b/spring-cloud-app-broker-sample/src/test/java/org.springframework.cloud.appbroker/sample/fixtures/CloudControllerStubFixture.java @@ -328,6 +328,13 @@ public class CloudControllerStubFixture extends WiremockStubFixture { .willReturn(ok())); } + public void stubUpdateServiceInstanceWithParameters(String serviceInstanceName, Map params) { + String serviceInstanceGuid = serviceInstanceName + "-GUID"; + stubFor(put(urlEqualTo("/v2/service_instances/" + serviceInstanceGuid + "?accepts_incomplete=true")) + .withRequestBody(matchingJsonPath("$.[?(@.parameters == " + new JSONObject(params) + ")]")) + .willReturn(ok())); + } + public void stubCreateServiceBinding(String appName, String serviceInstanceName) { String serviceInstanceGuid = serviceInstanceName + "-GUID"; String serviceBindingGuid = appGuid(appName) + "-" + serviceInstanceGuid;