Support upgrades to Trilio 4.1

Adding support for Trilio 4.1 includes the following changes:

* Add Trilio_properties config property to enables templates to
  distinguish between 4.0 and 4.1 release.
* Add get_trilio_codename_install_source to attempt to derive the
  Trilio version supported by an apt repo.
* Add get_trilio_charm_instance which overrides the default
  get_charm_instance.  This will pick the correct charm class based
  on both the Trilio release and the OpenStack release.
* Add select_trilio_release which overrides the default
  select_release and calculates the target OpenStack and Trilio
  release.
* Add a specialist Trilio metaclass BaseTrilioCharmMeta. This
  registers charm classes using their OpenStack release, Trilio
  release and package type.
* Move code shared between TrilioVaultCharm &
  TrilioVaultSubordinateCharm to TrilioVaultCharmMixin. Add support
  for Trilio upgrades to TrilioVaultCharmMixina.

NOTE: An earlier version of this change
      (I5a5e5721d9a713b66f8c796896c400481e9733a2) was landed and
      reverted. It was reverted because get_trilio_charm_instance
      and select_trilio_release were both registered as handlers
      irrespective of whether a charm explicitly imported
      charms_openstack.plugins.trilio This caused reactive charms
      which used the default handlers to fail as they imported the
      Trilio functions and the default ones and only one can ever
      registered. This patch fixes this by wrapping the trilio
      function definitions inside make_* methods, so the decorator
      only registers the methods when the make_* methods are
      explicitly called.

Change-Id: Id3bb13aff6d0e6df2d5ec144689c992cf09c1b4c
This commit is contained in:
Liam Young 2020-12-08 09:49:56 +00:00
parent 390a426d31
commit 2520764264
3 changed files with 796 additions and 33 deletions

View File

