diff --git a/doc/source/shell.rst b/doc/source/shell.rst index 8d726b6..e3a3322 100644 --- a/doc/source/shell.rst +++ b/doc/source/shell.rst @@ -45,7 +45,7 @@ set these environment variables:: export OS_AUTH_TOKEN=3bcc3d3a03f44e3d8377f9247b0ad155 Also, if the server doesn't support authentification, you can provide -:option:`--os-auth-plugon` gnocchi-noauth, :option:`--endpoint`, :option:`--user-id` +:option:`--os-auth-plugon` gnocchi-noauth, :option:`--gnocchi-endpoint`, :option:`--user-id` and :option:`--project-id`. You can alternatively set these environment variables:: export OS_AUTH_PLUGIN=gnocchi-noauth diff --git a/gnocchiclient/noauth.py b/gnocchiclient/noauth.py index 2230c67..0e22581 100644 --- a/gnocchiclient/noauth.py +++ b/gnocchiclient/noauth.py @@ -56,7 +56,8 @@ class GnocchiNoAuthPlugin(base.BaseAuthPlugin): options.extend([ cfg.StrOpt('user-id', help='User ID', required=True), cfg.StrOpt('project-id', help='Project ID', required=True), - cfg.StrOpt('endpoint', help='Gnocchi endpoint', required=True), + cfg.StrOpt('gnocchi-endpoint', help='Gnocchi endpoint', + dest="endpoint", required=True), ]) return options diff --git a/gnocchiclient/shell.py b/gnocchiclient/shell.py index 9e6749c..75182ce 100644 --- a/gnocchiclient/shell.py +++ b/gnocchiclient/shell.py @@ -118,7 +118,7 @@ class GnocchiShell(app.App): if plugin != noauth.GnocchiNoAuthPlugin: parser.add_argument( - '--endpoint', + '--gnocchi-endpoint', metavar='', dest='endpoint', default=os.environ.get('GNOCCHI_ENDPOINT'), diff --git a/gnocchiclient/tests/functional/base.py b/gnocchiclient/tests/functional/base.py index 79bc58c..16b75f4 100644 --- a/gnocchiclient/tests/functional/base.py +++ b/gnocchiclient/tests/functional/base.py @@ -14,6 +14,7 @@ import os import shlex import six import subprocess +import time import uuid from tempest_lib.cli import base @@ -36,9 +37,9 @@ class GnocchiClient(object): fail_ok=False, merge_stderr=False): creds = ("--os-auth-plugin gnocchi-noauth " "--user-id %s --project-id %s " - "--endpoint %s") % (self.user_id, - self.project_id, - self.endpoint) + "--gnocchi-endpoint %s") % (self.user_id, + self.project_id, + self.endpoint) flags = creds + ' ' + flags @@ -82,6 +83,15 @@ class ClientTestBase(base.ClientTestBase): def _get_clients(self): return GnocchiClient() + def retry_gnocchi(self, retry, *args, **kwargs): + result = "" + while not result.strip() and retry > 0: + result = self.gnocchi(*args, **kwargs) + if not result: + time.sleep(1) + retry -= 1 + return result + def gnocchi(self, *args, **kwargs): return self.clients.gnocchi(*args, **kwargs) diff --git a/gnocchiclient/tests/functional/test_metric.py b/gnocchiclient/tests/functional/test_metric.py index a0ee57f..b4b42a3 100644 --- a/gnocchiclient/tests/functional/test_metric.py +++ b/gnocchiclient/tests/functional/test_metric.py @@ -37,6 +37,29 @@ class MetricClientTest(base.ClientTestBase): metric_get = self.details_multiple(result)[0] self.assertEqual(metric, metric_get) + # MEASURES ADD + result = self.gnocchi('measures', + params=("add %s " + "-m '2015-03-06T14:33:57@43.11' " + "--measure '2015-03-06T14:34:12@12' " + ) % metric["id"]) + self.assertEqual("", result) + + # MEASURES GET + result = self.retry_gnocchi( + 5, 'measures', params=("get %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) @@ -87,6 +110,28 @@ class MetricClientTest(base.ClientTestBase): metric_get = self.details_multiple(result)[0] self.assertEqual(metric, metric_get) + # MEASURES ADD + result = self.gnocchi('measures', + params=("add metric-name metric-res " + "-m '2015-03-06T14:33:57@43.11' " + "--measure '2015-03-06T14:34:12@12'")) + self.assertEqual("", result) + + # MEASURES GET + result = self.retry_gnocchi( + 5, 'measures', params=("get metric-name metric-res " + "--aggregation mean " + "--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) diff --git a/gnocchiclient/v1/metric.py b/gnocchiclient/v1/metric.py index abb9339..6011083 100644 --- a/gnocchiclient/v1/metric.py +++ b/gnocchiclient/v1/metric.py @@ -11,6 +11,7 @@ # License for the specific language governing permissions and limitations # under the License. +import datetime import uuid from oslo_serialization import jsonutils @@ -97,3 +98,61 @@ class MetricManager(base.Manager): url = self.client._build_url("resource/generic/%s/metric/%s" % ( resource_id, metric)) self.client.api.delete(url) + + def add_measures(self, metric, measures, resource_id=None): + """Add measurements to a metric + + :param metric: ID or Name of the metric + :type metric: str + :param resource_id: ID of the resource (required + to get a metric by name) + :type resource_id: str + :param measures: measurements + :type measures: list of dict(timestamp=timestamp, value=float) + """ + if resource_id is None: + self._ensure_metric_is_uuid(metric) + url = self.client._build_url("metric/%s/measures" % metric) + else: + url = self.client._build_url( + "resource/generic/%s/metric/%s/measures" % ( + resource_id, metric)) + return self.client.api.post( + url, headers={'Content-Type': "application/json"}, + data=jsonutils.dumps(measures)) + + def get_measures(self, metric, start=None, end=None, aggregation=None, + resource_id=None, **kwargs): + """Get measurements of a metric + + :param metric: ID or Name of the metric + :type metric: str + :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 + :param resource_id: ID of the resource (required + to get a metric by name) + :type resource_id: str + + All other arguments are arguments are dedicated to custom aggregation + method passed as-is to the Gnocchi. + """ + + if isinstance(start, datetime.datetime): + start = start.isoformat() + if isinstance(end, datetime.datetime): + end = end.isoformat() + + params = dict(start=start, end=end, aggregation=aggregation) + params.update(kwargs) + if resource_id is None: + self._ensure_metric_is_uuid(metric) + url = self.client._build_url("metric/%s/measures" % metric) + else: + url = self.client._build_url( + "resource/generic/%s/metric/%s/measures" % ( + resource_id, metric)) + return self.client.api.get(url, params=params).json() diff --git a/gnocchiclient/v1/metric_cli.py b/gnocchiclient/v1/metric_cli.py index 1c9c609..a8f99b7 100644 --- a/gnocchiclient/v1/metric_cli.py +++ b/gnocchiclient/v1/metric_cli.py @@ -85,3 +85,56 @@ class CliMetricDelete(command.Command): def take_action(self, parsed_args): self.app.client.metric.delete(metric=parsed_args.metric, resource_id=parsed_args.resource_id) + + +class CliMeasuresGet(lister.Lister): + COLS = ('timestamp', 'granularity', 'value') + + def get_parser(self, prog_name): + parser = super(CliMeasuresGet, self).get_parser(prog_name) + parser.add_argument("metric", + help="ID or name of the metric") + parser.add_argument("resource_id", nargs='?', + help="ID of the resource") + 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") + return parser + + def take_action(self, parsed_args): + measures = self.app.client.metric.get_measures( + metric=parsed_args.metric, + resource_id=parsed_args.resource_id, + aggregation=parsed_args.aggregation, + start=parsed_args.start, + end=parsed_args.end, + ) + return self.COLS, measures + + +class CliMeasuresAdd(command.Command): + def measure(self, measure): + timestamp, __, value = measure.rpartition("@") + return {'timestamp': timestamp, 'value': float(value)} + + def get_parser(self, prog_name): + parser = super(CliMeasuresAdd, self).get_parser(prog_name) + parser.add_argument("metric", + help="ID or name of the metric") + parser.add_argument("resource_id", nargs='?', + help="ID of the resource") + parser.add_argument("-m", "--measure", action='append', + required=True, type=self.measure, + help=("timestamp and value of a measure " + "separated with a '@'")) + return parser + + def take_action(self, parsed_args): + self.app.client.metric.add_measures( + metric=parsed_args.metric, + resource_id=parsed_args.resource_id, + measures=parsed_args.measure, + ) diff --git a/setup-tests.sh b/setup-tests.sh index 923ffc6..95d6979 100755 --- a/setup-tests.sh +++ b/setup-tests.sh @@ -39,6 +39,7 @@ policy_file = ${GNOCCHI_DATA}/policy.json [api] middlewares = [storage] +metric_processing_delay = 1 file_basepath = ${GNOCCHI_DATA} driver = file coordination_url = file://${GNOCCHI_DATA} diff --git a/setup.cfg b/setup.cfg index 2c4d043..869fe72 100644 --- a/setup.cfg +++ b/setup.cfg @@ -47,6 +47,8 @@ gnocchi.cli.v1 = metric_show = gnocchiclient.v1.metric_cli:CliMetricShow metric_create = gnocchiclient.v1.metric_cli:CliMetricCreate metric_delete = gnocchiclient.v1.metric_cli:CliMetricDelete + measures_get = gnocchiclient.v1.metric_cli:CliMeasuresGet + measures_add = gnocchiclient.v1.metric_cli:CliMeasuresAdd keystoneclient.auth.plugin = gnocchi-noauth = gnocchiclient.noauth:GnocchiNoAuthPlugin