From bd9ad524c7742b103dc9982fada4cf2f9891c2cd Mon Sep 17 00:00:00 2001 From: Scott Frederick Date: Fri, 19 Feb 2016 18:27:33 -0600 Subject: [PATCH] Provide default implementation of ServiceInstanceBindingService for use with service brokers that do not provide bindable service offerings. --- README.md | 4 +- .../ServiceBrokerAutoConfiguration.java | 7 +++ ...BindableServiceInstanceBindingService.java | 30 +++++++++++ ...tanceBindingControllerIntegrationTest.java | 54 +++++++++++++++++++ ...tanceBindingControllerIntegrationTest.java | 28 +--------- ...ServiceInstanceBindingIntegrationTest.java | 35 ++++++++++++ 6 files changed, 129 insertions(+), 29 deletions(-) create mode 100644 src/main/java/org/springframework/cloud/servicebroker/service/NonBindableServiceInstanceBindingService.java create mode 100644 src/test/java/org/springframework/cloud/servicebroker/controller/NonBindableServiceInstanceBindingControllerIntegrationTest.java create mode 100644 src/test/java/org/springframework/cloud/servicebroker/controller/ServiceInstanceBindingIntegrationTest.java diff --git a/README.md b/README.md index 19518984..09a19da3 100644 --- a/README.md +++ b/README.md @@ -66,11 +66,11 @@ This will trigger the inclusion of the default configuration. The Cloud Foundry service broker API has three main endpoint groupings: catalog management, service instance provisioning/deprovisioning, and service instance binding/unbinding. The broker will need to provide one Spring bean to provide the necessary functionality for each endpoint grouping. -For catalog management, the framework provides a default implementation that requires the broker to just provide an implementation of a [`Catalog` bean](src/main/java/org/springframework/cloud/servicebroker/model/Catalog.java). There is an example of this approach in the [MongoDB sample broker](https://github.com/spgreenberg/spring-boot-cf-service-broker-mongo/blob/master/src/main/java/org/springframework/cloud/servicebroker/mongodb/config/CatalogConfig.java). To override this default, provide your own bean that implements the [`CatalogService`](src/main/java/org/springframework/cloud/servicebroker/service/CatalogService.java) interface. +For catalog management, the framework provides a default implementation that requires the broker to just provide an implementation of a [`Catalog` bean](src/main/java/org/springframework/cloud/servicebroker/model/Catalog.java). There is an example of this approach in the [MongoDB sample broker](https://github.com/spring-cloud-samples/cloudfoundry-service-broker/blob/master/src/main/java/org/springframework/cloud/servicebroker/mongodb/config/CatalogConfig.java). To override this default, provide your own bean that implements the [`CatalogService`](src/main/java/org/springframework/cloud/servicebroker/service/CatalogService.java) interface. For service instance provisioning/deprovisioning, provide a Spring bean that implements the [`ServiceInstanceService`](src/main/java/org/springframework/cloud/servicebroker/service/ServiceInstanceService.java) interface. There is no default implementation provided. -For service instance binding/unbinding, provide a Spring bean that implements the [`ServiceInstanceBindingService`](src/main/java/org/springframework/cloud/servicebroker/service/ServiceInstanceBindingService.java) interface. There is no default implementation provided. +For service instance binding/unbinding, provide a Spring bean that implements the [`ServiceInstanceBindingService`](src/main/java/org/springframework/cloud/servicebroker/service/ServiceInstanceBindingService.java) interface. If the service broker does not provide any bindable services, this bean can be omitted and a default implementation will be provided. ## Security diff --git a/src/main/java/org/springframework/cloud/servicebroker/config/ServiceBrokerAutoConfiguration.java b/src/main/java/org/springframework/cloud/servicebroker/config/ServiceBrokerAutoConfiguration.java index 61712ce7..aa970071 100644 --- a/src/main/java/org/springframework/cloud/servicebroker/config/ServiceBrokerAutoConfiguration.java +++ b/src/main/java/org/springframework/cloud/servicebroker/config/ServiceBrokerAutoConfiguration.java @@ -8,6 +8,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration; +import org.springframework.cloud.servicebroker.service.NonBindableServiceInstanceBindingService; +import org.springframework.cloud.servicebroker.service.ServiceInstanceBindingService; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @@ -30,4 +32,9 @@ public CatalogService beanCatalogService(Catalog catalog) { return new BeanCatalogService(catalog); } + @Bean + @ConditionalOnMissingBean(ServiceInstanceBindingService.class) + public ServiceInstanceBindingService nonBindableServiceInstanceBindingService() { + return new NonBindableServiceInstanceBindingService(); + } } \ No newline at end of file diff --git a/src/main/java/org/springframework/cloud/servicebroker/service/NonBindableServiceInstanceBindingService.java b/src/main/java/org/springframework/cloud/servicebroker/service/NonBindableServiceInstanceBindingService.java new file mode 100644 index 00000000..a5bbefa4 --- /dev/null +++ b/src/main/java/org/springframework/cloud/servicebroker/service/NonBindableServiceInstanceBindingService.java @@ -0,0 +1,30 @@ +package org.springframework.cloud.servicebroker.service; + +import org.springframework.cloud.servicebroker.model.CreateServiceInstanceBindingRequest; +import org.springframework.cloud.servicebroker.model.CreateServiceInstanceBindingResponse; +import org.springframework.cloud.servicebroker.model.DeleteServiceInstanceBindingRequest; + +/** + * Default implementation of ServiceInstanceBindingService for service brokers that do not support bindable services. + * + * See http://docs.cloudfoundry.org/services/api.html#binding + * + * @author Scott Frederick + */ +public class NonBindableServiceInstanceBindingService implements ServiceInstanceBindingService { + @Override + public CreateServiceInstanceBindingResponse createServiceInstanceBinding(CreateServiceInstanceBindingRequest request) { + throw nonBindableException(); + } + + @Override + public void deleteServiceInstanceBinding(DeleteServiceInstanceBindingRequest request) { + throw nonBindableException(); + } + + private UnsupportedOperationException nonBindableException() { + return new UnsupportedOperationException("This service broker does not support bindable services. " + + "The service broker should set 'bindable: false' in the service catalog for all service offerings, " + + "or provide an implementation of this service."); + } +} diff --git a/src/test/java/org/springframework/cloud/servicebroker/controller/NonBindableServiceInstanceBindingControllerIntegrationTest.java b/src/test/java/org/springframework/cloud/servicebroker/controller/NonBindableServiceInstanceBindingControllerIntegrationTest.java new file mode 100644 index 00000000..aa279a21 --- /dev/null +++ b/src/test/java/org/springframework/cloud/servicebroker/controller/NonBindableServiceInstanceBindingControllerIntegrationTest.java @@ -0,0 +1,54 @@ +package org.springframework.cloud.servicebroker.controller; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.cloud.servicebroker.model.fixture.DataFixture; +import org.springframework.cloud.servicebroker.service.NonBindableServiceInstanceBindingService; +import org.springframework.cloud.servicebroker.service.ServiceInstanceBindingService; +import org.springframework.http.MediaType; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@RunWith(MockitoJUnitRunner.class) +public class NonBindableServiceInstanceBindingControllerIntegrationTest extends ServiceInstanceBindingIntegrationTest { + + private MockMvc mockMvc; + + @Before + public void setup() { + ServiceInstanceBindingService serviceInstanceBindingService = new NonBindableServiceInstanceBindingService(); + ServiceInstanceBindingController controller = + new ServiceInstanceBindingController(catalogService, serviceInstanceBindingService); + + this.mockMvc = MockMvcBuilders.standaloneSetup(controller) + .setMessageConverters(new MappingJackson2HttpMessageConverter()).build(); + } + + @Test + public void createBindingToAppFails() throws Exception { + setupCatalogService(createRequest.getServiceDefinitionId()); + + mockMvc.perform(put(buildUrl(createRequest)) + .content(DataFixture.toJson(createRequest)) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isInternalServerError()); + } + + @Test + public void deleteBindingFails() throws Exception { + setupCatalogService(deleteRequest.getServiceDefinitionId()); + + mockMvc.perform(delete(buildUrl(deleteRequest)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isInternalServerError()); + } + +} diff --git a/src/test/java/org/springframework/cloud/servicebroker/controller/ServiceInstanceBindingControllerIntegrationTest.java b/src/test/java/org/springframework/cloud/servicebroker/controller/ServiceInstanceBindingControllerIntegrationTest.java index 30465a47..1438dba6 100644 --- a/src/test/java/org/springframework/cloud/servicebroker/controller/ServiceInstanceBindingControllerIntegrationTest.java +++ b/src/test/java/org/springframework/cloud/servicebroker/controller/ServiceInstanceBindingControllerIntegrationTest.java @@ -6,7 +6,6 @@ import org.springframework.cloud.servicebroker.model.CreateServiceInstanceBindingRequest; import org.springframework.cloud.servicebroker.model.CreateServiceInstanceAppBindingResponse; import org.springframework.cloud.servicebroker.model.CreateServiceInstanceRouteBindingResponse; -import org.springframework.cloud.servicebroker.model.DeleteServiceInstanceBindingRequest; import org.springframework.cloud.servicebroker.service.ServiceInstanceBindingService; import org.junit.Before; import org.junit.Test; @@ -21,7 +20,6 @@ import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.util.UriComponentsBuilder; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; @@ -36,7 +34,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @RunWith(MockitoJUnitRunner.class) -public class ServiceInstanceBindingControllerIntegrationTest extends ControllerIntegrationTest { +public class ServiceInstanceBindingControllerIntegrationTest extends ServiceInstanceBindingIntegrationTest { private MockMvc mockMvc; @@ -45,24 +43,11 @@ public class ServiceInstanceBindingControllerIntegrationTest extends ControllerI @Mock private ServiceInstanceBindingService serviceInstanceBindingService; - - private UriComponentsBuilder uriBuilder; - - private CreateServiceInstanceBindingRequest createRequest; - - private DeleteServiceInstanceBindingRequest deleteRequest; @Before public void setup() { this.mockMvc = MockMvcBuilders.standaloneSetup(controller) .setMessageConverters(new MappingJackson2HttpMessageConverter()).build(); - - uriBuilder = UriComponentsBuilder.fromPath("/v2/service_instances/") - .pathSegment("service-instance-one-id", "service_bindings"); - - createRequest = ServiceInstanceBindingFixture.buildCreateAppBindingRequest(); - - deleteRequest = ServiceInstanceBindingFixture.buildDeleteServiceInstanceBindingRequest(); } @Test @@ -245,15 +230,4 @@ public void deleteBindingWithUnknownServiceDefinitionIdSucceeds() throws Excepti .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()); } - - private String buildUrl(CreateServiceInstanceBindingRequest request) { - return uriBuilder.path(request.getBindingId()).toUriString(); - } - - private String buildUrl(DeleteServiceInstanceBindingRequest request) { - return uriBuilder.path(request.getBindingId()) - .queryParam("service_id", request.getServiceDefinitionId()) - .queryParam("plan_id", request.getPlanId()) - .toUriString(); - } } diff --git a/src/test/java/org/springframework/cloud/servicebroker/controller/ServiceInstanceBindingIntegrationTest.java b/src/test/java/org/springframework/cloud/servicebroker/controller/ServiceInstanceBindingIntegrationTest.java new file mode 100644 index 00000000..73a8381a --- /dev/null +++ b/src/test/java/org/springframework/cloud/servicebroker/controller/ServiceInstanceBindingIntegrationTest.java @@ -0,0 +1,35 @@ +package org.springframework.cloud.servicebroker.controller; + +import org.junit.Before; +import org.springframework.cloud.servicebroker.model.CreateServiceInstanceBindingRequest; +import org.springframework.cloud.servicebroker.model.DeleteServiceInstanceBindingRequest; +import org.springframework.cloud.servicebroker.model.fixture.ServiceInstanceBindingFixture; +import org.springframework.web.util.UriComponentsBuilder; + +public abstract class ServiceInstanceBindingIntegrationTest extends ControllerIntegrationTest { + protected UriComponentsBuilder uriBuilder; + + protected CreateServiceInstanceBindingRequest createRequest; + protected DeleteServiceInstanceBindingRequest deleteRequest; + + @Before + public void setupBase() { + uriBuilder = UriComponentsBuilder.fromPath("/v2/service_instances/") + .pathSegment("service-instance-one-id", "service_bindings"); + + createRequest = ServiceInstanceBindingFixture.buildCreateAppBindingRequest(); + + deleteRequest = ServiceInstanceBindingFixture.buildDeleteServiceInstanceBindingRequest(); + } + + protected String buildUrl(CreateServiceInstanceBindingRequest request) { + return uriBuilder.path(request.getBindingId()).toUriString(); + } + + protected String buildUrl(DeleteServiceInstanceBindingRequest request) { + return uriBuilder.path(request.getBindingId()) + .queryParam("service_id", request.getServiceDefinitionId()) + .queryParam("plan_id", request.getPlanId()) + .toUriString(); + } +}