Add snap support
Update core charms.openstack classes to support installation from snap as an alternative to installation from deb packages. Change-Id: Ia2dc6975bdb4809a3cd69b5fae8124d8f7808a34
This commit is contained in:
parent
b79e94ba03
commit
e8e7f83d00
|
@ -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
|
||||
|
||||
|
|
|
@ -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(OpenStackAPICharm, self).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(HAOpenStackCharm, self).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,15 @@ 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 +603,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 +634,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:
|
||||
|
|
|
@ -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,6 +102,7 @@ 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:
|
||||
|
@ -106,7 +113,7 @@ def get_charm_instance(release=None, *args, **kwargs):
|
|||
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,58 @@ 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)
|
||||
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)
|
||||
|
||||
version_or_codename = None
|
||||
lines = out.split('\n')
|
||||
for line in lines:
|
||||
if snap in line:
|
||||
# Second item in list is version or a codename
|
||||
version_or_codename = line.split()[1]
|
||||
return version_or_codename
|
||||
|
||||
|
||||
class BaseOpenStackCharmMeta(type):
|
||||
"""Metaclass to provide a classproperty of 'singleton' so that class
|
||||
methods in the derived BaseOpenStackCharm() class can simply use
|
||||
|
@ -190,18 +250,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 +288,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 +421,46 @@ 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]
|
||||
if (snap in codenames and
|
||||
major_vers in codenames[snap]):
|
||||
return codenames[snap][major_vers]
|
||||
else:
|
||||
# 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 +504,28 @@ 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
|
||||
"""
|
||||
# TODO(coreycb): Add support for get_os_version_snap
|
||||
if os_utils.snap_install_requested():
|
||||
codenames = self.snap_codenames
|
||||
codename = self.get_os_codename_snap(snap, 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
|
||||
|
||||
return None
|
||||
|
||||
def get_os_version_package(self, package, fatal=True):
|
||||
"""Derive OpenStack version number from an installed package.
|
||||
|
||||
|
@ -403,16 +533,19 @@ 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():
|
||||
codenames = self.package_codenames or os_utils.PACKAGE_CODENAMES
|
||||
codename = self.get_os_codename_package(
|
||||
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
|
||||
vers_map = os_utils.OPENSTACK_CODENAMES
|
||||
for version, cname in vers_map.items():
|
||||
if cname == codename:
|
||||
return version
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class BaseOpenStackCharmActions(object):
|
||||
|
@ -440,15 +573,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 +818,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 +826,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 +852,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 +860,14 @@ 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))
|
||||
|
||||
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 +902,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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,6 +247,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.assertEqual(self.target.application_version, '1.2.3')
|
||||
self.get_upstream_version.assert_called_once_with('p2')
|
||||
|
||||
|
@ -252,6 +257,8 @@ class TestMyOpenStackCharm(BaseOpenStackCharmTest):
|
|||
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 +373,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 +400,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 +526,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 +536,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 +579,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 +590,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 +620,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 +685,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 +707,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 +718,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')
|
||||
|
|
|
@ -60,10 +60,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={})
|
||||
|
@ -557,6 +563,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 +584,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')
|
||||
|
@ -590,6 +600,8 @@ class TestMyOpenStackCharm(BaseOpenStackCharmTest):
|
|||
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')
|
||||
|
|
|
@ -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')
|
||||
|
|
Loading…
Reference in New Issue