Updated _get_alarm_id function in aodh plugin

- Request server for alarm_id before creating new one
  - Adding http get method to _perform_request of common Sender
  - Add unit tests for {aodh,common}/sender

Change-Id: I48e83e87349048962c400a2660cd0c0252563ea0
Closes-bug: #1672296
Signed-off-by: Ivan Dyukov <i.dyukov@samsung.com>
This commit is contained in:
Ivan Dyukov 2017-08-22 14:42:17 +00:00
parent d474b8ba68
commit 680a43d179
4 changed files with 241 additions and 8 deletions

View File

@ -25,6 +25,7 @@ import requests
import collectd_ceilometer
from collectd_ceilometer.common import sender as common_sender
from collectd_ceilometer.common.settings import Config
from collections import OrderedDict
LOGGER = logging.getLogger(__name__)
ROOT_LOGGER = logging.getLogger(collectd_ceilometer.__name__)
@ -114,24 +115,50 @@ class Sender(common_sender.Sender):
Config.instance().CEILOMETER_URL_TYPE)
return endpoint
def _get_remote_alarm_id(self, endpoint, alarm_name):
"""Request alarm with given name."""
url = "{}/v2/alarms".format(endpoint)
# request id from a server. openstack service can
# handle only predefined order of args: q.field=&q.op=&q.value=
# in other cases it will fail
params = OrderedDict([("q.field", "name"), ("q.op", "eq"),
("q.value", alarm_name)])
alarm_id = None
try:
result = self._perform_request(url, params, self._auth_token,
req_type="get")
except Exception as exc:
LOGGER.warn('Invalid response from server for alarm:'
' %s error = %s %s' % (alarm_name, type(exc), exc))
return None
try:
# parse response
alarm_id = json.loads(result.text)[0]['alarm_id']
except (KeyError, ValueError, IndexError):
LOGGER.warn('NO alarm on the server: %s' % alarm_name)
return alarm_id
def _get_alarm_id(self, alarm_name, severity, metername, alarm_severity):
# check for an alarm and update
try:
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)
LOGGER.warn('No ID in a cache for %s', alarm_name)
endpoint = self._get_endpoint("aodh")
alarm_id = \
self._create_alarm(endpoint, severity,
metername, alarm_name, alarm_severity)
alarm_id = self._get_remote_alarm_id(endpoint, alarm_name)
# create new alarm
if alarm_id is None:
alarm_id = \
self._create_alarm(endpoint, severity,
metername, alarm_name, alarm_severity)
if alarm_id is not None:
# Add alarm ids/names to relevant dictionaries/lists
self._alarm_ids[alarm_name] = alarm_id
return None
return alarm_id
def _create_alarm(self, endpoint, severity, metername,
alarm_name, alarm_severity):

View File

@ -177,13 +177,18 @@ class Sender(object):
@classmethod
def _perform_request(cls, url, payload, auth_token, req_type="post"):
"""Perform the POST/PUT request."""
LOGGER.debug('Performing request to %s', url)
LOGGER.debug('Performing request to %s, payload=%s, req_type = %s' %
(url, payload, req_type))
# request headers
headers = {'X-Auth-Token': auth_token,
'Content-type': 'application/json'}
# perform request and return its result
if req_type == "put":
if req_type == "get":
response = requests.get(
url, params=payload, headers=headers,
timeout=(Config.instance().CEILOMETER_TIMEOUT / 1000.))
elif req_type == "put":
response = requests.put(
url, data=payload, headers=headers,
timeout=(Config.instance().CEILOMETER_TIMEOUT / 1000.))

View File

