Merge "Support aggregation accross metrics and resources"

This commit is contained in:
Jenkins 2015-09-18 16:24:13 +00:00 committed by Gerrit Code Review
commit 2a84d7337f
7 changed files with 131 additions and 9 deletions

View File

@ -60,6 +60,22 @@ class MetricClientTest(base.ClientTestBase):
'timestamp': '2015-03-06T14:34:12+00:00',
'value': '12.0'}], measures)
# MEASURES AGGREGATION
result = self.gnocchi(
'measures', params=("aggregation "
"--metric %s "
"--aggregation mean "
"--start 2015-03-06T14:32:00 "
"--end 2015-03-06T14:36:00"
) % metric["id"])
measures = self.parser.listing(result)
self.assertEqual([{'granularity': '1.0',
'timestamp': '2015-03-06T14:33:57+00:00',
'value': '43.11'},
{'granularity': '1.0',
'timestamp': '2015-03-06T14:34:12+00:00',
'value': '12.0'}], measures)
# LIST
result = self.gnocchi('metric', params="list")
metrics = self.parser.listing(result)
@ -132,6 +148,23 @@ class MetricClientTest(base.ClientTestBase):
'timestamp': '2015-03-06T14:34:12+00:00',
'value': '12.0'}], measures)
# MEASURES AGGREGATION
result = self.gnocchi(
'measures', params=("--debug aggregation "
"--query \"id='metric-res'\" "
"-m metric-name "
"--aggregation mean "
"--needed-overlap 0 "
"--start 2015-03-06T14:32:00 "
"--end 2015-03-06T14:36:00"))
measures = self.parser.listing(result)
self.assertEqual([{'granularity': '1.0',
'timestamp': '2015-03-06T14:33:57+00:00',
'value': '43.11'},
{'granularity': '1.0',
'timestamp': '2015-03-06T14:34:12+00:00',
'value': '12.0'}], measures)
# LIST
result = self.gnocchi('metric', params="list")
metrics = self.parser.listing(result)

View File

@ -119,3 +119,9 @@ def dict_from_parsed_args(parsed_args, attrs):
if value is not None:
d[attr] = value
return d
def dict_to_querystring(objs):
return "&".join(["%s=%s" % (k, v)
for k, v in objs.items()
if v is not None])

View File

