Merge "Included the aodh sender class in the common one"

This commit is contained in:
Jenkins 2017-07-17 01:58:11 +00:00 committed by Gerrit Code Review
commit 0aa39651ee
5 changed files with 168 additions and 381 deletions

View File

@ -16,7 +16,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from collectd_ceilometer.aodh.sender import Sender from collectd_ceilometer.aodh import sender as aodh_sender
import datetime import datetime
import logging import logging
@ -30,7 +30,7 @@ class Notifier(object):
def __init__(self, meters, config): def __init__(self, meters, config):
"""Initialize Notifier.""" """Initialize Notifier."""
self._meters = meters self._meters = meters
self._sender = Sender() self._sender = aodh_sender.Sender()
self._config = config self._config = config
def notify(self, vl, data): def notify(self, vl, data):
@ -58,4 +58,6 @@ class Notifier(object):
def _send_data(self, metername, severity, resource_id, alarm_severity): def _send_data(self, metername, severity, resource_id, alarm_severity):
"""Send data to Aodh.""" """Send data to Aodh."""
LOGGER.debug('Sending alarm for %s', metername) 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)

View File

@ -17,17 +17,14 @@
from __future__ import division from __future__ import division
from __future__ import unicode_literals 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 json
import logging import logging
import requests 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__) LOGGER = logging.getLogger(__name__)
ROOT_LOGGER = logging.getLogger(collectd_ceilometer.__name__) ROOT_LOGGER = logging.getLogger(collectd_ceilometer.__name__)
@ -38,151 +35,77 @@ HTTP_UNAUTHORIZED = 401
HTTP_NOT_FOUND = 404 HTTP_NOT_FOUND = 404
class Sender(object): class Sender(common_sender.Sender):
"""Sends the JSON serialized data to Aodh.""" """Sends the JSON serialized data to Aodh."""
def __init__(self): def __init__(self):
"""Create the Sender instance. """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 super(Sender, self).__init__()
self._keystone = None
self._auth_token = None
self._auth_lock = threading.Lock()
self._failed_auth = False
self._alarm_ids = {} self._alarm_ids = {}
def _authenticate(self): def _on_authenticated(self):
"""Authenticate and renew the authentication token.""" # get the uri of service endpoint for an alarm state update
# if auth_token is available, just return it endpoint = self._get_endpoint("aodh")
if self._auth_token is not None:
return self._auth_token
# aquire the authentication lock self._url_base = "{}/v2/alarms/%s/state".format(endpoint)
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
LOGGER.debug('Authenticating request') def _create_request_url(self, metername, **kwargs):
# pylint: disable=broad-except """Create the request url for an alarm update."""
try: severity = kwargs['severity']
# create a keystone client if it doesn't exist resource_id = kwargs['resource_id']
if self._keystone is None: alarm_severity = kwargs['alarm_severity']
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
alarm_name = self._get_alarm_name(metername, resource_id) 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 # Create a url if an alarm already exists
result = self._update_or_create_alarm(alarm_name, auth_token, severity, if alarm_id is not None:
metername, alarm_severity) 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 None
return
LOGGER.info('Result: %s %s', def _handle_http_error(self, exc, metername,
six.text_type(result.status_code), payload, auth_token, **kwargs):
result.text) """Handle and log a http error request."""
severity = kwargs['severity']
# if the request failed due to an auth error resource_id = kwargs['resource_id']
if result.status_code == HTTP_UNAUTHORIZED: alarm_name = self._get_alarm_name(metername, resource_id)
# reset the auth token in order to force the subsequent response = exc.response
# _authenticate() call to renew it if response.status_code == common_sender.Sender.HTTP_NOT_FOUND:
# Here, it can happen that the token is reset right after LOGGER.debug("Received 404 error when submitting %s update, \
# another thread has finished the authentication and thus updating a new alarm",
# 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",
alarm_name) alarm_name)
# check for and/or get alarm_id # get alarm id for
result = self._update_or_create_alarm(alarm_name, auth_token, alarm_id = self._get_alarm_id(alarm_name, severity, metername)
severity, metername,
alarm_severity)
if result.status_code == HTTP_CREATED: LOGGER.info('alarmname: %s, alarm_id: %s', alarm_name, alarm_id)
LOGGER.debug('Result: %s', HTTP_CREATED)
# 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: else:
LOGGER.info('Result: %s %s', raise exc
result.status_code,
result.text)
def _get_endpoint(self, service): def _get_endpoint(self, service):
"""Get the uri of service endpoint.""" """Get the uri of service endpoint."""
@ -191,23 +114,24 @@ class Sender(object):
Config.instance().CEILOMETER_URL_TYPE) Config.instance().CEILOMETER_URL_TYPE)
return endpoint return endpoint
def _update_or_create_alarm(self, alarm_name, auth_token, def _get_alarm_id(self, alarm_name, severity, metername, alarm_severity):
severity, metername, alarm_severity):
# check for an alarm and update # check for an alarm and update
try: try:
alarm_id = self._get_alarm_id(alarm_name) return self._alarm_ids[alarm_name]
result = self._update_alarm(alarm_id, severity, auth_token)
# or create a new alarm # or create a new alarm
except KeyError as ke: except KeyError as ke:
LOGGER.warn(ke) LOGGER.warn(ke)
LOGGER.warn('No known ID for %s', alarm_name)
endpoint = self._get_endpoint("aodh") endpoint = self._get_endpoint("aodh")
LOGGER.warn('No known ID for %s', alarm_name) alarm_id = \
result, self._alarm_ids[alarm_name] = \
self._create_alarm(endpoint, severity, self._create_alarm(endpoint, severity,
metername, alarm_name, alarm_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, def _create_alarm(self, endpoint, severity, metername,
alarm_name, alarm_severity): alarm_name, alarm_severity):
@ -222,14 +146,10 @@ class Sender(object):
'event_rule': rule, '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'] alarm_id = json.loads(result.text)['alarm_id']
LOGGER.debug("alarm_id=%s", alarm_id) LOGGER.debug("alarm_id=%s", alarm_id)
return result, alarm_id return 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]
def _get_alarm_state(self, severity): def _get_alarm_state(self, severity):
"""Get the state of the alarm.""" """Get the state of the alarm."""
@ -246,66 +166,8 @@ class Sender(object):
alarm_name = metername + "(" + resource_id + ")" alarm_name = metername + "(" + resource_id + ")"
return alarm_name return alarm_name
def _update_alarm(self, alarm_id, severity, auth_token): def _get_alarm_payload(self, **kwargs):
"""Perform the alarm update.""" """Get the payload for the update/post request of the alarm."""
url = self._url_base % (alarm_id) severity = kwargs['severity']
# create the payload and update the state of the alarm
payload = json.dumps(self._get_alarm_state(severity)) payload = json.dumps(self._get_alarm_state(severity))
return self._perform_update_request(url, auth_token, payload) return 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