@ -0,0 +1,133 @@
# -*- coding: utf-8 -*-
# Copyright 2010-2011 OpenStack Foundation
# Copyright (c) 2017 Intel Corporation.
#
# 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.
"""Sender tests."""
import mock
import requests
import unittest
from collectd_ceilometer.aodh import sender as aodh_sender
from collections import OrderedDict
valid_resp = '[{"alarm_actions": [], "event_rule": {"query": [],'\
'"event_type": "events.gauge"}, "ok_actions": [],'\
'"name": "alarm", "severity": "moderate",'\
'"timestamp": "2017-08-22T06:22:46.790949", "enabled": true,'\
'"alarm_id": "11af9327-8c3a-4120-8a74-bbc672c90f0a",'\
'"time_constraints": [], "insufficient_data_actions": []}]'
valid_alarm_id = "valid_alarm_id"
class response(object):
__attrs__ = [
'_content', 'status_code', 'headers', 'url', 'history',
'encoding', 'reason', 'cookies', 'elapsed', 'request', 'text'
]
def __init__(self, text, code):
self.text = text
self.status_code = code
def raise_for_status(self):
pass
class TestSender(unittest.TestCase):
"""Test the Sender class."""
def setUp(self):
super(TestSender, self).setUp()
self.sender = aodh_sender.Sender()
@mock.patch.object(aodh_sender.Sender, "_get_remote_alarm_id",
autospec=True)
@mock.patch.object(aodh_sender.Sender, "_get_endpoint", autospec=True)
@mock.patch.object(aodh_sender.Sender, "_create_alarm", spec=callable)
def test_get_alarm_id_no_local_alarm(self, _create_alarm, _get_endpoint,
_get_remote_alarm_id):
"""Test the behaviour when the alarm id doesn't exist locally.
Set-up:
Test: call _get_alarm_id when no local alarm exists.
Expected behaviour:
* _get_remote_alarm_id is called
* self._alarm_ids is updated
"""
_get_remote_alarm_id.return_value = valid_alarm_id
alarm_id = self.sender._get_alarm_id("alarm", "critical", "link status",
"critical")
self.assertEqual(alarm_id, valid_alarm_id,
"_get_remote_alarm_id is not called")
_get_remote_alarm_id.assert_called_once_with(mock.ANY, mock.ANY,
"alarm")
_create_alarm.assert_not_called()
_get_endpoint.assert_called_once_with(mock.ANY, "aodh")
self.assertIn("alarm", self.sender._alarm_ids,
"self._alarm_ids is not updated")
@mock.patch.object(aodh_sender.Sender, "_create_alarm", spec=callable)
@mock.patch.object(requests, 'get', spec=callable)
def test_get_remote_alarm_id(self, get, _create_alarm):
"""Test behaviour of _get_remote_alarm_id
Set-up:
Test: Call _get_remote_alarm_id with typical parameters
Expected behaviour:
* requests.get is called with correct args
* Alarm ID is returned
"""
resp = response(valid_resp, 200)
get.return_value = resp
params = OrderedDict([(u"q.field", u"name"), (u"q.op", u"eq"),
(u"q.value", u"alarm")])
alarm_id = self.sender._get_remote_alarm_id(u"endpoint", u"alarm")
self.assertEqual(alarm_id, "11af9327-8c3a-4120-8a74-bbc672c90f0a",
"invalid alarm id")
_create_alarm.assert_not_called()
get.assert_called_once_with(u"endpoint/v2/alarms", params=params,
headers=mock.ANY, timeout=mock.ANY)
@mock.patch.object(aodh_sender.Sender, "_get_endpoint", autospec=True)
@mock.patch.object(aodh_sender.Sender, "_create_alarm", spec=callable)
@mock.patch.object(requests, 'get', spec=callable)
def test_get_alarm_id_not_found(self, get, _create_alarm, _get_endpoint):
"""Test behaviour of _get_alarm_id when alarm does not exist
Set up:
Test:
* call _get_alarm_id
* requests.get/sender._perform_request return an error
Expected behaviour: _create_alarm is called
"""
resp = response("some invalid response", 404)
get.return_value = resp
_create_alarm.return_value = valid_alarm_id
alarm_id = self.sender._get_alarm_id("alarm", "critical", "link status",
"critical")
_create_alarm.assert_called_once_with(
mock.ANY, "critical", "link status", "alarm", "critical")
_get_endpoint.assert_called_once_with(mock.ANY, "aodh")
self.assertEqual(alarm_id, valid_alarm_id, "invalid alarm id")

View File

@ -0,0 +1,68 @@
# -*- coding: utf-8 -*-
# Copyright 2010-2011 OpenStack Foundation
# Copyright (c) 2017 Intel Corporation.
#
# 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.
"""Sender tests."""
import mock
import requests
import unittest
from collectd_ceilometer.common import sender as common_sender
class TestSender(unittest.TestCase):
"""Test the Sender class."""
def setUp(self):
super(TestSender, self).setUp()
self.sender = common_sender.Sender()
@mock.patch.object(requests, 'post')
@mock.patch.object(requests, 'get')
def test_perform_request_req_type_get(self, get, post):
"""Test the behaviour when performing a get request
Set-up: None
Test: call _perform_request with req_type="get"
Expected behaviour:
* requests.get is called with appropriate params
* requests.post is not called
"""
self.sender._perform_request("my-url", "some payload",
"some headers", req_type="get")
post.assert_not_called()
get.assert_called_with("my-url", params="some payload",
headers=mock.ANY, timeout=mock.ANY)
@mock.patch.object(requests, 'post')
@mock.patch.object(requests, 'get')
def test_perform_request_req_type_post(self, get, post):
"""Test the behaviour when performing a post request
Set-up: None
Test: call _perform_request with req_type="post"
Expected behaviour:
* requests.get is not called
* requests.post is called with appropriate params
"""
self.sender._perform_request("my-url", "some payload",
"some headers", req_type="post")
get.assert_not_called()
post.assert_called_with("my-url", data="some payload",
headers=mock.ANY, timeout=mock.ANY)