Support the Event API
This patch adds support for the currently available Event API, including events, event types, and traits. Add an optional data type to the query, ex: ceilometer list-events -q 'hostname=string::localhost" bp extend-client-operations Change-Id: Icea9bd67f8ee4ff2bf9da9ff6894218689580eb3
This commit is contained in:
		| @@ -66,6 +66,21 @@ def print_list(objs, fields, field_labels, formatters={}, sortby=0): | ||||
|                         sortby_index=sortby) | ||||
|  | ||||
|  | ||||
| def nested_dict_formatter(field): | ||||
|     # (TMaddox) Because the formatting scheme actually drops the whole object | ||||
|     # into the formatter, rather than just the specified field, we have to | ||||
|     # extract it and then pass the value. | ||||
|     return lambda o: format_nested_dict(getattr(o, field)) | ||||
|  | ||||
|  | ||||
| def format_nested_dict(d): | ||||
|     pt = prettytable.PrettyTable(caching=False, print_empty=False, | ||||
|                                  header=False, hrules=prettytable.FRAME) | ||||
|     for k, v in six.iteritems(d): | ||||
|         pt.add_row([k, format_nested_dict(v) if isinstance(v, dict) else v]) | ||||
|     return pt.get_string() | ||||
|  | ||||
|  | ||||
| def print_dict(d, dict_property="Property", wrap=0): | ||||
|     pt = prettytable.PrettyTable([dict_property, 'Value'], | ||||
|                                  caching=False, print_empty=False) | ||||
|   | ||||
| @@ -189,7 +189,7 @@ fixtures = { | ||||
|  | ||||
|     }, | ||||
|     '/v2/alarms?q.field=project_id&q.field=name&q.op=&q.op=' | ||||
|     '&q.value=project-id&q.value=SwiftObjectAlarm': | ||||
|     '&q.type=&q.type=&q.value=project-id&q.value=SwiftObjectAlarm': | ||||
|     { | ||||
|         'GET': ( | ||||
|             {}, | ||||
| @@ -210,7 +210,7 @@ fixtures = { | ||||
|             ALARM_HISTORY, | ||||
|         ), | ||||
|     }, | ||||
|     '/v2/alarms/alarm-id/history?q.field=timestamp&q.op=&q.value=NOW': | ||||
|     '/v2/alarms/alarm-id/history?q.field=timestamp&q.op=&q.type=&q.value=NOW': | ||||
|     { | ||||
|         'GET': ( | ||||
|             {}, | ||||
| @@ -244,7 +244,7 @@ class AlarmManagerTest(testtools.TestCase): | ||||
|         expect = [ | ||||
|             ('GET', | ||||
|              '/v2/alarms?q.field=project_id&q.field=name&q.op=&q.op=' | ||||
|              '&q.value=project-id&q.value=SwiftObjectAlarm', | ||||
|              '&q.type=&q.type=&q.value=project-id&q.value=SwiftObjectAlarm', | ||||
|              {}, None), | ||||
|         ] | ||||
|         self.assertEqual(self.api.calls, expect) | ||||
| @@ -334,7 +334,7 @@ class AlarmManagerTest(testtools.TestCase): | ||||
|     def test_get_constrained_history(self): | ||||
|         q = [dict(field='timestamp', value='NOW')] | ||||
|         url = ('/v2/alarms/alarm-id/history?q.field=timestamp' | ||||
|                '&q.op=&q.value=NOW') | ||||
|                '&q.op=&q.type=&q.value=NOW') | ||||
|         self._do_test_get_history(q, url) | ||||
|  | ||||
|  | ||||
|   | ||||
							
								
								
									
										47
									
								
								ceilometerclient/tests/v2/test_event_types.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								ceilometerclient/tests/v2/test_event_types.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | ||||
| # -*- encoding: utf-8 -*- | ||||
| # Copyright 2014 Hewlett-Packard Development Company, L.P. | ||||
| # | ||||
| #    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. | ||||
|  | ||||
| from ceilometerclient.tests import utils | ||||
| import ceilometerclient.v2.event_types | ||||
|  | ||||
|  | ||||
| fixtures = { | ||||
|     '/v2/event_types/': { | ||||
|         'GET': ( | ||||
|             {}, | ||||
|             ['Foo', 'Bar', 'Sna', 'Fu'] | ||||
|         ), | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| class EventTypesManagerTest(utils.BaseTestCase): | ||||
|  | ||||
|     def setUp(self): | ||||
|         super(EventTypesManagerTest, self).setUp() | ||||
|         self.api = utils.FakeAPI(fixtures) | ||||
|         self.mgr = ceilometerclient.v2.event_types.EventTypeManager(self.api) | ||||
|  | ||||
|     def test_list(self): | ||||
|         event_types = list(self.mgr.list()) | ||||
|         expect = [ | ||||
|             ('GET', '/v2/event_types/', {}, None), | ||||
|         ] | ||||
|         self.assertEqual(self.api.calls, expect) | ||||
|         self.assertEqual(len(event_types), 4) | ||||
|         self.assertEqual(event_types[0].event_type, "Foo") | ||||
|         self.assertEqual(event_types[1].event_type, "Bar") | ||||
|         self.assertEqual(event_types[2].event_type, "Sna") | ||||
|         self.assertEqual(event_types[3].event_type, "Fu") | ||||
							
								
								
									
										189
									
								
								ceilometerclient/tests/v2/test_events.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										189
									
								
								ceilometerclient/tests/v2/test_events.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,189 @@ | ||||
| # -*- encoding: utf-8 -*- | ||||
| # Copyright 2014 Hewlett-Packard Development Company, L.P. | ||||
| # | ||||
| #    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. | ||||
|  | ||||
| from ceilometerclient.tests import utils | ||||
| import ceilometerclient.v2.events | ||||
|  | ||||
|  | ||||
| fixtures = { | ||||
|     '/v2/events': { | ||||
|         'GET': ( | ||||
|             {}, | ||||
|             [ | ||||
|                 { | ||||
|                     'event_type': 'Foo', | ||||
|                     'generated': '1970-01-01T00:00:00', | ||||
|                     'traits': {'trait_A': 'abc', | ||||
|                                'message_id': '1'}, | ||||
|                 }, | ||||
|                 { | ||||
|                     'event_type': 'Foo', | ||||
|                     'generated': '1970-01-01T00:00:00', | ||||
|                     'traits': {'trait_A': 'def', | ||||
|                                'message_id': '2'}, | ||||
|                 }, | ||||
|                 { | ||||
|                     'event_type': 'Bar', | ||||
|                     'generated': '1970-01-01T00:00:00', | ||||
|                     'traits': {'trait_B': 'bartrait', | ||||
|                                'message_id': '3'}, | ||||
|                 }, | ||||
|             ] | ||||
|         ), | ||||
|     }, | ||||
|     '/v2/events?q.field=hostname&q.op=&q.type=string&q.value=localhost': | ||||
|     { | ||||
|         'GET': ( | ||||
|             {}, | ||||
|             [ | ||||
|                 { | ||||
|                     'event_type': 'Foo', | ||||
|                     'generated': '1970-01-01T00:00:00', | ||||
|                     'traits': {'trait_A': 'abc', | ||||
|                                'hostname': 'localhost', | ||||
|                                'message_id': '1'}, | ||||
|                 }, | ||||
|                 { | ||||
|                     'event_type': 'Foo', | ||||
|                     'generated': '1970-01-01T00:00:00', | ||||
|                     'traits': {'trait_A': 'def', | ||||
|                                'hostname': 'localhost', | ||||
|                                'message_id': '2'}, | ||||
|                 } | ||||
|             ] | ||||
|         ), | ||||
|     }, | ||||
|     '/v2/events?q.field=hostname&q.op=&q.type=&q.value=foreignhost': | ||||
|     { | ||||
|         'GET': ( | ||||
|             {}, | ||||
|             [ | ||||
|                 { | ||||
|                     'event_type': 'Foo', | ||||
|                     'generated': '1970-01-01T00:00:00', | ||||
|                     'traits': {'trait_A': 'abc', | ||||
|                                'hostname': 'foreignhost', | ||||
|                                'message_id': '1'}, | ||||
|                 }, | ||||
|                 { | ||||
|                     'event_type': 'Foo', | ||||
|                     'generated': '1970-01-01T00:00:00', | ||||
|                     'traits': {'trait_A': 'def', | ||||
|                                'hostname': 'foreignhost', | ||||
|                                'message_id': '2'}, | ||||
|                 } | ||||
|             ] | ||||
|         ), | ||||
|     }, | ||||
|     '/v2/events?q.field=hostname&q.field=num_cpus&q.op=&q.op=' | ||||
|     '&q.type=&q.type=integer&q.value=localhost&q.value=5': | ||||
|     { | ||||
|         'GET': ( | ||||
|             {}, | ||||
|             [ | ||||
|                 { | ||||
|                     'event_type': 'Bar', | ||||
|                     'generated': '1970-01-01T00:00:00', | ||||
|                     'traits': {'trait_A': 'abc', | ||||
|                                'hostname': 'localhost', | ||||
|                                'num_cpus': '5', | ||||
|                                'message_id': '1'}, | ||||
|                 }, | ||||
|             ] | ||||
|         ), | ||||
|     }, | ||||
|  | ||||
|     '/v2/events/2': | ||||
|     { | ||||
|         'GET': ( | ||||
|             {}, | ||||
|             { | ||||
|                 'event_type': 'Foo', | ||||
|                 'generated': '1970-01-01T00:00:00', | ||||
|                 'traits': {'trait_A': 'def', | ||||
|                            'message_id': '2', | ||||
|                            'intTrait': '42'}, | ||||
|             } | ||||
|         ), | ||||
|     }, | ||||
| } | ||||
|  | ||||
|  | ||||
| class EventManagerTest(utils.BaseTestCase): | ||||
|  | ||||
|     def setUp(self): | ||||
|         super(EventManagerTest, self).setUp() | ||||
|         self.api = utils.FakeAPI(fixtures) | ||||
|         self.mgr = ceilometerclient.v2.events.EventManager(self.api) | ||||
|  | ||||
|     def test_list_all(self): | ||||
|         events = list(self.mgr.list()) | ||||
|         expect = [ | ||||
|             ('GET', '/v2/events', {}, None), | ||||
|         ] | ||||
|         self.assertEqual(self.api.calls, expect) | ||||
|         self.assertEqual(len(events), 3) | ||||
|         self.assertEqual(events[0].event_type, 'Foo') | ||||
|         self.assertEqual(events[1].event_type, 'Foo') | ||||
|         self.assertEqual(events[2].event_type, 'Bar') | ||||
|  | ||||
|     def test_list_one(self): | ||||
|         event = self.mgr.get(2) | ||||
|         expect = [ | ||||
|             ('GET', '/v2/events/2', {}, None), | ||||
|         ] | ||||
|         self.assertEqual(self.api.calls, expect) | ||||
|         self.assertTrue(event) | ||||
|         self.assertEqual(event.event_type, 'Foo') | ||||
|  | ||||
|     def test_list_with_query(self): | ||||
|         events = list(self.mgr.list(q=[{"field": "hostname", | ||||
|                                         "value": "localhost", | ||||
|                                         "type": "string"}])) | ||||
|         expect = [ | ||||
|             ('GET', '/v2/events?q.field=hostname&q.op=&q.type=string' | ||||
|                     '&q.value=localhost', | ||||
|              {}, None), | ||||
|         ] | ||||
|         self.assertEqual(self.api.calls, expect) | ||||
|         self.assertEqual(len(events), 2) | ||||
|         self.assertEqual(events[0].event_type, 'Foo') | ||||
|  | ||||
|     def test_list_with_query_no_type(self): | ||||
|         events = list(self.mgr.list(q=[{"field": "hostname", | ||||
|                                         "value": "foreignhost"}])) | ||||
|         expect = [ | ||||
|             ('GET', '/v2/events?q.field=hostname&q.op=' | ||||
|                     '&q.type=&q.value=foreignhost', | ||||
|              {}, None), | ||||
|         ] | ||||
|         self.assertEqual(self.api.calls, expect) | ||||
|         self.assertEqual(len(events), 2) | ||||
|         self.assertEqual(events[0].event_type, 'Foo') | ||||
|  | ||||
|     def test_list_with_multiple_filters(self): | ||||
|         events = list(self.mgr.list(q=[{"field": "hostname", | ||||
|                                         "value": "localhost"}, | ||||
|                                        {"field": "num_cpus", | ||||
|                                         "value": "5", | ||||
|                                         "type": "integer"}])) | ||||
|  | ||||
|         expect = [ | ||||
|             ('GET', '/v2/events?q.field=hostname&q.field=num_cpus&q.op=&q.op=' | ||||
|                     '&q.type=&q.type=integer&q.value=localhost&q.value=5', | ||||
|              {}, None), | ||||
|         ] | ||||
|         self.assertEqual(self.api.calls, expect) | ||||
|         self.assertEqual(len(events), 1) | ||||
| @@ -21,7 +21,7 @@ class BuildUrlTest(utils.BaseTestCase): | ||||
|         url = options.build_url('/', [{'field': 'this', | ||||
|                                        'op': 'gt', | ||||
|                                        'value': 43}]) | ||||
|         self.assertEqual(url, '/?q.field=this&q.op=gt&q.value=43') | ||||
|         self.assertEqual(url, '/?q.field=this&q.op=gt&q.type=&q.value=43') | ||||
|  | ||||
|     def test_two(self): | ||||
|         url = options.build_url('/', [{'field': 'this', | ||||
| @@ -32,13 +32,14 @@ class BuildUrlTest(utils.BaseTestCase): | ||||
|                                        'value': 88}]) | ||||
|         ops = 'q.op=gt&q.op=lt' | ||||
|         vals = 'q.value=43&q.value=88' | ||||
|         types = 'q.type=&q.type=' | ||||
|         fields = 'q.field=this&q.field=that' | ||||
|         self.assertEqual(url, '/?%s&%s&%s' % (fields, ops, vals)) | ||||
|         self.assertEqual(url, '/?%s&%s&%s&%s' % (fields, ops, types, vals)) | ||||
|  | ||||
|     def test_default_op(self): | ||||
|         url = options.build_url('/', [{'field': 'this', | ||||
|                                        'value': 43}]) | ||||
|         self.assertEqual(url, '/?q.field=this&q.op=&q.value=43') | ||||
|         self.assertEqual(url, '/?q.field=this&q.op=&q.type=&q.value=43') | ||||
|  | ||||
|     def test_one_param(self): | ||||
|         url = options.build_url('/', None, ['period=60']) | ||||
| @@ -49,26 +50,38 @@ class BuildUrlTest(utils.BaseTestCase): | ||||
|                                             'others=value']) | ||||
|         self.assertEqual(url, '/?period=60&others=value') | ||||
|  | ||||
|     def test_with_data_type(self): | ||||
|         url = options.build_url('/', [{'field': 'f1', | ||||
|                                        'value': '10', | ||||
|                                        'type': 'integer'}]) | ||||
|  | ||||
|         self.assertEqual('/?q.field=f1&q.op=&q.type=integer&q.value=10', url) | ||||
|  | ||||
|  | ||||
| class CliTest(utils.BaseTestCase): | ||||
|  | ||||
|     def test_one(self): | ||||
|         ar = options.cli_to_array('this<=34') | ||||
|         self.assertEqual(ar, [{'field': 'this', 'op': 'le', 'value': '34'}]) | ||||
|         self.assertEqual(ar, [{'field': 'this', 'op': 'le', | ||||
|                                'value': '34', 'type': ''}]) | ||||
|  | ||||
|     def test_two(self): | ||||
|         ar = options.cli_to_array('this<=34;that!=foo') | ||||
|         self.assertEqual(ar, [{'field': 'this', 'op': 'le', 'value': '34'}, | ||||
|                               {'field': 'that', 'op': 'ne', 'value': 'foo'}]) | ||||
|         self.assertEqual(ar, [{'field': 'this', 'op': 'le', | ||||
|                                'value': '34', 'type': ''}, | ||||
|                               {'field': 'that', 'op': 'ne', | ||||
|                                'value': 'foo', 'type': ''}]) | ||||
|  | ||||
|     def test_negative(self): | ||||
|         ar = options.cli_to_array('this>=-783') | ||||
|         self.assertEqual(ar, [{'field': 'this', 'op': 'ge', 'value': '-783'}]) | ||||
|         self.assertEqual(ar, [{'field': 'this', 'op': 'ge', | ||||
|                                'value': '-783', 'type': ''}]) | ||||
|  | ||||
|     def test_float(self): | ||||
|         ar = options.cli_to_array('this<=283.347') | ||||
|         self.assertEqual(ar, [{'field': 'this', | ||||
|                                'op': 'le', 'value': '283.347'}]) | ||||
|                                'op': 'le', 'value': '283.347', | ||||
|                                'type': ''}]) | ||||
|  | ||||
|     def test_invalid_seperator(self): | ||||
|         self.assertRaises(ValueError, options.cli_to_array, | ||||
| @@ -81,4 +94,61 @@ class CliTest(utils.BaseTestCase): | ||||
|     def test_with_dot(self): | ||||
|         ar = options.cli_to_array('metadata.this<=34') | ||||
|         self.assertEqual(ar, [{'field': 'metadata.this', | ||||
|                                'op': 'le', 'value': '34'}]) | ||||
|                                'op': 'le', 'value': '34', | ||||
|                                'type': ''}]) | ||||
|  | ||||
|     def test_without_data_type(self): | ||||
|         ar = options.cli_to_array('hostname=localhost') | ||||
|         self.assertEqual(ar, [{'field': 'hostname', | ||||
|                                'op': 'eq', | ||||
|                                'value': 'localhost', | ||||
|                                'type': ''}]) | ||||
|  | ||||
|     def test_with_string_data_type(self): | ||||
|         ar = options.cli_to_array('hostname=string::localhost') | ||||
|         self.assertEqual(ar, [{'field': 'hostname', | ||||
|                                'op': 'eq', | ||||
|                                'type': 'string', | ||||
|                                'value': 'localhost'}]) | ||||
|  | ||||
|     def test_with_int_data_type(self): | ||||
|         ar = options.cli_to_array('port=integer::1234') | ||||
|         self.assertEqual(ar, [{'field': 'port', | ||||
|                                'op': 'eq', | ||||
|                                'type': 'integer', | ||||
|                                'value': '1234'}]) | ||||
|  | ||||
|     def test_with_bool_data_type(self): | ||||
|         ar = options.cli_to_array('port=boolean::true') | ||||
|         self.assertEqual(ar, [{'field': 'port', | ||||
|                                'op': 'eq', | ||||
|                                'type': 'boolean', | ||||
|                                'value': 'true'}]) | ||||
|  | ||||
|     def test_with_float_data_type(self): | ||||
|         ar = options.cli_to_array('average=float::1234.5678') | ||||
|         self.assertEqual(ar, [{'field': 'average', | ||||
|                                'op': 'eq', | ||||
|                                'type': 'float', | ||||
|                                'value': '1234.5678'}]) | ||||
|  | ||||
|     def test_with_datetime_data_type(self): | ||||
|         ar = options.cli_to_array('timestamp=datetime::sometimestamp') | ||||
|         self.assertEqual(ar, [{'field': 'timestamp', | ||||
|                                'op': 'eq', | ||||
|                                'type': 'datetime', | ||||
|                                'value': 'sometimestamp'}]) | ||||
|  | ||||
|     def test_with_incorrect_type(self): | ||||
|         ar = options.cli_to_array('timestamp=invalid::sometimestamp') | ||||
|         self.assertEqual(ar, [{'field': 'timestamp', | ||||
|                                'op': 'eq', | ||||
|                                'type': '', | ||||
|                                'value': 'invalid::sometimestamp'}]) | ||||
|  | ||||
|     def test_with_single_colon(self): | ||||
|         ar = options.cli_to_array('timestamp=datetime:sometimestamp') | ||||
|         self.assertEqual(ar, [{'field': 'timestamp', | ||||
|                                'op': 'eq', | ||||
|                                'type': '', | ||||
|                                'value': 'datetime:sometimestamp'}]) | ||||
|   | ||||
| @@ -37,7 +37,7 @@ fixtures = { | ||||
|             ] | ||||
|         ), | ||||
|     }, | ||||
|     '/v2/resources?q.field=resource_id&q.op=&q.value=a': | ||||
|     '/v2/resources?q.field=resource_id&q.op=&q.type=&q.value=a': | ||||
|     { | ||||
|         'GET': ( | ||||
|             {}, | ||||
| @@ -97,7 +97,8 @@ class ResourceManagerTest(utils.BaseTestCase): | ||||
|                                            "value": "a"}, | ||||
|                                           ])) | ||||
|         expect = [ | ||||
|             ('GET', '/v2/resources?q.field=resource_id&q.op=&q.value=a', | ||||
|             ('GET', '/v2/resources?q.field=resource_id&q.op=' | ||||
|                     '&q.type=&q.value=a', | ||||
|              {}, None), | ||||
|         ] | ||||
|         self.assertEqual(self.api.calls, expect) | ||||
|   | ||||
| @@ -35,7 +35,8 @@ del CREATE_SAMPLE['message_id'] | ||||
| del CREATE_SAMPLE['source'] | ||||
|  | ||||
| base_url = '/v2/meters/instance' | ||||
| args = 'q.field=resource_id&q.field=source&q.op=&q.op=&q.value=foo&q.value=bar' | ||||
| args = ('q.field=resource_id&q.field=source&q.op=&q.op=' | ||||
|         '&q.type=&q.type=&q.value=foo&q.value=bar') | ||||
| args_limit = 'limit=1' | ||||
| fixtures = { | ||||
|     base_url: | ||||
|   | ||||
| @@ -133,7 +133,8 @@ class ShellAlarmHistoryCommandTest(utils.BaseTestCase): | ||||
|     def test_alarm_constrained_history(self): | ||||
|         parsed_query = [dict(field='timestamp', | ||||
|                              value='2013-10-03T08:59:28', | ||||
|                              op='gt')] | ||||
|                              op='gt', | ||||
|                              type='')] | ||||
|         self._do_test_alarm_history(raw_query='timestamp>2013-10-03T08:59:28', | ||||
|                                     parsed_query=parsed_query) | ||||
|  | ||||
|   | ||||
| @@ -17,7 +17,8 @@ from ceilometerclient.tests import utils | ||||
| import ceilometerclient.v2.statistics | ||||
|  | ||||
| base_url = '/v2/meters/instance/statistics' | ||||
| qry = 'q.field=resource_id&q.field=source&q.op=&q.op=&q.value=foo&q.value=bar' | ||||
| qry = ('q.field=resource_id&q.field=source&q.op=&q.op=' | ||||
|        '&q.type=&q.type=&q.value=foo&q.value=bar') | ||||
| period = '&period=60' | ||||
| samples = [ | ||||
|     {u'count': 135, | ||||
|   | ||||
							
								
								
									
										55
									
								
								ceilometerclient/tests/v2/test_trait_descriptions.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								ceilometerclient/tests/v2/test_trait_descriptions.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| # -*- encoding: utf-8 -*- | ||||
| # Copyright 2014 Hewlett-Packard Development Company, L.P. | ||||
| # | ||||
| #    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. | ||||
|  | ||||
| from ceilometerclient.tests import utils | ||||
| import ceilometerclient.v2.trait_descriptions | ||||
|  | ||||
|  | ||||
| fixtures = { | ||||
|     '/v2/event_types/Foo/traits': { | ||||
|         'GET': ( | ||||
|             {}, | ||||
|             [ | ||||
|                 {'name': 'trait_1', 'type': 'string'}, | ||||
|                 {'name': 'trait_2', 'type': 'integer'}, | ||||
|                 {'name': 'trait_3', 'type': 'datetime'} | ||||
|             ] | ||||
|         ), | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| class TraitDescriptionManagerTest(utils.BaseTestCase): | ||||
|  | ||||
|     def setUp(self): | ||||
|         super(TraitDescriptionManagerTest, self).setUp() | ||||
|         self.api = utils.FakeAPI(fixtures) | ||||
|         self.mgr = (ceilometerclient.v2.trait_descriptions. | ||||
|                     TraitDescriptionManager(self.api)) | ||||
|  | ||||
|     def test_list(self): | ||||
|         trait_descriptions = list(self.mgr.list('Foo')) | ||||
|         expect = [ | ||||
|             ('GET', '/v2/event_types/Foo/traits', {}, None), | ||||
|         ] | ||||
|         self.assertEqual(self.api.calls, expect) | ||||
|         self.assertEqual(len(trait_descriptions), 3) | ||||
|         for i, vals in enumerate([('trait_1', 'string'), | ||||
|                                   ('trait_2', 'integer'), | ||||
|                                   ('trait_3', 'datetime')]): | ||||
|  | ||||
|             name, type = vals | ||||
|             self.assertEqual(trait_descriptions[i].name, name) | ||||
|             self.assertEqual(trait_descriptions[i].type, type) | ||||
							
								
								
									
										61
									
								
								ceilometerclient/tests/v2/test_traits.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								ceilometerclient/tests/v2/test_traits.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | ||||
| # -*- encoding: utf-8 -*- | ||||
| # Copyright 2014 Hewlett-Packard Development Company, L.P. | ||||
| # | ||||
| #    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. | ||||
|  | ||||
| from ceilometerclient.tests import utils | ||||
| import ceilometerclient.v2.traits | ||||
|  | ||||
|  | ||||
| fixtures = { | ||||
|     '/v2/event_types/Foo/traits/trait_1': { | ||||
|         'GET': ( | ||||
|             {}, | ||||
|             [ | ||||
|                 {'name': 'trait_1', | ||||
|                  'type': 'datetime', | ||||
|                  'value': '2014-01-07T17:22:10.925553'}, | ||||
|                 {'name': 'trait_1', | ||||
|                  'type': 'datetime', | ||||
|                  'value': '2014-01-07T17:23:10.925553'} | ||||
|             ] | ||||
|         ), | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| class TraitManagerTest(utils.BaseTestCase): | ||||
|  | ||||
|     def setUp(self): | ||||
|         super(TraitManagerTest, self).setUp() | ||||
|         self.api = utils.FakeAPI(fixtures) | ||||
|         self.mgr = ceilometerclient.v2.traits.TraitManager(self.api) | ||||
|  | ||||
|     def test_list(self): | ||||
|         traits = list(self.mgr.list('Foo', 'trait_1')) | ||||
|         expect = [ | ||||
|             ('GET', '/v2/event_types/Foo/traits/trait_1', {}, None), | ||||
|         ] | ||||
|         self.assertEqual(self.api.calls, expect) | ||||
|         self.assertEqual(len(traits), 2) | ||||
|         for i, vals in enumerate([('trait_1', | ||||
|                                    'datetime', | ||||
|                                    '2014-01-07T17:22:10.925553'), | ||||
|                                   ('trait_1', | ||||
|                                    'datetime', | ||||
|                                    '2014-01-07T17:23:10.925553')]): | ||||
|  | ||||
|             name, type, value = vals | ||||
|             self.assertEqual(traits[i].name, name) | ||||
|             self.assertEqual(traits[i].type, type) | ||||
|             self.assertEqual(traits[i].value, value) | ||||
| @@ -15,10 +15,14 @@ | ||||
|  | ||||
| from ceilometerclient.common import http | ||||
| from ceilometerclient.v2 import alarms | ||||
| from ceilometerclient.v2 import event_types | ||||
| from ceilometerclient.v2 import events | ||||
| from ceilometerclient.v2 import meters | ||||
| from ceilometerclient.v2 import resources | ||||
| from ceilometerclient.v2 import samples | ||||
| from ceilometerclient.v2 import statistics | ||||
| from ceilometerclient.v2 import trait_descriptions | ||||
| from ceilometerclient.v2 import traits | ||||
|  | ||||
|  | ||||
| class Client(http.HTTPClient): | ||||
| @@ -39,3 +43,7 @@ class Client(http.HTTPClient): | ||||
|         self.statistics = statistics.StatisticsManager(self) | ||||
|         self.resources = resources.ResourceManager(self) | ||||
|         self.alarms = alarms.AlarmManager(self) | ||||
|         self.events = events.EventManager(self) | ||||
|         self.event_types = event_types.EventTypeManager(self) | ||||
|         self.traits = traits.TraitManager(self) | ||||
|         self.trait_info = trait_descriptions.TraitDescriptionManager(self) | ||||
|   | ||||
							
								
								
									
										31
									
								
								ceilometerclient/v2/event_types.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								ceilometerclient/v2/event_types.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| # -*- encoding: utf-8 -*- | ||||
| # Copyright 2014 Hewlett-Packard Development Company, L.P. | ||||
| # | ||||
| #    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. | ||||
|  | ||||
| from ceilometerclient.common import base | ||||
|  | ||||
|  | ||||
| class EventType(base.Resource): | ||||
|     def __repr__(self): | ||||
|         return "<Event %s>" % self._info | ||||
|  | ||||
|  | ||||
| def object_class_str(mgr, value, loaded): | ||||
|     return EventType(mgr, {"event_type": value}, loaded) | ||||
|  | ||||
|  | ||||
| class EventTypeManager(base.Manager): | ||||
|  | ||||
|     def list(self): | ||||
|         return self._list('/v2/event_types/', obj_class=object_class_str) | ||||
							
								
								
									
										37
									
								
								ceilometerclient/v2/events.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								ceilometerclient/v2/events.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| # -*- encoding: utf-8 -*- | ||||
| # Copyright 2014 Hewlett-Packard Development Company, L.P. | ||||
| # | ||||
| #    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. | ||||
|  | ||||
| from ceilometerclient.common import base | ||||
| from ceilometerclient.v2 import options | ||||
|  | ||||
|  | ||||
| class Event(base.Resource): | ||||
|     def __repr__(self): | ||||
|         return "<Event %s>" % self._info | ||||
|  | ||||
|  | ||||
| class EventManager(base.Manager): | ||||
|     resource_class = Event | ||||
|  | ||||
|     def list(self, q=None): | ||||
|         path = '/v2/events' | ||||
|         return self._list(options.build_url(path, q)) | ||||
|  | ||||
|     def get(self, message_id): | ||||
|         path = '/v2/events/%s' | ||||
|         try: | ||||
|             return self._list(path % message_id, expect_single=True)[0] | ||||
|         except IndexError: | ||||
|             return None | ||||
| @@ -28,10 +28,11 @@ def build_url(path, q, params=None): | ||||
|     if q: | ||||
|         query_params = {'q.field': [], | ||||
|                         'q.value': [], | ||||
|                         'q.op': []} | ||||
|                         'q.op': [], | ||||
|                         'q.type': []} | ||||
|  | ||||
|         for query in q: | ||||
|             for name in ['field', 'op', 'value']: | ||||
|             for name in ['field', 'op', 'value', 'type']: | ||||
|                 query_params['q.%s' % name].append(query.get(name, '')) | ||||
|  | ||||
|         # Transform the dict to a sequence of two-element tuples in fixed | ||||
| @@ -50,13 +51,15 @@ def build_url(path, q, params=None): | ||||
|  | ||||
|  | ||||
| def cli_to_array(cli_query): | ||||
|     '''This converts from the cli list of queries to what is required | ||||
|     """This converts from the cli list of queries to what is required | ||||
|     by the python api. | ||||
|     so from: | ||||
|     "this<=34;that=foo" | ||||
|     to | ||||
|     "[{field=this,op=le,value=34},{field=that,op=eq,value=foo}]" | ||||
|     ''' | ||||
|  | ||||
|     """ | ||||
|  | ||||
|     if cli_query is None: | ||||
|         return None | ||||
|  | ||||
| @@ -77,6 +80,14 @@ def cli_to_array(cli_query): | ||||
|                                string) | ||||
|         return frags | ||||
|  | ||||
|     def split_by_data_type(string): | ||||
|         frags = re.findall(r'^(string|integer|float|datetime|boolean)(::)' | ||||
|                            r'([^ -,\t\n\r\f\v]+)$', string) | ||||
|  | ||||
|         # frags[1] is the separator. Return a list without it if the type | ||||
|         # identifier was found. | ||||
|         return [frags[0][0], frags[0][2]] if frags else None | ||||
|  | ||||
|     opts = [] | ||||
|     queries = cli_query.split(';') | ||||
|     for q in queries: | ||||
| @@ -90,6 +101,15 @@ def cli_to_array(cli_query): | ||||
|         opt = {} | ||||
|         opt['field'] = query[0] | ||||
|         opt['op'] = op_lookup[query[1]] | ||||
|         opt['value'] = query[2] | ||||
|  | ||||
|         # Allow the data type of the value to be specified via <type>::<value>, | ||||
|         # where type can be one of integer, string, float, datetime, boolean | ||||
|         value_frags = split_by_data_type(query[2]) | ||||
|         if not value_frags: | ||||
|             opt['value'] = query[2] | ||||
|             opt['type'] = '' | ||||
|         else: | ||||
|             opt['type'] = value_frags[0] | ||||
|             opt['value'] = value_frags[1] | ||||
|         opts.append(opt) | ||||
|     return opts | ||||
|   | ||||
| @@ -35,7 +35,8 @@ OPERATORS_STRING = dict(gt='>', ge='>=', | ||||
|  | ||||
|  | ||||
| @utils.arg('-q', '--query', metavar='<QUERY>', | ||||
|            help='key[op]value; list.') | ||||
|            help='key[op]data_type::value; list. data_type is optional, ' | ||||
|                 'but if supplied must be string, integer, float, or boolean') | ||||
| @utils.arg('-m', '--meter', metavar='<NAME>', required=True, | ||||
|            help='Name of meter to show samples for.') | ||||
| @utils.arg('-p', '--period', metavar='<PERIOD>', | ||||
| @@ -60,7 +61,8 @@ def do_statistics(cc, args): | ||||
|  | ||||
|  | ||||
| @utils.arg('-q', '--query', metavar='<QUERY>', | ||||
|            help='key[op]value; list.') | ||||
|            help='key[op]data_type::value; list. data_type is optional, ' | ||||
|                 'but if supplied must be string, integer, float, or boolean') | ||||
| @utils.arg('-m', '--meter', metavar='<NAME>', required=True, | ||||
|            help='Name of meter to show samples for.') | ||||
| @utils.arg('-l', '--limit', metavar='<NUMBER>', | ||||
| @@ -121,7 +123,8 @@ def do_sample_create(cc, args={}): | ||||
|  | ||||
|  | ||||
| @utils.arg('-q', '--query', metavar='<QUERY>', | ||||
|            help='key[op]value; list.') | ||||
|            help='key[op]data_type::value; list. data_type is optional, ' | ||||
|                 'but if supplied must be string, integer, float, or boolean') | ||||
| def do_meter_list(cc, args={}): | ||||
|     '''List the user's meters.''' | ||||
|     meters = cc.meters.list(q=options.cli_to_array(args.query)) | ||||
| @@ -193,7 +196,8 @@ def alarm_change_detail_formatter(change): | ||||
|  | ||||
|  | ||||
| @utils.arg('-q', '--query', metavar='<QUERY>', | ||||
|            help='key[op]value; list.') | ||||
|            help='key[op]data_type::value; list. data_type is optional, ' | ||||
|                 'but if supplied must be string, integer, float, or boolean') | ||||
| def do_alarm_list(cc, args={}): | ||||
|     '''List the user's alarms.''' | ||||
|     alarms = cc.alarms.list(q=options.cli_to_array(args.query)) | ||||
| @@ -326,9 +330,8 @@ def do_alarm_create(cc, args={}): | ||||
|            dest='threshold_rule/threshold', | ||||
|            help='Threshold to evaluate against') | ||||
| @utils.arg('-q', '--query', metavar='<QUERY>', | ||||
|            dest='threshold_rule/query', | ||||
|            help='The query to find the data for computing statistics ' | ||||
|            '(key[op]value; list.)') | ||||
|            help='key[op]data_type::value; list. data_type is optional, ' | ||||
|                 'but if supplied must be string, integer, float, or boolean') | ||||
| @utils.arg('--repeat-actions', dest='repeat_actions', | ||||
|            metavar='{True|False}', type=utils.string_to_bool, | ||||
|            default=False, | ||||
| @@ -425,9 +428,8 @@ def do_alarm_update(cc, args={}): | ||||
|            dest='threshold_rule/threshold', | ||||
|            help='Threshold to evaluate against') | ||||
| @utils.arg('-q', '--query', metavar='<QUERY>', | ||||
|            dest='threshold_rule/query', | ||||
|            help='The query to find the data for computing statistics ' | ||||
|            '(key[op]value; list.)') | ||||
|            help='key[op]data_type::value; list. data_type is optional, ' | ||||
|                 'but if supplied must be string, integer, float, or boolean') | ||||
| @utils.arg('--repeat-actions', dest='repeat_actions', | ||||
|            metavar='{True|False}', type=utils.string_to_bool, | ||||
|            help=('True if actions should be repeatedly notified ' | ||||
| @@ -512,7 +514,8 @@ def do_alarm_state_get(cc, args={}): | ||||
| @utils.arg('-a', '--alarm_id', metavar='<ALARM_ID>', required=True, | ||||
|            help='ID of the alarm for which history is shown.') | ||||
| @utils.arg('-q', '--query', metavar='<QUERY>', | ||||
|            help='key[op]value; list.') | ||||
|            help='key[op]data_type::value; list. data_type is optional, ' | ||||
|                 'but if supplied must be string, integer, float, or boolean') | ||||
| def do_alarm_history(cc, args={}): | ||||
|     '''Display the change history of an alarm.''' | ||||
|     kwargs = dict(alarm_id=args.alarm_id, | ||||
| @@ -529,7 +532,8 @@ def do_alarm_history(cc, args={}): | ||||
|  | ||||
|  | ||||
| @utils.arg('-q', '--query', metavar='<QUERY>', | ||||
|            help='key[op]value; list.') | ||||
|            help='key[op]data_type::value; list. data_type is optional, ' | ||||
|                 'but if supplied must be string, integer, float, or boolean.') | ||||
| def do_resource_list(cc, args={}): | ||||
|     '''List the resources.''' | ||||
|     resources = cc.resources.list(q=options.cli_to_array(args.query)) | ||||
| @@ -553,3 +557,61 @@ def do_resource_show(cc, args={}): | ||||
|                   'project_id', 'metadata'] | ||||
|         data = dict([(f, getattr(resource, f, '')) for f in fields]) | ||||
|         utils.print_dict(data, wrap=72) | ||||
|  | ||||
|  | ||||
| @utils.arg('-q', '--query', metavar='<QUERY>', | ||||
|            help='key[op]data_type::value; list. data_type is optional, ' | ||||
|                 'but if supplied must be string, integer, float' | ||||
|                 'or datetime.') | ||||
| def do_event_list(cc, args={}): | ||||
|     '''List events.''' | ||||
|     events = cc.events.list(q=options.cli_to_array(args.query)) | ||||
|     field_labels = ['Message ID', 'Event Type', 'Generated', 'Traits'] | ||||
|     fields = ['message_id', 'event_type', 'generated', 'traits'] | ||||
|     utils.print_list(events, fields, field_labels, | ||||
|                      formatters={ | ||||
|                      'traits': utils.nested_dict_formatter('traits')}) | ||||
|  | ||||
|  | ||||
| @utils.arg('-m', '--message_id', metavar='<message_id>', | ||||
|            help='The id of the event. Should be a UUID', | ||||
|            required=True) | ||||
| def do_event_show(cc, args={}): | ||||
|     '''Show a particular event.''' | ||||
|     event = cc.events.get(args.message_id) | ||||
|     fields = ['event_type', 'generated', 'traits'] | ||||
|     data = dict([(f, getattr(event, f, '')) for f in fields]) | ||||
|     utils.print_dict(data, wrap=72) | ||||
|  | ||||
|  | ||||
| def do_event_type_list(cc, args={}): | ||||
|     '''List event types.''' | ||||
|     event_types = cc.event_types.list() | ||||
|     utils.print_list(event_types, ['event_type'], ['Event Type']) | ||||
|  | ||||
|  | ||||
| @utils.arg('-e', '--event_type', metavar='<EVENT_TYPE>', | ||||
|            help='Type of the event for which traits will be shown', | ||||
|            required=True) | ||||
| def do_trait_description_list(cc, args={}): | ||||
|     '''List trait info for an event type.''' | ||||
|     trait_descriptions = cc.trait_descriptions.list(args.event_type) | ||||
|     field_labels = ['Trait Name', 'Data Type'] | ||||
|     fields = ['name', 'type'] | ||||
|     utils.print_list(trait_descriptions, fields, field_labels) | ||||
|  | ||||
|  | ||||
| @utils.arg('-e', '--event_type', metavar='<EVENT_TYPE>', | ||||
|            help='Type of the event for which traits will listed', | ||||
|            required=True) | ||||
| @utils.arg('-t', '--trait_name', metavar='<TRAIT_NAME>', | ||||
|            help='The name of the trait to list', | ||||
|            required=True) | ||||
| def do_trait_list(cc, args={}): | ||||
|     '''List trait all traits with name <trait_name> for Event Type | ||||
|     <event_type>. | ||||
|     ''' | ||||
|     traits = cc.traits.list(args.event_type, args.trait_name) | ||||
|     field_labels = ['Trait Name', 'Value', 'Data Type'] | ||||
|     fields = ['name', 'value', 'type'] | ||||
|     utils.print_list(traits, fields, field_labels) | ||||
|   | ||||
							
								
								
									
										29
									
								
								ceilometerclient/v2/trait_descriptions.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								ceilometerclient/v2/trait_descriptions.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| # -*- encoding: utf-8 -*- | ||||
| # Copyright 2013 Hewlett-Packard Development Company, L.P. | ||||
| # | ||||
| #    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. | ||||
|  | ||||
| from ceilometerclient.common import base | ||||
|  | ||||
|  | ||||
| class TraitDescription(base.Resource): | ||||
|     def __repr__(self): | ||||
|         return "<Trait %s>" % self._info | ||||
|  | ||||
|  | ||||
| class TraitDescriptionManager(base.Manager): | ||||
|     resource_class = TraitDescription | ||||
|  | ||||
|     def list(self, event_type): | ||||
|         path = '/v2/event_types/%s/traits' % event_type | ||||
|         return self._list(path) | ||||
							
								
								
									
										29
									
								
								ceilometerclient/v2/traits.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								ceilometerclient/v2/traits.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| # -*- encoding: utf-8 -*- | ||||
| # Copyright 2013 Hewlett-Packard Development Company, L.P. | ||||
| # | ||||
| #    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. | ||||
|  | ||||
| from ceilometerclient.common import base | ||||
|  | ||||
|  | ||||
| class Trait(base.Resource): | ||||
|     def __repr__(self): | ||||
|         return "<Trait %s>" % self._info | ||||
|  | ||||
|  | ||||
| class TraitManager(base.Manager): | ||||
|     resource_class = Trait | ||||
|  | ||||
|     def list(self, event_type, trait_name): | ||||
|         path = '/v2/event_types/%s/traits/%s' % (event_type, trait_name) | ||||
|         return self._list(path) | ||||
		Reference in New Issue
	
	Block a user
	 John Herndon
					John Herndon