diff --git a/ceilometer/api/controllers/v2.py b/ceilometer/api/controllers/v2.py index 14773707..3f8475fe 100644 --- a/ceilometer/api/controllers/v2.py +++ b/ceilometer/api/controllers/v2.py @@ -56,7 +56,6 @@ from ceilometer import sample from ceilometer import storage from ceilometer import utils - LOG = log.getLogger(__name__) @@ -79,7 +78,8 @@ cfg.CONF.register_opts(ALARM_API_OPTS, group='alarm') state_kind = ["ok", "alarm", "insufficient data"] state_kind_enum = wtypes.Enum(str, *state_kind) -operation_kind = wtypes.Enum(str, 'lt', 'le', 'eq', 'ne', 'ge', 'gt') +operation_kind = ('lt', 'le', 'eq', 'ne', 'ge', 'gt') +operation_kind_enum = wtypes.Enum(str, *operation_kind) class ClientSideError(wsme.exc.ClientSideError): @@ -245,7 +245,7 @@ class Query(_Base): # op = wsme.wsattr(operation_kind, default='eq') # this ^ doesn't seem to work. - op = wsme.wsproperty(operation_kind, get_op, set_op) + op = wsme.wsproperty(operation_kind_enum, get_op, set_op) "The comparison operator. Defaults to 'eq'." value = wtypes.text @@ -2321,16 +2321,17 @@ def _event_query_to_event_filter(q): traits_filter = [] for i in q: - # FIXME(herndon): Support for operators other than - # 'eq' will come later. - if i.op != 'eq': - error = _("operator %s not supported") % i.op + if not i.op: + i.op = 'eq' + elif i.op not in operation_kind: + error = _("operator {} is incorrect").format(i.op) raise ClientSideError(error) if i.field in evt_model_filter: evt_model_filter[i.field] = i.value else: traits_filter.append({"key": i.field, - i.type: i._get_value_as_type()}) + i.type: i._get_value_as_type(), + "op": i.op}) return storage.EventFilter(traits_filter=traits_filter, **evt_model_filter) diff --git a/ceilometer/storage/hbase/inmemory.py b/ceilometer/storage/hbase/inmemory.py index 48718e50..3065078f 100644 --- a/ceilometer/storage/hbase/inmemory.py +++ b/ceilometer/storage/hbase/inmemory.py @@ -131,7 +131,6 @@ class MTable(object): r = {} for row in rows: data = rows[row] - if op == '=': if column in data and data[column] == value: r[row] = data @@ -147,11 +146,9 @@ class MTable(object): elif op == '>=': if column in data and data[column] >= value: r[row] = data - else: - raise NotImplementedError("In-memory " - "SingleColumnValueFilter " - "doesn't support the %s operation " - "yet" % op) + elif op == '!=': + if column in data and data[column] != value: + r[row] = data return r @staticmethod diff --git a/ceilometer/storage/impl_sqlalchemy.py b/ceilometer/storage/impl_sqlalchemy.py index 340727a1..7ee715d1 100644 --- a/ceilometer/storage/impl_sqlalchemy.py +++ b/ceilometer/storage/impl_sqlalchemy.py @@ -770,19 +770,14 @@ class Connection(base.Connection): # Build a sub query that joins Trait to TraitType # where the trait name matches trait_name = trait_filter.pop('key') + op = trait_filter.pop('op', 'eq') conditions = [models.Trait.trait_type_id == models.TraitType.id, models.TraitType.desc == trait_name] for key, value in six.iteritems(trait_filter): - if key == 'string': - conditions.append(models.Trait.t_string == value) - elif key == 'integer': - conditions.append(models.Trait.t_int == value) - elif key == 'datetime': - conditions.append(models.Trait.t_datetime == value) - elif key == 'float': - conditions.append(models.Trait.t_float == value) + sql_utils.trait_op_condition(conditions, + key, value, op) trait_query = (session.query(models.Trait.event_id). join(models.TraitType, diff --git a/ceilometer/storage/sqlalchemy/utils.py b/ceilometer/storage/sqlalchemy/utils.py index 38d6e190..c4291891 100644 --- a/ceilometer/storage/sqlalchemy/utils.py +++ b/ceilometer/storage/sqlalchemy/utils.py @@ -122,3 +122,18 @@ class QueryTransformer(object): def get_query(self): return self.query + + +trait_models_dict = {'string': models.Trait.t_string, + 'integer': models.Trait.t_int, + 'datetime': models.Trait.t_datetime, + 'float': models.Trait.t_float} + + +def trait_op_condition(conditions, trait_type, value, op='eq'): + trait_model = trait_models_dict[trait_type] + op_dict = {'eq': (trait_model == value), 'lt': (trait_model < value), + 'le': (trait_model <= value), 'gt': (trait_model > value), + 'ge': (trait_model >= value), 'ne': (trait_model != value)} + conditions.append(op_dict[op]) + return conditions diff --git a/ceilometer/tests/api/v2/test_event_scenarios.py b/ceilometer/tests/api/v2/test_event_scenarios.py index a07e2dd0..355d7b64 100644 --- a/ceilometer/tests/api/v2/test_event_scenarios.py +++ b/ceilometer/tests/api/v2/test_event_scenarios.py @@ -17,6 +17,7 @@ import datetime from oslo.utils import timeutils +import webtest.app from ceilometer.storage import models from ceilometer.tests.api import v2 @@ -50,13 +51,16 @@ class EventTestBase(v2.FunctionalTest, # Message ID for test will be 'base'. So, message ID for the first # event will be '0', the second '100', and so on. + # trait_time in first event will be equal to self.trait_time + # (datetime.datetime(2013, 12, 31, 5, 0)), next will add 1 day, so + # second will be (datetime.datetime(2014, 01, 01, 5, 0)) and so on. event_models.append( models.Event(message_id=str(base), event_type=event_type, generated=self.trait_time, traits=trait_models)) base += 100 - + self.trait_time += datetime.timedelta(days=1) self.conn.record_events(event_models) @@ -107,7 +111,8 @@ class TestTraitAPI(EventTestBase): data = self.get_json(path, headers=headers) self.assertEqual(1, len(data)) self.assertEqual("trait_D", data[0]['name']) - self.assertEqual(self.trait_time.isoformat(), data[0]['value']) + self.assertEqual((self.trait_time - datetime.timedelta(days=3)). + isoformat(), data[0]['value']) def test_get_trait_data_for_non_existent_event(self): path = (self.PATH % "NO_SUCH_EVENT") + "/trait_A" @@ -125,15 +130,17 @@ class TestTraitAPI(EventTestBase): class TestEventAPI(EventTestBase): PATH = '/events' + START_TRAIT_TIME = datetime.datetime(2013, 12, 31, 5, 0) def test_get_events(self): data = self.get_json(self.PATH, headers=headers) self.assertEqual(3, len(data)) # We expect to get native UTC generated time back - expected_generated = timeutils.strtime( - at=timeutils.normalize_time(self.trait_time), - fmt=timeutils._ISO8601_TIME_FORMAT) + trait_time = self.START_TRAIT_TIME for event in data: + expected_generated = timeutils.strtime( + at=timeutils.normalize_time(trait_time), + fmt=timeutils._ISO8601_TIME_FORMAT) self.assertTrue(event['event_type'] in ['Foo', 'Bar', 'Zoo']) self.assertEqual(4, len(event['traits'])) self.assertEqual(expected_generated, event['generated']) @@ -141,6 +148,7 @@ class TestEventAPI(EventTestBase): 'trait_C', 'trait_D']: self.assertTrue(trait_name in map(lambda x: x['name'], event['traits'])) + trait_time += datetime.timedelta(days=1) def test_get_event_by_message_id(self): event = self.get_json(self.PATH + "/100", headers=headers) @@ -155,10 +163,10 @@ class TestEventAPI(EventTestBase): 'value': '100.123456'}, {'name': 'trait_D', 'type': 'datetime', - 'value': '2013-12-31T05:00:00'}] + 'value': '2014-01-01T05:00:00'}] self.assertEqual('100', event['message_id']) self.assertEqual('Bar', event['event_type']) - self.assertEqual('2013-12-31T05:00:00', event['generated']) + self.assertEqual('2014-01-01T05:00:00', event['generated']) self.assertEqual(expected_traits, event['traits']) def test_get_event_by_message_id_no_such_id(self): @@ -209,13 +217,13 @@ class TestEventAPI(EventTestBase): def test_get_events_filter_datetime_trait(self): data = self.get_json(self.PATH, headers=headers, q=[{'field': 'trait_D', - 'value': self.trait_time.isoformat(), + 'value': '2014-01-01T05:00:00', 'type': 'datetime'}]) - self.assertEqual(3, len(data)) + self.assertEqual(1, len(data)) traits = filter(lambda x: x['name'] == 'trait_D', data[0]['traits']) self.assertEqual(1, len(traits)) self.assertEqual('datetime', traits[0]['type']) - self.assertEqual(self.trait_time.isoformat(), traits[0]['value']) + self.assertEqual('2014-01-01T05:00:00', traits[0]['value']) def test_get_events_multiple_filters(self): data = self.get_json(self.PATH, headers=headers, @@ -243,3 +251,163 @@ class TestEventAPI(EventTestBase): data = self.get_json(self.PATH, headers=headers, q=[]) self.assertEqual(3, len(data)) + + def test_get_events_filter_op_string(self): + data = self.get_json(self.PATH, headers=headers, + q=[{'field': 'trait_A', + 'value': 'my_Foo_text', + 'type': 'string', + 'op': 'eq'}]) + self.assertEqual(1, len(data)) + data = self.get_json(self.PATH, headers=headers, + q=[{'field': 'trait_A', + 'value': 'my_Bar_text', + 'type': 'string', + 'op': 'lt'}]) + self.assertEqual(0, len(data)) + data = self.get_json(self.PATH, headers=headers, + q=[{'field': 'trait_A', + 'value': 'my_Zoo_text', + 'type': 'string', + 'op': 'le'}]) + self.assertEqual(3, len(data)) + data = self.get_json(self.PATH, headers=headers, + q=[{'field': 'trait_A', + 'value': 'my_Foo_text', + 'type': 'string', + 'op': 'ne'}]) + self.assertEqual(2, len(data)) + data = self.get_json(self.PATH, headers=headers, + q=[{'field': 'trait_A', + 'value': 'my_Bar_text', + 'type': 'string', + 'op': 'gt'}]) + self.assertEqual(2, len(data)) + data = self.get_json(self.PATH, headers=headers, + q=[{'field': 'trait_A', + 'value': 'my_Zoo_text', + 'type': 'string', + 'op': 'ge'}]) + self.assertEqual(1, len(data)) + + def test_get_events_filter_op_integer(self): + data = self.get_json(self.PATH, headers=headers, + q=[{'field': 'trait_B', + 'value': '101', + 'type': 'integer', + 'op': 'eq'}]) + self.assertEqual(1, len(data)) + data = self.get_json(self.PATH, headers=headers, + q=[{'field': 'trait_B', + 'value': '201', + 'type': 'integer', + 'op': 'lt'}]) + self.assertEqual(2, len(data)) + data = self.get_json(self.PATH, headers=headers, + q=[{'field': 'trait_B', + 'value': '1', + 'type': 'integer', + 'op': 'le'}]) + self.assertEqual(1, len(data)) + data = self.get_json(self.PATH, headers=headers, + q=[{'field': 'trait_B', + 'value': '101', + 'type': 'integer', + 'op': 'ne'}]) + self.assertEqual(2, len(data)) + data = self.get_json(self.PATH, headers=headers, + q=[{'field': 'trait_B', + 'value': '201', + 'type': 'integer', + 'op': 'gt'}]) + self.assertEqual(0, len(data)) + data = self.get_json(self.PATH, headers=headers, + q=[{'field': 'trait_B', + 'value': '1', + 'type': 'integer', + 'op': 'ge'}]) + self.assertEqual(3, len(data)) + + def test_get_events_filter_op_float(self): + data = self.get_json(self.PATH, headers=headers, + q=[{'field': 'trait_C', + 'value': '100.123456', + 'type': 'float', + 'op': 'eq'}]) + self.assertEqual(1, len(data)) + data = self.get_json(self.PATH, headers=headers, + q=[{'field': 'trait_C', + 'value': '200.123456', + 'type': 'float', + 'op': 'lt'}]) + self.assertEqual(2, len(data)) + data = self.get_json(self.PATH, headers=headers, + q=[{'field': 'trait_C', + 'value': '0.123456', + 'type': 'float', + 'op': 'le'}]) + self.assertEqual(1, len(data)) + data = self.get_json(self.PATH, headers=headers, + q=[{'field': 'trait_C', + 'value': '100.123456', + 'type': 'float', + 'op': 'ne'}]) + self.assertEqual(2, len(data)) + data = self.get_json(self.PATH, headers=headers, + q=[{'field': 'trait_C', + 'value': '200.123456', + 'type': 'float', + 'op': 'gt'}]) + self.assertEqual(0, len(data)) + data = self.get_json(self.PATH, headers=headers, + q=[{'field': 'trait_C', + 'value': '0.123456', + 'type': 'float', + 'op': 'ge'}]) + self.assertEqual(3, len(data)) + + def test_get_events_filter_op_datatime(self): + data = self.get_json(self.PATH, headers=headers, + q=[{'field': 'trait_D', + 'value': '2014-01-01T05:00:00', + 'type': 'datetime', + 'op': 'eq'}]) + self.assertEqual(1, len(data)) + data = self.get_json(self.PATH, headers=headers, + q=[{'field': 'trait_D', + 'value': '2014-01-02T05:00:00', + 'type': 'datetime', + 'op': 'lt'}]) + self.assertEqual(2, len(data)) + data = self.get_json(self.PATH, headers=headers, + q=[{'field': 'trait_D', + 'value': '2013-12-31T05:00:00', + 'type': 'datetime', + 'op': 'le'}]) + self.assertEqual(1, len(data)) + data = self.get_json(self.PATH, headers=headers, + q=[{'field': 'trait_D', + 'value': '2014-01-01T05:00:00', + 'type': 'datetime', + 'op': 'ne'}]) + self.assertEqual(2, len(data)) + data = self.get_json(self.PATH, headers=headers, + q=[{'field': 'trait_D', + 'value': '2014-01-02T05:00:00', + 'type': 'datetime', + 'op': 'gt'}]) + self.assertEqual(0, len(data)) + data = self.get_json(self.PATH, headers=headers, + q=[{'field': 'trait_D', + 'value': '2013-12-31T05:00:00', + 'type': 'datetime', + 'op': 'ge'}]) + self.assertEqual(3, len(data)) + + def test_get_events_filter_wrong_op(self): + self.assertRaises(webtest.app.AppError, + self.get_json, self.PATH, headers=headers, + q=[{'field': 'trait_B', + 'value': '1', + 'type': 'integer', + 'op': 'el'}]) \ No newline at end of file diff --git a/ceilometer/tests/storage/test_storage_scenarios.py b/ceilometer/tests/storage/test_storage_scenarios.py index af014bf0..c8598c5a 100644 --- a/ceilometer/tests/storage/test_storage_scenarios.py +++ b/ceilometer/tests/storage/test_storage_scenarios.py @@ -2825,6 +2825,165 @@ class GetEventTest(EventTestBase): self.assertEqual(events[0].event_type, "Bar") self.assertEqual(4, len(events[0].traits)) + def test_get_event_trait_filter_op_string(self): + trait_filters = [{'key': 'trait_A', 'string': 'my_Foo_text', + 'op': 'eq'}] + event_filter = storage.EventFilter(self.start, self.end, + traits_filter=trait_filters) + events = [event for event in self.conn.get_events(event_filter)] + self.assertEqual(2, len(events)) + self.assertEqual("Foo", events[0].event_type) + self.assertEqual(4, len(events[0].traits)) + trait_filters[0].update({'key': 'trait_A', 'op': 'lt'}) + event_filter = storage.EventFilter(self.start, self.end, + traits_filter=trait_filters) + events = [event for event in self.conn.get_events(event_filter)] + self.assertEqual(2, len(events)) + self.assertEqual("Bar", events[0].event_type) + trait_filters[0].update({'key': 'trait_A', 'op': 'le'}) + event_filter = storage.EventFilter(self.start, self.end, + traits_filter=trait_filters) + events = [event for event in self.conn.get_events(event_filter)] + self.assertEqual(4, len(events)) + self.assertEqual("Bar", events[1].event_type) + trait_filters[0].update({'key': 'trait_A', 'op': 'ne'}) + event_filter = storage.EventFilter(self.start, self.end, + traits_filter=trait_filters) + events = [event for event in self.conn.get_events(event_filter)] + self.assertEqual(4, len(events)) + self.assertEqual("Zoo", events[3].event_type) + trait_filters[0].update({'key': 'trait_A', 'op': 'gt'}) + event_filter = storage.EventFilter(self.start, self.end, + traits_filter=trait_filters) + events = [event for event in self.conn.get_events(event_filter)] + self.assertEqual(2, len(events)) + self.assertEqual("Zoo", events[0].event_type) + trait_filters[0].update({'key': 'trait_A', 'op': 'ge'}) + event_filter = storage.EventFilter(self.start, self.end, + traits_filter=trait_filters) + events = [event for event in self.conn.get_events(event_filter)] + self.assertEqual(4, len(events)) + self.assertEqual("Foo", events[2].event_type) + + def test_get_event_trait_filter_op_integer(self): + trait_filters = [{'key': 'trait_B', 'integer': 101, 'op': 'eq'}] + event_filter = storage.EventFilter(self.start, self.end, + traits_filter=trait_filters) + events = [event for event in self.conn.get_events(event_filter)] + self.assertEqual(1, len(events)) + self.assertEqual("Bar", events[0].event_type) + self.assertEqual(4, len(events[0].traits)) + trait_filters[0].update({'key': 'trait_B', 'op': 'lt'}) + event_filter = storage.EventFilter(self.start, self.end, + traits_filter=trait_filters) + events = [event for event in self.conn.get_events(event_filter)] + self.assertEqual(1, len(events)) + self.assertEqual("Foo", events[0].event_type) + trait_filters[0].update({'key': 'trait_B', 'op': 'le'}) + event_filter = storage.EventFilter(self.start, self.end, + traits_filter=trait_filters) + events = [event for event in self.conn.get_events(event_filter)] + self.assertEqual(2, len(events)) + self.assertEqual("Bar", events[1].event_type) + trait_filters[0].update({'key': 'trait_B', 'op': 'ne'}) + event_filter = storage.EventFilter(self.start, self.end, + traits_filter=trait_filters) + events = [event for event in self.conn.get_events(event_filter)] + self.assertEqual(5, len(events)) + self.assertEqual("Zoo", events[4].event_type) + trait_filters[0].update({'key': 'trait_B', 'op': 'gt'}) + event_filter = storage.EventFilter(self.start, self.end, + traits_filter=trait_filters) + events = [event for event in self.conn.get_events(event_filter)] + self.assertEqual(4, len(events)) + self.assertEqual("Zoo", events[0].event_type) + trait_filters[0].update({'key': 'trait_B', 'op': 'ge'}) + event_filter = storage.EventFilter(self.start, self.end, + traits_filter=trait_filters) + events = [event for event in self.conn.get_events(event_filter)] + self.assertEqual(5, len(events)) + self.assertEqual("Foo", events[2].event_type) + + def test_get_event_trait_filter_op_float(self): + trait_filters = [{'key': 'trait_C', 'float': 300.123456, 'op': 'eq'}] + event_filter = storage.EventFilter(self.start, self.end, + traits_filter=trait_filters) + events = [event for event in self.conn.get_events(event_filter)] + self.assertEqual(1, len(events)) + self.assertEqual("Foo", events[0].event_type) + self.assertEqual(4, len(events[0].traits)) + trait_filters[0].update({'key': 'trait_C', 'op': 'lt'}) + event_filter = storage.EventFilter(self.start, self.end, + traits_filter=trait_filters) + events = [event for event in self.conn.get_events(event_filter)] + self.assertEqual(3, len(events)) + self.assertEqual("Zoo", events[2].event_type) + trait_filters[0].update({'key': 'trait_C', 'op': 'le'}) + event_filter = storage.EventFilter(self.start, self.end, + traits_filter=trait_filters) + events = [event for event in self.conn.get_events(event_filter)] + self.assertEqual(4, len(events)) + self.assertEqual("Bar", events[1].event_type) + trait_filters[0].update({'key': 'trait_C', 'op': 'ne'}) + event_filter = storage.EventFilter(self.start, self.end, + traits_filter=trait_filters) + events = [event for event in self.conn.get_events(event_filter)] + self.assertEqual(5, len(events)) + self.assertEqual("Zoo", events[2].event_type) + trait_filters[0].update({'key': 'trait_C', 'op': 'gt'}) + event_filter = storage.EventFilter(self.start, self.end, + traits_filter=trait_filters) + events = [event for event in self.conn.get_events(event_filter)] + self.assertEqual(2, len(events)) + self.assertEqual("Bar", events[0].event_type) + trait_filters[0].update({'key': 'trait_C', 'op': 'ge'}) + event_filter = storage.EventFilter(self.start, self.end, + traits_filter=trait_filters) + events = [event for event in self.conn.get_events(event_filter)] + self.assertEqual(3, len(events)) + self.assertEqual("Zoo", events[2].event_type) + + def test_get_event_trait_filter_op_datetime(self): + trait_filters = [{'key': 'trait_D', + 'datetime': self.start + datetime.timedelta(hours=2), + 'op': 'eq'}] + event_filter = storage.EventFilter(self.start, self.end, + traits_filter=trait_filters) + events = [event for event in self.conn.get_events(event_filter)] + self.assertEqual(1, len(events)) + self.assertEqual("Zoo", events[0].event_type) + self.assertEqual(4, len(events[0].traits)) + trait_filters[0].update({'key': 'trait_D', 'op': 'lt'}) + event_filter = storage.EventFilter(self.start, self.end, + traits_filter=trait_filters) + events = [event for event in self.conn.get_events(event_filter)] + self.assertEqual(2, len(events)) + trait_filters[0].update({'key': 'trait_D', 'op': 'le'}) + self.assertEqual("Bar", events[1].event_type) + event_filter = storage.EventFilter(self.start, self.end, + traits_filter=trait_filters) + events = [event for event in self.conn.get_events(event_filter)] + self.assertEqual(3, len(events)) + self.assertEqual("Bar", events[1].event_type) + trait_filters[0].update({'key': 'trait_D', 'op': 'ne'}) + event_filter = storage.EventFilter(self.start, self.end, + traits_filter=trait_filters) + events = [event for event in self.conn.get_events(event_filter)] + self.assertEqual(5, len(events)) + self.assertEqual("Foo", events[2].event_type) + trait_filters[0].update({'key': 'trait_D', 'op': 'gt'}) + event_filter = storage.EventFilter(self.start, self.end, + traits_filter=trait_filters) + events = [event for event in self.conn.get_events(event_filter)] + self.assertEqual(3, len(events)) + self.assertEqual("Zoo", events[2].event_type) + trait_filters[0].update({'key': 'trait_D', 'op': 'ge'}) + event_filter = storage.EventFilter(self.start, self.end, + traits_filter=trait_filters) + events = [event for event in self.conn.get_events(event_filter)] + self.assertEqual(4, len(events)) + self.assertEqual("Bar", events[2].event_type) + def test_get_event_multiple_trait_filter(self): trait_filters = [{'key': 'trait_B', 'integer': 1}, {'key': 'trait_A', 'string': 'my_Foo_text'}] @@ -2832,7 +2991,7 @@ class GetEventTest(EventTestBase): traits_filter=trait_filters) events = [event for event in self.conn.get_events(event_filter)] self.assertEqual(1, len(events)) - self.assertEqual(events[0].event_type, "Foo") + self.assertEqual("Foo", events[0].event_type) self.assertEqual(4, len(events[0].traits)) def test_get_event_multiple_trait_filter_expect_none(self):