From f3226f8dde2fb36ad13c164591d0c9ed79eafc0d Mon Sep 17 00:00:00 2001 From: Liam Young Date: Thu, 8 Dec 2016 15:27:42 +0000 Subject: [PATCH] Enable Memcache service for Token Caching With the release of 4.2.0 of keystonemiddleware using the in-process token cache is no longer recommended. It is recommended that a memcache backend to store tokens is used instead, This installs and configures memcache and configures neutron-server to use memcache for token caching. http://docs.openstack.org/releasenotes/keystonemiddleware/mitaka.html#id2 Change-Id: I1f7764afb24b511508432fa758596cc9ab40aba8 --- .../contrib/openstack/amulet/utils.py | 67 +++++++++++++++++++ charmhelpers/contrib/openstack/context.py | 25 +++++++ .../openstack/templates/memcached.conf | 53 +++++++++++++++ .../section-keystone-authtoken-mitaka | 3 + charmhelpers/contrib/openstack/utils.py | 25 +++++++ lib/ceilometer_utils.py | 14 +++- tests/basic_deployment.py | 6 ++ tests/charmhelpers/contrib/amulet/utils.py | 3 +- .../contrib/openstack/amulet/utils.py | 67 +++++++++++++++++++ unit_tests/test_actions_openstack_upgrade.py | 2 +- unit_tests/test_ceilometer_utils.py | 11 ++- 11 files changed, 271 insertions(+), 5 deletions(-) create mode 100644 charmhelpers/contrib/openstack/templates/memcached.conf diff --git a/charmhelpers/contrib/openstack/amulet/utils.py b/charmhelpers/contrib/openstack/amulet/utils.py index 6a0ba83..2b0a562 100644 --- a/charmhelpers/contrib/openstack/amulet/utils.py +++ b/charmhelpers/contrib/openstack/amulet/utils.py @@ -1133,3 +1133,70 @@ class OpenStackAmuletUtils(AmuletUtils): else: msg = 'No message retrieved.' amulet.raise_status(amulet.FAIL, msg) + + def validate_memcache(self, sentry_unit, conf, os_release, + earliest_release=5, section='keystone_authtoken', + check_kvs=None): + """Check Memcache is running and is configured to be used + + Example call from Amulet test: + + def test_110_memcache(self): + u.validate_memcache(self.neutron_api_sentry, + '/etc/neutron/neutron.conf', + self._get_openstack_release()) + + :param sentry_unit: sentry unit + :param conf: OpenStack config file to check memcache settings + :param os_release: Current OpenStack release int code + :param earliest_release: Earliest Openstack release to check int code + :param section: OpenStack config file section to check + :param check_kvs: Dict of settings to check in config file + :returns: None + """ + if os_release < earliest_release: + self.log.debug('Skipping memcache checks for deployment. {} <' + 'mitaka'.format(os_release)) + return + _kvs = check_kvs or {'memcached_servers': 'inet6:[::1]:11211'} + self.log.debug('Checking memcached is running') + ret = self.validate_services_by_name({sentry_unit: ['memcached']}) + if ret: + amulet.raise_status(amulet.FAIL, msg='Memcache running check' + 'failed {}'.format(ret)) + else: + self.log.debug('OK') + self.log.debug('Checking memcache url is configured in {}'.format( + conf)) + if self.validate_config_data(sentry_unit, conf, section, _kvs): + message = "Memcache config error in: {}".format(conf) + amulet.raise_status(amulet.FAIL, msg=message) + else: + self.log.debug('OK') + self.log.debug('Checking memcache configuration in ' + '/etc/memcached.conf') + contents = self.file_contents_safe(sentry_unit, '/etc/memcached.conf', + fatal=True) + ubuntu_release, _ = self.run_cmd_unit(sentry_unit, 'lsb_release -cs') + if ubuntu_release <= 'trusty': + memcache_listen_addr = 'ip6-localhost' + else: + memcache_listen_addr = '::1' + expected = { + '-p': '11211', + '-l': memcache_listen_addr} + found = [] + for key, value in expected.items(): + for line in contents.split('\n'): + if line.startswith(key): + self.log.debug('Checking {} is set to {}'.format( + key, + value)) + assert value == line.split()[-1] + self.log.debug(line.split()[-1]) + found.append(key) + if sorted(found) == sorted(expected.keys()): + self.log.debug('OK') + else: + message = "Memcache config error in: /etc/memcached.conf" + amulet.raise_status(amulet.FAIL, msg=message) diff --git a/charmhelpers/contrib/openstack/context.py b/charmhelpers/contrib/openstack/context.py index d5b3a33..91ddcec 100644 --- a/charmhelpers/contrib/openstack/context.py +++ b/charmhelpers/contrib/openstack/context.py @@ -90,6 +90,7 @@ from charmhelpers.contrib.network.ip import ( from charmhelpers.contrib.openstack.utils import ( config_flags_parser, get_host_ip, + enable_memcache, ) from charmhelpers.core.unitdata import kv @@ -1512,3 +1513,27 @@ class AppArmorContext(OSContextGenerator): "".format(self.ctxt['aa_profile'], self.ctxt['aa_profile_mode'])) raise e + + +class MemcacheContext(OSContextGenerator): + """Memcache context + + This context provides options for configuring a local memcache client and + server + """ + def __call__(self): + ctxt = {} + ctxt['use_memcache'] = enable_memcache(config('openstack-origin')) + if ctxt['use_memcache']: + # Trusty version of memcached does not support ::1 as a listen + # address so use host file entry instead + if lsb_release()['DISTRIB_CODENAME'].lower() > 'trusty': + ctxt['memcache_server'] = '::1' + else: + ctxt['memcache_server'] = 'ip6-localhost' + ctxt['memcache_server_formatted'] = '[::1]' + ctxt['memcache_port'] = '11211' + ctxt['memcache_url'] = 'inet6:{}:{}'.format( + ctxt['memcache_server_formatted'], + ctxt['memcache_port']) + return ctxt diff --git a/charmhelpers/contrib/openstack/templates/memcached.conf b/charmhelpers/contrib/openstack/templates/memcached.conf new file mode 100644 index 0000000..26cb037 --- /dev/null +++ b/charmhelpers/contrib/openstack/templates/memcached.conf @@ -0,0 +1,53 @@ +############################################################################### +# [ WARNING ] +# memcached configuration file maintained by Juju +# local changes may be overwritten. +############################################################################### + +# memcached default config file +# 2003 - Jay Bonci +# This configuration file is read by the start-memcached script provided as +# part of the Debian GNU/Linux distribution. + +# Run memcached as a daemon. This command is implied, and is not needed for the +# daemon to run. See the README.Debian that comes with this package for more +# information. +-d + +# Log memcached's output to /var/log/memcached +logfile /var/log/memcached.log + +# Be verbose +# -v + +# Be even more verbose (print client commands as well) +# -vv + +# Start with a cap of 64 megs of memory. It's reasonable, and the daemon default +# Note that the daemon will grow to this size, but does not start out holding this much +# memory +-m 64 + +# Default connection port is 11211 +-p {{ memcache_port }} + +# Run the daemon as root. The start-memcached will default to running as root if no +# -u command is present in this config file +-u memcache + +# Specify which IP address to listen on. The default is to listen on all IP addresses +# This parameter is one of the only security measures that memcached has, so make sure +# it's listening on a firewalled interface. +-l {{ memcache_server }} + +# Limit the number of simultaneous incoming connections. The daemon default is 1024 +# -c 1024 + +# Lock down all paged memory. Consult with the README and homepage before you do this +# -k + +# Return error when memory is exhausted (rather than removing items) +# -M + +# Maximize core file limit +# -r diff --git a/charmhelpers/contrib/openstack/templates/section-keystone-authtoken-mitaka b/charmhelpers/contrib/openstack/templates/section-keystone-authtoken-mitaka index 7c6f0c3..8e6889e 100644 --- a/charmhelpers/contrib/openstack/templates/section-keystone-authtoken-mitaka +++ b/charmhelpers/contrib/openstack/templates/section-keystone-authtoken-mitaka @@ -14,4 +14,7 @@ project_name = {{ admin_tenant_name }} username = {{ admin_user }} password = {{ admin_password }} signing_dir = {{ signing_dir }} +{% if use_memcache == true %} +memcached_servers = {{ memcache_url }} +{% endif -%} {% endif -%} diff --git a/charmhelpers/contrib/openstack/utils.py b/charmhelpers/contrib/openstack/utils.py index 553ff8f..59f9f51 100644 --- a/charmhelpers/contrib/openstack/utils.py +++ b/charmhelpers/contrib/openstack/utils.py @@ -1925,3 +1925,28 @@ def os_application_version_set(package): application_version_set(os_release(package)) else: application_version_set(application_version) + + +def enable_memcache(source=None, release=None): + """Determine if memcache should be enabled on the local unit + + @param source: source string for charm + @param release: release of OpenStack currently deployed + @returns boolean Whether memcache should be enabled + """ + if not release: + release = get_os_codename_install_source(source) + return release >= 'mitaka' + + +def token_cache_pkgs(source=None, release=None): + """Determine additional packages needed for token caching + + @param source: source string for charm + @param release: release of OpenStack currently deployed + @returns List of package to enable token caching + """ + packages = [] + if enable_memcache(source=source, release=release): + packages.extend(['memcached', 'python-memcache']) + return packages diff --git a/lib/ceilometer_utils.py b/lib/ceilometer_utils.py index 3d1796e..cf04d33 100644 --- a/lib/ceilometer_utils.py +++ b/lib/ceilometer_utils.py @@ -38,6 +38,8 @@ from charmhelpers.contrib.openstack.utils import ( resume_unit, make_assess_status_func, os_application_version_set, + token_cache_pkgs, + enable_memcache, ) from charmhelpers.core.hookenv import config, log from charmhelpers.fetch import apt_update, apt_install, apt_upgrade @@ -54,6 +56,7 @@ HTTPS_APACHE_CONF = "/etc/apache2/sites-available/openstack_https_frontend" HTTPS_APACHE_24_CONF = "/etc/apache2/sites-available/" \ "openstack_https_frontend.conf" CLUSTER_RES = 'grp_ceilometer_vips' +MEMCACHED_CONF = '/etc/memcached.conf' CEILOMETER_BASE_SERVICES = [ 'ceilometer-agent-central', @@ -111,7 +114,8 @@ CONFIG_FILES = OrderedDict([ MongoDBContext(), CeilometerContext(), context.SyslogContext(), - HAProxyContext()], + HAProxyContext(), + context.MemcacheContext()], 'services': CEILOMETER_BASE_SERVICES }), (CEILOMETER_API_SYSTEMD_CONF, { @@ -133,7 +137,7 @@ CONFIG_FILES = OrderedDict([ (HTTPS_APACHE_24_CONF, { 'hook_contexts': [ApacheSSLContext()], 'services': ['ceilometer-api', 'apache2'], - }) + }), ]) TEMPLATES = 'templates' @@ -172,6 +176,8 @@ def register_configs(): else: configs.register(HTTPS_APACHE_CONF, CONFIG_FILES[HTTPS_APACHE_CONF]['hook_contexts']) + if enable_memcache(release=release): + configs.register(MEMCACHED_CONF, [context.MemcacheContext()]) return configs @@ -194,6 +200,9 @@ def restart_map(): if svcs: _map[f] = svcs + if enable_memcache(source=config('openstack-origin')): + _map[MEMCACHED_CONF] = ['memcached'] + return _map @@ -275,6 +284,7 @@ def ceilometer_release_packages(): def get_packages(): packages = (deepcopy(CEILOMETER_BASE_PACKAGES) + ceilometer_release_packages()) + packages.extend(token_cache_pkgs(source=config('openstack-origin'))) return packages diff --git a/tests/basic_deployment.py b/tests/basic_deployment.py index 212c63f..6b340f7 100644 --- a/tests/basic_deployment.py +++ b/tests/basic_deployment.py @@ -191,6 +191,12 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment): u.log.debug('OK') + def test_105_memcache(self): + u.validate_memcache(self.ceil_sentry, + '/etc/ceilometer/ceilometer.conf', + self._get_openstack_release(), + earliest_release=self.trusty_mitaka) + def test_110_service_catalog(self): """Verify that the service catalog endpoint data is valid.""" u.log.debug('Checking keystone service catalog data...') diff --git a/tests/charmhelpers/contrib/amulet/utils.py b/tests/charmhelpers/contrib/amulet/utils.py index 8e13ab1..f9e4c3a 100644 --- a/tests/charmhelpers/contrib/amulet/utils.py +++ b/tests/charmhelpers/contrib/amulet/utils.py @@ -148,7 +148,8 @@ class AmuletUtils(object): for service_name in services_list: if (self.ubuntu_releases.index(release) >= systemd_switch or - service_name in ['rabbitmq-server', 'apache2']): + service_name in ['rabbitmq-server', 'apache2', + 'memcached']): # init is systemd (or regular sysv) cmd = 'sudo service {} status'.format(service_name) output, code = sentry_unit.run(cmd) diff --git a/tests/charmhelpers/contrib/openstack/amulet/utils.py b/tests/charmhelpers/contrib/openstack/amulet/utils.py index 6a0ba83..2b0a562 100644 --- a/tests/charmhelpers/contrib/openstack/amulet/utils.py +++ b/tests/charmhelpers/contrib/openstack/amulet/utils.py @@ -1133,3 +1133,70 @@ class OpenStackAmuletUtils(AmuletUtils): else: msg = 'No message retrieved.' amulet.raise_status(amulet.FAIL, msg) + + def validate_memcache(self, sentry_unit, conf, os_release, + earliest_release=5, section='keystone_authtoken', + check_kvs=None): + """Check Memcache is running and is configured to be used + + Example call from Amulet test: + + def test_110_memcache(self): + u.validate_memcache(self.neutron_api_sentry, + '/etc/neutron/neutron.conf', + self._get_openstack_release()) + + :param sentry_unit: sentry unit + :param conf: OpenStack config file to check memcache settings + :param os_release: Current OpenStack release int code + :param earliest_release: Earliest Openstack release to check int code + :param section: OpenStack config file section to check + :param check_kvs: Dict of settings to check in config file + :returns: None + """ + if os_release < earliest_release: + self.log.debug('Skipping memcache checks for deployment. {} <' + 'mitaka'.format(os_release)) + return + _kvs = check_kvs or {'memcached_servers': 'inet6:[::1]:11211'} + self.log.debug('Checking memcached is running') + ret = self.validate_services_by_name({sentry_unit: ['memcached']}) + if ret: + amulet.raise_status(amulet.FAIL, msg='Memcache running check' + 'failed {}'.format(ret)) + else: + self.log.debug('OK') + self.log.debug('Checking memcache url is configured in {}'.format( + conf)) + if self.validate_config_data(sentry_unit, conf, section, _kvs): + message = "Memcache config error in: {}".format(conf) + amulet.raise_status(amulet.FAIL, msg=message) + else: + self.log.debug('OK') + self.log.debug('Checking memcache configuration in ' + '/etc/memcached.conf') + contents = self.file_contents_safe(sentry_unit, '/etc/memcached.conf', + fatal=True) + ubuntu_release, _ = self.run_cmd_unit(sentry_unit, 'lsb_release -cs') + if ubuntu_release <= 'trusty': + memcache_listen_addr = 'ip6-localhost' + else: + memcache_listen_addr = '::1' + expected = { + '-p': '11211', + '-l': memcache_listen_addr} + found = [] + for key, value in expected.items(): + for line in contents.split('\n'): + if line.startswith(key): + self.log.debug('Checking {} is set to {}'.format( + key, + value)) + assert value == line.split()[-1] + self.log.debug(line.split()[-1]) + found.append(key) + if sorted(found) == sorted(expected.keys()): + self.log.debug('OK') + else: + message = "Memcache config error in: /etc/memcached.conf" + amulet.raise_status(amulet.FAIL, msg=message) diff --git a/unit_tests/test_actions_openstack_upgrade.py b/unit_tests/test_actions_openstack_upgrade.py index 7f59ce8..3ec7238 100644 --- a/unit_tests/test_actions_openstack_upgrade.py +++ b/unit_tests/test_actions_openstack_upgrade.py @@ -29,7 +29,7 @@ with patch('charmhelpers.contrib.hardening.harden.harden') as mock_dec: mock_dec.side_effect = (lambda *dargs, **dkwargs: lambda f: lambda *args, **kwargs: f(*args, **kwargs)) with patch('ceilometer_utils.register_configs') as register_configs: - with patch('ceilometer_utils.ceilometer_release_services'): + with patch('ceilometer_utils.restart_map') as restart_map: import openstack_upgrade from test_utils import ( diff --git a/unit_tests/test_ceilometer_utils.py b/unit_tests/test_ceilometer_utils.py index 03079b6..0cb68cf 100644 --- a/unit_tests/test_ceilometer_utils.py +++ b/unit_tests/test_ceilometer_utils.py @@ -34,6 +34,8 @@ TO_PATCH = [ 'os_application_version_set', 'init_is_systemd', 'os', + 'enable_memcache', + 'token_cache_pkgs', ] @@ -109,6 +111,7 @@ class CeilometerUtilsTest(CharmTestCase): '/etc/systemd/system/ceilometer-api.service.d/override.conf': [ 'ceilometer-api'], '/etc/haproxy/haproxy.cfg': ['haproxy'], + '/etc/memcached.conf': ['memcached'], "/etc/apache2/sites-available/openstack_https_frontend": [ 'ceilometer-api', 'apache2'], "/etc/apache2/sites-available/openstack_https_frontend.conf": [ @@ -119,6 +122,7 @@ class CeilometerUtilsTest(CharmTestCase): def test_restart_map_mitaka(self): """Ensure that alarming services are missing for OpenStack Mitaka""" self.get_os_codename_install_source.return_value = 'mitaka' + self.maxDiff = None restart_map = utils.restart_map() self.assertEquals( restart_map, @@ -130,6 +134,7 @@ class CeilometerUtilsTest(CharmTestCase): '/etc/systemd/system/ceilometer-api.service.d/override.conf': [ 'ceilometer-api'], '/etc/haproxy/haproxy.cfg': ['haproxy'], + '/etc/memcached.conf': ['memcached'], "/etc/apache2/sites-available/openstack_https_frontend": [ 'ceilometer-api', 'apache2'], "/etc/apache2/sites-available/openstack_https_frontend.conf": [ @@ -153,6 +158,7 @@ class CeilometerUtilsTest(CharmTestCase): self.config.side_effect = self.test_config.get self.test_config.set('openstack-origin', 'cloud:trusty-kilo') self.get_os_codename_install_source.return_value = 'kilo' + self.enable_memcache.return_value = False configs = MagicMock() utils.do_openstack_upgrade(configs) configs.set_release.assert_called_with(openstack_release='kilo') @@ -172,15 +178,18 @@ class CeilometerUtilsTest(CharmTestCase): def test_get_packages_icehouse(self): self.get_os_codename_install_source.return_value = 'icehouse' + self.token_cache_pkgs.return_value = [] self.assertEqual(utils.get_packages(), utils.CEILOMETER_BASE_PACKAGES + utils.ICEHOUSE_PACKAGES) def test_get_packages_mitaka(self): self.get_os_codename_install_source.return_value = 'mitaka' + self.token_cache_pkgs.return_value = ['memcached'] self.assertEqual(utils.get_packages(), utils.CEILOMETER_BASE_PACKAGES + - utils.MITAKA_PACKAGES) + utils.MITAKA_PACKAGES + + ['memcached']) def test_assess_status(self): with patch.object(utils, 'assess_status_func') as asf: