This is part 5 in a series of posts about writing service brokers in .NET Core. All posts in the series:

  1. Marketplace and service provisioning
  2. Service binding
  3. Provisioning an Azure Storage account
  4. Asynchronous service provisioning (this post)

In the previous posts we implemented a service catalog, service (de)provisioning and service (un)binding. Both provisioning and binding were blocking operations, meaning the platform, Pivotal Cloud Foundry (PCF) in this case, has to wait for the operations to complete.

In the real world, provisioning and binding a service may take time so it makes sense to start the operation and then check for its completion once in a while (polling).

OSBAPI theory

According to the OSBAPI spec, a service broker should only support asynchronous operations if the platform supports it. The platform can indicate its support for asynchronous operations by adding a ?accepts_incomplete=true query parameter to requests. If this parameter is not present and the service broker only implements non-blocking operations, it should send a 422 Unprocessable Entity response.

Besides a 422 response, a service broker can send the following success response codes on a provisioning request:

  • 200 OK

    If the service instance already exists, is fully provisioned and the requested parameters are identical to the existing service instance.

  • 201 Created

    Must be returned if the service instance was provisioned as a result of this request. So even if we implement asynchronous provisioning, we can still provision the service instance in a blocking operation and return the result.

    So we could implement a blocking service instancing operation in terms of a non-blocking operation by just waiting on the result of the non-blocking operation (which is exactly what we’re gonna do).

  • 202 Accepted

    Must be returned if the operation is in progress. This is the response code that must be returned when we start an asynchronous provisioning operation. Note that we must also return a 202 when an attempt is made to provision the service instance again but it is not yet fully provisioned.

From the descriptions above it follows that we need a few things:

  • An operation that can check whether a service with the exact same parameters already exists. In this case we can send a 200 OK response.
  • An operation that starts the actual provisioning of a backend system.
  • An operation that can poll a backend system to check whether provisioning has finished. Suppose we are provisioning a MySQL database, we will not get a callback when the operation is done. Instead, we must check periodically whether the database exists.
  • A repository of provisioning operations that are in progress that we can query to tell us whether provisioning is still in progress so we can send a 202 Accepted response.

The platform, upon receiving a 202 Accepted response, will start polling the service broker for the state of the provisioning operation.

This is part 2 in a series of posts about writing service brokers in .NET Core. In the previous post we implemented the bare minimum: a catalog and a blocking implementation of (de)provisioning a service. In this post we will look at a blocking implementation of service (un)binding. All posts in the series:

  1. Marketplace and service provisioning
  2. Service binding (this post)

Setting the stage

As in the first post, we implement (parts of) the Open Service Broker API specification. We use the OpenServiceBroker .NET library that already defines all necessary endpoints and provides implementation hooks for binding and unbinding. We use Pivotal Cloud Foundry, hosted at https://run.pivotal.io for testing our implementation and CF CLI for communicating with the platform.

All source code for this blog post can be found at: https://github.com/orangeglasses/service-broker-dotnet/tree/master.

What to bind to

When we want to bind a service to an application, we need an actual application. So we implement a second (empty) .NET Core application.

We now have two applications: the service broker and a client application that we can bind to called rwwilden-client.

Updating the catalog

In the first post we introduced a service catalog that advertised the rwwilden service. We chose to make the service not-bindable because at that time, service binding was not implemented. When we try to bind the service anyway, an error occurs:

So we need to update the catalog to advertise a bindable service:

src/broker/Lib/CatalogService.cs view raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
private static readonly Task<Catalog> CatalogTask = Task.FromResult(
    new Catalog
    {
        Services =
        {
            // https://github.com/openservicebrokerapi/servicebroker/blob/v2.14/spec.md#service-object
            new Service
            {
                Id = ServiceId,
                Name = "rwwilden",
                Description = "The magnificent and well-known rwwilden service",

                // This service broker now has support for service binding so we will set this property to true.
                Bindable = true,
                BindingsRetrievable = false,

                // This service broker will be used to provision instances so fetching them should also be supported.
                InstancesRetrievable = true,

                // No support yet for service plan updates.
                PlanUpdateable = false,

                Metadata = ServiceMetadata,

                Plans =
                {
                    new Plan
                    {
                        Id = BasicPlanId,
                        Name = "basic",
                        Description = "Basic plan",
                        Bindable = true,
                        Free = true,
                        Metadata = BasicPlanMetadata
                    }
                }
            }
        }
    });

public Task<Catalog> GetCatalogAsync() => CatalogTask;

The only changes are at lines 14 and 32 where we set Bindable to true. Note that just setting Bindable to true at the plan level would also have been enough. Lower-level settings override higher-level ones.

Bind and unbind

Next step is to implement binding and unbinding. There are 4 different types of binding defined by the OSBAPI spec: credentials, log drain, route service and volume service. For this post we will implement the most common one: credentials. Since our service broker does not have an actual backing service, this is quite simple. In real life, you might have a MySQL service broker that provisions a database during bind and returns a connection string that allows your application to access the database.

The OSBAPI server library I used in the previous post provides hooks for implementing blocking (un)binding in the form of the IServiceBindingBlocking interface so we just need to implement the BindAsync and UnbindAsync methods:

src/broker/Lib/ServiceBindingBlocking.cs view raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public Task<ServiceBinding> BindAsync(ServiceBindingContext context, ServiceBindingRequest request)
{
    LogContext(_log, "Bind", context);
    LogRequest(_log, request);

    return Task.FromResult(new ServiceBinding
    {
        Credentials = JObject.FromObject(new
        {
            connectionString = "<very secret connection string>"
        })
    });
}

public Task UnbindAsync(ServiceBindingContext context, string serviceId, string planId)
{
    LogContext(_log, "Unbind", context);
    _log.LogInformation($"Deprovision: {{ service_id = {serviceId}, planId = {planId} }}");

    return Task.CompletedTask;
}

As you can see, our bind implementation simply returns a JObject with a very secret connection string.

The final change to our code is to register the IServiceBindingBlocking implementation with the DI container (line 4):

src/broker/Startup.cs view raw
1
2
3
4
5
services
    .AddTransient<ICatalogService, CatalogService>()
    .AddTransient<IServiceInstanceBlocking, ServiceInstanceBlocking>()
    .AddTransient<IServiceBindingBlocking, ServiceBindingBlocking>()
    .AddOpenServiceBroker();

Updating the service broker

When we push the new service broker application, the platform (PCF) does not yet know that the service broker has changed. So when we try to bind a service to an application, this still fails with the error: the service instance doesn’t support binding. To fix this, we can update the service broker using cf update-service-broker:

Binding and unbinding the service

With an updated service broker in place that supports binding we have finally reached the goal of this post: binding to and unbinding from the my-rwwilden service:

With the first command we bind the rwwilden-client application to the my-rwwilden service and give the binding a name: client-to-service-binding-rwwilden.

With the second command, cf env rwwilden-client, we check whether the credentials that the service broker provides when binding, are actually injected into the rwwilden-client application environment. And alas, there is our ‘very secret connection string’.

Conclusion

In the first post we implemented a service broker with a service catalog and (de)provisioning of a service. In this post we actually bound the service we created to an application and saw that the credentials the service broker returned when binding were injected into the application environment.

Both service provisioning and binding were implemented as blocking operations, which makes sense in the context of our in-memory service broker. However, often a service broker implements operations that take time, like provisioning a database server. In the next post we will look at how to implement a non-blocking version of service provisioning.