@ -15,16 +15,85 @@
import base64
import os
import re
from urllib.parse import urlparse
import charms_openstack.adapters
import charms_openstack.charm
import charmhelpers.core as ch_core
import charmhelpers.fetch as fetch
import charmhelpers.core.unitdata as unitdata
import charmhelpers.contrib.openstack.utils as os_utils
import charms.reactive as reactive
TV_MOUNTS = "/var/triliovault-mounts"
# Used to store the discovered release version for caching between invocations
TRILIO_RELEASE_KEY = 'charmers.trilio-release-version'
# _trilio_releases{} is a dictionary of release -> class that is instantiated
# according to the release that is being requested. i.e. a charm can
# handle more than one release. The BaseOpenStackCharm() derived class sets the
# `release` variable to indicate which OpenStack release that the charm
# supports # and `trilio_release` to indicate which Trilio release the charm
# supports. # Any subsequent releases that need a different/specialised charm
# uses the # `release` and `trilio_release` class properties to indicate that
# it handles those releases onwards.
_trilio_releases = {}
@charms_openstack.adapters.config_property
def trilio_properties(cls):
"""Trilio properties additions for config adapter.
:param cls: Configuration Adapter class
:type cls: charms_openstack.adapters.DefaultConfigurationAdapter
"""
cur_ver = cls.charm_instance.release_pkg_version()
comp = fetch.apt_pkg.version_compare(cur_ver, '4.1')
if comp >= 0:
return {
'db_type': 'dedicated',
'transport_type': 'dmapi'}
else:
return {
'db_type': 'legacy',
'transport_type': 'legacy'}
class AptPkgVersion():
"""Allow package version to be compared."""
def __init__(self, version):
self.version = version
def __lt__(self, other):
return fetch.apt_pkg.version_compare(self.version, other.version) == -1
def __le__(self, other):
return self.__lt__(other) or self.__eq__(other)
def __gt__(self, other):
return fetch.apt_pkg.version_compare(self.version, other.version) == 1
def __ge__(self, other):
return self.__gt__(other) or self.__eq__(other)
def __eq__(self, other):
return fetch.apt_pkg.version_compare(self.version, other.version) == 0
def __ne__(self, other):
return not self.__eq__(other)
def __repr__(self):
return self.version
def __hash__(self):
return hash(repr(self))
class NFSShareNotMountedException(Exception):
"""Signal that the trilio nfs share is not mount"""
@ -85,7 +154,248 @@ def _install_triliovault(charm):
reactive.clear_flag('upgrade.triliovault')
class TrilioVaultCharm(charms_openstack.charm.HAOpenStackCharm):
def get_trilio_codename_install_source(trilio_source):
"""Derive codename from trilio source string.
Try and derive a trilio version from a deb string like:
'deb [trusted=yes] https://apt.fury.io/triliodata-4-0/ /'
:param trilio_source: Trilio source
:type trilio_source: str
:returns: Trilio version
:rtype: str
:raises: AssertionError
"""
deb_url = trilio_source.split()[-2]
code = re.findall(r'-(\d*-\d*)', urlparse(deb_url).path)
assert len(code) == 1, "Cannot derive release from {}".format(deb_url)
new_os_rel = code[0].replace('-', '.')
return new_os_rel
def make_trilio_get_charm_instance_handler():
"""This handler sets the get_charm_instance function.
"""
@charms_openstack.charm.core.register_get_charm_instance
def get_trilio_charm_instance(release=None, package_type='deb', *args,
**kwargs):
"""Get an instance of the charm based on the release (or use the
default if release is None).
Note that it passes args and kwargs to the class __init__() method.
:param release: String representing release wanted. Should be of the
form '<openstack_release>_<trilio_release>'
eg 'queens_4.0'
:type release: str
:param package_type: The package type required
:type package_type: str
:returns: Charm class
:rtype: BaseOpenStackCharm() derived class according to cls.releases
"""
cls = None
known_os_releases = sorted(_trilio_releases.keys())
if release is None:
# If release is None then select the class(es) which supports the
# most recent OpenStack release, from within this set select the
# class that supports the most recent Trilio release.
os_release = known_os_releases[-1]
known_trilio_releases = sorted(_trilio_releases[os_release].keys())
trilio_release = known_trilio_releases[-1]
cls = _trilio_releases[os_release][trilio_release][package_type]
else:
os_release, trilio_release = release.split('_')
trilio_release = AptPkgVersion(trilio_release)
if os_release not in os_utils.OPENSTACK_RELEASES:
raise RuntimeError(
"Release {} is not a known OpenStack release?".format(
os_release))
os_release_index = os_utils.OPENSTACK_RELEASES.index(os_release)
if (os_release_index <
os_utils.OPENSTACK_RELEASES.index(known_os_releases[0])):
raise RuntimeError(
"Release {} is not supported by this charm. Earliest "
"support is {} release".format(
os_release,
known_os_releases[0]))
else:
known_trilio_releases = []
# Search through the dictionary of registered charm classes
# looking for the most recent group which can support
# `os_release`
for known_os_release in reversed(known_os_releases):
_idx = os_utils.OPENSTACK_RELEASES.index(known_os_release)
if os_release_index >= _idx:
trilio_classes = _trilio_releases[known_os_release]
known_trilio_releases = sorted(trilio_classes.keys())
break
# Search through the dictionary of registered charm classes
# that support `known_os_release` onwards and look for the
# class # which supports the most recent trilio release which
# is <= `trilio_release`
for known_trilio_release in reversed(known_trilio_releases):
if known_trilio_release <= trilio_release:
cls = trilio_classes[known_trilio_release][
package_type]
# Found a class so exit loop
break
if cls is None:
raise RuntimeError("Release {} is not supported".format(release))
return cls(release=os_release, *args, **kwargs)
def make_trilio_handlers():
"""This handler sets the trilio release selector get_charm_instance funcs.
"""
make_trilio_get_charm_instance_handler()
make_trilio_select_release_handler()
def make_trilio_select_release_handler():
"""This handler sets the release selector function.
"""
@charms_openstack.charm.core.register_os_release_selector
def select_trilio_release():
"""Determine the OpenStack and Trilio release
Determine the OpenStack release based on the `singleton.os_release_pkg`
that is installed. If it is not installed look for and exanine other
semantic versioned packages. If both those tactics fail fall back to
checking the charm `openstack-origin` option.
Determine the Trilio release based on the `singleton.version_package`
that is installed. If it is not installed fall back to checking the
charm `triliovault-pkg-source` option.
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.
"""
singleton = None
# Search for target OpenStack Release
os_release_version = unitdata.kv().get(
charms_openstack.charm.core.OPENSTACK_RELEASE_KEY,
None)
if os_release_version is None:
try:
# First make an attempt of determining release from a charm
# instance defined package codename dictionary.
singleton = charms_openstack.charm.core.get_charm_instance()
if singleton.release_pkg is None:
raise RuntimeError("release_pkg is not set")
os_release_version = singleton.get_os_codename_package(
singleton.os_release_pkg, singleton.package_codenames,
apt_cache_sufficient=(not singleton.source_config_key))
if os_release_version is None:
# Surprisingly get_os_codename_package called with
# ``Fatal=True`` does not raise an error when the charm
# class ``package_codenames`` map does not contain package
# or major version. We'll handle it here instead of
# changing the API of the method.
raise ValueError
except (AttributeError, ValueError):
try:
pkgs = os_utils.get_installed_semantic_versioned_packages()
pkg = pkgs[0]
except IndexError:
# A non-existent package will cause os_release to try other
# tactics for deriving the release.
pkg = 'dummy-package'
os_release_version = os_utils.os_release(
pkg, source_key=singleton.source_config_key)
unitdata.kv().set(
charms_openstack.charm.core.OPENSTACK_RELEASE_KEY,
os_release_version)
unitdata.kv().flush()
# Search for target Trilio Release
trilio_release_version = unitdata.kv().get(TRILIO_RELEASE_KEY, None)
if trilio_release_version is None:
if not singleton:
singleton = charms_openstack.charm.core.get_charm_instance()
if singleton.version_package is None:
raise RuntimeError("version_package is not set")
try:
trilio_release_version = singleton.get_package_version(
singleton.version_package)
except (AttributeError, ValueError):
trilio_release_version = get_trilio_codename_install_source(
singleton.trilio_source)
unitdata.kv().set(TRILIO_RELEASE_KEY, trilio_release_version)
unitdata.kv().flush()
return '{}_{}'.format(os_release_version, trilio_release_version)
class BaseTrilioCharmMeta(charms_openstack.charm.core.BaseOpenStackCharmMeta):
"""Metaclass to handle registering charm classes by their supported
OpenStack release, Trilio release and package typea
_trilio_releases has the form::
{
'Openstack Code Name': {
'Trilio Package Veersion': {
'Package Type': <charm class>}},
"""
def __init__(cls, name, mro, members):
"""Receive the BaseOpenStackCharm() (derived) class and store the
release that it works against. Each class defines a 'release' which
corresponds to the Openstack release that it handles. The class should
also specify 'trilio_release' which defines the Trilio releases it can
handle.
:param name: string for class name.
:param mro: tuple of base classes.
:param members: dictionary of name to class attribute (f, p, a, etc.)
"""
# Do not attempt to calculate the release for an abstract class
if members.get('abstract_class', False):
return
if all(key in members.keys() for key in ['release', 'trilio_release']):
package_type = members.get('package_type', 'deb')
if package_type not in ('deb', 'snap'):
raise RuntimeError(
"Package type {} is not a known type"
.format(package_type))
release = members['release']
trilio_release = AptPkgVersion(members['trilio_release'])
if release not in os_utils.OPENSTACK_RELEASES:
raise RuntimeError(
"Release {} is not a known OpenStack release"
.format(release))
try:
_pre = _trilio_releases[release][trilio_release][package_type]
except KeyError:
# All good this comination has not been registered yet.
pass
else:
raise RuntimeError(
"Release {} + {} defined more than once in classes {} and "
"{} (at least)"
.format(release,
trilio_release,
_pre.__name__,
name))
# store the class against the release.
if release not in _trilio_releases:
_trilio_releases[release] = {}
if trilio_release not in _trilio_releases[release]:
_trilio_releases[release][trilio_release] = {}
_trilio_releases[release][trilio_release][package_type] = cls
else:
raise RuntimeError(
"class '{}' must define both the release it supports using "
"the 'release' class property and the trilio release it "
"supports using the 'trilio_release' class property.".format(
name))
class TrilioVaultCharmMixin():
"""The TrilioVaultCharm class provides common specialisation of certain
functions for the Trilio charm set and is designed for use alongside
other base charms.openstack classes
@ -94,7 +404,11 @@ class TrilioVaultCharm(charms_openstack.charm.HAOpenStackCharm):
abstract_class = True
def __init__(self, **kwargs):
super(TrilioVaultCharm, self).__init__(**kwargs)
try:
del kwargs['trilio_release']
except KeyError:
pass
super().__init__(**kwargs)
def configure_source(self):
"""Configure triliovault specific package sources in addition to
@ -114,17 +428,112 @@ class TrilioVaultCharm(charms_openstack.charm.HAOpenStackCharm):
super().series_upgrade_complete()
self.configure_source()
@property
def trilio_source(self):
"""Trilio source config option"""
return self.config.get("triliovault-pkg-source")
class TrilioVaultSubordinateCharm(charms_openstack.charm.OpenStackCharm):
"""The TrilioVaultSubordinateCharm class provides common specialisation
of certain functions for the Trilio charm set and is designed for use
alongside other base charms.openstack classes for subordinate charms
"""
def do_trilio_pkg_upgrade(self):
"""Upgrade Trilio packages
"""
new_os_rel = get_trilio_codename_install_source(
self.trilio_source)
ch_core.hookenv.log('Performing Trilio upgrade to %s.' % (new_os_rel))
dpkg_opts = [
'--option', 'Dpkg::Options::=--force-confnew',
'--option', 'Dpkg::Options::=--force-confdef',
]
fetch.apt_update()
fetch.apt_install(
packages=self.all_packages,
options=dpkg_opts,
fatal=True)
self.remove_obsolete_packages()
def do_trilio_upgrade_db_migration(self):
"""Run Trilio DB sync
Trilio charms sync_cmd refers to a trilio db sync.
"""
super().do_openstack_upgrade_db_migration()
def run_trilio_upgrade(self, interfaces_list=None):
"""
:param interfaces_list: List of instances of interface classes
:returns: None
"""
ch_core.hookenv.status_set('maintenance', 'Running openstack upgrade')
cur_os_release = self.get_os_codename_package(
self.os_release_pkg,
self.package_codenames)
new_trilio_release = get_trilio_codename_install_source(
self.trilio_source)
new_release = '{}_{}'.format(cur_os_release, new_trilio_release)
unitdata.kv().set(TRILIO_RELEASE_KEY, new_trilio_release)
_configure_triliovault_source()
target_charm = charms_openstack.charm.core.get_charm_instance(
new_release)
target_charm.do_trilio_pkg_upgrade()
target_charm.render_with_interfaces(interfaces_list)
target_charm.do_trilio_upgrade_db_migration()
def trilio_upgrade_available(self, package=None):
"""Check if an OpenStack upgrade is available
:param package: str Package name to use to check upgrade availability
:returns: bool
"""
cur_vers = self.get_package_version(package)
avail_vers = get_trilio_codename_install_source(
self.trilio_source)
return fetch.apt_pkg.version_compare(avail_vers, cur_vers) == 1
def upgrade_if_available(self, interfaces_list):
if self.openstack_upgrade_available(self.os_release_pkg):
if self.config.get('action-managed-upgrade', False):
ch_core.hookenv.log('Not performing OpenStack upgrade as '
'action-managed-upgrade is enabled')
else:
self.run_upgrade(interfaces_list=interfaces_list)
if self.trilio_upgrade_available(
package=self.trilio_version_package()):
if self.config.get('action-managed-upgrade', False):
ch_core.hookenv.log('Not performing Trilio upgrade as '
'action-managed-upgrade is enabled')
else:
self.run_trilio_upgrade(interfaces_list=interfaces_list)
@classmethod
def trilio_version_package(cls):
raise NotImplementedError
@property
def version_package(self):
return self.trilio_version_package()
@property
def release_pkg(self):
return self.trilio_version_package()
@classmethod
def release_pkg_version(cls):
return cls.get_package_version(cls.trilio_version_package())
class TrilioVaultCharm(TrilioVaultCharmMixin,
charms_openstack.charm.HAOpenStackCharm,
metaclass=BaseTrilioCharmMeta):
abstract_class = True
def __init__(self, **kwargs):
super(TrilioVaultSubordinateCharm, self).__init__(**kwargs)
class TrilioVaultSubordinateCharm(TrilioVaultCharmMixin,
charms_openstack.charm.OpenStackCharm,
metaclass=BaseTrilioCharmMeta):
abstract_class = True
def configure_source(self):
"""Configure TrilioVault specific package sources
@ -132,17 +541,6 @@ class TrilioVaultSubordinateCharm(charms_openstack.charm.OpenStackCharm):
_configure_triliovault_source()
fetch.apt_update(fatal=True)
def install(self):
"""Install packages dealing with Trilio nuances for upgrades as well
"""
self.configure_source()
_install_triliovault(self)
def series_upgrade_complete(self):
"""Re-configure sources post series upgrade"""
super().series_upgrade_complete()
self.configure_source()
class TrilioVaultCharmGhostAction(object):
"""Shared 'ghost share' action for TrilioVault charms

