Browse Source

Meter Get Statistics by name implemented

Ceilometer API: Get Meter Statistics by name implemented and
unit tested.

Change-Id: I9869daaccb8a8e656e6ae283708883a97818b7ec
Xiao Tan 3 years ago
parent
commit
2e0a2a5e1a

+ 1
- 1
AUTHORS View File

@@ -2,5 +2,5 @@ Andreas Jaeger <aj@suse.com>
2 2
 Chang-Yi Lee <cy.l@inwinstack.com>
3 3
 Jiaming Lin <robin890650@gmail.com>
4 4
 Tong Li <litong01@us.ibm.com>
5
+Xiao Tan <xt85@cornell.edu>
5 6
 spzala <spzala@us.ibm.com>
6
-xiaotan2 <xiaotan2@uw.edu>

+ 1
- 0
ChangeLog View File

@@ -1,6 +1,7 @@
1 1
 CHANGES
2 2
 =======
3 3
 
4
+* Meter Get_Meter_Byname implemented
4 5
 * Meters GET request implemented
5 6
 * Added more instructions on how to configure keystone middleware
6 7
 * move up one more dependencies

+ 4
- 0
kiloeyes/api/ceilometer_api_v2.py View File

@@ -36,3 +36,7 @@ class V2API(object):
36 36
     @resource_api.Restify('/v2.0/meters/{meter_name}', method='get')
37 37
     def get_meter_byname(self, req, res, meter_name):
38 38
         res.status = '501 Not Implemented'
39
+
40
+    @resource_api.Restify('/v2.0/meters/{meter_name}/statistics', method='get')
41
+    def get_meter_statistics(self, req, res, meter_name):
42
+        res.status = '501 Not Implemented'

+ 60
- 0
kiloeyes/tests/v2/elasticsearch/test_meters.py View File

@@ -19,6 +19,7 @@ from oslotest import base
19 19
 import requests
20 20
 
21 21
 from kiloeyes.common import kafka_conn
22
+from kiloeyes.common import timeutils as tu
22 23
 from kiloeyes.v2.elasticsearch import meters
23 24
 
24 25
 try:
@@ -178,3 +179,62 @@ class TestMeterDispatcher(base.BaseTestCase):
178 179
         self.assertEqual(obj[0]['counter_volume'], 4)
179 180
         self.assertEqual(obj[0]['timestamp'], 1461337094000)
180 181
         self.assertEqual(len(obj), 1)
182
+
183
+    def test_do_get_statistics(self):
184
+        res = mock.Mock()
185
+        req = mock.Mock()
186
+
187
+        def _side_effect(arg):
188
+            if arg == 'name':
189
+                return 'tongli'
190
+            elif arg == 'dimensions':
191
+                return 'key1:100, key2:200'
192
+            elif arg == 'start_time':
193
+                return '2014-01-01'
194
+            elif arg == 'end_time':
195
+                return None
196
+            elif arg == 'period':
197
+                return None
198
+            elif arg == 'statistics':
199
+                return 'avg, sum, max'
200
+
201
+        req.get_param.side_effect = _side_effect
202
+
203
+        req_result = mock.Mock()
204
+        response_str = """
205
+        {"took":2006,"timed_out":false,"_shards":{"total":5,"successful":5,
206
+        "failed":0},"hits":{"total":600,"max_score":0.0,"hits":[]},
207
+        "aggregations":{"by_name":{"doc_count_error_upper_bound":0,
208
+        "sum_other_doc_count":0,"buckets":[{"key":"BABMGD","doc_count":300,
209
+        "by_dim":{"doc_count_error_upper_bound":0,"sum_other_doc_count":0,
210
+        "buckets":[{"key":"64e6ce08b3b8547b7c32e5cfa5b7d81f","doc_count":300,
211
+        "periods":{"buckets":[{"key":1421700000,"doc_count":130,
212
+        "statistics":{"count":130,"min":0.0,"max":595.0274095324651,
213
+        "avg":91.83085293930924,"sum":11938.0108821102}},
214
+        {"key":1422000000,"doc_count":170,"statistics":{"count":170,
215
+        "min":0.0,"max":1623.511307756313,"avg":324.69434786459897,
216
+        "sum":55198.039136981824}}]},"dimension":{"hits":{"total":300,
217
+        "max_score":1.4142135,"hits":[{"_index":"data_20150121",
218
+        "_type":"metrics","_id":"AUsSNF5mTZaMxA7_wmFx","_score":1.4142135,
219
+        "_source":{"name":"BABMGD","dimensions":{"key2":"NVITDU",
220
+        "key1":"FUFMPY","key_43":"ROQBZM"}}}]}}}]}}]}}}
221
+        """
222
+
223
+        req_result.json.return_value = json.loads(response_str)
224
+
225
+        req_result.status_code = 200
226
+
227
+        with mock.patch.object(requests, 'post', return_value=req_result):
228
+            self.dispatcher.get_meter_statistics(req, res, 'BABMGD')
229
+
230
+        # test that the response code is 200
231
+        self.assertEqual(res.status, getattr(falcon, 'HTTP_200'))
232
+        print(res.body)
233
+        obj = json.loads(res.body)
234
+        # there should be total of 2 objects
235
+        self.assertEqual(len(obj), 2)
236
+        self.assertEqual(obj[0]['avg'], 91.8308529393)
237
+        self.assertEqual(obj[1]['max'], 1623.51130776)
238
+        self.assertEqual(obj[1]['period'], 300)
239
+        self.assertEqual(obj[0]['duration_start'],
240
+                         tu.iso8601_from_timestamp(1421700000))

+ 111
- 0
kiloeyes/v2/elasticsearch/meters.py View File

