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