diff --git a/README.md b/README.md index a0d5765..a024da3 100644 --- a/README.md +++ b/README.md @@ -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 ------------- diff --git a/hooks/amqp-listener-relation-changed b/hooks/amqp-listener-relation-changed new file mode 120000 index 0000000..c948469 --- /dev/null +++ b/hooks/amqp-listener-relation-changed @@ -0,0 +1 @@ +ceilometer_hooks.py \ No newline at end of file diff --git a/hooks/amqp-listener-relation-departed b/hooks/amqp-listener-relation-departed new file mode 120000 index 0000000..c948469 --- /dev/null +++ b/hooks/amqp-listener-relation-departed @@ -0,0 +1 @@ +ceilometer_hooks.py \ No newline at end of file diff --git a/hooks/amqp-listener-relation-joined b/hooks/amqp-listener-relation-joined new file mode 120000 index 0000000..c948469 --- /dev/null +++ b/hooks/amqp-listener-relation-joined @@ -0,0 +1 @@ +ceilometer_hooks.py \ No newline at end of file diff --git a/hooks/ceilometer_hooks.py b/hooks/ceilometer_hooks.py index 7ef47b3..fee6822 100755 --- a/hooks/ceilometer_hooks.py +++ b/hooks/ceilometer_hooks.py @@ -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", diff --git a/lib/ceilometer_contexts.py b/lib/ceilometer_contexts.py index 069caa2..9023c92 100644 --- a/lib/ceilometer_contexts.py +++ b/lib/ceilometer_contexts.py @@ -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 diff --git a/lib/ceilometer_utils.py b/lib/ceilometer_utils.py index ed25e54..e146293 100644 --- a/lib/ceilometer_utils.py +++ b/lib/ceilometer_utils.py @@ -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 }), ]) diff --git a/metadata.yaml b/metadata.yaml index 150fd3e..2d3a371 100644 --- a/metadata.yaml +++ b/metadata.yaml @@ -32,6 +32,8 @@ requires: interface: mongodb amqp: interface: rabbitmq + amqp-listener: + interface: rabbitmq identity-service: interface: keystone identity-notifications: diff --git a/templates/ocata/ceilometer.conf b/templates/ocata/ceilometer.conf index 77f7334..084a931 100644 --- a/templates/ocata/ceilometer.conf +++ b/templates/ocata/ceilometer.conf @@ -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 }} diff --git a/tests/basic_deployment.py b/tests/basic_deployment.py index 8c6c560..ed7ea9e 100644 --- a/tests/basic_deployment.py +++ b/tests/basic_deployment.py @@ -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 = { diff --git a/unit_tests/test_ceilometer_contexts.py b/unit_tests/test_ceilometer_contexts.py index 61a2234..d9c41ae 100644 --- a/unit_tests/test_ceilometer_contexts.py +++ b/unit_tests/test_ceilometer_contexts.py @@ -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()(), {})