View File

@ -375,12 +375,22 @@ class TestBaseOpenStackCharmAssessStatus(BaseOpenStackCharmTest):
class TestMyOpenStackCharm(BaseOpenStackCharmTest):
def setUp(self):
self.save_rsf = chm_core._release_selector_function
chm_core._release_selector_function = None
self.save_cif = chm_core._get_charm_instance_function
chm_core._get_charm_instance_function = None
def make_open_stack_charm():
return MyOpenStackCharm(['interface1', 'interface2'])
super(TestMyOpenStackCharm, self).setUp(make_open_stack_charm,
TEST_CONFIG)
def tearDown(self):
chm_core._release_selector_function = self.save_rsf
chm_core._get_charm_instance_function = self.save_cif
super().tearDown()
def test_singleton(self):
# because we have two releases, we expect this to be the latter.
# e.g. MyNextOpenStackCharm

View File

@ -2,8 +2,9 @@ import unittest.mock as mock
import os
from unit_tests.charms_openstack.charm.utils import BaseOpenStackCharmTest
from unit_tests.utils import patch_open
from unit_tests.utils import BaseTestCase, patch_open
import charms_openstack.charm.core as co_core
import charms_openstack.plugins.trilio as trilio
@ -12,6 +13,11 @@ class TrilioVaultFoobar(trilio.TrilioVaultCharm):
abstract_class = True
name = 'test'
all_packages = ['foo', 'bar']
os_release_pkg = 'nova-common'
@classmethod
def trilio_version_package(cls):
return "dmapi"
class TrilioVaultFoobarSubordinate(trilio.TrilioVaultSubordinateCharm):
@ -114,6 +120,7 @@ class TestTrilioCommonBehaviours(BaseOpenStackCharmTest):
self.patch_object(trilio.ch_core.hookenv, "status_set")
self.patch_object(trilio.fetch, "filter_installed_packages")
self.patch_object(trilio.fetch, "apt_install")
self.patch_object(trilio.fetch.apt_pkg, 'version_compare')
self.patch_object(trilio.reactive, "is_flag_set")
self.patch_object(trilio.reactive, "clear_flag")
self.patch_target('update_api_ports')
@ -162,13 +169,216 @@ class TestTrilioCommonBehaviours(BaseOpenStackCharmTest):
)
_file.write.assert_called_once_with('testsource')
def test_trilio_properties(self):
cls_mock = mock.MagicMock()
cls_mock.charm_instance.release_pkg_version = lambda: '4.0'
self.version_compare.return_value = 0
self.assertEqual(
trilio.trilio_properties(cls_mock),
{'db_type': 'dedicated', 'transport_type': 'dmapi'})
self.version_compare.return_value = -1
self.assertEqual(
trilio.trilio_properties(cls_mock),
{'db_type': 'legacy', 'transport_type': 'legacy'})
def test_get_trilio_codename_install_source(self):
self.assertEqual(
trilio.get_trilio_codename_install_source(
'deb [trusted=yes] https://apt.fury.io/triliodata-4-0/ /'),
'4.0')
self.assertEqual(
trilio.get_trilio_codename_install_source(
'deb [trusted=yes] https://apt.fury.io/triliodata-4-0-0/ /'),
'4.0')
with self.assertRaises(AssertionError):
trilio.get_trilio_codename_install_source(
'deb [trusted=yes] https://apt.fury.io/triliodata/ /')
def test_get_trilio_charm_instance(self):
_safe_gcif = co_core._get_charm_instance_function
co_core._get_charm_instance_function = None
class BaseClass():
def __init__(self, release, *args, **kwargs):
pass
class Pike39(BaseClass):
release = 'pike'
trilio_release = '3.9'
class Queens40(BaseClass):
release = 'queens'
trilio_release = '4.0'
class Queens41(BaseClass):
release = 'queens'
trilio_release = '4.1'
class Rocky40(BaseClass):
release = 'rocky'
trilio_release = '4.0'
def _version_compare(ver1, ver2):
if float(ver1) > float(ver2):
return 1
elif float(ver1) < float(ver2):
return -1
else:
return 0
save_releases = trilio._trilio_releases
self.version_compare.side_effect = _version_compare
trilio._trilio_releases = {
'pike': {
trilio.AptPkgVersion('3.9'): {
'deb': Pike39}},
'queens': {
trilio.AptPkgVersion('4.0'): {
'deb': Queens40},
trilio.AptPkgVersion('4.1'): {
'deb': Queens41}},
'rocky': {
trilio.AptPkgVersion('4.0'): {
'deb': Rocky40}}}
trilio.make_trilio_get_charm_instance_handler()
# Check with no release being supplied. Should return the
# highest release class.
self.assertIsInstance(
co_core.get_charm_instance(),
Rocky40)
self.assertIsInstance(
co_core.get_charm_instance(release='queens_4.0'),
Queens40)
self.assertIsInstance(
co_core.get_charm_instance(release='queens_4.1'),
Queens41)
# Ensure an error is raised if a class satisfying the trilio condition
# is not found for the highest matching OpenStack class.
with self.assertRaises(RuntimeError):
co_core.get_charm_instance(release='rocky_3.9')
# Match the openstack release and then the closest trilio releases
# within that subset.
self.assertIsInstance(
co_core.get_charm_instance(release='rocky_4.1'),
Rocky40)
with self.assertRaises(RuntimeError):
co_core.get_charm_instance(release='icehouse_4.1')
trilio._trilio_releases = save_releases
co_core._get_charm_instance_function = _safe_gcif
def test_select_trilio_release(self):
def get_charm_class(release_pkg='trilio_pkg', package_version='4.0',
os_codename_exception=None,
version_package='trilio_pkg',
package_version_exception=None,
os_release_pkg='nova_pkg',
os_codename_pkg='queens',
trilio_source='deb https://a.io/trilio-4-2-0/ /'):
class _TrilioCharm():
def __init__(self):
self.release_pkg = release_pkg
self.version_package = version_package
self.os_release_pkg = os_release_pkg
self.source_config_key = 'openstack-origin'
self.package_codenames = {}
self.package_version = package_version
self.os_codename_exception = os_codename_exception
self.os_codename_pkg = os_codename_pkg
self.trilio_source = trilio_source
@staticmethod
def get_os_codename_package(pkg, code_names,
apt_cache_sufficient=True):
if os_codename_exception:
raise os_codename_exception
else:
return os_codename_pkg
@staticmethod
def get_package_version(pkg, apt_cache_sufficient=True):
if package_version_exception:
raise package_version_exception
else:
return package_version
return _TrilioCharm()
_safe_rsf = co_core._release_selector_function
co_core._release_selector_function = None
self.patch_object(
trilio.os_utils,
"get_installed_semantic_versioned_packages")
self.patch_object(trilio.os_utils, "os_release")
self.patch_object(trilio.unitdata, "kv")
kv_mock = mock.MagicMock()
self.kv.return_value = kv_mock
kv_mock.get.return_value = None
self.patch_object(
trilio.charms_openstack.charm.core,
"get_charm_instance")
trilio.make_trilio_get_charm_instance_handler()
trilio.make_trilio_select_release_handler()
select_trilio_release = co_core._release_selector_function
self.get_charm_instance.return_value = get_charm_class()
self.assertEqual(
select_trilio_release(),
'queens_4.0')
# Check RuntimeError is raised if release_pkg is missing from charm
# class
self.get_charm_instance.return_value = get_charm_class(
release_pkg=None)
with self.assertRaises(RuntimeError):
select_trilio_release()
# Test falling back to get_installed_semantic_versioned_packages
self.os_release.return_value = 'pike'
self.get_installed_semantic_versioned_packages.reset_mock()
self.get_installed_semantic_versioned_packages.return_value = ['nova']
self.get_charm_instance.return_value = get_charm_class(
os_codename_pkg=None)
self.assertEqual(
select_trilio_release(),
'pike_4.0')
# Check RuntimeError is raised if version_package is missing from charm
# class
self.get_charm_instance.return_value = get_charm_class(
version_package=None)
with self.assertRaises(RuntimeError):
select_trilio_release()
# Test falling back to get_trilio_codename_install_source
self.get_charm_instance.return_value = get_charm_class(
package_version_exception=ValueError)
self.assertEqual(
select_trilio_release(),
'queens_4.2')
co_core._release_selector_function = _safe_rsf
class TestTrilioVaultCharm(BaseOpenStackCharmTest):
def setUp(self):
super().setUp(TrilioVaultFoobar, {})
self.patch_object(trilio.ch_core.hookenv, "log")
self.patch_object(trilio.ch_core.hookenv, "status_set")
self.patch_object(
trilio.charms_openstack.charm.core,
"get_charm_instance")
self.patch_object(trilio, "_install_triliovault")
self.patch_object(trilio, "_configure_triliovault_source")
self.patch_object(trilio.fetch, "apt_update")
self.patch_object(trilio.fetch, "apt_install")
self.patch_object(trilio.fetch.apt_pkg, "version_compare")
self.patch_target('config')
self._conf = {
'triliovault-pkg-source': 'deb https://a.io/trilio-4-2-0/ /'
}
self.config.get.side_effect = lambda x, b=None: self._conf.get(x, b)
def test_series_upgrade_complete(self):
self.patch_object(trilio.charms_openstack.charm.HAOpenStackCharm,
@ -190,6 +400,66 @@ class TestTrilioVaultCharm(BaseOpenStackCharmTest):
self.target.install()
self._install_triliovault.assert_called_once_with(self.target)
def test_trilio_source(self):
self.assertEqual(
self.target.trilio_source,
'deb https://a.io/trilio-4-2-0/ /')
def test_do_trilio_pkg_upgrade(self):
self.target.do_trilio_pkg_upgrade()
self.apt_update.assert_called_once_with()
self.apt_install.assert_called_once_with(
packages=['foo', 'bar'],
options=[
'--option', 'Dpkg::Options::=--force-confnew',
'--option', 'Dpkg::Options::=--force-confdef'],
fatal=True)
def test_run_trilio_upgrade(self):
self.patch_target('get_os_codename_package')
self.get_os_codename_package.return_value = 'queens'
charm_cls = mock.MagicMock()
interface_mocks = [mock.MagicMock(), mock.MagicMock()]
self.get_charm_instance.return_value = charm_cls
self.target.run_trilio_upgrade(interfaces_list=interface_mocks)
self._configure_triliovault_source.assert_called_once_with()
charm_cls.do_trilio_pkg_upgrade.assert_called_once_with()
charm_cls.render_with_interfaces.assert_called_once_with(
interface_mocks)
charm_cls.do_trilio_upgrade_db_migration.assert_called_once_with()
def test_trilio_upgrade_available(self):
self.patch_target('get_package_version')
self.get_package_version.return_value = '4.1'
self.version_compare.return_value = 1
self.assertTrue(self.target.trilio_upgrade_available())
self.version_compare.assert_called_once_with('4.2', '4.1')
def test_upgrade_if_available(self):
self.patch_target('openstack_upgrade_available')
self.patch_target('trilio_upgrade_available')
self.patch_target('run_upgrade')
self.patch_target('run_trilio_upgrade')
interface_mocks = [mock.MagicMock(), mock.MagicMock()]
self._conf['action-managed-upgrade'] = False
self.openstack_upgrade_available.return_value = True
self.trilio_upgrade_available.return_value = True
self.target.upgrade_if_available(interface_mocks)
self.run_upgrade.assert_called_once_with(
interfaces_list=interface_mocks)
self.run_trilio_upgrade.assert_called_once_with(
interfaces_list=interface_mocks)
self.run_upgrade.reset_mock()
self.run_trilio_upgrade.reset_mock()
self._conf['action-managed-upgrade'] = True
self.openstack_upgrade_available.return_value = True
self.trilio_upgrade_available.return_value = True
self.target.upgrade_if_available(interface_mocks)
self.assertFalse(self.run_upgrade.called)
self.assertFalse(self.run_trilio_upgrade.called)
class TestTrilioVaultSubordinateCharm(BaseOpenStackCharmTest):
@ -197,13 +467,7 @@ class TestTrilioVaultSubordinateCharm(BaseOpenStackCharmTest):
super().setUp(TrilioVaultFoobarSubordinate, {})
self.patch_object(trilio, "_install_triliovault")
self.patch_object(trilio, "_configure_triliovault_source")
def test_series_upgrade_complete(self):
self.patch_object(trilio.charms_openstack.charm.OpenStackCharm,
'series_upgrade_complete')
self.patch_target('configure_source')
self.target.series_upgrade_complete()
self.configure_source.assert_called_once_with()
self.patch_object(trilio.fetch, "apt_update")
def test_configure_source(self):
self.patch_object(trilio.charms_openstack.charm.OpenStackCharm,
@ -211,9 +475,100 @@ class TestTrilioVaultSubordinateCharm(BaseOpenStackCharmTest):
self.target.configure_source()
self._configure_triliovault_source.assert_called_once_with()
self.configure_source.assert_not_called()
self.apt_update.assert_called_once_with(fatal=True)
def test_install(self):
self.patch_object(trilio.charms_openstack.charm.OpenStackCharm,
'configure_source')
self.target.install()
self._install_triliovault.assert_called_once_with(self.target)
class TestBaseTrilioCharmMeta(BaseTestCase):
def setUp(self):
self.save_releases = trilio._trilio_releases
super().setUp()
self.patch_object(trilio.fetch.apt_pkg, 'version_compare')
def _version_compare(ver1, ver2):
if float(ver1) > float(ver2):
return 1
elif float(ver1) < float(ver2):
return -1
else:
return 0
self.version_compare.side_effect = _version_compare
def tearDown(self):
super().tearDown()
trilio._trilio_releases = self.save_releases
def register_classes(self):
class TrilioQueens40(metaclass=trilio.BaseTrilioCharmMeta):
release = 'queens'
trilio_release = '4.0'
class TrilioQueens41(metaclass=trilio.BaseTrilioCharmMeta):
release = 'queens'
trilio_release = '4.1'
class TrilioRocky40(metaclass=trilio.BaseTrilioCharmMeta):
release = 'rocky'
trilio_release = '4.0'
return {
'queens_4.0': TrilioQueens40,
'queens_4.1': TrilioQueens41,
'rocky_4.0': TrilioRocky40}
def register_classes_missing_key(self):
class TrilioQueens40(metaclass=trilio.BaseTrilioCharmMeta):
release = 'queens'
def register_classes_wrong_pkg_type(self):
class TrilioQueens40(metaclass=trilio.BaseTrilioCharmMeta):
release = 'queens'
trilio_release = '4.1'
package_type = 'up2date'
def register_classes_duplicate(self):
class TrilioQueens40A(metaclass=trilio.BaseTrilioCharmMeta):
release = 'queens'
trilio_release = '4.0'
class TrilioQueens40B(metaclass=trilio.BaseTrilioCharmMeta):
release = 'queens'
trilio_release = '4.0'
def test_class_register(self):
charm_classes = self.register_classes()
self.maxDiff = None
self.assertEqual(
trilio._trilio_releases,
{
'queens': {
trilio.AptPkgVersion('4.0'): {
'deb': charm_classes['queens_4.0']},
trilio.AptPkgVersion('4.1'): {
'deb': charm_classes['queens_4.1']}},
'rocky': {
trilio.AptPkgVersion('4.0'): {
'deb': charm_classes['rocky_4.0']}}})
def test_class_register_missing_key(self):
with self.assertRaises(RuntimeError):
self.register_classes_missing_key()
def test_class_register_wrong_pkg_type(self):
with self.assertRaises(RuntimeError):
self.register_classes_wrong_pkg_type()
def test_class_register_duplicate(self):
with self.assertRaises(RuntimeError):
self.register_classes_duplicate()