Merge "Refactor charm to work with declarative-changes"
This commit is contained in:
commit
49947b0052
|
@ -1,2 +1,3 @@
|
|||
charm-tools
|
||||
#charm-tools
|
||||
git+https://github.com/juju/charm-tools#egg=charm-tools
|
||||
simplejson
|
||||
|
|
|
@ -24,10 +24,11 @@ basic.bootstrap_charm_deps()
|
|||
basic.init_config_states()
|
||||
|
||||
import charms.reactive as reactive
|
||||
|
||||
import charmhelpers.core.hookenv as hookenv
|
||||
import charms_openstack.charm
|
||||
|
||||
import charm.openstack.barbican as barbican
|
||||
# import the barbican module to get the charm definitions created.
|
||||
import charm.openstack.barbican # noqa
|
||||
|
||||
|
||||
def generate_mkek_action(*args):
|
||||
|
@ -41,7 +42,8 @@ def generate_mkek_action(*args):
|
|||
"Can't generate an MKEK in associated HSM because HSM is not "
|
||||
"available.")
|
||||
return
|
||||
barbican.generate_mkek(hsm)
|
||||
with charms_openstack.charm.provide_charm_instance as barbican_charm:
|
||||
barbican_charm.generate_mkek(hsm)
|
||||
|
||||
|
||||
def generate_hmac_action(*args):
|
||||
|
@ -54,7 +56,8 @@ def generate_hmac_action(*args):
|
|||
hookenv.action_fail(
|
||||
"Can't generate an HMAC in associated HSM because HSM is not "
|
||||
"available.")
|
||||
barbican.generate_hmac(hsm)
|
||||
with charms_openstack.charm.provide_charm_instance as barbican_charm:
|
||||
barbican_charm.generate_hmac(hsm)
|
||||
|
||||
|
||||
# Actions to function mapping, to allow for illegal python action names that
|
||||
|
|
|
@ -19,10 +19,7 @@ from __future__ import absolute_import
|
|||
|
||||
import subprocess
|
||||
|
||||
import charmhelpers.contrib.openstack.utils as ch_utils
|
||||
import charmhelpers.core.hookenv as hookenv
|
||||
import charmhelpers.core.unitdata as unitdata
|
||||
import charmhelpers.fetch
|
||||
|
||||
import charms_openstack.charm
|
||||
import charms_openstack.adapters
|
||||
|
@ -38,158 +35,80 @@ BARBICAN_WSGI_CONF = '/etc/apache2/conf-available/barbican-api.conf'
|
|||
OPENSTACK_RELEASE_KEY = 'barbican-charm.openstack-release-version'
|
||||
|
||||
|
||||
###
|
||||
# Handler functions for events that are interesting to the Barbican charms
|
||||
|
||||
def install():
|
||||
"""Use the singleton from the BarbicanCharm to install the packages on the
|
||||
unit
|
||||
"""
|
||||
unitdata.kv().unset(OPENSTACK_RELEASE_KEY)
|
||||
BarbicanCharm.singleton.install()
|
||||
|
||||
|
||||
def setup_endpoint(keystone):
|
||||
"""When the keystone interface connects, register this unit in the keystone
|
||||
catalogue.
|
||||
|
||||
:param keystone: instance of KeystoneRequires() class from i/f
|
||||
"""
|
||||
charm = BarbicanCharm.singleton
|
||||
keystone.register_endpoints(charm.service_type,
|
||||
charm.region,
|
||||
charm.public_url,
|
||||
charm.internal_url,
|
||||
charm.admin_url)
|
||||
|
||||
|
||||
def render_configs(interfaces_list):
|
||||
"""Using a list of interfaces, render the configs and, if they have
|
||||
changes, restart the services on the unit.
|
||||
|
||||
:param interfaces_list: [RelationBase] interfaces from reactive
|
||||
"""
|
||||
BarbicanCharm.singleton.render_with_interfaces(interfaces_list)
|
||||
|
||||
|
||||
def generate_mkek(hsm):
|
||||
"""Ask barbican to generate an MKEK in the backend store using the HSM.
|
||||
This assumes that an HSM is available, and configured. Uses the charm.
|
||||
"""
|
||||
BarbicanCharm.singleton.action_generate_mkek(hsm)
|
||||
|
||||
|
||||
def generate_hmac(hsm):
|
||||
"""Ask barbican to generate an HMAC in the backend store using the HSM.
|
||||
This assumes that an HSM is available, and configured. Uses the charm.
|
||||
"""
|
||||
BarbicanCharm.singleton.action_generate_hmac(hsm)
|
||||
|
||||
|
||||
def assess_status():
|
||||
"""Just call the BarbicanCharm.singleton.assess_status() command to update
|
||||
status on the unit.
|
||||
"""
|
||||
BarbicanCharm.singleton.assess_status()
|
||||
|
||||
|
||||
def configure_ssl(keystone=None):
|
||||
"""Use the singleton from the BarbicanCharm to configure ssl
|
||||
|
||||
:param keystone: KeystoneRequires() interface class
|
||||
"""
|
||||
BarbicanCharm.singleton.configure_ssl(keystone)
|
||||
# select the default release function
|
||||
charms_openstack.charm.use_defaults('charm.default-select-release')
|
||||
|
||||
|
||||
###
|
||||
# Implementation of the Barbican Charm classes
|
||||
|
||||
class BarbicanConfigurationAdapter(
|
||||
charms_openstack.adapters.APIConfigurationAdapter):
|
||||
# Add some properties to the configuration for templates/code to use with the
|
||||
# charm instance. The config_validator is called when the configuration is
|
||||
# loaded, and the properties are to add those names to the config object.
|
||||
|
||||
def __init__(self, port_map=None):
|
||||
super(BarbicanConfigurationAdapter, self).__init__(
|
||||
service_name='barbican',
|
||||
port_map=port_map)
|
||||
if self.keystone_api_version not in ['2', '3', 'none']:
|
||||
raise ValueError(
|
||||
"Unsupported keystone-api-version ({}). It should be 2 or 3"
|
||||
.format(self.keystone_api_version))
|
||||
|
||||
@property
|
||||
def barbican_api_keystone_pipeline(self):
|
||||
if self.keystone_api_version == "2":
|
||||
return 'cors keystone_authtoken context apiapp'
|
||||
else:
|
||||
return 'cors keystone_v3_authtoken context apiapp'
|
||||
|
||||
@property
|
||||
def barbican_api_pipeline(self):
|
||||
return {
|
||||
"2": "cors keystone_authtoken context apiapp",
|
||||
"3": "cors keystone_v3_authtoken context apiapp",
|
||||
"none": "cors unauthenticated-context apiapp"
|
||||
}[self.keystone_api_version]
|
||||
|
||||
@property
|
||||
def barbican_api_keystone_audit_pipeline(self):
|
||||
if self.keystone_api_version == "2":
|
||||
return 'keystone_authtoken context audit apiapp'
|
||||
else:
|
||||
return 'keystone_v3_authtoken context audit apiapp'
|
||||
@charms_openstack.adapters.config_property
|
||||
def validate_keystone_api_version(config):
|
||||
if config.keystone_api_version not in ['2', '3', 'none']:
|
||||
raise ValueError(
|
||||
"Unsupported keystone-api-version ({}). It should be 2 or 3"
|
||||
.format(config.keystone_api_version))
|
||||
|
||||
|
||||
class HSMAdapter(charms_openstack.adapters.OpenStackRelationAdapter):
|
||||
"""Adapt the barbican-hsm-plugin relation for use in rendering the config
|
||||
for Barbican. Note that the HSM relation is optional, so we have a class
|
||||
variable 'exists' that we can test in the template to see if we should
|
||||
render HSM parameters into the template.
|
||||
"""
|
||||
|
||||
interface_type = 'hsm'
|
||||
|
||||
@property
|
||||
def library_path(self):
|
||||
"""Provide a library_path property to the template if it exists"""
|
||||
try:
|
||||
return self.relation.plugin_data['library_path']
|
||||
except:
|
||||
return ''
|
||||
|
||||
@property
|
||||
def login(self):
|
||||
"""Provide a login property to the template if it exists"""
|
||||
try:
|
||||
return self.relation.plugin_data['login']
|
||||
except:
|
||||
return ''
|
||||
|
||||
@property
|
||||
def slot_id(self):
|
||||
"""Provide a slot_id property to the template if it exists"""
|
||||
try:
|
||||
return self.relation.plugin_data['slot_id']
|
||||
except:
|
||||
return ''
|
||||
@charms_openstack.adapters.config_property
|
||||
def barbican_api_keystone_pipeline(config):
|
||||
if config.keystone_api_version == "2":
|
||||
return 'cors keystone_authtoken context apiapp'
|
||||
else:
|
||||
return 'cors keystone_v3_authtoken context apiapp'
|
||||
|
||||
|
||||
class BarbicanAdapters(charms_openstack.adapters.OpenStackAPIRelationAdapters):
|
||||
"""
|
||||
Adapters class for the Barbican charm.
|
||||
@charms_openstack.adapters.config_property
|
||||
def barbican_api_pipeline(config):
|
||||
return {
|
||||
"2": "cors keystone_authtoken context apiapp",
|
||||
"3": "cors keystone_v3_authtoken context apiapp",
|
||||
"none": "cors unauthenticated-context apiapp"
|
||||
}[config.keystone_api_version]
|
||||
|
||||
This plumbs in the BarbicanConfigurationAdapter as the ConfigurationAdapter
|
||||
to provide additional properties.
|
||||
"""
|
||||
|
||||
relation_adapters = {
|
||||
'hsm': HSMAdapter,
|
||||
}
|
||||
@charms_openstack.adapters.config_property
|
||||
def barbican_api_keystone_audit_pipeline(config):
|
||||
if config.keystone_api_version == "2":
|
||||
return 'keystone_authtoken context audit apiapp'
|
||||
else:
|
||||
return 'keystone_v3_authtoken context audit apiapp'
|
||||
|
||||
def __init__(self, relations):
|
||||
super(BarbicanAdapters, self).__init__(
|
||||
relations,
|
||||
options_instance=BarbicanConfigurationAdapter(
|
||||
port_map=BarbicanCharm.api_ports))
|
||||
|
||||
# Adapt the barbican-hsm-plugin relation for use in rendering the config
|
||||
# for Barbican. Note that the HSM relation is optional, so we have a class
|
||||
# variable 'exists' that we can test in the template to see if we should
|
||||
# render HSM parameters into the template.
|
||||
|
||||
@charms_openstack.adapters.adapter_property('hsm')
|
||||
def library_path(hsm):
|
||||
"""Provide a library_path property to the template if it exists"""
|
||||
try:
|
||||
return hsm.relation.plugin_data['library_path']
|
||||
except:
|
||||
return ''
|
||||
|
||||
|
||||
@charms_openstack.adapters.adapter_property('hsm')
|
||||
def login(hsm):
|
||||
"""Provide a login property to the template if it exists"""
|
||||
try:
|
||||
return hsm.relation.plugin_data['login']
|
||||
except:
|
||||
return ''
|
||||
|
||||
|
||||
@charms_openstack.adapters.adapter_property('hsm')
|
||||
def slot_id(hsm):
|
||||
"""Provide a slot_id property to the template if it exists"""
|
||||
try:
|
||||
return hsm.relation.plugin_data['slot_id']
|
||||
except:
|
||||
return ''
|
||||
|
||||
|
||||
class BarbicanCharm(charms_openstack.charm.HAOpenStackCharm):
|
||||
|
@ -220,22 +139,34 @@ class BarbicanCharm(charms_openstack.charm.HAOpenStackCharm):
|
|||
BARBICAN_WSGI_CONF: services,
|
||||
}
|
||||
|
||||
adapters_class = BarbicanAdapters
|
||||
ha_resources = ['vips', 'haproxy']
|
||||
|
||||
def install(self):
|
||||
"""Customise the installation, configure the source and then call the
|
||||
parent install() method to install the packages
|
||||
def get_amqp_credentials(self):
|
||||
"""Provide the default amqp username and vhost as a tuple.
|
||||
|
||||
:returns (username, host): two strings to send to the amqp provider.
|
||||
"""
|
||||
# DEBUG - until seed random change lands into xenial cloud archive
|
||||
# BUG #1599550 - barbican + softhsm2 + libssl1.0.0:
|
||||
# pkcs11:_generate_random() fails
|
||||
# WARNING: This charm can't be released into stable until the bug is
|
||||
# fixed.
|
||||
charmhelpers.fetch.add_source("ppa:ajkavanagh/barbican")
|
||||
self.configure_source()
|
||||
# and do the actual install
|
||||
super(BarbicanCharm, self).install()
|
||||
return (self.config['rabbit-user'], self.config['rabbit-vhost'])
|
||||
|
||||
def get_database_setup(self):
|
||||
"""Provide the default database credentials as a list of 3-tuples
|
||||
|
||||
returns a structure of:
|
||||
[
|
||||
{'database': <database>,
|
||||
'username': <username>,
|
||||
'hostname': <hostname of this unit>
|
||||
'prefix': <the optional prefix for the database>, },
|
||||
]
|
||||
|
||||
:returns [{'database': ...}, ...]: credentials for multiple databases
|
||||
"""
|
||||
return [
|
||||
dict(
|
||||
database=self.config['database'],
|
||||
username=self.config['database-user'],
|
||||
hostname=hookenv.unit_private_ip(), )
|
||||
]
|
||||
|
||||
def action_generate_mkek(self, hsm):
|
||||
"""Generate an MKEK on a connected HSM. Requires that an HSM is
|
||||
|
@ -308,19 +239,3 @@ class BarbicanCharm(charms_openstack.charm.HAOpenStackCharm):
|
|||
required_relations.append('hsm')
|
||||
return super(BarbicanCharm, self).states_to_check(
|
||||
required_relations=required_relations)
|
||||
|
||||
|
||||
# Determine the charm class by the supported release
|
||||
@charms_openstack.charm.register_os_release_selector
|
||||
def select_release():
|
||||
"""Determine the release based on the python-keystonemiddleware that is
|
||||
installed.
|
||||
|
||||
Note that this function caches the release after the first install so that
|
||||
it doesn't need to keep going and getting it from the package information.
|
||||
"""
|
||||
release_version = unitdata.kv().get(OPENSTACK_RELEASE_KEY, None)
|
||||
if release_version is None:
|
||||
release_version = ch_utils.os_release('python-keystonemiddleware')
|
||||
unitdata.kv().set(OPENSTACK_RELEASE_KEY, release_version)
|
||||
return release_version
|
||||
|
|
|
@ -18,46 +18,28 @@ from __future__ import absolute_import
|
|||
import charms.reactive as reactive
|
||||
import charmhelpers.core.hookenv as hookenv
|
||||
|
||||
import charms_openstack.charm as charm
|
||||
|
||||
# This charm's library contains all of the handler code associated with
|
||||
# barbican
|
||||
import charm.openstack.barbican as barbican
|
||||
# barbican -- we need to import it to get the definitions for the charm.
|
||||
import charm.openstack.barbican as barbican # noqa
|
||||
|
||||
|
||||
# use a synthetic state to ensure that it get it to be installed independent of
|
||||
# the install hook.
|
||||
@reactive.when_not('charm.installed')
|
||||
def install_packages():
|
||||
barbican.install()
|
||||
reactive.set_state('charm.installed')
|
||||
|
||||
|
||||
@reactive.when('amqp.connected')
|
||||
def setup_amqp_req(amqp):
|
||||
"""Use the amqp interface to request access to the amqp broker using our
|
||||
local configuration.
|
||||
"""
|
||||
amqp.request_access(username=hookenv.config('rabbit-user'),
|
||||
vhost=hookenv.config('rabbit-vhost'))
|
||||
barbican.assess_status()
|
||||
|
||||
|
||||
@reactive.when('shared-db.connected')
|
||||
def setup_database(database):
|
||||
"""On receiving database credentials, configure the database on the
|
||||
interface.
|
||||
"""
|
||||
database.configure(hookenv.config('database'),
|
||||
hookenv.config('database-user'),
|
||||
hookenv.unit_private_ip())
|
||||
barbican.assess_status()
|
||||
|
||||
|
||||
@reactive.when('identity-service.connected')
|
||||
def setup_endpoint(keystone):
|
||||
barbican.setup_endpoint(keystone)
|
||||
barbican.assess_status()
|
||||
# Use the charms.openstack defaults for common states and hooks
|
||||
charm.use_defaults(
|
||||
'charm.installed',
|
||||
'amqp.connected',
|
||||
'shared-db.connected',
|
||||
'identity-service.connected',
|
||||
'identity-service.available', # enables SSL support
|
||||
'config.changed',
|
||||
'update-status')
|
||||
|
||||
|
||||
# Note that because of the way reactive.when works, (which is to 'find' the
|
||||
# __code__ segment of the decorated function, it's very, very difficult to add
|
||||
# other kinds of decorators here. This rules out adding other things into the
|
||||
# charm args list. It is also CPython dependent.
|
||||
@reactive.when('shared-db.available')
|
||||
@reactive.when('identity-service.available')
|
||||
@reactive.when('amqp.available')
|
||||
|
@ -65,25 +47,11 @@ def render_stuff(*args):
|
|||
"""Render the configuration for Barbican when all the interfaces are
|
||||
available.
|
||||
|
||||
Note that the HSM interface is optional (hence the @when_any) and thus is
|
||||
only used if it is available.
|
||||
Note that the HSM interface is optional and thus is only used if it is
|
||||
available.
|
||||
"""
|
||||
# Get the optional hsm relation, if it is available for rendering.
|
||||
hsm = reactive.RelationBase.from_state('hsm.available')
|
||||
if hsm is not None:
|
||||
args = args + (hsm, )
|
||||
barbican.render_configs(args)
|
||||
barbican.assess_status()
|
||||
|
||||
|
||||
@reactive.when('config.changed')
|
||||
def config_changed():
|
||||
"""When the configuration changes, assess the unit's status to update any
|
||||
juju state required"""
|
||||
barbican.assess_status()
|
||||
|
||||
|
||||
@reactive.when('identity-service.available')
|
||||
def configure_ssl(keystone):
|
||||
"""Configure SSL access to Barbican if requested"""
|
||||
barbican.configure_ssl(keystone)
|
||||
hookenv.log("about to call the render_configs with {}".format(args))
|
||||
with charm.provide_charm_instance() as barbican_charm:
|
||||
barbican_charm.render_with_interfaces(
|
||||
charm.optional_interfaces(args, 'hsm.available'))
|
||||
barbican_charm.assess_status()
|
||||
|
|
|
@ -15,164 +15,53 @@
|
|||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
|
||||
import reactive.barbican_handlers as handlers
|
||||
|
||||
|
||||
_when_args = {}
|
||||
_when_not_args = {}
|
||||
import charms_openstack.test_utils as test_utils
|
||||
|
||||
|
||||
def mock_hook_factory(d):
|
||||
class TestRegisteredHooks(test_utils.TestRegisteredHooks):
|
||||
|
||||
def mock_hook(*args, **kwargs):
|
||||
|
||||
def inner(f):
|
||||
# remember what we were passed. Note that we can't actually
|
||||
# determine the class we're attached to, as the decorator only gets
|
||||
# the function.
|
||||
try:
|
||||
d[f.__name__].append(dict(args=args, kwargs=kwargs))
|
||||
except KeyError:
|
||||
d[f.__name__] = [dict(args=args, kwargs=kwargs)]
|
||||
return f
|
||||
return inner
|
||||
return mock_hook
|
||||
|
||||
|
||||
class TestBarbicanHandlers(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls._patched_when = mock.patch('charms.reactive.when',
|
||||
mock_hook_factory(_when_args))
|
||||
cls._patched_when_started = cls._patched_when.start()
|
||||
cls._patched_when_not = mock.patch('charms.reactive.when_not',
|
||||
mock_hook_factory(_when_not_args))
|
||||
cls._patched_when_not_started = cls._patched_when_not.start()
|
||||
# force requires to rerun the mock_hook decorator:
|
||||
# try except is Python2/Python3 compatibility as Python3 has moved
|
||||
# reload to importlib.
|
||||
try:
|
||||
reload(handlers)
|
||||
except NameError:
|
||||
import importlib
|
||||
importlib.reload(handlers)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls._patched_when.stop()
|
||||
cls._patched_when_started = None
|
||||
cls._patched_when = None
|
||||
cls._patched_when_not.stop()
|
||||
cls._patched_when_not_started = None
|
||||
cls._patched_when_not = None
|
||||
# and fix any breakage we did to the module
|
||||
try:
|
||||
reload(handlers)
|
||||
except NameError:
|
||||
import importlib
|
||||
importlib.reload(handlers)
|
||||
|
||||
def setUp(self):
|
||||
self._patches = {}
|
||||
self._patches_start = {}
|
||||
|
||||
def tearDown(self):
|
||||
for k, v in self._patches.items():
|
||||
v.stop()
|
||||
setattr(self, k, None)
|
||||
self._patches = None
|
||||
self._patches_start = None
|
||||
|
||||
def patch(self, obj, attr, return_value=None):
|
||||
mocked = mock.patch.object(obj, attr)
|
||||
self._patches[attr] = mocked
|
||||
started = mocked.start()
|
||||
started.return_value = return_value
|
||||
self._patches_start[attr] = started
|
||||
setattr(self, attr, started)
|
||||
|
||||
def test_registered_hooks(self):
|
||||
# test that the hooks actually registered the relation expressions that
|
||||
# are meaningful for this interface: this is to handle regressions.
|
||||
# The keys are the function names that the hook attaches to.
|
||||
when_patterns = {
|
||||
'setup_amqp_req': ('amqp.connected', ),
|
||||
'setup_database': ('shared-db.connected', ),
|
||||
'setup_endpoint': ('identity-service.connected', ),
|
||||
'render_stuff': ('shared-db.available',
|
||||
'identity-service.available',
|
||||
'amqp.available',),
|
||||
'config_changed': ('config.changed', ),
|
||||
'configure_ssl': ('identity-service.available', ),
|
||||
def test_hooks(self):
|
||||
defaults = [
|
||||
'charm.installed',
|
||||
'amqp.connected',
|
||||
'shared-db.connected',
|
||||
'identity-service.connected',
|
||||
'identity-service.available', # enables SSL support
|
||||
'config.changed',
|
||||
'update-status']
|
||||
hook_set = {
|
||||
'when': {
|
||||
'render_stuff': ('shared-db.available',
|
||||
'identity-service.available',
|
||||
'amqp.available',),
|
||||
}
|
||||
}
|
||||
when_not_patterns = {
|
||||
'install_packages': ('charm.installed', ),
|
||||
}
|
||||
# check the when hooks are attached to the expected functions
|
||||
for t, p in [(_when_args, when_patterns),
|
||||
(_when_not_args, when_not_patterns)]:
|
||||
for f, args in t.items():
|
||||
# check that function is in patterns
|
||||
# print("f: {}, args: {}".format(f, args))
|
||||
self.assertTrue(f in p.keys())
|
||||
# check that the lists are equal
|
||||
l = [a['args'][0] for a in args]
|
||||
self.assertEqual(l, sorted(p[f]))
|
||||
# test that the hooks were registered via the
|
||||
# reactive.barbican_handlers
|
||||
self.registered_hooks_test_helper(handlers, hook_set, defaults)
|
||||
|
||||
def test_install_packages(self):
|
||||
self.patch(handlers.barbican, 'install')
|
||||
self.patch(handlers.reactive, 'set_state')
|
||||
handlers.install_packages()
|
||||
self.install.assert_called_once_with()
|
||||
self.set_state.assert_called_once_with('charm.installed')
|
||||
|
||||
def test_setup_amqp_req(self):
|
||||
amqp = mock.MagicMock()
|
||||
self.patch(handlers.hookenv, 'config')
|
||||
reply = {
|
||||
'rabbit-user': 'user1',
|
||||
'rabbit-vhost': 'vhost1',
|
||||
}
|
||||
self.config.side_effect = lambda x: reply[x]
|
||||
self.patch(handlers.barbican, 'assess_status')
|
||||
handlers.setup_amqp_req(amqp)
|
||||
amqp.request_access.assert_called_once_with(
|
||||
username='user1', vhost='vhost1')
|
||||
self.assess_status.assert_called_once_with()
|
||||
|
||||
def test_database(self):
|
||||
database = mock.MagicMock()
|
||||
self.patch(handlers.hookenv, 'config')
|
||||
reply = {
|
||||
'database': 'db1',
|
||||
'database-user': 'dbuser1',
|
||||
}
|
||||
self.config.side_effect = lambda x: reply[x]
|
||||
self.patch(handlers.hookenv, 'unit_private_ip', 'private_ip')
|
||||
self.patch(handlers.barbican, 'assess_status')
|
||||
handlers.setup_database(database)
|
||||
database.configure.assert_called_once_with(
|
||||
'db1', 'dbuser1', 'private_ip')
|
||||
self.assess_status.assert_called_once_with()
|
||||
|
||||
def test_setup_endpoint(self):
|
||||
self.patch(handlers.barbican, 'setup_endpoint')
|
||||
self.patch(handlers.barbican, 'assess_status')
|
||||
handlers.setup_endpoint('endpoint_object')
|
||||
self.setup_endpoint.assert_called_once_with('endpoint_object')
|
||||
self.assess_status.assert_called_once_with()
|
||||
class TestRenderStuff(test_utils.PatchHelper):
|
||||
|
||||
def test_render_stuff(self):
|
||||
self.patch(handlers.barbican, 'render_configs')
|
||||
self.patch(handlers.barbican, 'assess_status')
|
||||
self.patch(handlers.reactive.RelationBase, 'from_state',
|
||||
return_value='hsm')
|
||||
barbican_charm = mock.MagicMock()
|
||||
self.patch_object(handlers.charm, 'provide_charm_instance',
|
||||
new=mock.MagicMock())
|
||||
self.provide_charm_instance().__enter__.return_value = barbican_charm
|
||||
self.provide_charm_instance().__exit__.return_value = None
|
||||
self.patch_object(handlers.charm, 'optional_interfaces')
|
||||
|
||||
def _optional_interfaces(args, *interfaces):
|
||||
self.assertEqual(interfaces, ('hsm.available', ))
|
||||
return args + ('hsm', )
|
||||
|
||||
self.optional_interfaces.side_effect = _optional_interfaces
|
||||
|
||||
handlers.render_stuff('arg1', 'arg2')
|
||||
self.render_configs.assert_called_once_with(('arg1', 'arg2', 'hsm'))
|
||||
self.assess_status.assert_called_once_with()
|
||||
self.from_state.assert_called_once_with('hsm.available')
|
||||
barbican_charm.render_with_interfaces.assert_called_once_with(
|
||||
('arg1', 'arg2', 'hsm'))
|
||||
barbican_charm.assess_status.assert_called_once_with()
|
||||
|
|
|
@ -15,151 +15,93 @@
|
|||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
import charms_openstack.test_utils as test_utils
|
||||
|
||||
import charm.openstack.barbican as barbican
|
||||
|
||||
|
||||
class Helper(unittest.TestCase):
|
||||
class Helper(test_utils.PatchHelper):
|
||||
|
||||
def setUp(self):
|
||||
self._patches = {}
|
||||
self._patches_start = {}
|
||||
# patch out the select_release to always return 'mitaka'
|
||||
self.patch(barbican.unitdata, 'kv')
|
||||
_getter = mock.MagicMock()
|
||||
_getter.get.return_value = barbican.BarbicanCharm.release
|
||||
self.kv.return_value = _getter
|
||||
|
||||
def tearDown(self):
|
||||
for k, v in self._patches.items():
|
||||
v.stop()
|
||||
setattr(self, k, None)
|
||||
self._patches = None
|
||||
self._patches_start = None
|
||||
|
||||
def patch(self, obj, attr, return_value=None, **kwargs):
|
||||
mocked = mock.patch.object(obj, attr, **kwargs)
|
||||
self._patches[attr] = mocked
|
||||
started = mocked.start()
|
||||
started.return_value = return_value
|
||||
self._patches_start[attr] = started
|
||||
setattr(self, attr, started)
|
||||
super().setUp()
|
||||
self.patch_release(barbican.BarbicanCharm.release)
|
||||
|
||||
|
||||
class TestOpenStackBarbican(Helper):
|
||||
class TestCustomProperties(Helper):
|
||||
|
||||
def test_install(self):
|
||||
self.patch(barbican.BarbicanCharm, 'set_config_defined_certs_and_keys')
|
||||
self.patch(barbican.BarbicanCharm.singleton, 'install')
|
||||
barbican.install()
|
||||
self.install.assert_called_once_with()
|
||||
|
||||
def test_setup_endpoint(self):
|
||||
self.patch(barbican.BarbicanCharm, 'set_config_defined_certs_and_keys')
|
||||
self.patch(barbican.BarbicanCharm, 'service_type',
|
||||
new_callable=mock.PropertyMock)
|
||||
self.patch(barbican.BarbicanCharm, 'region',
|
||||
new_callable=mock.PropertyMock)
|
||||
self.patch(barbican.BarbicanCharm, 'public_url',
|
||||
new_callable=mock.PropertyMock)
|
||||
self.patch(barbican.BarbicanCharm, 'internal_url',
|
||||
new_callable=mock.PropertyMock)
|
||||
self.patch(barbican.BarbicanCharm, 'admin_url',
|
||||
new_callable=mock.PropertyMock)
|
||||
self.service_type.return_value = 'type1'
|
||||
self.region.return_value = 'region1'
|
||||
self.public_url.return_value = 'public_url'
|
||||
self.internal_url.return_value = 'internal_url'
|
||||
self.admin_url.return_value = 'admin_url'
|
||||
keystone = mock.MagicMock()
|
||||
barbican.setup_endpoint(keystone)
|
||||
keystone.register_endpoints.assert_called_once_with(
|
||||
'type1', 'region1', 'public_url', 'internal_url', 'admin_url')
|
||||
|
||||
def test_render_configs(self):
|
||||
self.patch(barbican.BarbicanCharm, 'set_config_defined_certs_and_keys')
|
||||
self.patch(barbican.BarbicanCharm.singleton, 'render_with_interfaces')
|
||||
barbican.render_configs('interfaces-list')
|
||||
self.render_with_interfaces.assert_called_once_with(
|
||||
'interfaces-list')
|
||||
|
||||
|
||||
class TestBarbicanConfigurationAdapter(Helper):
|
||||
|
||||
@mock.patch('charmhelpers.core.hookenv.config')
|
||||
def test_barbican_configuration_adapter(self, config):
|
||||
self.patch(
|
||||
barbican.charms_openstack.adapters.APIConfigurationAdapter,
|
||||
'get_network_addresses')
|
||||
reply = {
|
||||
'keystone-api-version': '2',
|
||||
}
|
||||
config.side_effect = lambda: reply
|
||||
# Make one with no errors, api version 2
|
||||
a = barbican.BarbicanConfigurationAdapter()
|
||||
self.assertEqual(a.barbican_api_keystone_pipeline,
|
||||
'cors keystone_authtoken context apiapp')
|
||||
self.assertEqual(a.barbican_api_pipeline,
|
||||
'cors keystone_authtoken context apiapp')
|
||||
# Now test it with api version 3
|
||||
reply['keystone-api-version'] = '3'
|
||||
a = barbican.BarbicanConfigurationAdapter()
|
||||
self.assertEqual(a.barbican_api_keystone_pipeline,
|
||||
'cors keystone_v3_authtoken context apiapp')
|
||||
self.assertEqual(a.barbican_api_pipeline,
|
||||
'cors keystone_v3_authtoken context apiapp')
|
||||
# and a 'none' version
|
||||
reply['keystone-api-version'] = 'none'
|
||||
a = barbican.BarbicanConfigurationAdapter()
|
||||
self.assertEqual(a.barbican_api_keystone_pipeline,
|
||||
'cors keystone_v3_authtoken context apiapp')
|
||||
self.assertEqual(a.barbican_api_pipeline,
|
||||
'cors unauthenticated-context apiapp')
|
||||
# finally, try to create an invalid one.
|
||||
reply['keystone-api-version'] = None
|
||||
def test_validate_keystone_api_version(self):
|
||||
config = mock.MagicMock()
|
||||
for v in ['2', '3', 'none']:
|
||||
config.keystone_api_version = v
|
||||
barbican.validate_keystone_api_version(config)
|
||||
# ensure that it fails
|
||||
with self.assertRaises(ValueError):
|
||||
a = barbican.BarbicanConfigurationAdapter()
|
||||
config.keystone_api_version = 'fail-me'
|
||||
barbican.validate_keystone_api_version(config)
|
||||
|
||||
def test_barbican_api_keystone_pipeline(self):
|
||||
config = mock.MagicMock()
|
||||
config.keystone_api_version = '2'
|
||||
self.assertEqual(barbican.barbican_api_keystone_pipeline(config),
|
||||
'cors keystone_authtoken context apiapp')
|
||||
config.keystone_api_version = ''
|
||||
self.assertEqual(barbican.barbican_api_keystone_pipeline(config),
|
||||
'cors keystone_v3_authtoken context apiapp')
|
||||
|
||||
def test_barbican_api_pipeline(self):
|
||||
config = mock.MagicMock()
|
||||
config.keystone_api_version = '2'
|
||||
self.assertEqual(barbican.barbican_api_pipeline(config),
|
||||
'cors keystone_authtoken context apiapp')
|
||||
config.keystone_api_version = '3'
|
||||
self.assertEqual(barbican.barbican_api_pipeline(config),
|
||||
'cors keystone_v3_authtoken context apiapp')
|
||||
config.keystone_api_version = 'none'
|
||||
self.assertEqual(barbican.barbican_api_pipeline(config),
|
||||
'cors unauthenticated-context apiapp')
|
||||
|
||||
def test_barbican_api_keystone_audit_pipeline(self):
|
||||
config = mock.MagicMock()
|
||||
config.keystone_api_version = '2'
|
||||
self.assertEqual(barbican.barbican_api_keystone_audit_pipeline(config),
|
||||
'keystone_authtoken context audit apiapp')
|
||||
config.keystone_api_version = ''
|
||||
self.assertEqual(barbican.barbican_api_keystone_audit_pipeline(config),
|
||||
'keystone_v3_authtoken context audit apiapp')
|
||||
|
||||
|
||||
class TestBarbicanAdapters(Helper):
|
||||
class TestHSMProperties(Helper):
|
||||
|
||||
@mock.patch('charmhelpers.core.hookenv.config')
|
||||
def test_barbican_adapters(self, config):
|
||||
reply = {
|
||||
'keystone-api-version': '2',
|
||||
# for the charms.openstack code, which breaks if we don't have:
|
||||
'os-public-hostname': 'host',
|
||||
'os-internal-hostname': 'internal',
|
||||
'os-admin-hostname': 'admin',
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.data_none = {}
|
||||
self.data_set = {
|
||||
'library_path': 'a-path',
|
||||
'login': 'a-login',
|
||||
'slot_id': 'a-slot_id',
|
||||
}
|
||||
|
||||
def cf(key=None):
|
||||
if key is not None:
|
||||
return reply[key]
|
||||
return reply
|
||||
def test_library_path(self):
|
||||
hsm = mock.MagicMock()
|
||||
hsm.relation.plugin_data = self.data_none
|
||||
self.assertEqual(barbican.library_path(hsm), '')
|
||||
hsm.relation.plugin_data = self.data_set
|
||||
self.assertEqual(barbican.library_path(hsm), 'a-path')
|
||||
|
||||
config.side_effect = cf
|
||||
amqp_relation = mock.MagicMock()
|
||||
amqp_relation.relation_name = 'amqp'
|
||||
shared_db_relation = mock.MagicMock()
|
||||
shared_db_relation.relation_name = 'shared_db'
|
||||
other_relation = mock.MagicMock()
|
||||
other_relation.relation_name = 'other'
|
||||
other_relation.thingy = 'help'
|
||||
# verify that the class is created with a BarbicanConfigurationAdapter
|
||||
b = barbican.BarbicanAdapters([amqp_relation,
|
||||
shared_db_relation,
|
||||
other_relation])
|
||||
# ensure that the relevant things got put on.
|
||||
self.assertTrue(
|
||||
isinstance(
|
||||
b.other,
|
||||
barbican.charms_openstack.adapters.OpenStackRelationAdapter))
|
||||
self.assertTrue(isinstance(b.options,
|
||||
barbican.BarbicanConfigurationAdapter))
|
||||
def test_login(self):
|
||||
hsm = mock.MagicMock()
|
||||
hsm.relation.plugin_data = self.data_none
|
||||
self.assertEqual(barbican.login(hsm), '')
|
||||
hsm.relation.plugin_data = self.data_set
|
||||
self.assertEqual(barbican.login(hsm), 'a-login')
|
||||
|
||||
def test_slot_id(self):
|
||||
hsm = mock.MagicMock()
|
||||
hsm.relation.plugin_data = self.data_none
|
||||
self.assertEqual(barbican.slot_id(hsm), '')
|
||||
hsm.relation.plugin_data = self.data_set
|
||||
self.assertEqual(barbican.slot_id(hsm), 'a-slot_id')
|
||||
|
||||
|
||||
class TestBarbicanCharm(Helper):
|
||||
|
@ -171,7 +113,7 @@ class TestBarbicanCharm(Helper):
|
|||
'login': '1234',
|
||||
'slot_id': 'slot1'
|
||||
}
|
||||
self.patch(barbican.hookenv, 'config')
|
||||
self.patch_object(barbican.hookenv, 'config')
|
||||
config = {
|
||||
'mkek-key-length': 5,
|
||||
'label-mkek': 'the-label'
|
||||
|
@ -183,8 +125,8 @@ class TestBarbicanCharm(Helper):
|
|||
return config
|
||||
|
||||
self.config.side_effect = cf
|
||||
self.patch(barbican.subprocess, 'check_call')
|
||||
self.patch(barbican.hookenv, 'log')
|
||||
self.patch_object(barbican.subprocess, 'check_call')
|
||||
self.patch_object(barbican.hookenv, 'log')
|
||||
# try generating a an mkek with no failure
|
||||
c = barbican.BarbicanCharm()
|
||||
c.action_generate_mkek(hsm)
|
||||
|
@ -218,7 +160,7 @@ class TestBarbicanCharm(Helper):
|
|||
'login': '1234',
|
||||
'slot_id': 'slot1'
|
||||
}
|
||||
self.patch(barbican.hookenv, 'config')
|
||||
self.patch_object(barbican.hookenv, 'config')
|
||||
config = {
|
||||
'hmac-key-length': 5,
|
||||
'label-hmac': 'the-label'
|
||||
|
@ -230,8 +172,8 @@ class TestBarbicanCharm(Helper):
|
|||
return config
|
||||
|
||||
self.config.side_effect = cf
|
||||
self.patch(barbican.subprocess, 'check_call')
|
||||
self.patch(barbican.hookenv, 'log')
|
||||
self.patch_object(barbican.subprocess, 'check_call')
|
||||
self.patch_object(barbican.hookenv, 'log')
|
||||
# try generating a an hmac with no failure
|
||||
c = barbican.BarbicanCharm()
|
||||
c.action_generate_hmac(hsm)
|
||||
|
|
Loading…
Reference in New Issue