diff --git a/ceilometer/api/controllers/v2.py b/ceilometer/api/controllers/v2.py index 0fba3ff91..d627bc3cc 100644 --- a/ceilometer/api/controllers/v2.py +++ b/ceilometer/api/controllers/v2.py @@ -55,6 +55,16 @@ class _Base(wtypes.Base): def from_db_model(cls, m): return cls(**(m.as_dict())) + def as_dict(self, db_model): + valid_keys = inspect.getargspec(db_model.__init__)[0] + if 'self' in valid_keys: + valid_keys.remove('self') + + return dict((k, getattr(self, k)) + for k in valid_keys + if hasattr(self, k) and + getattr(self, k) != wsme.Unset) + class Query(_Base): """Query filter. @@ -526,8 +536,130 @@ class ResourcesController(rest.RestController): return resources +class Alarm(_Base): + """One category of measurements. + """ + + alarm_id = wtypes.text + "The UUID of the alarm" + + name = wtypes.text + "The name for the alarm" + + description = wtypes.text + "The description of the alarm" + + counter_name = wtypes.text + "The name of counter" + + project_id = wtypes.text + "The ID of the project or tenant that owns the alarm" + + user_id = wtypes.text + "The ID of the user who created the alarm" + + comparison_operator = wtypes.Enum(str, 'lt', 'le', 'eq', 'ne', 'ge', 'gt') + "The comparison against the alarm threshold" + + threshold = float + "The threshold of the alarm" + + statistic = wtypes.Enum(str, 'max', 'min', 'avg', 'sum', 'count') + "The statistic to compare to the threshold" + + enabled = bool + "This alarm is enabled?" + + evaluation_periods = int + "The number of periods to evaluate the threshold" + + period = float + "The time range in seconds over which to evaluate the threshold" + + timestamp = datetime.datetime + "The date of the last alarm definition update" + + state = wtypes.Enum(str, 'ok', 'alarm', 'insufficient data') + "The state offset the alarm" + + state_timestamp = datetime.datetime + "The date of the last alarm state changed" + + ok_actions = [wtypes.text] + "The actions to do when alarm state change to ok" + + alarm_actions = [wtypes.text] + "The actions to do when alarm state change to alarm" + + insufficient_data_actions = [wtypes.text] + "The actions to do when alarm state change to insufficient data" + + matching_metadata = {wtypes.text: wtypes.text} + "The matching_metadata of the alarm" + + def __init__(self, **kwargs): + super(Alarm, self).__init__(**kwargs) + + @classmethod + def sample(cls): + return cls(alarm_id=None, + name="SwiftObjectAlarm", + description="An alarm", + counter_name="storage.objects", + comparison_operator="gt", + threshold=200, + statistic="avg", + user_id="c96c887c216949acbdfbd8b494863567", + project_id="c96c887c216949acbdfbd8b494863567", + evaluation_periods=2, + period=240, + enabled=True, + timestamp=datetime.datetime.utcnow(), + state="ok", + state_timestamp=datetime.datetime.utcnow(), + ok_actions=["http://site:8000/ok"], + alarm_actions=["http://site:8000/alarm"], + insufficient_data_actions=["http://site:8000/nodata"], + matching_metadata={"key_name": + "key_value"} + ) + + +class AlarmsController(rest.RestController): + """Works on alarms.""" + + @wsme_pecan.wsexpose(Alarm, body=Alarm, status_code=201) + def post(self, data): + """Create a new alarm""" + raise wsme.exc.ClientSideError("Not implemented") + + @wsme_pecan.wsexpose(Alarm, wtypes.text, body=Alarm, status_code=201) + def put(self, alarm_id, data): + """Modify an alarm""" + raise wsme.exc.ClientSideError("Not implemented") + + @wsme_pecan.wsexpose(None, wtypes.text, status_code=204) + def delete(self, alarm_id): + """Delete an alarm""" + raise wsme.exc.ClientSideError("Not implemented") + + @wsme_pecan.wsexpose(Alarm, wtypes.text) + def get_one(self, alarm_id): + """Return one alarm""" + raise wsme.exc.ClientSideError("Not implemented") + + @wsme_pecan.wsexpose([Alarm], [Query]) + def get_all(self, q=[]): + """Return all alarms, based on the query provided. + + :param q: Filter rules for the alarms to be returned. + """ + return [] + + class V2Controller(object): """Version 2 API controller root.""" resources = ResourcesController() meters = MetersController() + alarms = AlarmsController() diff --git a/ceilometer/tests/api.py b/ceilometer/tests/api.py index 0fd711871..494f59cf1 100644 --- a/ceilometer/tests/api.py +++ b/ceilometer/tests/api.py @@ -27,6 +27,7 @@ from oslo.config import cfg import pecan import pecan.testing +from ceilometer.openstack.common import jsonutils from ceilometer.api import acl from ceilometer.api.v1 import app as v1_app from ceilometer.api.v1 import blueprint as v1_blueprint @@ -130,6 +131,40 @@ class FunctionalTest(db_test_base.TestBase): super(FunctionalTest, self).tearDown() pecan.set_config({}, overwrite=True) + def put_json(self, path, params, expect_errors=False, headers=None, + extra_environ=None, status=None): + return self.post_json(path=path, params=params, + expect_errors=expect_errors, + headers=headers, extra_environ=extra_environ, + status=status, method="put") + + def post_json(self, path, params, expect_errors=False, headers=None, + method="post", extra_environ=None, status=None): + full_path = self.PATH_PREFIX + path + print('%s: %s %s' % (method.upper(), full_path, params)) + response = getattr(self.app, "%s_json" % method)( + full_path, + params=params, + headers=headers, + status=status, + extra_environ=extra_environ, + expect_errors=expect_errors + ) + print('GOT:%s' % response) + return response + + def delete(self, path, expect_errors=False, headers=None, + extra_environ=None, status=None): + full_path = self.PATH_PREFIX + path + print('DELETE: %s' % (full_path)) + response = self.app.delete(full_path, + headers=headers, + status=status, + extra_environ=extra_environ, + expect_errors=expect_errors) + print('GOT:%s' % response) + return response + def get_json(self, path, expect_errors=False, headers=None, extra_environ=None, q=[], **params): full_path = self.PATH_PREFIX + path @@ -144,7 +179,7 @@ class FunctionalTest(db_test_base.TestBase): all_params.update(params) if q: all_params.update(query_params) - print 'GET: %s %r' % (full_path, all_params) + print('GET: %s %r' % (full_path, all_params)) response = self.app.get(full_path, params=all_params, headers=headers, @@ -152,5 +187,5 @@ class FunctionalTest(db_test_base.TestBase): expect_errors=expect_errors) if not expect_errors: response = response.json - print 'GOT:', response + print('GOT:%s' % response) return response diff --git a/tests/api/v2/test_alarm.py b/tests/api/v2/test_alarm.py new file mode 100644 index 000000000..52a12a207 --- /dev/null +++ b/tests/api/v2/test_alarm.py @@ -0,0 +1,69 @@ +# -*- encoding: utf-8 -*- +# +# Copyright © 2013 eNovance +# +# Author: Mehdi Abaakouk +# Angus Salkeld +# +# 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. +'''Tests alarm operation +''' + +import logging + +from .base import FunctionalTest + +LOG = logging.getLogger(__name__) + + +class TestListEmptyAlarms(FunctionalTest): + + def test_empty(self): + data = self.get_json('/alarms') + self.assertEquals([], data) + + +class TestAlarms(FunctionalTest): + + def setUp(self): + super(TestAlarms, self).setUp() + + def test_list_alarms(self): + data = self.get_json('/alarms') + self.assertEquals(0, len(data)) + + def test_get_alarm(self): + data = self.get_json('/alarms/1', expect_errors=True) + self.assertEquals(data.status_int, 400) + + def test_post_alarm(self): + json = { + 'name': 'added_alarm', + 'counter_name': 'ameter', + 'comparison_operator': 'gt', + 'threshold': 2.0, + 'statistic': 'avg', + } + data = self.post_json('/alarms', params=json, expect_errors=True) + self.assertEquals(data.status_int, 400) + + def test_put_alarm(self): + json = { + 'name': 'renamed_alarm', + } + data = self.put_json('/alarms/1', params=json, expect_errors=True) + self.assertEquals(data.status_int, 400) + + def test_delete_alarm(self): + data = self.delete('/alarms/1', expect_errors=True) + self.assertEquals(data.status_int, 400)