@ -16,6 +16,7 @@ import uuid
from oslo_serialization import jsonutils
from gnocchiclient import utils
from gnocchiclient.v1 import base
@ -29,11 +30,12 @@ class MetricManager(base.Manager):
return self.client.api.get(url).json()
@staticmethod
def _ensure_metric_is_uuid(metric):
def _ensure_metric_is_uuid(metric, attribute="resource_id"):
try:
uuid.UUID(metric)
except ValueError:
raise TypeError("resource_id is required to get a metric by name")
raise TypeError("%s is required to get a metric by name" %
attribute)
def get(self, metric, resource_id=None):
"""Get an metric
@ -156,3 +158,45 @@ class MetricManager(base.Manager):
"resource/generic/%s/metric/%s/measures" % (
resource_id, metric))
return self.client.api.get(url, params=params).json()
def aggregation(self, metrics, query=None,
start=None, end=None, aggregation=None,
needed_overlap=None):
"""Get measurements of a aggregated metrics
:param metrics: IDs of metric or metric name
:type metric: list or str
:param query: The query dictionary
:type query: dict
:param start: begin of the period
:type start: timestamp
:param end: end of the period
:type end: timestamp
:param aggregation: aggregation to retrieve
:type aggregation: str
See Gnocchi REST API documentation for the format
of *query dictionary*
http://docs.openstack.org/developer/gnocchi/rest.html#searching-for-resources
"""
if isinstance(start, datetime.datetime):
start = start.isoformat()
if isinstance(end, datetime.datetime):
end = end.isoformat()
params = dict(start=start, end=end, aggregation=aggregation,
needed_overlap=needed_overlap)
if query is None:
for metric in metrics:
self._ensure_metric_is_uuid(metric)
params['metric'] = metrics
url = self.client._build_url("aggregation/metric")
return self.client.api.get(url, params=params).json()
else:
url = self.client._build_url(
"aggregation/resource/generic/metric/%s?%s" % (
metrics, utils.dict_to_querystring(params)))
return self.client.api.post(
url, headers={'Content-Type': "application/json"},
data=jsonutils.dumps(query)).json()

View File

@ -138,3 +138,41 @@ class CliMeasuresAdd(command.Command):
resource_id=parsed_args.resource_id,
measures=parsed_args.measure,
)
class CliMeasuresAggregation(lister.Lister):
COLS = ('timestamp', 'granularity', 'value')
def get_parser(self, prog_name):
parser = super(CliMeasuresAggregation, self).get_parser(prog_name)
parser.add_argument("-m", "--metric", nargs='+', required=True,
help="metrics IDs or metric name")
parser.add_argument("--aggregation",
help="aggregation to retrieve")
parser.add_argument("--start",
help="start of the period")
parser.add_argument("--end",
help="end of the period")
parser.add_argument("--needed-overlap", type=float,
help=("percent of datapoints in each "
"metrics required"))
parser.add_argument("--query", help="Query"),
return parser
def take_action(self, parsed_args):
metrics = parsed_args.metric
query = None
if parsed_args.query:
query = utils.search_query_builder(parsed_args.query)
if len(parsed_args.metric) != 1:
raise ValueError("One metric is required if query is provied")
metrics = parsed_args.metric[0]
measures = self.app.client.metric.aggregation(
metrics=metrics,
query=query,
aggregation=parsed_args.aggregation,
start=parsed_args.start,
end=parsed_args.end,
needed_overlap=parsed_args.needed_overlap,
)
return self.COLS, measures

View File

@ -133,14 +133,14 @@ class ResourceManager(base.Manager):
url = self.client._build_url("resource/generic/%s" % (resource_id))
self.client.api.delete(url)
def search(self, resource_type="generic", request=None, details=False,
def search(self, resource_type="generic", query=None, details=False,
history=False, limit=None, marker=None, sorts=None):
"""List resources
:param resource_type: Type of the resource
:param resource_type: str
:param request: The search request dictionary
:type resource_type: dict
:param query: The query dictionary
:type query: dict
:param details: Show all attributes of resources
:type details: bool
:param history: Show the history of resources
@ -154,14 +154,14 @@ class ResourceManager(base.Manager):
:type sorts: list of str
See Gnocchi REST API documentation for the format
of *request dictionary*
of *query dictionary*
http://docs.openstack.org/developer/gnocchi/rest.html#searching-for-resources
"""
request = request or {}
query = query or {}
qs = _get_pagination_options(details, False, limit, marker, sorts)
url = self.client._build_url(
"search/resource/%s?%s" % (resource_type, qs))
return self.client.api.post(
url, headers={'Content-Type': "application/json"},
data=jsonutils.dumps(request)).json()
data=jsonutils.dumps(query)).json()

View File

@ -92,7 +92,7 @@ class CliResourceSearch(CliResourceList):
def take_action(self, parsed_args):
resources = self.app.client.resource.search(
resource_type=parsed_args.resource_type,
request=utils.search_query_builder(parsed_args.query),
query=utils.search_query_builder(parsed_args.query),
**self._get_pagination_options(parsed_args))
return utils.list2cols(self.COLS, resources)

View File

@ -49,6 +49,7 @@ gnocchi.cli.v1 =
metric_delete = gnocchiclient.v1.metric_cli:CliMetricDelete
measures_get = gnocchiclient.v1.metric_cli:CliMeasuresGet
measures_add = gnocchiclient.v1.metric_cli:CliMeasuresAdd
measures_aggregation = gnocchiclient.v1.metric_cli:CliMeasuresAggregation
keystoneclient.auth.plugin =
gnocchi-noauth = gnocchiclient.noauth:GnocchiNoAuthPlugin