@@ -23,6 +23,7 @@ from kiloeyes.common import es_conn
23 23
 from kiloeyes.common import kafka_conn
24 24
 from kiloeyes.common import namespace
25 25
 from kiloeyes.common import resource_api
26
+from kiloeyes.common import timeutils as tu
26 27
 from kiloeyes.v2.elasticsearch import metrics
27 28
 
28 29
 try:
@@ -115,6 +116,16 @@ class MeterDispatcher(object):
115 116
         ["dimensions_hash"]},"size":1}}}}}}}
116 117
         """
117 118
 
119
+        self._meter_stats_agg = """
120
+        {"by_name":{"terms":{"field":"name","size":%(size)d},
121
+        "aggs":{"by_dim":{"terms":{"field":"dimensions_hash",
122
+        "size":%(size)d},"aggs":{"dimension":{"top_hits":{"_source":
123
+        {"exclude":["dimensions_hash","timestamp","value"]},"size":1}},
124
+        "periods":{"date_histogram":{"field":"timestamp",
125
+        "interval":"%(period)s"},"aggs":{"statistics":{"stats":
126
+        {"field":"value"}}}}}}}}}
127
+        """
128
+
118 129
         self.setup_index_template()
119 130
 
120 131
     def setup_index_template(self):
@@ -284,3 +295,103 @@ class MeterDispatcher(object):
284 295
             res.content_type = 'application/json;charset=utf-8'
285 296
         else:
286 297
             res.body = ''
298
+
299
+    @resource_api.Restify('/v2.0/meters/{meter_name}/statistics', method='get')
300
+    def get_meter_statistics(self, req, res, meter_name):
301
+        LOG.debug('The meter %s statistics GET request is received' %
302
+                  meter_name)
303
+        # process query conditions
304
+        query = []
305
+        metrics.ParamUtil.common(req, query)
306
+        period = metrics.ParamUtil.period(req)
307
+
308
+        _stats_ag = (self._meter_stats_agg %
309
+                     {"size": self.size, "period": period})
310
+        if query:
311
+            body = ('{"query":{"bool":{"must":' + json.dumps(query) + '}},'
312
+                    '"size":' + str(self.size) + ','
313
+                    '"aggs":' + _stats_ag + '}')
314
+        else:
315
+            body = '{"aggs":' + _stats_ag + '}'
316
+
317
+        # modify the query url to filter out name
318
+        query_url = []
319
+        if meter_name:
320
+            query_url = self._query_url + '&q=name:' + meter_name
321
+        else:
322
+            query_url = self._query_url
323
+        es_res = requests.post(query_url, data=body)
324
+        res.status = getattr(falcon, 'HTTP_%s' % es_res.status_code)
325
+
326
+        LOG.debug('Query to ElasticSearch returned: %s' % es_res.status_code)
327
+        res_data = self._get_agg_response(es_res)
328
+        if res_data:
329
+            # convert the response into Ceilometer Statistics format
330
+            aggs = res_data['by_name']['buckets']
331
+
332
+            LOG.debug('@$Stats: %s' % json.dumps(aggs))
333
+
334
+            def _render_stats(dim):
335
+                is_first = True
336
+                oldest_time = []
337
+                previous_time = []
338
+                for item in dim['periods']['buckets']:
339
+                    current_time = item['key']
340
+                    # calculte period and duration difference
341
+                    if is_first:
342
+                        period_diff = 'null'
343
+                        oldest_time = current_time
344
+                        duration_diff = 'null'
345
+                        previous_time = current_time
346
+                    else:
347
+                        period_diff = (current_time - previous_time) / 1000
348
+                        duration_diff = (current_time - oldest_time) / 1000
349
+                    # parses the statistics data
350
+                    _max = str(item['statistics']['max'])
351
+                    _min = str(item['statistics']['min'])
352
+                    _sum = str(item['statistics']['sum'])
353
+                    _avg = str(item['statistics']['avg'])
354
+                    _count = str(item['statistics']['count'])
355
+                    curr_timestamp = tu.iso8601_from_timestamp(current_time)
356
+                    prev_timestamp = tu.iso8601_from_timestamp(previous_time)
357
+                    old_timestamp = tu.iso8601_from_timestamp(oldest_time)
358
+                    rslt = ('{"avg":' + _avg + ','
359
+                            '"count":' + _count + ','
360
+                            '"duration":' + str(duration_diff) + ','
361
+                            '"duration_end":' +
362
+                            '"%s"' % str(curr_timestamp) + ','
363
+                            '"duration_start":' +
364
+                            '"%s"' % str(old_timestamp) + ','
365
+                            '"max":' + _max + ','
366
+                            '"min":' + _min + ','
367
+                            '"period":' + str(period_diff) + ','
368
+                            '"period_end":' +
369
+                            '"%s"' % str(curr_timestamp) + ','
370
+                            '"period_start":' +
371
+                            '"%s"' % str(prev_timestamp) + ','
372
+                            '"sum":' + _sum + ','
373
+                            '"unit":null}')
374
+                    previous_time = current_time
375
+                    if is_first:
376
+                        yield rslt
377
+                        is_first = False
378
+                    else:
379
+                        yield ',' + rslt
380
+
381
+            def _make_body(items):
382
+                is_first = True
383
+                yield '['
384
+                for metric in items:
385
+                    for dim in metric['by_dim']['buckets']:
386
+                        if is_first:
387
+                            is_first = False
388
+                        else:
389
+                            yield ','
390
+                        for result in _render_stats(dim):
391
+                            yield result
392
+                yield ']'
393
+
394
+            res.body = ''.join(_make_body(aggs))
395
+            res.content_type = 'application/json;charset=utf-8'
396
+        else:
397
+            res.body = 'o'

Loading…
Cancel
Save