Creating a Sample Service Broker for Cloud Foundry with Python’s Flask

Follow the instructions to build a custom service broker using the Flask framework and run it on Cloud Foundry. Learn how to define a service catalog, register instances, add API version check and authentication, etc.

Two ways of provisioning services

The Cloud Foundry PaaS sets the scene for not just running cloud-native apps, but also for provisioning services these apps depend on. Services like databases, storage, or queues can be provisioned to applications on demand. So, whenever an app requires access to a service, there are two options:

  1. Connect it to a user-provided service instance
  2. Connect it to a managed service via the marketplace

User-provided service instances enable developers to use services that are not available in the marketplace with their applications running on Cloud Foundry. Managed services, in their turn, are integrated with Cloud Foundry via APIs, provisioning reserved resources and credentials to end users on demand.

There are quite a number of service brokers for well-known services available in the marketplace, including MySQL, MongoDB, or Redis. However, what do you do when there is no service broker for the service you need? You can create it.

 

What’s needed to create?

This diagram demonstrates the process of creating a service broker:

custom-service-broker-for-cloud-foundry-v1

In the Service Broker API documentation, a service broker is defined as “the component of the service that implements the Service Broker API, for which a platform’s marketplace is a client. Service brokers are responsible for advertising a catalog of service offerings and service plans to the marketplace, and acting on requests from the marketplace for provisioning, binding, unbinding, and deprovisioning.”

“The Service Broker API defines an HTTP interface between the services marketplace of a platform and service brokers.” So, what we need to create is an HTTP service.

As an example, we are going to create a service broker app using Python’s Flask framework, which is easy to read and translate to the preferable programming language.

 

Prerequisites

To create a managed service for Cloud Foundry:

  • We’ll need a Cloud Foundry instance to test the service broker.
  • We must have admin access to this Cloud Foundry instance to manage service brokers and the services marketplace catalog.

In this post, we target Service Broker API v2.11.

 

Defining the service and the service plan

The first thing to do is define the service and its service plan in terms of code. So, we need to advertise at least one service with at least one service plan. In the simplest case, it looks like this:

plan_one = {
    "id": "plan_one",
    "name": "plan_one",
    "description": "Simple free plan",
    "free": True
}

my_service = {
    'id': 'example_service',
    'name': 'example_service',
    'description': 'Simple service example',
    'bindable': True,
    'plans': [plan_one]
}

 

Defining the service catalog

Defining the first endpoint—the service catalog—in our case looks like this:

my_services = {"services": [my_service]}

@app.route('/v2/catalog')
def catalog():
    return jsonify(my_services)

The final code of the service broker app:

import os
from flask import Flask, jsonify

app = Flask(__name__)

log = app.logger

# Service plans
plan_one = {
    "id": "plan_one",
    "name": "plan_one",
    "description": "Simple free plan",
    "free": True
}

# Services
my_service = {
    'id': 'example_service',
    'name': 'example_service',
    'description': 'Simple service example',
    'bindable': True,
    'plans': [plan_one]
}

my_services = {"services": [my_service]}

@app.route('/v2/catalog')
def catalog():
    return jsonify(my_services)

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=int(os.getenv('VCAP_APP_PORT', '5000')))

That’s it! The code is ready to run and advertise our example service (example_service) with one service plan (plan_one). It can’t perform anything yet, but it is a bare minimum of the code that can be accepted by Cloud Foundry.

 

Running the service broker app on CF

In this example, the code is run on Cloud Foundry, which is, nevertheless, not imperative. In fact, the service broker is just a component of the service. We run the service broker app on Cloud Foundry by either typing the code, or checking it out from our GitHub:

git clone https://github.com/Altoros/simple-service-broker-for-cf.git
cd simple-service-broker-for-cf
git checkout first
cf push

 

Registering the app as a service broker

Now, we have to register the application as a service broker with Cloud Foundry. Let’s check what service brokers we already have available. In our case, there is none:

cloud-foundry-service-broker-not-found-create-one

Let’s create a Cloud Foundry service broker:

creating-a-simple-service-broker-for-cloud-foundry_v2

Note that we are using BOSH Lite for this example. If you are using a full-scale Cloud Foundry deployment, your application domain may differ.

Let’s check what we’ve got now:

registering-an-app-as-a-cloud-foundry-service-broker-bosh-lite_v2

The next step is to make the service accessible by org/space:

registering-the-app-as-a-cloud-foundry-service-broker-make-it-accessible-by-org-space

 

Registering service instances

If we now try registering a service instance within that service, we will fail:

simple-service-broker-for-cloud-foundry_v2

It happens because we haven’t provided support for this action in the code yet. So, let’s extend the code to support instances:

@app.route('/v2/service_instances/<instance_id>', methods=['PUT', 'DELETE',
           'PATCH'])
def service_instances(instance_id):
    if request.method == 'PUT':
        return make_response(jsonify({}), 201)
    else:
        return jsonify({})

Adding this endpoint allows us to define the actual logic for creating, updating, and deleting service instances. We can type this code, or check it out from Git:

git checkout service

Having added this endpoint, we now need to restart (and, since running on Cloud Foundry, restage) the instance of the service broker:

cf push

At this point, we are ready to create an instance of the service (actually, nothing is going to happen in our case, but the broker will respond with the 201 status code, making Cloud Foundry believe that the broker has successfully created an instance of the service).

Let’s create an instance of example_service with the service plan plan_one, where si1 is the name of the instance:

create-an-instance-of-example-service-si1

 

Adding API version check

According to the Service Broker API documentation, “requests from the platform to the service broker must contain a header that declares the version number of the Service Broker API that the marketplace will use X-Broker-Api-Version: 2.11.”

So, let’s enable the broker to check the API version number.

X_BROKER_API_MAJOR_VERSION = 2
X_BROKER_API_MINOR_VERSION = 10
X_BROKER_API_VERSION_NAME = 'X-Broker-Api-Version'

def api_version_is_valid(api_version):
    version_data = api_version.split('.')
    result = True
    if (float(version_data[0]) < X_BROKER_API_MAJOR_VERSION or
       (float(version_data[0]) == X_BROKER_API_MAJOR_VERSION and
       float(version_data[1]) < X_BROKER_API_MINOR_VERSION)):
                result = False
    return result

def requires_api_version(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        api_version = request.headers.get('X-Broker-Api-Version')
        if (not api_version or not (api_version_is_valid(api_version))):
            abort(412)
        return f(*args, **kwargs)
    return decorated

@app.errorhandler(412)
def version_mismatch(error):
    return 'Version mismatch. Expected: {}: {}.{}'.format(
        X_BROKER_API_VERSION_NAME,
        X_BROKER_API_MAJOR_VERSION,
        X_BROKER_API_MINOR_VERSION), 412

Now, we can style the endpoints:

@app.route('/v2/catalog')
@requires_api_version
def catalog():
....

The code can either be typed or checked out from Git:

git checkout api
cf push

 

Adding authentication

According to the Service Broker API documentation, “the marketplace must authenticate with the service broker using HTTP basic authentication (the Authorization: header) on every request. The broker is responsible for validating the username and password and returning a 401 Unauthorized message if credentials are invalid. It is recommended that brokers support secure communication from platform marketplaces over TLS.”

So, let’s make our broker a little more secure:

def check_auth(username, password):
    """This function is called to check if a username /
    password combination is valid.
    """
    if not (username == 'alex' and password == 'bigsecret'):
        log.warning('Authentication failed')
    return username == 'alex' and password == 'bigsecret'

def authenticate():
    """Sends a 401 response that enables basic auth"""
    return Response('Could not verify your access level for that URL.\n'
                    'You have to login with proper credentials', 401,
                    {'WWW-Authenticate': 'Basic realm="Login Required"'})

def requires_auth(f):
    """Cloud Controller (final release v145+) authenticates with the Broker
    using HTTP basic authentication (the Authorization: header) on every
    request and will reject any broker registrations that do not contain a
    username and password. The broker is responsible for checking the username
    and password and returning a 401 Unauthorized message if credentials are
    invalid.
    Cloud Controller supports connecting to a broker using SSL if additional
    security is desired."""
    @wraps(f)
    def decorated(*args, **kwargs):
        auth = request.authorization
        if not auth or not check_auth(auth.username, auth.password):
            return authenticate()
        return f(*args, **kwargs)
    return decorated

Now, we can style the endpoints:

@app.route('/v2/catalog')
@requires_auth
@requires_api_version
def catalog():
....

We can either type this code, or check it out from Git:

git checkout auth
cf push

To make sure it works as expected, we’ll delete all service instances and the service broker itself:

cf delete-service si1
cf disable-service-access example_service
cf delete-service-broker sb1

Now, if we try registering the service broker the way we did before, we will fail:

registering-service-instances-failed_v2

Registration fails because the credentials for this service broker have been changed. Let’s try using new credentials:

adding-authentication-for-a-cloud-foundry-service-broker-v12

This is quite a simple example of creating a service broker. However, it is good enough to get an idea of how it works. Now, you can move on adding more functionality.

You can access the source code of the developed service broker here.

 

Related reading


This blog post was written by Alexey Zakharov and Aliaksandr Prysmakou.