Add snap support

Update core charms.openstack classes to support installation
from snap as an alternative to installation from deb packages.

Introduce a new property 'package_type' supported on the
OpenStackCharm base class which can be set to 'deb' (default) or
'snap' to indicate which type of install  a specific OpenStackCharm
subclass supports.  The list of snaps is detailed in the 'snaps'
property, and the mode for installation of snaps is configured using
the 'snap_mode' property; as for packages, a specific snap can be
chosen to drive the application version that is used for the
deployed workload.

This is supported by an additional default package type selector
which, in conjunction with the existing release selector, will
pick the correct subclass of the OpenStackCharm class to use for
the configured installation source.

Snap installation is configured using the openstack-origin
charm configuration option - for example:

    openstack-origin=snap:ocata/stable

If no suitable class can be discovered for the configured
origin, the charm will throw an error as it does today for
missing openstack release support for deb based installations.

The linked gerrit review is the first charm implementation of
snap support based on these set of changes.

Required-By: I464025a2b72aba8c31a4a97ade39d2b2980c3a92
Change-Id: Iea33a939a6422da94d3c2c5b9a0748a47bfde11a
This commit is contained in:
Corey Bryant 2017-09-27 20:34:53 +00:00
parent 42ee4248b7
commit 43f32232aa
8 changed files with 513 additions and 51 deletions

View File

@ -660,6 +660,14 @@ class APIConfigurationAdapter(ConfigurationAdapter):
"""
return charms.reactive.bus.get_state('ssl.enabled')
@property
def ssl(self):
"""Whether SSL is being used for this service
@return True is SSL has been enable
"""
return charms.reactive.bus.get_state('ssl.enabled')
def determine_service_port(self, port):
"""Calculate port service should use given external port

View File

