Source code for ceilometer.tests.unit.dispatcher.test_gnocchi

#
# Copyright 2014 eNovance
#
# Authors: Mehdi Abaakouk <mehdi.abaakouk@enovance.com>
#
# 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.

import os
import uuid

from gnocchiclient import exceptions as gnocchi_exc
import mock
from oslo_config import fixture as config_fixture
from oslo_utils import fileutils
from oslotest import mockpatch
import requests
import six
import testscenarios

from ceilometer.dispatcher import gnocchi
from ceilometer import service as ceilometer_service
from ceilometer.tests import base

load_tests = testscenarios.load_tests_apply_scenarios


@mock.patch('gnocchiclient.v1.client.Client', mock.Mock())
[docs]class DispatcherTest(base.BaseTestCase):
[docs] def setUp(self): super(DispatcherTest, self).setUp() self.conf = self.useFixture(config_fixture.Config()) ceilometer_service.prepare_service(argv=[], config_files=[]) self.conf.config( resources_definition_file=self.path_get( 'etc/ceilometer/gnocchi_resources.yaml'), group="dispatcher_gnocchi" ) self.resource_id = str(uuid.uuid4()) self.samples = [{ 'counter_name': 'disk.root.size', 'counter_type': 'gauge', 'counter_volume': '2', 'user_id': 'test_user', 'project_id': 'test_project', 'source': 'openstack', 'timestamp': '2012-05-08 20:23:48.028195', 'resource_id': self.resource_id, 'resource_metadata': { 'host': 'foo', 'image_ref': 'imageref!', 'instance_flavor_id': 1234, 'display_name': 'myinstance', }}, { 'counter_name': 'disk.root.size', 'counter_type': 'gauge', 'counter_volume': '2', 'user_id': 'test_user', 'project_id': 'test_project', 'source': 'openstack', 'timestamp': '2014-05-08 20:23:48.028195', 'resource_id': self.resource_id, 'resource_metadata': { 'host': 'foo', 'image_ref': 'imageref!', 'instance_flavor_id': 1234, 'display_name': 'myinstance', } }] ks_client = mock.Mock(auth_token='fake_token') ks_client.projects.find.return_value = mock.Mock( name='gnocchi', id='a2d42c23-d518-46b6-96ab-3fba2e146859') self.useFixture(mockpatch.Patch( 'ceilometer.keystone_client.get_client', return_value=ks_client)) self.conf.conf.dispatcher_gnocchi.filter_service_activity = True
[docs] def test_config_load(self): self.conf.config(filter_service_activity=False, group='dispatcher_gnocchi') d = gnocchi.GnocchiDispatcher(self.conf.conf) names = [rd.cfg['resource_type'] for rd in d.resources_definition] self.assertIn('instance', names) self.assertIn('volume', names)
@mock.patch('ceilometer.dispatcher.gnocchi.LOG')
[docs] def test_broken_config_load(self, mylog): contents = [("---\n" "resources:\n" " - resource_type: foobar\n"), ("---\n" "resources:\n" " - resource_type: 0\n"), ("---\n" "resources:\n" " - sample_types: ['foo', 'bar']\n"), ("---\n" "resources:\n" " - sample_types: foobar\n" " - resource_type: foobar\n"), ] for content in contents: if six.PY3: content = content.encode('utf-8') temp = fileutils.write_to_tempfile(content=content, prefix='gnocchi_resources', suffix='.yaml') self.addCleanup(os.remove, temp) self.conf.config(filter_service_activity=False, resources_definition_file=temp, group='dispatcher_gnocchi') d = gnocchi.GnocchiDispatcher(self.conf.conf) self.assertTrue(mylog.error.called) self.assertEqual(0, len(d.resources_definition))
@mock.patch('ceilometer.dispatcher.gnocchi.GnocchiDispatcher' '._process_resource') def _do_test_activity_filter(self, expected_samples, fake_process_resource): def assert_samples(resource_id, metric_grouped_samples): samples = [] for metric_name, s in metric_grouped_samples: samples.extend(list(s)) self.assertEqual(expected_samples, samples) fake_process_resource.side_effect = assert_samples d = gnocchi.GnocchiDispatcher(self.conf.conf) d.record_metering_data(self.samples) fake_process_resource.assert_called_with(self.resource_id, mock.ANY)
[docs] def test_activity_filter_match_project_id(self): self.samples[0]['project_id'] = ( 'a2d42c23-d518-46b6-96ab-3fba2e146859') self._do_test_activity_filter([self.samples[1]])
[docs] def test_activity_filter_match_swift_event(self): self.samples[0]['counter_name'] = 'storage.api.request' self.samples[0]['resource_id'] = 'a2d42c23-d518-46b6-96ab-3fba2e146859' self._do_test_activity_filter([self.samples[1]])
[docs] def test_activity_filter_nomatch(self): self._do_test_activity_filter(self.samples)
[docs]class MockResponse(mock.NonCallableMock): def __init__(self, code): text = {500: 'Internal Server Error', 404: 'Not Found', 204: 'Created', 409: 'Conflict', }.get(code) super(MockResponse, self).__init__(spec=requests.Response, status_code=code, text=text)
[docs]class DispatcherWorkflowTest(base.BaseTestCase, testscenarios.TestWithScenarios): sample_scenarios = [ ('disk.root.size', dict( sample={ 'counter_name': 'disk.root.size', 'counter_type': 'gauge', 'counter_volume': '2', 'user_id': 'test_user', 'project_id': 'test_project', 'source': 'openstack', 'timestamp': '2012-05-08 20:23:48.028195', 'resource_metadata': { 'host': 'foo', 'image_ref': 'imageref!', 'instance_flavor_id': 1234, 'display_name': 'myinstance', } }, measures_attributes=[{ 'timestamp': '2012-05-08 20:23:48.028195', 'value': '2' }], postable_attributes={ 'user_id': 'test_user', 'project_id': 'test_project', }, patchable_attributes={ 'host': 'foo', 'image_ref': 'imageref!', 'flavor_id': 1234, 'display_name': 'myinstance', }, metric_names=[ 'instance', 'disk.root.size', 'disk.ephemeral.size', 'memory', 'vcpus', 'memory.usage', 'memory.resident', 'cpu', 'cpu.delta', 'cpu_util', 'vcpus', 'disk.read.requests', 'disk.read.requests.rate', 'disk.write.requests', 'disk.write.requests.rate', 'disk.read.bytes', 'disk.read.bytes.rate', 'disk.write.bytes', 'disk.write.bytes.rate', 'disk.latency', 'disk.iops', 'disk.capacity', 'disk.allocation', 'disk.usage'], resource_type='instance')), ('hardware.ipmi.node.power', dict( sample={ 'counter_name': 'hardware.ipmi.node.power', 'counter_type': 'gauge', 'counter_volume': '2', 'user_id': 'test_user', 'project_id': 'test_project', 'source': 'openstack', 'timestamp': '2012-05-08 20:23:48.028195', 'resource_metadata': { 'useless': 'not_used', } }, measures_attributes=[{ 'timestamp': '2012-05-08 20:23:48.028195', 'value': '2' }], postable_attributes={ 'user_id': 'test_user', 'project_id': 'test_project', }, patchable_attributes={ }, metric_names=[ 'hardware.ipmi.node.power', 'hardware.ipmi.node.temperature', 'hardware.ipmi.node.inlet_temperature', 'hardware.ipmi.node.outlet_temperature', 'hardware.ipmi.node.fan', 'hardware.ipmi.node.current', 'hardware.ipmi.node.voltage', 'hardware.ipmi.node.airflow', 'hardware.ipmi.node.cups', 'hardware.ipmi.node.cpu_util', 'hardware.ipmi.node.mem_util', 'hardware.ipmi.node.io_util' ], resource_type='ipmi')), ] worflow_scenarios = [ ('normal_workflow', dict(measure=204, post_resource=None, metric=None, measure_retry=None, patch_resource=204)), ('new_resource', dict(measure=404, post_resource=204, metric=None, measure_retry=204, patch_resource=204)), ('new_resource_fail', dict(measure=404, post_resource=500, metric=None, measure_retry=None, patch_resource=None)), ('resource_update_fail', dict(measure=204, post_resource=None, metric=None, measure_retry=None, patch_resource=500)), ('new_metric', dict(measure=404, post_resource=None, metric=204, measure_retry=204, patch_resource=204)), ('new_metric_fail', dict(measure=404, post_resource=None, metric=500, measure_retry=None, patch_resource=None)), ('retry_fail', dict(measure=404, post_resource=409, metric=None, measure_retry=500, patch_resource=None)), ('measure_fail', dict(measure=500, post_resource=None, metric=None, measure_retry=None, patch_resource=None)), ] @classmethod
[docs] def generate_scenarios(cls): cls.scenarios = testscenarios.multiply_scenarios(cls.sample_scenarios, cls.worflow_scenarios)
[docs] def setUp(self): super(DispatcherWorkflowTest, self).setUp() self.conf = self.useFixture(config_fixture.Config()) # Set this explicitly to avoid conflicts with any existing # configuration. self.conf.config(url='http://localhost:8041', group='dispatcher_gnocchi') ks_client = mock.Mock() ks_client.projects.find.return_value = mock.Mock( name='gnocchi', id='a2d42c23-d518-46b6-96ab-3fba2e146859') self.useFixture(mockpatch.Patch( 'ceilometer.keystone_client.get_client', return_value=ks_client)) self.ks_client = ks_client ceilometer_service.prepare_service(argv=[], config_files=[]) self.conf.config( resources_definition_file=self.path_get( 'etc/ceilometer/gnocchi_resources.yaml'), group="dispatcher_gnocchi" ) self.sample['resource_id'] = str(uuid.uuid4()) + "/foobar"
@mock.patch('ceilometer.dispatcher.gnocchi.LOG') @mock.patch('gnocchiclient.v1.client.Client')
[docs] def test_workflow(self, fakeclient_cls, logger): self.dispatcher = gnocchi.GnocchiDispatcher(self.conf.conf) fakeclient = fakeclient_cls.return_value # FIXME(sileht): we don't use urlparse.quote here # to ensure / is converted in %2F # temporary disabled until we find a solution # on gnocchi side. Current gnocchiclient doesn't # encode the resource_id resource_id = self.sample['resource_id'] # .replace("/", "%2F"), metric_name = self.sample['counter_name'] expected_calls = [ mock.call.capabilities.list(), mock.call.metric.add_measures(metric_name, self.measures_attributes, resource_id)] add_measures_side_effect = [] if self.measure == 404 and self.post_resource: add_measures_side_effect += [ gnocchi_exc.ResourceNotFound(404)] elif self.measure == 404 and self.metric: add_measures_side_effect += [ gnocchi_exc.MetricNotFound(404)] elif self.measure == 500: add_measures_side_effect += [Exception('boom!')] if self.post_resource: attributes = self.postable_attributes.copy() attributes.update(self.patchable_attributes) attributes['id'] = self.sample['resource_id'] attributes['metrics'] = dict((metric_name, {}) for metric_name in self.metric_names) expected_calls.append(mock.call.resource.create( self.resource_type, attributes)) if self.post_resource == 409: fakeclient.resource.create.side_effect = [ gnocchi_exc.ResourceAlreadyExists(409)] elif self.post_resource == 500: fakeclient.resource.create.side_effect = [Exception('boom!')] if self.metric: expected_calls.append(mock.call.metric.create({ 'name': self.sample['counter_name'], 'resource_id': resource_id})) if self.metric == 409: fakeclient.metric.create.side_effect = [ gnocchi_exc.NamedMetricAreadyExists(409)] elif self.metric == 500: fakeclient.metric.create.side_effect = [Exception('boom!')] if self.measure_retry: expected_calls.append(mock.call.metric.add_measures( metric_name, self.measures_attributes, resource_id)) if self.measure_retry == 204: add_measures_side_effect += [None] elif self.measure_retry == 500: add_measures_side_effect += [ Exception('boom!')] else: add_measures_side_effect += [None] if self.patch_resource and self.patchable_attributes: expected_calls.append(mock.call.resource.update( self.resource_type, resource_id, self.patchable_attributes)) if self.patch_resource == 500: fakeclient.resource.update.side_effect = [Exception('boom!')] fakeclient.metric.add_measures.side_effect = add_measures_side_effect self.dispatcher.record_metering_data([self.sample]) # Check that the last log message is the expected one if (self.measure == 500 or self.measure_retry == 500 or self.metric == 500 or self.post_resource == 500 or (self.patch_resource == 500 and self.patchable_attributes)): logger.error.assert_called_with('boom!', exc_info=True) elif self.patch_resource == 204 and self.patchable_attributes: logger.debug.assert_called_with( 'Resource %s updated', self.sample['resource_id']) self.assertEqual(0, logger.error.call_count) elif self.measure == 200: logger.debug.assert_called_with( "Measure posted on metric %s of resource %s", self.sample['counter_name'], self.sample['resource_id']) self.assertEqual(0, logger.error.call_count) self.assertEqual(expected_calls, fakeclient.mock_calls)
DispatcherWorkflowTest.generate_scenarios()

Project Source