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
========
This charm provides a infinidat storage backend for use with the Cinder
This charm provides Infinidat storage backend for use with the Manila
charm.
To use:
juju deploy cinder
juju deploy manila
juju deploy manila-infinidat
juju add-relation manila-infinidat cinder
juju add-relation manila-infinidat manila
Configuration
=============

View File

@ -11,16 +11,19 @@ options:
description: >
The status of service-affecting packages will be set to this
value in the dpkg database. Valid values are "install" and "hold".
source:
description: >
List of extra apt sources, per charm-helpers standard
format (a yaml list of strings encoded as a string). Each source
may be either a line that can be added directly to
sources.list(5), or in the form ppa:<user>/<ppa-name> for adding
Personal Package Archives, or a distribution component to enable.
install_sources:
description: |
Optional configuration to support use of additional sources such as:
- ppa:myteam/ppa
- cloud:trusty-proposed/kilo
- http://my.archive.com/ubuntu main
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
default: ""
key:
install_keys:
description: >
List of signing keys for install_sources package sources, per
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
Launchpad.
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:
default: !!bool false
type: boolean
@ -65,8 +86,18 @@ options:
thin-provision:
type: boolean
description: Choose whether to thin provision
default: !!bool true
default: !!bool "true"
share-backend-name:
type: string
description: Manila backend name
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.
from typing import OrderedDict
import ops_openstack.adapters
from ops_openstack.plugins.classes import CinderStoragePluginCharm
from ops_openstack.core import charm_class, get_charm_class_for_release, OSBaseCharm
from ops_openstack.core import OSBaseCharm
from ops.main import main
from ops.model import (
@ -45,19 +43,25 @@ import logging
RELATION_NAME = 'manila-plugin'
class ManilaInfinidatPluginCharm(OSBaseCharm):
PACKAGES = ['infinisdk', 'infinishell']
PACKAGES = ['python3-infinisdk', 'infinishell']
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):
super().__init__(*args, **kwargs)
self.framework.observe(
self.on.config_changed,
self.on_config)
@ -69,14 +73,16 @@ class ManilaInfinidatPluginCharm(OSBaseCharm):
def on_config(self, event):
self.on_manila_plugin(event)
self.set_started(started=True)
if self.framework.model.relations.get(RELATION_NAME):
self.send_backend_config()
self.unit.status = ActiveStatus('Unit is ready')
def on_manila_plugin(self, event):
logging.error("Relation changed")
def send_backend_config(self):
config = dict(self.framework.model.config)
manila_backends = self.manila_configuration(config)
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(
source="parts/backends",
template_loader=os_templating.get_loader(
@ -90,12 +96,15 @@ class ManilaInfinidatPluginCharm(OSBaseCharm):
}
})
def on_manila_plugin(self, event):
self.send_backend_config()
def manila_configuration(self, config):
"""
See https://docs.openstack.org/manila/latest/configuration/shared-file-systems/drivers/infinidat-share-driver.html # noqa: E501
"""
# Return the configuration to be set by the principal.
backends = OrderedDict()
backend_name = config.get('share-backend-name')
@ -103,6 +112,7 @@ class ManilaInfinidatPluginCharm(OSBaseCharm):
# backend name selection logic
if config.get('nas-network-space-name'):
backend_names = filter(
None,
map(
str.strip,
config.get('nas-network-space-name').split(',')
@ -115,66 +125,60 @@ class ManilaInfinidatPluginCharm(OSBaseCharm):
for backend_name in backend_names:
backends[backend_name] = (
backends[backend_name] = [
('share_driver', self.SHARE_DRIVER),
('share_backend_name', backend_name),
('infinibox_hostname', config.get('infinibox-ip')),
('infinibox_login', config.get('infinibox-login')),
('infinibox_password', config.get('infinibox-password')),
('infinidat_nas_network_space_name', backend_name), #TODO: mapping to nas-network-space-name
('driver_handles_share_servers', 'false'),
('infinidat_nas_network_space_name', backend_name),
('infinidat_pool_name', config.get('pool-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_login', config.get('infinibox-login')),
('infinibox_password', config.get('infinibox-password')),
])
if config.get('pool-name'):
backends[backend_name].append(
('infinidat_pool_name', config.get('pool-name'))
),
if not backends:
self.unit.status = BlockedStatus("No backends configured")
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):
logging.info("Installing packages")
# value of 'source' param is defined like this:
# deb https://repo.infinidat.com/packages/main-stable/apt/linux-ubuntu $codename main
if self.model.config.get('source'):
# we implement $codename expansion here
# see the default value for 'source' in config.yaml
if self.model.config.get('install_sources'):
distrib_codename = lsb_release()['DISTRIB_CODENAME'].lower()
add_source(
self.model.config['source'] \
.replace('$codename',
lsb_release()['DISTRIB_CODENAME'].lower()),
self.model.config.get('key'))
self.model.config['install_sources']
.format(distrib_codename=distrib_codename),
self.model.config.get('install_keys'))
apt_update(fatal=True)
apt_install(self.PACKAGES, fatal=True)
self.update_status()
def on_install(self, event):
self.install_pkgs()
self.update_status()
if __name__ == '__main__':
main(ManilaInfinidatPluginCharm)

View File

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

View File

@ -13,17 +13,136 @@
# limitations under the License.
import unittest
from src.charm import CinderCharmBase
from ops.model import ActiveStatus
from src.charm import ManilaInfinidatPluginCharm
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):
self.harness = Harness(CinderCharmBase)
self.harness = Harness(ManilaInfinidatPluginCharm)
self.addCleanup(self.harness.cleanup)
self.harness.begin()
self.harness.set_leader(True)
backend = self.harness.add_relation('manila-plugin', 'manila')
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)