LATEST VERSION: 0.16.1 - CHANGELOG

Creating the Service Author Deliverables

Service Author Requirements

The following deliverables are required from the service authors:

  • Service release(s)
  • BOSH release(s) to be deployed by the manifest that is generated by the Service Adapter
  • Service Adapter BOSH release
  • Contains the Service Adapter CLI
  • Documentation for the operator to configure plan definitions for the Service Adapter
  • Documentation for the operator to backup and restore service instances

For information about what is required of the Operator, see Responsibilities of the Operator.

Create a Service Release

A service release is a BOSH release that is deployed at instance creation time, once for each service instance, by the on-demand broker (ODB). We have created two examples:

See the BOSH docs for help creating a BOSH release. We recommend creating sample manifests that deploy the service release(s), as this will help you write the generate-manifest component of the Service Adapter later.

Service Instance Lifecycle Errands

Note: This feature requires BOSH director v261 or later.

A service release can provide job errands that can be used by ODB during the management of an instance lifecycle. Service instance lifecycle errands may be configured by the operator.

ODB supports the following service instance lifecycle errands:

  • post-deploy - Runs after the creation or updating of a service instance. See the workflow here.
  • pre-delete - Runs before the deletion of a service instance. See the workflow here.

A deployment is only considered successful if along with the deployment the lifecycle errand completes successfully.

See an example implementation of a health check post-deploy job in the example redis release.

In the generate-manifest command ensure to validate and include any supported errands that are specified in the instance groups array.

When generating a manifest, we recommend not using static IPs as this makes network IP management very complex. Instead, we recommend using BOSH’s job links feature. There are two types of job links, implicit and explicit. The example Kafka release uses implicit job links to get the IPs of the brokers and the zookeeper. Details on how to use the links feature are available here.

Create a Service Adapter

A Service Adapter is an executable invoked by ODB. It is expected to respond to these subcommands:

  • generate-manifest Generate a BOSH manifest for your service instance deployment and output to stdout as YAML, given information about the:

    • BOSH director (stemcells, release names)
    • service instance (ID, request parameters, plan properties, IAAS resources)
    • previous manifest, if this is an upgrade deployment
  • dashboard-url Generate an optional URL of a web-based management user interface for the service instance.

  • create-binding Create (unique, if possible) credentials for the service instance, printing them to stdout as JSON.

  • delete-binding Invalidate the created credentials, if possible. Some services (e.g. Redis) are single-user, and this endpoint will do nothing.

**Note**: The ODB requires generate-manifest to be a pure function when a previous manifest is supplied (updating a deployment scenario): given the same arguments the command should always output the same BOSH manifest.

The parameters, and expected output from these subcommands will be explained in detail below. For each of these subcommands, exit status 0 indicates that the command succeeded exit status 10 indicates not implemented, and any non-zero status indicates failure.

Handle Errors

If a subcommand fails, the adapter must return a non-zero exit status, and may optionally print to stdout and/or stderr.

When a subcommand exits with an unrecognized exit code anything printed to stdout will be returned to the CF CLI user.

Both the stdout and stderr streams will be printed in the broker log for the operator. For that reason, we recommend not printing the manifest or other sensitive details to stdout/stderr, as the ODB does no validation on this output.

See an example implementation here.

Inputs for manifest generation

Request parameters

The body of the provision request from Cloud Controller, including arbitrary parameters from the CLI user.

Service authors can choose to allow Cloud Foundry users to configure service instances with arbitrary parameters. See the PCF docs on Managing Service Instances with the CLI. Arbitrary parameters can be passed to the service adapter when creating, or updating a service instance. They allow Cloud Foundry users to override the default configuration for a service plan.

Service authors must document the usage of arbitrary parameters for Cloud Foundry users.

For example:

  • the Kafka service adapter supports the auto_create_topics arbitrary parameter to configure auto-creation of topics on the cluster.

Previous Manifest Properties

Service authors can choose to migrate certain properties for the service from the previous manifest when updating a service instance. If the previous manifest is ignored then any properties configured using arbitrary parameters will not be migrated when a service instance is updated.

Service authors must document the migration of previous manifest properties for operators.

For example:

  • the Kafka service adapter supports migration of the auto_create_topics previous plan property to configure auto-creation of topics on the cluster.

Service Plan Properties

Service authors can choose to support certain properties for the service in the adapter code. These properties are service-specific traits used to customize the service. They do not necessarily map to jobs one to one; a plan property may affect multiple jobs in the deployment. Plan properties are a mechanism for the operator to define different plans.

