Add support for remote consumers of Ceilometer event data.

There is a use-case to forward events received by ceilometer from
OpenStack services to an external system via http/udp. It comes
from the fact that aodh does not include all information about an
event alarm into POSTed data. Raw event data can be published by
ceilometer via http/udp to an external system without giving a
user direct access to rabbitmq.

This patch adds a context generator, associated templates, and
configuration entry for configuring multiple event-sinks in
/etc/ceilometer/event_pipeline.yaml. This also modifies the
ceilometer.conf templates to reflect the correct naming of the
same file to align with upstream Ceilometer.

Also adds support for a future Panko charm event consumer using
the event-service interface in the above context with associated
metadata and hook symlinks.

This change will only effect Mitaka and later clouds.

Closes Bug: 1763321

Change-Id: I931438c720272bd9a3d2b958ebabcd3584790bd0
This commit is contained in:
Michael Skalka
2018-05-04 03:52:18 -04:00
parent 9dc9ce6e09
commit 668a289862
15 changed files with 144 additions and 4 deletions

View File

@@ -239,3 +239,13 @@ options:
Openstack mostly defaults to using public endpoints for Openstack mostly defaults to using public endpoints for
internal communication between services. If set to True this option internal communication between services. If set to True this option
will configure services to use internal endpoints where possible. will configure services to use internal endpoints where possible.
remote-sink:
type: string
default:
description: |
Space delimited list of remote consumers of Ceilometer event reporting
which reside outside of the deployed model. Only supported for Mitaka
and later clouds. e.g.
.
'udp://<host>:<port>/'
'prometheus://pushgateway-host:9091/metrics/job/openstack-telemetry'

View File

@@ -153,7 +153,9 @@ def metric_service_joined():
"identity-credentials-relation-changed", "identity-credentials-relation-changed",
"identity-credentials-relation-departed", "identity-credentials-relation-departed",
"metric-service-relation-changed", "metric-service-relation-changed",
"metric-service-relation-departed") "metric-service-relation-departed",
"event-service-relation-changed",
"event-service-relation-departed",)
@restart_on_change(restart_map()) @restart_on_change(restart_map())
def any_changed(): def any_changed():
CONFIGS.write_all() CONFIGS.write_all()

View File

@@ -0,0 +1 @@
ceilometer_hooks.py

View File

@@ -0,0 +1 @@
ceilometer_hooks.py

View File

@@ -0,0 +1 @@
ceilometer_hooks.py

View File

@@ -0,0 +1 @@
ceilometer_hooks.py

View File

@@ -140,6 +140,26 @@ class HAProxyContext(OSContextGenerator):
return ctxt return ctxt
class RemoteSinksContext(OSContextGenerator):
interfaces = ['event-service']
def __call__(self):
'''Generates context for remote sinks for Panko and other compatible
remote consumers of Ceilometer event data.
'''
ctxt = {}
if config('remote-sink'):
ctxt['remote_sinks'] = config('remote-sink').split(' ')
for relid in relation_ids('event-service'):
for unit in related_units(relid):
publisher = relation_get('publisher', unit=unit, rid=relid)
if publisher:
if not ctxt.get('internal_sinks'):
ctxt['internal_sinks'] = {}
ctxt['internal_sinks'][unit.split('/')[0]] = publisher
return ctxt
class ApacheSSLContext(SSLContext): class ApacheSSLContext(SSLContext):
external_ports = [CEILOMETER_PORT] external_ports = [CEILOMETER_PORT]

View File

