diff --git a/zaza/openstack/charm_tests/ceilometer_agent/__init__.py b/zaza/openstack/charm_tests/ceilometer_agent/__init__.py new file mode 100644 index 0000000..6272e26 --- /dev/null +++ b/zaza/openstack/charm_tests/ceilometer_agent/__init__.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 + +# Copyright 2021 Canonical Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Collection of code for setting up and testing ceilometer-agent.""" diff --git a/zaza/openstack/charm_tests/ceilometer_agent/tests.py b/zaza/openstack/charm_tests/ceilometer_agent/tests.py new file mode 100644 index 0000000..655b1c7 --- /dev/null +++ b/zaza/openstack/charm_tests/ceilometer_agent/tests.py @@ -0,0 +1,225 @@ +#!/usr/bin/env python3 + +# Copyright 2021 Canonical Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Encapsulate ceilometer-agent testing.""" + +import logging +import time +from gnocchiclient.v1 import client as gnocchi_client + +import zaza.openstack.charm_tests.glance.setup as glance_setup +import zaza.openstack.charm_tests.test_utils as test_utils +import zaza.openstack.utilities.openstack as openstack_utils + + +class CeilometerAgentTest(test_utils.OpenStackBaseTest): + """Encapsulate ceilometer-agent tests.""" + + def tearDown(self): + """Cleanup of VM guests.""" + self.resource_cleanup() + + def test_400_gnocchi_metrics(self): + """Verify that ceilometer-agent publishes metrics to gnocchi.""" + current_os_release = openstack_utils.get_os_release() + openstack_pike_or_older = ( + current_os_release <= + openstack_utils.get_os_release('xenial_pike')) + if openstack_pike_or_older: + # Both the charm and Ceilometer itself had different behaviors in + # terms of which metrics were published and how fast, which would + # lead to a combinatorial explosion if we had to maintain test + # expectations for these old releases. + logging.info( + 'OpenStack Pike or older, skipping') + return + + # ceilometer-agent-compute reports metrics for each existing VM, so at + # least one VM is needed: + self.RESOURCE_PREFIX = 'zaza-ceilometer-agent' + self.launch_guest( + 'ubuntu', instance_key=glance_setup.LTS_IMAGE_NAME) + + logging.info('Instantiating gnocchi client...') + overcloud_auth = openstack_utils.get_overcloud_auth() + keystone = openstack_utils.get_keystone_client(overcloud_auth) + gnocchi_ep = keystone.service_catalog.url_for( + service_type='metric', + interface='publicURL' + ) + gnocchi = gnocchi_client.Client( + session=openstack_utils.get_overcloud_keystone_session(), + adapter_options={ + 'endpoint_override': gnocchi_ep, + } + ) + + expected_metric_names = self.__get_expected_metric_names( + current_os_release) + + min_timeout_seconds = 500 + polling_interval_seconds = ( + openstack_utils.get_application_config_option( + self.application_name, 'polling-interval')) + timeout_seconds = max(10 * polling_interval_seconds, + min_timeout_seconds) + logging.info('Giving ceilometer-agent {}s to publish all metrics to ' + 'gnocchi...'.format(timeout_seconds)) + + max_time = time.time() + timeout_seconds + while time.time() < max_time: + found_metric_names = {metric['name'] + for metric in gnocchi.metric.list()} + missing_metric_names = expected_metric_names - found_metric_names + if len(missing_metric_names) == 0: + logging.info('All expected metrics found.') + break + time.sleep(polling_interval_seconds) + + unexpected_found_metric_names = ( + found_metric_names - expected_metric_names) + if len(unexpected_found_metric_names) > 0: + self.fail( + 'Unexpected metrics ' + 'published: ' + ', '.join(unexpected_found_metric_names)) + + if len(missing_metric_names) > 0: + self.fail('These metrics should have been published but ' + "weren't: " + ', '.join(missing_metric_names)) + + def __get_expected_metric_names(self, current_os_release): + expected_metric_names = { + 'compute.instance.booting.time', + 'disk.ephemeral.size', + 'disk.root.size', + 'image.download', + 'image.serve', + 'image.size', + 'memory', + 'vcpus', + } + + all_polsters_are_enabled = ( + openstack_utils.get_application_config_option( + self.application_name, 'enable-all-pollsters')) + + if all_polsters_are_enabled: + expected_metric_names |= { + 'disk.device.allocation', + 'disk.device.capacity', + 'disk.device.read.latency', + 'disk.device.usage', + 'disk.device.write.latency', + 'memory.resident', + 'memory.swap.in', + 'memory.swap.out', + 'network.incoming.packets.drop', + 'network.incoming.packets.error', + 'network.outgoing.packets.drop', + 'network.outgoing.packets.error', + } + + openstack_queens_or_older = ( + current_os_release <= + openstack_utils.get_os_release('bionic_queens')) + openstack_rocky_or_older = ( + current_os_release <= + openstack_utils.get_os_release('bionic_rocky')) + openstack_victoria_or_older = ( + current_os_release <= + openstack_utils.get_os_release('groovy_victoria')) + + if openstack_victoria_or_older: + expected_metric_names |= { + 'cpu', + 'disk.device.read.bytes', + 'disk.device.read.requests', + 'disk.device.write.bytes', + 'disk.device.write.requests', + 'memory.usage', + 'network.incoming.bytes', + 'network.incoming.packets', + 'network.outgoing.bytes', + 'network.outgoing.packets', + } + + if openstack_rocky_or_older: + expected_metric_names |= { + 'cpu.delta', + 'cpu_util', + 'disk.device.read.bytes.rate', + 'disk.device.read.requests.rate', + 'disk.device.write.bytes.rate', + 'disk.device.write.requests.rate', + 'network.incoming.bytes.rate', + 'network.incoming.packets.rate', + 'network.outgoing.bytes.rate', + 'network.outgoing.packets.rate', + } + if all_polsters_are_enabled: + expected_metric_names |= { + 'disk.allocation', + 'disk.capacity', + 'disk.read.bytes', + 'disk.read.bytes.rate', + 'disk.read.requests', + 'disk.read.requests.rate', + 'disk.usage', + 'disk.write.bytes', + 'disk.write.bytes.rate', + 'disk.write.requests', + 'disk.write.requests.rate', + } + + if openstack_queens_or_older: + expected_metric_names |= { + 'cpu_l3_cache', + 'disk.allocation', + 'disk.capacity', + 'disk.device.allocation', + 'disk.device.capacity', + 'disk.device.iops', + 'disk.device.latency', + 'disk.device.read.latency', + 'disk.device.usage', + 'disk.device.write.latency', + 'disk.iops', + 'disk.latency', + 'disk.read.bytes', + 'disk.read.bytes.rate', + 'disk.read.requests', + 'disk.read.requests.rate', + 'disk.usage', + 'disk.write.bytes', + 'disk.write.bytes.rate', + 'disk.write.requests', + 'disk.write.requests.rate', + 'memory.bandwidth.local', + 'memory.bandwidth.total', + 'memory.resident', + 'memory.swap.in', + 'memory.swap.out', + 'network.incoming.packets.drop', + 'network.incoming.packets.error', + 'network.outgoing.packets.drop', + 'network.outgoing.packets.error', + 'perf.cache.misses', + 'perf.cache.references', + 'perf.cpu.cycles', + 'perf.instructions', + } + + return expected_metric_names diff --git a/zaza/openstack/charm_tests/neutron/setup.py b/zaza/openstack/charm_tests/neutron/setup.py index a1d1dd4..11a01bc 100644 --- a/zaza/openstack/charm_tests/neutron/setup.py +++ b/zaza/openstack/charm_tests/neutron/setup.py @@ -82,7 +82,7 @@ def basic_overcloud_network(limit_gws=None): # Get keystone session keystone_session = openstack_utils.get_overcloud_keystone_session() - # Get optional use_juju_wait for netw ork option + # Get optional use_juju_wait for network option options = (lifecycle_utils .get_charm_config(fatal=False) .get('configure_options', {})) diff --git a/zaza/openstack/charm_tests/test_utils.py b/zaza/openstack/charm_tests/test_utils.py index bc67933..be47dd9 100644 --- a/zaza/openstack/charm_tests/test_utils.py +++ b/zaza/openstack/charm_tests/test_utils.py @@ -635,7 +635,7 @@ class OpenStackBaseTest(BaseCharmTest): def launch_guest(self, guest_name, userdata=None, use_boot_volume=False, instance_key=None): - """Launch two guests to use in tests. + """Launch one guest to use in tests. Note that it is up to the caller to have set the RESOURCE_PREFIX class variable prior to calling this method.