Add snap support

Enable snap installation support for Gnocchi.

The Gnocchi snap provides nginx and uwsgi instead of
Apache2 + mod_wsgi so the config/restart map looks a
little different.

Snap support has been implemented using a subclass of
the GnocchiCharmBase class, which provides common
methods and properties to both deb and snap based
installations.

Snaps are only published for Ocata or later releases
of OpenStack.

Change-Id: I464025a2b72aba8c31a4a97ade39d2b2980c3a92
This commit is contained in:
Corey Bryant 2017-09-27 19:17:41 +00:00 committed by James Page
parent cba656d685
commit ecaea903e7
8 changed files with 331 additions and 76 deletions

View File

@ -27,14 +27,52 @@ import charms_openstack.ip as os_ip
GNOCCHI_DIR = '/etc/gnocchi'
GNOCCHI_CONF = os.path.join(GNOCCHI_DIR, 'gnocchi.conf')
GNOCCHI_APACHE_SITE = 'gnocchi-api'
GNOCCHI_WEBSERVER_SITE = 'gnocchi-api'
GNOCCHI_WSGI_CONF = '/etc/apache2/sites-available/{}.conf'.format(
GNOCCHI_APACHE_SITE)
GNOCCHI_WEBSERVER_SITE)
SNAP_PREFIX = '/var/snap/gnocchi/common'
GNOCCHI_DIR_SNAP = '{}{}'.format(SNAP_PREFIX, GNOCCHI_DIR)
GNOCCHI_CONF_SNAP = os.path.join(GNOCCHI_DIR_SNAP, 'gnocchi.conf')
GNOCCHI_WEBSERVER_SITE_SNAP = 'gnocchi-nginx'
GNOCCHI_NGINX_SITE_CONF_SNAP = '{}{}'.format(
SNAP_PREFIX,
'/etc/nginx/sites-enabled/{}.conf'.format(
GNOCCHI_WEBSERVER_SITE_SNAP
)
)
GNOCCHI_NGINX_CONF_SNAP = '{}{}'.format(SNAP_PREFIX,
'/etc/nginx/nginx.conf')
CEPH_CONF = '/etc/ceph/ceph.conf'
CEPH_CONF_SNAP = '{}{}'.format(SNAP_PREFIX, CEPH_CONF)
CEPH_KEYRING = '/etc/ceph/ceph.client.{}.keyring'
CEPH_KEYRING_SNAP = '{}{}'.format(SNAP_PREFIX, CEPH_KEYRING)
DB_INTERFACE = 'shared-db'
charms_openstack.charm.use_defaults('charm.default-select-package-type')
charms_openstack.charm.use_defaults('charm.default-select-release')
@charms_openstack.adapters.config_property
def log_config(config):
if ch_utils.snap_install_requested():
return os.path.join(SNAP_PREFIX,
'log/gnocchi-api.log')
else:
return '/var/log/gnocchi/gnocchi-api.log'
@charms_openstack.adapters.config_property
def ceph_config(config):
if ch_utils.snap_install_requested():
return CEPH_CONF_SNAP
else:
return CEPH_CONF
# TODO(jamespage): charms.openstack
class StorageCephRelationAdapter(adapters.OpenStackRelationAdapter):
@ -51,7 +89,7 @@ class StorageCephRelationAdapter(adapters.OpenStackRelationAdapter):
Comma separated list of hosts that should be used
to access Ceph.
"""
hosts = self.relation.mon_hosts()
hosts = sorted(self.relation.mon_hosts())
if len(hosts) > 0:
return ','.join(hosts)
else:
@ -69,7 +107,7 @@ class MemcacheRelationAdapter(adapters.OpenStackRelationAdapter):
@property
def url(self):
hosts = self.relation.memcache_hosts()
hosts = sorted(self.relation.memcache_hosts())
if hosts:
return "memcached://{}:11211?timeout=5".format(hosts[0])
return None
@ -89,22 +127,19 @@ class GnocchiCharmRelationAdapaters(adapters.OpenStackAPIRelationAdapters):
}
class GnocchiCharm(charms_openstack.charm.HAOpenStackCharm):
class GnochiCharmBase(charms_openstack.charm.HAOpenStackCharm):
"""
Charm for Juju deployment of Gnocchi
Base class for shared charm functions for all package types
"""
abstract_class = True
# Internal name of charm
service_name = name = 'gnocchi'
# First release supported
release = 'mitaka'
default_service = 'gnocchi-api'
# List of packages to install for this charm
packages = ['gnocchi-api', 'gnocchi-metricd', 'python-apt',
'ceph-common', 'python-rados', 'python-keystonemiddleware',
'apache2', 'libapache2-mod-wsgi']
service_type = 'gnocchi'
api_ports = {
'gnocchi-api': {
@ -114,64 +149,16 @@ class GnocchiCharm(charms_openstack.charm.HAOpenStackCharm):
}
}
default_service = 'gnocchi-api'
service_type = 'gnocchi'
services = ['gnocchi-metricd', 'apache2']
required_relations = ['shared-db', 'identity-service',
'storage-ceph', 'coordinator-memcached']
restart_map = {
GNOCCHI_CONF: services,
GNOCCHI_WSGI_CONF: ['apache2'],
CEPH_CONF: services,
}
ha_resources = ['vips', 'haproxy']
release_pkg = 'gnocchi-common'
package_codenames = {
'gnocchi-common': collections.OrderedDict([
('2', 'mitaka'),
('3', 'newton'),
('4', 'pike'),
]),
}
sync_cmd = ['gnocchi-upgrade',
'--log-file=/var/log/gnocchi/gnocchi-upgrade.log']
adapters_class = GnocchiCharmRelationAdapaters
def __init__(self, release=None, **kwargs):
"""Custom initialiser for class
If no release is passed, then the charm determines the release from the
ch_utils.os_release() function.
"""
if release is None:
release = ch_utils.os_release('python-keystonemiddleware')
super(GnocchiCharm, self).__init__(release=release, **kwargs)
def install(self):
super(GnocchiCharm, self).install()
# NOTE(jamespage): always pause gnocchi-api service as we force
# execution with Apache2+mod_wsgi
host.service_pause('gnocchi-api')
def enable_apache2_site(self):
"""Enable Gnocchi API apache2 site if rendered or installed"""
if os.path.exists(GNOCCHI_WSGI_CONF):
check_enabled = subprocess.call(
['a2query', '-s', GNOCCHI_APACHE_SITE]
)
if check_enabled != 0:
subprocess.check_call(['a2ensite',
GNOCCHI_APACHE_SITE])
host.service_reload('apache2',
restart_on_failure=True)
def enable_webserver_site(self):
"""Enable Gnocchi Webserver sites if rendered or installed"""
pass
def get_database_setup(self):
return [{
@ -188,3 +175,143 @@ class GnocchiCharm(charms_openstack.charm.HAOpenStackCharm):
'''Enable all services related to gnocchi'''
for svc in self.services:
host.service_resume(svc)
@property
def gnocchi_user(self):
'''Determine user gnocchi processes will run as
:return string: user for gnocchi processes
'''
return self.user
@property
def gnocchi_group(self):
'''Determine group gnocchi processes will run as
:return string: group for gnocchi processes
'''
return self.group
@property
def ceph_keyring(self):
'''Determine location for ceph keyring file
:return string: location of keyrings
'''
_type_map = {
'snap': CEPH_KEYRING_SNAP,
'deb': CEPH_KEYRING,
}
try:
return _type_map[self.package_type]
except KeyError:
return CEPH_KEYRING
class GnocchiCharm(GnochiCharmBase):
"""
Charm for Juju deployment of Gnocchi
"""
# First release supported
release = 'mitaka'
# Deb package type
package_type = 'deb'
# List of packages to install for this charm
packages = ['gnocchi-api', 'gnocchi-metricd', 'python-apt',
'ceph-common', 'python-rados', 'python-keystonemiddleware',
'apache2', 'libapache2-mod-wsgi']
services = ['gnocchi-metricd', 'apache2']
restart_map = {
GNOCCHI_CONF: services,
GNOCCHI_WSGI_CONF: ['apache2'],
CEPH_CONF: services,
}
release_pkg = 'gnocchi-common'
package_codenames = {
'gnocchi-common': collections.OrderedDict([
('2', 'mitaka'),
('3', 'newton'),
('4', 'pike'),
]),
}
sync_cmd = ['gnocchi-upgrade',
'--log-file=/var/log/gnocchi/gnocchi-upgrade.log']
# User and group for permissions management
user = 'gnocchi'
group = 'gnocchi'
def install(self):
super(GnocchiCharm, self).install()
# NOTE(jamespage): always pause gnocchi-api service as we force
# execution with Apache2+mod_wsgi
host.service_pause('gnocchi-api')
def enable_webserver_site(self):
"""Enable Gnocchi API apache2 site if rendered or installed"""
if os.path.exists(GNOCCHI_WSGI_CONF):
check_enabled = subprocess.call(
['a2query', '-s', GNOCCHI_WEBSERVER_SITE]
)
if check_enabled != 0:
subprocess.check_call(['a2ensite',
GNOCCHI_WEBSERVER_SITE])
host.service_reload('apache2',
restart_on_failure=True)
class GnocchiSnapCharm(GnochiCharmBase):
"""
Charm for Juju deployment of Gnocchi via Snap
"""
# First release supported
# NOTE(coreycb): snap install is only supported from ocata up.
release = 'ocata'
# Snap package type
package_type = 'snap'
# List of packages and snaps to install for this charm
snaps = ['gnocchi']
packages = ['ceph-common']
services = ['snap.gnocchi.metricd',
'snap.gnocchi.nginx',
'snap.gnocchi.uwsgi']
snap_codenames = {
'gnocchi': collections.OrderedDict([
('2', 'mitaka'),
('3', 'newton'),
('4', 'pike'),
]),
}
release_snap = 'gnocchi'
restart_map = {
GNOCCHI_CONF_SNAP: ['snap.gnocchi.metricd', 'snap.gnocchi.uwsgi'],
GNOCCHI_NGINX_SITE_CONF_SNAP: ['snap.gnocchi.nginx'],
GNOCCHI_NGINX_CONF_SNAP: ['snap.gnocchi.nginx'],
CEPH_CONF_SNAP: ['snap.gnocchi.metricd', 'snap.gnocchi.uwsgi'],
}
sync_cmd = [
'/snap/bin/gnocchi.upgrade',
'--log-file=/var/snap/gnocchi/common/log/gnocchi-upgrade.log'
]
# User and group for permissions management
user = 'root'
group = 'root'

View File

@ -12,6 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import charms_openstack.charm as charm
import charms.reactive as reactive
@ -19,6 +21,7 @@ import charm.openstack.gnocchi as gnocchi # noqa
import charmhelpers.contrib.storage.linux.ceph as ceph_helper
import charmhelpers.core.hookenv as hookenv
import charmhelpers.core.host as host
charm.use_defaults(
'charm.installed',
@ -48,7 +51,7 @@ def render_config(*args):
with charm.provide_charm_instance() as charm_class:
charm_class.enable_services()
charm_class.render_with_interfaces(args)
charm_class.enable_apache2_site()
charm_class.enable_webserver_site()
charm_class.assess_status()
reactive.set_state('config.rendered')
@ -75,15 +78,22 @@ def storage_ceph_connected(ceph):
@reactive.when('storage-ceph.available')
def configure_ceph(ceph):
ceph_helper.ensure_ceph_keyring(service=hookenv.service_name(),
key=ceph.key(),
user='gnocchi',
group='gnocchi')
with charm.provide_charm_instance() as charm_class:
# TODO(jamespage): refactor to avoid massaging helper
ceph_helper.KEYRING = charm_class.ceph_keyring
host.mkdir(os.path.dirname(charm_class.ceph_keyring))
ceph_helper.ensure_ceph_keyring(service=hookenv.service_name(),
key=ceph.key(),
user=charm_class.gnocchi_user,
group=charm_class.gnocchi_group)
@reactive.when_not('storage-ceph.connected')
def storage_ceph_disconnected():
ceph_helper.delete_keyring(hookenv.service_name())
with charm.provide_charm_instance() as charm_class:
# TODO(jamespage): refactor to avoid massaging helper
ceph_helper.KEYRING = charm_class.ceph_keyring
ceph_helper.delete_keyring(hookenv.service_name())
@reactive.when('metric-service.connected')

View File

@ -8,7 +8,7 @@ debug = {{ options.debug }}
use_syslog = {{ options.use_syslog }}
# NOTE(jamespage): Set sensible log file location for WSGI processes,
# other daemons will override using CLI options.
log_file = /var/log/gnocchi/gnocchi-api.log
log_file = {{ options.log_config }}
[api]
auth_mode = keystone
@ -28,10 +28,10 @@ coordination_url = {{ coordinator_memcached.url }}
{% if storage_ceph.key -%}
driver = ceph
ceph_pool = {{ options.service_name }}
ceph_username = {{ options.service_name }}
ceph_pool = {{ options.application_name }}
ceph_username = {{ options.application_name }}
ceph_secret = {{ storage_ceph.key }}
ceph_conffile = /etc/ceph/ceph.conf
ceph_conffile = {{ options.ceph_config }}
{%- endif %}
{% include "parts/section-keystone-authtoken" %}

View File

@ -0,0 +1,38 @@
server {
listen {{ options.service_listen_info.gnocchi_api.public_port }};
access_log /var/snap/gnocchi/common/log/nginx-access.log;
error_log /var/snap/gnocchi/common/log/nginx-error.log;
location / {
include /snap/gnocchi/current/usr/conf/uwsgi_params;
uwsgi_param SCRIPT_NAME '';
uwsgi_pass unix:///var/snap/gnocchi/common/run/gnocchi-api.sock;
}
}
{% if options.ssl -%}
{% if options.endpoints -%}
{% for address, endpoint, ext, int in options.endpoints -%}
server {
listen {{ ext }} {% if options.ssl -%}ssl{% endif -%};
{% if options.ssl -%}
ssl on;
ssl_certificate /var/snap/gnocchi/common/etc/nginx/ssl/cert_{{ address }};
ssl_certificate_key /var/snap/gnocchi/common/etc/nginx/ssl/key_{{ address }};
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!RC4:!MD5:!aNULL:!eNULL:!EXP:!LOW:!MEDIUM;
{% endif -%}
server_name {{ endpoint }};
access_log /var/snap/gnocchi/common/log/nginx-access.log;
error_log /var/snap/gnocchi/common/log/nginx-error.log;
location / {
include /snap/gnocchi/current/usr/conf/uwsgi_params;
uwsgi_param SCRIPT_NAME '';
uwsgi_pass unix:///var/snap/gnocchi/common/run/gnocchi-api.sock;
}
}
{% endfor -%}
{% endif -%}
{% endif -%}

View File

@ -0,0 +1,39 @@
user root root;
worker_processes {{ options.workers }};
pid /var/snap/gnocchi/common/run/nginx.pid;
events {
worker_connections 768;
}
http {
##
# Basic Settings
##
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /snap/gnocchi/current/usr/conf/mime.types;
default_type application/octet-stream;
##
# Logging Settings
##
access_log /var/snap/gnocchi/common/log/nginx-access.log;
error_log /var/snap/gnocchi/common/log/nginx-error.log;
##
# Gzip Settings
##
gzip on;
gzip_disable "msie6";
include /var/snap/gnocchi/common/etc/nginx/conf.d/*.conf;
include /var/snap/gnocchi/common/etc/nginx/sites-enabled/*;
}

View File

@ -37,10 +37,12 @@ class GnocchiCharmDeployment(amulet_deployment.OpenStackAmuletDeployment):
no_origin = ['memcached', 'percona-cluster', 'rabbitmq-server',
'ceph-mon', 'ceph-osd']
def __init__(self, series, openstack=None, source=None, stable=False):
def __init__(self, series, openstack=None, source=None, stable=False,
snap_source=None):
"""Deploy the entire test environment."""
super(GnocchiCharmDeployment, self).__init__(series, openstack,
source, stable)
self.snap_source = snap_source
self._add_services()
self._add_relations()
self._configure_services()
@ -99,8 +101,12 @@ class GnocchiCharmDeployment(amulet_deployment.OpenStackAmuletDeployment):
ceph_osd_config = {'osd-devices': '/dev/vdb',
'osd-reformat': 'yes',
'ephemeral-unmount': '/mnt'}
gnocchi_config = {}
if self.snap_source:
gnocchi_config['openstack-origin'] = self.snap_source
configs = {'keystone': keystone_config,
'ceph-osd': ceph_osd_config}
'ceph-osd': ceph_osd_config,
'gnocchi': gnocchi_config}
super(GnocchiCharmDeployment, self)._configure_services(configs)
def _get_token(self):
@ -192,3 +198,11 @@ class GnocchiCharmDeployment(amulet_deployment.OpenStackAmuletDeployment):
u.log.debug('Checking api functionality...')
assert(self.gnocchi.status.get() != [])
u.log.debug('OK')
class GnocchiCharmSnapDeployment(GnocchiCharmDeployment):
"""Amulet tests on a snap based gnocchi deployment."""
gnocchi_svcs = ['haproxy', 'snap.gnocchi.metricd',
'snap.gnocchi.uwsgi',
'snap.gnocchi.nginx']

View File

@ -0,0 +1,25 @@
#!/usr/bin/env python
#
# Copyright 2016 Canonical Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Amulet tests on a basic Gnocchi Charm deployment on xenial-ocata."""
from basic_deployment import GnocchiCharmSnapDeployment
if __name__ == '__main__':
deployment = GnocchiCharmSnapDeployment(series='xenial',
openstack='cloud:xenial-ocata',
snap_source='snap:ocata/edge')
deployment.run_tests()

View File

@ -77,6 +77,8 @@ class TestHandlers(test_utils.PatchHelper):
def setUp(self):
super(TestHandlers, self).setUp()
self.gnocchi_charm = mock.MagicMock()
self.gnocchi_charm.gnocchi_user = 'gnocchi'
self.gnocchi_charm.gnocchi_group = 'gnocchi'
self.patch_object(handlers.charm, 'provide_charm_instance',
new=mock.MagicMock())
self.provide_charm_instance().__enter__.return_value = \
@ -89,7 +91,7 @@ class TestHandlers(test_utils.PatchHelper):
('arg1', 'arg2')
)
self.gnocchi_charm.assess_status.assert_called_once_with()
self.gnocchi_charm.enable_apache2_site.assert_called_once_with()
self.gnocchi_charm.enable_webserver_site.assert_called_once_with()
def test_init_db(self):
handlers.init_db()