@@ -31,6 +31,7 @@ from ceilometer_contexts import (
HAProxyContext, HAProxyContext,
MetricServiceContext, MetricServiceContext,
CEILOMETER_PORT, CEILOMETER_PORT,
RemoteSinksContext,
) )
from charmhelpers.contrib.openstack.utils import ( from charmhelpers.contrib.openstack.utils import (
get_os_codename_package, get_os_codename_package,
@@ -51,6 +52,7 @@ from charmhelpers.core.hookenv import (
is_leader, is_leader,
log, log,
DEBUG, DEBUG,
relation_ids,
) )
from charmhelpers.fetch import apt_update, apt_install, apt_upgrade from charmhelpers.fetch import apt_update, apt_install, apt_upgrade
from charmhelpers.core.host import init_is_systemd from charmhelpers.core.host import init_is_systemd
@@ -67,6 +69,7 @@ HTTPS_APACHE_24_CONF = "/etc/apache2/sites-available/" \
"openstack_https_frontend.conf" "openstack_https_frontend.conf"
CLUSTER_RES = 'grp_ceilometer_vips' CLUSTER_RES = 'grp_ceilometer_vips'
MEMCACHED_CONF = '/etc/memcached.conf' MEMCACHED_CONF = '/etc/memcached.conf'
PIPELINE_CONF = '/etc/ceilometer/event_pipeline.yaml'
CEILOMETER_BASE_SERVICES = [ CEILOMETER_BASE_SERVICES = [
'ceilometer-agent-central', 'ceilometer-agent-central',
@@ -238,6 +241,8 @@ def register_configs():
CeilometerContext(), CeilometerContext(),
HAProxyContext()] HAProxyContext()]
) )
if CompareOpenStackReleases(release) >= 'mitaka':
configs.register(PIPELINE_CONF, [RemoteSinksContext()])
return configs return configs
@@ -420,6 +425,17 @@ def set_shared_secret(secret):
secret_file.write(secret) secret_file.write(secret)
def get_optional_relations():
"""Return a dictionary of optional relations.
@returns {relation: relation_name}
"""
optional_interfaces = {}
if relation_ids('event-service'):
optional_interfaces['event-service'] = ['event-service']
return optional_interfaces
def assess_status(configs): def assess_status(configs):
"""Assess status of current unit """Assess status of current unit
@@ -444,6 +460,7 @@ def resolve_required_interfaces():
@returns dict - a dictionary keyed by high-level type of interfaces names @returns dict - a dictionary keyed by high-level type of interfaces names
""" """
required_ints = deepcopy(REQUIRED_INTERFACES) required_ints = deepcopy(REQUIRED_INTERFACES)
required_ints.update(get_optional_relations())
if CompareOpenStackReleases(os_release('ceilometer-common')) >= 'mitaka': if CompareOpenStackReleases(os_release('ceilometer-common')) >= 'mitaka':
required_ints['database'].append('metric-service') required_ints['database'].append('metric-service')
if CompareOpenStackReleases(os_release('ceilometer-common')) >= 'queens': if CompareOpenStackReleases(os_release('ceilometer-common')) >= 'queens':

View File

@@ -43,6 +43,8 @@ requires:
scope: container scope: container
metric-service: metric-service:
interface: gnocchi interface: gnocchi
event-service:
interface: event-service
peers: peers:
cluster: cluster:
interface: ceilometer-ha interface: ceilometer-ha

View File

@@ -12,7 +12,7 @@ api_workers = {{ workers }}
collector_workers = {{ workers }} collector_workers = {{ workers }}
notification_workers = {{ workers }} notification_workers = {{ workers }}
event_pipeline_cfg_file = /etc/ceilometer/event_pipeline_alarm.yaml event_pipeline_cfg_file = /etc/ceilometer/event_pipeline.yaml
{% if gnocchi_url -%} {% if gnocchi_url -%}
meter_dispatchers = gnocchi meter_dispatchers = gnocchi

View File

@@ -0,0 +1,37 @@
---
sources:
- name: event_source
events:
- "*"
sinks:
- event_sink
{%- if remote_sinks %}
- remote_sink
{% endif %}
{%- if internal_sinks %}
{%- for item in internal_sinks.keys() %}
- {{ item }}
{% endfor -%}
{% endif %}
sinks:
{%- if remote_sinks %}
- name: remote_sink
transformers:
publishers:
{% for item in remote_sinks -%}
- {{ item }}
{% endfor %}
{%- endif -%}
{%- if internal_sinks %}
{%- for item, target in internal_sinks.items() -%}
- name: {{ item }}
transformers:
publishers:
- {{ target }}
{%- endfor %}
{% endif %}
- name: event_sink
transformers:
publishers:
- notifier://
- notifier://?topic=alarm.all

View File

@@ -8,7 +8,7 @@
debug = {{ debug }} debug = {{ debug }}
verbose = {{ verbose }} verbose = {{ verbose }}
use_syslog = {{ use_syslog }} use_syslog = {{ use_syslog }}
event_pipeline_cfg_file = /etc/ceilometer/event_pipeline_alarm.yaml event_pipeline_cfg_file = /etc/ceilometer/event_pipeline.yaml
{% if gnocchi_url -%} {% if gnocchi_url -%}
meter_dispatchers = gnocchi meter_dispatchers = gnocchi

View File

