From f7dc1f111c3599e309fa6416de72a9464a89a260 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Fri, 9 Dec 2016 10:29:31 +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: Ic921a7efbd860cbcd4da2313126f73d7bdfdb4ed --- .../contrib/openstack/amulet/utils.py | 67 +++++++++++++++++++ .../charmhelpers/contrib/openstack/context.py | 25 +++++++ .../openstack/templates/memcached.conf | 53 +++++++++++++++ .../section-keystone-authtoken-mitaka | 3 + hooks/charmhelpers/contrib/openstack/utils.py | 25 +++++++ hooks/heat_utils.py | 17 ++++- tests/basic_deployment.py | 6 ++ tests/charmhelpers/contrib/amulet/utils.py | 3 +- .../contrib/openstack/amulet/utils.py | 67 +++++++++++++++++++ unit_tests/test_heat_utils.py | 12 ++++ 10 files changed, 275 insertions(+), 3 deletions(-) create mode 100644 hooks/charmhelpers/contrib/openstack/templates/memcached.conf diff --git a/hooks/charmhelpers/contrib/openstack/amulet/utils.py b/hooks/charmhelpers/contrib/openstack/amulet/utils.py index 6a0ba83..2b0a562 100644 --- a/hooks/charmhelpers/contrib/openstack/amulet/utils.py +++ b/hooks/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/hooks/charmhelpers/contrib/openstack/context.py b/hooks/charmhelpers/contrib/openstack/context.py index d5b3a33..91ddcec 100644 --- a/hooks/charmhelpers/contrib/openstack/context.py +++ b/hooks/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/hooks/charmhelpers/contrib/openstack/templates/memcached.conf b/hooks/charmhelpers/contrib/openstack/templates/memcached.conf new file mode 100644 index 0000000..26cb037 --- /dev/null +++ b/hooks/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/hooks/charmhelpers/contrib/openstack/templates/section-keystone-authtoken-mitaka b/hooks/charmhelpers/contrib/openstack/templates/section-keystone-authtoken-mitaka index 7c6f0c3..8e6889e 100644 --- a/hooks/charmhelpers/contrib/openstack/templates/section-keystone-authtoken-mitaka +++ b/hooks/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/hooks/charmhelpers/contrib/openstack/utils.py b/hooks/charmhelpers/contrib/openstack/utils.py index 553ff8f..59f9f51 100644 --- a/hooks/charmhelpers/contrib/openstack/utils.py +++ b/hooks/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/hooks/heat_utils.py b/hooks/heat_utils.py index f6eb8aa..9e50a3e 100644 --- a/hooks/heat_utils.py +++ b/hooks/heat_utils.py @@ -22,7 +22,10 @@ from charmhelpers.contrib.openstack import context, templating from charmhelpers.contrib.openstack.utils import ( configure_installation_source, get_os_codename_install_source, - os_release) + os_release, + token_cache_pkgs, + enable_memcache, +) from charmhelpers.fetch import ( add_source, @@ -89,6 +92,7 @@ HTTPS_APACHE_CONF = '/etc/apache2/sites-available/openstack_https_frontend' HTTPS_APACHE_24_CONF = os.path.join('/etc/apache2/sites-available', 'openstack_https_frontend.conf') ADMIN_OPENRC = '/root/admin-openrc-v3' +MEMCACHED_CONF = '/etc/memcached.conf' CONFIG_FILES = OrderedDict([ (HEAT_CONF, { @@ -104,7 +108,8 @@ CONFIG_FILES = OrderedDict([ context.SyslogContext(), context.LogLevelContext(), context.WorkerConfigContext(), - context.BindHostContext()] + context.BindHostContext(), + context.MemcacheContext()], }), (HEAT_API_PASTE, { 'services': [s for s in BASE_SERVICES if 'api' in s], @@ -128,6 +133,10 @@ CONFIG_FILES = OrderedDict([ service_user=SVC)], 'services': [] }), + (MEMCACHED_CONF, { + 'hook_contexts': [context.MemcacheContext()], + 'services': ['memcached'], + }), ]) @@ -147,6 +156,9 @@ def register_configs(): configs.register(HTTPS_APACHE_CONF, CONFIG_FILES[HTTPS_APACHE_CONF]['contexts']) + if enable_memcache(release=release): + configs.register(MEMCACHED_CONF, + CONFIG_FILES[MEMCACHED_CONF]['hook_contexts']) return configs @@ -157,6 +169,7 @@ def api_port(service): def determine_packages(): # currently all packages match service names packages = BASE_PACKAGES + BASE_SERVICES + packages.extend(token_cache_pkgs(source=config('openstack-origin'))) return list(set(packages)) diff --git a/tests/basic_deployment.py b/tests/basic_deployment.py index 21b6650..54b55b4 100644 --- a/tests/basic_deployment.py +++ b/tests/basic_deployment.py @@ -354,6 +354,12 @@ class HeatBasicDeployment(OpenStackAmuletDeployment): message = 'heat endpoint: {}'.format(ret) amulet.raise_status(amulet.FAIL, msg=message) + def test_130_memcache(self): + u.validate_memcache(self.heat_sentry, + '/etc/heat/heat.conf', + self._get_openstack_release(), + earliest_release=self.trusty_mitaka) + def test_200_heat_mysql_shared_db_relation(self): """Verify the heat:mysql shared-db relation data""" u.log.debug('Checking heat:mysql shared-db relation 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_heat_utils.py b/unit_tests/test_heat_utils.py index 0cd4c0d..934ff4a 100644 --- a/unit_tests/test_heat_utils.py +++ b/unit_tests/test_heat_utils.py @@ -37,6 +37,8 @@ TO_PATCH = [ 'check_call', 'service_start', 'service_stop', + 'token_cache_pkgs', + 'enable_memcache', ] @@ -49,6 +51,7 @@ RESTART_MAP = OrderedDict([ ('/etc/apache2/sites-available/openstack_https_frontend', ['apache2']), ('/etc/apache2/sites-available/openstack_https_frontend.conf', ['apache2']), + ('/etc/memcached.conf', ['memcached']), ]) @@ -65,6 +68,15 @@ class HeatUtilsTests(CharmTestCase): ex = list(set(utils.BASE_PACKAGES + utils.BASE_SERVICES)) self.assertEquals(ex, pkgs) + @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext') + def test_determine_packages_newton(self, subcontext): + self.os_release.return_value = 'newton' + self.token_cache_pkgs.return_value = ['memcached'] + pkgs = utils.determine_packages() + ex = list(set(utils.BASE_PACKAGES + ['memcached'] + + utils.BASE_SERVICES)) + self.assertEquals(ex, pkgs) + def test_restart_map(self): self.assertEquals(RESTART_MAP, utils.restart_map())