Service authors must document the usage of plan properties for the operator.

For example:

  • the Redis service adapter supports the persistence property which can be used to attach a disk to the vm.
  • the Kafka service adapter supports the auto_create_topics property to enable auto-creation of topics on the cluster.

Order of Precedence

Note, we recommend service authors use the following order of precedence in their service adapters when generating manifests:

  1. arbitrary parameters
  2. previous manifest properties
  3. plan properties

For example, see auto_create_topics in the example Kafka service adapter.

Service Adapter Interface

A service adapter is expected to be implemented as a binary with the interface

service-adapter [subcommand] [params ...]

where the subcommand can be generate-manifest, create-binding, delete-binding

Examples are provided for Redis and Kafka. Note that these Golang examples us the SDK to help with cross-cutting concerns such as unmarshalling the JSON command line parameters. For example, see the use of HandleCommandLineInvocation in the redis-adapter.

Subcommands

generate-manifest

service-adapter generate-manifest [service-deployment-JSON] [plan-JSON] [request-params-JSON] [previous-manifest-YAML] [previous-plan-JSON]

The generate-manifest subcommand takes in 5 arguments and returns a BOSH deployment manifest YAML.

**Note**: The ODB requires generate-manifest to be a pure function when a previous manifest is supplied (updating a deployment scenario): given the same arguments the command should always output the same BOSH manifest.

Output

The following table describes the supported exit codes and output for the generate-manifest subcommand:

Supported Exit Codes for generate-manifest

exit code Description Output
0 success Stdout: BOSH manifest YAML
10 not implemented
anything else failure Stdout: optional error message for CF CLI users
Stderr: error message for operator
ODB will log both stdout and stderr

Parameters


service-deployment-JSON

Provides information regarding the BOSH director

field Type Description
deployment_name string name of the deployment on the director, in the format service-instance_$guid
releases array of releases list of service releases configured for the deployment by the operator
release.name string name of the release on the director
release.version string version of the release
release.jobs array of strings list of jobs required from the release
stemcell map the stemcell available on the director
stemcell.stemcell_os string stemcell OS available on the director
stemcell.stemcell_version string stemcell version available on the director

For example

{
    "deployment_name": "service-instance_$GUID",
    "releases": [{
        "name": "kafka",
        "version": "dev.42",
        "jobs": [
            "kafka_node",
            "zookeeper"
        ]
    }],
    "stemcell": {
        "stemcell_os": "BeOS",
        "stemcell_version": "2"
    }
}

ODB only supports injecting one stemcell into each service deployment (different instance groups cannot have different stemcells).

ODB only supports using exact release and stemcell versions. The use of latest and floating stemcells are not supported.

Your Service Adapter should be opinionated about which jobs it requires to generate its manifest. For example, the Kafka example requires kafka_node and zookeeper. It should not be opinionated about the mapping of BOSH release to job. The jobs can all be provided by one release, or across many. The SDK provides the helper function GenerateInstanceGroupsWithNoProperties for generating instance groups without any properties. The Kafka example service adapter uses this helper function and invokes it to map the service releases parameter to the BOSH manifest releases and instance_groups sections.

You should provide documentation about which jobs are required by your Service Adapter, and which BOSH releases operators should get these jobs from.

plan-JSON

Plan for which the manifest is supposed to be generated

plan-JSON schema
field Type Description
instance_groups array of instance groups instance groups configured for the plan
instance_group.name string name of the instance group
instance_group.vm_type string the vm_type configured for the instance group, matches one in the cloud config on the director
instance_group.vm_extensions array of strings Optional, the vm_extensions configured for the instance group, must be present in the cloud config on the director
instance_group.persistent_disk_type string Optional, the persistent_disk_type configured for the instance group, matches one in the cloud config on the director
instance_group.networks array of strings the networks the instance group is supposed to be in
instance_group.instances int number of instances for the instance group
instance_group.lifecycle string Optional, specifies the kind of workload the instance group represents. Valid values are service and errand; defaults to service
instance_group.azs array of strings a list of availability zones that the instance groups should be striped across
properties map properties which the operator has configured for deployments of the current plan
update map update block which the operator has configured for deployments of the current plan
update.canaries int plan-specific number of canary instances
update.maxinflight int plan-specific maximum number of non-canary instances to update in parallel
update.canarywatchtime string plan-specific time in milliseconds that the BOSH Director sleeps before checking whether the canary instances are healthy
update.updatewatchtime string plan-specific time in milliseconds that the BOSH Director sleeps before checking whether the non-canary instances are healthy
update.serial boolean Optional, plan-specific flag to deploy instance groups sequentially (true), or in parallel (false); defaults to true

