Develop Cinder storage driver charm

Important

This page has been identified as being affected by the breaking changes introduced between versions 2.9.x and 3.x of the Juju client. Read support note Breaking changes between Juju 2.9.x and 3.x before continuing.

The example below will walk through the creation of a basic Cinder storage subordinate charm for the Openstack Cinder NFS backend. The charm will use existing operator framework helpers. Once the charm is written it will be composed using charmcraft.

Before writing a new charm the charm author needs to have a clear idea of what files and services the charm is going to manage and what dependencies will be needed for it to perform its designated task.

The Cinder NFS charm created in this example requires an NFS host and export.

Create the skeleton charm

Prerequisites

The charmcraft tool is needed to build and publish the new charm, and a virtualenv is used for charm creation.

sudo snap install charmcraft
sudo apt install python3.9-venv tox
venv/bin/pip3 install cookiecutter

Create the charm

The OpenStack Charms team has prepared a cookiecutter template to help create a new Cinder driver charm. Running the cookiecutter will prompt the user to answer some questions about the charm and does include defaults.

venv/bin/cookiecutter https://github.com/openstack-charmers/cinder-storage-backend-template
driver_name [MyDriver]: NFS
driver_name_lc [nfs]:
release [queens]: xena
additional_package_name []:
Select driver_install_method:
1 - None
2 - PPA
3 - Juju Resource DEB
Choose from 1, 2, 3 [1]: 1
Initialized empty Git repository in /home/chris/code/canonical/cinder-nfs/.git/
[main (root-commit) 88a54ad] Initial Cookiecutter Commit.
18 files changed, 573 insertions(+)
create mode 100644 .gitignore
create mode 100644 .stestr.conf
create mode 100644 README.md
create mode 100644 build-requirements.txt
create mode 100644 config.yaml
create mode 100644 metadata.yaml
create mode 100755 pip.sh
create mode 100644 requirements.txt
create mode 100755 src/charm.py
create mode 100644 src/test-requirements.txt
create mode 100644 src/tox.ini
create mode 100644 test-requirements.txt
create mode 100644 tests/bundles/bionic-xena.yaml
create mode 100644 tests/tests.yaml
create mode 100644 tests/tests_cinder_nfs.py
create mode 100644 tox.ini
create mode 100644 unit_tests/__init__.py
create mode 100644 unit_tests/test_cinder_nfs_charm.py

It’s important to verify that the versions match what you expect. For example, in the above output we can see that tests/bundles/bionic-xena.yaml was generated but we’d expect tests/bundles/focal-xena.yaml so we’ll need to update that file name as well as its content.

mv tests/bundles/bionic-xena.yaml tests/bundles/focal-xena.yaml
sed -i 's/bionic/focal/g' tests/bundles/focal-xena.yaml

Build the charm

The charm now needs to be built to pull down all the dependencies and rolled into the built charm which can then be deployed.

cd ~/cinder-nfs
tox -e build

Deploy the charm

juju deploy --channel yoga/edge ch:cinder
juju deploy ./cinder-nfs.charm
juju integrate cinder:storage-backend cinder-nfs:storage-backend

juju status will now show both charms deployed. The cinder status will show some missing relations but that’s not an issue for this demonstration.

Update Cinder configuration

During the initial deploy of this charm, the standard openstack.core default installer will install the packages specified in the class CharmName.PACKAGES, but it will not do any other configuration. In order to update cinder.conf in the cinder principal charm, this NFS subordinate charm will need to access the storage-backend relation, which will allow it to send configuration information to the cinder principal charm for inclusion in cinder.conf on the co-located machine.

In the generated charm, we can see that there’s already the minimal code to send data over the storage-backend relation in src/charm.py:

def cinder_configuration(self, config):
    # Return the configuration to be set by the principal.
    volume_driver = ''
    options = [
        ('volume_driver', volume_driver)
    ]
    return options

What this is doing is sending structured data over the relation that the cinder charm knows how to convert into native Cinder configuration. To support the NFS backend, we’ll need to modify the config.yaml file and cinder_configuration function.

options:
    nfs_host:
        default:
        description: IP or Hostname for NFS server
        type: string
    nfs_path:
        default:
        description: Path to the NFS export
        type: string
def cinder_configuration(self, config):
    # Return the configuration to be set by the principal.
    volume_driver = 'cinder.volume.drivers.nfs.NfsDriver'
    options = [
        ('volume_driver', volume_driver),
        ('nas_host', config['nfs_host']),
        ('nas_share_path', config['nfs_path']),
    ]
    return options

This tells the charm to send that configuration to the principal charm where the storage-backend relation changes. Then repeat the steps in the Build the charm section.

Upgrade to new charm

To test the updates, upgrade the running charm to the one that was just built.

juju upgrade-charm --path ./cinder-nfs.charm

Verify the update

We can confirm that the update has been applied by checking for the driver in the cinder.conf file:

juju exec --unit cinder/0 "grep NfsDriver /etc/cinder/cinder.conf"
volume_driver = cinder.volume.drivers.nfs.NfsDriver