View File

@ -124,7 +124,7 @@ class Sender(object):
raise exc raise exc
def send(self, metername, payload, **kwargs): def send(self, metername, payload, **kwargs):
"""Send the payload to Ceilometer/Gnocchi""" """Send the payload to Ceilometer/Gnocchi/Aodh"""
# get the auth_token # get the auth_token
auth_token = self._authenticate() auth_token = self._authenticate()
@ -175,18 +175,22 @@ class Sender(object):
auth_token, **kwargs) auth_token, **kwargs)
@classmethod @classmethod
def _perform_request(cls, url, payload, auth_token): def _perform_request(cls, url, payload, auth_token, req_type="post"):
"""Perform the POST request""" """Perform the POST/PUT request."""
LOGGER.debug('Performing request to %s', url) LOGGER.debug('Performing request to %s', url)
# request headers # request headers
headers = {'X-Auth-Token': auth_token, headers = {'X-Auth-Token': auth_token,
'Content-type': 'application/json'} 'Content-type': 'application/json'}
# perform request and return its result # perform request and return its result
response = requests.post( if req_type == "put":
url, data=payload, headers=headers, response = requests.put(
timeout=(Config.instance().CEILOMETER_TIMEOUT / 1000.)) 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 # Raises exception if there was an error
try: try:

View File

@ -25,9 +25,10 @@ import six
import unittest import unittest
from collectd_ceilometer.aodh import plugin 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.keystone_light import KeystoneException
from collectd_ceilometer.common.meters import base from collectd_ceilometer.common.meters import base
from collectd_ceilometer.common import sender as common_sender
from collectd_ceilometer.common import settings from collectd_ceilometer.common import settings
Logger = logging.getLoggerClass() Logger = logging.getLoggerClass()
@ -93,21 +94,20 @@ def config_module(
def config_severities(severities): def config_severities(severities):
"""Create a mocked collectd config node having severities for alarms."""
children = [config_value('ALARM_SEVERITY', key, value) children = [config_value('ALARM_SEVERITY', key, value)
for key, value in six.iteritems(severities)] for key, value in six.iteritems(severities)]
return config_node('ALARM_SEVERITIES', children) return config_node('ALARM_SEVERITIES', children)
def config_node(key, children, value=None): 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( return mock.create_autospec(
spec=MockedConfig, spec_set=True, instance=True, spec=MockedConfig, spec_set=True, instance=True,
children=tuple(children), key=key, values=(value,)) children=tuple(children), key=key, values=(value,))
def config_value(key, *values): 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( return mock.create_autospec(
spec=MockedConfig, spec_set=True, instance=True, spec=MockedConfig, spec_set=True, instance=True,
children=tuple(), key=key, values=values) children=tuple(), key=key, values=values)
@ -118,17 +118,14 @@ class MockedConfig(object):
@abc.abstractproperty @abc.abstractproperty
def children(self): def children(self):
"""Mocked children method."""
pass pass
@abc.abstractproperty @abc.abstractproperty
def key(self): def key(self):
"""Mocked key method."""
pass pass
@abc.abstractproperty @abc.abstractproperty
def values(self): def values(self):
"""Mocked values method."""
pass pass
@ -162,7 +159,6 @@ class TestPlugin(unittest.TestCase):
@property @property
def default_values(self): def default_values(self):
"""Default configuration values."""
return dict( return dict(
BATCH_SIZE=1, BATCH_SIZE=1,
OS_AUTH_URL='https://test-auth.url.tld/test', 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_notification.assert_called_once_with(instance.notify)
collectd.register_shutdown.assert_called_once_with(instance.shutdown) collectd.register_shutdown.assert_called_once_with(instance.shutdown)
@mock.patch.object(sender.Sender, '_update_or_create_alarm', autospec=True) @mock.patch.object(aodh_sender.Sender, '_get_alarm_id', autospec=True)
@mock.patch.object(sender.Sender, '_get_alarm_name', autospec=True) @mock.patch.object(aodh_sender.Sender, '_get_alarm_state', autospec=True)
@mock.patch.object(base, 'Meter', autospec=True) @mock.patch.object(requests, 'put', spec=callable)
@mock.patch.object(sender, 'ClientV3', autospec=True) @mock.patch.object(common_sender, 'ClientV3', autospec=True)
@mock_collectd() @mock_collectd()
@mock_config() @mock_config()
@mock_value() @mock_value()
def test_update_or_create_alarm(self, data, config, collectd, def test_update_alarm(self, data, config, collectd, ClientV3,
ClientV3, meter, put, _get_alarm_state, _get_alarm_id):
_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):
"""Test the update alarm function. """Test the update alarm function.
Set-up: create a sender object and get an alarm-id for it Set-up: get an alarm-id for some notification values to be sent
Test: update an alarm when there is an alarm-id and when there isn't Test: perform an update request
Expected behaviour: 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 = ClientV3.return_value
auth_client.get_service_endpoint.return_value = \ auth_client.get_service_endpoint.return_value = \
'https://test-aodh.tld' 'https://test-aodh.tld'
# init instance # init instance
instance = sender.Sender() instance = plugin.Plugin(collectd=collectd, config=config)
# init values to send # init values to send
_get_alarm_id.return_value = 'my-alarm-id' _get_alarm_id.return_value = 'my-alarm-id'
metername = meter.meter_name.return_value _get_alarm_state.return_value = 'insufficient data'
severity = meter.collectd_severity.return_value
rid = meter.resource_id.return_value
alarm_severity = meter.alarm_severity.return_value
# send the values # notify aodh of the update
instance.send(metername, severity, rid, alarm_severity) instance.notify(data)
# update the alarm # update the alarm with a put request
put.assert_called_once_with( put.assert_called_once_with(
'https://test-aodh.tld' + 'https://test-aodh.tld' +
'/v2/alarms/my-alarm-id/state', '/v2/alarms/my-alarm-id/state',
@ -292,45 +236,43 @@ class TestPlugin(unittest.TestCase):
# reset method # reset method
put.reset_mock() put.reset_mock()
@mock.patch.object(sender.Sender, '_create_alarm', autospec=True) @mock.patch.object(aodh_sender.Sender, '_create_alarm', autospec=True)
@mock.patch.object(sender.Sender, '_get_alarm_id', autospec=True) @mock.patch.object(aodh_sender.Sender, '_get_alarm_id', autospec=True)
@mock.patch.object(requests, 'put', spec=callable) @mock.patch.object(requests, 'put', spec=callable)
@mock.patch.object(base, 'Meter', autospec=True) @mock.patch.object(common_sender, 'ClientV3', autospec=True)
@mock.patch.object(sender, 'ClientV3', autospec=True)
@mock_collectd() @mock_collectd()
@mock_config() @mock_config()
def test_alarm_not_updated(self, config, collectd, ClientV3, @mock_value()
meter, put, _get_alarm_id, _create_alarm): def test_update_alarm_no_id(self, data, config, collectd, ClientV3,
"""Test if an alarm is created, hence it will not be updated 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 Set-up: create a client and an instance to send an update to
Test: alarm won't be updated if one is created throw a side-effect when looking for an id
Test: send a notification for a new alarm
Expected behaviour: Expected behaviour:
- No alarm exists alarm-id throws a KeyError and a put/update request - if an alarm is create an update request is not performed
isn't called
""" """
# init instance auth_client = ClientV3.return_value
instance = sender.Sender() auth_client.get_service_endpoint.return_value = \
'https://test-aodh.tld'
instance = plugin.Plugin(collectd=collectd, config=config)
# init values to send # init values to send
_get_alarm_id.return_value = None _get_alarm_id.return_value = None
_get_alarm_id.side_effect = KeyError() _create_alarm.return_value = 'my-alarm-id'
_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
# send the values again # try and perform an update without an id
instance.send(metername, severity, rid, alarm_severity) instance.notify(data)
put.assert_not_called() put.assert_not_called()
put.reset_mock() put.reset_mock()
@mock.patch.object(requests, 'put', spec=callable) @mock.patch.object(requests, 'put', spec=callable)
@mock.patch.object(sender, 'ClientV3', autospec=True) @mock.patch.object(common_sender, 'ClientV3', autospec=True)
@mock.patch.object(sender, 'LOGGER', autospec=True) @mock.patch.object(common_sender, 'LOGGER', autospec=True)
@mock_collectd() @mock_collectd()
@mock_config() @mock_config()
@mock_value() @mock_value()
@ -368,7 +310,7 @@ class TestPlugin(unittest.TestCase):
Expected-behaviour: returned state value should equal 'ok' Expected-behaviour: returned state value should equal 'ok'
and won't equal 'alarm' or insufficient data' and won't equal 'alarm' or insufficient data'
""" """
instance = sender.Sender() instance = aodh_sender.Sender()
# run test for moderate severity # run test for moderate severity
severity.return_value = 'low' severity.return_value = 'low'
@ -389,7 +331,7 @@ class TestPlugin(unittest.TestCase):
Expected-behaviour: returned state value should equal 'alarm' Expected-behaviour: returned state value should equal 'alarm'
and won't equal 'ok' or insufficient data' and won't equal 'ok' or insufficient data'
""" """
instance = sender.Sender() instance = aodh_sender.Sender()
# run test for moderate severity # run test for moderate severity
severity.return_value = 'moderate' severity.return_value = 'moderate'
@ -410,7 +352,7 @@ class TestPlugin(unittest.TestCase):
Expected-behaviour: returned state value should equal 'alarm' Expected-behaviour: returned state value should equal 'alarm'
and won't equal 'ok' or 'insufficient data' and won't equal 'ok' or 'insufficient data'
""" """
instance = sender.Sender() instance = aodh_sender.Sender()
# run test for moderate severity # run test for moderate severity
severity.return_value = 'critical' severity.return_value = 'critical'
@ -422,8 +364,8 @@ class TestPlugin(unittest.TestCase):
self.assertNotEqual(instance._get_alarm_state('critical'), self.assertNotEqual(instance._get_alarm_state('critical'),
'insufficient data') 'insufficient data')
@mock.patch.object(sender.Sender, '_perform_post_request', spec=callable) @mock.patch.object(common_sender.Sender, '_perform_request', spec=callable)
@mock.patch.object(sender, 'ClientV3', autospec=True) @mock.patch.object(common_sender, 'ClientV3', autospec=True)
@mock_collectd() @mock_collectd()
@mock_config() @mock_config()
@mock_value() @mock_value()
@ -439,16 +381,15 @@ class TestPlugin(unittest.TestCase):
# the value # the value
self.assertRaises(requests.RequestException, instance.notify, data) self.assertRaises(requests.RequestException, instance.notify, data)
@mock.patch.object(sender.Sender, '_update_or_create_alarm', autospec=True) @mock.patch.object(aodh_sender.Sender, '_get_alarm_state', autospec=True)
@mock.patch.object(sender.Sender, '_get_alarm_name', autospec=True) @mock.patch.object(aodh_sender.Sender, '_get_alarm_id', autospec=True)
@mock.patch.object(base, 'Meter', autospec=True) @mock.patch.object(requests, 'put', spec=callable)
@mock.patch.object(sender, 'ClientV3', autospec=True) @mock.patch.object(common_sender, 'ClientV3', autospec=True)
@mock_collectd() @mock_collectd()
@mock_config() @mock_config()
@mock_value() @mock_value()
def test_reauthentication(self, data, config, collectd, def test_reauthentication(self, data, config, collectd,
ClientV3, meter, _get_alarm_name, ClientV3, put, _get_alarm_id, _get_alarm_state):
_update_or_create_alarm):
"""Test re-authentication for update request.""" """Test re-authentication for update request."""
# response returned on success # response returned on success
@ -459,62 +400,32 @@ class TestPlugin(unittest.TestCase):
response_unauthorized = requests.Response() response_unauthorized = requests.Response()
response_unauthorized.status_code = requests.codes["UNAUTHORIZED"] response_unauthorized.status_code = requests.codes["UNAUTHORIZED"]
_update_or_create_alarm.return_value = response_ok # set-up client
client = ClientV3.return_value client = ClientV3.return_value
client.auth_token = 'Test auth token' client.auth_token = 'Test auth token'
client.get_service_endpoint.return_value = \
'https://test-aodh.tld'
# init instance attempt to update/create alarm # init instance attempt to update/create alarm
instance = sender.Sender() instance = plugin.Plugin(collectd=collectd, config=config)
alarm_name = _get_alarm_name.return_value put.return_value = response_ok
meter_name = meter.meter_name.return_value _get_alarm_id.return_value = 'my-alarm-id'
severity = meter.collectd_severity.return_value _get_alarm_state.return_value = 'insufficient data'
resource_id = meter.resource_id.return_value
alarm_severity = meter.alarm_severity.return_value
# send the data # send notification to aodh
instance.send(meter_name, severity, resource_id, alarm_severity) instance.notify(data)
_update_or_create_alarm.assert_called_once_with( # put/update is called
instance, alarm_name, client.auth_token, put.assert_called_once_with(
severity, meter_name, alarm_severity) '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 @mock.patch.object(common_sender, 'ClientV3', autospec=True)
_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(plugin, 'Notifier', autospec=True) @mock.patch.object(plugin, 'Notifier', autospec=True)
@mock.patch.object(plugin, 'LOGGER', autospec=True) @mock.patch.object(plugin, 'LOGGER', autospec=True)
@mock_collectd() @mock_collectd()
@ -531,7 +442,7 @@ class TestPlugin(unittest.TestCase):
self.assertRaises(ValueError, instance.notify, data) 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.patch.object(plugin, 'LOGGER', autospec=True)
@mock_collectd() @mock_collectd()
@mock_config() @mock_config()
@ -545,7 +456,7 @@ class TestPlugin(unittest.TestCase):
@mock.patch.object(settings, 'LOGGER', autospec=True) @mock.patch.object(settings, 'LOGGER', autospec=True)
def test_user_severities(self, LOGGER): 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 Set-up: Create a node with some user defined severities
Configure the node Configure the node
@ -572,7 +483,7 @@ class TestPlugin(unittest.TestCase):
@mock.patch.object(settings, 'LOGGER', autospec=True) @mock.patch.object(settings, 'LOGGER', autospec=True)
def test_user_severities_invalid(self, LOGGER): 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-up: Configure the node with one defined severity
Set a configuration to have 3 entries instead of the 2 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 Log will be written that severities were
incorrectly configured incorrectly configured
""" """
node = config_module(values=self.default_values, node = config_module(values=self.default_values,
severities=dict(age='low')) severities=dict(age='low'))
# make some alarm severity entry invalid # make some alarm severity entry invalid
@ -601,7 +513,7 @@ class TestPlugin(unittest.TestCase):
@mock.patch.object(settings, 'LOGGER', autospec=True) @mock.patch.object(settings, 'LOGGER', autospec=True)
def test_user_severities_invalid_node(self, LOGGER): 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 Set-up: Set up a configuration node with a severity defined
Configure the node with an incorrect module title 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 Expected-behaviour: Error will be recorded in the log
Severity configuration will return None Severity configuration will return None
""" """
node = config_module(values=self.default_values, node = config_module(values=self.default_values,
severities=dict(age='moderate')) severities=dict(age='moderate'))
# make some alarm severity entry invalid # make some alarm severity entry invalid
@ -625,7 +538,7 @@ class TestPlugin(unittest.TestCase):
self.assertEqual(config.alarm_severity('age'), 'moderate') self.assertEqual(config.alarm_severity('age'), 'moderate')
def test_read_alarm_severities(self): 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 Set-up: Set up a node configured with a severities dictionary defined
Test: Read the node for the ALARM_SEVERITY configuration Test: Read the node for the ALARM_SEVERITY configuration

View File

@ -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.