For example

{
   "instance_groups": [
      {
         "name": "example-server",
         "vm_type": "small",
         "vm_extensions": ["some", "extensions"],
         "persistent_disk_type": "ten",
         "networks": [
            "example-network"
         ],
         "azs": [
            "example-az"
         ],
         "instances": 1
      },
      {
         "name": "example-migrations",
         "vm_type": "small",
         "persistent_disk_type": "ten",
         "networks": [
            "example-network"
         ],
         "instances": 1,
         "lifecycle": "errand"
      }
   ],
   "properties": {
      "example": "property"
   },
   "update": {
      "canaries": 1,
      "max_in_flight": 2,
      "canary_watch_time": "1000-30000",
      "update_watch_time": "1000-30000",
      "serial": true
  }
}

Plans are composed by the operator and consist of resource mappings, properties and an optional update block:

  • Resource Mappings

The instance_groups section of the plan JSON. This maps service deployment instance groups (defined by the service author) to resources (defined by the operator). The service developers should document the list of instance group names required for their deployment (e.g. “redis-server”) and any constraints they recommend on resources (e.g. operator must add a persistent disk if persistence property is enabled). These constraints can of course be enforced in code. The instance_groups section also contains a field for lifecycle, which can be set by the operator. The service adapter will add a lifecycle field to the instance group within the BOSH manifest when specified.

  • Properties

Properties are service-specific parameters chosen by the service author. The Redis example exposes a property persistence, which takes a boolean value and toggles disk persistence for Redis. These should be documented by the service developers for the operator.

  • Update Block (optional)

This block defines a plan-specific configuration for BOSH’s update instance operation. Although the ODB considers this block optional, the service adapter must output an update block in every manifest it generates. Some ways to achieve that are:

  1. (Recommended) Define a default update block for all plans, which is used when a plan-specific update block is not provided by the operator
  2. Hard code an update block for all plans in the service adapter
  3. Make the update block mandatory, so that operators must provide an update block for every plan in the service catalogue section of the ODB manifest

request-params-JSON

This is a JSON object that holds the entire body of the service provision or service update request sent by the Cloud Controller to the service broker. The request parameters JSON will be null for upgrades.

The field parameters contains arbitrary key-value pairs which were passed by the application developer as a cf CLI parameter when creating, or updating the service instance.

Note: when updating an existing service instance, any arbitrary parameters passed on a previous create or update will not be passed again. Therefore, for arbitrary parameters to stay the same across multiple deployments they must be retrieved from the previous manifest.

previous-manifest-YAML

The previous manifest as YAML. The previous manifest is nil if this is a new deployment. The format of the manifest should match the BOSH v2 manifest.

It is up to the service author to perform any necessary service-specific migration logic here, if previous manifest is non-nil.

Another use-case of the previous manifest is for the migration of deployment properties which need to stay the same across multiple deployments of a manifest. For example in the Redis example, we generate a password when we do a new deployment. But when the previous deployment manifest is provided, we copy the password over from the previous deployment, as generating a new password for existing deployments will break existing bindings.

For example see the example Redis service adapter.

previous-plan-JSON

The previous plan as JSON. The previous plan is nil if this is a new deployment. The format of the plan should match plan schema. The previous plan can be used for complex plan migration logic, for example the kafka service adapter, rejects a plan migration if the new plan reduces the number of instances, to prevent data loss.


dashboard-url

service-adapter dashboard-url [instance-ID] [plan-JSON] [manifest-YAML]

The dashboard-url subcommand takes in 3 arguments and returns a JSON with the dashboard_url. The dashboard URL is optional. If no dashboard URL is relevant to the service, the subcommand should exit with code 10. Provisioning will be successful without the dashboard URL.

Output

If the dashboard-url command generates a url successfully, it should exit with 0 and return a dashboard URL JSON with the following structure:

field Type Description
dashboard_url string dashboard url returned to the cf user
{
   "dashboard_url":"https://someurl.example.com"
}

Supported exit codes for dashboard-url

exit code Description Output
0 success Stdout: dashboard URL JSON
10 not implemented
anything else failure Stdout: optional error message for CF CLI users
Stderr: error message for operator
ODB will log both stdout and stderr

instance-ID

Provided by the cloud controller which uniquely identifies the service-instance.

plan-JSON

Current plan for the service instance as JSON. The structure should be the same as the plan given in the generate manifest

manifest-YAML