@ -16,6 +16,7 @@ from charms_openstack.charm.core import (
BaseOpenStackCharm,
BaseOpenStackCharmActions,
BaseOpenStackCharmAssessStatus,
get_snap_version,
)
from charms_openstack.charm.utils import (
get_upstream_version,
@ -50,15 +51,28 @@ class OpenStackCharm(BaseOpenStackCharm,
# first_release = this is the first release in which this charm works
release = 'icehouse'
# package type - package type (deb or snap) in which this charm works
package_type = 'deb'
# The name of the charm (for printing, etc.)
name = 'charmname'
# List of packages to install
packages = []
# List of snaps to install
snaps = []
# Mode to install snaps in (jailmode/devmode/classic)
snap_mode = 'jailmode'
# Package to determine application version from
# defaults to first in packages if not provided
version_package = None
version_package = release_pkg = None
# Snap to determine application version from;
# defaults to first in snaps if not provided
version_snap = release_snap = None
# Keystone endpoint type
service_type = None
@ -121,15 +135,26 @@ class OpenStackCharm(BaseOpenStackCharm,
@property
def application_version(self):
"""Return the current version of the application being deployed by
the charm, as indicated by the version_package attribute
the charm, as indicated by the version_package or version_snap
attribute
"""
if not self.version_package:
self.version_package = self.packages[0]
version = get_upstream_version(
self.version_package
)
if not version:
version = os_utils.os_release(self.version_package)
if os_utils.snap_install_requested():
if not self.version_snap:
self.version_snap = self.snaps[0]
version = get_snap_version(self.version_snap,
fatal=False)
if not version:
version = os_utils.get_os_codename_install_source(
self.config['openstack-origin']
)
else:
if not self.version_package:
self.version_package = self.packages[0]
version = get_upstream_version(
self.version_package
)
if not version:
version = os_utils.os_release(self.version_package)
return version
@ -250,6 +275,14 @@ class OpenStackAPICharm(OpenStackCharm):
return (super(OpenStackAPICharm, self).all_packages +
self.token_cache_pkgs())
@property
def all_snaps(self):
"""List of snaps to be installed
@return ['snap1', 'snap2', ...]
"""
return super().all_snaps
@property
def full_restart_map(self):
"""Map of services to be restarted if a file changes
@ -278,7 +311,7 @@ class HAOpenStackCharm(OpenStackAPICharm):
self.set_haproxy_stat_password()
@property
def apache_vhost_file(self):
def apache_ssl_vhost_file(self):
"""Apache vhost for SSL termination
:returns: string
@ -291,8 +324,8 @@ class HAOpenStackCharm(OpenStackAPICharm):
Enable Apache vhost for SSL termination if vhost exists and it is not
curently enabled
"""
if not os.path.exists(self.apache_vhost_file):
open(self.apache_vhost_file, 'a').close()
if not os.path.exists(self.apache_ssl_vhost_file):
open(self.apache_ssl_vhost_file, 'a').close()
check_enabled = subprocess.call(
['a2query', '-s', 'openstack_https_frontend'])
@ -315,10 +348,20 @@ class HAOpenStackCharm(OpenStackAPICharm):
_packages = super(HAOpenStackCharm, self).all_packages
if self.haproxy_enabled():
_packages.append('haproxy')
if self.apache_enabled():
_packages.append('apache2')
if not os_utils.snap_install_requested():
if self.apache_enabled():
_packages.append('apache2')
return _packages
@property
def all_snaps(self):
"""List of snaps to be installed
@return ['snap1', 'snap2', ...]
"""
_snaps = super().all_snaps
return _snaps
@property
def full_restart_map(self):
"""Map of services to be restarted if a file changes
@ -332,16 +375,33 @@ class HAOpenStackCharm(OpenStackAPICharm):
_restart_map = super(HAOpenStackCharm, self).full_restart_map
if self.haproxy_enabled():
_restart_map[self.HAPROXY_CONF] = ['haproxy']
if self.apache_enabled():
_restart_map[self.apache_vhost_file] = ['apache2']
if os_utils.snap_install_requested():
# TODO(coreycb): add nginx config/service for ssl vhost
pass
else:
if self.apache_enabled():
_restart_map[self.apache_ssl_vhost_file] = ['apache2']
return _restart_map
def apache_enabled(self):
"""Determine if apache is being used
@return True if apache is being used"""
return (self.get_state('ssl.enabled') or
self.get_state('ssl.requested'))
if os_utils.snap_install_requested():
return False
else:
return (self.get_state('ssl.enabled') or
self.get_state('ssl.requested'))
def nginx_ssl_enabled(self):
"""Determine if nginx is being used
@return True if nginx is being used"""
if os_utils.snap_install_requested():
return (self.get_state('ssl.enabled') or
self.get_state('ssl.requested'))
else:
return False
def haproxy_enabled(self):
"""Determine if haproxy is fronting the services
@ -394,6 +454,8 @@ class HAOpenStackCharm(OpenStackAPICharm):
def enable_apache_modules(self):
"""Enable Apache modules needed for SSL termination"""
if os_utils.snap_install_requested():
return
restart = False
for module in ['ssl', 'proxy', 'proxy_http']:
check_enabled = subprocess.call(['a2query', '-m', module])
@ -412,9 +474,14 @@ class HAOpenStackCharm(OpenStackAPICharm):
@param key string SSL Key
@param cn string Canonical name for service
"""
if os_utils.snap_install_requested():
ssl_dir = '/var/snap/{snap_name}/etc/nginx/ssl'.format(
snap_name=self.primary_snap)
else:
ssl_dir = os.path.join('/etc/apache2/ssl/', self.name)
if not cn:
cn = os_ip.resolve_address(endpoint_type=os_ip.INTERNAL)
ssl_dir = os.path.join('/etc/apache2/ssl/', self.name)
ch_host.mkdir(path=ssl_dir)
if cn:
cert_filename = 'cert_{}'.format(cn)
@ -535,7 +602,10 @@ class HAOpenStackCharm(OpenStackAPICharm):
self.configure_cert(
ssl['cert'], ssl['key'], cn=ssl['cn'])
self.configure_ca(ssl['ca'])
self.configure_apache()
if not os_utils.snap_install_requested():
self.configure_apache()
self.remove_state('ssl.requested')
self.set_state('ssl.enabled', True)
else:
@ -563,6 +633,7 @@ class HAOpenStackCharm(OpenStackAPICharm):
def configure_ca(self, ca_cert, update_certs=True):
"""Write Certificate Authority certificate"""
# TODO(jamespage): work this out for snap based installations
cert_file = (
'/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt')
if ca_cert:

View File

@ -36,6 +36,12 @@ _singleton = None
# This is to enable the defining code to define which release is used.
_release_selector_function = None
# `_package_type_selector_function` holds a function that optionally takes a
# package type and commutes it to another package type or just returns a
# package type. This is to enable the defining code to define which
# package type is used.
_package_type_selector_function = None
def optional_interfaces(args, *interfaces):
"""Return a tuple with possible optional interfaces
@ -85,7 +91,7 @@ class provide_charm_instance(object):
return False
def get_charm_instance(release=None, *args, **kwargs):
def get_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).
@ -96,17 +102,18 @@ def get_charm_instance(release=None, *args, **kwargs):
Note that it passes args and kwargs to the class __init__() method.
:param release: lc string representing release wanted.
:param package_type: string representing the package type required
:returns: BaseOpenStackCharm() derived class according to cls.releases
"""
if len(_releases.keys()) == 0:
raise RuntimeError(
"No derived BaseOpenStackCharm() classes registered")
# Note that this relies on OS releases being in alphabetica order
# Note that this relies on OS releases being in alphabetical order
known_releases = sorted(_releases.keys())
cls = None
if release is None:
# take the latest version of the charm if no release is passed.
cls = _releases[known_releases[-1]]
cls = _releases[known_releases[-1]][package_type]
else:
# check that the release is a valid release
if release not in os_utils.OPENSTACK_RELEASES:
@ -122,8 +129,9 @@ def get_charm_instance(release=None, *args, **kwargs):
# try to find the release that is supported.
for known_release in reversed(known_releases):
if (release_index >=
os_utils.OPENSTACK_RELEASES.index(known_release)):
cls = _releases[known_release]
os_utils.OPENSTACK_RELEASES.index(known_release) and
package_type in _releases[known_release]):
cls = _releases[known_release][package_type]
break
if cls is None:
raise RuntimeError("Release {} is not supported".format(release))
@ -154,6 +162,57 @@ def register_os_release_selector(f):
return f
def register_package_type_selector(f):
"""Register a function that determines what the package type is for the
invocation run. This allows the charm to define HOW the package type is
determined.
Usage:
@register_package_type_selector
def my_package_type_selector():
return package_type_chooser()
The function should return a string which is 'snap' or 'deb'.
"""
global _package_type_selector_function
if _package_type_selector_function is None:
# we can only do this once in a system invocation.
_package_type_selector_function = f
else:
raise RuntimeError(
"Only a single package_type_selector_function is supported."
" Called with {}".format(f.__name__))
return f
# TODO(jamespage): move to snap charmhelper
def get_snap_version(snap, fatal=True):
"""Determine version for an installed snap.
:param package: str Snap name to lookup (ie. in snap list)
:param fatal: bool Raise exception if snap not installed
:returns: str version of snap installed
"""
cmd = ['snap', 'list', snap]
try:
out = subprocess.check_output(cmd).decode('UTF-8')
except subprocess.CalledProcessError:
if not fatal:
return None
# the snap is unknown to snapd
e = ('Could not determine version of snap: {} as it\'s'
' not installed'.format(snap))
raise Exception(e)
lines = out.splitlines()
for line in lines:
if snap in line:
# Second item in list is version or a codename
return line.split()[1]
return None
class BaseOpenStackCharmMeta(type):
"""Metaclass to provide a classproperty of 'singleton' so that class
methods in the derived BaseOpenStackCharm() class can simply use
@ -190,18 +249,28 @@ class BaseOpenStackCharmMeta(type):
if members.get('abstract_class', False):
return
if 'release' in members.keys():
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']
if release not in os_utils.OPENSTACK_RELEASES:
raise RuntimeError(
"Release {} is not a known OpenStack release"
.format(release))
if release in _releases.keys():
if (release in _releases.keys() and
package_type in _releases[release].keys()):
raise RuntimeError(
"Release {} defined more than once in classes {} and {} "
" (at least)"
.format(release, _releases[release].__name__, name))
.format(release,
_releases[release][package_type].__name__,
name))
# store the class against the release.
_releases[release] = cls
if release not in _releases:
_releases[release] = {}
_releases[release][package_type] = cls
else:
raise RuntimeError(
"class '{}' does not define a release that it supports. "
@ -218,10 +287,14 @@ class BaseOpenStackCharmMeta(type):
global _singleton
if _singleton is None:
release = None
package_type = None
# see if a _release_selector_function has been registered.
if _release_selector_function is not None:
release = _release_selector_function()
_singleton = get_charm_instance(release=release)
if _package_type_selector_function is not None:
package_type = _package_type_selector_function()
_singleton = get_charm_instance(release=release,
package_type=package_type or 'deb')
return _singleton
@ -347,12 +420,48 @@ class BaseOpenStackCharm(object, metaclass=BaseOpenStackCharmMeta):
"""proxy for charms.reactive.bus.get_state()"""
return reactive.bus.get_state(state)
@staticmethod
def get_os_codename_snap(snap, codenames, fatal=True):
"""Derive OpenStack release codename from an installed snap.
:param package: str Snap name to lookup (ie. in snap list)
:param codenames: dict of OrderedDict
{
'snap1': collections.OrderedDict([
('2', 'mitaka'),
('3', 'newton'),
('4', 'ocata'), ]),
'snap2': collections.OrderedDict([
('12', 'mitaka'),
('13', 'newton'),
('14', 'ocata'), ]),
}
:param fatal: bool Raise exception if snap not installed
:returns: str OpenStack version name corresponding to package
"""
version_or_codename = get_snap_version(snap, fatal)
match = re.match('^(\d+)\.(\d+)', version_or_codename)
if match:
version = match.group(0)
# Generate a major version number for newer semantic
# versions of openstack projects
major_vers = version.split('.')[0]
try:
return codenames[snap][major_vers]
except KeyError:
# NOTE(jamespage): fallthrough to codename assumption
pass
# NOTE(jamespage): fallback to codename assumption
return version_or_codename
@staticmethod
def get_os_codename_package(package, codenames, fatal=True):
"""Derive OpenStack release codename from an installed package.
:param package: str Package name to lookup in apt cache
:param codenames: dict of OrderedDict eg
:param package: str Package name to lookup (ie. in apt cache)
:param codenames: dict of OrderedDict eg (not applicable for snap pkgs)
{
'pkg1': collections.OrderedDict([
('2', 'mitaka'),
@ -396,6 +505,26 @@ class BaseOpenStackCharm(object, metaclass=BaseOpenStackCharmMeta):
major_vers in codenames[package]):
return codenames[package][major_vers]
def get_os_version_snap(self, snap, fatal=True):
"""Derive OpenStack version number from an installed snap.
:param package: str Snap name to lookup in snap list
:param fatal: bool Raise exception if snap not installed
:returns: str OpenStack version number corresponding to snap
"""
if os_utils.snap_install_requested():
codename = self.get_os_codename_snap(snap,
self.snap_codenames,
fatal=fatal)
if not codename:
return None
for version, cname in os_utils.OPENSTACK_CODENAMES.items():
if cname == codename:
return version
return None
def get_os_version_package(self, package, fatal=True):
"""Derive OpenStack version number from an installed package.
@ -403,16 +532,18 @@ class BaseOpenStackCharm(object, metaclass=BaseOpenStackCharmMeta):
:param fatal: bool Raise exception if pkg not installed
:returns: str OpenStack version number corresponding to package
"""
codenames = self.package_codenames or os_utils.PACKAGE_CODENAMES
codename = self.get_os_codename_package(
package, codenames, fatal=fatal)
if not codename:
return None
if not os_utils.snap_install_requested():
codename = self.get_os_codename_package(
package, self.package_codenames or os_utils.PACKAGE_CODENAMES,
fatal=fatal)
if not codename:
return None
vers_map = os_utils.OPENSTACK_CODENAMES
for version, cname in vers_map.items():
if cname == codename:
return version
for version, cname in os_utils.OPENSTACK_CODENAMES.items():
if cname == codename:
return version
return None
class BaseOpenStackCharmActions(object):
@ -440,15 +571,48 @@ class BaseOpenStackCharmActions(object):
"""
return self.packages
@property
def all_snaps(self):
"""List of snaps to be installed
Relies on the class variable 'snaps'
@return ['snap1', 'snap2', ...]
"""
return self.snaps
@property
def primary_snap(self):
"""Primary snap to use for configuration
Relies on the class variable 'snaps'
:return string: first snap found in 'snaps'
"""
if self.snaps:
return self.snaps[0]
return None
def install(self):
"""Install packages related to this charm based on
contents of self.packages attribute.
"""Install packages or snaps related to this charm based on
contents of self.packages or self.snaps attribute.
"""
packages = fetch.filter_installed_packages(
self.all_packages)
if packages:
hookenv.status_set('maintenance', 'Installing packages')
fetch.apt_install(packages, fatal=True)
if os_utils.snap_install_requested():
if self.all_snaps:
hookenv.status_set('maintenance', 'Installing snaps')
os_utils.install_os_snaps(
os_utils.get_snaps_install_info_from_origin(
self.all_snaps,
self.config['openstack-origin'],
mode=self.snap_mode)
)
# AJK: we set this as charms can use it to detect installed state
self.set_state('{}-installed'.format(self.name))
self.update_api_ports()
@ -652,7 +816,7 @@ class BaseOpenStackCharmActions(object):
ports.append(line)
return ports
def openstack_upgrade_available(self, package=None):
def openstack_upgrade_available(self, package=None, snap=None):
"""Check if an OpenStack upgrade is available
:param package: str Package name to use to check upgrade availability
@ -660,10 +824,16 @@ class BaseOpenStackCharmActions(object):
"""
if not package:
package = self.release_pkg
if not snap:
snap = self.release_snap
src = self.config['openstack-origin']
cur_vers = self.get_os_version_package(package)
avail_vers = os_utils.get_os_version_install_source(src)
if os_utils.snap_install_requested():
cur_vers = self.get_os_version_snap(snap)
else:
cur_vers = self.get_os_version_package(package)
apt.init()
return apt.version_compare(avail_vers, cur_vers) == 1
@ -680,7 +850,7 @@ class BaseOpenStackCharmActions(object):
self.do_openstack_upgrade_db_migration()
def do_openstack_pkg_upgrade(self):
"""Upgrade OpenStack packages
"""Upgrade OpenStack packages and snaps
:returns: None
"""
@ -688,6 +858,15 @@ class BaseOpenStackCharmActions(object):
new_os_rel = os_utils.get_os_codename_install_source(new_src)
hookenv.log('Performing OpenStack upgrade to %s.' % (new_os_rel))
# TODO(jamespage): Deal with deb->snap->deb migrations
if os_utils.snap_install_requested() and self.all_snaps:
os_utils.install_os_snaps(
snaps=os_utils.get_snaps_install_info_from_origin(
self.all_snaps,
self.config['openstack-origin'],
mode=self.snap_mode),
refresh=True)
os_utils.configure_installation_source(new_src)
fetch.apt_update()
@ -722,6 +901,15 @@ class BaseOpenStackCharmActions(object):
else:
hookenv.log("Deferring DB sync to leader", level=hookenv.INFO)
# NOTE(jamespage): Not currently used - switch from c-h function for perf?
def snap_install_requested(self):
"""Determine whether a snap based install is configured
via the openstack-origin configuration option
:returns: None
"""
return self.options.openstack_origin.startswith('snap:')
class BaseOpenStackCharmAssessStatus(object):
"""Provides the 'Assess Status' functionality to the OpenStack charm class.

View File

@ -4,6 +4,7 @@ import charms.reactive as reactive
from charms_openstack.charm.classes import OpenStackCharm
from charms_openstack.charm.core import register_os_release_selector
from charms_openstack.charm.core import register_package_type_selector
# The default handlers that charms.openstack provides.
ALLOWED_DEFAULT_HANDLERS = [
@ -14,6 +15,7 @@ ALLOWED_DEFAULT_HANDLERS = [
'identity-service.available',
'config.changed',
'charm.default-select-release',
'charm.default-select-package-type',
'update-status',
'upgrade-charm',
]
@ -23,6 +25,7 @@ _default_handler_map = {}
# Used to store the discovered release version for caching between invocations
OPENSTACK_RELEASE_KEY = 'charmers.openstack-release-version'
OPENSTACK_PACKAGE_TYPE_KEY = 'charmers.openstack-package-type'
def use_defaults(*defaults):
@ -98,6 +101,31 @@ def make_default_select_release_handler():
return release_version
@_map_default_handler('charm.default-select-package-type')
def make_default_select_package_type_handler():
"""This handler is a bit more unusual, as it just sets the package type
selector using the @register_package_type_selector decorator
"""
@register_package_type_selector
def default_select_package_type():
"""Determine the package type (snap or deb) based on the
openstack-origin setting.
Note that this function caches the package type after the first
install so that it doesn't need to keep going and getting it from
the config information.
"""
package_type = unitdata.kv().get(OPENSTACK_PACKAGE_TYPE_KEY, None)
if package_type is None:
if os_utils.snap_install_requested():
package_type = 'snap'
else:
package_type = 'deb'
unitdata.kv().set(OPENSTACK_PACKAGE_TYPE_KEY, package_type)
return package_type
@_map_default_handler('amqp.connected')
def make_default_amqp_connection_handler():
"""Set the default amqp.connected state so that the default handler in

View File

@ -24,7 +24,9 @@ class MyOpenStackCharm(chm_classes.OpenStackCharm):
release = 'icehouse'
name = 'my-charm'
packages = ['p1', 'p2', 'p3', 'package-to-filter']
snaps = ['mysnap']
version_package = 'p2'
version_snap = 'mysnap'
api_ports = {
'service1': {
'public': 1,

View File

@ -8,7 +8,8 @@ from unit_tests.charms_openstack.charm.common import MyOpenStackCharm
import charms_openstack.charm.classes as chm
import charms_openstack.charm.core as chm_core
TEST_CONFIG = {'config': True}
TEST_CONFIG = {'config': True,
'openstack-origin': None}
class TestOpenStackCharm__init__(BaseOpenStackCharmTest):
@ -236,6 +237,8 @@ class TestMyOpenStackCharm(BaseOpenStackCharmTest):
self.patch_object(chm.os_utils, 'os_release')
self.patch_object(chm, 'get_upstream_version',
return_value='1.2.3')
self.patch_object(chm.os_utils, 'snap_install_requested',
return_value=False)
self.target.version_package = None
self.assertEqual(self.target.application_version, '1.2.3')
self.get_upstream_version.assert_called_once_with('p1')
@ -244,14 +247,26 @@ class TestMyOpenStackCharm(BaseOpenStackCharmTest):
self.patch_object(chm.os_utils, 'os_release')
self.patch_object(chm, 'get_upstream_version',
return_value='1.2.3')
self.patch_object(chm.os_utils, 'snap_install_requested',
return_value=False)
self.assertEqual(self.target.application_version, '1.2.3')
self.get_upstream_version.assert_called_once_with('p2')
def test_application_version_snap(self):
self.patch_object(chm, 'get_snap_version',
return_value='4.0.3')
self.patch_object(chm.os_utils, 'snap_install_requested',
return_value=True)
self.assertEqual(self.target.application_version, '4.0.3')
self.get_snap_version.assert_called_once_with('mysnap', fatal=False)
def test_application_version_dfs(self):
self.patch_object(chm.os_utils, 'os_release',
return_value='mitaka')
self.patch_object(chm, 'get_upstream_version',
return_value=None)
self.patch_object(chm.os_utils, 'snap_install_requested',
return_value=False)
self.assertEqual(self.target.application_version, 'mitaka')
self.get_upstream_version.assert_called_once_with('p2')
self.os_release.assert_called_once_with('p2')
@ -366,6 +381,8 @@ class TestHAOpenStackCharm(BaseOpenStackCharmTest):
self.patch_target('token_cache_pkgs', return_value=[])
self.patch_target('haproxy_enabled', return_value=False)
self.patch_target('apache_enabled', return_value=False)
self.patch_object(chm.os_utils, 'snap_install_requested',
return_value=False)
self.assertEqual(['pkg1'], self.target.all_packages)
self.token_cache_pkgs.return_value = ['memcache']
self.haproxy_enabled.return_value = True
@ -391,6 +408,8 @@ class TestHAOpenStackCharm(BaseOpenStackCharmTest):
self.patch_target('enable_memcache', return_value=True)
self.patch_target('haproxy_enabled', return_value=True)
self.patch_target('apache_enabled', return_value=True)
self.patch_object(chm.os_utils, 'snap_install_requested',
return_value=False)
self.assertEqual(
self.target.full_restart_map,
{'/etc/apache2/sites-available/openstack_https_frontend.conf':
@ -515,6 +534,8 @@ class TestHAOpenStackCharm(BaseOpenStackCharmTest):
self.patch_object(
chm.subprocess, 'call',
new=lambda x: apache_mods[x.pop()])
self.patch_object(chm.os_utils, 'snap_install_requested',
return_value=False)
self.target.enable_apache_modules()
self.check_call.assert_called_once_with(
['a2enmod', 'proxy_http'])
@ -523,6 +544,8 @@ class TestHAOpenStackCharm(BaseOpenStackCharmTest):
def test_configure_cert(self):
self.patch_object(chm.ch_host, 'mkdir')
self.patch_object(chm.ch_host, 'write_file')
self.patch_object(chm.os_utils, 'snap_install_requested',
return_value=False)
self.target.configure_cert('mycert', 'mykey', cn='mycn')
self.mkdir.assert_called_once_with(path='/etc/apache2/ssl/charmname')
calls = [
@ -564,6 +587,8 @@ class TestHAOpenStackCharm(BaseOpenStackCharmTest):
'ssl_cert': base64.b64encode(b'cert'),
'ssl_ca': base64.b64encode(b'ca')}
self.patch_target('config', new=config)
self.patch_object(chm.os_utils, 'snap_install_requested',
return_value=False)
self.assertEqual(
self.target.get_certs_and_keys(),
[{'key': 'key', 'cert': 'cert', 'ca': 'ca', 'cn': None}])
@ -573,6 +598,8 @@ class TestHAOpenStackCharm(BaseOpenStackCharmTest):
'ssl_key': base64.b64encode(b'key'),
'ssl_cert': base64.b64encode(b'cert')}
self.patch_target('config', new=config)
self.patch_object(chm.os_utils, 'snap_install_requested',
return_value=False)
self.assertEqual(
self.target.get_certs_and_keys(),
[{'key': 'key', 'cert': 'cert', 'ca': None, 'cn': None}])
@ -601,6 +628,8 @@ class TestHAOpenStackCharm(BaseOpenStackCharmTest):
self.patch_target(
'get_local_addresses',
return_value=['int_addr', 'priv_addr', 'pub_addr', 'admin_addr'])
self.patch_object(chm.os_utils, 'snap_install_requested',
return_value=False)
expect = [
{
'ca': 'ca',
@ -664,6 +693,8 @@ class TestHAOpenStackCharm(BaseOpenStackCharmTest):
self.patch_object(chm_core.charmhelpers.fetch,
'apt_install',
name='apt_install')
self.patch_object(chm.os_utils, 'snap_install_requested',
return_value=False)
self.target.configure_ssl()
cert_calls = [
mock.call('cert1', 'key1', cn='cn1'),
@ -684,6 +715,8 @@ class TestHAOpenStackCharm(BaseOpenStackCharmTest):
self.patch_object(chm.reactive.bus, 'set_state')
self.patch_object(chm.reactive.RelationBase, 'from_state',
return_value=None)
self.patch_object(chm.os_utils, 'snap_install_requested',
return_value=False)
self.target.configure_ssl()
self.set_state.assert_called_once_with('ssl.enabled', False)
@ -693,6 +726,8 @@ class TestHAOpenStackCharm(BaseOpenStackCharmTest):
self.patch_object(chm.reactive.bus, 'set_state')
self.patch_object(chm.reactive.RelationBase, 'from_state',
return_value='ssl_int')
self.patch_object(chm.os_utils, 'snap_install_requested',
return_value=False)
self.target.configure_ssl()
self.set_state.assert_called_once_with('ssl.enabled', False)
self.configure_rabbit_cert.assert_called_once_with('ssl_int')

View File

@ -13,7 +13,14 @@ from unit_tests.charms_openstack.charm.common import (
import unit_tests.utils as utils
TEST_CONFIG = {'config': True}
TEST_CONFIG = {'config': True,
'openstack-origin': None}
SNAP_MAP = {
'mysnap': {
'channel': 'edge',
'mode': 'jailmode',
}
}
class TestRegisterOSReleaseSelector(unittest.TestCase):
@ -60,10 +67,16 @@ class TestBaseOpenStackCharmMeta(BaseOpenStackCharmTest):
class TestC2(chm_core.BaseOpenStackCharm):
release = 'mitaka'
class TestC3(chm_core.BaseOpenStackCharm):
release = 'ocata'
package_type = 'snap'
self.assertTrue('liberty' in chm_core._releases.keys())
self.assertTrue('mitaka' in chm_core._releases.keys())
self.assertEqual(chm_core._releases['liberty'], TestC1)
self.assertEqual(chm_core._releases['mitaka'], TestC2)
self.assertTrue('ocata' in chm_core._releases.keys())
self.assertEqual(chm_core._releases['liberty']['deb'], TestC1)
self.assertEqual(chm_core._releases['mitaka']['deb'], TestC2)
self.assertEqual(chm_core._releases['ocata']['snap'], TestC3)
def test_register_unknown_series(self):
self.patch_object(chm_core, '_releases', new={})
@ -351,14 +364,28 @@ class TestMyOpenStackCharm(BaseOpenStackCharmTest):
self.patch_object(chm_core.hookenv, 'apt_install')
self.patch_object(chm_core.subprocess,
'check_output', return_value=b'\n')
self.patch_object(chm_core.os_utils, 'snap_install_requested',
return_value=True)
self.patch_object(chm_core.os_utils, 'install_os_snaps')
self.patch_object(chm_core.os_utils,
'get_snaps_install_info_from_origin',
return_value=SNAP_MAP)
self.target.install()
# TODO: remove next commented line as we don't set this state anymore
# self.target.set_state.assert_called_once_with('my-charm-installed')
self.fip.assert_called_once_with(self.target.packages)
self.status_set.assert_has_calls([
mock.call('maintenance', 'Installing packages'),
mock.call('maintenance', 'Installing snaps'),
mock.call('maintenance',
'Installation complete - awaiting next status')])
self.install_os_snaps.assert_called_once_with(SNAP_MAP)
self.get_snaps_install_info_from_origin.assert_called_once_with(
['mysnap'],
None,
mode='jailmode'
)
def test_api_port(self):
self.assertEqual(self.target.api_port('service1'), 1)
@ -557,6 +584,8 @@ class TestMyOpenStackCharm(BaseOpenStackCharmTest):
self.apt_cache.return_value = {
'testpkg': pkg_mock}
self.patch_object(chm_core.apt, 'upstream_version')
self.patch_object(chm_core.os_utils, 'snap_install_requested',
return_value=False)
self.upstream_version.return_value = '3.0.0~b1'
self.assertEqual(
chm_core.BaseOpenStackCharm.get_os_codename_package(
@ -576,6 +605,8 @@ class TestMyOpenStackCharm(BaseOpenStackCharmTest):
self.patch_target('package_codenames')
self.patch_target('get_os_codename_package',
return_value='my-series')
self.patch_object(chm_core.os_utils, 'snap_install_requested',
return_value=False)
self.assertEqual(
self.target.get_os_version_package('testpkg'),
'2011.2')
@ -584,15 +615,31 @@ class TestMyOpenStackCharm(BaseOpenStackCharmTest):
return_value='unknown-series')
self.assertEqual(self.target.get_os_version_package('testpkg'), None)
def test_openstack_upgrade_available(self):
def test_openstack_upgrade_available_package(self):
self.patch_target('get_os_version_package')
self.patch_object(chm_core.os_utils, 'get_os_version_install_source')
self.patch_object(chm_core, 'apt')
self.patch_target('config',
new={'openstack-origin': 'cloud:natty-folsom'})
self.patch_object(chm_core.os_utils, 'snap_install_requested',
return_value=False)
self.get_os_version_package.return_value = 2
self.get_os_version_install_source.return_value = 3
self.target.openstack_upgrade_available('testpkg')
self.target.openstack_upgrade_available(package='testpkg')
self.apt.version_compare.assert_called_once_with(3, 2)
def test_openstack_upgrade_available_snap(self):
self.patch_target('get_os_version_snap')
self.patch_object(chm_core.os_utils, 'get_os_version_install_source')
self.patch_object(chm_core, 'apt')
self.patch_target('config',
new={'openstack-origin': 'snap:ocata/stable'})
self.patch_object(chm_core.os_utils, 'snap_install_requested',
return_value=True)
self.get_os_version_snap.return_value = 2
self.get_os_version_install_source.return_value = 3
self.target.openstack_upgrade_available(snap='testsnap')
self.get_os_version_snap.assert_called_once_with('testsnap')
self.apt.version_compare.assert_called_once_with(3, 2)
def test_upgrade_if_available(self):
@ -618,7 +665,7 @@ class TestMyOpenStackCharm(BaseOpenStackCharmTest):
'int_list')
self.do_openstack_upgrade_db_migration.assert_called_once_with()
def test_do_openstack_pkg_upgrade(self):
def test_do_openstack_pkg_upgrade_package(self):
self.patch_target('config',
new={'openstack-origin': 'cloud:natty-kilo'})
self.patch_object(chm_core.os_utils, 'get_os_codename_install_source')
@ -627,6 +674,8 @@ class TestMyOpenStackCharm(BaseOpenStackCharmTest):
self.patch_object(chm_core.charmhelpers.fetch, 'apt_update')
self.patch_object(chm_core.charmhelpers.fetch, 'apt_upgrade')
self.patch_object(chm_core.charmhelpers.fetch, 'apt_install')
self.patch_object(chm_core.os_utils, 'snap_install_requested',
return_value=False)
self.target.do_openstack_pkg_upgrade()
self.configure_installation_source.assert_called_once_with(
'cloud:natty-kilo')
@ -643,6 +692,44 @@ class TestMyOpenStackCharm(BaseOpenStackCharmTest):
'Dpkg::Options::=--force-confdef'],
fatal=True)
def test_do_openstack_pkg_upgrade_snap(self):
self.patch_target('config',
new={'openstack-origin': 'snap:ocata/stable'})
self.patch_object(chm_core.os_utils, 'get_os_codename_install_source')
self.patch_object(chm_core.hookenv, 'log')
self.patch_object(chm_core.os_utils, 'configure_installation_source')
self.patch_object(chm_core.charmhelpers.fetch, 'apt_update')
self.patch_object(chm_core.charmhelpers.fetch, 'apt_upgrade')
self.patch_object(chm_core.charmhelpers.fetch, 'apt_install')
self.patch_object(chm_core.os_utils, 'snap_install_requested',
return_value=True)
self.patch_object(chm_core.os_utils, 'install_os_snaps')
self.patch_object(chm_core.os_utils,
'get_snaps_install_info_from_origin',
return_value=SNAP_MAP)
self.target.do_openstack_pkg_upgrade()
self.configure_installation_source.assert_called_once_with(
'snap:ocata/stable')
self.apt_update.assert_called_once_with()
self.apt_upgrade.assert_called_once_with(
dist=True, fatal=True,
options=[
'--option', 'Dpkg::Options::=--force-confnew', '--option',
'Dpkg::Options::=--force-confdef'])
self.apt_install.assert_called_once_with(
packages=['p1', 'p2', 'p3', 'package-to-filter'],
options=[
'--option', 'Dpkg::Options::=--force-confnew', '--option',
'Dpkg::Options::=--force-confdef'],
fatal=True)
self.install_os_snaps.assert_called_once_with(snaps=SNAP_MAP,
refresh=True)
self.get_snaps_install_info_from_origin.assert_called_once_with(
['mysnap'],
'snap:ocata/stable',
mode='jailmode',
)
def test_do_openstack_upgrade_config_render(self):
self.patch_target('render_with_interfaces')
self.target.do_openstack_upgrade_config_render('int_list')

View File

@ -120,6 +120,49 @@ class TestDefaults(BaseOpenStackCharmTest):
kv.set.assert_called_once_with(chm.OPENSTACK_RELEASE_KEY, 'two')
self.os_release.assert_called_once_with('python-keystonemiddleware')
def test_default_select_package_type_handler(self):
self.assertIn('charm.default-select-package-type',
chm._default_handler_map)
self.patch_object(chm, 'register_package_type_selector')
h = self.mock_decorator_gen_simple()
self.register_package_type_selector.side_effect = h.decorator
# call the default handler installer function, and check its map.
f = chm._default_handler_map['charm.default-select-package-type']
f()
self.assertIsNotNone(h.map['function'])
# verify that the installed function works
kv = mock.MagicMock()
self.patch_object(chm.unitdata, 'kv', new=lambda: kv)
self.patch_object(chm.os_utils, 'snap_install_requested',
return_value=False)
# set a package_type
kv.get.return_value = 'deb'
package_type = h.map['function']()
self.assertEqual(package_type, 'deb')
kv.set.assert_not_called()
kv.get.assert_called_once_with(chm.OPENSTACK_PACKAGE_TYPE_KEY, None)
# No release set, ensure it calls snap_install_requested and
# sets package_type to 'snap'
kv.reset_mock()
kv.get.return_value = None
self.snap_install_requested.return_value = True
package_type = h.map['function']()
self.assertEqual(package_type, 'snap')
kv.set.assert_called_once_with(chm.OPENSTACK_PACKAGE_TYPE_KEY, 'snap')
self.snap_install_requested.assert_called_once_with()
# No release set, ensure it calls snap_install_requested and
# sets package_type to 'deb'
kv.reset_mock()
kv.get.return_value = None
self.snap_install_requested.reset_mock()
self.snap_install_requested.return_value = False
package_type = h.map['function']()
self.assertEqual(package_type, 'deb')
kv.set.assert_called_once_with(chm.OPENSTACK_PACKAGE_TYPE_KEY, 'deb')
self.snap_install_requested.assert_called_once_with()
def test_default_amqp_connection_handler(self):
self.assertIn('amqp.connected', chm._default_handler_map)
self.patch_object(chm.reactive, 'set_state')