From 0d180c360d4ad34f44028870453122dd14377f61 Mon Sep 17 00:00:00 2001 From: Mehdi Abaakouk Date: Sun, 30 Aug 2015 19:31:02 +0200 Subject: [PATCH] Write the base of the documentation --- .gitignore | 3 + README.rst | 9 +- doc/source/api.rst | 27 ++++ doc/source/conf.py | 42 +++++- doc/source/index.rst | 20 ++- doc/source/readme.rst | 1 - doc/source/shell.rst | 67 +++++++++ doc/source/usage.rst | 7 - gnocchiclient/v1/client.py | 19 ++- gnocchiclient/v1/resource.py | 232 +++++++++----------------------- gnocchiclient/v1/resourcecli.py | 170 +++++++++++++++++++++++ setup.cfg | 12 +- 12 files changed, 415 insertions(+), 194 deletions(-) create mode 100644 doc/source/api.rst delete mode 100644 doc/source/readme.rst create mode 100644 doc/source/shell.rst delete mode 100644 doc/source/usage.rst create mode 100644 gnocchiclient/v1/resourcecli.py diff --git a/.gitignore b/.gitignore index e625780..0d7d16b 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,6 @@ ChangeLog *~ .*.swp .*sw? + +# generated docs +doc/source/ref/ diff --git a/README.rst b/README.rst index 0d1fde0..dae3966 100644 --- a/README.rst +++ b/README.rst @@ -2,11 +2,12 @@ python-gnocchiclient =============================== -Python client library for Gnocchi +Python bindings to the OpenStack Gnocchi API -Please feel here a long description which must be at least 3 lines wrapped on -80 cols, so that distribution package maintainers can use it in their packages. -Note that this is a hard requirement. +This is a client for OpenStack gnocchi API. There's :doc:`a Python API +` (the :mod:`gnocchiclient` module), and a :doc:`command-line script +` (installed as :program:`gnocchi`). Each implements the entire +OpenStack Gnocchi API. * Free software: Apache license * Documentation: http://docs.openstack.org/developer/python-gnocchiclient diff --git a/doc/source/api.rst b/doc/source/api.rst new file mode 100644 index 0000000..3250669 --- /dev/null +++ b/doc/source/api.rst @@ -0,0 +1,27 @@ +The :mod:`gnocchiclient` Python API +=================================== + +.. module:: gnocchiclient + :synopsis: A client for the Gnocchi API. + +.. currentmodule:: gnocchiclient + +Usage +----- + +To use python-gnocchiclient in a project:: + + >>> from gnocchiclient.v1 import client + >>> gnocchi = client.Client(...) + >>> gnocchi.resource.list("instance") + +Reference +--------- + +For more information, see the reference: + +.. toctree:: + :maxdepth: 2 + + ref/v1/index + diff --git a/doc/source/conf.py b/doc/source/conf.py index 3c4cdc4..2039375 100755 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -15,7 +15,47 @@ import os import sys -sys.path.insert(0, os.path.abspath('../..')) +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) +ROOT = os.path.abspath(os.path.join(BASE_DIR, "..", "..")) + +sys.path.insert(0, ROOT) +sys.path.insert(0, BASE_DIR) + + +def gen_ref(ver, title, names): + refdir = os.path.join(BASE_DIR, "ref") + pkg = "gnocchiclient" + if ver: + pkg = "%s.%s" % (pkg, ver) + refdir = os.path.join(refdir, ver) + if not os.path.exists(refdir): + os.makedirs(refdir) + idxpath = os.path.join(refdir, "index.rst") + with open(idxpath, "w") as idx: + idx.write(("%(title)s\n" + "%(signs)s\n" + "\n" + ".. toctree::\n" + " :maxdepth: 1\n" + "\n") % {"title": title, "signs": "=" * len(title)}) + for name in names: + idx.write(" %s\n" % name) + rstpath = os.path.join(refdir, "%s.rst" % name) + with open(rstpath, "w") as rst: + rst.write(("%(title)s\n" + "%(signs)s\n" + "\n" + ".. automodule:: %(pkg)s.%(name)s\n" + " :members:\n" + " :undoc-members:\n" + " :show-inheritance:\n" + " :noindex:\n") + % {"title": name.capitalize(), + "signs": "=" * len(name), + "pkg": pkg, "name": name}) + +gen_ref("v1", "Version 1 API", ["client", "resource"]) + # -- General configuration ---------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be diff --git a/doc/source/index.rst b/doc/source/index.rst index 222e2ef..39c713e 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -3,17 +3,31 @@ You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -Welcome to python-gnocchiclient's documentation! +Python bindings to the Gnocchi API ======================================================== +This is a client for gnocchi API. There's :doc:`a Python API +` (the :mod:`gnocchiclient` module), and a :doc:`command-line script +` (installed as :program:`gnocchi`). Each implements the entire +Gnocchi API. + +.. seealso:: + + You may want to read the `Gnocchi Developer Guide`__ -- the overview, at + least -- to get an idea of the concepts. By understanding the concepts + this library should make more sense. + + __ http://docs.openstack.org/developer/gnocchi/ + + Contents: .. toctree:: :maxdepth: 2 - readme installation - usage + shell + api contributing Indices and tables diff --git a/doc/source/readme.rst b/doc/source/readme.rst deleted file mode 100644 index a6210d3..0000000 --- a/doc/source/readme.rst +++ /dev/null @@ -1 +0,0 @@ -.. include:: ../../README.rst diff --git a/doc/source/shell.rst b/doc/source/shell.rst new file mode 100644 index 0000000..e11c4aa --- /dev/null +++ b/doc/source/shell.rst @@ -0,0 +1,67 @@ +The :program:`gnocchi` shell utility +========================================= + +.. program:: gnocchi +.. highlight:: bash + +The :program:`gnocchi` shell utility interacts with Gnocchi API +from the command line. It supports the entirety of the Gnocchi API. + +You'll need to provide :program:`gnocchi` with your OpenStack credentials. +You can do this with the :option:`--os-username`, :option:`--os-password`, +:option:`--os-tenant-id` and :option:`--os-auth-url` options, but it's easier to +just set them as environment variables: + +.. envvar:: OS_USERNAME + + Your OpenStack username. + +.. envvar:: OS_PASSWORD + + Your password. + +.. envvar:: OS_TENANT_NAME + + Project to work on. + +.. envvar:: OS_AUTH_URL + + The OpenStack auth server URL (keystone). + +For example, in Bash you would use:: + + export OS_USERNAME=user + export OS_PASSWORD=pass + export OS_TENANT_NAME=myproject + export OS_AUTH_URL=http://auth.example.com:5000/v2.0 + +The command line tool will attempt to reauthenticate using your provided credentials +for every request. You can override this behavior by manually supplying an auth +token using :option:`--os-gnocchi-url` and :option:`--os-auth-token`. You can alternatively +set these environment variables:: + + export GNOCCHI_ENDPOINT=http://gnocchi.example.org:8041 + export OS_AUTH_TOKEN=3bcc3d3a03f44e3d8377f9247b0ad155 + +From there, all shell commands take the form:: + + gnocchi [arguments...] + +Run :program:`gnocchi help` to get a full list of all possible commands, +and run :program:`gnocchi help ` to get detailed help for that +command. + +Examples +-------- + +Create a resource:: + + gnocch resource create instance --attribute id:5a301761-f78b-46e2-8900-8b4f6fe6675a --attribute project_id:eba5c38f-c3dd-4d9c-9235-32d430471f94 --metric temperature:high + +List resources:: + + gnocchi resource list instance + +Search of resources:: + + gnocchi resource search -q "project_id=5a301761-f78b-46e2-8900-8b4f6fe6675a and not (name like '%foobar%' or name='my_resource')" diff --git a/doc/source/usage.rst b/doc/source/usage.rst deleted file mode 100644 index 1915af2..0000000 --- a/doc/source/usage.rst +++ /dev/null @@ -1,7 +0,0 @@ -======== -Usage -======== - -To use python-gnocchiclient in a project:: - - import gnocchiclient diff --git a/gnocchiclient/v1/client.py b/gnocchiclient/v1/client.py index abffa18..584e8b1 100644 --- a/gnocchiclient/v1/client.py +++ b/gnocchiclient/v1/client.py @@ -21,12 +21,21 @@ from gnocchiclient.v1 import resource class Client(object): """Client for the Gnocchi v1 API. - :param string auth: An keystoneclient authentication plugin to - authenticate the session with + :param string auth: An optional keystoneclient authentication plugin + to authenticate the session with :type auth: :py:class:`keystoneclient.auth.base.BaseAuthPlugin` + :param endpoint: The optional Gnocchi API endpoint + :type endpoint: str + :param interface: The endpoint interface ('public', 'internal', 'admin') + :type interface: str + :param region_name: The keystone region name + :type region_name: str + :param \*\*kwargs: Any option supported by + :py:class:`keystoneclient.session.Session` + """ - VERSION = "v1" + _VERSION = "v1" def __init__(self, auth=None, endpoint=None, interface=None, region_name=None, **kwargs): @@ -45,7 +54,7 @@ class Client(object): region_name=self.region_name) return self._endpoint - def url(self, url_suffix): + def _build_url(self, url_suffix): return "%s/%s/%s" % (self.endpoint.rstrip("/"), - self.VERSION, + self._VERSION, url_suffix) diff --git a/gnocchiclient/v1/resource.py b/gnocchiclient/v1/resource.py index 13af5d5..9e63739 100644 --- a/gnocchiclient/v1/resource.py +++ b/gnocchiclient/v1/resource.py @@ -1,5 +1,3 @@ -# Copyright 2012 OpenStack Foundation -# All Rights Reserved. # # 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 @@ -13,203 +11,103 @@ # License for the specific language governing permissions and limitations # under the License. -import uuid - -from cliff import command -from cliff import lister -from cliff import show from oslo_serialization import jsonutils -from gnocchiclient import utils from gnocchiclient.v1 import base class ResourceManager(base.Manager): def list(self, resource_type="generic", details=False, history=False): + """List resources + + :param resource_type: Type of the resource + :type resource_type: str + :param details: Show all attributes of resources + :type details: bool + :param history: Show the history of resources + :type history: bool + """ details = "true" if details else "false" history = "true" if history else "false" - url = self.client.url("resource/%s?details=%s&history=%s" % ( + url = self.client._build_url("resource/%s?details=%s&history=%s" % ( resource_type, details, history)) return self.client.api.get(url).json() def get(self, resource_type, resource_id): - url = self.client.url("resource/%s/%s" % ( + """Get a resource + + :param resource_type: Type of the resource + :type resource_type: str + :param resource_id: ID of the resource + :type resource_id: str + """ + url = self.client._build_url("resource/%s/%s" % ( resource_type, resource_id)) return self.client.api.get(url).json() def create(self, resource_type, resource): - url = self.client.url("resource/%s" % resource_type) + """Create a resource + + :param resource_type: Type of the resource + :type resource_type: str + :param resource: Attribute of the resource + :type resource: dict + """ + url = self.client._build_url("resource/%s" % resource_type) return self.client.api.post( url, headers={'Content-Type': "application/json"}, data=jsonutils.dumps(resource)).json() def update(self, resource_type, resource_id, resource): - url = self.client.url("resource/%s/%s" % (resource_type, resource_id)) + """Update a resource + + :param resource_type: Type of the resource + :type resource_type: str + :param resource_id: ID of the resource + :type resource_id: str + :param resource: Attribute of the resource + :type resource: dict + """ + + url = self.client._build_url("resource/%s/%s" % (resource_type, + resource_id)) return self.client.api.patch( url, headers={'Content-Type': "application/json"}, data=jsonutils.dumps(resource)).json() def delete(self, resource_id): - url = self.client.url("resource/generic/%s" % (resource_id)) + """Delete a resource + + :param resource_id: ID of the resource + :type resource_id: str + """ + url = self.client._build_url("resource/generic/%s" % (resource_id)) self.client.api.delete(url) - def search(self, resource_type="generic", details=False, history=False, - request=None): + def search(self, resource_type="generic", request=None, details=False, + history=False): + """List resources + + :param resource_type: Type of the resource + :param resource_type: str + :param request: The search request dictionary + :type resource_type: dict + :param details: Show all attributes of resources + :type details: bool + :param history: Show the history of resources + :type history: bool + + See Gnocchi REST API documentation for the format + of *request dictionary* + http://docs.openstack.org/developer/gnocchi/rest.html#searching-for-resources + """ + request = request or {} details = "true" if details else "false" history = "true" if history else "false" - url = self.client.url("/search/resource/%s?details=%s&history=%s" % ( - resource_type, details, history)) + url = self.client._build_url( + "/search/resource/%s?details=%s&history=%s" % ( + resource_type, details, history)) return self.client.api.post( url, headers={'Content-Type': "application/json"}, data=jsonutils.dumps(request)).json() - - -class CliResourceList(lister.Lister): - COLS = ('id', 'type', - 'project_id', 'user_id', - 'started_at', 'ended_at', - 'revision_start', 'revision_end') - - def get_parser(self, prog_name): - parser = super(CliResourceList, self).get_parser(prog_name) - parser.add_argument("--details", action='store_true', - help="Show all attributes of generic resources"), - parser.add_argument("--history", action='store_true', - help="Show history of the resources"), - parser.add_argument("resource_type", - default="generic", - nargs='?', - help="Type of resource") - return parser - - def take_action(self, parsed_args): - resources = self.app.client.resource.list( - resource_type=parsed_args.resource_type, - details=parsed_args.details, - history=parsed_args.history) - return self.COLS, [self._resource2tuple(r) for r in resources] - - @classmethod - def _resource2tuple(cls, resource): - return tuple([resource[k] for k in cls.COLS]) - - -class CliResourceSearch(CliResourceList): - def get_parser(self, prog_name): - parser = super(CliResourceSearch, self).get_parser(prog_name) - parser.add_argument("-q", "--query", - help="Query"), - return parser - - def take_action(self, parsed_args): - resources = self.app.client.resource.search( - resource_type=parsed_args.resource_type, - details=parsed_args.details, - history=parsed_args.history, - request=utils.search_query_builder(parsed_args.query)) - return self.COLS, [self._resource2tuple(r) for r in resources] - - -def normalize_metrics(res): - res['metrics'] = "\n".join(sorted( - ["%s: %s" % (name, _id) - for name, _id in res['metrics'].items()])) - - -class CliResourceShow(show.ShowOne): - def get_parser(self, prog_name): - parser = super(CliResourceShow, self).get_parser(prog_name) - parser.add_argument("resource_type", - default="generic", - nargs='?', - help="Type of resource") - parser.add_argument("resource_id", - help="ID of a resource") - return parser - - def take_action(self, parsed_args): - res = self.app.client.resource.get( - resource_type=parsed_args.resource_type, - resource_id=parsed_args.resource_id) - normalize_metrics(res) - return self.dict2columns(res) - - -class CliResourceCreate(show.ShowOne): - def get_parser(self, prog_name): - parser = super(CliResourceCreate, self).get_parser(prog_name) - parser.add_argument("resource_type", - default="generic", - nargs='?', - help="Type of resource") - parser.add_argument("-a", "--attribute", action='append', - help=("name and value of a attribute " - "separated with a ':'")) - parser.add_argument("-m", "--metric", action='append', - help=("To add a metric use 'name:id' or " - "'name:archive_policy_name'. " - "To remove a metric use 'name:-'.")) - return parser - - def _resource_from_args(self, parsed_args): - resource = {} - if parsed_args.attribute: - for attr in parsed_args.attribute: - attr, __, value = attr.partition(":") - resource[attr] = value - if parsed_args.metric: - rid = getattr(parsed_args, 'resource_id', None) - if rid: - r = self.app.client.resource.get(parsed_args.resource_type, - parsed_args.resource_id) - default = r['metrics'] - else: - default = {} - resource['metrics'] = default - for metric in parsed_args.metric: - name, __, value = metric.partition(":") - if value == '-' or not value: - resource['metrics'].pop(name, None) - else: - try: - value = uuid.UUID(value) - except ValueError: - value = {'archive_policy_name': value} - resource['metrics'][name] = value - return resource - - def take_action(self, parsed_args): - resource = self._resource_from_args(parsed_args) - res = self.app.client.resource.create( - resource_type=parsed_args.resource_type, resource=resource) - normalize_metrics(res) - return self.dict2columns(res) - - -class CliResourceUpdate(CliResourceCreate): - def get_parser(self, prog_name): - parser = super(CliResourceUpdate, self).get_parser(prog_name) - parser.add_argument("resource_id", - help="ID of the resource") - return parser - - def take_action(self, parsed_args): - resource = self._resource_from_args(parsed_args) - res = self.app.client.resource.update( - resource_type=parsed_args.resource_type, - resource_id=parsed_args.resource_id, - resource=resource) - normalize_metrics(res) - return self.dict2columns(res) - - -class CliResourceDelete(command.Command): - def get_parser(self, prog_name): - parser = super(CliResourceDelete, self).get_parser(prog_name) - parser.add_argument("resource_id", - help="ID of the resource") - return parser - - def take_action(self, parsed_args): - self.app.client.resource.delete(parsed_args.resource_id) diff --git a/gnocchiclient/v1/resourcecli.py b/gnocchiclient/v1/resourcecli.py new file mode 100644 index 0000000..ca775a7 --- /dev/null +++ b/gnocchiclient/v1/resourcecli.py @@ -0,0 +1,170 @@ +# +# 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. + +import uuid + +from cliff import command +from cliff import lister +from cliff import show + +from gnocchiclient import utils + + +class CliResourceList(lister.Lister): + COLS = ('id', 'type', + 'project_id', 'user_id', + 'started_at', 'ended_at', + 'revision_start', 'revision_end') + + def get_parser(self, prog_name): + parser = super(CliResourceList, self).get_parser(prog_name) + parser.add_argument("--details", action='store_true', + help="Show all attributes of generic resources"), + parser.add_argument("--history", action='store_true', + help="Show history of the resources"), + parser.add_argument("resource_type", + default="generic", + nargs='?', + help="Type of resource") + return parser + + def take_action(self, parsed_args): + resources = self.app.client.resource.list( + resource_type=parsed_args.resource_type, + details=parsed_args.details, + history=parsed_args.history) + return self.COLS, [self._resource2tuple(r) for r in resources] + + @classmethod + def _resource2tuple(cls, resource): + return tuple([resource[k] for k in cls.COLS]) + + +class CliResourceSearch(CliResourceList): + def get_parser(self, prog_name): + parser = super(CliResourceSearch, self).get_parser(prog_name) + parser.add_argument("-q", "--query", + help="Query"), + return parser + + def take_action(self, parsed_args): + resources = self.app.client.resource.search( + resource_type=parsed_args.resource_type, + details=parsed_args.details, + history=parsed_args.history, + request=utils.search_query_builder(parsed_args.query)) + return self.COLS, [self._resource2tuple(r) for r in resources] + + +def normalize_metrics(res): + res['metrics'] = "\n".join(sorted( + ["%s: %s" % (name, _id) + for name, _id in res['metrics'].items()])) + + +class CliResourceShow(show.ShowOne): + def get_parser(self, prog_name): + parser = super(CliResourceShow, self).get_parser(prog_name) + parser.add_argument("resource_type", + default="generic", + nargs='?', + help="Type of resource") + parser.add_argument("resource_id", + help="ID of a resource") + return parser + + def take_action(self, parsed_args): + res = self.app.client.resource.get( + resource_type=parsed_args.resource_type, + resource_id=parsed_args.resource_id) + normalize_metrics(res) + return self.dict2columns(res) + + +class CliResourceCreate(show.ShowOne): + def get_parser(self, prog_name): + parser = super(CliResourceCreate, self).get_parser(prog_name) + parser.add_argument("resource_type", + default="generic", + nargs='?', + help="Type of resource") + parser.add_argument("-a", "--attribute", action='append', + help=("name and value of a attribute " + "separated with a ':'")) + parser.add_argument("-m", "--metric", action='append', + help=("To add a metric use 'name:id' or " + "'name:archive_policy_name'. " + "To remove a metric use 'name:-'.")) + return parser + + def _resource_from_args(self, parsed_args): + resource = {} + if parsed_args.attribute: + for attr in parsed_args.attribute: + attr, __, value = attr.partition(":") + resource[attr] = value + if parsed_args.metric: + rid = getattr(parsed_args, 'resource_id', None) + if rid: + r = self.app.client.resource.get(parsed_args.resource_type, + parsed_args.resource_id) + default = r['metrics'] + else: + default = {} + resource['metrics'] = default + for metric in parsed_args.metric: + name, __, value = metric.partition(":") + if value == '-' or not value: + resource['metrics'].pop(name, None) + else: + try: + value = uuid.UUID(value) + except ValueError: + value = {'archive_policy_name': value} + resource['metrics'][name] = value + return resource + + def take_action(self, parsed_args): + resource = self._resource_from_args(parsed_args) + res = self.app.client.resource.create( + resource_type=parsed_args.resource_type, resource=resource) + normalize_metrics(res) + return self.dict2columns(res) + + +class CliResourceUpdate(CliResourceCreate): + def get_parser(self, prog_name): + parser = super(CliResourceUpdate, self).get_parser(prog_name) + parser.add_argument("resource_id", + help="ID of the resource") + return parser + + def take_action(self, parsed_args): + resource = self._resource_from_args(parsed_args) + res = self.app.client.resource.update( + resource_type=parsed_args.resource_type, + resource_id=parsed_args.resource_id, + resource=resource) + normalize_metrics(res) + return self.dict2columns(res) + + +class CliResourceDelete(command.Command): + def get_parser(self, prog_name): + parser = super(CliResourceDelete, self).get_parser(prog_name) + parser.add_argument("resource_id", + help="ID of the resource") + return parser + + def take_action(self, parsed_args): + self.app.client.resource.delete(parsed_args.resource_id) diff --git a/setup.cfg b/setup.cfg index 52b4874..d598d63 100644 --- a/setup.cfg +++ b/setup.cfg @@ -28,12 +28,12 @@ console_scripts = gnocchi = gnocchiclient.shell:main gnocchi.cli.v1 = - resource_list = gnocchiclient.v1.resource:CliResourceList - resource_show = gnocchiclient.v1.resource:CliResourceShow - resource_search = gnocchiclient.v1.resource:CliResourceSearch - resource_create = gnocchiclient.v1.resource:CliResourceCreate - resource_update = gnocchiclient.v1.resource:CliResourceUpdate - resource_delete = gnocchiclient.v1.resource:CliResourceDelete + resource_list = gnocchiclient.v1.resourcecli:CliResourceList + resource_show = gnocchiclient.v1.resourcecli:CliResourceShow + resource_search = gnocchiclient.v1.resourcecli:CliResourceSearch + resource_create = gnocchiclient.v1.resourcecli:CliResourceCreate + resource_update = gnocchiclient.v1.resourcecli:CliResourceUpdate + resource_delete = gnocchiclient.v1.resourcecli:CliResourceDelete [build_sphinx] source-dir = doc/source