The current manifest as YAML. The format of the manifest should match the [BOSH] v2 manifest](https://bosh.io/docs/manifest-v2.html)


create-binding

service-adapter create-binding [binding-ID] [bosh-VMs-JSON] [manifest-YAML] [request-params-JSON]

Binding credentials for a service instance should share a namespace, and should be unique if possible. E.g. for MySQL, two bindings could include a different username/password pairs, but share the same MySQL database tables and data. The first step is to determine which credentials are best to supply in the context of your service. We recommend that users can be identified statelessly from the binding ID, and the simplest way to do this is to name the user after the binding ID.

Note that at this time ODB does not support syslog drains or route services, so bindings are only a map of credentials.

Output

If the create-binding command is successful, it should return an exit code of 0 and print a service broker API binding JSON response on stdout. An example response is shown below. If the command failed, it should return any non-zero exit code, see the supported exit code table for details of supported failure cases. Stdout and stderr from the command will be logged by the ODB.

Example success response to create-binding:

{
  "credentials": {
    "username": "user1",
    "password": "reallysecret"
  },
  "syslog_drain_url": "optional: for syslog drain services only",
  "route_service_url": "optional: for route services only"
}

Supported exit codes for binding

exit code Description Output
0 success Stdout: binding credentials JSON
10 subcommand not implemented
42 app_guid not provided in the binding request body Stderr: error message for operator
ODB will log both stdout and stderr
49 binding already exists Stderr: error message for operator
ODB will log both stdout and stderr
anything else failure Stdout: optional error message for CF CLI users
Stderr: error message for operator
ODB will log both stdout and stderr

Parameters


binding-ID

The binding-ID generated by the Cloud Controller.

bosh-VMs-JSON

A map of instance group name to an array of IPs provisioned for that instance group.

For example

{
  "mysql_node": ["192.0.2.1", "192.0.2.2", "192.0.2.3"],
  "management_box": ["192.0.2.4"]
}

This can be used to connect to the instance deployment if required, to create a service specific binding. In the example above, the Service Adapter may connect to MySQL as the admin and create a user. As part of the binding, the mysql_node IPs would be returned, but maybe not the management_box.

manifest-YAML

The current manifest as YAML. This is used to extract information about the deployment that is necessary for the binding (e.g. admin credentials with which to create users). The format of the manifest should match the BOSH v2 manifest

request-params-JSON

This is a JSON object that holds the entire body of the service binding request sent by the Cloud Controller to the service broker.

The field parameters contains arbitrary key-value pairs which were passed by the application developer as a cf CLI parameter when creating, or updating the service instance.

Credentials for Bindings

We have identified three approaches to credentials for a service binding.

Static Credentials

In this case, the same credentials are used for all bindings. One option is to define these credentials in the service instance manifest.

This scenario makes sense for services that use the same credentials for all bindings, such as Redis. For example:

properties:
  redis:
    password: <same-for-all-bindings>
Credentials Unique to Each Binding

In this case, when the adapter generate-manifest subcommand is invoked, it generates random admin credentials and returns them as part of the service instance manifest. When the create-binding subcommand is invoked, the adapter can use the admin credentials from the manifest to create unique credentials for the binding. Subsequent create-bindings create new credentials.

This option makes sense for services whose binding creation resembles user creation, such as MySQL or RabbitMQ. For example, in MySQL the admin user can be used to create a new user and database for the binding:

properties:
  admin_password: <use-to-create-credentials>
Using an Agent

In this case, the author defines an agent responsible for handling creation of credentials unique to each binding. The agent must be added as a BOSH release in the service manifest. Moreover, the service and agent jobs should be co-located in the same instance group.

This option is useful for services where the adapter cannot or prefers not to directly call out to the service instance, and instead delegates responsibility for setting up new credentials to an agent.

For example:

releases:
  - name: service-release
    version: 1.5.7
  - name: credentials-agent-release
    version: 4.2.0

instance_groups:
  - name: service-group
    jobs:
      - name: service-job
        release: service-release
      - name: credentials-agent-job
        release: credentials-agent-release

delete-binding

service-adapter delete-binding [binding-ID] [bosh-VMs-JSON] [manifest-YAML] [request-params-JSON]

This should invalidate the credentials that were generated by create-binding if possible. E.g. for MySQL, it would delete the binding user.

Output

The following table describes the supported exit codes and output for the delete-binding subcommand:

Supported exit codes for delete-binding

exit code Description Output
0 success No output is required
10 not implemented
41 binding does not exist Stderr: error message for operator
ODB will log both stdout and stderr
anything else failure Stdout: optional error message for CF CLI users
Stderr: error message for operator
ODB will log both stdout and stderr

Parameters


binding-ID

The binding to be deleted.

bosh-VMs-JSON

A map of instance group name to an array of IPs provisioned for that instance group.

For example

For example

{
  "my-instance-group": ["192.0.2.1", "192.0.2.2", "192.0.2.3"]
}

This can be used to connect to the actual VMs if required, to delete a service specific binding. For example delete a user in MySQL.

manifest-YAML

The current manifest as YAML. This is used to extract information about the deployment that is necessary for the binding (e.g. credentials). The format of the manifest should match the BOSH v2 manifest

For example see the kafka delete binding

Request Params JSON

This is a JSON object that holds the entire body of the service unbinding request sent by the Cloud Controller to the service broker.

The field parameters contains arbitrary key-value pairs which were passed by the application developer as a cf CLI parameter when creating, or updating the service instance.

Packaging

This topic describes workflows for setting up and maintaining of a service instance. The diagrams show which tasks are undertaken by the ODB and which require interaction with the Service Adapter.

The adapter should be packaged as a BOSH release, which should be co-located with the ODB release in a BOSH manifest by the operator. This is only done in order to place the adapter executable on the same VM as the ODB server, therefore the adapter BOSH job’s monit file should probably have no processes defined.

Example service adapter releases:

Golang SDK

We have published a SDK for teams writing their service adapters in Golang. It encapsulates the command line invocation handling, parameter parsing, response serialization and error handling so the adapter authors can focus on the service-specific logic in the adapter.

You should use the same version of the SDK as your ODB release. For example if you are using v0.8.0 of the ODB BOSH release you should checkout the v0.8.0 tag of the SDK.

For the generated BOSH manifest the SDK supports properties in two levels: manifest (global) and job level. Global properties are deprecated in BOSH, in favour of job level properties and job links. As an example, refer to the Kafka example service adapter property generation.

Usage

Get the SDK:

go get github.com/pivotal-cf/on-demand-services-sdk

In the main function for the service adapter, call the HandleCommandLineInvocation function:

package main

import (
    "log"
    "os"

    "github.com/bar-org/foo-service-adapter/adapter"
    "github.com/pivotal-cf/on-demand-services-sdk/serviceadapter"
)

func main() {
  logger := log.New(os.Stderr, "[foo-service-adapter] ", log.LstdFlags)
  manifestGenerator := adapter.ManifestGenerator{}
  binder := adapter.Binder{}
  dashboardUrlGenerator := adapter.DashboardUrlGenerator{}
  serviceadapter.HandleCommandLineInvocation(os.Args, manifestGenerator, binder, dashboardUrlGenerator)
}

Interfaces

The HandleCommandLineInvocation function accepts structs that implement these interfaces:

type ManifestGenerator interface {
    GenerateManifest(serviceDeployment ServiceDeployment, plan Plan, requestParams RequestParameters, previousManifest *bosh.BoshManifest, previousPlan *Plan) (bosh.BoshManifest, error)
}

type Binder interface {
    CreateBinding(bindingID string, deploymentTopology bosh.BoshVMs, manifest bosh.BoshManifest, requestParams RequestParameters) (Binding, error)
    DeleteBinding(bindingID string, deploymentTopology bosh.BoshVMs, manifest bosh.BoshManifest, requestParams RequestParameters) error
}

type DashboardUrlGenerator interface {
    DashboardUrl(instanceID string, plan Plan, manifest bosh.BoshManifest) (DashboardUrl, error)
}

Helpers

The helper function GenerateInstanceGroupsWithNoProperties can be used to generate the instance groups for the BOSH manifest from the arguments passed to the adapter. One of the inputs for this function is the mapping of instance groups to jobs for the deployment (deploymentInstanceGroupsToJobs). This mapping must be provided by the service author. This function will not address job level properties for the generated instance groups; these properties must also be provided by the service author. For an example implementation see the job mapping in the Kafka example adapter.

Error handling

Any error returned by the interface functions is considered to be for the Cloud Foundry CLI user and will accordingly be printed to stdout.

The adapter code is responsible for performing any error logging to stderr that the authors think is relevant for the operator logs.

There are three specialised errors for the CreateBinding function, which allow the adapter to exit with the appropriate code:

serviceadapter.NewBindingAlreadyExistsError()
serviceadapter.NewBindingNotFoundError()
serviceadapter.NewAppGuidNotProvidedError()

For more complete code examples please take a look at the kafka adapter or the redis adapter.

Create a pull request or raise an issue on the source for this page in GitHub