@@ -141,7 +141,7 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment):
} }
if self._get_openstack_release() >= self.xenial_pike: if self._get_openstack_release() >= self.xenial_pike:
configs['ceph-osd'] = {'osd-devices': '/dev/vdb', configs['ceph-osd'] = {'osd-devices': '/dev/vdb',
'osd-reformat': 'yes', 'osd-reformat': True,
'ephemeral-unmount': '/mnt'} 'ephemeral-unmount': '/mnt'}
super(CeilometerBasicDeployment, self)._configure_services(configs) super(CeilometerBasicDeployment, self)._configure_services(configs)

View File

@@ -193,3 +193,33 @@ class CeilometerContextsTest(CharmTestCase):
'port': api_port 'port': api_port
} }
self.assertEqual(contexts.HAProxyContext()(), expected) self.assertEqual(contexts.HAProxyContext()(), expected)
def test_remote_sink_context_no_config(self):
self.relation_ids.return_value = []
self.os_release.return_value = 'mitaka'
self.assertEqual(contexts.RemoteSinksContext()(), {})
def test_remote_sink_context_event_service_relation(self):
self.relation_ids.return_value = ['event-service:0']
self.related_units.return_value = ['panko/0']
self.os_release.return_value = 'mitaka'
data = {
'publisher': 'panko://'
}
self.test_relation.set(data)
self.assertEqual(contexts.RemoteSinksContext()(),
{'internal_sinks': {'panko': 'panko://'}})
def test_remote_sink_context_with_single_config(self):
self.relation_ids.return_value = []
self.os_release.return_value = 'mitaka'
self.test_config.set('remote-sink', 'http://foo')
self.assertEqual(contexts.RemoteSinksContext()(),
{'remote_sinks': ['http://foo']})
def test_remote_sink_context_with_multiple_config(self):
self.relation_ids.return_value = []
self.os_release.return_value = 'mitaka'
self.test_config.set('remote-sink', 'http://foo http://bar')
self.assertEqual(contexts.RemoteSinksContext()(),
{'remote_sinks': ['http://foo', 'http://bar']})

View File

@@ -39,6 +39,7 @@ TO_PATCH = [
'os_release', 'os_release',
'is_leader', 'is_leader',
'reset_os_release', 'reset_os_release',
'relation_ids',
] ]
@@ -275,6 +276,7 @@ class CeilometerUtilsTest(CharmTestCase):
def test_resolve_required_interfaces(self): def test_resolve_required_interfaces(self):
self.os_release.side_effect = None self.os_release.side_effect = None
self.os_release.return_value = 'icehouse' self.os_release.return_value = 'icehouse'
self.relation_ids.return_value = None
self.assertEqual( self.assertEqual(
utils.resolve_required_interfaces(), utils.resolve_required_interfaces(),
{ {
@@ -287,6 +289,7 @@ class CeilometerUtilsTest(CharmTestCase):
def test_resolve_required_interfaces_mitaka(self): def test_resolve_required_interfaces_mitaka(self):
self.os_release.side_effect = None self.os_release.side_effect = None
self.os_release.return_value = 'mitaka' self.os_release.return_value = 'mitaka'
self.relation_ids.return_value = None
self.assertEqual( self.assertEqual(
utils.resolve_required_interfaces(), utils.resolve_required_interfaces(),
{ {
@@ -299,6 +302,7 @@ class CeilometerUtilsTest(CharmTestCase):
def test_resolve_required_interfaces_queens(self): def test_resolve_required_interfaces_queens(self):
self.os_release.side_effect = None self.os_release.side_effect = None
self.os_release.return_value = 'queens' self.os_release.return_value = 'queens'
self.relation_ids.return_value = None
self.assertEqual( self.assertEqual(
utils.resolve_required_interfaces(), utils.resolve_required_interfaces(),
{ {
@@ -308,6 +312,20 @@ class CeilometerUtilsTest(CharmTestCase):
} }
) )
def test_resolve_optional_interfaces(self):
self.os_release.side_effect = None
self.os_release.return_value = 'icehouse'
self.relation_ids.return_value = [0]
self.assertEqual(
utils.resolve_required_interfaces(),
{
'database': ['mongodb'],
'messaging': ['amqp'],
'identity': ['identity-service'],
'event-service': ['event-service'],
}
)
@patch.object(utils, 'subprocess') @patch.object(utils, 'subprocess')
def test_ceilometer_upgrade(self, mock_subprocess): def test_ceilometer_upgrade(self, mock_subprocess):
self.is_leader.return_value = True self.is_leader.return_value = True