Spring Cloud Open Service Broker is a framework for building Spring Boot that implement the Open Service Broker API
Introduction
The Open Service Broker API defines an HTTP interface between the services marketplace of a platform and service brokers. Service Brokers are responsible for;
-
Advertising a catalog of their service offerings and plans
-
Acting on requests from the marketplace for
-
provisioning (creating or updating) a service
-
creating a binding between a service and client application
-
unbinding or deleting a binding between a service and client application
-
deprovisioning (deleting) a service
-
Scaffolding for the broker side of the Open Service Broker API is implemented by the Spring Cloud Open Service Broker project, by providing the required Spring MVC controllers, domain objects and configuration. Services authored using Spring Boot and Spring Cloud Open Service Broker can easily implement the endpoints by providing Spring beans that implement the appropriate interface.
Getting started
See the Spring Boot documentation for getting started building a Spring Boot application.
Most service broker applications will implement API or web UI endpoints beyond the Open Service Broker API endpoints. These additional endpoints might provide information about the application, provide a dashboard UI, or provide controls over application behavior. Developers can choose to implement these additional endpoints using Spring MVC or Spring WebFlux.
The choice of Spring web framework does not affect the behavior of the Open Service Broker API endpoints, but the auto-configuration of the project depends on which web framework the service broker application uses.
Two Spring Boot starter dependencies are provided, reflecting the choice of Spring web framework.
Advertising Services
The brokers catalog is available to the platform market place via the endpoint /v2/catalog
Implementing Service Catalogs
The service brokers catalog provides a list of metadata describing the available services along with attributes such as cost and memory allocation.
The broker can either provide a @Bean
of type Catalog
or implement the service CatalogService
Example Implementation
package org.springframework.cloud.servicebroker.example;
import org.springframework.cloud.servicebroker.model.catalog.Catalog;
import org.springframework.cloud.servicebroker.model.catalog.Plan;
import org.springframework.cloud.servicebroker.model.catalog.ServiceDefinition;
import org.springframework.cloud.servicebroker.service.CatalogService;
import org.springframework.stereotype.Service;
@Service
public class ExampleCatalog implements CatalogService {
@Override
public Catalog getCatalog() {
return Catalog.builder()
.serviceDefinitions(getServiceDefinition("exampleservice1"))
.build();
}
@Override
public ServiceDefinition getServiceDefinition(String serviceId) {
return ServiceDefinition.builder()
.id(serviceId)
.name("example")
.description("A simple example")
.bindable(true)
.tags("example", "tags")
.plans(getPlan())
.build();
}
private Plan getPlan() {
return Plan.builder()
.id("simple-plan-1")
.name("standard")
.description("A simple plan")
.free(true)
.build();
}
}
Service Instance Provision & Deprovision
Service brokers are responsible for providing the services advertised in their catalog and managing their lifecycle in the underlying cloud platform. The services created by the broker are referred to as service instances. Service instances can be standalone servers or part of a multi-tenant service (such as a database schema).
Service Instance Creation
The createServiceInstance() method must be overridden to implement service instance creation behavior.
In most cases cases, brokers will either create an application to be consumed or provision a resource from a multi-tenant service such as a database server. Brokers are required to keep track of service instances they manage. It is possible to specify that provisioning is asynchronous, in which case a response should be returned immediately and provisioning performed in the background.
Parameters can be passed when creating / updating services which can be used to configure how the service is deployed.
Service Instance Updating
The updateServiceInstance() method must be overridden to implement service instance update behavior.
In the Open Service Broker API creating and updating services conform to the same contract.
The process of updating a service can range from changing configuration values to deploying an entirely new version of the service.
Service Instance Deletion
The updateServiceInstance() method must be overridden to implement service instance deletion behavior. It should be ensured that both the state of the service instance as well as the actual resource are removed.
Service Instance Retrieval
The getServiceInstance() method can optionally be overridden to implement service instance retrieval behavior.
Example Implementation
package org.springframework.cloud.servicebroker.example;
import java.util.Map;
import org.springframework.cloud.servicebroker.model.instance.CreateServiceInstanceRequest;
import org.springframework.cloud.servicebroker.model.instance.CreateServiceInstanceResponse;
import org.springframework.cloud.servicebroker.model.instance.DeleteServiceInstanceRequest;
import org.springframework.cloud.servicebroker.model.instance.DeleteServiceInstanceResponse;
import org.springframework.cloud.servicebroker.model.instance.GetLastServiceOperationRequest;
import org.springframework.cloud.servicebroker.model.instance.GetLastServiceOperationResponse;
import org.springframework.cloud.servicebroker.model.instance.GetServiceInstanceRequest;
import org.springframework.cloud.servicebroker.model.instance.GetServiceInstanceResponse;
import org.springframework.cloud.servicebroker.model.instance.OperationState;
import org.springframework.cloud.servicebroker.model.instance.UpdateServiceInstanceRequest;
import org.springframework.cloud.servicebroker.model.instance.UpdateServiceInstanceResponse;
import org.springframework.cloud.servicebroker.service.ServiceInstanceService;
import org.springframework.stereotype.Service;
@Service
public class ExampleServiceInstanceProvision implements ServiceInstanceService {
@Override
public CreateServiceInstanceResponse createServiceInstance(CreateServiceInstanceRequest request) {
String spaceGuid = request.getContext().getProperty("space_guid").toString();
String orgGuid = request.getContext().getProperty("organization_guid").toString();
String dashboardUrl = deployServiceInstanceToPlaform(
request.getServiceInstanceId(),
request.getPlanId(),
request.getServiceDefinitionId(),
request.getParameters(),
orgGuid,
spaceGuid);
return CreateServiceInstanceResponse.builder().dashboardUrl(dashboardUrl).build();
}
@Override
public DeleteServiceInstanceResponse deleteServiceInstance(DeleteServiceInstanceRequest request) {
deleteServiceFromPlatform(request.getServiceInstanceId());
return DeleteServiceInstanceResponse.builder().build();
}
@Override
public GetLastServiceOperationResponse getLastOperation(GetLastServiceOperationRequest request) {
OperationState status = getServiceInstanceStatuse(request.getServiceInstanceId());
return GetLastServiceOperationResponse.builder().operationState(status).build();
}
/**
* Implementation of getServiceInstance() is optional
*/
@Override
public GetServiceInstanceResponse getServiceInstance(GetServiceInstanceRequest request) {
return getServiceInstanceDetails(request.getServiceInstanceId());
}
@Override
public UpdateServiceInstanceResponse updateServiceInstance(UpdateServiceInstanceRequest request) {
updateService(request.getServiceInstanceId(), request.getParameters());
return UpdateServiceInstanceResponse.builder().build();
}
private void deleteServiceFromPlatform(String serviceInstanceId) {
throw new UnsupportedOperationException();
}
private String deployServiceInstanceToPlaform(String serviceInstanceId, String planId, String serviceDefinitionId,
Map<String, Object> parameters, String orgGuid, String spaceGuid) {
throw new UnsupportedOperationException();
}
private GetServiceInstanceResponse getServiceInstanceDetails(String serviceInstanceId) {
throw new UnsupportedOperationException();
}
private OperationState getServiceInstanceStatuse(String serviceInstanceId) {
return OperationState.SUCCEEDED;
}
private void updateService(String serviceInstanceId, Map<String, Object> parameters) {
throw new UnsupportedOperationException();
}
}
Service Binding & Unbinding
Services can optionally provide functionality for creating bindings for applications to consume. Services that support binding must advertise the fact in their Service Definition
Service bindings are typically used to expose credentials to an application. They can also be used to expose route, logging and volume services.
Creating a Service Binding
The createServiceInstanceBinding() method must be overridden to implement service binding creation behavior
A typical flow for creating a service binding is;
-
Check if the binding already exists with the same parameters. If it did then return
bindingExisted
in the response -
Create the requested resource in the underlying platform (credential, route or volume). Parameters may be included in the request to specify how the resource is created.
-
Build a response with details of the binding.
Implementations should be mindful of returning credentials in clear text and instead return a reference that enables the consumer to retrieve the credential in secure way.
Retrieving Service Bindings
The getServiceInstanceBinding() method must be overridden to implement service binding retrieval behavior Implementations need to retrieve the state of bindings owned by the service. Binding state could be retrieved from the underlying platform or from another datastore.
Deleting a Service Binding
The deleteServiceInstanceBinding() method must be overridden to implement service binding deletion behavior A binding deletion operation will need to delete any platform resources associated with the binding and any persisted state.
Example implementation
package org.springframework.cloud.servicebroker.example;
import org.springframework.cloud.servicebroker.model.binding.CreateServiceInstanceAppBindingResponse;
import org.springframework.cloud.servicebroker.model.binding.CreateServiceInstanceAppBindingResponse.CreateServiceInstanceAppBindingResponseBuilder;
import org.springframework.cloud.servicebroker.model.binding.CreateServiceInstanceBindingRequest;
import org.springframework.cloud.servicebroker.model.binding.CreateServiceInstanceBindingResponse;
import org.springframework.cloud.servicebroker.model.binding.DeleteServiceInstanceBindingRequest;
import org.springframework.cloud.servicebroker.model.binding.GetServiceInstanceBindingRequest;
import org.springframework.cloud.servicebroker.model.binding.GetServiceInstanceBindingResponse;
import org.springframework.cloud.servicebroker.service.ServiceInstanceBindingService;
import org.springframework.stereotype.Service;
@Service
public class ExampleServiceBinding implements ServiceInstanceBindingService {
/**
* A typical binding flow will check if a binding already exists, creating appropriate resources such as credentials
* then persisting the binding state. Service binding requests may also be passed arbitrary parameters which may be
* used to influence how bindings are created.
* <p>
* For brevity this example does not show the processes involved in creating resources security credentials
* and persisting service bindings.
*
* @param request Details of a request to create a service instance binding.
* @return Details of the created service binding
*/
@Override
public CreateServiceInstanceBindingResponse createServiceInstanceBinding(CreateServiceInstanceBindingRequest request) {
CreateServiceInstanceAppBindingResponseBuilder responseBuilder = CreateServiceInstanceAppBindingResponse.builder();
String bindingUsername = String.format("%s-%s", request.getPlanId(), request.getBindingId());
String bindingPassword = "supersecret";
Object securityRole = request.getParameters().getOrDefault("securityRole", "user");
return responseBuilder.credentials("username", bindingUsername)
.credentials("password", bindingPassword)
.credentials("role", securityRole)
.build();
}
/**
* Binding deletion typically involves checking if a binding exists, then deleting the persisted record of a binding
* and any associated resources.
*
* @param bindingRequest Details of a request to delete a service instance binding.
*/
@Override
public void deleteServiceInstanceBinding(DeleteServiceInstanceBindingRequest bindingRequest) {
deleteBinding(bindingRequest.getBindingId());
}
/**
* Typical flow will attempt to find the binding and return details to the requester.
*
* @param bindingRequest Details of a request to retrieve a service instance binding.
* @return Details of the service binding
*/
@Override
public GetServiceInstanceBindingResponse getServiceInstanceBinding(GetServiceInstanceBindingRequest bindingRequest) {
return findBindingById(bindingRequest.getBindingId());
}
private void deleteBinding(String bindingId) {
throw new UnsupportedOperationException();
}
private GetServiceInstanceBindingResponse findBindingById(String bindingId) {
throw new UnsupportedOperationException();
}
}
Service Broker Security
Securing the /v2/* Endpoints
The Open Service Broker API mandates that the Service Broker endpoints must be secured. This is usually done with basic authentication but some platforms may support other mechanisms such OAuth 2.
Broker endpoints can be secured using Spring Security. The following example shows how this might be achieved;
Example Spring Security Configuration
package org.springframework.cloud.servicebroker.example;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@Configuration
@EnableWebSecurity
public class ExampleSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/v2/**").hasRole("ADMIN")
.and()
.httpBasic();
}
@Bean
public InMemoryUserDetailsManager userDetailsService() {
return new InMemoryUserDetailsManager(adminUser());
}
private UserDetails adminUser() {
return User.withUsername("admin")
.password("supersecret")
.roles("ADMIN")
.build();
}
}
Example Service Broker Application
The Bookstore Broker example project implements a sample service broker that adheres to the Open Service Broker API using the Spring Cloud Open Service Broker framework. It can be deployed to either Cloud Foundry or Kubernetes, and can be registered as a service broker to either platform.