From 9487a8d79349005d2e2e6d10e60140f3f3145660 Mon Sep 17 00:00:00 2001 From: Scott Frederick Date: Tue, 11 Dec 2018 21:28:20 -0600 Subject: [PATCH] Make update service operations return when complete. --- ...ateInstanceWithServicesAcceptanceTest.java | 11 +- .../fixtures/cf/CloudFoundryService.java | 31 +++++ .../cloudfoundry/CloudFoundryAppDeployer.java | 34 ++++- .../CloudFoundryAppDeployerTest.java | 83 ++++++++---- ...ceWithServicesParametersComponentTest.java | 2 + .../fixtures/CloudControllerStubFixture.java | 122 +++++++++++++----- .../cloudcontroller/get-service_bindings.json | 21 +++ .../get-service_instances.json | 35 +++++ .../list-service-bindings.json | 54 +++++--- 9 files changed, 311 insertions(+), 82 deletions(-) create mode 100644 spring-cloud-app-broker-sample/src/test/resources/responses/cloudcontroller/get-service_bindings.json create mode 100644 spring-cloud-app-broker-sample/src/test/resources/responses/cloudcontroller/get-service_instances.json 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 7d4e39b..09db08b 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 @@ -84,7 +84,16 @@ class UpdateInstanceWithServicesAcceptanceTest extends CloudFoundryAcceptanceTes parameters.put("parameter3", "value3"); updateServiceInstance(SI_NAME, parameters); - // the services are still bound to it + Optional updatedServiceInstance = getServiceInstance(SI_NAME); + assertThat(updatedServiceInstance).hasValueSatisfying(value -> + assertThat(value.getLastOperation()).contains("completed")); + + // then a backing application is re-deployed + Optional updatedBackingApplication = getApplicationSummaryByName(APP_NAME); + assertThat(updatedBackingApplication).hasValueSatisfying(app -> + assertThat(app.getRunningInstances()).isEqualTo(1)); + + // and the services are still bound to it Optional backingServiceInstanceUpdated = getServiceInstance(BACKING_SI_NAME); assertThat(backingServiceInstanceUpdated).hasValueSatisfying(instance -> assertThat(instance.getApplications()).contains(APP_NAME)); diff --git a/spring-cloud-app-broker-acceptance-tests/src/test/java/org.springframework.cloud.appbroker.acceptance/fixtures/cf/CloudFoundryService.java b/spring-cloud-app-broker-acceptance-tests/src/test/java/org.springframework.cloud.appbroker.acceptance/fixtures/cf/CloudFoundryService.java index 670d5ce..3a2ac74 100644 --- a/spring-cloud-app-broker-acceptance-tests/src/test/java/org.springframework.cloud.appbroker.acceptance/fixtures/cf/CloudFoundryService.java +++ b/spring-cloud-app-broker-acceptance-tests/src/test/java/org.springframework.cloud.appbroker.acceptance/fixtures/cf/CloudFoundryService.java @@ -17,11 +17,14 @@ package org.springframework.cloud.appbroker.acceptance.fixtures.cf; import java.nio.file.Path; +import java.time.Duration; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import org.cloudfoundry.client.CloudFoundryClient; +import org.cloudfoundry.client.v2.serviceinstances.GetServiceInstanceResponse; import org.cloudfoundry.operations.CloudFoundryOperations; import org.cloudfoundry.operations.DefaultCloudFoundryOperations; import org.cloudfoundry.operations.applications.ApplicationDetail; @@ -40,11 +43,15 @@ import org.cloudfoundry.operations.serviceadmin.DeleteServiceBrokerRequest; import org.cloudfoundry.operations.serviceadmin.EnableServiceAccessRequest; import org.cloudfoundry.operations.services.CreateServiceInstanceRequest; import org.cloudfoundry.operations.services.DeleteServiceInstanceRequest; +import org.cloudfoundry.operations.services.GetServiceInstanceRequest; +import org.cloudfoundry.operations.services.ServiceInstance; import org.cloudfoundry.operations.services.ServiceInstanceSummary; import org.cloudfoundry.operations.services.UpdateServiceInstanceRequest; import org.cloudfoundry.operations.spaces.CreateSpaceRequest; import org.cloudfoundry.operations.spaces.SpaceSummary; import org.cloudfoundry.operations.spaces.Spaces; +import org.cloudfoundry.util.LastOperationUtils; +import org.cloudfoundry.util.ResourceUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Flux; @@ -60,11 +67,14 @@ public class CloudFoundryService { private static final Logger LOGGER = LoggerFactory.getLogger(CloudFoundryService.class); private final CloudFoundryOperations cloudFoundryOperations; + private final CloudFoundryClient cloudFoundryClient; private final CloudFoundryProperties cloudFoundryProperties; public CloudFoundryService(CloudFoundryOperations cloudFoundryOperations, + CloudFoundryClient cloudFoundryClient, CloudFoundryProperties cloudFoundryProperties) { this.cloudFoundryOperations = cloudFoundryOperations; + this.cloudFoundryClient = cloudFoundryClient; this.cloudFoundryProperties = cloudFoundryProperties; } @@ -174,6 +184,12 @@ public class CloudFoundryService { .serviceInstanceName(serviceInstanceName) .parameters(parameters) .build()) + .then(cloudFoundryOperations.services() + .getInstance(GetServiceInstanceRequest.builder() + .name(serviceInstanceName) + .build())) + .map(ServiceInstance::getId) + .flatMap(this::waitForUpdateInstance) .doOnSuccess(item -> LOGGER.info("Updated service instance " + serviceInstanceName)) .doOnError(error -> LOGGER.error("Error updating service instance " + serviceInstanceName + ": " + error)); } @@ -298,6 +314,21 @@ public class CloudFoundryService { .build(); } + private Mono waitForUpdateInstance(String serviceInstanceId) { + return LastOperationUtils + .waitForCompletion(Duration.ofMinutes(5), () -> + requestGetServiceInstance(cloudFoundryClient, serviceInstanceId) + .map(response -> ResourceUtils.getEntity(response).getLastOperation())); + } + + private Mono requestGetServiceInstance(CloudFoundryClient cloudFoundryClient, + String serviceInstanceId) { + return cloudFoundryClient.serviceInstances() + .get(org.cloudfoundry.client.v2.serviceinstances.GetServiceInstanceRequest.builder() + .serviceInstanceId(serviceInstanceId) + .build()); + } + private Map appBrokerDeployerEnvironmentVariables() { Map deployerVariables = new HashMap<>(); deployerVariables.put("spring.cloud.appbroker.deployer.cloudfoundry.api-host", 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 7e48858..1eab7f0 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 @@ -35,6 +35,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.cloudfoundry.AbstractCloudFoundryException; import org.cloudfoundry.UnknownCloudFoundryException; import org.cloudfoundry.client.CloudFoundryClient; +import org.cloudfoundry.client.v2.serviceinstances.GetServiceInstanceResponse; import org.cloudfoundry.client.v2.spaces.CreateSpaceRequest; import org.cloudfoundry.client.v2.spaces.DeleteSpaceRequest; import org.cloudfoundry.operations.CloudFoundryOperations; @@ -52,6 +53,8 @@ import org.cloudfoundry.operations.services.ServiceInstance; import org.cloudfoundry.operations.services.UnbindServiceInstanceRequest; import org.cloudfoundry.operations.spaces.GetSpaceRequest; import org.cloudfoundry.operations.spaces.SpaceDetail; +import org.cloudfoundry.util.LastOperationUtils; +import org.cloudfoundry.util.ResourceUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.Exceptions; @@ -82,6 +85,8 @@ public class CloudFoundryAppDeployer implements AppDeployer, ResourceLoaderAware private final Logger logger = LoggerFactory.getLogger(CloudFoundryAppDeployer.class); + private static final Duration ASYNC_OPERATION_TIMEOUT = Duration.ofMinutes(5); + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); private final CloudFoundryDeploymentProperties defaultDeploymentProperties; @@ -552,9 +557,19 @@ public class CloudFoundryAppDeployer implements AppDeployer, ResourceLoaderAware .name(request.getServiceInstanceName()) .build()); - return getOperations(request.getProperties()) - .services() + CloudFoundryOperations operations = getOperations(request.getProperties()); + return operations.services() .updateInstance(updateServiceInstanceRequest) + // for consistency with the createServiceInstance() and deleteServiceInstance() methods, + // this code must wait for the update operation to complete (with either a succeeded or failed status) + // before returning, until this waiting logic is implemented in CF Java Client + // https://github.com/cloudfoundry/cf-java-client/issues/929 + .then(operations.services() + .getInstance(GetServiceInstanceRequest.builder() + .name(request.getServiceInstanceName()) + .build())) + .map(ServiceInstance::getId) + .flatMap(this::waitForUpdateInstance) .then(updateServiceInstanceResponseMono); } @@ -599,4 +614,19 @@ public class CloudFoundryAppDeployer implements AppDeployer, ResourceLoaderAware return this.operations; } } + + private Mono waitForUpdateInstance(String serviceInstanceId) { + return LastOperationUtils + .waitForCompletion(ASYNC_OPERATION_TIMEOUT, () -> + requestGetServiceInstance(client, serviceInstanceId) + .map(response -> ResourceUtils.getEntity(response).getLastOperation())); + } + + private Mono requestGetServiceInstance(CloudFoundryClient cloudFoundryClient, + String serviceInstanceId) { + return cloudFoundryClient.serviceInstances() + .get(org.cloudfoundry.client.v2.serviceinstances.GetServiceInstanceRequest.builder() + .serviceInstanceId(serviceInstanceId) + .build()); + } } \ 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 b6dad65..e2b276f 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 @@ -20,6 +20,10 @@ import java.io.File; import java.util.ArrayList; 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; import org.cloudfoundry.operations.applications.ApplicationManifest; @@ -69,13 +73,16 @@ class CloudFoundryAppDeployerTest { private AppDeployer appDeployer; @Mock - private Applications applications; + private Applications operationsApplications; @Mock - private Services services; + private Services operationsServices; @Mock - private Spaces spaces; + private Spaces operationsSpaces; + + @Mock + private ServiceInstances clientServiceInstances; @Mock private CloudFoundryOperations cloudFoundryOperations; @@ -93,12 +100,13 @@ class CloudFoundryAppDeployerTest { deploymentProperties = new CloudFoundryDeploymentProperties(); CloudFoundryTargetProperties targetProperties = new CloudFoundryTargetProperties(); - when(applications.pushManifest(any())).thenReturn(Mono.empty()); - when(cloudFoundryOperations.applications()).thenReturn(applications); + when(operationsApplications.pushManifest(any())).thenReturn(Mono.empty()); when(resourceLoader.getResource(APP_PATH)).thenReturn(new FileSystemResource(APP_PATH)); - when(cloudFoundryOperations.services()).thenReturn(services); - when(cloudFoundryOperations.spaces()).thenReturn(spaces); + when(cloudFoundryOperations.spaces()).thenReturn(operationsSpaces); + when(cloudFoundryOperations.applications()).thenReturn(operationsApplications); + when(cloudFoundryOperations.services()).thenReturn(operationsServices); + when(cloudFoundryClient.serviceInstances()).thenReturn(clientServiceInstances); appDeployer = new CloudFoundryAppDeployer(deploymentProperties, cloudFoundryOperations, cloudFoundryClient, targetProperties, resourceLoader); @@ -120,7 +128,7 @@ class CloudFoundryAppDeployerTest { .path(new File(APP_PATH).toPath()) .build(); - verify(applications).pushManifest(argThat(matchesManifest(expectedManifest))); + verify(operationsApplications).pushManifest(argThat(matchesManifest(expectedManifest))); } @Test @@ -157,7 +165,7 @@ class CloudFoundryAppDeployerTest { .noRoute(true) .build(); - verify(applications).pushManifest(argThat(matchesManifest(expectedManifest))); + verify(operationsApplications).pushManifest(argThat(matchesManifest(expectedManifest))); } @Test @@ -193,7 +201,7 @@ class CloudFoundryAppDeployerTest { .host("host") .build(); - verify(applications).pushManifest(argThat(matchesManifest(expectedManifest))); + verify(operationsApplications).pushManifest(argThat(matchesManifest(expectedManifest))); } @Test @@ -239,7 +247,7 @@ class CloudFoundryAppDeployerTest { .noRoute(true) .build(); - verify(applications).pushManifest(argThat(matchesManifest(expectedManifest))); + verify(operationsApplications).pushManifest(argThat(matchesManifest(expectedManifest))); } @Test @@ -265,7 +273,7 @@ class CloudFoundryAppDeployerTest { .environmentVariable("SPRING_APPLICATION_JSON", "{\"ENV_VAR_2\":\"value2\",\"ENV_VAR_1\":\"value1\"}") .build(); - verify(applications).pushManifest(argThat(matchesManifest(expectedManifest))); + verify(operationsApplications).pushManifest(argThat(matchesManifest(expectedManifest))); } @Test @@ -292,18 +300,18 @@ class CloudFoundryAppDeployerTest { .environmentVariable("ENV_VAR_2", "value2") .build(); - verify(applications).pushManifest(argThat(matchesManifest(expectedManifest))); + verify(operationsApplications).pushManifest(argThat(matchesManifest(expectedManifest))); } @Test void deleteServiceInstanceShouldUnbindServices() { - when(services.deleteInstance( + when(operationsServices.deleteInstance( org.cloudfoundry.operations.services.DeleteServiceInstanceRequest.builder() .name("service-instance-name") .build())) .thenReturn(Mono.empty()); - when(services.getInstance(GetServiceInstanceRequest.builder().name("service-instance-name").build())) + when(operationsServices.getInstance(GetServiceInstanceRequest.builder().name("service-instance-name").build())) .thenReturn(Mono.just(ServiceInstance.builder() .id("siid") .type(ServiceInstanceType.MANAGED) @@ -311,13 +319,13 @@ class CloudFoundryAppDeployerTest { .applications("app1", "app2") .build())); - when(services.unbind(UnbindServiceInstanceRequest.builder() + when(operationsServices.unbind(UnbindServiceInstanceRequest.builder() .serviceInstanceName("service-instance-name") .applicationName("app1") .build())) .thenReturn(Mono.empty()); - when(services.unbind(UnbindServiceInstanceRequest.builder() + when(operationsServices.unbind(UnbindServiceInstanceRequest.builder() .serviceInstanceName("service-instance-name") .applicationName("app2") .build())) @@ -338,7 +346,7 @@ class CloudFoundryAppDeployerTest { @Test void createServiceInstance() { - when(services.createInstance( + when(operationsServices.createInstance( org.cloudfoundry.operations.services.CreateServiceInstanceRequest.builder() .serviceInstanceName("service-instance-name") .serviceName("db-service") @@ -363,23 +371,44 @@ class CloudFoundryAppDeployerTest { @Test void updateServiceInstance() { - when(services.updateInstance( + when(operationsServices.updateInstance( org.cloudfoundry.operations.services.UpdateServiceInstanceRequest.builder() - .serviceInstanceName("service-instance-name") - .parameters(emptyMap()) - .build())) + .serviceInstanceName("service-instance-name") + .parameters(emptyMap()) + .build())) .thenReturn(Mono.empty()); + when(operationsServices.getInstance(GetServiceInstanceRequest.builder() + .name("service-instance-name") + .build())) + .thenReturn(Mono.just(ServiceInstance.builder() + .name("service-instance-name") + .id("service-instance-guid") + .type(ServiceInstanceType.MANAGED) + .build())); + + when(clientServiceInstances.get( + org.cloudfoundry.client.v2.serviceinstances.GetServiceInstanceRequest.builder() + .serviceInstanceId("service-instance-guid") + .build())) + .thenReturn(Mono.just(GetServiceInstanceResponse.builder() + .entity(ServiceInstanceEntity.builder() + .lastOperation(LastOperation.builder() + .state("succeeded") + .build()) + .build()) + .build())); + UpdateServiceInstanceRequest request = UpdateServiceInstanceRequest.builder() - .serviceInstanceName("service-instance-name") - .parameters(emptyMap()) - .build(); + .serviceInstanceName("service-instance-name") + .parameters(emptyMap()) + .build(); StepVerifier.create( appDeployer.updateServiceInstance(request)) - .assertNext(response -> assertThat(response.getName()).isEqualTo("service-instance-name")) - .verifyComplete(); + .assertNext(response -> assertThat(response.getName()).isEqualTo("service-instance-name")) + .verifyComplete(); } private ApplicationManifest.Builder baseManifest() { 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 index 06fb518..0507027 100644 --- 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 @@ -66,6 +66,8 @@ class UpdateInstanceWithServicesParametersComponentTest extends WiremockComponen cloudControllerFixture.stubUpdateApp(APP_NAME); cloudControllerFixture.stubServiceInstanceExists(SERVICE_INSTANCE_NAME); + cloudControllerFixture.stubListServiceBindings(APP_NAME, SERVICE_INSTANCE_NAME); + cloudControllerFixture.stubServiceBindingExists(APP_NAME, SERVICE_INSTANCE_NAME); // will update with filtered parameters and bind the service instance HashMap expectedCreationParameters = new HashMap<>(); 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 f2d63fe..aac602f 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 @@ -26,6 +26,7 @@ import org.springframework.boot.test.context.TestComponent; import static com.github.tomakehurst.wiremock.client.WireMock.created; import static com.github.tomakehurst.wiremock.client.WireMock.delete; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.matchingJsonPath; import static com.github.tomakehurst.wiremock.client.WireMock.noContent; @@ -120,20 +121,26 @@ public class CloudControllerStubFixture extends WiremockStubFixture { } public void stubAppDoesNotExistInSpace(final String appName, final String space) { - stubFor(get(urlEqualTo("/v2/spaces/" + space + "/apps?q=name:" + appName + "&page=1")) + stubFor(get(urlPathEqualTo("/v2/spaces/" + space + "/apps")) + .withQueryParam("q", equalTo("name:" + appName)) + .withQueryParam("page", equalTo("1")) .willReturn(ok() .withBody(cc("empty-query-results")))); stubFor(post(urlPathEqualTo("/v2/spaces")) .willReturn(ok())); - stubFor(get(urlEqualTo("/v2/spaces/" + TEST_SPACE_GUID + "/apps?q=name:" + appName + "&page=1")) + stubFor(get(urlPathEqualTo("/v2/spaces/" + TEST_SPACE_GUID + "/apps")) + .withQueryParam("q", equalTo("name:" + appName)) + .withQueryParam("page", equalTo("1")) .willReturn(ok() .withBody(cc("empty-query-results")))); } public void stubAppExists(final String appName) { - stubFor(get(urlEqualTo("/v2/spaces/" + TEST_SPACE_GUID + "/apps?q=name:" + appName + "&page=1")) + 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("@guid", appGuid(appName)), @@ -186,7 +193,7 @@ public class CloudControllerStubFixture extends WiremockStubFixture { } private void stubCreateAppMetadata(String appName, ContentPattern... appMetadataPatterns) { - MappingBuilder mappingBuilder = post(urlEqualTo("/v2/apps")) + MappingBuilder mappingBuilder = post(urlPathEqualTo("/v2/apps")) .withRequestBody(matchingJsonPath("$.[?(@.name == '" + appName + "')]")); for (ContentPattern appMetadataPattern : appMetadataPatterns) { mappingBuilder.withRequestBody(appMetadataPattern); @@ -199,7 +206,7 @@ public class CloudControllerStubFixture extends WiremockStubFixture { } private void stubUpdateAppMetadata(String appName, ContentPattern... appMetadataPatterns) { - MappingBuilder mappingBuilder = put(urlEqualTo("/v2/apps/" + appGuid(appName))) + MappingBuilder mappingBuilder = put(urlPathEqualTo("/v2/apps/" + appGuid(appName))) .withRequestBody(matchingJsonPath("$.[?(@.name == '" + appName + "')]")); for (ContentPattern appMetadataPattern : appMetadataPatterns) { mappingBuilder.withRequestBody(appMetadataPattern); @@ -216,18 +223,21 @@ public class CloudControllerStubFixture extends WiremockStubFixture { .willReturn(ok() .withBody(cc("empty-query-results")))); - stubFor(get(urlEqualTo("/v2/routes?q=domain_guid:" + TEST_ORG_GUID + "&q=host:" + host + "&page=1")) + stubFor(get(urlPathEqualTo("/v2/routes")) + .withQueryParam("q", equalTo("domain_guid:" + TEST_ORG_GUID)) + .withQueryParam("q", equalTo("host:" + host)) + .withQueryParam("page", equalTo("1")) .willReturn(ok() .withBody(cc("empty-query-results")))); - stubFor(post(urlEqualTo("/v2/routes")) + stubFor(post(urlPathEqualTo("/v2/routes")) .withRequestBody(matchingJsonPath("$.[?(@.host == '" + host + "')]")) .willReturn(created() .withBody(cc("list-routes", replace("@name", appName), replace("@guid", routeGuid(appName)))))); - stubFor(put(urlEqualTo("/v2/apps/" + appGuid(appName) + "/routes/" + routeGuid(appName))) + stubFor(put(urlPathEqualTo("/v2/apps/" + appGuid(appName) + "/routes/" + routeGuid(appName))) .willReturn(created() .withBody(cc("get-app-STOPPED", replace("@name", appName), @@ -235,26 +245,27 @@ public class CloudControllerStubFixture extends WiremockStubFixture { } private void stubUploadAppBits(String appName) { - stubFor(put(urlEqualTo("/v2/resource_match")) + stubFor(put(urlPathEqualTo("/v2/resource_match")) .withMetadata(optionalStubMapping()) .willReturn(ok() .withBody("[]"))); - stubFor(put(urlEqualTo("/v2/apps/" + appGuid(appName) + "/bits?async=true")) + stubFor(put(urlPathEqualTo("/v2/apps/" + appGuid(appName) + "/bits")) + .withQueryParam("async", equalTo("true")) .willReturn(created() .withBody(cc("put-app-bits", replace("@guid", appName + "-JOB-GUID"))))); } private void stubInitializeAppState(String appName) { - stubFor(put(urlEqualTo("/v2/apps/" + appGuid(appName))) + stubFor(put(urlPathEqualTo("/v2/apps/" + appGuid(appName))) .withRequestBody(matchingJsonPath("$.[?(@.state == 'STOPPED')]")) .willReturn(created() .withBody(cc("get-app-STOPPED", replace("@name", appName), replace("@guid", appGuid(appName)))))); - stubFor(put(urlEqualTo("/v2/apps/" + appGuid(appName))) + stubFor(put(urlPathEqualTo("/v2/apps/" + appGuid(appName))) .withRequestBody(matchingJsonPath("$.[?(@.state == 'STARTED')]")) .willReturn(created() .withBody(cc("get-app-STARTED", @@ -263,11 +274,11 @@ public class CloudControllerStubFixture extends WiremockStubFixture { } private void stubCheckAppState(String appName) { - stubFor(get(urlEqualTo("/v2/apps/" + appGuid(appName))) + stubFor(get(urlPathEqualTo("/v2/apps/" + appGuid(appName))) .willReturn(ok() .withBody(cc("get-app-STAGED")))); - stubFor(get(urlEqualTo("/v2/apps/" + appGuid(appName) + "/instances")) + stubFor(get(urlPathEqualTo("/v2/apps/" + appGuid(appName) + "/instances")) .willReturn(ok() .withBody(cc("get-app-instances")))); } @@ -283,72 +294,109 @@ public class CloudControllerStubFixture extends WiremockStubFixture { } public void stubServiceInstanceExists(String serviceInstanceName) { - stubFor(get(urlEqualTo("/v2/spaces/" + TEST_SPACE_GUID + "/service_instances" + - "?q=name:" + serviceInstanceName + - "&page=1" + - "&return_user_provided_service_instances=true")) + stubFor(get(urlPathEqualTo("/v2/spaces/" + TEST_SPACE_GUID + "/service_instances")) + .withQueryParam("q", equalTo("name:" + serviceInstanceName)) + .withQueryParam("page", equalTo("1")) + .withQueryParam("return_user_provided_service_instances", equalTo("true")) .willReturn(ok() .withBody(cc("list-space-service_instances", replace("@space-guid", TEST_SPACE_GUID), replace("@name", serviceInstanceName), replace("@guid", serviceInstanceName + "-GUID"))))); + + stubFor(get(urlPathEqualTo("/v2/service_instances/" + serviceInstanceGuid(serviceInstanceName))) + .willReturn(ok() + .withBody(cc("get-service_instances", + replace("@space-guid", TEST_SPACE_GUID), + replace("@name", serviceInstanceName), + replace("@guid", serviceInstanceName + "-GUID"))))); } public void stubServiceInstanceDoesNotExists(String serviceInstanceName) { - stubFor(get(urlEqualTo("/v2/spaces/" + TEST_SPACE_GUID + "/service_instances" + - "?q=name:" + serviceInstanceName + - "&page=1" + - "&return_user_provided_service_instances=true")) + stubFor(get(urlPathEqualTo("/v2/spaces/" + TEST_SPACE_GUID + "/service_instances")) + .withQueryParam("q", equalTo("name:" + serviceInstanceName)) + .withQueryParam("page", equalTo("1")) + .withQueryParam("return_user_provided_service_instances", equalTo("true")) .willReturn(ok() .withBody(cc("list-space-service_instances-empty")))); } public void stubServiceExists(String serviceName) { - stubFor(get(urlEqualTo("/v2/spaces/" + TEST_SPACE_GUID + "/services" + - "?q=label:" + serviceName + - "&page=1")) + stubFor(get(urlPathEqualTo("/v2/spaces/" + TEST_SPACE_GUID + "/services")) + .withQueryParam("q", equalTo("label:" + serviceName)) + .withQueryParam("page", equalTo("1")) .willReturn(ok() .withBody(cc("list-space-services")))); - stubFor(get(urlEqualTo("/v2/service_plans?q=service_guid:SERVICE-ID&page=1")) + stubFor(get(urlPathEqualTo("/v2/service_plans")) + .withQueryParam("q", equalTo("service_guid:SERVICE-ID")) + .withQueryParam("page", equalTo("1")) .willReturn(ok() .withBody(cc("list-service-plans")))); } public void stubCreateServiceInstance(String serviceInstanceName) { - stubFor(post(urlEqualTo("/v2/service_instances?accepts_incomplete=true")) + stubFor(post(urlPathEqualTo("/v2/service_instances")) + .withQueryParam("accepts_incomplete", equalTo("true")) .withRequestBody(matchingJsonPath("$.[?(@.name == '" + serviceInstanceName + "')]")) .willReturn(ok())); } public void stubCreateServiceInstanceWithParameters(String serviceInstanceName, Map params) { - stubFor(post(urlEqualTo("/v2/service_instances?accepts_incomplete=true")) + stubFor(post(urlPathEqualTo("/v2/service_instances")) + .withQueryParam("accepts_incomplete", equalTo("true")) .withRequestBody(matchingJsonPath("$.[?(@.name == '" + serviceInstanceName + "')]")) .withRequestBody(matchingJsonPath("$.[?(@.parameters == " + new JSONObject(params) + ")]")) .willReturn(ok())); } public void stubUpdateServiceInstanceWithParameters(String serviceInstanceName, Map params) { - String serviceInstanceGuid = serviceInstanceName + "-GUID"; - stubFor(put(urlEqualTo("/v2/service_instances/" + serviceInstanceGuid + "?accepts_incomplete=true")) + stubFor(put(urlPathEqualTo("/v2/service_instances/" + serviceInstanceGuid(serviceInstanceName))) + .withQueryParam("accepts_incomplete", equalTo("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; + String serviceInstanceGuid = serviceInstanceGuid(serviceInstanceName); + String serviceBindingGuid = serviceBindingGuid(appName, serviceInstanceName); - stubFor(post(urlEqualTo("/v2/service_bindings")) + stubFor(post(urlPathEqualTo("/v2/service_bindings")) .withRequestBody(matchingJsonPath("$.[?(@.app_guid == '" + appGuid(appName) + "')]")) .withRequestBody(matchingJsonPath("$.[?(@.service_instance_guid == '" + serviceInstanceGuid + "')]")) .willReturn(created() + .withBody(cc("get-service_bindings", + replace("@guid", serviceBindingGuid), + replace("@app-guid", appGuid(appName)), + replace("@service-instance-guid", serviceInstanceGuid))))); + } + + public void stubListServiceBindings(String appName, String serviceInstanceName) { + String serviceInstanceGuid = serviceInstanceGuid(serviceInstanceName); + String serviceBindingGuid = serviceBindingGuid(appName, serviceInstanceName); + + stubFor(get(urlPathEqualTo("/v2/service_bindings")) + .withQueryParam("q", equalTo("service_instance_guid:" + serviceInstanceGuid)) + .withQueryParam("page", equalTo("1")) + .willReturn(ok() .withBody(cc("list-service-bindings", replace("@guid", serviceBindingGuid), replace("@app-guid", appGuid(appName)), replace("@service-instance-guid", serviceInstanceGuid))))); } + public void stubServiceBindingExists(String appName, String serviceInstanceName) { + String serviceInstanceGuid = serviceInstanceGuid(serviceInstanceName); + String serviceBindingGuid = serviceBindingGuid(appName, serviceInstanceName); + + stubFor(get(urlPathEqualTo("/v2/apps/" + appGuid(appName) + "/service_bindings")) + .willReturn(ok() + .withBody(cc("get-service_bindings", + replace("@guid", serviceBindingGuid), + replace("@app-guid", appGuid(appName)), + replace("@service-instance-guid", serviceInstanceGuid))))); + } + public void stubServiceBindingDoesNotExist(String appName) { stubFor(get(urlPathEqualTo("/v2/apps/" + appGuid(appName) + "/service_bindings")) .willReturn(ok() @@ -374,4 +422,12 @@ public class CloudControllerStubFixture extends WiremockStubFixture { private String stackGuid(String appName) { return appName + "-STACK-GUID"; } + + private String serviceInstanceGuid(String serviceInstanceName) { + return serviceInstanceName + "-GUID"; + } + + private String serviceBindingGuid(String appName, String serviceInstanceName) { + return appGuid(appName) + "-" + serviceInstanceGuid(serviceInstanceName); + } } diff --git a/spring-cloud-app-broker-sample/src/test/resources/responses/cloudcontroller/get-service_bindings.json b/spring-cloud-app-broker-sample/src/test/resources/responses/cloudcontroller/get-service_bindings.json new file mode 100644 index 0000000..c8505fd --- /dev/null +++ b/spring-cloud-app-broker-sample/src/test/resources/responses/cloudcontroller/get-service_bindings.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" + } +} \ No newline at end of file diff --git a/spring-cloud-app-broker-sample/src/test/resources/responses/cloudcontroller/get-service_instances.json b/spring-cloud-app-broker-sample/src/test/resources/responses/cloudcontroller/get-service_instances.json new file mode 100644 index 0000000..9517979 --- /dev/null +++ b/spring-cloud-app-broker-sample/src/test/resources/responses/cloudcontroller/get-service_instances.json @@ -0,0 +1,35 @@ +{ + "metadata": { + "guid": "@guid", + "url": "/v2/service_instances/@guid", + "created_at": "2016-06-08T16:41:29Z", + "updated_at": "2016-06-08T16:41:26Z" + }, + "entity": { + "name": "@name", + "credentials": {}, + "service_guid": "a14baddf-1ccc-5299-0152-ab9s49de4422", + "service_plan_guid": "779d2df0-9cdd-48e8-9781-ea05301cedb1", + "space_guid": "@space-guid", + "gateway_data": null, + "dashboard_url": null, + "type": "managed_service_instance", + "last_operation": { + "type": "create", + "state": "succeeded", + "description": "service broker-provided description", + "updated_at": "2016-06-08T16:41:29Z", + "created_at": "2016-06-08T16:41:29Z" + }, + "tags": [], + "space_url": "/v2/spaces/@space-guid", + "service_url": "/v2/services/a14baddf-1ccc-5299-0152-ab9s49de4422", + "service_plan_url": "/v2/service_plans/779d2df0-9cdd-48e8-9781-ea05301cedb1", + "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", + "shared_from_url": "/v2/service_instances/@guid/shared_from", + "shared_to_url": "/v2/service_instances/@guid/shared_to", + "service_instance_parameters_url": "/v2/service_instances/@guid/parameters" + } +} \ No newline at end of file diff --git a/spring-cloud-app-broker-sample/src/test/resources/responses/cloudcontroller/list-service-bindings.json b/spring-cloud-app-broker-sample/src/test/resources/responses/cloudcontroller/list-service-bindings.json index c8505fd..bc55ca7 100644 --- a/spring-cloud-app-broker-sample/src/test/resources/responses/cloudcontroller/list-service-bindings.json +++ b/spring-cloud-app-broker-sample/src/test/resources/responses/cloudcontroller/list-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": "@guid", + "url": "/v2/service_bindings/@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": "prod-db", + "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/@guid/parameters" + } + } + ] } \ No newline at end of file