git-harry 239bfd30c6 Add devices-list to support /v1/devices
This commit adds a new command, 'craton device-list', to support the
endpoint /v1/devices. Currently the endpoint only supports GET requests.

The command supports filtering by cloud, cell, region, parent and active
status. In addition, where a parent is specified, it is also possible to
request descendants.

The API response body is of the form:

    {
        "devices: {
            "hosts": [
            ],
            "network-devices": [
            ],
        },
        "links": [
        ],
    }

This object differs, from the other response bodies that return
collections, in that devices is not an array but is instead an object
whose values are arrays. This difference has necessitated modifying the
list and pagination functionality to support nested data.

Change-Id: I7cdec9935a360dae3910802f210ab9341ef7a696
Closes-bug: 1668705
2017-03-03 08:41:13 +00:00

286 lines
9.6 KiB
Python

# -*- coding: utf-8 -*-
# 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.
"""Craton-specific session details."""
from itertools import chain
import logging
from keystoneauth1 import session as ksa_session
from requests import exceptions as requests_exc
import cratonclient
from cratonclient import auth
from cratonclient import exceptions as exc
LOG = logging.getLogger(__name__)
class Session(object):
"""Management class to allow different types of sessions to be used.
If an instance of Craton is deployed with Keystone Middleware, this allows
for a keystoneauth session to be used so authentication will happen
immediately.
"""
def __init__(self, session=None, username=None, token=None,
project_id=None):
"""Initialize our Session.
:param session:
The session instance to use as an underlying HTTP transport. If
not provided, we will create a keystoneauth1 Session object.
:param str username:
The username of the person authenticating against the API.
:param str token:
The authentication token of the user authenticating.
:param str project_id:
The user's project id in Craton.
"""
if session is None:
_auth = auth.CratonAuth(
username=username,
project_id=project_id,
token=token,
)
session = ksa_session.Session(auth=_auth)
self._session = session
self._session.user_agent = 'python-cratonclient/{0}'.format(
cratonclient.__version__)
def delete(self, url, **kwargs):
"""Make a DELETE request with url and optional parameters.
See the :meth:`Session.request` documentation for more details.
.. code-block:: python
>>> from cratonclient import session as craton
>>> session = craton.Session(
... username='demo',
... token='p@$$w0rd',
... project_id='1',
... )
>>> response = session.delete('http://example.com')
"""
return self.request('DELETE', url, **kwargs)
def get(self, url, **kwargs):
"""Make a GET request with url and optional parameters.
See the :meth:`Session.request` documentation for more details.
.. code-block:: python
>>> from cratonclient import session as craton
>>> session = craton.Session(
... username='demo',
... token='p@$$w0rd',
... project_id='1',
... )
>>> response = session.get('http://example.com')
"""
return self.request('GET', url, **kwargs)
def head(self, url, **kwargs):
"""Make a HEAD request with url and optional parameters.
See the :meth:`Session.request` documentation for more details.
.. code-block:: python
>>> from cratonclient import session as craton
>>> session = craton.Session(
... username='demo',
... token='p@$$w0rd',
... project_id='1',
... )
>>> response = session.head('http://example.com')
"""
return self.request('HEAD', url, **kwargs)
def options(self, url, **kwargs):
"""Make an OPTIONS request with url and optional parameters.
See the :meth:`Session.request` documentation for more details.
.. code-block:: python
>>> from cratonclient import session as craton
>>> session = craton.Session(
... username='demo',
... token='p@$$w0rd',
... project_id='1',
... )
>>> response = session.options('http://example.com')
"""
return self.request('OPTIONS', url, **kwargs)
def post(self, url, **kwargs):
"""Make a POST request with url and optional parameters.
See the :meth:`Session.request` documentation for more details.
.. code-block:: python
>>> from cratonclient import session as craton
>>> session = craton.Session(
... username='demo',
... token='p@$$w0rd',
... project_id='1',
... )
>>> response = session.post(
... 'http://example.com',
... data=b'foo',
... headers={'Content-Type': 'text/plain'},
... )
"""
return self.request('POST', url, **kwargs)
def put(self, url, **kwargs):
"""Make a PUT request with url and optional parameters.
See the :meth:`Session.request` documentation for more details.
.. code-block:: python
>>> from cratonclient import session as craton
>>> session = craton.Session(
... username='demo',
... token='p@$$w0rd',
... project_id='1',
... )
>>> response = session.put(
... 'http://example.com',
... data=b'foo',
... headers={'Content-Type': 'text/plain'},
... )
"""
return self.request('PUT', url, **kwargs)
def patch(self, url, **kwargs):
"""Make a PATCH request with url and optional parameters.
See the :meth:`Session.request` documentation for more details.
.. code-block:: python
>>> from cratonclient import session as craton
>>> session = craton.Session(
... username='demo',
... token='p@$$w0rd',
... project_id='1',
... )
>>> response = session.put(
... 'http://example.com',
... data=b'foo',
... headers={'Content-Type': 'text/plain'},
... )
>>> response = session.patch(
... 'http://example.com',
... data=b'bar',
... headers={'Content-Type': 'text/plain'},
... )
"""
return self.request('PATCH', url, **kwargs)
def request(self, method, url, **kwargs):
"""Make a request with a method, url, and optional parameters.
See also: python-requests.org for documentation of acceptable
parameters.
.. code-block:: python
>>> from cratonclient import session as craton
>>> session = craton.Session(
... username='demo',
... token='p@$$w0rd',
... project_id='1',
... )
>>> response = session.request('GET', 'http://example.com')
"""
kwargs.setdefault('endpoint_filter',
{'service_type': 'fleet_management'})
try:
response = self._session.request(
method=method,
url=url,
**kwargs
)
except requests_exc.HTTPError as err:
raise exc.HTTPError(exception=err, response=err.response)
# NOTE(sigmavirus24): The ordering of Timeout before ConnectionError
# is important on requests 2.x. The ConnectTimeout exception inherits
# from both ConnectionError and Timeout. To catch both connect and
# read timeouts similarly, we need to catch this one first.
except requests_exc.Timeout as err:
raise exc.Timeout(exception=err)
except requests_exc.ConnectionError as err:
raise exc.ConnectionFailed(exception=err)
if response.status_code >= 400:
raise exc.error_from(response)
return response
def paginate(self, url, items_key, autopaginate=True, nested=False,
**kwargs):
"""Make a GET request to a paginated resource.
If :param:`autopaginate` is set to ``True``, this will automatically
handle finding and retrieving the next page of items.
.. code-block:: python
>>> from cratonclient import session as craton
>>> session = craton.Session(
... username='demo',
... token='p@##w0rd',
... project_id='84363597-721c-4068-9731-8824692b51bb',
... )
>>> url = 'https://example.com/v1/hosts'
>>> for response in session.paginate(url, items_key='hosts'):
... print("Received status {}".format(response.status_code))
... print("Received {} items".format(len(items)))
:param bool autopaginate:
Determines whether or not this method continues requesting items
automatically after the first page.
"""
get_items = True
while get_items:
response = self.get(url, **kwargs)
json_body = response.json()
if nested:
items = list(chain(*json_body[items_key].values()))
else:
items = json_body[items_key]
yield response, items
links = json_body['links']
url = _find_next_link(links)
kwargs = {}
get_items = url and autopaginate and len(items) > 0
def _find_next_link(links):
for link in links:
if link['rel'] == 'next':
return link['href']
return None