diff --git a/spring-cloud-app-broker-acceptance-tests/src/test/java/org.springframework.cloud.appbroker.acceptance/UpdateInstanceWithServicesAcceptanceTest.java b/spring-cloud-app-broker-acceptance-tests/src/test/java/org.springframework.cloud.appbroker.acceptance/UpdateInstanceWithServicesAcceptanceTest.java index 390337f..71f5d2e 100644 --- a/spring-cloud-app-broker-acceptance-tests/src/test/java/org.springframework.cloud.appbroker.acceptance/UpdateInstanceWithServicesAcceptanceTest.java +++ b/spring-cloud-app-broker-acceptance-tests/src/test/java/org.springframework.cloud.appbroker.acceptance/UpdateInstanceWithServicesAcceptanceTest.java @@ -45,6 +45,7 @@ class UpdateInstanceWithServicesAcceptanceTest extends CloudFoundryAcceptanceTes "spring.cloud.appbroker.services[0].services[0].name=" + BACKING_SERVICE_NAME, "spring.cloud.appbroker.services[0].services[0].plan=" + PLAN_NAME, "spring.cloud.appbroker.services[0].services[0].service-instance-name=" + BACKING_SI_NAME, + "spring.cloud.appbroker.services[0].services[0].rebind-on-update=true" }) void shouldPushAppWithServicesBind() { // when a service instance is created diff --git a/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/deployer/BackingService.java b/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/deployer/BackingService.java index 6f2e306..28c9082 100644 --- a/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/deployer/BackingService.java +++ b/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/deployer/BackingService.java @@ -31,6 +31,7 @@ public class BackingService { private Map parameters; private Map properties; private List parametersTransformers; + private boolean rebindOnUpdate; private BackingService() { } @@ -40,13 +41,15 @@ public class BackingService { String plan, Map parameters, Map properties, - List parametersTransformers) { + List parametersTransformers, + boolean rebindOnUpdate) { this.serviceInstanceName = serviceInstanceName; this.name = name; this.plan = plan; this.parameters = parameters; this.properties = properties; this.parametersTransformers = parametersTransformers; + this.rebindOnUpdate = rebindOnUpdate; } BackingService(BackingService backingServiceToCopy) { @@ -62,6 +65,7 @@ public class BackingService { this.parametersTransformers = backingServiceToCopy.parametersTransformers == null ? new ArrayList<>() : new ArrayList<>(backingServiceToCopy.parametersTransformers); + this.rebindOnUpdate = backingServiceToCopy.rebindOnUpdate; } public String getServiceInstanceName() { @@ -96,6 +100,10 @@ public class BackingService { this.parameters = parameters; } + public void addParameter(String key, Object value) { + parameters.put(key, value); + } + public Map getProperties() { return properties; } @@ -112,8 +120,12 @@ public class BackingService { this.parametersTransformers = parametersTransformers; } - public void addParameter(String key, Object value) { - parameters.put(key, value); + public boolean isRebindOnUpdate() { + return rebindOnUpdate; + } + + public void setRebindOnUpdate(boolean rebindOnUpdate) { + this.rebindOnUpdate = rebindOnUpdate; } @Override @@ -162,14 +174,11 @@ public class BackingService { private Map parameters = new HashMap<>(); private Map properties = new HashMap<>(); private final List parameterTransformers = new ArrayList<>(); + private boolean rebindOnUpdate; BackingServiceBuilder() { } - public BackingService build() { - return new BackingService(serviceInstanceName, name, plan, parameters, properties, parameterTransformers); - } - public BackingServiceBuilder serviceInstanceName(String serviceInstanceName) { this.serviceInstanceName = serviceInstanceName; return this; @@ -200,6 +209,14 @@ public class BackingService { return this; } + public BackingServiceBuilder rebindOnUpdate(boolean rebindOnUpdate) { + this.rebindOnUpdate = rebindOnUpdate; + return this; + } + + public BackingService build() { + return new BackingService(serviceInstanceName, name, plan, parameters, properties, parameterTransformers, rebindOnUpdate); + } } } 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 75a0f69..36b0b50 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 @@ -49,9 +49,6 @@ public class BackingServicesProvisionService { 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)) 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 4db91ad..cf1bfc9 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 @@ -75,6 +75,7 @@ public class DeployerClient { .serviceInstanceName(backingService.getServiceInstanceName()) .parameters(backingService.getParameters()) .properties(backingService.getProperties()) + .rebindOnUpdate(backingService.isRebindOnUpdate()) .build()) .doOnRequest(l -> log.debug("Creating backing service {}", backingService.getName())) .doOnSuccess(d -> log.debug("Finished creating backing service {}", backingService.getName())) 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 7f4c54b..9cc81ff 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,6 @@ 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; @@ -56,22 +55,29 @@ class BackingServicesProvisionServiceTest { expectedValues.add("si2"); StepVerifier.create(backingServicesProvisionService.createServiceInstance(backingServices)) - // deployments are run in parallel, so the order of completion is not predictable - // ensure that both expected signals are sent in any order - .expectNextMatches(expectedValues::remove) - .expectNextMatches(expectedValues::remove) - .verifyComplete(); + // deployments are run in parallel, so the order of completion is not predictable + // ensure that both expected signals are sent in any order + .expectNextMatches(expectedValues::remove) + .expectNextMatches(expectedValues::remove) + .verifyComplete(); } @Test @SuppressWarnings("UnassignedFluxMonoInstance") void updateServiceInstance() { - doReturn(Mono.just("si1")) + doReturn(Mono.just("updated1")) .when(deployerClient).updateServiceInstance(backingServices.get(0)); + doReturn(Mono.just("updated2")) + .when(deployerClient).updateServiceInstance(backingServices.get(1)); + + List expectedValues = new ArrayList<>(); + expectedValues.add("updated1"); + expectedValues.add("updated2"); StepVerifier.create(backingServicesProvisionService.updateServiceInstance(backingServices)) - .assertNext(value -> assertThat(value).isEqualTo("si1")) - .verifyComplete(); + .expectNextMatches(expectedValues::remove) + .expectNextMatches(expectedValues::remove) + .verifyComplete(); verifyNoMoreInteractions(deployerClient); } @@ -89,11 +95,11 @@ class BackingServicesProvisionServiceTest { expectedValues.add("deleted2"); StepVerifier.create(backingServicesProvisionService.deleteServiceInstance(backingServices)) - // deployments are run in parallel, so the order of completion is not predictable - // ensure that both expected signals are sent in any order - .expectNextMatches(expectedValues::remove) - .expectNextMatches(expectedValues::remove) - .verifyComplete(); + // deployments are run in parallel, so the order of completion is not predictable + // ensure that both expected signals are sent in any order + .expectNextMatches(expectedValues::remove) + .expectNextMatches(expectedValues::remove) + .verifyComplete(); } } \ No newline at end of file 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 11863b5..ca74d5a 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 @@ -21,12 +21,10 @@ import java.nio.file.Path; import java.time.Duration; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Consumer; -import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -492,21 +490,6 @@ public class CloudFoundryAppDeployer implements AppDeployer, ResourceLoaderAware } } - /** - * Return a function usable in {@literal doOnError} constructs that will unwrap unrecognized Cloud Foundry Exceptions - * and log the text payload. - */ - private Consumer logError(String msg) { - return e -> { - if (e instanceof UnknownCloudFoundryException) { - logger.error(msg + "\nUnknownCloudFoundryException encountered, whose payload follows:\n" - + ((UnknownCloudFoundryException)e).getPayload(), e); - } else { - logger.error(msg, e); - } - }; - } - @Override public Mono createServiceInstance(CreateServiceInstanceRequest request) { org.cloudfoundry.operations.services.CreateServiceInstanceRequest createServiceInstanceRequest = @@ -540,56 +523,75 @@ public class CloudFoundryAppDeployer implements AppDeployer, ResourceLoaderAware @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()); - - CloudFoundryOperations operations = getOperations(request.getProperties()); - return operations.services() - .updateInstance(updateServiceInstanceRequest) - .then(updateServiceInstanceResponseMono); + CloudFoundryOperations cloudFoundryOperations = getOperations(request.getProperties()); + return unbindServiceInstanceIfNecessary(request, cloudFoundryOperations) + .then(updateServiceInstanceIfNecessary(request, cloudFoundryOperations)); } + @Override public Mono deleteServiceInstance(DeleteServiceInstanceRequest request) { final String serviceInstanceName = request.getServiceInstanceName(); - return getOperations(request.getProperties()) - .services() - .getInstance(GetServiceInstanceRequest.builder() + CloudFoundryOperations cloudFoundryOperations = getOperations(request.getProperties()); + return unbindServiceInstance(serviceInstanceName, cloudFoundryOperations) + .then(deleteServiceInstance(serviceInstanceName, cloudFoundryOperations) + .then(Mono.just(DeleteServiceInstanceResponse.builder() + .name(serviceInstanceName) + .build()))); + } + + private Mono deleteServiceInstance(String serviceInstanceName, CloudFoundryOperations cloudFoundryOperations) { + return cloudFoundryOperations.services().deleteInstance( + org.cloudfoundry.operations.services.DeleteServiceInstanceRequest + .builder() .name(serviceInstanceName) - .build()) + .build()); + } + + private Mono unbindServiceInstance(String serviceInstanceName, + CloudFoundryOperations cloudFoundryOperations) { + return cloudFoundryOperations.services().getInstance(GetServiceInstanceRequest.builder() + .name(serviceInstanceName) + .build()) .map(ServiceInstance::getApplications) - .flatMap((Function, Mono>) applications -> - Flux.fromIterable(applications) - .flatMap( - application -> - getOperations(request.getProperties()) - .services() - .unbind( - UnbindServiceInstanceRequest - .builder() - .applicationName(application) - .serviceInstanceName(serviceInstanceName) - .build()) - ).collectList()) - .then( - getOperations(request.getProperties()) - .services() - .deleteInstance( - org.cloudfoundry.operations.services.DeleteServiceInstanceRequest - .builder() - .name(serviceInstanceName) - .build()) - .then(Mono.just(DeleteServiceInstanceResponse.builder().name(serviceInstanceName).build()))); + .flatMap(applications -> Flux.fromIterable(applications) + .flatMap(application -> cloudFoundryOperations.services().unbind( + UnbindServiceInstanceRequest.builder() + .applicationName(application) + .serviceInstanceName(serviceInstanceName) + .build()) + ) + .then(Mono.empty())); + } + + private Mono unbindServiceInstanceIfNecessary(UpdateServiceInstanceRequest request, + CloudFoundryOperations cloudFoundryOperations) { + if (request.isRebindOnUpdate()) { + return unbindServiceInstance(request.getServiceInstanceName(), cloudFoundryOperations); + } + return Mono.empty(); + } + + private Mono updateServiceInstanceIfNecessary(UpdateServiceInstanceRequest request, + CloudFoundryOperations cloudFoundryOperations) { + // service instances can be updated with a change to the plan, name, or parameters; + // of these only parameter changes are supported, so don't update if the + // backing service instance has no parameters + if (request.getParameters() == null || request.getParameters().isEmpty()) { + return Mono.empty(); + } + + final String serviceInstanceName = request.getServiceInstanceName(); + + return cloudFoundryOperations.services().updateInstance( + org.cloudfoundry.operations.services.UpdateServiceInstanceRequest.builder() + .serviceInstanceName(serviceInstanceName) + .parameters(request.getParameters()) + .build()) + .then(Mono.just(UpdateServiceInstanceResponse.builder() + .name(serviceInstanceName) + .build())); } private CloudFoundryOperations getOperations(Map properties) { @@ -599,4 +601,19 @@ public class CloudFoundryAppDeployer implements AppDeployer, ResourceLoaderAware return this.operations; } } + + /** + * Return a function usable in {@literal doOnError} constructs that will unwrap unrecognized Cloud Foundry Exceptions + * and log the text payload. + */ + private Consumer logError(String msg) { + return e -> { + if (e instanceof UnknownCloudFoundryException) { + logger.error(msg + "\nUnknownCloudFoundryException encountered, whose payload follows:\n" + + ((UnknownCloudFoundryException)e).getPayload(), e); + } else { + logger.error(msg, e); + } + }; + } } \ 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 e2b276f..ae57ed6 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 @@ -18,11 +18,10 @@ package org.springframework.cloud.appbroker.deployer.cloudfoundry; import java.io.File; import java.util.ArrayList; +import java.util.Collections; +import java.util.Map; import org.cloudfoundry.client.CloudFoundryClient; -import org.cloudfoundry.client.v2.serviceinstances.GetServiceInstanceResponse; -import org.cloudfoundry.client.v2.serviceinstances.LastOperation; -import org.cloudfoundry.client.v2.serviceinstances.ServiceInstanceEntity; import org.cloudfoundry.client.v2.serviceinstances.ServiceInstances; import org.cloudfoundry.operations.CloudFoundryOperations; import org.cloudfoundry.operations.applications.ApplicationHealthCheck; @@ -370,7 +369,30 @@ class CloudFoundryAppDeployerTest { } @Test - void updateServiceInstance() { + void updateServiceInstanceUpdatesWithParameters() { + Map parameters = Collections.singletonMap("param1", "value"); + + when(operationsServices.updateInstance( + org.cloudfoundry.operations.services.UpdateServiceInstanceRequest.builder() + .serviceInstanceName("service-instance-name") + .parameters(parameters) + .build())) + .thenReturn(Mono.empty()); + + UpdateServiceInstanceRequest request = + UpdateServiceInstanceRequest.builder() + .serviceInstanceName("service-instance-name") + .parameters(parameters) + .build(); + + StepVerifier.create( + appDeployer.updateServiceInstance(request)) + .assertNext(response -> assertThat(response.getName()).isEqualTo("service-instance-name")) + .verifyComplete(); + } + + @Test + void updateServiceInstanceUnbindsWhenRequired() { when(operationsServices.updateInstance( org.cloudfoundry.operations.services.UpdateServiceInstanceRequest.builder() .serviceInstanceName("service-instance-name") @@ -379,25 +401,47 @@ class CloudFoundryAppDeployerTest { .thenReturn(Mono.empty()); when(operationsServices.getInstance(GetServiceInstanceRequest.builder() - .name("service-instance-name") - .build())) + .name("service-instance-name") + .build())) .thenReturn(Mono.just(ServiceInstance.builder() .name("service-instance-name") .id("service-instance-guid") .type(ServiceInstanceType.MANAGED) + .applications("app1", "app2") .build())); - when(clientServiceInstances.get( - org.cloudfoundry.client.v2.serviceinstances.GetServiceInstanceRequest.builder() - .serviceInstanceId("service-instance-guid") + when(operationsServices.unbind(UnbindServiceInstanceRequest.builder() + .applicationName("app1") + .serviceInstanceName("service-instance-name") + .build())) + .thenReturn(Mono.empty()); + + when(operationsServices.unbind(UnbindServiceInstanceRequest.builder() + .applicationName("app2") + .serviceInstanceName("service-instance-name") + .build())) + .thenReturn(Mono.empty()); + + UpdateServiceInstanceRequest request = + UpdateServiceInstanceRequest.builder() + .serviceInstanceName("service-instance-name") + .parameters(emptyMap()) + .rebindOnUpdate(true) + .build(); + + StepVerifier.create( + appDeployer.updateServiceInstance(request)) + .verifyComplete(); + } + + @Test + void updateServiceInstanceDoesNothingWithoutParameters() { + when(operationsServices.updateInstance( + org.cloudfoundry.operations.services.UpdateServiceInstanceRequest.builder() + .serviceInstanceName("service-instance-name") + .parameters(emptyMap()) .build())) - .thenReturn(Mono.just(GetServiceInstanceResponse.builder() - .entity(ServiceInstanceEntity.builder() - .lastOperation(LastOperation.builder() - .state("succeeded") - .build()) - .build()) - .build())); + .thenReturn(Mono.empty()); UpdateServiceInstanceRequest request = UpdateServiceInstanceRequest.builder() @@ -407,7 +451,6 @@ class CloudFoundryAppDeployerTest { StepVerifier.create( appDeployer.updateServiceInstance(request)) - .assertNext(response -> assertThat(response.getName()).isEqualTo("service-instance-name")) .verifyComplete(); } 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 index 93b5f24..19e13a7 100644 --- 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 @@ -24,13 +24,16 @@ public class UpdateServiceInstanceRequest { private final String serviceInstanceName; private final Map parameters; private final Map properties; + private final boolean rebindOnUpdate; UpdateServiceInstanceRequest(String serviceInstanceName, Map parameters, - Map properties) { + Map properties, + boolean rebindOnUpdate) { this.serviceInstanceName = serviceInstanceName; this.parameters = parameters; this.properties = properties; + this.rebindOnUpdate = rebindOnUpdate; } public static UpdateServiceInstanceRequestBuilder builder() { @@ -49,11 +52,16 @@ public class UpdateServiceInstanceRequest { return properties; } + public boolean isRebindOnUpdate() { + return rebindOnUpdate; + } + public static class UpdateServiceInstanceRequestBuilder { private String serviceInstanceName; private final Map parameters = new HashMap<>(); private final Map properties = new HashMap<>(); + private boolean rebindOnUpdate; UpdateServiceInstanceRequestBuilder() { } @@ -84,8 +92,13 @@ public class UpdateServiceInstanceRequest { return this; } + public UpdateServiceInstanceRequestBuilder rebindOnUpdate(boolean rebindOnUpdate) { + this.rebindOnUpdate = rebindOnUpdate; + return this; + } + public UpdateServiceInstanceRequest build() { - return new UpdateServiceInstanceRequest(serviceInstanceName, parameters, properties); + return new UpdateServiceInstanceRequest(serviceInstanceName, parameters, properties, rebindOnUpdate); } } diff --git a/spring-cloud-app-broker-integration-tests/src/test/java/org.springframework.cloud.appbroker/integration/CreateInstanceWithExistingServicesComponentTest.java b/spring-cloud-app-broker-integration-tests/src/test/java/org.springframework.cloud.appbroker/integration/CreateInstanceWithExistingServicesComponentTest.java index 7d3893a..d2fc420 100644 --- a/spring-cloud-app-broker-integration-tests/src/test/java/org.springframework.cloud.appbroker/integration/CreateInstanceWithExistingServicesComponentTest.java +++ b/spring-cloud-app-broker-integration-tests/src/test/java/org.springframework.cloud.appbroker/integration/CreateInstanceWithExistingServicesComponentTest.java @@ -30,23 +30,23 @@ 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.integration.CreateInstanceWithExistingServicesComponentTest.APP_NAME; -import static org.springframework.cloud.appbroker.integration.CreateInstanceWithExistingServicesComponentTest.SERVICE_INSTANCE_1_NAME; -import static org.springframework.cloud.appbroker.integration.CreateInstanceWithExistingServicesComponentTest.SERVICE_INSTANCE_2_NAME; +import static org.springframework.cloud.appbroker.integration.CreateInstanceWithExistingServicesComponentTest.BACKING_SI_1_NAME; +import static org.springframework.cloud.appbroker.integration.CreateInstanceWithExistingServicesComponentTest.BACKING_SI_2_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_1_NAME, - "spring.cloud.appbroker.services[0].apps[0].services[1].service-instance-name=" + SERVICE_INSTANCE_2_NAME + "spring.cloud.appbroker.services[0].apps[0].services[0].service-instance-name=" + BACKING_SI_1_NAME, + "spring.cloud.appbroker.services[0].apps[0].services[1].service-instance-name=" + BACKING_SI_2_NAME }) class CreateInstanceWithExistingServicesComponentTest extends WiremockComponentTest { static final String APP_NAME = "app-with-services"; - static final String SERVICE_INSTANCE_1_NAME = "my-db-service"; - static final String SERVICE_INSTANCE_2_NAME = "my-rabbit-service"; + static final String BACKING_SI_1_NAME = "my-db-service"; + static final String BACKING_SI_2_NAME = "my-rabbit-service"; @Autowired private OpenServiceBrokerApiFixture brokerFixture; @@ -60,11 +60,11 @@ class CreateInstanceWithExistingServicesComponentTest extends WiremockComponentT cloudControllerFixture.stubPushApp(APP_NAME); // given that service instances exist - cloudControllerFixture.stubServiceInstanceExists(SERVICE_INSTANCE_1_NAME); - cloudControllerFixture.stubServiceInstanceExists(SERVICE_INSTANCE_2_NAME); + cloudControllerFixture.stubServiceInstanceExists(BACKING_SI_1_NAME); + cloudControllerFixture.stubServiceInstanceExists(BACKING_SI_2_NAME); - cloudControllerFixture.stubCreateServiceBinding(APP_NAME, SERVICE_INSTANCE_1_NAME); - cloudControllerFixture.stubCreateServiceBinding(APP_NAME, SERVICE_INSTANCE_2_NAME); + cloudControllerFixture.stubCreateServiceBinding(APP_NAME, BACKING_SI_1_NAME); + cloudControllerFixture.stubCreateServiceBinding(APP_NAME, BACKING_SI_2_NAME); // when a service instance is created given(brokerFixture.serviceInstanceRequest()) diff --git a/spring-cloud-app-broker-integration-tests/src/test/java/org.springframework.cloud.appbroker/integration/CreateInstanceWithServicesAndTargetComponentTest.java b/spring-cloud-app-broker-integration-tests/src/test/java/org.springframework.cloud.appbroker/integration/CreateInstanceWithServicesAndTargetComponentTest.java index 5abc7b9..86a7add 100644 --- a/spring-cloud-app-broker-integration-tests/src/test/java/org.springframework.cloud.appbroker/integration/CreateInstanceWithServicesAndTargetComponentTest.java +++ b/spring-cloud-app-broker-integration-tests/src/test/java/org.springframework.cloud.appbroker/integration/CreateInstanceWithServicesAndTargetComponentTest.java @@ -30,17 +30,17 @@ 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.integration.CreateInstanceWithServicesAndTargetComponentTest.APP_NAME; -import static org.springframework.cloud.appbroker.integration.CreateInstanceWithServicesAndTargetComponentTest.SERVICE_1_NAME; -import static org.springframework.cloud.appbroker.integration.CreateInstanceWithServicesAndTargetComponentTest.SERVICE_INSTANCE_1_NAME; +import static org.springframework.cloud.appbroker.integration.CreateInstanceWithServicesAndTargetComponentTest.BACKING_SERVICE_NAME; +import static org.springframework.cloud.appbroker.integration.CreateInstanceWithServicesAndTargetComponentTest.BACKING_SI_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_1_NAME, - "spring.cloud.appbroker.services[0].services[0].service-instance-name=" + SERVICE_INSTANCE_1_NAME, - "spring.cloud.appbroker.services[0].services[0].name=" + SERVICE_1_NAME, + "spring.cloud.appbroker.services[0].apps[0].services[0].service-instance-name=" + BACKING_SI_NAME, + "spring.cloud.appbroker.services[0].services[0].service-instance-name=" + BACKING_SI_NAME, + "spring.cloud.appbroker.services[0].services[0].name=" + BACKING_SERVICE_NAME, "spring.cloud.appbroker.services[0].services[0].plan=standard", "spring.cloud.appbroker.services[0].target.name=SpacePerServiceInstance" }) @@ -48,8 +48,8 @@ class CreateInstanceWithServicesAndTargetComponentTest extends WiremockComponent static final String APP_NAME = "app-new-services-target"; - static final String SERVICE_INSTANCE_1_NAME = "my-db-service"; - static final String SERVICE_1_NAME = "db-service"; + static final String BACKING_SI_NAME = "my-db-service"; + static final String BACKING_SERVICE_NAME = "db-service"; @Autowired private OpenServiceBrokerApiFixture brokerFixture; @@ -65,15 +65,15 @@ class CreateInstanceWithServicesAndTargetComponentTest extends WiremockComponent cloudControllerFixture.stubPushAppWithHost(APP_NAME, host); // given that service instances does not exist - cloudControllerFixture.stubServiceInstanceDoesNotExists(SERVICE_INSTANCE_1_NAME); + cloudControllerFixture.stubServiceInstanceDoesNotExists(BACKING_SI_NAME); // and the services are available in the marketplace - cloudControllerFixture.stubServiceExists(SERVICE_1_NAME); + cloudControllerFixture.stubServiceExists(BACKING_SERVICE_NAME); // will create and bind the service instance - cloudControllerFixture.stubCreateServiceInstance(SERVICE_INSTANCE_1_NAME); - cloudControllerFixture.stubCreateServiceBinding(APP_NAME, SERVICE_INSTANCE_1_NAME); - cloudControllerFixture.stubServiceInstanceExists(SERVICE_INSTANCE_1_NAME); + cloudControllerFixture.stubCreateServiceInstance(BACKING_SI_NAME); + cloudControllerFixture.stubCreateServiceBinding(APP_NAME, BACKING_SI_NAME); + cloudControllerFixture.stubServiceInstanceExists(BACKING_SI_NAME); // when a service instance is created given(brokerFixture.serviceInstanceRequest()) diff --git a/spring-cloud-app-broker-integration-tests/src/test/java/org.springframework.cloud.appbroker/integration/CreateInstanceWithServicesComponentTest.java b/spring-cloud-app-broker-integration-tests/src/test/java/org.springframework.cloud.appbroker/integration/CreateInstanceWithServicesComponentTest.java index b71b17a..2be5d71 100644 --- a/spring-cloud-app-broker-integration-tests/src/test/java/org.springframework.cloud.appbroker/integration/CreateInstanceWithServicesComponentTest.java +++ b/spring-cloud-app-broker-integration-tests/src/test/java/org.springframework.cloud.appbroker/integration/CreateInstanceWithServicesComponentTest.java @@ -30,25 +30,25 @@ 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.integration.CreateInstanceWithServicesComponentTest.APP_NAME; -import static org.springframework.cloud.appbroker.integration.CreateInstanceWithServicesComponentTest.SERVICE_1_NAME; -import static org.springframework.cloud.appbroker.integration.CreateInstanceWithServicesComponentTest.SERVICE_INSTANCE_1_NAME; +import static org.springframework.cloud.appbroker.integration.CreateInstanceWithServicesComponentTest.BACKING_SERVICE_NAME; +import static org.springframework.cloud.appbroker.integration.CreateInstanceWithServicesComponentTest.BACKING_SI_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_1_NAME, - "spring.cloud.appbroker.services[0].services[0].service-instance-name=" + SERVICE_INSTANCE_1_NAME, - "spring.cloud.appbroker.services[0].services[0].name=" + SERVICE_1_NAME, + "spring.cloud.appbroker.services[0].apps[0].services[0].service-instance-name=" + BACKING_SI_NAME, + "spring.cloud.appbroker.services[0].services[0].service-instance-name=" + BACKING_SI_NAME, + "spring.cloud.appbroker.services[0].services[0].name=" + BACKING_SERVICE_NAME, "spring.cloud.appbroker.services[0].services[0].plan=standard" }) class CreateInstanceWithServicesComponentTest extends WiremockComponentTest { static final String APP_NAME = "app-with-new-services"; - static final String SERVICE_INSTANCE_1_NAME = "my-db-service"; - static final String SERVICE_1_NAME = "db-service"; + static final String BACKING_SI_NAME = "my-db-service"; + static final String BACKING_SERVICE_NAME = "db-service"; @Autowired private OpenServiceBrokerApiFixture brokerFixture; @@ -62,15 +62,15 @@ class CreateInstanceWithServicesComponentTest extends WiremockComponentTest { cloudControllerFixture.stubPushApp(APP_NAME); // given that service instances does not exist - cloudControllerFixture.stubServiceInstanceDoesNotExists(SERVICE_INSTANCE_1_NAME); + cloudControllerFixture.stubServiceInstanceDoesNotExists(BACKING_SI_NAME); // and the services are available in the marketplace - cloudControllerFixture.stubServiceExists(SERVICE_1_NAME); + cloudControllerFixture.stubServiceExists(BACKING_SERVICE_NAME); // will create and bind the service instance - cloudControllerFixture.stubCreateServiceInstance(SERVICE_INSTANCE_1_NAME); - cloudControllerFixture.stubCreateServiceBinding(APP_NAME, SERVICE_INSTANCE_1_NAME); - cloudControllerFixture.stubServiceInstanceExists(SERVICE_INSTANCE_1_NAME); + cloudControllerFixture.stubCreateServiceInstance(BACKING_SI_NAME); + cloudControllerFixture.stubCreateServiceBinding(APP_NAME, BACKING_SI_NAME); + cloudControllerFixture.stubServiceInstanceExists(BACKING_SI_NAME); // when a service instance is created given(brokerFixture.serviceInstanceRequest()) diff --git a/spring-cloud-app-broker-integration-tests/src/test/java/org.springframework.cloud.appbroker/integration/CreateInstanceWithServicesParametersComponentTest.java b/spring-cloud-app-broker-integration-tests/src/test/java/org.springframework.cloud.appbroker/integration/CreateInstanceWithServicesParametersComponentTest.java index 061a0f4..6e3f761 100644 --- a/spring-cloud-app-broker-integration-tests/src/test/java/org.springframework.cloud.appbroker/integration/CreateInstanceWithServicesParametersComponentTest.java +++ b/spring-cloud-app-broker-integration-tests/src/test/java/org.springframework.cloud.appbroker/integration/CreateInstanceWithServicesParametersComponentTest.java @@ -33,17 +33,17 @@ 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.integration.CreateInstanceWithServicesParametersComponentTest.APP_NAME; -import static org.springframework.cloud.appbroker.integration.CreateInstanceWithServicesParametersComponentTest.SERVICE_1_NAME; -import static org.springframework.cloud.appbroker.integration.CreateInstanceWithServicesParametersComponentTest.SERVICE_INSTANCE_1_NAME; +import static org.springframework.cloud.appbroker.integration.CreateInstanceWithServicesParametersComponentTest.BACKING_SERVICE_NAME; +import static org.springframework.cloud.appbroker.integration.CreateInstanceWithServicesParametersComponentTest.BACKING_SI_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_1_NAME, - "spring.cloud.appbroker.services[0].services[0].service-instance-name=" + SERVICE_INSTANCE_1_NAME, - "spring.cloud.appbroker.services[0].services[0].name=" + SERVICE_1_NAME, + "spring.cloud.appbroker.services[0].apps[0].services[0].service-instance-name=" + BACKING_SI_NAME, + "spring.cloud.appbroker.services[0].services[0].service-instance-name=" + BACKING_SI_NAME, + "spring.cloud.appbroker.services[0].services[0].name=" + BACKING_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" @@ -52,8 +52,8 @@ class CreateInstanceWithServicesParametersComponentTest extends WiremockComponen static final String APP_NAME = "app-services-param"; - static final String SERVICE_INSTANCE_1_NAME = "my-db-service"; - static final String SERVICE_1_NAME = "db-service"; + static final String BACKING_SI_NAME = "my-db-service"; + static final String BACKING_SERVICE_NAME = "db-service"; @Autowired private OpenServiceBrokerApiFixture brokerFixture; @@ -67,19 +67,19 @@ class CreateInstanceWithServicesParametersComponentTest extends WiremockComponen cloudControllerFixture.stubPushApp(APP_NAME); // given that service instances does not exist - cloudControllerFixture.stubServiceInstanceDoesNotExists(SERVICE_INSTANCE_1_NAME); + cloudControllerFixture.stubServiceInstanceDoesNotExists(BACKING_SI_NAME); // and the services are available in the marketplace - cloudControllerFixture.stubServiceExists(SERVICE_1_NAME); + cloudControllerFixture.stubServiceExists(BACKING_SERVICE_NAME); // will create with filtered parameters and bind the service instance HashMap expectedCreationParameters = new HashMap<>(); expectedCreationParameters.put("paramA", "valueA"); expectedCreationParameters.put("paramC", Collections.singletonMap("paramC1", "valueC1")); - cloudControllerFixture.stubCreateServiceInstanceWithParameters(SERVICE_INSTANCE_1_NAME, expectedCreationParameters); - cloudControllerFixture.stubCreateServiceBinding(APP_NAME, SERVICE_INSTANCE_1_NAME); - cloudControllerFixture.stubServiceInstanceExists(SERVICE_INSTANCE_1_NAME); + cloudControllerFixture.stubCreateServiceInstanceWithParameters(BACKING_SI_NAME, expectedCreationParameters); + cloudControllerFixture.stubCreateServiceBinding(APP_NAME, BACKING_SI_NAME); + cloudControllerFixture.stubServiceInstanceExists(BACKING_SI_NAME); // when a service instance is created with parameters HashMap creationParameters = new HashMap<>(); diff --git a/spring-cloud-app-broker-integration-tests/src/test/java/org.springframework.cloud.appbroker/integration/DeleteInstanceWithServicesComponentTest.java b/spring-cloud-app-broker-integration-tests/src/test/java/org.springframework.cloud.appbroker/integration/DeleteInstanceWithServicesComponentTest.java new file mode 100644 index 0000000..7e9d214 --- /dev/null +++ b/spring-cloud-app-broker-integration-tests/src/test/java/org.springframework.cloud.appbroker/integration/DeleteInstanceWithServicesComponentTest.java @@ -0,0 +1,89 @@ +/* + * 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.integration; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.appbroker.integration.fixtures.CloudControllerStubFixture; +import org.springframework.cloud.appbroker.integration.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.integration.DeleteInstanceWithServicesComponentTest.APP_NAME; +import static org.springframework.cloud.appbroker.integration.DeleteInstanceWithServicesComponentTest.BACKING_SERVICE_NAME; +import static org.springframework.cloud.appbroker.integration.DeleteInstanceWithServicesComponentTest.BACKING_SI_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=" + BACKING_SI_NAME, + "spring.cloud.appbroker.services[0].services[0].service-instance-name=" + BACKING_SI_NAME, + "spring.cloud.appbroker.services[0].services[0].name=" + BACKING_SERVICE_NAME, + "spring.cloud.appbroker.services[0].services[0].plan=standard" +}) +class DeleteInstanceWithServicesComponentTest extends WiremockComponentTest { + + static final String APP_NAME = "app-delete-with-services"; + + static final String BACKING_SI_NAME = "my-db-service"; + static final String BACKING_SERVICE_NAME = "db-service"; + + @Autowired + private OpenServiceBrokerApiFixture brokerFixture; + + @Autowired + private CloudControllerStubFixture cloudControllerFixture; + + @Test + void deleteAppsAndServicesWhenTheyExist() { + cloudControllerFixture.stubAppExists(APP_NAME); + cloudControllerFixture.stubServiceBindingDoesNotExist(APP_NAME); + cloudControllerFixture.stubDeleteApp(APP_NAME); + + cloudControllerFixture.stubServiceInstanceExists(BACKING_SI_NAME); + + cloudControllerFixture.stubListServiceBindings(APP_NAME, BACKING_SI_NAME); + cloudControllerFixture.stubServiceBindingExists(APP_NAME, BACKING_SI_NAME); + cloudControllerFixture.stubDeleteServiceBinding(APP_NAME, BACKING_SI_NAME); + cloudControllerFixture.stubDeleteServiceInstance(BACKING_SI_NAME); + + // when the service instance is deleted + given(brokerFixture.serviceInstanceRequest()) + .when() + .delete(brokerFixture.deleteServiceInstanceUrl(), "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-integration-tests/src/test/java/org.springframework.cloud.appbroker/integration/UpdateInstanceWithServicesComponentTest.java b/spring-cloud-app-broker-integration-tests/src/test/java/org.springframework.cloud.appbroker/integration/UpdateInstanceWithServicesComponentTest.java index 4278b18..6061445 100644 --- a/spring-cloud-app-broker-integration-tests/src/test/java/org.springframework.cloud.appbroker/integration/UpdateInstanceWithServicesComponentTest.java +++ b/spring-cloud-app-broker-integration-tests/src/test/java/org.springframework.cloud.appbroker/integration/UpdateInstanceWithServicesComponentTest.java @@ -56,7 +56,7 @@ class UpdateInstanceWithServicesComponentTest extends WiremockComponentTest { private CloudControllerStubFixture cloudControllerFixture; @Test - void updateAppWithServicesWhenServicesExist() { + void updateAppWithServices() { cloudControllerFixture.stubAppExists(APP_NAME); cloudControllerFixture.stubUpdateApp(APP_NAME); diff --git a/spring-cloud-app-broker-integration-tests/src/test/java/org.springframework.cloud.appbroker/integration/UpdateInstanceWithServicesParametersComponentTest.java b/spring-cloud-app-broker-integration-tests/src/test/java/org.springframework.cloud.appbroker/integration/UpdateInstanceWithServicesParametersComponentTest.java index 345e96c..572c937 100644 --- a/spring-cloud-app-broker-integration-tests/src/test/java/org.springframework.cloud.appbroker/integration/UpdateInstanceWithServicesParametersComponentTest.java +++ b/spring-cloud-app-broker-integration-tests/src/test/java/org.springframework.cloud.appbroker/integration/UpdateInstanceWithServicesParametersComponentTest.java @@ -32,17 +32,17 @@ 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.integration.UpdateInstanceWithServicesParametersComponentTest.APP_NAME; -import static org.springframework.cloud.appbroker.integration.UpdateInstanceWithServicesParametersComponentTest.SERVICE_NAME; -import static org.springframework.cloud.appbroker.integration.UpdateInstanceWithServicesParametersComponentTest.SERVICE_INSTANCE_NAME; +import static org.springframework.cloud.appbroker.integration.UpdateInstanceWithServicesParametersComponentTest.BACKING_SERVICE_NAME; +import static org.springframework.cloud.appbroker.integration.UpdateInstanceWithServicesParametersComponentTest.BACKING_SI_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].apps[0].services[0].service-instance-name=" + BACKING_SI_NAME, + "spring.cloud.appbroker.services[0].services[0].service-instance-name=" + BACKING_SI_NAME, + "spring.cloud.appbroker.services[0].services[0].name=" + BACKING_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" @@ -51,8 +51,8 @@ class UpdateInstanceWithServicesParametersComponentTest extends WiremockComponen 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"; + static final String BACKING_SI_NAME = "my-db-service"; + static final String BACKING_SERVICE_NAME = "db-service"; @Autowired private OpenServiceBrokerApiFixture brokerFixture; @@ -65,17 +65,17 @@ class UpdateInstanceWithServicesParametersComponentTest extends WiremockComponen cloudControllerFixture.stubAppExists(APP_NAME); cloudControllerFixture.stubUpdateApp(APP_NAME); - cloudControllerFixture.stubServiceInstanceExists(SERVICE_INSTANCE_NAME); - cloudControllerFixture.stubListServiceBindings(APP_NAME, SERVICE_INSTANCE_NAME); - cloudControllerFixture.stubServiceBindingExists(APP_NAME, SERVICE_INSTANCE_NAME); + cloudControllerFixture.stubServiceInstanceExists(BACKING_SI_NAME); + cloudControllerFixture.stubListServiceBindings(APP_NAME, BACKING_SI_NAME); + cloudControllerFixture.stubServiceBindingExists(APP_NAME, BACKING_SI_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); + cloudControllerFixture.stubUpdateServiceInstanceWithParameters(BACKING_SI_NAME, expectedCreationParameters); + cloudControllerFixture.stubCreateServiceBinding(APP_NAME, BACKING_SI_NAME); // when a service instance is created with parameters HashMap creationParameters = new HashMap<>(); diff --git a/spring-cloud-app-broker-integration-tests/src/test/java/org.springframework.cloud.appbroker/integration/UpdateInstanceWithServicesRebindComponentTest.java b/spring-cloud-app-broker-integration-tests/src/test/java/org.springframework.cloud.appbroker/integration/UpdateInstanceWithServicesRebindComponentTest.java new file mode 100644 index 0000000..3f1700c --- /dev/null +++ b/spring-cloud-app-broker-integration-tests/src/test/java/org.springframework.cloud.appbroker/integration/UpdateInstanceWithServicesRebindComponentTest.java @@ -0,0 +1,89 @@ +/* + * 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.integration; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.appbroker.integration.fixtures.CloudControllerStubFixture; +import org.springframework.cloud.appbroker.integration.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.integration.UpdateInstanceWithServicesRebindComponentTest.APP_NAME; +import static org.springframework.cloud.appbroker.integration.UpdateInstanceWithServicesRebindComponentTest.BACKING_SI_NAME; +import static org.springframework.cloud.appbroker.integration.UpdateInstanceWithServicesRebindComponentTest.BACKING_SERVICE_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=" + BACKING_SI_NAME, + "spring.cloud.appbroker.services[0].services[0].service-instance-name=" + BACKING_SI_NAME, + "spring.cloud.appbroker.services[0].services[0].name=" + BACKING_SERVICE_NAME, + "spring.cloud.appbroker.services[0].services[0].plan=standard", + "spring.cloud.appbroker.services[0].services[0].rebind-on-update=true" +}) +class UpdateInstanceWithServicesRebindComponentTest extends WiremockComponentTest { + + static final String APP_NAME = "app-update-rebind-with-services"; + + static final String BACKING_SI_NAME = "my-db-service"; + static final String BACKING_SERVICE_NAME = "db-service"; + + @Autowired + private OpenServiceBrokerApiFixture brokerFixture; + + @Autowired + private CloudControllerStubFixture cloudControllerFixture; + + @Test + void updateAppWithServices() { + cloudControllerFixture.stubAppExists(APP_NAME); + cloudControllerFixture.stubUpdateApp(APP_NAME); + + cloudControllerFixture.stubServiceInstanceExists(BACKING_SI_NAME); + + cloudControllerFixture.stubListServiceBindings(APP_NAME, BACKING_SI_NAME); + cloudControllerFixture.stubServiceBindingExists(APP_NAME, BACKING_SI_NAME); + cloudControllerFixture.stubDeleteServiceBinding(APP_NAME, BACKING_SI_NAME); + cloudControllerFixture.stubCreateServiceBinding(APP_NAME, BACKING_SI_NAME); + + // when a service instance is updated + 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-integration-tests/src/test/java/org.springframework.cloud.appbroker/integration/fixtures/CloudControllerStubFixture.java b/spring-cloud-app-broker-integration-tests/src/test/java/org.springframework.cloud.appbroker/integration/fixtures/CloudControllerStubFixture.java index e46cc45..6911f34 100644 --- a/spring-cloud-app-broker-integration-tests/src/test/java/org.springframework.cloud.appbroker/integration/fixtures/CloudControllerStubFixture.java +++ b/spring-cloud-app-broker-integration-tests/src/test/java/org.springframework.cloud.appbroker/integration/fixtures/CloudControllerStubFixture.java @@ -138,11 +138,17 @@ public class CloudControllerStubFixture extends WiremockStubFixture { } public void stubAppExists(final String appName) { + stubFor(get(urlPathEqualTo("/v2/apps/" + appGuid(appName))) + .willReturn(ok() + .withBody(cc("get-app-STAGED", + replace("@name", appName))))); + stubFor(get(urlPathEqualTo("/v2/spaces/" + TEST_SPACE_GUID + "/apps")) .withQueryParam("q", equalTo("name:" + appName)) .withQueryParam("page", equalTo("1")) .willReturn(ok() .withBody(cc("list-space-apps", + replace("@name", appName), replace("@guid", appGuid(appName)), replace("@space-guid", TEST_SPACE_GUID), replace("@stack-guid", stackGuid(appName)))))); @@ -276,7 +282,8 @@ public class CloudControllerStubFixture extends WiremockStubFixture { private void stubCheckAppState(String appName) { stubFor(get(urlPathEqualTo("/v2/apps/" + appGuid(appName))) .willReturn(ok() - .withBody(cc("get-app-STAGED")))); + .withBody(cc("get-app-STAGED", + replace("@name", appName))))); stubFor(get(urlPathEqualTo("/v2/apps/" + appGuid(appName) + "/instances")) .willReturn(ok() @@ -357,6 +364,13 @@ public class CloudControllerStubFixture extends WiremockStubFixture { .willReturn(ok())); } + public void stubDeleteServiceInstance(String serviceInstanceName) { + String serviceInstanceGuid = serviceInstanceGuid(serviceInstanceName); + + stubFor(delete(urlPathEqualTo("/v2/service_instances/" + serviceInstanceGuid)) + .willReturn(ok())); + } + public void stubCreateServiceBinding(String appName, String serviceInstanceName) { String serviceInstanceGuid = serviceInstanceGuid(serviceInstanceName); String serviceBindingGuid = serviceBindingGuid(appName, serviceInstanceName); @@ -365,12 +379,24 @@ public class CloudControllerStubFixture extends WiremockStubFixture { .withRequestBody(matchingJsonPath("$.[?(@.app_guid == '" + appGuid(appName) + "')]")) .withRequestBody(matchingJsonPath("$.[?(@.service_instance_guid == '" + serviceInstanceGuid + "')]")) .willReturn(created() - .withBody(cc("get-service_bindings", + .withBody(cc("get-service_binding", replace("@guid", serviceBindingGuid), replace("@app-guid", appGuid(appName)), replace("@service-instance-guid", serviceInstanceGuid))))); } + public void stubDeleteServiceBinding(String appName, String serviceInstanceName) { + String appGuid = appGuid(appName); + String serviceBindingGuid = serviceBindingGuid(appName, serviceInstanceName); + + stubFor(delete(urlPathEqualTo("/v2/service_bindings/" + serviceBindingGuid)) + .withQueryParam("async", equalTo("true")) + .willReturn(noContent())); + + stubFor(delete(urlPathEqualTo("/v2/apps/" + appGuid + "/service_bindings/" + serviceBindingGuid)) + .willReturn(noContent())); + } + public void stubListServiceBindings(String appName, String serviceInstanceName) { String serviceInstanceGuid = serviceInstanceGuid(serviceInstanceName); String serviceBindingGuid = serviceBindingGuid(appName, serviceInstanceName); @@ -392,7 +418,16 @@ public class CloudControllerStubFixture extends WiremockStubFixture { stubFor(get(urlPathEqualTo("/v2/apps/" + appGuid(appName) + "/service_bindings")) .willReturn(ok() .withBody(cc("get-service_bindings", - replace("@guid", serviceBindingGuid), + replace("@service-binding-guid", serviceBindingGuid), + replace("@app-guid", appGuid(appName)), + replace("@service-instance-guid", serviceInstanceGuid))))); + + stubFor(get(urlPathEqualTo("/v2/apps/" + appGuid(appName) + "/service_bindings")) + .withQueryParam("q", equalTo("service_instance_guid:" + serviceInstanceGuid)) + .withQueryParam("page", equalTo("1")) + .willReturn(ok() + .withBody(cc("get-service_bindings", + replace("@service-binding-guid", serviceBindingGuid), replace("@app-guid", appGuid(appName)), replace("@service-instance-guid", serviceInstanceGuid))))); } diff --git a/spring-cloud-app-broker-integration-tests/src/test/resources/responses/cloudcontroller/get-service_binding.json b/spring-cloud-app-broker-integration-tests/src/test/resources/responses/cloudcontroller/get-service_binding.json new file mode 100644 index 0000000..c0132a9 --- /dev/null +++ b/spring-cloud-app-broker-integration-tests/src/test/resources/responses/cloudcontroller/get-service_binding.json @@ -0,0 +1,21 @@ +{ + "entity": { + "app_guid": "@app-guid", + "app_url": "/v2/apps/@app-guid", + "binding_options": {}, + "credentials": {}, + "gateway_data": null, + "gateway_name": "", + "name": null, + "service_instance_guid": "@service-instance-guid", + "service_instance_url": "/v2/service_instances/@service-instance-guid", + "syslog_drain_url": "", + "volume_mounts": null + }, + "metadata": { + "created_at": "2018-08-17T22:01:54Z", + "guid": "@guid", + "updated_at": "2018-08-17T22:01:54Z", + "url": "/v2/service_bindings/@guid" + } +} diff --git a/spring-cloud-app-broker-integration-tests/src/test/resources/responses/cloudcontroller/get-service_bindings.json b/spring-cloud-app-broker-integration-tests/src/test/resources/responses/cloudcontroller/get-service_bindings.json index c8505fd..bcac32d 100644 --- a/spring-cloud-app-broker-integration-tests/src/test/resources/responses/cloudcontroller/get-service_bindings.json +++ b/spring-cloud-app-broker-integration-tests/src/test/resources/responses/cloudcontroller/get-service_bindings.json @@ -1,21 +1,37 @@ { - "entity": { - "app_guid": "@app-guid", - "app_url": "/v2/apps/@app-guid", - "binding_options": {}, - "credentials": {}, - "gateway_data": null, - "gateway_name": "", - "name": null, - "service_instance_guid": "@service-instance-guid", - "service_instance_url": "/v2/service_instances/@service-instance-guid", - "syslog_drain_url": "", - "volume_mounts": null - }, - "metadata": { - "created_at": "2018-08-17T22:01:54Z", - "guid": "@guid", - "updated_at": "2018-08-17T22:01:54Z", - "url": "/v2/service_bindings/@guid" - } + "total_results": 1, + "total_pages": 1, + "prev_url": null, + "next_url": null, + "resources": [ + { + "metadata": { + "guid": "@service-binding-guid", + "url": "/v2/service_bindings/@service-binding-guid", + "created_at": "2016-06-08T16:41:43Z", + "updated_at": "2016-06-08T16:41:26Z" + }, + "entity": { + "app_guid": "@app-guid", + "service_instance_guid": "@service-instance-guid", + "credentials": {}, + "binding_options": {}, + "gateway_data": null, + "gateway_name": "", + "syslog_drain_url": null, + "volume_mounts": [], + "name": "", + "last_operation": { + "type": "create", + "state": "succeeded", + "description": "", + "updated_at": "2018-02-28T16:25:19Z", + "created_at": "2018-02-28T16:25:19Z" + }, + "app_url": "/v2/apps/@app-guid", + "service_instance_url": "/v2/service_instances/@service-instance-guid", + "service_binding_parameters_url": "/v2/service_bindings/@service-binding-guid/parameters" + } + } + ] } \ No newline at end of file diff --git a/spring-cloud-app-broker-integration-tests/src/test/resources/responses/cloudcontroller/list-space-service_instances.json b/spring-cloud-app-broker-integration-tests/src/test/resources/responses/cloudcontroller/list-space-service_instances.json index 3909b7d..3563aff 100644 --- a/spring-cloud-app-broker-integration-tests/src/test/resources/responses/cloudcontroller/list-space-service_instances.json +++ b/spring-cloud-app-broker-integration-tests/src/test/resources/responses/cloudcontroller/list-space-service_instances.json @@ -7,7 +7,7 @@ { "metadata": { "guid": "@guid", - "url": "/v2/user_provided_service_instances/@guid", + "url": "/v2/service_instances/@guid", "created_at": "2018-07-19T22:29:41Z", "updated_at": "2018-07-19T22:29:41Z" }, @@ -15,13 +15,13 @@ "name": "@name", "credentials": {}, "space_guid": "@space-guid", - "type": "user_provided_service_instance", + "type": "managed_service_instance", "syslog_drain_url": "", "route_service_url": "", "space_url": "/v2/spaces/@space-guid", - "service_bindings_url": "/v2/user_provided_service_instances/@guid/service_bindings", - "service_keys_url": "/v2/user_provided_service_instances/@guid/service_keys", - "routes_url": "/v2/user_provided_service_instances/@guid/routes" + "service_bindings_url": "/v2/service_instances/@guid/service_bindings", + "service_keys_url": "/v2/service_instances/@guid/service_keys", + "routes_url": "/v2/service_instances/@guid/routes" } } ]