From 2223d14005f5ea31fb7def1941fbaef01c8c7a5f Mon Sep 17 00:00:00 2001 From: Helena McGough Date: Fri, 30 Jun 2017 12:41:49 +0000 Subject: [PATCH] Included the aodh sender class in the common one - Combined commonalities from the aodh and common sender classes - Updated all of the relevant tests for each class - Included a reno Change-Id: I78fcdb075661e125b0e9f16bdee626cd811ad78e Closes-bug: #1668210 --- collectd_ceilometer/aodh/notifier.py | 8 +- collectd_ceilometer/aodh/sender.py | 282 +++++------------- collectd_ceilometer/common/sender.py | 18 +- collectd_ceilometer/tests/aodh/test_plugin.py | 235 +++++---------- .../notes/bug-1668210-5b8220bcc27a81ef.yaml | 6 + 5 files changed, 168 insertions(+), 381 deletions(-) create mode 100644 releasenotes/notes/bug-1668210-5b8220bcc27a81ef.yaml diff --git a/collectd_ceilometer/aodh/notifier.py b/collectd_ceilometer/aodh/notifier.py index 4713b42..7be4dd9 100644 --- a/collectd_ceilometer/aodh/notifier.py +++ b/collectd_ceilometer/aodh/notifier.py @@ -16,7 +16,7 @@ from __future__ import unicode_literals -from collectd_ceilometer.aodh.sender import Sender +from collectd_ceilometer.aodh import sender as aodh_sender import datetime import logging @@ -30,7 +30,7 @@ class Notifier(object): def __init__(self, meters, config): """Initialize Notifier.""" self._meters = meters - self._sender = Sender() + self._sender = aodh_sender.Sender() self._config = config def notify(self, vl, data): @@ -58,4 +58,6 @@ class Notifier(object): def _send_data(self, metername, severity, resource_id, alarm_severity): """Send data to Aodh.""" LOGGER.debug('Sending alarm for %s', metername) - self._sender.send(metername, severity, resource_id, alarm_severity) + self._sender.send(metername, None, severity=severity, + resource_id=resource_id, + alarm_severity=alarm_severity) diff --git a/collectd_ceilometer/aodh/sender.py b/collectd_ceilometer/aodh/sender.py index 04ce0b6..4ae4329 100644 --- a/collectd_ceilometer/aodh/sender.py +++ b/collectd_ceilometer/aodh/sender.py @@ -17,17 +17,14 @@ from __future__ import division from __future__ import unicode_literals -import collectd_ceilometer -from collectd_ceilometer.common.keystone_light import ClientV3 -from collectd_ceilometer.common.keystone_light import KeystoneException -from collectd_ceilometer.common.settings import Config - import json import logging import requests -from requests.exceptions import RequestException -import six -import threading + + +import collectd_ceilometer +from collectd_ceilometer.common import sender as common_sender +from collectd_ceilometer.common.settings import Config LOGGER = logging.getLogger(__name__) ROOT_LOGGER = logging.getLogger(collectd_ceilometer.__name__) @@ -38,151 +35,77 @@ HTTP_UNAUTHORIZED = 401 HTTP_NOT_FOUND = 404 -class Sender(object): +class Sender(common_sender.Sender): """Sends the JSON serialized data to Aodh.""" def __init__(self): """Create the Sender instance. - The cofiguration must be initialized before the object is created. + The configuration must be initialized before the object is created. """ - self._url_base = None - self._keystone = None - self._auth_token = None - self._auth_lock = threading.Lock() - self._failed_auth = False + super(Sender, self).__init__() + self._alarm_ids = {} - def _authenticate(self): - """Authenticate and renew the authentication token.""" - # if auth_token is available, just return it - if self._auth_token is not None: - return self._auth_token + def _on_authenticated(self): + # get the uri of service endpoint for an alarm state update + endpoint = self._get_endpoint("aodh") - # aquire the authentication lock - with self._auth_lock: - # re-check the auth_token as another thread could set it - if self._auth_token is not None: - return self._auth_token + self._url_base = "{}/v2/alarms/%s/state".format(endpoint) - LOGGER.debug('Authenticating request') - # pylint: disable=broad-except - try: - # create a keystone client if it doesn't exist - if self._keystone is None: - cfg = Config.instance() - self._keystone = ClientV3( - auth_url=cfg.OS_AUTH_URL, - username=cfg.OS_USERNAME, - password=cfg.OS_PASSWORD, - tenant_name=cfg.OS_TENANT_NAME - ) - # store the authentication token - self._auth_token = self._keystone.auth_token - - # get the uri of service endpoint - endpoint = self._get_endpoint("aodh") - - self._url_base = "{}/v2/alarms/%s/state".format(endpoint) - - LOGGER.info('Authenticating request - success') - self._failed_auth = False - - except KeystoneException as exc: - log_level = logging.DEBUG - - if not self._failed_auth: - - log_level = logging.ERROR - LOGGER.error( - 'Suspending error logs until successful auth' - ) - - LOGGER.log(log_level, 'Authentication error: %s', - six.text_type(exc), - exc_info=0) - - if exc.response: - LOGGER.debug('Response: %s', exc.response) - - self._auth_token = None - self._failed_auth = True - - return self._auth_token - - def send(self, metername, severity, resource_id, alarm_severity): - """Send the payload to Aodh. - - severity: is retrieved from the collectd notification itself, it - defines how severely a threshold is broken. Changes everytime - a notification is generated for a specific meter. - alarm_severity: is a variable used to define the severity of the aodh - alarm that will be created. Defined when the alarm is - created and doesn't change, it defines how severe the - situation is if that alarm is triggered. - """ - # get the auth_token - auth_token = self._authenticate() - LOGGER.info('Auth_token: %s', - auth_token, - ) - # if auth_token is not set, there is nothing to do - if auth_token is None: - LOGGER.debug('Unable to send data. Not authenticated') - return - - if self._url_base is None: - LOGGER.debug( - 'Unable to send data. Missing endpoint from ident server') - return - - # Create alarm name + def _create_request_url(self, metername, **kwargs): + """Create the request url for an alarm update.""" + severity = kwargs['severity'] + resource_id = kwargs['resource_id'] + alarm_severity = kwargs['alarm_severity'] alarm_name = self._get_alarm_name(metername, resource_id) + alarm_id = self._get_alarm_id(alarm_name, + severity, metername, alarm_severity) + payload = self._get_alarm_payload(**kwargs) - # Update or create this alarm - result = self._update_or_create_alarm(alarm_name, auth_token, severity, - metername, alarm_severity) + # Create a url if an alarm already exists + if alarm_id is not None: + url = self._url_base % (alarm_id) + try: + self._perform_request(url, payload, self._auth_token, "put") + except requests.exceptions.HTTPError as exc: + # This is an error and it has to be forwarded + self._handle_http_error(exc, metername, payload, + self._auth_token, **kwargs) - if result is None: - return + return None - LOGGER.info('Result: %s %s', - six.text_type(result.status_code), - result.text) - - # if the request failed due to an auth error - if result.status_code == HTTP_UNAUTHORIZED: - # reset the auth token in order to force the subsequent - # _authenticate() call to renew it - # Here, it can happen that the token is reset right after - # another thread has finished the authentication and thus - # the authentication may be performed twice - self._auth_token = None - - # renew the authentication token - auth_token = self._authenticate() - - if auth_token is not None: - result = self._update_or_create_alarm(alarm_name, auth_token, - severity, metername, - alarm_severity) - - if result.status_code == HTTP_NOT_FOUND: - LOGGER.debug("Received 404 error when submitting %s notification, \ - creating a new alarm", + def _handle_http_error(self, exc, metername, + payload, auth_token, **kwargs): + """Handle and log a http error request.""" + severity = kwargs['severity'] + resource_id = kwargs['resource_id'] + alarm_name = self._get_alarm_name(metername, resource_id) + response = exc.response + if response.status_code == common_sender.Sender.HTTP_NOT_FOUND: + LOGGER.debug("Received 404 error when submitting %s update, \ + updating a new alarm", alarm_name) - # check for and/or get alarm_id - result = self._update_or_create_alarm(alarm_name, auth_token, - severity, metername, - alarm_severity) + # get alarm id for + alarm_id = self._get_alarm_id(alarm_name, severity, metername) - if result.status_code == HTTP_CREATED: - LOGGER.debug('Result: %s', HTTP_CREATED) + LOGGER.info('alarmname: %s, alarm_id: %s', alarm_name, alarm_id) + + # Set a new url for the request + url = self._url_base % (alarm_id) + + # Get the responses for the alarm + result = self._perform_request(url, payload, auth_token, "put") + + if result.status_code == common_sender.Sender.HTTP_CREATED: + LOGGER.debug('Result: %s', common_sender.Sender.HTTP_CREATED) + else: + LOGGER.info('Result: %s %s', + result.status_code, + result.text) else: - LOGGER.info('Result: %s %s', - result.status_code, - result.text) + raise exc def _get_endpoint(self, service): """Get the uri of service endpoint.""" @@ -191,23 +114,24 @@ class Sender(object): Config.instance().CEILOMETER_URL_TYPE) return endpoint - def _update_or_create_alarm(self, alarm_name, auth_token, - severity, metername, alarm_severity): + def _get_alarm_id(self, alarm_name, severity, metername, alarm_severity): # check for an alarm and update try: - alarm_id = self._get_alarm_id(alarm_name) - result = self._update_alarm(alarm_id, severity, auth_token) + return self._alarm_ids[alarm_name] # or create a new alarm except KeyError as ke: LOGGER.warn(ke) + LOGGER.warn('No known ID for %s', alarm_name) endpoint = self._get_endpoint("aodh") - LOGGER.warn('No known ID for %s', alarm_name) - result, self._alarm_ids[alarm_name] = \ + alarm_id = \ self._create_alarm(endpoint, severity, metername, alarm_name, alarm_severity) - return result + if alarm_id is not None: + # Add alarm ids/names to relevant dictionaries/lists + self._alarm_ids[alarm_name] = alarm_id + return None def _create_alarm(self, endpoint, severity, metername, alarm_name, alarm_severity): @@ -222,14 +146,10 @@ class Sender(object): 'event_rule': rule, }) - result = self._perform_post_request(url, payload, self._auth_token) + result = self._perform_request(url, payload, self._auth_token, "post") alarm_id = json.loads(result.text)['alarm_id'] LOGGER.debug("alarm_id=%s", alarm_id) - return result, alarm_id - - def _get_alarm_id(self, alarm_name): - """Try and return an alarm_id for an collectd notification""" - return self._alarm_ids[alarm_name] + return alarm_id def _get_alarm_state(self, severity): """Get the state of the alarm.""" @@ -246,66 +166,8 @@ class Sender(object): alarm_name = metername + "(" + resource_id + ")" return alarm_name - def _update_alarm(self, alarm_id, severity, auth_token): - """Perform the alarm update.""" - url = self._url_base % (alarm_id) - # create the payload and update the state of the alarm + def _get_alarm_payload(self, **kwargs): + """Get the payload for the update/post request of the alarm.""" + severity = kwargs['severity'] payload = json.dumps(self._get_alarm_state(severity)) - return self._perform_update_request(url, auth_token, payload) - - @classmethod - def _perform_post_request(cls, url, payload, auth_token): - """Perform the POST request.""" - LOGGER.debug('Performing request to %s', url) - - # request headers - headers = {'X-Auth-Token': auth_token, - 'Content-type': 'application/json'} - # perform request and return its result - response = None - try: - LOGGER.debug( - "Performing request to: %s with data=%s and headers=%s", - url, payload, headers) - - response = requests.post( - url, data=payload, headers=headers, - timeout=(Config.instance().CEILOMETER_TIMEOUT / 1000.)) - LOGGER.info('Response: %s: %s', - response.status_code, response.text - ) - except RequestException as exc: - LOGGER.error('aodh request error: %s', six.text_type(exc)) - finally: - LOGGER.debug( - "Returning response from _perform_post_request(): %s", - response.status_code) - return response - - @classmethod - def _perform_update_request(cls, url, auth_token, payload): - """Perform the PUT/update request.""" - LOGGER.debug('Performing request to %s', url) - - # request headers - headers = {'X-Auth-Token': auth_token, - 'Content-type': 'application/json'} - # perform request and return its result - response = None - try: - LOGGER.debug( - "Performing request to: %s with data=%s and headers=%s", - url, payload, headers) - response = requests.put( - url, data=payload, headers=headers, - timeout=(Config.instance().CEILOMETER_TIMEOUT / 1000.)) - LOGGER.info('Response: %s: %s', - response.status_code, response.text - ) - except RequestException as exc: - LOGGER.error('aodh request error: %s', six.text_type(exc)) - finally: - LOGGER.debug( - 'Returning response from _perform_update_request(): %s', - response.status_code) - return response + return payload diff --git a/collectd_ceilometer/common/sender.py b/collectd_ceilometer/common/sender.py index 3c37501..b324fb7 100644 --- a/collectd_ceilometer/common/sender.py +++ b/collectd_ceilometer/common/sender.py @@ -124,7 +124,7 @@ class Sender(object): raise exc def send(self, metername, payload, **kwargs): - """Send the payload to Ceilometer/Gnocchi""" + """Send the payload to Ceilometer/Gnocchi/Aodh""" # get the auth_token auth_token = self._authenticate() @@ -175,18 +175,22 @@ class Sender(object): auth_token, **kwargs) @classmethod - def _perform_request(cls, url, payload, auth_token): - """Perform the POST request""" - + def _perform_request(cls, url, payload, auth_token, req_type="post"): + """Perform the POST/PUT request.""" LOGGER.debug('Performing request to %s', url) # request headers headers = {'X-Auth-Token': auth_token, 'Content-type': 'application/json'} # perform request and return its result - response = requests.post( - url, data=payload, headers=headers, - timeout=(Config.instance().CEILOMETER_TIMEOUT / 1000.)) + if req_type == "put": + response = requests.put( + url, data=payload, headers=headers, + timeout=(Config.instance().CEILOMETER_TIMEOUT / 1000.)) + else: + response = requests.post( + url, data=payload, headers=headers, + timeout=(Config.instance().CEILOMETER_TIMEOUT / 1000.)) # Raises exception if there was an error try: diff --git a/collectd_ceilometer/tests/aodh/test_plugin.py b/collectd_ceilometer/tests/aodh/test_plugin.py index f6f036c..03476fb 100644 --- a/collectd_ceilometer/tests/aodh/test_plugin.py +++ b/collectd_ceilometer/tests/aodh/test_plugin.py @@ -25,9 +25,10 @@ import six import unittest from collectd_ceilometer.aodh import plugin -from collectd_ceilometer.aodh import sender +from collectd_ceilometer.aodh import sender as aodh_sender from collectd_ceilometer.common.keystone_light import KeystoneException from collectd_ceilometer.common.meters import base +from collectd_ceilometer.common import sender as common_sender from collectd_ceilometer.common import settings Logger = logging.getLoggerClass() @@ -93,21 +94,20 @@ def config_module( def config_severities(severities): - """Create a mocked collectd config node having severities for alarms.""" children = [config_value('ALARM_SEVERITY', key, value) for key, value in six.iteritems(severities)] return config_node('ALARM_SEVERITIES', children) def config_node(key, children, value=None): - """Create a mocked collectd config node having given children and value.""" + "Create a mocked collectd config node having given children and value" return mock.create_autospec( spec=MockedConfig, spec_set=True, instance=True, children=tuple(children), key=key, values=(value,)) def config_value(key, *values): - """Create a mocked collectd config node having given multiple values.""" + "Create a mocked collectd config node having given multiple values" return mock.create_autospec( spec=MockedConfig, spec_set=True, instance=True, children=tuple(), key=key, values=values) @@ -118,17 +118,14 @@ class MockedConfig(object): @abc.abstractproperty def children(self): - """Mocked children method.""" pass @abc.abstractproperty def key(self): - """Mocked key method.""" pass @abc.abstractproperty def values(self): - """Mocked values method.""" pass @@ -162,7 +159,6 @@ class TestPlugin(unittest.TestCase): @property def default_values(self): - """Default configuration values.""" return dict( BATCH_SIZE=1, OS_AUTH_URL='https://test-auth.url.tld/test', @@ -198,89 +194,37 @@ class TestPlugin(unittest.TestCase): collectd.register_notification.assert_called_once_with(instance.notify) collectd.register_shutdown.assert_called_once_with(instance.shutdown) - @mock.patch.object(sender.Sender, '_update_or_create_alarm', autospec=True) - @mock.patch.object(sender.Sender, '_get_alarm_name', autospec=True) - @mock.patch.object(base, 'Meter', autospec=True) - @mock.patch.object(sender, 'ClientV3', autospec=True) + @mock.patch.object(aodh_sender.Sender, '_get_alarm_id', autospec=True) + @mock.patch.object(aodh_sender.Sender, '_get_alarm_state', autospec=True) + @mock.patch.object(requests, 'put', spec=callable) + @mock.patch.object(common_sender, 'ClientV3', autospec=True) @mock_collectd() @mock_config() @mock_value() - def test_update_or_create_alarm(self, data, config, collectd, - ClientV3, meter, - _get_alarm_name, _update_or_create_alarm): - """Test the update/create alarm function""" - auth_client = ClientV3.return_value - auth_client.get_service_endpoint.return_value = \ - 'https://test-aodh.tld' - - _update_or_create_alarm.return_value = requests.Response() - - # init sender instance - instance = sender.Sender() - - _get_alarm_name.return_value = 'my-alarm' - meter_name = meter.meter_name.return_value - severity = meter.collectd_severity.return_value - resource_id = meter.resource_id.return_value - alarm_severity = meter.alarm_severity.return_value - - # send the values - instance.send(meter_name, severity, resource_id, alarm_severity) - - # check that the function is called - _update_or_create_alarm.assert_called_once_with( - instance, 'my-alarm', auth_client.auth_token, - severity, meter_name, alarm_severity) - - # reset function - _update_or_create_alarm.reset_mock() - - # run test again for failed attempt - _update_or_create_alarm.return_value = None - - instance.send(meter_name, severity, resource_id, alarm_severity) - - # and values that have been sent - _update_or_create_alarm.assert_called_once_with( - instance, 'my-alarm', auth_client.auth_token, - severity, meter_name, alarm_severity) - - # reset post method - _update_or_create_alarm.reset_mock() - - @mock.patch.object(sender.Sender, '_get_alarm_id', autospec=True) - @mock.patch.object(requests, 'put', spec=callable) - @mock.patch.object(base, 'Meter', autospec=True) - @mock.patch.object(sender, 'ClientV3', autospec=True) - @mock_collectd() - @mock_config() - def test_update_alarm(self, config, collectd, ClientV3, - meter, put, _get_alarm_id): + def test_update_alarm(self, data, config, collectd, ClientV3, + put, _get_alarm_state, _get_alarm_id): """Test the update alarm function. - Set-up: create a sender object and get an alarm-id for it - Test: update an alarm when there is an alarm-id and when there isn't + Set-up: get an alarm-id for some notification values to be sent + Test: perform an update request Expected behaviour: - - If alarm-id present a put/update request is called + - If alarm-id is present a put request is performed """ auth_client = ClientV3.return_value auth_client.get_service_endpoint.return_value = \ 'https://test-aodh.tld' # init instance - instance = sender.Sender() + instance = plugin.Plugin(collectd=collectd, config=config) # init values to send _get_alarm_id.return_value = 'my-alarm-id' - metername = meter.meter_name.return_value - severity = meter.collectd_severity.return_value - rid = meter.resource_id.return_value - alarm_severity = meter.alarm_severity.return_value + _get_alarm_state.return_value = 'insufficient data' - # send the values - instance.send(metername, severity, rid, alarm_severity) + # notify aodh of the update + instance.notify(data) - # update the alarm + # update the alarm with a put request put.assert_called_once_with( 'https://test-aodh.tld' + '/v2/alarms/my-alarm-id/state', @@ -292,45 +236,43 @@ class TestPlugin(unittest.TestCase): # reset method put.reset_mock() - @mock.patch.object(sender.Sender, '_create_alarm', autospec=True) - @mock.patch.object(sender.Sender, '_get_alarm_id', autospec=True) + @mock.patch.object(aodh_sender.Sender, '_create_alarm', autospec=True) + @mock.patch.object(aodh_sender.Sender, '_get_alarm_id', autospec=True) @mock.patch.object(requests, 'put', spec=callable) - @mock.patch.object(base, 'Meter', autospec=True) - @mock.patch.object(sender, 'ClientV3', autospec=True) + @mock.patch.object(common_sender, 'ClientV3', autospec=True) @mock_collectd() @mock_config() - def test_alarm_not_updated(self, config, collectd, ClientV3, - meter, put, _get_alarm_id, _create_alarm): - """Test if an alarm is created, hence it will not be updated + @mock_value() + def test_update_alarm_no_id(self, data, config, collectd, ClientV3, + put, _get_alarm_id, _create_alarm): + """Test if the is no alarm id the alarm won't be updated. - Set-up: create a sender object and create an alarm - Test: alarm won't be updated if one is created + Set-up: create a client and an instance to send an update to + throw a side-effect when looking for an id + Test: send a notification for a new alarm Expected behaviour: - - No alarm exists alarm-id throws a KeyError and a put/update request - isn't called + - if an alarm is create an update request is not performed """ - # init instance - instance = sender.Sender() + auth_client = ClientV3.return_value + auth_client.get_service_endpoint.return_value = \ + 'https://test-aodh.tld' + + instance = plugin.Plugin(collectd=collectd, config=config) # init values to send _get_alarm_id.return_value = None - _get_alarm_id.side_effect = KeyError() - _create_alarm.return_value = requests.Response(), 'my-alarm-id' - metername = meter.meter_name.return_value - severity = meter.collectd_severity.return_value - rid = meter.resource_id.return_value - alarm_severity = meter.alarm_severity.return_value + _create_alarm.return_value = 'my-alarm-id' - # send the values again - instance.send(metername, severity, rid, alarm_severity) + # try and perform an update without an id + instance.notify(data) put.assert_not_called() put.reset_mock() @mock.patch.object(requests, 'put', spec=callable) - @mock.patch.object(sender, 'ClientV3', autospec=True) - @mock.patch.object(sender, 'LOGGER', autospec=True) + @mock.patch.object(common_sender, 'ClientV3', autospec=True) + @mock.patch.object(common_sender, 'LOGGER', autospec=True) @mock_collectd() @mock_config() @mock_value() @@ -368,7 +310,7 @@ class TestPlugin(unittest.TestCase): Expected-behaviour: returned state value should equal 'ok' and won't equal 'alarm' or insufficient data' """ - instance = sender.Sender() + instance = aodh_sender.Sender() # run test for moderate severity severity.return_value = 'low' @@ -389,7 +331,7 @@ class TestPlugin(unittest.TestCase): Expected-behaviour: returned state value should equal 'alarm' and won't equal 'ok' or insufficient data' """ - instance = sender.Sender() + instance = aodh_sender.Sender() # run test for moderate severity severity.return_value = 'moderate' @@ -410,7 +352,7 @@ class TestPlugin(unittest.TestCase): Expected-behaviour: returned state value should equal 'alarm' and won't equal 'ok' or 'insufficient data' """ - instance = sender.Sender() + instance = aodh_sender.Sender() # run test for moderate severity severity.return_value = 'critical' @@ -422,8 +364,8 @@ class TestPlugin(unittest.TestCase): self.assertNotEqual(instance._get_alarm_state('critical'), 'insufficient data') - @mock.patch.object(sender.Sender, '_perform_post_request', spec=callable) - @mock.patch.object(sender, 'ClientV3', autospec=True) + @mock.patch.object(common_sender.Sender, '_perform_request', spec=callable) + @mock.patch.object(common_sender, 'ClientV3', autospec=True) @mock_collectd() @mock_config() @mock_value() @@ -439,16 +381,15 @@ class TestPlugin(unittest.TestCase): # the value self.assertRaises(requests.RequestException, instance.notify, data) - @mock.patch.object(sender.Sender, '_update_or_create_alarm', autospec=True) - @mock.patch.object(sender.Sender, '_get_alarm_name', autospec=True) - @mock.patch.object(base, 'Meter', autospec=True) - @mock.patch.object(sender, 'ClientV3', autospec=True) + @mock.patch.object(aodh_sender.Sender, '_get_alarm_state', autospec=True) + @mock.patch.object(aodh_sender.Sender, '_get_alarm_id', autospec=True) + @mock.patch.object(requests, 'put', spec=callable) + @mock.patch.object(common_sender, 'ClientV3', autospec=True) @mock_collectd() @mock_config() @mock_value() def test_reauthentication(self, data, config, collectd, - ClientV3, meter, _get_alarm_name, - _update_or_create_alarm): + ClientV3, put, _get_alarm_id, _get_alarm_state): """Test re-authentication for update request.""" # response returned on success @@ -459,62 +400,32 @@ class TestPlugin(unittest.TestCase): response_unauthorized = requests.Response() response_unauthorized.status_code = requests.codes["UNAUTHORIZED"] - _update_or_create_alarm.return_value = response_ok - + # set-up client client = ClientV3.return_value client.auth_token = 'Test auth token' + client.get_service_endpoint.return_value = \ + 'https://test-aodh.tld' # init instance attempt to update/create alarm - instance = sender.Sender() + instance = plugin.Plugin(collectd=collectd, config=config) - alarm_name = _get_alarm_name.return_value - meter_name = meter.meter_name.return_value - severity = meter.collectd_severity.return_value - resource_id = meter.resource_id.return_value - alarm_severity = meter.alarm_severity.return_value + put.return_value = response_ok + _get_alarm_id.return_value = 'my-alarm-id' + _get_alarm_state.return_value = 'insufficient data' - # send the data - instance.send(meter_name, severity, resource_id, alarm_severity) + # send notification to aodh + instance.notify(data) - _update_or_create_alarm.assert_called_once_with( - instance, alarm_name, client.auth_token, - severity, meter_name, alarm_severity) + # put/update is called + put.assert_called_once_with( + 'https://test-aodh.tld' + + '/v2/alarms/my-alarm-id/state', + data='"insufficient data"', + headers={u'Content-type': 'application/json', + u'X-Auth-Token': 'Test auth token'}, + timeout=1.0) - # de-assert the request - _update_or_create_alarm.reset_mock() - - # response returned on success - response_ok = requests.Response() - response_ok.status_code = requests.codes["OK"] - - # response returned on failure - response_unauthorized = requests.Response() - response_unauthorized.status_code = requests.codes["UNAUTHORIZED"] - - _update_or_create_alarm.return_value = response_ok - - client = ClientV3.return_value - client.auth_token = 'Test auth token' - - # send the data - instance.send(meter_name, severity, resource_id, alarm_severity) - - _update_or_create_alarm.assert_called_once_with( - instance, alarm_name, client.auth_token, - severity, meter_name, alarm_severity) - - # update/create response is unauthorized -> new token needs - # to be acquired - _update_or_create_alarm.side_effect = [response_unauthorized, - response_ok] - - # set a new auth token - client.auth_token = 'New test auth token' - - # send the data again - instance.send(meter_name, severity, resource_id, alarm_severity) - - @mock.patch.object(sender, 'ClientV3', autospec=True) + @mock.patch.object(common_sender, 'ClientV3', autospec=True) @mock.patch.object(plugin, 'Notifier', autospec=True) @mock.patch.object(plugin, 'LOGGER', autospec=True) @mock_collectd() @@ -531,7 +442,7 @@ class TestPlugin(unittest.TestCase): self.assertRaises(ValueError, instance.notify, data) - @mock.patch.object(sender, 'ClientV3', autospec=True) + @mock.patch.object(common_sender, 'ClientV3', autospec=True) @mock.patch.object(plugin, 'LOGGER', autospec=True) @mock_collectd() @mock_config() @@ -545,7 +456,7 @@ class TestPlugin(unittest.TestCase): @mock.patch.object(settings, 'LOGGER', autospec=True) def test_user_severities(self, LOGGER): - """Test if a user enters a severity for a specific meter. + """Test if a user enters a severity for a specific meter Set-up: Create a node with some user defined severities Configure the node @@ -572,7 +483,7 @@ class TestPlugin(unittest.TestCase): @mock.patch.object(settings, 'LOGGER', autospec=True) def test_user_severities_invalid(self, LOGGER): - """Test invalid user defined severities. + """Test invalid user defined severities Set-up: Configure the node with one defined severity Set a configuration to have 3 entries instead of the 2 @@ -583,6 +494,7 @@ class TestPlugin(unittest.TestCase): Log will be written that severities were incorrectly configured """ + node = config_module(values=self.default_values, severities=dict(age='low')) # make some alarm severity entry invalid @@ -601,7 +513,7 @@ class TestPlugin(unittest.TestCase): @mock.patch.object(settings, 'LOGGER', autospec=True) def test_user_severities_invalid_node(self, LOGGER): - """Test invalid node with severities configuration. + """Test invalid node with severities configuration Set-up: Set up a configuration node with a severity defined Configure the node with an incorrect module title @@ -609,6 +521,7 @@ class TestPlugin(unittest.TestCase): Expected-behaviour: Error will be recorded in the log Severity configuration will return None """ + node = config_module(values=self.default_values, severities=dict(age='moderate')) # make some alarm severity entry invalid @@ -625,7 +538,7 @@ class TestPlugin(unittest.TestCase): self.assertEqual(config.alarm_severity('age'), 'moderate') def test_read_alarm_severities(self): - """Test reading in user defined alarm severities method. + """Test reading in user defined alarm severities method Set-up: Set up a node configured with a severities dictionary defined Test: Read the node for the ALARM_SEVERITY configuration diff --git a/releasenotes/notes/bug-1668210-5b8220bcc27a81ef.yaml b/releasenotes/notes/bug-1668210-5b8220bcc27a81ef.yaml new file mode 100644 index 0000000..1e2ef12 --- /dev/null +++ b/releasenotes/notes/bug-1668210-5b8220bcc27a81ef.yaml @@ -0,0 +1,6 @@ +--- +Fixes: + - | + Fixes 'bug 1668210 https://bugs.launchpad.net/collectd-ceilometer-plugin/+bug/1668210', + refactored the aodh sender and notifier classes so that the + collectd-aodh-plugin makes use of the common sender class.