
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
286 lines
9.6 KiB
Python
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
|