Building and Testing Your Driver

This document will show you how to implement a Flocker storage driver. Your driver will be a Python 2.7 library providing a class implementing the flocker.node.agents.blockdevice.IBlockDeviceAPI interface.

The best way to build your driver is to model it on the canonical implementations provided by the ClusterHQ team. These drivers include:

After you have implemented the driver, you will need to test your implementation, and ClusterHQ provide a number of test suites to help you do this. These tests are the bare minimum required to accept the driver. Other tests may be required that are specific to your design choices and driver implementation that cannot be covered by the test suites provided by ClusterHQ. We highly encourage driver developers to write additional tests for these cases before your driver is ready to be deployed with Flocker in a customer environment.

Before beginning the testing steps below, you can review our FAQ for common issues encountered during driver development and testing.

Testing Your Driver

  1. Implement minimal functional tests:

    To test that your implementation is correct you should instantiate a generic test suite that makes sure your class correctly implements the interface:

    from uuid import uuid4
    from flocker.node.agents.testtools import (
        get_blockdeviceapi_with_cleanup,
        make_iblockdeviceapi_tests,
    )
    
    def api_factory(test):
        # Return an instance of your IBlockDeviceAPI implementation class,
        # given a twisted.trial.unittest.TestCase instance or use
        # ``get_blockdeviceapi_with_cleanup`` which will also take care of
        # cleaning up any volumes that are created in your tests.
        return get_blockdeviceapi_with_cleanup(test)
    
    # Smallest volume to create in tests, e.g. 1GiB:
    MIN_ALLOCATION_SIZE = 1024 * 1024 * 1024
    
    # Minimal unit of volume allocation, e.g. 1MiB:
    MIN_ALLOCATION_UNIT = 1024 * 1024
    
    class YourStorageTests(make_iblockdeviceapi_tests(
        api_factory, MIN_ALLOCATION_SIZE, MIN_ALLOCATION_UNIT,
        # Factory for valid but unknown volume id specific to your backend:
        lambda test: unicode(uuid4()))):
        """
        Tests for your storage.
        """
    

    If you wish the tests to cleanup volumes after each run, please provide a cleanup version of IBlockDeviceAPI. For example, see flocker.node.agents.testtools.get_blockdeviceapi_with_cleanup.

    You can run these tests with the trial test runner, provided by Twisted, one of Flocker’s dependencies:

    FLOCKER_FUNCTIONAL_TEST=TRUE \
    FLOCKER_FUNCTIONAL_TEST_CLOUD_CONFIG_FILE=/path/to/agent.yml \
    FLOCKER_FUNCTIONAL_TEST_CLOUD_CONFIG_SECTION=dataset \
    trial yourstorage.test_yourstorage
    
  2. Additional functional tests:

    You are encouraged to write additional functional tests to cover logic specific to your driver implementation. For example, here are some EBS driver-specific tests written by ClusterHQ.

  3. Run acceptance tests:

    After all functional tests pass, please run acceptance tests according to our Acceptance Testing documentation. Make sure you configure the acceptance test environment to use your new backend.

  4. Setup a Continuous Integration environment for tests.

    After your acceptance tests pass, we recommend you set up a CI environment for functional and acceptance tests for your driver.

Enabling Flocker Users to Install Your Storage Driver

Once you’ve implemented your storage backend you’ll want to allow Flocker users to use your package. The basic implementation strategy is that your user installs a Python package with your backend implementation on all Flocker nodes:

/opt/flocker/bin/pip install https://example.com/your/storageplugin-1.0.tar.gz

You can also provide RPMs or DEBs that have same effect of installing a new Python package.

Once your users have installed the package, you should instruct your users to write an agent.yml file (/etc/flocker/agent.yml), whose backend key in the dataset section is the importable name of the Python package you’ve installed.

All other sub-keys of the dataset section will be passed to a function you must implement (see below), and can be used to configure the resulting IBlockDeviceAPI instance.

Typical parameters are authentication information or server addresses; whatever is necessary to configure your class.

For example, if you installed a Python package which is importable as mystorage_flocker_plugin, and you require a username and password in order to log in to your storage system, you could tell your users to write a agent.yml that looks like this:

version: 1
  control-service:
    hostname: "user.controlserver.example.com"
  dataset:
    backend: "mystorage_flocker_plugin"
    username: "username_for_mystorage"
    password: "abc123"

Your mystorage_flocker_plugin/__init__.py module needs to have a FLOCKER_BACKEND attribute with a flocker.node.BackendDescription instance, which will include a reference to factory function that constructs a IBlockDeviceAPI instance.

The factory function will be called with whatever parameters the dataset section in agent.yml is configured with. In the above example, that would be username and password.

Here’s what the module could look like:

from flocker.node import BackendDescription, DeployerType
from mystorage_flocker_plugin._backend import MyStorageAPI

def api_factory(cluster_id, **kwargs):
    return MyStorageAPI(cluster_id=cluster_id, username=kwargs[u"username"],
                        password=kwargs[u"password"])

FLOCKER_BACKEND = BackendDescription(
    name=u"mystorage_flocker_plugin",
    needs_reactor=False, needs_cluster_id=True,
    api_factory=api_factory,
    required_config={u"username", u"password"},
    deployer_type=DeployerType.block)

The required_config set in a BackendDescription is an optional set of configuration keys that must be present in your backend’s agent.yml for your driver to successfully initialize. If you specify required_config, the dataset agent will validate that all of these keys are present in the user’s dataset configuration when starting. The specified keys must be a set of unicode objects.

The cluster_id parameter is a Python uuid.UUID instance uniquely identifying the cluster. This is useful if you want to build a system that supports multiple Flocker clusters talking to a shared storage backend.

Make sure that your factory function raises an exception if it is given incorrect or insufficient parameters, so that users can easily see when they have misconfigured your backend.

Publishing Your Driver

Once your CI tests are running and passing successfully, you are ready to publish your driver and assert that it is certified to work with Flocker.

Completed drivers should be published as open source, publicly available source code, e.g. a Public repository on GitHub.

Please include the Apache 2.0 License as part of the repository. For example, see the Flocker License .

Certifying Your Driver

To demonstrate that your driver passes all tests, we recommend you include a Build Status badge at the top of the README on your driver’s GitHub repository.

Examples of status images include Travis CI and Jenkins.

You should also clearly indicate which version of Flocker your driver has been certified against.

What’s Next?

We recommend a demo to show off your hard work!

After driver development clears all tests and you’ve published getting-started instructions for your users, we recommend a video which you can use to share with others how they can build a Dockerized application using your storage backend.