Support listening to multiple rabbit queues.

This change adds the ability to listen for events across multiple
rabbit queues. Nova cells v2 has a message broker per cell and
the charms already support having a separate message broker for
neutron, in both these topologies ceilometer needs the ability to
listen to multiple brokers.

To achieve this a new relation 'amqp-listener' has been introduced.
The existing 'amqp' relation should be used for the broker that
ceilometer listens to and publishes to. 'amqp-listener' should be
used for additional brokers that ceilometer just listens on.

Update functional tests to satisfy relation with
nova-cloud-controller.

Change-Id: Ifdade3f7814620f4cd4a1d35a584cbc099bb6d88
This commit is contained in:
Liam Young 2018-10-16 17:49:16 +00:00
parent 173374187e
commit 67b149f89c
11 changed files with 107 additions and 5 deletions

View File

@ -36,7 +36,7 @@ must be run post deployment in order to update its data store in gnocchi.
then Keystone and Rabbit relationships need to be established:
juju add-relation ceilometer rabbitmq
juju add-relation ceilometer:amqp rabbitmq
juju add-relation ceilometer keystone:identity-service
juju add-relation ceilometer keystone:identity-notifications
@ -57,6 +57,14 @@ installed in each nova node, and be related with Ceilometer service:
Ceilometer provides an API service that can be used to retrieve
Openstack metrics.
If ceilometer needs to listen to multiple message queues then use the amqp interface
to relate ceilometer to the message broker that it should publish to and use the
amqp-listener interface for all message brokers ceilometer should monitor.
juju add-relation ceilometer:amqp rabbitmq-central
juju add-relation ceilometer:amqp-listener rabbitmq-neutron
juju add-relation ceilometer:amqp-listener rabbitmq-nova-cell2
HA/Clustering
-------------

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

@ -130,6 +130,7 @@ def install():
disable_package_apache_site()
@hooks.hook("amqp-listener-relation-joined")
@hooks.hook("amqp-relation-joined")
def amqp_joined():
relation_set(username=config('rabbit-user'),
@ -155,6 +156,8 @@ def metric_service_joined():
@hooks.hook("amqp-relation-changed",
"amqp-relation-departed",
"amqp-listener-relation-changed",
"amqp-listener-relation-departed",
"shared-db-relation-changed",
"shared-db-relation-departed",
"identity-service-relation-changed",

View File

@ -28,6 +28,7 @@ from charmhelpers.contrib.openstack.context import (
OSContextGenerator,
context_complete,
ApacheSSLContext as SSLContext,
AMQPContext,
)
from charmhelpers.contrib.hahelpers.cluster import (
@ -186,3 +187,22 @@ class MetricServiceContext(OSContextGenerator):
return {'gnocchi_url': gnocchi_url,
'archive_policy': config('gnocchi-archive-policy')}
return {}
class AMQPListenersContext(OSContextGenerator):
interfaces = ['amqp-listener']
def __init__(self, ssl_dir=None):
self.ssl_dir = ssl_dir
def __call__(self):
ctxt = {}
messaging_urls = []
relids = relation_ids('amqp-listener') + relation_ids('amqp')
for relid in relids:
amqp_ctxt = AMQPContext(ssl_dir=self.ssl_dir, relation_id=relid)()
if amqp_ctxt.get('transport_url'):
messaging_urls.append(amqp_ctxt['transport_url'])
if messaging_urls:
ctxt['messaging_urls'] = messaging_urls
return ctxt

View File

@ -33,6 +33,7 @@ from ceilometer_contexts import (
MetricServiceContext,
CEILOMETER_PORT,
RemoteSinksContext,
AMQPListenersContext,
)
from charmhelpers.contrib.openstack.utils import (
get_os_codename_package,
@ -155,7 +156,8 @@ QUEENS_CONFIG_FILES = OrderedDict([
context.SyslogContext(),
context.MemcacheContext(),
MetricServiceContext(),
context.WorkerConfigContext()],
context.WorkerConfigContext(),
AMQPListenersContext(ssl_dir=CEILOMETER_CONF_DIR)],
'services': QUEENS_SERVICES
}),
])

