Manila-infinidat charm

- basic functionality and configuration
- unit-tests
This commit is contained in:
Nikolay Vinogradov 2022-10-15 03:33:56 +03:00
parent 6c45de0fbe
commit c8f1cd90bd
5 changed files with 224 additions and 70 deletions

View File

@ -1,17 +1,17 @@
infinidat Storage Backend for Cinder Infinidat Storage Backend for Manila
------------------------------- ------------------------------------
Overview Overview
======== ========
This charm provides a infinidat storage backend for use with the Cinder This charm provides Infinidat storage backend for use with the Manila
charm. charm.
To use: To use:
juju deploy cinder juju deploy manila
juju deploy manila-infinidat juju deploy manila-infinidat
juju add-relation manila-infinidat cinder juju add-relation manila-infinidat manila
Configuration Configuration
============= =============

View File

@ -11,16 +11,19 @@ options:
description: > description: >
The status of service-affecting packages will be set to this The status of service-affecting packages will be set to this
value in the dpkg database. Valid values are "install" and "hold". value in the dpkg database. Valid values are "install" and "hold".
source: install_sources:
description: > description: |
List of extra apt sources, per charm-helpers standard Optional configuration to support use of additional sources such as:
format (a yaml list of strings encoded as a string). Each source - ppa:myteam/ppa
may be either a line that can be added directly to - cloud:trusty-proposed/kilo
sources.list(5), or in the form ppa:<user>/<ppa-name> for adding - http://my.archive.com/ubuntu main
Personal Package Archives, or a distribution component to enable. The last option should be used in conjunction with the key configuration
option. See https://repo.infinidat.com/home/main-stable for details.
The charm also supports templating of the distribution codename via
automatic expansion of {distrib_codename} depending on the host system
default: "deb https://repo.infinidat.com/packages/main-stable/apt/linux-ubuntu {distrib_codename} main"
type: string type: string
default: "" install_keys:
key:
description: > description: >
List of signing keys for install_sources package sources, per List of signing keys for install_sources package sources, per
charmhelpers standard format (a yaml list of strings encoded as charmhelpers standard format (a yaml list of strings encoded as
@ -32,7 +35,25 @@ options:
sources where the package signing key is securely retrieved from sources where the package signing key is securely retrieved from
Launchpad. Launchpad.
type: string type: string
default: "" default: |
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v1.4.11 (GNU/Linux)
mQENBFESDRIBCADMR7MQMbH4GdCQqfrOMt35MhBwwH4wv9kb1WRSTxa0CmuzYaBB
1nJ0nLaMAwHsEr9CytPWDpMngm/3nt+4F2hJcsOEkQkqeJ31gScJewM+AOUV3DEl
qOeXXYLcP+jUY6pPjlZpOw0p7moUQPXHn+7amVrk7cXGQ8O3B+5a5wjN86LT2hlX
DlBlV5bX/DYluiPUbvQLOknmwO53KpaeDeZc4a8iIOCYWu2ntuAMddBkTps0El5n
JJZMTf6os2ZzngWMZRMDiVJgqVRi2b+8SgFQlQy0cAmne/mpgPrRq0ZMX3DokGG5
hnIg1mF82laTxd+9qtiOxupzJqf8mncQHdaTABEBAAG0IWFwcF9yZXBvIChDb21t
ZW50KSA8bm9AZW1haWwuY29tPokBOAQTAQIAIgUCURINEgIbLwYLCQgHAwIGFQgC
CQoLBBYCAwECHgECF4AACgkQem2D/j05RYSrcggAsCc4KppV/SZX5XI/CWFXIAXw
+HaNsh2EwYKf9DhtoGbTOuwePvrPGcgFYM3Tu+m+rziPnnFl0bs0xwQyNEVQ9yDw
t465pSgmXwEHbBkoISV1e4WYtZAsnTNne9ieJ49Ob/WY4w3AkdPRK/41UP5Ct6lR
HHRXrSWJYHVq5Rh6BakRuMJyJLz/KvcJAaPkA4U6VrPD7PFtSecMTaONPjGCcomq
b7q84G5ZfeJWb742PWBTS8fJdC+Jd4y5fFdJS9fQwIo52Ff9In2QBpJt5Wdc02SI
fvQnuh37D2P8OcIfMxMfoFXpAMWjrMYc5veyQY1GXD/EOkfjjLne6qWPLfNojA==
=w5Os
-----END PGP PUBLIC KEY BLOCK-----
debug: debug:
default: !!bool false default: !!bool false
type: boolean type: boolean
@ -65,8 +86,18 @@ options:
thin-provision: thin-provision:
type: boolean type: boolean
description: Choose whether to thin provision description: Choose whether to thin provision
default: !!bool true default: !!bool "true"
share-backend-name: share-backend-name:
type: string type: string
description: Manila backend name description: Manila backend name
default: __app__ default: __app__
use-ssl:
type: boolean
description: >
Configures SSL support for Infinidat management API
default: !!bool "false"
suppress-ssl-warnings:
type: boolean
description: >
Configures SSL warnings suppression
default: !!bool "false"

View File

@ -15,9 +15,7 @@
# limitations under the License. # limitations under the License.
from typing import OrderedDict from typing import OrderedDict
import ops_openstack.adapters from ops_openstack.core import OSBaseCharm
from ops_openstack.plugins.classes import CinderStoragePluginCharm
from ops_openstack.core import charm_class, get_charm_class_for_release, OSBaseCharm
from ops.main import main from ops.main import main
from ops.model import ( from ops.model import (
@ -45,15 +43,21 @@ import logging
RELATION_NAME = 'manila-plugin' RELATION_NAME = 'manila-plugin'
class ManilaInfinidatPluginCharm(OSBaseCharm): class ManilaInfinidatPluginCharm(OSBaseCharm):
PACKAGES = ['infinisdk', 'infinishell'] PACKAGES = ['python3-infinisdk', 'infinishell']
REQUIRED_RELATIONS = [RELATION_NAME] REQUIRED_RELATIONS = [RELATION_NAME]
SHARE_DRIVER = 'manila.share.drivers.infinidat.infinibox.InfiniboxShareDriver' SHARE_DRIVER = \
'manila.share.drivers.infinidat.infinibox.InfiniboxShareDriver'
active_active = True MANDATORY_CONFIG = ['infinibox-ip', 'infinibox-login',
'infinibox-password', 'pool-name']
DEFAULT_REPO_BASEURL = \
'https://repo.infinidat.com/packages/main-stable/apt/linux-ubuntu'
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@ -69,14 +73,16 @@ class ManilaInfinidatPluginCharm(OSBaseCharm):
def on_config(self, event): def on_config(self, event):
self.on_manila_plugin(event) self.on_manila_plugin(event)
self.set_started(started=True) self.set_started(started=True)
if self.framework.model.relations.get(RELATION_NAME):
self.send_backend_config()
self.unit.status = ActiveStatus('Unit is ready') self.unit.status = ActiveStatus('Unit is ready')
def on_manila_plugin(self, event): def send_backend_config(self):
logging.error("Relation changed")
config = dict(self.framework.model.config) config = dict(self.framework.model.config)
manila_backends = self.manila_configuration(config) manila_backends = self.manila_configuration(config)
for relation in self.framework.model.relations.get(RELATION_NAME): for relation in self.framework.model.relations.get(RELATION_NAME):
relation.data[self.unit]['_name'] = ','.join(manila_backends.keys()) relation.data[self.unit]['_name'] = \
','.join(manila_backends.keys())
rendered_config = render( rendered_config = render(
source="parts/backends", source="parts/backends",
template_loader=os_templating.get_loader( template_loader=os_templating.get_loader(
@ -90,6 +96,9 @@ class ManilaInfinidatPluginCharm(OSBaseCharm):
} }
}) })
def on_manila_plugin(self, event):
self.send_backend_config()
def manila_configuration(self, config): def manila_configuration(self, config):
""" """
See https://docs.openstack.org/manila/latest/configuration/shared-file-systems/drivers/infinidat-share-driver.html # noqa: E501 See https://docs.openstack.org/manila/latest/configuration/shared-file-systems/drivers/infinidat-share-driver.html # noqa: E501
@ -103,6 +112,7 @@ class ManilaInfinidatPluginCharm(OSBaseCharm):
# backend name selection logic # backend name selection logic
if config.get('nas-network-space-name'): if config.get('nas-network-space-name'):
backend_names = filter( backend_names = filter(
None,
map( map(
str.strip, str.strip,
config.get('nas-network-space-name').split(',') config.get('nas-network-space-name').split(',')
@ -115,66 +125,60 @@ class ManilaInfinidatPluginCharm(OSBaseCharm):
for backend_name in backend_names: for backend_name in backend_names:
backends[backend_name] = ( backends[backend_name] = [
('share_driver', self.SHARE_DRIVER), ('share_driver', self.SHARE_DRIVER),
('share_backend_name', backend_name), ('share_backend_name', backend_name),
('driver_handles_share_servers', 'false'),
('infinidat_nas_network_space_name', backend_name),
('infinidat_thin_provision', config.get('thin-provision')),
('infinidat_use_ssl', config.get('use-ssl', False)),
('infinidat_suppress_ssl_warnings',
config.get('suppress-ssl-warnings', True)),
]
if config.get('infinibox-ip') and config.get('infinibox-login') \
and config.get('infinibox-password'):
backends[backend_name].extend([
('infinibox_hostname', config.get('infinibox-ip')), ('infinibox_hostname', config.get('infinibox-ip')),
('infinibox_login', config.get('infinibox-login')), ('infinibox_login', config.get('infinibox-login')),
('infinibox_password', config.get('infinibox-password')), ('infinibox_password', config.get('infinibox-password')),
])
('infinidat_nas_network_space_name', backend_name), #TODO: mapping to nas-network-space-name if config.get('pool-name'):
backends[backend_name].append(
('infinidat_pool_name', config.get('pool-name')), ('infinidat_pool_name', config.get('pool-name'))
('infinidat_thin_provision', config.get('thin-provision')), ),
)
if not backends: if not backends:
self.unit.status = BlockedStatus("No backends configured") self.unit.status = BlockedStatus("No backends configured")
return backends return backends
@property
def stateless(self):
"""Indicate whether the charm is stateless.
For more information, see: https://cinderlib.readthedocs.io/en/v0.2.1/topics/serialization.html
:returns: A boolean value indicating statefulness.
:rtype: bool
""" # noqa
return False
@property
def active_active(self):
"""Indicate active-active support in the charm.
For more information, see: https://specs.openstack.org/openstack/cinder-specs/specs/mitaka/cinder-volume-active-active-support.html
:returns: A boolean indicating active-active support.
:rtype: bool
""" # noqa
return False
def install_pkgs(self): def install_pkgs(self):
logging.info("Installing packages") logging.info("Installing packages")
# value of 'source' param is defined like this: # we implement $codename expansion here
# deb https://repo.infinidat.com/packages/main-stable/apt/linux-ubuntu $codename main # see the default value for 'source' in config.yaml
if self.model.config.get('source'): if self.model.config.get('install_sources'):
distrib_codename = lsb_release()['DISTRIB_CODENAME'].lower()
add_source( add_source(
self.model.config['source'] \ self.model.config['install_sources']
.replace('$codename', .format(distrib_codename=distrib_codename),
lsb_release()['DISTRIB_CODENAME'].lower()), self.model.config.get('install_keys'))
self.model.config.get('key'))
apt_update(fatal=True) apt_update(fatal=True)
apt_install(self.PACKAGES, fatal=True) apt_install(self.PACKAGES, fatal=True)
self.update_status() self.update_status()
def on_install(self, event): def on_install(self, event):
self.install_pkgs() self.install_pkgs()
self.update_status() self.update_status()
if __name__ == '__main__': if __name__ == '__main__':
main(ManilaInfinidatPluginCharm) main(ManilaInfinidatPluginCharm)

View File

@ -1,6 +1,6 @@
{% for backend_name in backends %} {% for backend_name in backends %}
[{{ backend_name }}] [{{ backend_name }}]
{% for key, value in backends[backend_name] -%} {% for key, value in backends[backend_name] %}
{{ key }} = {{ value }} {{ key }} = {{ value }}
{%- endfor %} {%- endfor %}
{% endfor %} {% endfor %}

View File

@ -13,17 +13,136 @@
# limitations under the License. # limitations under the License.
import unittest import unittest
from src.charm import CinderCharmBase from src.charm import ManilaInfinidatPluginCharm
from ops.model import ActiveStatus
from ops.testing import Harness from ops.testing import Harness
from unittest import mock
class TestManilainfinidatCharm(unittest.TestCase): from charmhelpers.core.host_factory.ubuntu import UBUNTU_RELEASES
SOURCE = "deb https://repo.infinidat.com/packages/main-stable/apt/linux-ubuntu focal main" # noqa: E501
KEY = """\
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v1.4.11 (GNU/Linux)
mQENBFESDRIBCADMR7MQMbH4GdCQqfrOMt35MhBwwH4wv9kb1WRSTxa0CmuzYaBB
1nJ0nLaMAwHsEr9CytPWDpMngm/3nt+4F2hJcsOEkQkqeJ31gScJewM+AOUV3DEl
qOeXXYLcP+jUY6pPjlZpOw0p7moUQPXHn+7amVrk7cXGQ8O3B+5a5wjN86LT2hlX
DlBlV5bX/DYluiPUbvQLOknmwO53KpaeDeZc4a8iIOCYWu2ntuAMddBkTps0El5n
JJZMTf6os2ZzngWMZRMDiVJgqVRi2b+8SgFQlQy0cAmne/mpgPrRq0ZMX3DokGG5
hnIg1mF82laTxd+9qtiOxupzJqf8mncQHdaTABEBAAG0IWFwcF9yZXBvIChDb21t
ZW50KSA8bm9AZW1haWwuY29tPokBOAQTAQIAIgUCURINEgIbLwYLCQgHAwIGFQgC
CQoLBBYCAwECHgECF4AACgkQem2D/j05RYSrcggAsCc4KppV/SZX5XI/CWFXIAXw
+HaNsh2EwYKf9DhtoGbTOuwePvrPGcgFYM3Tu+m+rziPnnFl0bs0xwQyNEVQ9yDw
t465pSgmXwEHbBkoISV1e4WYtZAsnTNne9ieJ49Ob/WY4w3AkdPRK/41UP5Ct6lR
HHRXrSWJYHVq5Rh6BakRuMJyJLz/KvcJAaPkA4U6VrPD7PFtSecMTaONPjGCcomq
b7q84G5ZfeJWb742PWBTS8fJdC+Jd4y5fFdJS9fQwIo52Ff9In2QBpJt5Wdc02SI
fvQnuh37D2P8OcIfMxMfoFXpAMWjrMYc5veyQY1GXD/EOkfjjLne6qWPLfNojA==
=w5Os
-----END PGP PUBLIC KEY BLOCK-----
"""
class TestManilaInfinidatCharm(unittest.TestCase):
def setUp(self): def setUp(self):
self.harness = Harness(CinderCharmBase) self.harness = Harness(ManilaInfinidatPluginCharm)
self.addCleanup(self.harness.cleanup) self.addCleanup(self.harness.cleanup)
self.harness.begin() self.harness.begin()
self.harness.set_leader(True) self.harness.set_leader(True)
backend = self.harness.add_relation('manila-plugin', 'manila') backend = self.harness.add_relation('manila-plugin', 'manila')
self.harness.add_relation_unit(backend, 'manila/0') self.harness.add_relation_unit(backend, 'manila/0')
def _get_partial_config_sample(self):
"""
A config with all mandatory params set
"""
return {
'infinibox-ip': '123.123.123.123',
'infinibox-login': 'login',
'infinibox-password': 'password',
'pool-name': 'test',
}
def _get_valid_config_sample(self):
"""
A minimal config that would transition the charm to ActiveState
"""
partial_config = self._get_partial_config_sample()
partial_config.update({
'nas-network-space-name': 'A,B',
})
return partial_config
def _get_source(self, codename, pocket, baseurl=None):
if baseurl is None:
baseurl = self.harness.charm.DEFAULT_REPO_BASEURL
return ' '.join((
'deb',
baseurl,
codename,
pocket))
def test_update_config(self):
self.harness.update_config({
"infinibox-ip": "172.27.12.151",
"infinibox-login": "admin",
"infinibox-password": "123456",
"nas-network-space-name": "iscsi",
"package_status": "install",
"pool-name": "manila",
"share-backend-name": "__app__",
"suppress-ssl-warnings": True,
"thin-provision": True,
"use-ssl": True,
"verbose": False,
})
@mock.patch('src.charm.add_source')
@mock.patch('src.charm.apt_update')
@mock.patch('src.charm.apt_install')
@mock.patch('src.charm.lsb_release')
def test_repo_management(self, lsb_release,
apt_install, apt_update, add_source):
add_source.return_value = None
apt_install.return_value = None
apt_update.return_value = None
# we'll need the config the charm considers valid
# in order to test repo management:
cfg = self._get_valid_config_sample()
dynamic_source = self._get_source('{distrib_codename}', 'main')
# generate test data for both 'source' values that need substituion
# and for the static ones
test_data = []
for release in UBUNTU_RELEASES:
static_source = self._get_source(release, 'main')
test_data.append(
(dynamic_source, release,
self._get_source(release, 'main')),
)
test_data.append(
(static_source, release, static_source),
)
for i in test_data:
# distro codename the charm runs on
lsb_release.return_value = {'DISTRIB_CODENAME': i[1]}
# configure to use specific repo version
cfg['install_sources'] = i[0]
cfg['install_keys'] = KEY
# on_config calls package installation
self.harness.update_config(cfg)
self.harness.charm.on.install.emit()
# make sure the repo management calls were correct
add_source.assert_called_with(i[2], KEY)
apt_install.assert_called_with(self.harness.charm.PACKAGES,
fatal=True)