diff --git a/README.md b/README.md index 01e6789..f510b3c 100644 --- a/README.md +++ b/README.md @@ -17,18 +17,31 @@ default install Ceilometer and its dependencies from the Cloud Archive. Usage ----- -In order to deploy Ceilometer service, the MongoDB service is required: +In order to deploy Ceilometer service (prior to Queens), the MongoDB +service is required: juju deploy mongodb juju deploy ceilometer juju add-relation ceilometer mongodb +For OpenStack Queens or later, Gnocchi should be used instead of MongoDB +for resource, metrics and measure storage: + + juju add-relation ceilometer gnocchi + then Keystone and Rabbit relationships need to be established: juju add-relation ceilometer rabbitmq juju add-relation ceilometer keystone:identity-service juju add-relation ceilometer keystone:identity-notifications +For OpenStack Queens, the identity-service relation must be replaced +with the identity-credentials relation: + + juju add-relation ceilometer keystone:identity-credentials + +Ceilometer@Queens does not provide an API service. + In order to capture the calculations, a Ceilometer compute agent needs to be installed in each nova node, and be related with Ceilometer service: diff --git a/charmhelpers/contrib/openstack/context.py b/charmhelpers/contrib/openstack/context.py index 70850c1..321fe32 100644 --- a/charmhelpers/contrib/openstack/context.py +++ b/charmhelpers/contrib/openstack/context.py @@ -334,10 +334,7 @@ class IdentityServiceContext(OSContextGenerator): self.rel_name = rel_name self.interfaces = [self.rel_name] - def __call__(self): - log('Generating template context for ' + self.rel_name, level=DEBUG) - ctxt = {} - + def _setup_pki_cache(self): if self.service and self.service_user: # This is required for pki token signing if we don't want /tmp to # be used. @@ -347,6 +344,15 @@ class IdentityServiceContext(OSContextGenerator): mkdir(path=cachedir, owner=self.service_user, group=self.service_user, perms=0o700) + return cachedir + return None + + def __call__(self): + log('Generating template context for ' + self.rel_name, level=DEBUG) + ctxt = {} + + cachedir = self._setup_pki_cache() + if cachedir: ctxt['signing_dir'] = cachedir for rid in relation_ids(self.rel_name): @@ -385,6 +391,62 @@ class IdentityServiceContext(OSContextGenerator): return {} +class IdentityCredentialsContext(IdentityServiceContext): + '''Context for identity-credentials interface type''' + + def __init__(self, + service=None, + service_user=None, + rel_name='identity-credentials'): + super(IdentityCredentialsContext, self).__init__(service, + service_user, + rel_name) + + def __call__(self): + log('Generating template context for ' + self.rel_name, level=DEBUG) + ctxt = {} + + cachedir = self._setup_pki_cache() + if cachedir: + ctxt['signing_dir'] = cachedir + + for rid in relation_ids(self.rel_name): + self.related = True + for unit in related_units(rid): + rdata = relation_get(rid=rid, unit=unit) + credentials_host = rdata.get('credentials_host') + credentials_host = ( + format_ipv6_addr(credentials_host) or credentials_host + ) + auth_host = rdata.get('auth_host') + auth_host = format_ipv6_addr(auth_host) or auth_host + svc_protocol = rdata.get('credentials_protocol') or 'http' + auth_protocol = rdata.get('auth_protocol') or 'http' + api_version = rdata.get('api_version') or '2.0' + ctxt.update({ + 'service_port': rdata.get('credentials_port'), + 'service_host': credentials_host, + 'auth_host': auth_host, + 'auth_port': rdata.get('auth_port'), + 'admin_tenant_name': rdata.get('credentials_project'), + 'admin_tenant_id': rdata.get('credentials_project_id'), + 'admin_user': rdata.get('credentials_username'), + 'admin_password': rdata.get('credentials_password'), + 'service_protocol': svc_protocol, + 'auth_protocol': auth_protocol, + 'api_version': api_version + }) + + if float(api_version) > 2: + ctxt.update({'admin_domain_name': + rdata.get('domain')}) + + if self.context_complete(ctxt): + return ctxt + + return {} + + class AMQPContext(OSContextGenerator): def __init__(self, ssl_dir=None, rel_name='amqp', relation_prefix=None): diff --git a/charmhelpers/contrib/openstack/utils.py b/charmhelpers/contrib/openstack/utils.py index 9e5af34..e1d852d 100644 --- a/charmhelpers/contrib/openstack/utils.py +++ b/charmhelpers/contrib/openstack/utils.py @@ -2045,14 +2045,25 @@ def token_cache_pkgs(source=None, release=None): def update_json_file(filename, items): """Updates the json `filename` with a given dict. - :param filename: json filename (i.e.: /etc/glance/policy.json) + :param filename: path to json file (e.g. /etc/glance/policy.json) :param items: dict of items to update """ + if not items: + return + with open(filename) as fd: policy = json.load(fd) + + # Compare before and after and if nothing has changed don't write the file + # since that could cause unnecessary service restarts. + before = json.dumps(policy, indent=4, sort_keys=True) policy.update(items) + after = json.dumps(policy, indent=4, sort_keys=True) + if before == after: + return + with open(filename, "w") as fd: - fd.write(json.dumps(policy, indent=4)) + fd.write(after) @cached diff --git a/charmhelpers/contrib/storage/linux/ceph.py b/charmhelpers/contrib/storage/linux/ceph.py index 0d9bacf..12c041e 100644 --- a/charmhelpers/contrib/storage/linux/ceph.py +++ b/charmhelpers/contrib/storage/linux/ceph.py @@ -621,16 +621,24 @@ def create_erasure_profile(service, profile_name, erasure_plugin_name='jerasure' :param durability_estimator: int :return: None. Can raise CalledProcessError """ + version = ceph_version() + # Ensure this failure_domain is allowed by Ceph validator(failure_domain, six.string_types, ['chassis', 'datacenter', 'host', 'osd', 'pdu', 'pod', 'rack', 'region', 'room', 'root', 'row']) cmd = ['ceph', '--id', service, 'osd', 'erasure-code-profile', 'set', profile_name, - 'plugin=' + erasure_plugin_name, 'k=' + str(data_chunks), 'm=' + str(coding_chunks), - 'ruleset_failure_domain=' + failure_domain] + 'plugin=' + erasure_plugin_name, 'k=' + str(data_chunks), 'm=' + str(coding_chunks) + ] if locality is not None and durability_estimator is not None: raise ValueError("create_erasure_profile should be called with k, m and one of l or c but not both.") + # failure_domain changed in luminous + if version and version >= '12.0.0': + cmd.append('crush-failure-domain=' + failure_domain) + else: + cmd.append('ruleset-failure-domain=' + failure_domain) + # Add plugin specific information if locality is not None: # For local erasure codes @@ -1064,14 +1072,24 @@ class CephBrokerRq(object): self.ops = [] def add_op_request_access_to_group(self, name, namespace=None, - permission=None, key_name=None): + permission=None, key_name=None, + object_prefix_permissions=None): """ Adds the requested permissions to the current service's Ceph key, - allowing the key to access only the specified pools + allowing the key to access only the specified pools or + object prefixes. object_prefix_permissions should be a dictionary + keyed on the permission with the corresponding value being a list + of prefixes to apply that permission to. + { + 'rwx': ['prefix1', 'prefix2'], + 'class-read': ['prefix3']} """ - self.ops.append({'op': 'add-permissions-to-key', 'group': name, - 'namespace': namespace, 'name': key_name or service_name(), - 'group-permission': permission}) + self.ops.append({ + 'op': 'add-permissions-to-key', 'group': name, + 'namespace': namespace, + 'name': key_name or service_name(), + 'group-permission': permission, + 'object-prefix-permissions': object_prefix_permissions}) def add_op_create_pool(self, name, replica_count=3, pg_num=None, weight=None, group=None, namespace=None): @@ -1107,7 +1125,10 @@ class CephBrokerRq(object): def _ops_equal(self, other): if len(self.ops) == len(other.ops): for req_no in range(0, len(self.ops)): - for key in ['replicas', 'name', 'op', 'pg_num', 'weight']: + for key in [ + 'replicas', 'name', 'op', 'pg_num', 'weight', + 'group', 'group-namespace', 'group-permission', + 'object-prefix-permissions']: if self.ops[req_no].get(key) != other.ops[req_no].get(key): return False else: diff --git a/hooks/ceilometer_hooks.py b/hooks/ceilometer_hooks.py index e85ef97..1a36513 100755 --- a/hooks/ceilometer_hooks.py +++ b/hooks/ceilometer_hooks.py @@ -26,6 +26,7 @@ from charmhelpers.fetch import ( ) from charmhelpers.core.hookenv import ( open_port, + close_port, relation_get, relation_set, relation_ids, @@ -48,6 +49,8 @@ from charmhelpers.contrib.openstack.utils import ( openstack_upgrade_available, pausable_restart_on_change as restart_on_change, is_unit_paused_set, + get_os_codename_install_source, + CompareOpenStackReleases, ) from charmhelpers.contrib.openstack.ha.utils import ( update_dns_ha_resource_params, @@ -112,7 +115,6 @@ def install(): status_set('maintenance', 'Installing packages') apt_update(fatal=True) apt_install(packages, fatal=True) - open_port(CEILOMETER_PORT) if init_is_systemd(): # NOTE(jamespage): ensure systemd override folder exists prior to # attempting to write override.conf @@ -146,6 +148,8 @@ def metric_service_joined(): "shared-db-relation-departed", "identity-service-relation-changed", "identity-service-relation-departed", + "identity-credentials-relation-changed", + "identity-credentials-relation-departed", "metric-service-relation-changed", "metric-service-relation-departed") @restart_on_change(restart_map()) @@ -159,8 +163,13 @@ def any_changed(): # and mongodb to be configured to successfully # upgrade the underlying data stores. if ('metric-service' in CONFIGS.complete_contexts() and - 'identity-service' in CONFIGS.complete_contexts() and - 'mongodb' in CONFIGS.complete_contexts()): + 'identity-service' in CONFIGS.complete_contexts()): + cmp_codename = CompareOpenStackReleases( + get_os_codename_install_source(config('openstack-origin'))) + # NOTE(jamespage): however at queens, this limitation has gone! + if (cmp_codename < 'queens' and + 'mongodb' not in CONFIGS.complete_contexts()): + return ceilometer_upgrade() @@ -168,6 +177,10 @@ def configure_https(): """Enables SSL API Apache config if appropriate.""" # need to write all to ensure changes to the entire request pipeline # propagate (c-api, haprxy, apache) + cmp_codename = CompareOpenStackReleases( + get_os_codename_install_source(config('openstack-origin'))) + if cmp_codename >= 'queens': + return CONFIGS.write_all() if 'https' in CONFIGS.complete_contexts(): cmd = ['a2ensite', 'openstack_https_frontend'] @@ -200,9 +213,23 @@ def config_changed(): # reload ensures port override is set correctly reload_systemd() ceilometer_joined() + + cmp_codename = CompareOpenStackReleases( + get_os_codename_install_source(config('openstack-origin'))) + if cmp_codename < 'queens': + open_port(CEILOMETER_PORT) + else: + close_port(CEILOMETER_PORT) + configure_https() + + # NOTE(jamespage): Iterate identity-{service,credentials} relations + # to pickup any required databag changes on these + # relations. for rid in relation_ids('identity-service'): keystone_joined(relid=rid) + for rid in relation_ids('identity-credentials'): + keystone_credentials_joined(relid=rid) # Define the new ocf resource and use the key delete_resources to delete # legacy resource for >= Liberty since the ceilometer-agent-central moved @@ -350,8 +377,21 @@ def ha_changed(): keystone_joined(relid=rid) +@hooks.hook("identity-credentials-relation-joined") +def keystone_credentials_joined(relid=None): + relation_set(relation_id=relid, + username=CEILOMETER_SERVICE, + requested_roles=CEILOMETER_ROLE) + + @hooks.hook("identity-service-relation-joined") def keystone_joined(relid=None): + cmp_codename = CompareOpenStackReleases( + get_os_codename_install_source(config('openstack-origin'))) + if cmp_codename >= 'queens': + log('Skipping endpoint registration for >= Queens', level=DEBUG) + return + if config('vip') and not is_clustered(): log('Defering registration until clustered', level=DEBUG) return diff --git a/hooks/identity-credentials-relation-broken b/hooks/identity-credentials-relation-broken new file mode 120000 index 0000000..c948469 --- /dev/null +++ b/hooks/identity-credentials-relation-broken @@ -0,0 +1 @@ +ceilometer_hooks.py \ No newline at end of file diff --git a/hooks/identity-credentials-relation-changed b/hooks/identity-credentials-relation-changed new file mode 120000 index 0000000..c948469 --- /dev/null +++ b/hooks/identity-credentials-relation-changed @@ -0,0 +1 @@ +ceilometer_hooks.py \ No newline at end of file diff --git a/hooks/identity-credentials-relation-departed b/hooks/identity-credentials-relation-departed new file mode 120000 index 0000000..c948469 --- /dev/null +++ b/hooks/identity-credentials-relation-departed @@ -0,0 +1 @@ +ceilometer_hooks.py \ No newline at end of file diff --git a/hooks/identity-credentials-relation-joined b/hooks/identity-credentials-relation-joined new file mode 120000 index 0000000..c948469 --- /dev/null +++ b/hooks/identity-credentials-relation-joined @@ -0,0 +1 @@ +ceilometer_hooks.py \ No newline at end of file diff --git a/lib/ceilometer_contexts.py b/lib/ceilometer_contexts.py index 3068e3d..c577fb5 100644 --- a/lib/ceilometer_contexts.py +++ b/lib/ceilometer_contexts.py @@ -16,7 +16,7 @@ from charmhelpers.core.hookenv import ( relation_ids, relation_get, related_units, - config + config, ) from charmhelpers.contrib.openstack.utils import ( diff --git a/lib/ceilometer_utils.py b/lib/ceilometer_utils.py index deed81e..f75da72 100644 --- a/lib/ceilometer_utils.py +++ b/lib/ceilometer_utils.py @@ -69,6 +69,11 @@ CEILOMETER_BASE_SERVICES = [ 'ceilometer-api', ] +QUEENS_SERVICES = [ + 'ceilometer-agent-central', + 'ceilometer-agent-notification' +] + ICEHOUSE_SERVICES = [ 'ceilometer-alarm-notifier', 'ceilometer-alarm-evaluator', @@ -101,6 +106,11 @@ MITAKA_PACKAGES = [ 'ceilometer-agent-notification' ] +QUEENS_PACKAGES = [ + 'ceilometer-agent-central', + 'ceilometer-agent-notification' +] + REQUIRED_INTERFACES = { 'database': ['mongodb'], 'messaging': ['amqp'], @@ -112,6 +122,23 @@ SVC = 'ceilometer' WSGI_CEILOMETER_API_CONF = '/etc/apache2/sites-enabled/wsgi-openstack-api.conf' PACKAGE_CEILOMETER_API_CONF = '/etc/apache2/sites-enabled/ceilometer-api.conf' +QUEENS_CONFIG_FILES = OrderedDict([ + (CEILOMETER_CONF, { + 'hook_contexts': [ + context.IdentityCredentialsContext(service=SVC, + service_user=SVC), + context.AMQPContext(ssl_dir=CEILOMETER_CONF_DIR), + LoggingConfigContext(), + MongoDBContext(), + CeilometerContext(), + context.SyslogContext(), + context.MemcacheContext(), + MetricServiceContext(), + context.WorkerConfigContext()], + 'services': QUEENS_SERVICES + }), +]) + CONFIG_FILES = OrderedDict([ (CEILOMETER_CONF, { 'hook_contexts': [context.IdentityServiceContext(service=SVC, @@ -124,7 +151,8 @@ CONFIG_FILES = OrderedDict([ context.SyslogContext(), HAProxyContext(), context.MemcacheContext(), - MetricServiceContext()], + MetricServiceContext(), + context.WorkerConfigContext()], 'services': CEILOMETER_BASE_SERVICES }), (CEILOMETER_API_SYSTEMD_CONF, { @@ -165,36 +193,46 @@ def register_configs(): # if called without anything installed (eg during install hook) # just default to earliest supported release. configs dont get touched # till post-install, anyway. + release = (get_os_codename_package('ceilometer-common', fatal=False) or 'grizzly') configs = templating.OSConfigRenderer(templates_dir=TEMPLATES, openstack_release=release) - - for conf in (CEILOMETER_CONF, HAPROXY_CONF): - configs.register(conf, CONFIG_FILES[conf]['hook_contexts']) - - if init_is_systemd(): - configs.register( - CEILOMETER_API_SYSTEMD_CONF, - CONFIG_FILES[CEILOMETER_API_SYSTEMD_CONF]['hook_contexts'] - ) - - if os.path.exists('/etc/apache2/conf-available'): - configs.register(HTTPS_APACHE_24_CONF, - CONFIG_FILES[HTTPS_APACHE_24_CONF]['hook_contexts']) + if CompareOpenStackReleases(release) >= 'queens': + for conf in QUEENS_CONFIG_FILES: + configs.register(conf, QUEENS_CONFIG_FILES[conf]['hook_contexts']) else: - configs.register(HTTPS_APACHE_CONF, - CONFIG_FILES[HTTPS_APACHE_CONF]['hook_contexts']) - if enable_memcache(release=release): - configs.register(MEMCACHED_CONF, [context.MemcacheContext()]) + for conf in (CEILOMETER_CONF, HAPROXY_CONF): + configs.register(conf, CONFIG_FILES[conf]['hook_contexts']) - if run_in_apache(): - wsgi_script = "/usr/share/ceilometer/app.wsgi" - configs.register(WSGI_CEILOMETER_API_CONF, - [context.WSGIWorkerConfigContext(name="ceilometer", - script=wsgi_script), - CeilometerContext(), - HAProxyContext()]) + if init_is_systemd(): + configs.register( + CEILOMETER_API_SYSTEMD_CONF, + CONFIG_FILES[CEILOMETER_API_SYSTEMD_CONF]['hook_contexts'] + ) + + if os.path.exists('/etc/apache2/conf-available'): + configs.register( + HTTPS_APACHE_24_CONF, + CONFIG_FILES[HTTPS_APACHE_24_CONF]['hook_contexts'] + ) + else: + configs.register( + HTTPS_APACHE_CONF, + CONFIG_FILES[HTTPS_APACHE_CONF]['hook_contexts'] + ) + if enable_memcache(release=release): + configs.register(MEMCACHED_CONF, [context.MemcacheContext()]) + + if run_in_apache(): + wsgi_script = "/usr/share/ceilometer/app.wsgi" + configs.register( + WSGI_CEILOMETER_API_CONF, + [context.WSGIWorkerConfigContext(name="ceilometer", + script=wsgi_script), + CeilometerContext(), + HAProxyContext()] + ) return configs @@ -206,8 +244,14 @@ def restart_map(): :returns: dict: A dictionary mapping config file to lists of services that should be restarted when file changes. """ + cmp_codename = CompareOpenStackReleases( + get_os_codename_install_source(config('openstack-origin'))) + if cmp_codename >= 'queens': + _config_files = QUEENS_CONFIG_FILES + else: + _config_files = CONFIG_FILES _map = {} - for f, ctxt in CONFIG_FILES.iteritems(): + for f, ctxt in _config_files.items(): svcs = [] for svc in ctxt['services']: svcs.append(svc) @@ -217,10 +261,11 @@ def restart_map(): if svcs: _map[f] = svcs - if enable_memcache(source=config('openstack-origin')): + if (cmp_codename < 'queens' and + enable_memcache(source=config('openstack-origin'))): _map[MEMCACHED_CONF] = ['memcached'] - if run_in_apache(): + if cmp_codename < 'queens' and run_in_apache(): for cfile in _map: svcs = _map[cfile] if 'ceilometer-api' in svcs: @@ -254,14 +299,24 @@ def determine_ports(): """ # TODO(ajkavanagh) - determine what other ports the service listens on # apart from the main CEILOMETER port - ports = [CEILOMETER_PORT] - return ports + cmp_codename = CompareOpenStackReleases( + get_os_codename_install_source(config('openstack-origin'))) + if cmp_codename >= 'queens': + # NOTE(jamespage): No API service for queens or later + return [] + return [CEILOMETER_PORT] def get_ceilometer_context(): """ Retrieve a map of all current relation data for agent configuration """ + cmp_codename = CompareOpenStackReleases( + get_os_codename_install_source(config('openstack-origin'))) + if cmp_codename >= 'queens': + _config_files = QUEENS_CONFIG_FILES + else: + _config_files = CONFIG_FILES ctxt = {} - for hcontext in CONFIG_FILES[CEILOMETER_CONF]['hook_contexts']: + for hcontext in _config_files[CEILOMETER_CONF]['hook_contexts']: ctxt.update(hcontext()) return ctxt @@ -301,9 +356,9 @@ def do_openstack_upgrade(configs): def ceilometer_release_services(): cmp_codename = CompareOpenStackReleases( get_os_codename_install_source(config('openstack-origin'))) - if cmp_codename >= 'mitaka': + if cmp_codename >= 'mitaka' and cmp_codename < 'queens': return MITAKA_SERVICES - elif cmp_codename >= 'icehouse': + elif cmp_codename >= 'icehouse' and cmp_codename < 'mitaka': return ICEHOUSE_SERVICES else: return [] @@ -312,7 +367,7 @@ def ceilometer_release_services(): def ceilometer_release_packages(): cmp_codename = CompareOpenStackReleases( get_os_codename_install_source(config('openstack-origin'))) - if cmp_codename >= 'mitaka': + if cmp_codename >= 'mitaka' and cmp_codename < 'queens': return MITAKA_PACKAGES elif cmp_codename >= 'icehouse': return ICEHOUSE_PACKAGES @@ -321,6 +376,14 @@ def ceilometer_release_packages(): def get_packages(): + cmp_codename = CompareOpenStackReleases( + get_os_codename_install_source(config('openstack-origin'))) + + # NOTE(jamespage): @queens ceilometer has no API service, so + # no requirement for token caching. + if cmp_codename >= 'queens': + return deepcopy(QUEENS_PACKAGES) + packages = (deepcopy(CEILOMETER_BASE_PACKAGES) + ceilometer_release_packages()) packages.extend(token_cache_pkgs(source=config('openstack-origin'))) @@ -378,6 +441,9 @@ def resolve_required_interfaces(): required_ints = deepcopy(REQUIRED_INTERFACES) if CompareOpenStackReleases(os_release('ceilometer-common')) >= 'mitaka': required_ints['database'].append('metric-service') + if CompareOpenStackReleases(os_release('ceilometer-common')) >= 'queens': + required_ints['database'].remove('mongodb') + required_ints['identity'] = ['identity-credentials'] return required_ints @@ -446,7 +512,8 @@ def run_in_apache(): """Return true if ceilometer API is run under apache2 with mod_wsgi in this release. """ - return CompareOpenStackReleases(os_release('ceilometer-common')) >= 'ocata' + os_cmp = CompareOpenStackReleases(os_release('ceilometer-common')) + return (os_cmp >= 'ocata' and os_cmp < 'queens') def disable_package_apache_site(): diff --git a/metadata.yaml b/metadata.yaml index 7a38dac..2e4d6df 100644 --- a/metadata.yaml +++ b/metadata.yaml @@ -36,6 +36,8 @@ requires: interface: keystone identity-notifications: interface: keystone-notifications + identity-credentials: + interface: keystone-credentials ha: interface: hacluster scope: container diff --git a/templates/ocata/ceilometer.conf b/templates/ocata/ceilometer.conf index 622b40b..ad8020d 100644 --- a/templates/ocata/ceilometer.conf +++ b/templates/ocata/ceilometer.conf @@ -17,10 +17,17 @@ event_dispatchers = gnocchi meter_dispatchers = database {%- endif %} +{% if transport_url -%} +transport_url = {{ transport_url }} +{%- endif %} + [api] port = {{ port }} workers = {{ api_workers }} +[notification] +workers = {{ workers }} + {% if service_host -%} [service_credentials] auth_url = {{ service_protocol }}://{{ service_host }}:{{ service_port }} diff --git a/unit_tests/test_ceilometer_hooks.py b/unit_tests/test_ceilometer_hooks.py index b2d8440..b271c8a 100644 --- a/unit_tests/test_ceilometer_hooks.py +++ b/unit_tests/test_ceilometer_hooks.py @@ -48,6 +48,7 @@ TO_PATCH = [ 'apt_install', 'apt_update', 'open_port', + 'close_port', 'config', 'log', 'relation_ids', @@ -69,6 +70,7 @@ TO_PATCH = [ 'init_is_systemd', 'get_relation_ip', 'is_clustered', + 'get_os_codename_install_source', ] @@ -82,6 +84,7 @@ class CeilometerHooksTest(CharmTestCase): self.filter_installed_packages.return_value = \ ceilometer_utils.CEILOMETER_BASE_PACKAGES self.lsb_release.return_value = {'DISTRIB_CODENAME': 'precise'} + self.get_os_codename_install_source.return_value = 'mitaka' @patch('charmhelpers.payload.execd.default_execd_dir', return_value=os.path.join(os.getcwd(), 'exec.d')) @@ -99,7 +102,6 @@ class CeilometerHooksTest(CharmTestCase): hooks.hooks.execute(['hooks/install.real']) self.configure_installation_source.\ assert_called_with('cloud:precise-grizzly') - self.open_port.assert_called_with(hooks.CEILOMETER_PORT) self.apt_update.assert_called_with(fatal=True) self.apt_install.assert_called_with( ceilometer_utils.CEILOMETER_BASE_PACKAGES, @@ -114,7 +116,6 @@ class CeilometerHooksTest(CharmTestCase): hooks.hooks.execute(['hooks/install.real']) self.configure_installation_source.\ assert_called_with('distro') - self.open_port.assert_called_with(hooks.CEILOMETER_PORT) self.apt_update.assert_called_with(fatal=True) self.apt_install.assert_called_with( ceilometer_utils.CEILOMETER_BASE_PACKAGES, @@ -201,6 +202,25 @@ class CeilometerHooksTest(CharmTestCase): self.assertTrue(self.CONFIGS.write_all.called) self.assertTrue(joined.called) self.assertTrue(self.reload_systemd.called) + self.open_port.assert_called_with(hooks.CEILOMETER_PORT) + + @patch.object(hooks, 'install_event_pipeline_setting') + @patch('charmhelpers.core.hookenv.config') + @patch.object(hooks, 'ceilometer_joined') + def test_config_changed_queens(self, + joined, mock_config, event_pipe): + self.openstack_upgrade_available.return_value = False + self.get_os_codename_install_source.return_value = 'queens' + hooks.hooks.execute(['hooks/config-changed']) + self.openstack_upgrade_available.\ + assert_called_with('ceilometer-common') + self.assertFalse(self.do_openstack_upgrade.called) + self.assertTrue(event_pipe.called) + self.assertTrue(self.CONFIGS.write_all.called) + self.assertTrue(joined.called) + self.assertTrue(self.reload_systemd.called) + self.close_port.assert_called_with(hooks.CEILOMETER_PORT) + self.open_port.assert_not_called() @patch.object(hooks, 'install_event_pipeline_setting') @patch('charmhelpers.core.hookenv.config') @@ -216,6 +236,7 @@ class CeilometerHooksTest(CharmTestCase): self.assertTrue(self.CONFIGS.write_all.called) self.assertTrue(joined.called) self.assertTrue(self.reload_systemd.called) + self.open_port.assert_called_with(hooks.CEILOMETER_PORT) @patch.object(hooks, 'install_event_pipeline_setting') def test_config_changed_with_openstack_upgrade_action(self, @@ -227,6 +248,14 @@ class CeilometerHooksTest(CharmTestCase): self.assertFalse(self.do_openstack_upgrade.called) self.assertTrue(event_pipe.called) + self.open_port.assert_called_with(hooks.CEILOMETER_PORT) + + def test_keystone_credentials_joined(self): + hooks.hooks.execute(['hooks/identity-credentials-relation-joined']) + self.relation_set.assert_called_with( + username=hooks.CEILOMETER_SERVICE, + requested_roles=hooks.CEILOMETER_ROLE, + relation_id=None) @patch.object(hooks, 'canonical_url') @patch('charmhelpers.core.hookenv.config') diff --git a/unit_tests/test_ceilometer_utils.py b/unit_tests/test_ceilometer_utils.py index 9a4a0b9..e6367d0 100644 --- a/unit_tests/test_ceilometer_utils.py +++ b/unit_tests/test_ceilometer_utils.py @@ -47,6 +47,7 @@ class CeilometerUtilsTest(CharmTestCase): def setUp(self): super(CeilometerUtilsTest, self).setUp(utils, TO_PATCH) self.config.side_effect = self.test_config.get + self.get_os_codename_install_source.return_value = 'icehouse' def tearDown(self): super(CeilometerUtilsTest, self).tearDown() @@ -55,6 +56,7 @@ class CeilometerUtilsTest(CharmTestCase): self.os.path.exists.return_value = True self.init_is_systemd.return_value = False self.os_release.return_value = 'havana' + self.get_os_codename_package.return_value = 'havana' configs = utils.register_configs() calls = [] for conf in (utils.CEILOMETER_CONF, utils.HAPROXY_CONF, @@ -67,6 +69,7 @@ class CeilometerUtilsTest(CharmTestCase): self.os.path.exists.return_value = False self.init_is_systemd.return_value = False self.os_release.return_value = 'havana' + self.get_os_codename_package.return_value = 'havana' configs = utils.register_configs() calls = [] for conf in (utils.CEILOMETER_CONF, utils.HAPROXY_CONF, @@ -79,6 +82,7 @@ class CeilometerUtilsTest(CharmTestCase): self.os.path.exists.return_value = True self.init_is_systemd.return_value = True self.os_release.return_value = 'havana' + self.get_os_codename_package.return_value = 'havana' configs = utils.register_configs() calls = [] for conf in (utils.CEILOMETER_CONF, utils.HAPROXY_CONF, @@ -101,6 +105,12 @@ class CeilometerUtilsTest(CharmTestCase): self.assertEqual(['ceilometer-agent-notification'], utils.ceilometer_release_services()) + def test_ceilometer_release_services_queens(self): + """Ensure that queens specific services are identified""" + self.get_os_codename_install_source.return_value = 'queens' + self.assertEqual([], + utils.ceilometer_release_services()) + def test_restart_map(self): """Ensure that alarming services are present for < OpenStack Mitaka""" self.get_os_codename_install_source.return_value = 'icehouse' @@ -150,6 +160,20 @@ class CeilometerUtilsTest(CharmTestCase): } ) + def test_restart_map_queens(self): + """Ensure that alarming services are missing for OpenStack Queens""" + self.get_os_codename_install_source.return_value = 'queens' + self.os_release.return_value = 'queens' + self.maxDiff = None + restart_map = utils.restart_map() + self.assertEqual( + restart_map, + {'/etc/ceilometer/ceilometer.conf': [ + 'ceilometer-agent-central', + 'ceilometer-agent-notification'], + } + ) + def test_get_ceilometer_conf(self): class TestContext(): @@ -272,6 +296,18 @@ class CeilometerUtilsTest(CharmTestCase): } ) + def test_resolve_required_interfaces_queens(self): + self.os_release.side_effect = None + self.os_release.return_value = 'queens' + self.assertEqual( + utils.resolve_required_interfaces(), + { + 'database': ['metric-service'], + 'messaging': ['amqp'], + 'identity': ['identity-credentials'], + } + ) + @patch.object(utils, 'subprocess') def test_ceilometer_upgrade(self, mock_subprocess): self.is_leader.return_value = True