View File

@ -32,6 +32,8 @@ requires:
interface: mongodb
amqp:
interface: rabbitmq
amqp-listener:
interface: rabbitmq
identity-service:
interface: keystone
identity-notifications:

View File

@ -23,6 +23,12 @@ transport_url = {{ transport_url }}
[notification]
workers = {{ workers }}
{% if messaging_urls -%}
{% for item in messaging_urls -%}
messaging_urls = {{ item }}
{% endfor %}
{% endif %}
[collector]
workers = {{ workers }}

View File

@ -67,7 +67,8 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment):
{'name': 'keystone'},
{'name': 'glance'}, # to satisfy workload status
{'name': 'ceilometer-agent'},
{'name': 'nova-compute'}
{'name': 'nova-compute'},
{'name': 'nova-cloud-controller'},
]
if self._get_openstack_release() >= self.xenial_pike:
other_services.extend([
@ -99,7 +100,14 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment):
'glance:identity-service': 'keystone:identity-service',
'glance:shared-db': 'percona-cluster:shared-db',
'glance:amqp': 'rabbitmq-server:amqp',
'nova-compute:image-service': 'glance:image-service'
'nova-compute:image-service': 'glance:image-service',
'nova-cloud-controller:shared-db': 'percona-cluster:shared-db',
'nova-cloud-controller:amqp': 'rabbitmq-server:amqp',
'nova-cloud-controller:identity-service': 'keystone:'
'identity-service',
'nova-cloud-controller:cloud-compute': 'nova-compute:'
'cloud-compute',
'nova-cloud-controller:image-service': 'glance:image-service',
}
if self._get_openstack_release() >= self.xenial_pike:
additional_relations = {

View File

@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from mock import patch
from mock import patch, MagicMock
import ceilometer_contexts as contexts
import ceilometer_utils as utils
@ -223,3 +223,53 @@ class CeilometerContextsTest(CharmTestCase):
self.test_config.set('remote-sink', 'http://foo http://bar')
self.assertEqual(contexts.RemoteSinksContext()(),
{'remote_sinks': ['http://foo', 'http://bar']})
@patch.object(contexts, 'AMQPContext')
def test_AMQPListenersContext(self, mock_AMQPContext):
def _context(ssl_dir, relation_id):
fake_context1 = MagicMock(
return_value={'transport_url': 'rabbit://rab1:1010/os'})
fake_context2 = MagicMock(
return_value={'other_setting': 'sss'})
fake_context3 = MagicMock(
return_value={'transport_url': 'rabbit://rab2:1010/os'})
rdata = {
'amqp-listener:23': fake_context1,
'amqp-listener:8': fake_context2,
'amqp:2': fake_context3}
return rdata[relation_id]
mock_AMQPContext.side_effect = _context
rids = {
'amqp-listener': ['amqp-listener:23', 'amqp-listener:8'],
'amqp': ['amqp:2']}
self.relation_ids.side_effect = lambda x: rids[x]
self.assertEqual(
contexts.AMQPListenersContext()(),
{'messaging_urls': [
'rabbit://rab1:1010/os',
'rabbit://rab2:1010/os']})
@patch.object(contexts, 'AMQPContext')
def test_AMQPListenersContext_no_transport_urls(self, mock_AMQPContext):
def _context(ssl_dir, relation_id):
fake_context1 = MagicMock(return_value={})
fake_context2 = MagicMock(return_value={})
fake_context3 = MagicMock(return_value={})
rdata = {
'amqp-listener:23': fake_context1,
'amqp-listener:8': fake_context2,
'amqp:2': fake_context3}
return rdata[relation_id]
mock_AMQPContext.side_effect = _context
rids = {
'amqp-listener': ['amqp-listener:23', 'amqp-listener:8'],
'amqp': ['amqp:2']}
self.relation_ids.side_effect = lambda x: rids[x]
self.assertEqual(contexts.AMQPListenersContext()(), {})