DC/OS Open Service Broker is a toolkit that enables the quick and painless integration of (dcos-commons powered) DC/OS
Services in to CloudFoundry, or any other OSB implementing platform, by providing a high level ServiceModule
API, that allows for the service specific bits -mostly around transforming OSB configuration types to DC/OS package options and service intialization/teardown- to reside in a well isolated, plugin-like implementation class.
No knowledge of the Open Service Broker spec or the DC/OS Service lifecycle management
APIs is necessary - simply fill in the service specific gaps by extending the ServiceModule
abstract class, drop credentials and
other configuration, fire up the main class from io.predix.dcosb:service-broker
,
and you are good to go!
ServiceModule
implementations represent DC/OS Services towards the toolkit. For
an example, see the service-module-cassandra
module.
The tl;dr is
- extend the
ServiceModule
abstract class fromservice-module-api
- add
io.predix.dcosb:service-broker
as a run & build-time dependency - optionally, add
io.predix.dcosb:cli
as a run & build-time dependency - review configuration options in
service-broker/src/main/resources/reference.conf
- drop any overrides at the root of your classpath in toapplication.conf
, or any other location specified by-Dconfig.file
You may also override individual configuration parameters this way, see the typsafe config documentation for details - for a HOCON representation of the minimally required ServiceModuleConfiguration, see
service-broker-api/src/test/resources/reference.conf
A helper to parse this structure in to a ServiceModuleConfiguration object is available by mixing inBasicServiceModule
Seeservice-module-cassandra
for an example. - start
sbt "runMain io.predix.dcosb.servicebroker.Daemon"
for the Open Service Broker. By default, the Service Broker will listen for requests on0.0.0.0:8080
- run
sbt "runMain io.predix.dcosb.cli.DCOSBCli --help"
to get the CLI help, if you addedcli
as a dependency
The DC/OS Open Service Broker
intends to eventually fully implement an Open Service Broker API version 2.12
compatible RESTful web service. However, we anticipate this to be achieved over several releases. Please find a
matrix describing API compatibility in this release
OSB API Feature | Supported? | ServiceModule API | Notes |
---|---|---|---|
Catalog management | The catalog response is completely served up from the ServiceModuleConfiguration object and involves no interaction with the ServiceModule implememntation | ||
Provisioning | In summary, the Provisioning action is mostly supported. The context object parameter is not yet supported and synchronous processing may not be supported in the near future | ||
uri fragment/v2/service_instances/instance_id |
YES | def createServiceInstance(... serviceInstanceId: String, ...): Future[_ <: PackageOptions] |
|
Asynchronous processing ( query parameter "accepts_incomplete" = true ) |
YES | the value of the returned operation field is "create" | |
Synchronous processing ( query parameter "accepts_incomplete" = false, or absent ) |
NO | The Service Broker will return 422 Unprocessable Entity per the spec | |
request object field "service_id" |
YES | def createServiceInstance(... serviceId: String, ...): Future[_ <: PackageOptions] |
|
request object field "plan_id" |
YES | def createServiceInstance(... planId: String, ...): Future[_ <: PackageOptions] |
|
request object field "context" |
NO | ||
request object field "organization_guid" |
YES | def createServiceInstance(... organizationGuid: String, ...): Future[_ <: PackageOptions] |
|
request object field "space_guid" |
YES | def createServiceInstance(... spaceGuid: String, ...): Future[_ <: PackageOptions] |
|
request object field "parameters" |
YES | def createServiceInstance(... parameters: Option[T] forSome { type T <: InstanceParameters } = None, ...): Future[_ <: PackageOptions] | To implement support for receiving arbitrary parameters at provisioning time ("instance parameters") you need to:
|
Updating a Service Instance | In summary, updating a service instance is supported on the most basic level. We do not currently allow for an arbitrary parameters object to be sent with the request ( this will require yet another type + reader combo ) nor do we support the similar context object. Additionally, only asynchronous processing is supported | ||
uri fragment/v2/service_instances/instance_id |
YES | def updateServiceInstance(... serviceInstanceId: String, ...): Future[_ <: PackageOptions] |
|
Asynchronous processing ( query parameter "accepts_incomplete" = true ) |
YES | the value of the returned operation field is "update" | |
Synchronous processing ( query parameter "accepts_incomplete" = false, or absent ) |
NO | The Service Broker will return 422 Unprocessable Entity per the spec | |
request object field "context" |
NO | ||
request object field "service_id" |
YES | def updateServiceInstance(... serviceId: String, ...): Future[_ <: PackageOptions] |
|
request object field "plan_id" |
YES | def updateServiceInstance(... planId: Option[String], ...): Future[_ <: PackageOptions] |
|
request object field "parameters" |
NO | ||
request object field "previous_values" |
PARTIAL | def updateServiceInstance(... previousValues: Option[OSB.PreviousValues], ...): Future[_ <: PackageOptions] |
On the previous_values object we only support the single non-deprecated field: "plan_id"case class PreviousValues(planId: String) |
Binding | In summary, the binding operation is mostly supported. There is no support for the parameters object yet and currently only the app_guid field on the bind_response object will be parsed | ||
uri fragment/v2/service_instances/instance_id/service_bindings/binding_id |
YES | def bindApplicationToServiceInstance(... serviceInstanceId: String, ...): Future[_ <: BindResponse] |
|
uri fragment/v2/service_instances/instance_id/service_bindings/binding_id |
YES | def bindApplicationToServiceInstance(... bindingId: String, ...): Future[_ <: BindResponse] |
|
request object field service_id |
YES | def bindApplicationToServiceInstance(... serviceId: String, ...): Future[_ <: BindResponse] |
|
request object field plan_id |
YES | def bindApplicationToServiceInstance(... planId: String, ...): Future[_ <: BindResponse] |
|
deprecated request object field app_guid |
NO | See bind_resource below | |
request object field bind_resource |
PARTIAL | def bindApplicationToServiceInstance(... bindResource: Option[OSB.BindResource], ...): Future[_ <: BindResponse] |
The BindResource object supports only the app_guid field currently: case class BindResource(appGuid: String) |
request object field parameters |
NO | ||
returned binding value (type) | YES* | trait BindResponse { def credentials: Option[Any] } |
*Currently, only an optional credentials field is enforced on the returned value. This may change if we decide to implement support for proxy or log consuming applications. In the mean time, to return an abitrary binding response object, you need to:
|
Unbinding | |||
uri fragment/v2/service_instances/instance_id/service_bindings/binding_id |
YES | def unbindApplicationFromServiceInstance(... serviceInstanceId: String, ...): Future[_ <: ApplicationUnboundFromServiceInstance] |
|
uri fragment/v2/service_instances/instance_id/service_bindings/binding_id |
YES | def unbindApplicationFromServiceInstance(... bindingId: String, ...): Future[_ <: ApplicationUnboundFromServiceInstance] |
|
request object field service_id |
YES | def unbindApplicationFromServiceInstance(... serviceId: String, ...): Future[_ <: ApplicationUnboundFromServiceInstance] |
|
request object field plan_id |
YES | def unbindApplicationFromServiceInstance(... planId: String, ...): Future[_ <: ApplicationUnboundFromServiceInstance] |
|
Deprovisioning | |||
Asynchronous processing ( query parameter "accepts_incomplete" = true ) |
YES | the value of the returned operation field is "destroy" | |
Synchronous processing ( query parameter "accepts_incomplete" = false, or absent ) |
NO | The Service Broker will return 422 Unprocessable Entity per the spec | |
uri fragment/v2/service_instances/instance_id |
YES | def destroyServiceInstance(... serviceInstanceId: String, ...): Future[ServiceInstanceDestroyed] |
|
request object field service_id |
YES | def destroyServiceInstance(... serviceId: String, ...): Future[ServiceInstanceDestroyed] |
|
request object field plan_id |
YES | def destroyServiceInstance(... planId: String, ...): Future[ServiceInstanceDestroyed] |
|
Last Operation | |||
uri fragment/v2/service_instances/instance_id |
YES | def lastOperation(... serviceInstanceId: String, ...): Future[LastOperationStatus] |
|
request object field service_id |
YES | def lastOperation(... serviceId: Optional[String], ...): Future[LastOperationStatus] |
|
request object field plan_id |
YES | def lastOperation(... planId: Optional[String], ...): Future[LastOperationStatus] |
|
request object field operation |
YES | def lastOperation(... operation: OSB.Operation.Value, ...): Future[LastOperationStatus] |
The features of the toolkit don't stop at simply translating HTTP API calls to the Open Service Broker in to invocations of your ServiceModule API methods.
On many of the abstract Service Module methods that you are expected to implement for a fully functional Service Broker, you'll find parameters that are not parsed out of the incoming request, but help you communicate with the DC/OS Service Instance for which the request is being made.
An Endpoint is discoverability information per service port that is registered by a single task of the DC/OS Service, on a single slave.
/**
* If there are service discovery mechanisms available in the
* DC/OS cluster, the application endpoints / service endpoints of
* the service instance will be presented to the API methods whenever
* possible via this type
*/
case class Endpoint(name: String,
ports: List[Port] = List.empty)
case class Port(name: Option[String] = None,
address: InetSocketAddress)
Receiving this with every invocation of the API ( except for provisioning of course ) will help you carry out your service specific lifecycle actions like creating a user account at binding time, cleaning up at unbinding, etc.
There is an Actor, accessible via sending a Forward
message to dcosProxy
that can
pull named plans from a dcos-commons
compatible service API. For this you'll require
a package name ( available on your ServiceModuleConfiguration
) and a Service Instance ID ( available on every method )
Plans give you insight in to any change operation ( create/update/delete ) you perform on a DC/OS service.
You could use this in your lastOperation
implementation to discover the progress of an asynchronous operation ( createServiceInstance
, updateServiceInstance
and destroyServiceInstance
), return a status update to the platform and take any additional action necesarry ( update billing, etc )
There is a helper method available via ServiceModule
to send HttpRequests to the DC/OS admin proxy. Typically you will use this to communicate with your Service API to perform service specific tasks. The signature of the helper method is:
def `sendRequest and handle response`[T](request: HttpRequest,
handling: Try[HttpResponse] => T)
For examples how this method is used to send requests and process responses, take a look at PlanApiConsumer
The HttpClientActor
trait that contributes method is undergoing some changes, and more helpers should be available soon.
Should you ever need them, actor and service module configuration instances are available via the two below helper methods on ServiceModule
def withServiceModuleConfigurationOrFailPromise[T](
serviceId: String,
f: (ServiceModuleConfiguration[_, _, _] => _),
promise: Promise[T])
def withActorConfigurationOrFailPromise[T](
f: (ServiceModule.ActorConfiguration => _),
promise: Promise[T])
f
is your code needing a configuration object
Provides the abstract classes and traits to implement a ServiceModule. ServiceModules represent DC/OS Services we wish to expose to CloudFoundry (or any other OSB implementing platform), via the toolkit
A very basic ServiceModule implementation to launch and manage Cassandra clusters
Check dcos connection information under src/main/resources/application.conf
, then launch the Cassandra ServiceModule in a Service Broker by running
sbt -Dakka.loglevel=DEBUG "project smCassandra" run
Once launched, you can (currently) send the following requests:
http -a apiuser:YYz3aN-kmw PUT http://localhost:8080/dcosb/cassandra-example/broker/v2/service_instances/dcosb-cassandra-1 parameters:='{"nodes":3,"cluster_name":"dcosb-cassandra-1"}' organization_guid=SomeORG plan_id=developer service_id=SomeServiceGUID space_guid=SomeSpaceGUID
HTTP/1.1 201 Created
Content-Length: 22
Content-Type: application/json
Date: Thu, 24 Aug 2017 03:55:25 GMT
Server: akka-http/10.0.5
{
"operation": "create"
}
http -a apiuser:YYz3aN-kmw GET http://localhost:8080/dcosb/cassandra-example/broker/v2/service_instances/dcosb-cassandra-1/last_operation/?operation=create
HTTP/1.1 200 OK
Content-Length: 123
Content-Type: application/json
Date: Thu, 24 Aug 2017 03:56:32 GMT
Server: akka-http/10.0.5
{
"description": "node-0:[server] -> STARTING, node-1:[server] -> PENDING, node-2:[server] -> PENDING",
"state": "in progress"
}
http -a apiuser:YYz3aN-kmw PUT http://localhost:8080/dcosb/cassandra-example/broker/v2/service_instances/dcosb-cassandra-1/service_bindings/binding-1 service_id=SomeServiceGUID plan_id=developer bind_resource:='{"app_guid":"SomeAppGUID"}'
HTTP/1.1 200 OK
Content-Length: 259
Content-Type: application/json
Date: Thu, 24 Aug 2017 23:48:09 GMT
Server: akka-http/10.0.5
{
"credentials": {
"password": "cassandra",
"username": "cassandra"
},
"nodes": [
{
"host": "node-2-server.dcosb-cassandra-1.mesos",
"port": 24339
},
{
"host": "node-1-server.dcosb-cassandra-1.mesos",
"port": 24339
},
{
"host": "node-0-server.dcosb-cassandra-1.mesos",
"port": 24339
}
]
}
coming soon
http -a apiuser:YYz3aN-kmw DELETE http://localhost:8080/dcosb/cassandra-example/broker/v2/service_instances/dcosb-cassandra-1/service_bindings/binding-1/?service_id=SomeServiceGUID\&plan_id=developer
HTTP/1.1 200 OK
Content-Length: 2
Content-Type: application/json
Date: Fri, 25 Aug 2017 20:46:21 GMT
Server: akka-http/10.0.5
{}
http -a apiuser:YYz3aN-kmw DELETE http://localhost:8080/dcosb/cassandra-example/broker/v2/service_instances/dcosb-cassandra-1/?service_id=service_guid\&plan_id=developer
HTTP/1.1 202 Accepted
Content-Length: 23
Content-Type: application/json
Date: Thu, 24 Aug 2017 03:59:47 GMT
Server: akka-http/10.0.5
{
"operation": "destroy"
}
http -a apiuser:YYz3aN-kmw GET http://localhost:8080/dcosb/cassandra-example/broker/v2/service_instances/dcosb-cassandra-1/last_operation/?operation=destroy
HTTP/1.1 200 OK
Content-Length: 151
Content-Type: application/json
Date: Thu, 24 Aug 2017 03:59:53 GMT
Server: akka-http/10.0.5
{
"description": "Scheduler for cluster with id dcosb-cassandra-1 is being re/started. Please re-try later for operation details.",
"state": "in progress"
}
Actors that consume DC/OS authentication/authorization, service lifecycle and various other management APIs
Actors that consume Mesos REST APIs for DC/OS Service monitoring and management
Utility objects, classes, traits, shared all over
Actors that provide a Open Service Broker API compatible HTTP RESTful service around ServiceModule implementations. Also includes a main class to start the Service Broker in an embedded ( akka-http ) web server
Reads the DC/OS Metrics API and forwards data points to the Predix Timeseries service
Provides a Grafana simple-json-datasource
compatible HTTP RESTful service wrapping around the Predix Timeseries service. By default, deployed by service-broker
via dcos-utils
, as a Marathon application per ServiceModule instance
Convenience maintenance, debugging and monitoring actions for ServiceModule implementation instances