Retire python-cratonclient

Remove everything, add a README with explanation.

Change-Id: I9530985e91423a451f2f7d9f5a37d06de2b8a473
This commit is contained in:
Andreas Jaeger 2018-01-18 15:21:03 +01:00
parent adad8bdc7e
commit 98234f46ab
140 changed files with 8 additions and 49744 deletions

View File

@ -1,7 +0,0 @@
[run]
branch = True
source = cratonclient
omit = cratonclient/openstack/*
[report]
ignore_errors = True

55
.gitignore vendored
View File

@ -1,55 +0,0 @@
*.py[cod]
# C extensions
*.so
# Packages
*.egg*
*.egg-info
dist
build
eggs
parts
bin
var
sdist
develop-eggs
.installed.cfg
lib
lib64
# Installer logs
pip-log.txt
# Unit test / coverage reports
cover/
.coverage*
!.coveragerc
.tox
nosetests.xml
.testrepository
.venv
# Translations
*.mo
# Mr Developer
.mr.developer.cfg
.project
.pydevproject
# Complexity
output/*.html
output/*/index.html
# Sphinx
doc/build
# pbr generates these
AUTHORS
ChangeLog
# Editors
*~
.*.swp
.*sw?

View File

@ -1,4 +0,0 @@
[gerrit]
host=review.openstack.org
port=29418
project=openstack/python-cratonclient.git

View File

@ -1,3 +0,0 @@
# Format is:
# <preferred e-mail> <other e-mail 1>
# <preferred e-mail> <other e-mail 2>

View File

@ -1,7 +0,0 @@
[DEFAULT]
test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \
OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \
OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \
${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION
test_id_option=--load-list $IDFILE
test_list_option=--list

View File

@ -1,14 +0,0 @@
sudo: false
language: python
matrix:
include:
- python: 2.7
env: TOXENV=py27
- python: 3.5
env: TOXENV=py35
- env: TOXENV=pep8
install: pip install tox-travis
script: tox

View File

@ -1,17 +0,0 @@
If you would like to contribute to the development of OpenStack, you must
follow the steps in this page:
http://docs.openstack.org/infra/manual/developers.html
If you already have a good understanding of how the system works and your
OpenStack accounts are set up, you can skip to the development workflow
section of this documentation to learn how changes to OpenStack should be
submitted for review via the Gerrit tool:
http://docs.openstack.org/infra/manual/developers.html#development-workflow
Pull requests submitted through GitHub will be ignored.
Bugs should be filed on Launchpad, not GitHub:
https://bugs.launchpad.net/python-cratonclient

View File

@ -1,4 +0,0 @@
python-cratonclient Style Commandments
===============================================
Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/

176
LICENSE
View File

@ -1,176 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.

View File

@ -1,6 +0,0 @@
include AUTHORS
include ChangeLog
exclude .gitignore
exclude .gitreview
global-exclude *.pyc

View File

@ -1,19 +1,10 @@
===============================
python-cratonclient
===============================
This project is no longer maintained.
Craton API Client and Command-line Utility
The contents of this repository are still available in the Git
source code management system. To see the contents of this
repository before it reached its end of life, please check out the
previous commit with "git checkout HEAD^1".
Please fill 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.
* Free software: Apache license
* Documentation: https://python-cratonclient.readthedocs.io
* Source: http://git.openstack.org/cgit/openstack/python-cratonclient
* Bugs: http://bugs.launchpad.net/python-cratonclient
Features
--------
* TODO
For any further questions, please email
openstack-dev@lists.openstack.org or join #openstack-dev on
Freenode.

View File

@ -1,2 +0,0 @@
[python: **.py]

View File

@ -1,20 +0,0 @@
# -*- 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 API Client Library and Command-Line Application."""
import pbr.version
__version__ = pbr.version.VersionInfo(
'cratonclient').version_string()

View File

@ -1,198 +0,0 @@
# Copyright (c) 2016 Rackspace
#
# 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.
"""Module that simplifies and unifies authentication for Craton."""
from keystoneauth1.identity.v3 import password as ksa_password
from keystoneauth1 import plugin
from keystoneauth1 import session as ksa_session
from cratonclient import exceptions as exc
def craton_auth(username, token, project_id, verify=True):
"""Configure a cratonclient Session to authenticate to Craton.
This will create, configure, and return a Session object that will use
Craton's built-in authentication method.
:param str username:
The username with which to authentiate against the API.
:param str token:
The token with which to authenticate against the API.
:param str project_id:
The project ID that the user belongs to.
:param bool verify:
(Optional) Whether or not to verify HTTPS certificates provided by the
server. Default: True
:returns:
Configured cratonclient session.
:rtype:
cratonclient.session.Session
Example:
.. code-block:: python
from cratonclient import auth
from cratonclient.v1 import client
craton = client.Client(session=auth.craton_auth(
username='demo',
token='demo',
project_id='b9f10eca66ac4c279c139d01e65f96b4',
))
"""
auth_plugin = CratonAuth(
username=username,
token=token,
project_id=project_id,
)
return create_session_with(auth_plugin, verify)
def keystone_auth(auth_url, username, password, verify=True,
project_name=None, project_id=None,
project_domain_name=None, project_domain_id=None,
user_domain_name=None, user_domain_id=None,
**auth_parameters):
r"""Configure a cratonclient Session to authenticate with Keystone.
This will create, configure, and return a Session using thet appropriate
Keystone authentication plugin to be able to communicate and authenticate
to Craton.
.. note::
Presently, this function supports only V3 Password based
authentication to Keystone. We also do not validate that you specify
required attributes. For example, Keystone will require you provide
``project_name`` or ``project_id`` but we will not enforce whether or
not you've specified one.
:param str auth_url:
The URL of the Keystone instance to authenticate to.
:param str username:
The username with which we will authenticate to Keystone.
:param str password:
The password used to authenticate to Keystone.
:param str project_name:
(Optional) The name of the project the user belongs to.
:param str project_id:
(Optional) The ID of the project the user belongs to.
:param str project_domain_name:
(Optional) The name of the project's domain.
:param str project_domain_id:
(Optional) The ID of the project's domain.
:param str user_domain_name:
(Optional) The name of the user's domain.
:param str user_domain_id:
(Optional) The ID of the user's domain.
:param bool verify:
(Optional) Whether or not to verify HTTPS certificates provided by the
server. Default: True
:param \*\*auth_parameters:
Any extra authentication parameters used to authenticate to Keystone.
See the Keystone documentation for usage of:
- ``trust_id``
- ``domain_id``
- ``domain_name``
- ``reauthenticate``
:returns:
Configured cratonclient session.
:rtype:
cratonclient.session.Session
Example:
.. code-block:: python
from cratonclient import auth
from cratonclient.v1 import client
craton = client.Client(session=auth.keystone_auth(
auth_url='https://keystone.cloud.org/v3',
username='admin',
password='s3cr373p@55w0rd',
project_name='admin',
project_domain_name='Default',
user_domain_name='Default',
))
"""
password_auth = ksa_password.Password(
auth_url=auth_url,
username=username,
password=password,
project_id=project_id,
project_name=project_name,
project_domain_id=project_domain_id,
project_domain_name=project_domain_name,
user_domain_id=user_domain_id,
user_domain_name=user_domain_name,
**auth_parameters
)
return create_session_with(password_auth, verify)
def create_session_with(auth_plugin, verify):
"""Create a cratonclient Session with the specified auth and verify values.
:param auth_plugin:
The authentication plugin to use with the keystoneauth1 Session
object.
:type auth_plugin:
keystoneauth1.plugin.BaseAuthPlugin
:param bool verify:
Whether or not to verify HTTPS certificates provided by the server.
:returns:
Configured cratonclient session.
:rtype:
cratonclient.session.Session
"""
from cratonclient import session
return session.Session(session=ksa_session.Session(
auth=auth_plugin,
verify=verify,
))
class CratonAuth(plugin.BaseAuthPlugin):
"""Custom authentication plugin for keystoneauth1.
This is specifically for the case where we're not using Keystone for
authentication.
"""
def __init__(self, username, project_id, token):
"""Initialize our craton authentication class."""
self.username = username
self.project_id = project_id
self.token = token
def get_token(self, session, **kwargs):
"""Return our token."""
return self.token
def get_headers(self, session, **kwargs):
"""Return the craton authentication headers."""
headers = super(CratonAuth, self).get_headers(session, **kwargs)
if headers is None:
# NOTE(sigmavirus24): This means that the token must be None. We
# should not allow this to go further. We're using built-in Craton
# authentication (not authenticating against Keystone) so we will
# be unable to authenticate.
raise exc.UnableToAuthenticate()
headers['X-Auth-User'] = self.username
headers['X-Auth-Project'] = '{}'.format(self.project_id)
return headers

View File

@ -1 +0,0 @@
"""Common Craton common classes and functions."""

View File

@ -1,176 +0,0 @@
# -*- 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 CLI helper classes and functions."""
import functools
import json
import os
import sys
from oslo_utils import encodeutils
from oslo_utils import strutils
from cratonclient import exceptions as exc
def arg(*args, **kwargs):
"""Decorator for CLI args.
Example:
>>> @arg("name", help="Name of the new entity.")
... def entity_create(args):
... pass
"""
def _decorator(func):
"""Decorator definition."""
add_arg(func, *args, **kwargs)
return func
return _decorator
def add_arg(func, *args, **kwargs):
"""Bind CLI arguments to a shell.py `do_foo` function."""
if not hasattr(func, 'arguments'):
func.arguments = []
# NOTE(sirp): avoid dups that can occur when the module is shared across
# tests.
if (args, kwargs) not in func.arguments:
# Because of the semantics of decorator composition if we just append
# to the options list positional options will appear to be backwards.
func.arguments.insert(0, (args, kwargs))
def field_labels_from(attributes):
"""Generate a list of slightly more human readable field names.
This takes the list of fields/attributes on the object and makes them
easier to read.
:param list attributes:
The attribute names to convert. For example, ``["parent_id"]``.
:returns:
List of field names. For example ``["Parent Id"]``
:rtype:
list
Example:
>>> field_labels_from(["id", "name", "cloud_id"])
["Id", "Name", "Cloud Id"]
"""
return [field.replace('_', ' ').title() for field in attributes]
def handle_shell_exception(function):
"""Generic error handler for shell methods."""
@functools.wraps(function)
def wrapper(cc, args):
prop_map = {
"vars": "variables"
}
try:
function(cc, args)
except exc.ClientException as client_exc:
# NOTE(thomasem): All shell methods follow a similar pattern,
# so we can parse this name to get intended parts for
# messaging what went wrong to the end-user.
# The pattern is "do_<resource>_(<prop>_)<verb>", like
# do_project_show or do_project_vars_get, where <prop> is
# not guaranteed to be there, but will afford support for
# actions on some property of the resource.
parsed = function.__name__.split('_')
resource = parsed[1]
verb = parsed[-1]
prop = parsed[2] if len(parsed) > 3 else None
msg = 'Failed to {}'.format(verb)
if prop:
# NOTE(thomasem): Prop would be something like "vars" in
# "do_project_vars_get".
msg = '{} {}'.format(msg, prop_map.get(prop))
# NOTE(thomasem): Append resource and ClientException details
# to error message.
msg = '{} for {} {} due to "{}: {}"'.format(
msg, resource, args.id, client_exc.__class__,
encodeutils.exception_to_unicode(client_exc)
)
raise exc.CommandError(msg)
return wrapper
def env(*args, **kwargs):
"""Return the first environment variable set.
If all are empty, defaults to '' or keyword arg `default`.
"""
for arg in args:
value = os.environ.get(arg)
if value:
return value
return kwargs.get('default', '')
def convert_arg_value(v):
"""Convert different user inputs to normalized type."""
# NOTE(thomasem): Handle case where one wants to escape this value
# conversion using the format key='"value"'
if v.startswith('"'):
return v.strip('"')
lower_v = v.lower()
if strutils.is_int_like(v):
return int(v)
if strutils.is_valid_boolstr(lower_v):
return strutils.bool_from_string(lower_v)
if lower_v == 'null' or lower_v == 'none':
return None
try:
return float(v)
except ValueError:
pass
return v
def variable_updates(variables):
"""Derive list of expected variables for a resource and set them."""
update_variables = {}
delete_variables = set()
for variable in variables:
k, v = variable.split('=', 1)
if v:
update_variables[k] = convert_arg_value(v)
else:
delete_variables.add(k)
if not sys.stdin.isatty():
if update_variables or delete_variables:
raise exc.CommandError("Cannot use variable settings from both "
"stdin and command line arguments. Please "
"choose one or the other.")
update_variables = json.load(sys.stdin)
return (update_variables, list(delete_variables))
def variable_deletes(variables):
"""Delete a list of variables (by key) from a resource."""
if not sys.stdin.isatty():
if variables:
raise exc.CommandError("Cannot use variable settings from both "
"stdin and command line arguments. Please "
"choose one or the other.")
delete_variables = json.load(sys.stdin)
else:
delete_variables = variables
return delete_variables

View File

@ -1,263 +0,0 @@
# -*- 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.
"""Client for CRUD operations."""
import copy
from oslo_utils import strutils
class CRUDClient(object):
"""Class that handles the basic create, read, upload, delete workflow."""
key = ""
base_path = None
resource_class = None
def __init__(self, session, url, **extra_request_kwargs):
"""Initialize our Client with a session and base url."""
self.session = session
self.url = url.rstrip('/')
self.extra_request_kwargs = extra_request_kwargs
def build_url(self, path_arguments=None):
"""Build a complete URL from the url, base_path, and arguments.
A CRUDClient is constructed with the base URL, e.g.
.. code-block:: python
RegionManager(url='https://10.1.1.0:8080/v1', ...)
The child class of the CRUDClient may set the ``base_path``, e.g.,
.. code-block:: python
base_path = '/regions'
And its ``key``, e.g.,
.. code-block:: python
key = 'region'
And based on the ``path_arguments`` parameter we will construct a
complete URL. For example, if someone calls:
.. code-block:: python
self.build_url(path_arguments={'region_id': 1})
with the hypothetical values above, we would return
https://10.1.1.0:8080/v1/regions/1
Users can also override ``base_path`` in ``path_arguments``.
"""
if path_arguments is None:
path_arguments = {}
base_path = path_arguments.pop('base_path', None) or self.base_path
item_id = path_arguments.pop('{0}_id'.format(self.key), None)
url = self.url + base_path
if item_id is not None:
url += '/{0}'.format(item_id)
return url
def merge_request_arguments(self, request_kwargs, skip_merge):
"""Merge the extra request arguments into the per-request args."""
if skip_merge:
return
keys = set(self.extra_request_kwargs.keys())
missing_keys = keys.difference(request_kwargs.keys())
for key in missing_keys:
request_kwargs[key] = self.extra_request_kwargs[key]
def create(self, skip_merge=False, **kwargs):
"""Create a new item based on the keyword arguments provided."""
self.merge_request_arguments(kwargs, skip_merge)
url = self.build_url(path_arguments=kwargs)
response = self.session.post(url, json=kwargs)
return self.resource_class(self, response.json(), loaded=True)
def get(self, item_id=None, skip_merge=True, **kwargs):
"""Retrieve the item based on the keyword arguments provided."""
self.merge_request_arguments(kwargs, skip_merge)
kwargs.setdefault(self.key + '_id', item_id)
url = self.build_url(path_arguments=kwargs)
response = self.session.get(url)
return self.resource_class(self, response.json(), loaded=True)
def list(self, skip_merge=False, **kwargs):
"""Generate the items from this endpoint."""
autopaginate = kwargs.pop('autopaginate', True)
nested = kwargs.pop('nested', False)
self.merge_request_arguments(kwargs, skip_merge)
url = self.build_url(path_arguments=kwargs)
response_generator = self.session.paginate(
url,
autopaginate=autopaginate,
items_key=(self.key + 's'),
nested=nested,
params=kwargs,
)
for response, items in response_generator:
for item in items:
yield self.resource_class(self, item, loaded=True)
def update(self, item_id=None, skip_merge=True, **kwargs):
"""Update the item based on the keyword arguments provided."""
self.merge_request_arguments(kwargs, skip_merge)
kwargs.setdefault(self.key + '_id', item_id)
url = self.build_url(path_arguments=kwargs)
response = self.session.put(url, json=kwargs)
return self.resource_class(self, response.json(), loaded=True)
def delete(self, item_id=None, skip_merge=True, json=None, **kwargs):
"""Delete the item based on the keyword arguments provided."""
self.merge_request_arguments(kwargs, skip_merge)
kwargs.setdefault(self.key + '_id', item_id)
url = self.build_url(path_arguments=kwargs)
response = self.session.delete(url, params=kwargs, json=json)
if 200 <= response.status_code < 300:
return True
return False
def __repr__(self):
"""Return string representation of a Variable."""
return '%(class)s(%(session)s, %(url)s, %(extra_request_kwargs)s)' % \
{
"class": self.__class__.__name__,
"session": self.session,
"url": self.url,
"extra_request_kwargs": self.extra_request_kwargs,
}
# NOTE(sigmavirus24): Credit for this Resource object goes to the
# keystoneclient developers and contributors.
class Resource(object):
"""Base class for OpenStack resources (tenant, user, etc.).
This is pretty much just a bag for attributes.
"""
HUMAN_ID = False
NAME_ATTR = 'name'
subresource_managers = {}
def __init__(self, manager, info, loaded=False):
"""Populate and bind to a manager.
:param manager: BaseManager object
:param info: dictionary representing resource attributes
:param loaded: prevent lazy-loading if set to True
"""
self.manager = manager
self._info = info
self._add_details(info)
self._loaded = loaded
session = self.manager.session
subresource_base_url = self.manager.build_url(
{"{0}_id".format(self.manager.key): self.id}
)
for attribute, cls in self.subresource_managers.items():
setattr(self, attribute,
cls(session, subresource_base_url,
**self.manager.extra_request_kwargs))
def __repr__(self):
"""Return string representation of resource attributes."""
reprkeys = sorted(k
for k in self.__dict__.keys()
if k[0] != '_' and k != 'manager')
info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys)
return "<%s %s>" % (self.__class__.__name__, info)
@property
def human_id(self):
"""Human-readable ID which can be used for bash completion."""
if self.HUMAN_ID:
name = getattr(self, self.NAME_ATTR, None)
if name is not None:
return strutils.to_slug(name)
return None
def _add_details(self, info):
for (k, v) in info.items():
try:
setattr(self, k, v)
self._info[k] = v
except AttributeError: # nosec(cjschaef): we already defined the
# attribute on the class
pass
def __getattr__(self, k):
"""Checking attrbiute existence."""
if k not in self.__dict__:
# NOTE(bcwaldon): disallow lazy-loading if already loaded once
if not self.is_loaded():
self.get()
return self.__getattr__(k)
raise AttributeError(k)
else:
return self.__dict__[k]
def get(self):
"""Support for lazy loading details.
Some clients, such as novaclient have the option to lazy load the
details, details which can be loaded with this function.
"""
# set_loaded() first ... so if we have to bail, we know we tried.
self.set_loaded(True)
if not hasattr(self.manager, 'get'):
return
new = self.manager.get(self.id)
if new:
self._add_details(new._info)
self._add_details(
{'x_request_id': self.manager.client.last_request_id})
def __eq__(self, other):
"""Define equality for resources."""
if not isinstance(other, Resource):
return NotImplemented
# two resources of different types are not equal
if not isinstance(other, self.__class__):
return False
return self._info == other._info
def is_loaded(self):
"""Check if the resource has been loaded."""
return self._loaded
def set_loaded(self, val):
"""Set whether the resource has been loaded or not."""
self._loaded = val
def to_dict(self):
"""Return the resource as a dictionary."""
return copy.deepcopy(self._info)
def delete(self):
"""Delete the resource from the service."""
return self.manager.delete(self.id)

View File

@ -1,292 +0,0 @@
# -*- 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.
"""Exception classes and logic for cratonclient."""
class ClientException(Exception):
"""Base exception class for all exceptions in cratonclient."""
message = None
def __init__(self, message=None):
"""Initialize our exception instance with our class level message."""
if message is None:
if self.message is None:
message = self.__class__.__name__
else:
message = self.message
super(ClientException, self).__init__(message)
class UnableToAuthenticate(ClientException):
"""There are insufficient parameters for authentication."""
message = "Some of the parameters required to authenticate were missing."""
class Timeout(ClientException):
"""Catch-all class for connect and read timeouts from requests."""
message = 'Request timed out'
def __init__(self, message=None, **kwargs):
"""Initialize our Timeout exception.
This takes an optional keyword-only argument of
``original_exception``.
"""
self.original_exception = kwargs.pop('exception', None)
super(Timeout, self).__init__(message)
class HTTPError(ClientException):
"""Base exception class for all HTTP related exceptions in."""
message = "An error occurred while talking to the remote server."
status_code = None
def __init__(self, message=None, **kwargs):
"""Initialize our HTTPError instance.
Optional keyword-only arguments include:
- response: for the response generating the error
- original_exception: in the event that this is a requests exception
that we are re-raising.
"""
self.response = kwargs.pop('response', None)
self.original_exception = kwargs.pop('exception', None)
self.status_code = (self.status_code
or getattr(self.response, 'status_code', None))
super(HTTPError, self).__init__(message)
@property
def status_code(self):
"""Shim to provide a similar API to other OpenStack clients."""
return self.status_code
@status_code.setter
def status_code(self, code):
self.status_code = code
class CommandError(ClientException):
"""Client command was invalid or failed."""
message = "The command used was invalid or caused an error."""
class ConnectionFailed(HTTPError):
"""Connecting to the server failed."""
message = "An error occurred while connecting to the server."""
class HTTPClientError(HTTPError):
"""Base exception for client side errors (4xx status codes)."""
message = "Something went wrong with the request."
class BadRequest(HTTPClientError):
"""Client sent a malformed request."""
status_code = 400
message = "The request sent to the server was invalid."
class Unauthorized(HTTPClientError):
"""Client is unauthorized to access the resource in question."""
status_code = 401
message = ("The user has either provided insufficient parameters for "
"authentication or is not authorized to access this resource.")
class Forbidden(HTTPClientError):
"""Client is forbidden to access the resource."""
status_code = 403
message = ("The user was unable to access the resource because they are "
"forbidden.")
class NotFound(HTTPClientError):
"""Resource could not be found."""
status_code = 404
message = "The requested resource was not found."""
class MethodNotAllowed(HTTPClientError):
"""The request method is not supported."""
status_code = 405
message = "The method used in the request is not supported."
class NotAcceptable(HTTPClientError):
"""The requested resource can not respond with acceptable content.
Based on the Accept headers specified by the client, the resource can not
generate content that is an acceptable content-type.
"""
status_code = 406
message = "The resource can not return acceptable content."
class ProxyAuthenticationRequired(HTTPClientError):
"""The client must first authenticate itself with the proxy."""
status_code = 407
message = "The client must first authenticate itself with a proxy."
class Conflict(HTTPClientError):
"""The request presents a conflict."""
status_code = 409
message = "The request could not be processed due to a conflict."
class Gone(HTTPClientError):
"""The requested resource is no longer available.
The resource requested is no longer available and will not be available
again.
"""
status_code = 410
message = ("The resource requested is no longer available and will not be"
" available again.")
class LengthRequired(HTTPClientError):
"""The request did not specify a Content-Length header."""
status_code = 411
message = ("The request did not contain a Content-Length header but one"
" was required by the resource.")
class PreconditionFailed(HTTPClientError):
"""The server failed to meet one of the preconditions in the request."""
status_code = 412
message = ("The server failed to meet one of the preconditions in the "
"request.")
class RequestEntityTooLarge(HTTPClientError):
"""The request is larger than the server is willing or able to process."""
status_code = 413
message = ("The request is larger than the server is willing or able to "
"process.")
class RequestUriTooLong(HTTPClientError):
"""The URI provided was too long for the server to process."""
status_code = 414
message = "The URI provided was too long for the server to process."
class UnsupportedMediaType(HTTPClientError):
"""The request entity has a media type which is unsupported."""
status_code = 415
message = ("The request entity has a media type which is unsupported by "
"the server or resource.")
class RequestedRangeNotSatisfiable(HTTPClientError):
"""The requestor wanted a range but the server was unable to provide it."""
status_code = 416
message = ("The requestor wanted a range but the server was unable to "
"provide it.")
class UnprocessableEntity(HTTPClientError):
"""There were semantic errors in the request."""
status_code = 422
message = ("The request is of a valid content-type and structure but "
"semantically invalid.")
_4xx_classes = [
BadRequest,
Unauthorized,
Forbidden,
NotFound,
MethodNotAllowed,
NotAcceptable,
ProxyAuthenticationRequired,
Conflict,
Gone,
LengthRequired,
PreconditionFailed,
RequestEntityTooLarge,
RequestUriTooLong,
UnsupportedMediaType,
RequestedRangeNotSatisfiable,
UnprocessableEntity,
]
_4xx_codes = {cls.status_code: cls for cls in _4xx_classes}
class HTTPServerError(HTTPError):
"""The server encountered an error it could not recover from."""
message = "HTTP Server-side Error"
class InternalServerError(HTTPServerError):
"""The server encountered an error it could not recover from."""
status_code = 500
message = ("There was an internal server error that could not be recovered"
" from.")
_5xx_classes = [
InternalServerError,
# NOTE(sigmavirus24): Allow for future expansion
]
_5xx_codes = {cls.status_code: cls for cls in _5xx_classes}
def _error_class_from(status_code):
if 400 <= status_code < 500:
cls = _4xx_codes.get(status_code, HTTPClientError)
elif 500 <= status_code < 600:
cls = _5xx_codes.get(status_code, HTTPServerError)
else:
cls = HTTPError
return cls
def error_from(response):
"""Find an error code that matches a response status_code."""
cls = _error_class_from(response.status_code)
return cls(response=response)
def raise_from(exception):
"""Raise an exception from the keystoneauth1 exception."""
cls = _error_class_from(exception.http_status)
return cls(response=exception.response, exception=exception)

View File

@ -1,12 +0,0 @@
# 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.
"""Module containing built-in formatters for cratonclient CLI."""

View File

@ -1,107 +0,0 @@
# 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.
"""Base class implementation for formatting plugins."""
class Formatter(object):
"""Class that defines the formatter interface.
Instead of having to override and call up to this class's ``__init__``
method, we also provide an ``after_init`` method that can be implemented
to extend what happens on initialization.
.. attribute:: args
Parsed command-line arguments stored as an instance of
:class:`argparse.Namespace`.
"""
def __init__(self, parsed_args):
"""Instantiate our formatter with the parsed CLI arguments.
:param parsed_args:
The CLI arguments parsed by :mod:`argparse`.
:type parsed_args:
argparse.Namespace
"""
self.args = parsed_args
self.after_init()
def after_init(self):
"""Initialize the object further after ``__init__``."""
pass
def configure(self, *args, **kwargs):
"""Optional configuration of the plugin after instantiation."""
return self
def handle(self, item_to_format):
"""Handle a returned item from the cratonclient API.
cratonclient's API produces both single Resource objects as well as
generators of those objects. This method should be capable of handling
both.
Based on the type, this will either call ``handle_generator`` or
``handle_instance``. Subclasses must implement both of those methods.
:returns:
None
:rtype:
None
:raises ValueError:
If the item provided is not a subclass of
:class:`~cratonclient.crud.Resource` or an iterable class then
we will not know how to handle it. In that case, we will raise a
ValueError.
"""
to_dict = getattr(item_to_format, 'to_dict', None)
if to_dict is not None:
self.handle_instance(item_to_format)
return
try:
self.handle_generator(item_to_format)
except TypeError as err:
raise ValueError(
"Expected an iterable object but instead received something "
"of type: %s. Received a TypeError: %s" % (
type(item_to_format),
err
)
)
def handle_instance(self, instance):
"""Format and print the instance provided.
:param instance:
The instance retrieved from the API that needs to be formatted.
:type instance:
cratonclient.crud.Resource
"""
raise NotImplementedError(
"A formatter plugin subclassed Formatter but did not implement"
" the handle_instance method."
)
def handle_generator(self, generator):
"""Format and print the instance provided.
:param generator:
The generator retrieved from the API whose items need to be
formatted.
"""
raise NotImplementedError(
"A formatter plugin subclassed Formatter but did not implement"
" the handle_generator method."
)

View File

@ -1,72 +0,0 @@
# 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.
"""JSON formatter implementation for the craton CLI."""
from __future__ import print_function
import json
from cratonclient.formatters import base
class Formatter(base.Formatter):
"""JSON output formatter for the CLI."""
def after_init(self):
"""Set-up our defaults.
At some point in the future, we may allow people to configure this via
the CLI.
"""
self.indent = 4
self.sort_keys = True
def format(self, dictionary):
"""Return the dictionary as a JSON string."""
return json.dumps(
dictionary,
sort_keys=self.sort_keys,
indent=self.indent,
)
def handle_instance(self, instance):
"""Print the JSON representation of a single instance."""
print(self.format(instance.to_dict()))
def handle_generator(self, generator):
"""Print the JSON representation of a collection."""
# NOTE(sigmavirus24): This is tricky logic that is caused by the JSON
# specification's intolerance for trailing commas.
try:
instance = next(generator)
except StopIteration:
# If there is nothing in the generator, we should just print an
# empty Array and then exit immediately.
print('[]')
return
# Otherwise, let's print our opening bracket to start our Array
# formatting.
print('[', end='')
while True:
print(self.format(instance.to_dict()), end='')
# After printing our instance as a JSON object, we need to
# decide if we have another object to print. If we do have
# another object to print, we need to print a comma to separate
# our previous object and our next one. If we don't, we exit our
# loop to print our closing Array bracket.
try:
instance = next(generator)
except StopIteration:
break
else:
print(', ', end='')
print(']')

View File

@ -1,181 +0,0 @@
# 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.
"""Pretty-table formatter implementation for the craton CLI."""
from __future__ import print_function
import textwrap
from oslo_utils import encodeutils
import prettytable
import six
from cratonclient.common import cliutils
from cratonclient.formatters import base
class Formatter(base.Formatter):
"""Implementation of the default table-style formatter."""
def after_init(self):
"""Set-up after initialization."""
self.fields = []
self.formatters = {}
self.sortby_index = None
self.mixed_case_fields = set([])
self.field_labels = []
self.dict_property = "Property"
self.wrap = 0
self.dict_value = "Value"
def configure(self, fields=None, formatters=None, sortby_index=False,
mixed_case_fields=None, field_labels=None,
dict_property=None, dict_value=None, wrap=None):
"""Configure some of the settings used to print the tables.
Parameters that configure list presentation:
:param list fields:
List of field names as strings.
:param dict formatters:
Mapping of field names to formatter functions that accept the
resource.
:param int sortby_index:
The index of the field name in :param:`fields` to sort the table
rows by. If ``None``, PrettyTable will not sort the items at all.
:param list mixed_case_fields:
List of field names also in :param:`fields` that are mixed case
and need preprocessing prior to retrieving the attribute.
:param list field_labels:
List of field labels that need to match :param:`fields`.
Parameters that configure the plain resource representation:
:param str dict_property:
The name of the first column.
:param str dict_value:
The name of the second column.
:param int wrap:
Length at which to wrap the second column.
All of these may be specified, but will be ignored based on how the
formatter is executed.
"""
if fields is not None:
self.fields = fields
if field_labels is None:
self.field_labels = cliutils.field_labels_from(self.fields)
elif len(field_labels) != len(self.fields):
raise ValueError(
"Field labels list %(labels)s has different number "
"of elements than fields list %(fields)s" %
{'labels': field_labels, 'fields': fields}
)
else:
self.field_labels = field_labels
if formatters is not None:
self.formatters = formatters
if sortby_index is not False:
try:
sortby_index = int(sortby_index)
except TypeError:
if sortby_index is not None:
raise ValueError(
'sortby_index must be None or an integer'
)
except ValueError:
raise
else:
if self.field_labels and (
sortby_index < 0 or
sortby_index > len(self.field_labels)):
raise ValueError(
'sortby_index must be a non-negative number less '
'than {}'.format(len(self.field_labels))
)
self.sortby_index = sortby_index
if mixed_case_fields is not None:
self.mixed_case_fields = set(mixed_case_fields)
if dict_property is not None:
self.dict_property = dict_property
if dict_value is not None:
self.dict_value = dict_value
if wrap is not None:
self.wrap = wrap
return self
def sortby_kwargs(self):
"""Generate the sortby keyword argument for PrettyTable."""
if self.sortby_index is None:
return {}
return {'sortby': self.field_labels[self.sortby_index]}
def build_table(self, field_labels, alignment='l'):
"""Create a PrettyTable instance based off of the labels."""
table = prettytable.PrettyTable(field_labels)
table.align = alignment
return table
def handle_generator(self, generator):
"""Handle a generator of resources."""
sortby_kwargs = self.sortby_kwargs()
table = self.build_table(self.field_labels)
for resource in generator:
row = []
for field in self.fields:
formatter = self.formatters.get(field)
if formatter is not None:
data = formatter(resource)
else:
if field in self.mixed_case_fields:
field_name = field.replace(' ', '_')
else:
field_name = field.lower().replace(' ', '_')
data = getattr(resource, field_name, '')
row.append(data)
table.add_row(row)
output = encodeutils.safe_encode(table.get_string(**sortby_kwargs))
if six.PY3:
output = output.decode()
print(output)
def handle_instance(self, instance):
"""Handle a single resource."""
table = self.build_table([self.dict_property, self.dict_value])
for key, value in sorted(instance.to_dict().items()):
if isinstance(value, dict):
value = six.text_type(value)
if self.wrap > 0:
value = textwrap.fill(six.text_type(value), self.wrap)
if value and isinstance(value, six.string_types) and '\n' in value:
lines = value.strip().split('\n')
column1 = key
for line in lines:
table.add_row([column1, line])
column1 = ''
else:
table.add_row([key, value])
output = encodeutils.safe_encode(table.get_string())
if six.PY3:
output = output.decode()
print(output)

View File

@ -1,288 +0,0 @@
# -*- 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 exceptions as ksa_exc
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)
except ksa_exc.HttpError as err:
raise exc.raise_from(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

View File

@ -1 +0,0 @@
"""Command-line application that interfaces with Craton API."""

View File

@ -1,207 +0,0 @@
# 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.
"""Main shell for parsing arguments directed toward Craton."""
from __future__ import print_function
import argparse
import sys
from oslo_utils import encodeutils
from oslo_utils import importutils
from stevedore import extension
from cratonclient import __version__
from cratonclient import exceptions as exc
from cratonclient import session as craton
from cratonclient.common import cliutils
from cratonclient.v1 import client
FORMATTERS_NAMESPACE = 'cratonclient.formatters'
class CratonShell(object):
"""Class used to handle shell definition and parsing."""
def __init__(self):
"""Initialize our shell object.
This sets up our formatters extension manager. If we add further
managers, they will be initialized here.
"""
self.extension_mgr = extension.ExtensionManager(
namespace=FORMATTERS_NAMESPACE,
invoke_on_load=False,
)
def get_base_parser(self):
"""Configure base craton arguments and parsing."""
parser = argparse.ArgumentParser(
prog='craton',
description=__doc__.strip(),
epilog='See "craton help COMMAND" '
'for help on a specific command.',
add_help=False,
formatter_class=argparse.HelpFormatter
)
parser.add_argument('-h', '--help',
action='store_true',
help=argparse.SUPPRESS,
)
parser.add_argument('--version',
action='version',
version=__version__,
)
parser.add_argument('--format',
default='default',
choices=list(sorted(self.extension_mgr.names())),
help='The format to use to print the information '
'to the console. Defaults to pretty-printing '
'using ASCII tables.',
)
parser.add_argument('--craton-url',
default=cliutils.env('CRATON_URL'),
help='The base URL of the running Craton service.'
' Defaults to env[CRATON_URL].',
)
parser.add_argument('--craton-version',
type=int,
default=cliutils.env('CRATON_VERSION',
default=1),
help='The version of the Craton API to use. '
'Defaults to env[CRATON_VERSION].'
)
parser.add_argument('--os-project-id',
default=cliutils.env('OS_PROJECT_ID'),
help='The project ID used to authenticate to '
'Craton. Defaults to env[OS_PROJECT_ID].',
)
parser.add_argument('--os-username',
default=cliutils.env('OS_USERNAME'),
help='The username used to authenticate to '
'Craton. Defaults to env[OS_USERNAME].',
)
parser.add_argument('--os-password',
default=cliutils.env('OS_PASSWORD'),
help='The password used to authenticate to '
'Craton. Defaults to env[OS_PASSWORD].',
)
return parser
# NOTE(cmspence): Credit for this get_subcommand_parser function
# goes to the magnumclient developers and contributors.
def get_subcommand_parser(self, api_version):
"""Get subcommands by parsing COMMAND_MODULES."""
parser = self.get_base_parser()
self.subcommands = {}
subparsers = parser.add_subparsers(metavar='<subcommand>',
dest='subparser_name')
shell = importutils.import_versioned_module(
'cratonclient.shell',
api_version,
'shell',
)
command_modules = shell.COMMAND_MODULES
for command_module in command_modules:
self._find_subparsers(subparsers, command_module)
self._find_subparsers(subparsers, self)
return parser
# NOTE(cmspence): Credit for this function goes to the
# magnumclient developers and contributors.
def _find_subparsers(self, subparsers, actions_module):
"""Find subparsers by looking at *_shell files."""
help_formatter = argparse.HelpFormatter
for attr in (a for a in dir(actions_module) if a.startswith('do_')):
command = attr[3:].replace('_', '-')
callback = getattr(actions_module, attr)
desc = callback.__doc__ or ''
action_help = desc.strip()
arguments = getattr(callback, 'arguments', [])
subparser = (subparsers.add_parser(command,
help=action_help,
description=desc,
add_help=False,
formatter_class=help_formatter)
)
subparser.add_argument('-h', '--help',
action='help',
help=argparse.SUPPRESS)
self.subcommands[command] = subparser
for (args, kwargs) in arguments:
subparser.add_argument(*args, **kwargs)
subparser.set_defaults(func=callback)
def main(self, argv):
"""Main entry-point for cratonclient shell argument parsing."""
parser = self.get_base_parser()
(options, args) = parser.parse_known_args(argv)
subcommand_parser = (
self.get_subcommand_parser(options.craton_version)
)
self.parser = subcommand_parser
if options.help or not argv:
self.parser.print_help()
return 0
args = subcommand_parser.parse_args(argv)
# Short-circuit and deal with help right away.
if args.func == self.do_help:
self.do_help(args)
return 0
session = craton.Session(
username=args.os_username,
token=args.os_password,
project_id=args.os_project_id,
)
self.cc = client.Client(session, args.craton_url)
formatter_class = self.extension_mgr[args.format].plugin
args.formatter = formatter_class(args)
args.func(self.cc, args)
@cliutils.arg(
'command',
metavar='<subcommand>',
nargs='?',
help='Display help for <subcommand>.')
def do_help(self, args):
"""Display help about this program or one of its subcommands."""
if args.command:
if args.command in self.subcommands:
self.subcommands[args.command].print_help()
else:
raise exc.CommandError("'%s' is not a valid subcommand" %
args.command)
else:
self.parser.print_help()
def main():
"""Main entry-point for cratonclient's CLI."""
try:
CratonShell().main([encodeutils.safe_decode(a) for a in sys.argv[1:]])
except Exception as e:
print("ERROR: {}".format(encodeutils.exception_to_unicode(e)),
file=sys.stderr)
sys.exit(1)
return 0
if __name__ == "__main__":
main()

View File

@ -1 +0,0 @@
"""Shell libraries for version 1 of Craton's API."""

View File

@ -1,283 +0,0 @@
# -*- 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.
"""Cells resource and resource shell wrapper."""
from __future__ import print_function
import argparse
import sys
from cratonclient.common import cliutils
from cratonclient import exceptions as exc
DEFAULT_CELL_FIELDS = [
'id',
'name',
'cloud_id',
'region_id',
'created_at',
]
CELL_FIELDS = DEFAULT_CELL_FIELDS + [
'updated_at',
'note',
'variables',
'project_id',
]
@cliutils.arg('id',
metavar='<cell>',
type=int,
help='ID of the cell.')
def do_cell_show(cc, args):
"""Show detailed information about a cell."""
cell = cc.cells.get(args.id)
args.formatter.configure(wrap=72).handle(cell)
@cliutils.arg('-r', '--region',
metavar='<region>',
type=int,
help='ID of the region that the cell belongs to.')
@cliutils.arg('--cloud',
metavar='<cloud>',
type=int,
help='ID of the cloud that the cell belongs to.')
@cliutils.arg('--detail',
action='store_true',
default=False,
help='Show detailed information about the cells.')
@cliutils.arg('--sort-key',
metavar='<field>',
help='Cell field that will be used for sorting.')
@cliutils.arg('--sort-dir',
metavar='<direction>',
default='asc',
choices=('asc', 'desc'),
help='Sort direction: "asc" (default) or "desc".')
@cliutils.arg('--fields',
nargs='+',
metavar='<fields>',
default=DEFAULT_CELL_FIELDS,
help='Space-separated list of fields to display. '
'Only these fields will be fetched from the server. '
'Can not be used when "--detail" is specified')
@cliutils.arg('--all',
action='store_true',
default=False,
help='Retrieve and show all cells. This will override '
'the provided value for --limit and automatically '
'retrieve each page of results.')
@cliutils.arg('--limit',
metavar='<limit>',
type=int,
help='Maximum number of cells to return.')
@cliutils.arg('--marker',
metavar='<marker>',
default=None,
help='ID of the cell to use to resume listing cells.')
@cliutils.arg('--vars',
metavar='<vars>',
nargs='+',
action='append',
default=[],
help='Variables to use as filter in the form of '
'--vars="key:value" --vars="key2:value2"')
def do_cell_list(cc, args):
"""Print list of cells which are registered with the Craton service."""
params = {}
if args.vars:
query_vars = ",".join([i[0] for i in args.vars])
params['vars'] = query_vars
if args.cloud is not None:
params['cloud_id'] = args.cloud
if args.limit is not None:
if args.limit < 0:
raise exc.CommandError('Invalid limit specified. Expected '
'non-negative limit, got {0}'
.format(args.limit))
params['limit'] = args.limit
if args.all is True:
params['limit'] = 100
if args.detail:
if args.fields and args.fields == DEFAULT_CELL_FIELDS:
args.fields = CELL_FIELDS
else:
raise exc.CommandError(
'Cannot specify both --fields and --detail.'
)
params['detail'] = args.detail
fields = args.fields
for field in fields:
if field not in CELL_FIELDS:
raise exc.CommandError(
'Invalid field "{}"'.format(field)
)
sort_key = args.sort_key and args.sort_key.lower()
if sort_key is not None:
if sort_key not in CELL_FIELDS:
raise exc.CommandError(
('"--sort-key" value was "{}" but should '
'be one of: {}').format(
args.sort_key,
', '.join(CELL_FIELDS)
)
)
params['sort_key'] = sort_key
if args.region is not None:
params['region_id'] = args.region
params['sort_dir'] = args.sort_dir
params['marker'] = args.marker
params['autopaginate'] = args.all
listed_cells = cc.cells.list(**params)
args.formatter.configure(fields=fields).handle(listed_cells)
@cliutils.arg('-n', '--name',
metavar='<name>',
required=True,
help='Name of the cell.')
@cliutils.arg('-r', '--region',
dest='region_id',
metavar='<region>',
type=int,
required=True,
help='ID of the region that the cell belongs to.')
@cliutils.arg('--cloud',
dest='cloud_id',
metavar='<cloud>',
type=int,
required=True,
help='ID of the cloud that the cell belongs to.')
@cliutils.arg('--note',
help='Note about the cell.')
def do_cell_create(cc, args):
"""Register a new cell with the Craton service."""
fields = {k: v for (k, v) in vars(args).items()
if k in CELL_FIELDS and not (v is None)}
cell = cc.cells.create(**fields)
args.formatter.configure(wrap=72).handle(cell)
@cliutils.arg('id',
metavar='<cell>',
type=int,
help='ID of the cell.')
@cliutils.arg('-n', '--name',
metavar='<name>',
help='Name of the cell.')
@cliutils.arg('-r', '--region',
dest='region_id',
metavar='<region>',
type=int,
help='Desired ID of the region that the cell should change to.')
@cliutils.arg('--cloud',
dest='cloud_id',
metavar='<cloud>',
type=int,
help='Desired ID of the cloud that the cell should change to.')
@cliutils.arg('--note',
help='Note about the cell.')
def do_cell_update(cc, args):
"""Update a cell that is registered with the Craton service."""
fields = {k: v for (k, v) in vars(args).items()
if k in CELL_FIELDS and not (v is None)}
cell_id = fields.pop('id')
if not fields:
raise exc.CommandError(
'Nothing to update... Please specify one of --name, --region, '
'--cloud, or --note'
)
cell = cc.cells.update(cell_id, **fields)
args.formatter.configure(wrap=72).handle(cell)
@cliutils.arg('id',
metavar='<cell>',
type=int,
help='ID of the cell.')
def do_cell_delete(cc, args):
"""Delete a cell that is registered with the Craton service."""
try:
response = cc.cells.delete(args.id)
except exc.ClientException as client_exc:
raise exc.CommandError(
'Failed to delete cell {} due to "{}:{}"'.format(
args.id, client_exc.__class__, str(client_exc)
)
)
else:
print("Cell {0} was {1} deleted.".
format(args.id, 'successfully' if response else 'not'))
@cliutils.arg('id',
metavar='<cell>',
type=int,
help='ID or name of the cell.')
@cliutils.handle_shell_exception
def do_cell_vars_get(cc, args):
"""Get variables for a cell."""
variables = cc.cells.get(args.id).variables.get()
formatter = args.formatter.configure(dict_property="Variable", wrap=72)
formatter.handle(variables)
@cliutils.arg('id',
metavar='<cell>',
type=int,
help='ID of the cell.')
@cliutils.arg('variables', nargs=argparse.REMAINDER)
@cliutils.handle_shell_exception
def do_cell_vars_set(cc, args):
"""Set variables for a cell."""
cell_id = args.id
if not args.variables and sys.stdin.isatty():
raise exc.CommandError(
'Nothing to update... Please specify variables to set in the '
'following format: "key=value". You may also specify variables to '
'delete by key using the format: "key="'
)
adds, deletes = cliutils.variable_updates(args.variables)
variables = cc.cells.get(cell_id).variables
if deletes:
variables.delete(*deletes)
variables.update(**adds)
formatter = args.formatter.configure(wrap=72, dict_property="Variable")
formatter.handle(variables.get())
@cliutils.arg('id',
metavar='<cell>',
type=int,
help='ID of the cell.')
@cliutils.arg('variables', nargs=argparse.REMAINDER)
@cliutils.handle_shell_exception
def do_cell_vars_delete(cc, args):
"""Delete variables for a cell by key."""
cell_id = args.id
if not args.variables and sys.stdin.isatty():
raise exc.CommandError(
'Nothing to delete... Please specify variables to delete by '
'listing the keys you wish to delete separated by spaces.'
)
deletes = cliutils.variable_deletes(args.variables)
variables = cc.cells.get(cell_id).variables
response = variables.delete(*deletes)
print("Variables {0} deleted.".
format('successfully' if response else 'not'))

View File

@ -1,215 +0,0 @@
# 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.
"""Hosts resource and resource shell wrapper."""
from __future__ import print_function
import argparse
import sys
from cratonclient.common import cliutils
from cratonclient import exceptions as exc
DEFAULT_CLOUD_FIELDS = [
'id',
'name',
'created_at',
]
CLOUD_FIELDS = DEFAULT_CLOUD_FIELDS + [
'updated_at',
'note',
'project_id',
]
@cliutils.arg('-n', '--name',
metavar='<name>',
required=True,
help='Name of the host.')
@cliutils.arg('--note',
help='Note about the host.')
def do_cloud_create(cc, args):
"""Register a new cloud with the Craton service."""
fields = {k: v for (k, v) in vars(args).items()
if k in CLOUD_FIELDS and not (v is None)}
cloud = cc.clouds.create(**fields)
args.formatter.configure(wrap=72).handle(cloud)
@cliutils.arg('--fields',
nargs='+',
metavar='<fields>',
default=DEFAULT_CLOUD_FIELDS,
help='Comma-separated list of fields to display. '
'Only these fields will be fetched from the server. '
'Can not be used when "--detail" is specified')
@cliutils.arg('--all',
action='store_true',
default=False,
help='Retrieve and show all clouds. This will override '
'the provided value for --limit and automatically '
'retrieve each page of results.')
@cliutils.arg('--detail',
action='store_true',
default=False,
help='Show detailed information about all clouds.')
@cliutils.arg('--limit',
metavar='<limit>',
type=int,
help='Maximum number of clouds to return.')
@cliutils.arg('--marker',
metavar='<marker>',
default=None,
help='ID of the cell to use to resume listing clouds.')
def do_cloud_list(cc, args):
"""List all clouds."""
params = {}
if args.limit is not None:
if args.limit < 0:
raise exc.CommandError('Invalid limit specified. Expected '
'non-negative limit, got {0}'
.format(args.limit))
params['limit'] = args.limit
if args.all is True:
params['limit'] = 100
if args.detail:
if args.fields and args.fields == DEFAULT_CLOUD_FIELDS:
args.fields = CLOUD_FIELDS
else:
raise exc.CommandError(
'Cannot specify both --fields and --detail.'
)
params['detail'] = args.detail
fields = args.fields
for field in args.fields:
if field not in CLOUD_FIELDS:
raise exc.CommandError(
'Invalid field "{}"'.format(field)
)
params['marker'] = args.marker
params['autopaginate'] = args.all
clouds_list = cc.clouds.list(**params)
args.formatter.configure(fields=list(fields)).handle(clouds_list)
@cliutils.arg('id',
metavar='<cloud>',
type=int,
help='ID of the cloud.')
def do_cloud_show(cc, args):
"""Show detailed information about a cloud."""
cloud = cc.clouds.get(args.id)
args.formatter.configure(wrap=72).handle(cloud)
@cliutils.arg('id',
metavar='<cloud>',
type=int,
help='ID of the cloud')
@cliutils.arg('-n', '--name',
metavar='<name>',
help='Name of the cloud.')
@cliutils.arg('--note',
help='Note about the cloud.')
def do_cloud_update(cc, args):
"""Update a cloud that is registered with the Craton service."""
fields = {k: v for (k, v) in vars(args).items()
if k in CLOUD_FIELDS and not (v is None)}
item_id = fields.pop('id')
if not fields:
raise exc.CommandError(
'Nothing to update... Please specify one or more of --name, or '
'--note'
)
cloud = cc.clouds.update(item_id, **fields)
args.formatter.configure(wrap=72).handle(cloud)
@cliutils.arg('id',
metavar='<cloud>',
type=int,
help='ID of the cloud.')
def do_cloud_delete(cc, args):
"""Delete a cloud that is registered with the Craton service."""
try:
response = cc.clouds.delete(args.id)
except exc.ClientException as client_exc:
raise exc.CommandError(
'Failed to delete cloud {} due to "{}:{}"'.format(
args.id, client_exc.__class__, str(client_exc),
)
)
else:
print("Cloud {0} was {1} deleted.".
format(args.id, 'successfully' if response else 'not'))
@cliutils.arg('id',
metavar='<cloud>',
type=int,
help='ID or name of the cloud.')
@cliutils.handle_shell_exception
def do_cloud_vars_get(cc, args):
"""Get variables for a cloud."""
variables = cc.clouds.get(args.id).variables.get()
formatter = args.formatter.configure(dict_property="Variable", wrap=72)
formatter.handle(variables)
@cliutils.arg('id',
metavar='<cloud>',
type=int,
help='ID of the cloud.')
@cliutils.arg('variables', nargs=argparse.REMAINDER)
@cliutils.handle_shell_exception
def do_cloud_vars_set(cc, args):
"""Set variables for a cloud."""
cloud_id = args.id
if not args.variables and sys.stdin.isatty():
raise exc.CommandError(
'Nothing to update... Please specify variables to set in the '
'following format: "key=value". You may also specify variables to '
'delete by key using the format: "key="'
)
adds, deletes = cliutils.variable_updates(args.variables)
variables = cc.clouds.get(cloud_id).variables
if deletes:
variables.delete(*deletes)
variables.update(**adds)
formatter = args.formatter.configure(wrap=72, dict_property="Variable")
formatter.handle(variables.get())
@cliutils.arg('id',
metavar='<cloud>',
type=int,
help='ID of the cloud.')
@cliutils.arg('variables', nargs=argparse.REMAINDER)
@cliutils.handle_shell_exception
def do_cloud_vars_delete(cc, args):
"""Delete variables for a cloud by key."""
cloud_id = args.id
if not args.variables and sys.stdin.isatty():
raise exc.CommandError(
'Nothing to delete... Please specify variables to delete by '
'listing the keys you wish to delete separated by spaces.'
)
deletes = cliutils.variable_deletes(args.variables)
variables = cc.clouds.get(cloud_id).variables
response = variables.delete(*deletes)
print("Variables {0} deleted.".
format('successfully' if response else 'not'))

View File

@ -1,150 +0,0 @@
# 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.
"""Hosts resource and resource shell wrapper."""
from __future__ import print_function
from cratonclient.common import cliutils
from cratonclient import exceptions as exc
DEFAULT_DEVICE_FIELDS = [
'id',
'name',
'device_type',
'ip_address',
'cloud_id',
'region_id',
'cell_id',
'parent_id',
]
DEVICE_FIELDS = DEFAULT_DEVICE_FIELDS + [
'note',
'created_at',
'updated_at',
'project_id',
]
@cliutils.arg('--fields',
nargs='+',
metavar='<fields>',
default=DEFAULT_DEVICE_FIELDS,
help='Space-separated list of fields to display. '
'Only these fields will be fetched from the server. '
'This cannot be combined with --detail.')
@cliutils.arg('--detail',
action='store_true',
default=False,
help='Retrieve and show all detail about devices in listing.')
@cliutils.arg('--all',
action='store_true',
default=False,
help='Retrieve and show all devices. This will override '
'the provided value for --limit and automatically '
'retrieve each page of results.')
@cliutils.arg('--sort-key',
metavar='<field>',
help='Device field that will be used for sorting.')
@cliutils.arg('--sort-dir',
metavar='<direction>',
default='asc',
choices=('asc', 'desc'),
help='Sort direction: "asc" (default) or "desc".')
@cliutils.arg('--limit',
metavar='<limit>',
type=int,
help='Maximum number of devices to return.')
@cliutils.arg('--marker',
metavar='<marker>',
default=None,
help='ID of the device to use to resume listing devices.')
@cliutils.arg('--cloud',
metavar='<cloud>',
type=int,
help='ID of the cloud that the device belongs to.')
@cliutils.arg('-r', '--region',
metavar='<region>',
type=int,
help='ID of the region that the device belongs to.')
@cliutils.arg('-c', '--cell',
metavar='<cell>',
type=int,
help='Integer ID of the cell that contains '
'the desired list of devices.')
@cliutils.arg('--parent',
metavar='<parent>',
type=int,
help='Parent ID of required devices.')
@cliutils.arg('--descendants',
default=False,
action='store_true',
help='When parent is also specified, include all descendants.')
@cliutils.arg('--active',
metavar='<active>',
choices=("true", "false"),
help='Filter devices by their active state.')
def do_device_list(cc, args):
"""List all devices."""
params = {}
if args.limit is not None:
if args.limit < 0:
raise exc.CommandError('Invalid limit specified. Expected '
'non-negative limit, got {0}'
.format(args.limit))
params['limit'] = args.limit
if args.all is True:
params['limit'] = 100
if args.detail:
if args.fields and args.fields == DEFAULT_DEVICE_FIELDS:
args.fields = DEVICE_FIELDS
else:
raise exc.CommandError(
'Cannot specify both --fields and --detail.'
)
params['detail'] = args.detail
fields = args.fields
for field in fields:
if field not in DEVICE_FIELDS:
raise exc.CommandError(
'Invalid field "{}"'.format(field)
)
sort_key = args.sort_key and args.sort_key.lower()
if sort_key is not None:
if sort_key not in DEVICE_FIELDS:
raise exc.CommandError(
'{0} is an invalid key for sorting, valid values for '
'--sort-key are: {1}'.format(
args.sort_key, DEVICE_FIELDS
)
)
params['sort_keys'] = sort_key
params['sort_dir'] = args.sort_dir
params['marker'] = args.marker
params['autopaginate'] = args.all
if args.parent:
params['parent_id'] = args.parent
params['descendants'] = args.descendants
if args.cloud:
params['cloud_id'] = args.cloud
if args.region:
params['region_id'] = args.region
if args.cell:
params['cell_id'] = args.cell
if args.active:
params['active'] = args.active
devices_list = cc.devices.list(**params)
args.formatter.configure(fields=fields).handle(devices_list)

View File

@ -1,341 +0,0 @@
# -*- 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.
"""Hosts resource and resource shell wrapper."""
from __future__ import print_function
import argparse
import sys
from cratonclient.common import cliutils
from cratonclient import exceptions as exc
DEFAULT_HOST_FIELDS = [
'id',
'name',
'active',
'device_type',
'ip_address',
'cloud_id',
'region_id',
'cell_id',
'created_at',
]
HOST_FIELDS = DEFAULT_HOST_FIELDS + [
'updated_at',
'note',
'variables',
'labels',
'parent_id',
'project_id',
]
@cliutils.arg('id',
metavar='<host>',
type=int,
help='ID of the host.')
def do_host_show(cc, args):
"""Show detailed information about a host."""
host = cc.hosts.get(args.id)
args.formatter.configure(wrap=72).handle(host)
@cliutils.arg('-r', '--region',
metavar='<region>',
type=int,
help='ID of the region that the host belongs to.')
@cliutils.arg('--cloud',
metavar='<cloud>',
type=int,
help='ID of the cloud that the host belongs to.')
@cliutils.arg('-c', '--cell',
metavar='<cell>',
type=int,
help='Integer ID of the cell that contains '
'the desired list of hosts.')
@cliutils.arg('--detail',
action='store_true',
default=False,
help='Show detailed information about the hosts.')
@cliutils.arg('--sort-key',
metavar='<field>',
help='Host field that will be used for sorting.')
@cliutils.arg('--sort-dir',
metavar='<direction>',
default='asc',
choices=('asc', 'desc'),
help='Sort direction: "asc" (default) or "desc".')
@cliutils.arg('--fields',
nargs='+',
metavar='<fields>',
default=DEFAULT_HOST_FIELDS,
help='Space-separated list of fields to display. '
'Only these fields will be fetched from the server. '
'Can not be used when "--detail" is specified')
@cliutils.arg('--all',
action='store_true',
default=False,
help='Retrieve and show all hosts. This will override '
'the provided value for --limit and automatically '
'retrieve each page of results.')
@cliutils.arg('--limit',
metavar='<limit>',
type=int,
help='Maximum number of hosts to return.')
@cliutils.arg('--marker',
metavar='<marker>',
default=None,
help='ID of the cell to use to resume listing hosts.')
@cliutils.arg('--device-type',
metavar='<device_type>',
default=None,
help='Device type to use as filter.')
@cliutils.arg('--vars',
metavar='<vars>',
default=None,
help='Variables to use as filter in the form of key:value.')
@cliutils.arg('--label',
metavar='<label>',
default=None,
help='Label to use as filter.')
@cliutils.arg('--ip',
metavar='<ip_address>',
default=None,
help='IP address to use as filter.')
def do_host_list(cc, args):
"""Print list of hosts which are registered with the Craton service."""
params = {}
if args.cell is not None:
params['cell_id'] = args.cell
if args.cloud is not None:
params['cloud_id'] = args.cloud
if args.device_type is not None:
params['device_type'] = args.device_type
if args.vars is not None:
params['vars'] = args.vars
if args.label is not None:
params['label'] = args.label
if args.ip is not None:
params['ip_address'] = args.ip
if args.limit is not None:
if args.limit < 0:
raise exc.CommandError('Invalid limit specified. Expected '
'non-negative limit, got {0}'
.format(args.limit))
params['limit'] = args.limit
if args.all is True:
params['limit'] = 100
if args.detail:
if args.fields and args.fields == DEFAULT_HOST_FIELDS:
args.fields = HOST_FIELDS
else:
raise exc.CommandError(
'Cannot specify both --fields and --detail.'
)
params['detail'] = args.detail
fields = args.fields
for field in args.fields:
if field not in HOST_FIELDS:
raise exc.CommandError(
'Invalid field "{}"'.format(field)
)
sort_key = args.sort_key and args.sort_key.lower()
if sort_key is not None:
if sort_key not in HOST_FIELDS:
raise exc.CommandError(
'{0} is an invalid key for sorting, valid values for '
'--sort-key are: {1}'.format(
args.sort_key, HOST_FIELDS
)
)
params['sort_key'] = sort_key
if args.region is not None:
params['region_id'] = args.region
params['sort_dir'] = args.sort_dir
params['marker'] = args.marker
params['autopaginate'] = args.all
host_list = cc.hosts.list(**params)
args.formatter.configure(fields=fields).handle(host_list)
@cliutils.arg('-n', '--name',
metavar='<name>',
required=True,
help='Name of the host.')
@cliutils.arg('-i', '--ip_address',
metavar='<ipaddress>',
required=True,
help='IP Address of the host.')
@cliutils.arg('-r', '--region',
dest='region_id',
metavar='<region>',
type=int,
required=True,
help='ID of the region that the host belongs to.')
@cliutils.arg('--cloud',
dest='cloud_id',
metavar='<cloud>',
type=int,
required=True,
help='ID of the cloud that the host belongs to.')
@cliutils.arg('-c', '--cell',
dest='cell_id',
metavar='<cell>',
type=int,
help='ID of the cell that the host belongs to.')
@cliutils.arg('-t', '--type',
dest='device_type',
metavar='<type>',
required=True,
help='Type of the host.')
@cliutils.arg('-a', '--active',
default=True,
help='Status of the host. Active or inactive.')
@cliutils.arg('--note',
help='Note about the host.')
@cliutils.arg('-l', '--labels',
default=[],
help='List of labels for the host.')
def do_host_create(cc, args):
"""Register a new host with the Craton service."""
fields = {k: v for (k, v) in vars(args).items()
if k in HOST_FIELDS and (v or v is False)}
host = cc.hosts.create(**fields)
args.formatter.configure(wrap=72).handle(host)
@cliutils.arg('id',
metavar='<host>',
type=int,
help='ID of the host.')
@cliutils.arg('-n', '--name',
metavar='<name>',
help='Name of the host.')
@cliutils.arg('-i', '--ip_address',
metavar='<ipaddress>',
help='IP Address of the host.')
@cliutils.arg('-r', '--region',
dest='region_id',
metavar='<region>',
type=int,
help='Desired ID of the region that the host should change to.')
@cliutils.arg('--cloud',
dest='cloud_id',
metavar='<cloud>',
type=int,
help='Desired ID of the cloud that the host should change to.')
@cliutils.arg('-c', '--cell',
dest='cell_id',
metavar='<cell>',
type=int,
help='ID of the cell that the host belongs to.')
@cliutils.arg('-a', '--active',
default=True,
help='Status of the host. Active or inactive.')
@cliutils.arg('--note',
help='Note about the host.')
@cliutils.arg('-l', '--labels',
default=[],
help='List of labels for the host.')
def do_host_update(cc, args):
"""Update a host that is registered with the Craton service."""
fields = {k: v for (k, v) in vars(args).items()
if k in HOST_FIELDS and (v or v is False)}
item_id = fields.pop('id')
host = cc.hosts.update(item_id, **fields)
print("Host {0} has been successfully updated.".format(host.id))
args.formatter.configure(wrap=72).handle(host)
@cliutils.arg('id',
metavar='<host>',
type=int,
help='ID of the host.')
def do_host_delete(cc, args):
"""Delete a host that is registered with the Craton service."""
try:
response = cc.hosts.delete(args.id)
except exc.ClientException as client_exc:
raise exc.CommandError(
'Failed to delete cell {} due to "{}:{}"'.format(
args.id, client_exc.__class__, str(client_exc),
)
)
else:
print("Host {0} was {1} deleted.".
format(args.id, 'successfully' if response else 'not'))
@cliutils.arg('id',
metavar='<host>',
type=int,
help='ID or name of the host.')
@cliutils.handle_shell_exception
def do_host_vars_get(cc, args):
"""Get variables for a host."""
variables = cc.hosts.get(args.id).variables.get()
formatter = args.formatter.configure(dict_property="Variable", wrap=72)
formatter.handle(variables)
@cliutils.arg('id',
metavar='<host>',
type=int,
help='ID of the host.')
@cliutils.arg('variables', nargs=argparse.REMAINDER)
@cliutils.handle_shell_exception
def do_host_vars_set(cc, args):
"""Set variables for a host."""
host_id = args.id
if not args.variables and sys.stdin.isatty():
raise exc.CommandError(
'Nothing to update... Please specify variables to set in the '
'following format: "key=value". You may also specify variables to '
'delete by key using the format: "key="'
)
adds, deletes = cliutils.variable_updates(args.variables)
variables = cc.hosts.get(host_id).variables
if deletes:
variables.delete(*deletes)
variables.update(**adds)
formatter = args.formatter.configure(wrap=72, dict_property="Variable")
formatter.handle(variables.get())
@cliutils.arg('id',
metavar='<host>',
type=int,
help='ID of the host.')
@cliutils.arg('variables', nargs=argparse.REMAINDER)
@cliutils.handle_shell_exception
def do_host_vars_delete(cc, args):
"""Delete variables for a host by key."""
host_id = args.id
if not args.variables and sys.stdin.isatty():
raise exc.CommandError(
'Nothing to delete... Please specify variables to delete by '
'listing the keys you wish to delete separated by spaces.'
)
deletes = cliutils.variable_deletes(args.variables)
variables = cc.hosts.get(host_id).variables
response = variables.delete(*deletes)
print("Variables {0} deleted.".
format('successfully' if response else 'not'))

View File

@ -1,190 +0,0 @@
# -*- 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.
"""Projects resource and resource shell wrapper."""
from __future__ import print_function
import argparse
import sys
from cratonclient.common import cliutils
from cratonclient import exceptions as exc
DEFAULT_PROJECT_FIELDS = [
'id',
'name',
]
PROJECT_FIELDS = DEFAULT_PROJECT_FIELDS + [
'created_at',
'updated_at',
]
@cliutils.arg('id',
metavar='<project>',
help='ID of the project.')
def do_project_show(cc, args):
"""Show detailed information about a project."""
project = cc.projects.get(args.id)
args.formatter.configure(wrap=72).handle(project)
@cliutils.arg('-n', '--name',
metavar='<name>',
help='Name of the project.')
@cliutils.arg('--detail',
action='store_true',
default=False,
help='Show detailed information about the projects.')
@cliutils.arg('--fields',
nargs='+',
metavar='<fields>',
default=DEFAULT_PROJECT_FIELDS,
help='Space-separated list of fields to display. '
'Only these fields will be fetched from the server. '
'Can not be used when "--detail" is specified')
@cliutils.arg('--all',
action='store_true',
default=False,
help='Retrieve and show all projects. This will override '
'the provided value for --limit and automatically '
'retrieve each page of results.')
@cliutils.arg('--limit',
metavar='<limit>',
type=int,
help='Maximum number of projects to return.')
@cliutils.arg('--marker',
metavar='<marker>',
default=None,
help='ID of the cell to use to resume listing projects.')
def do_project_list(cc, args):
"""Print list of projects which are registered with the Craton service."""
params = {}
if args.limit is not None:
if args.limit < 0:
raise exc.CommandError('Invalid limit specified. Expected '
'non-negative limit, got {0}'
.format(args.limit))
params['limit'] = args.limit
if args.all is True:
params['limit'] = 100
if args.detail:
if args.fields and args.fields == DEFAULT_PROJECT_FIELDS:
args.fields = PROJECT_FIELDS
else:
raise exc.CommandError(
'Cannot specify both --fields and --detail.'
)
fields = args.fields
for field in fields:
if field not in PROJECT_FIELDS:
raise exc.CommandError(
'Invalid field "{}"'.format(field)
)
if args.name:
params['name'] = args.name
params['marker'] = args.marker
params['autopaginate'] = args.all
listed_projects = cc.projects.list(**params)
args.formatter.configure(fields=list(fields)).handle(listed_projects)
@cliutils.arg('-n', '--name',
metavar='<name>',
required=True,
help='Name of the project.')
def do_project_create(cc, args):
"""Register a new project with the Craton service."""
fields = {k: v for (k, v) in vars(args).items()
if k in PROJECT_FIELDS and not (v is None)}
project = cc.projects.create(**fields)
args.formatter.configure(wrap=72).handle(project)
@cliutils.arg('id',
metavar='<project>',
help='ID of the project.')
def do_project_delete(cc, args):
"""Delete a project that is registered with the Craton service."""
try:
response = cc.projects.delete(args.id)
except exc.ClientException as client_exc:
raise exc.CommandError(
'Failed to delete project {} due to "{}:{}"'.format(
args.id, client_exc.__class__, str(client_exc)
)
)
else:
print("Project {0} was {1} deleted.".
format(args.id, 'successfully' if response else 'not'))
@cliutils.arg('id',
metavar='<project>',
help='ID or name of the project.')
@cliutils.handle_shell_exception
def do_project_vars_get(cc, args):
"""Get variables for a project."""
variables = cc.projects.get(args.id).variables.get()
formatter = args.formatter.configure(dict_property="Variable", wrap=72)
formatter.handle(variables)
@cliutils.arg('id',
metavar='<project>',
help='ID of the project.')
@cliutils.arg('variables', nargs=argparse.REMAINDER)
@cliutils.handle_shell_exception
def do_project_vars_set(cc, args):
"""Set variables for a project."""
project_id = args.id
if not args.variables and sys.stdin.isatty():
raise exc.CommandError(
'Nothing to update... Please specify variables to set in the '
'following format: "key=value". You may also specify variables to '
'delete by key using the format: "key="'
)
adds, deletes = cliutils.variable_updates(args.variables)
variables = cc.projects.get(project_id).variables
if deletes:
variables.delete(*deletes)
variables.update(**adds)
formatter = args.formatter.configure(wrap=72, dict_property="Variable")
formatter.handle(variables.get())
@cliutils.arg('id',
metavar='<project>',
help='ID of the project.')
@cliutils.arg('variables', nargs=argparse.REMAINDER)
@cliutils.handle_shell_exception
def do_project_vars_delete(cc, args):
"""Delete variables for a project by key."""
project_id = args.id
if not args.variables and sys.stdin.isatty():
raise exc.CommandError(
'Nothing to delete... Please specify variables to delete by '
'listing the keys you wish to delete separated by spaces.'
)
deletes = cliutils.variable_deletes(args.variables)
variables = cc.projects.get(project_id).variables
response = variables.delete(*deletes)
print("Variables {0} deleted.".
format('successfully' if response else 'not'))

View File

@ -1,243 +0,0 @@
# 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.
"""Hosts resource and resource shell wrapper."""
from __future__ import print_function
import argparse
import sys
from cratonclient.common import cliutils
from cratonclient import exceptions as exc
DEFAULT_REGION_FIELDS = [
'id',
'name',
'cloud_id',
]
REGION_FIELDS = DEFAULT_REGION_FIELDS + [
'project_id',
'note',
'created_at',
'updated_at',
]
@cliutils.arg('-n', '--name',
metavar='<name>',
required=True,
help='Name of the host.')
@cliutils.arg('--cloud',
dest='cloud_id',
metavar='<cloud>',
type=int,
required=True,
help='ID of the cloud that the region belongs to.')
@cliutils.arg('--note',
help='Note about the host.')
def do_region_create(cc, args):
"""Register a new region with the Craton service."""
fields = {k: v for (k, v) in vars(args).items()
if k in REGION_FIELDS and not (v is None)}
region = cc.regions.create(**fields)
args.formatter.configure(wrap=72).handle(region)
@cliutils.arg('--cloud',
metavar='<cloud>',
type=int,
help='ID of the cloud that the region belongs to.')
@cliutils.arg('--fields',
nargs='+',
metavar='<fields>',
default=DEFAULT_REGION_FIELDS,
help='Space-separated list of fields to display. '
'Only these fields will be fetched from the server. '
'Can not be used when "--detail" is specified')
@cliutils.arg('--detail',
action='store_true',
default=False,
help='Show detailed information about the regions.')
@cliutils.arg('--all',
action='store_true',
default=False,
help='Retrieve and show all regions. This will override '
'the provided value for --limit and automatically '
'retrieve each page of results.')
@cliutils.arg('--limit',
metavar='<limit>',
type=int,
help='Maximum number of regions to return.')
@cliutils.arg('--marker',
metavar='<marker>',
default=None,
help='ID of the region to use to resume listing regions.')
@cliutils.arg('--vars',
metavar='<vars>',
nargs='+',
action='append',
default=[],
help='Variables to use as filter in the form of '
'--vars="key:value" --vars="key2:value2"')
def do_region_list(cc, args):
"""List all regions."""
params = {}
if args.vars:
query_vars = ",".join([i[0] for i in args.vars])
params['vars'] = query_vars
if args.cloud is not None:
params['cloud_id'] = args.cloud
if args.limit is not None:
if args.limit < 0:
raise exc.CommandError('Invalid limit specified. Expected '
'non-negative limit, got {0}'
.format(args.limit))
params['limit'] = args.limit
if args.all is True:
params['limit'] = 100
if args.detail:
if args.fields and args.fields == DEFAULT_REGION_FIELDS:
args.fields = REGION_FIELDS
else:
raise exc.CommandError(
'Cannot specify both --fields and --detail.'
)
params['detail'] = args.detail
fields = args.fields
for field in args.fields:
if field not in REGION_FIELDS:
raise exc.CommandError(
'Invalid field "{}"'.format(field)
)
params['marker'] = args.marker
params['autopaginate'] = args.all
regions_list = cc.regions.list(**params)
args.formatter.configure(fields=list(fields)).handle(regions_list)
@cliutils.arg('id',
metavar='<region>',
type=int,
help='ID of the region.')
def do_region_show(cc, args):
"""Show detailed information about a region."""
region = cc.regions.get(args.id)
args.formatter.configure(wrap=72).handle(region)
@cliutils.arg('id',
metavar='<region>',
type=int,
help='ID of the region')
@cliutils.arg('-n', '--name',
metavar='<name>',
help='Name of the region.')
@cliutils.arg('--cloud',
dest='cloud_id',
metavar='<cloud>',
type=int,
help='Desired ID of the cloud that the region should change to.')
@cliutils.arg('--note',
help='Note about the region.')
def do_region_update(cc, args):
"""Update a region that is registered with the Craton service."""
fields = {k: v for (k, v) in vars(args).items()
if k in REGION_FIELDS and not (v is None)}
item_id = fields.pop('id')
if not fields:
raise exc.CommandError(
'Nothing to update... Please specify one or more of --name, '
'--cloud, or --note'
)
region = cc.regions.update(item_id, **fields)
args.formatter.configure(wrap=72).handle(region)
@cliutils.arg('id',
metavar='<region>',
type=int,
help='ID of the region.')
def do_region_delete(cc, args):
"""Delete a region that is registered with the Craton service."""
try:
response = cc.regions.delete(args.id)
except exc.ClientException as client_exc:
raise exc.CommandError(
'Failed to delete region {} due to "{}:{}"'.format(
args.id, client_exc.__class__, str(client_exc),
)
)
else:
print("Region {0} was {1} deleted.".
format(args.id, 'successfully' if response else 'not'))
@cliutils.arg('id',
metavar='<region>',
type=int,
help='ID or name of the region.')
@cliutils.handle_shell_exception
def do_region_vars_get(cc, args):
"""Get variables for a region."""
variables = cc.regions.get(args.id).variables.get()
formatter = args.formatter.configure(dict_property="Variable", wrap=72)
formatter.handle(variables)
@cliutils.arg('id',
metavar='<region>',
type=int,
help='ID of the region.')
@cliutils.arg('variables', nargs=argparse.REMAINDER)
@cliutils.handle_shell_exception
def do_region_vars_set(cc, args):
"""Set variables for a region."""
region_id = args.id
if not args.variables and sys.stdin.isatty():
raise exc.CommandError(
'Nothing to update... Please specify variables to set in the '
'following format: "key=value". You may also specify variables to '
'delete by key using the format: "key="'
)
adds, deletes = cliutils.variable_updates(args.variables)
variables = cc.regions.get(region_id).variables
if deletes:
variables.delete(*deletes)
variables.update(**adds)
formatter = args.formatter.configure(wrap=72, dict_property="Variable")
formatter.handle(variables.get())
@cliutils.arg('id',
metavar='<region>',
type=int,
help='ID of the region.')
@cliutils.arg('variables', nargs=argparse.REMAINDER)
@cliutils.handle_shell_exception
def do_region_vars_delete(cc, args):
"""Delete variables for a region by key."""
region_id = args.id
if not args.variables and sys.stdin.isatty():
raise exc.CommandError(
'Nothing to delete... Please specify variables to delete by '
'listing the keys you wish to delete separated by spaces.'
)
deletes = cliutils.variable_deletes(args.variables)
variables = cc.regions.get(region_id).variables
response = variables.delete(*deletes)
print("Variables {0} deleted.".
format('successfully' if response else 'not'))

View File

@ -1,29 +0,0 @@
# 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.
"""Command-line interface to the OpenStack Craton API V1."""
from cratonclient.shell.v1 import cells_shell
from cratonclient.shell.v1 import clouds_shell
from cratonclient.shell.v1 import devices_shell
from cratonclient.shell.v1 import hosts_shell
from cratonclient.shell.v1 import projects_shell
from cratonclient.shell.v1 import regions_shell
COMMAND_MODULES = [
# TODO(cmspence): project_shell, cell_shell, device_shell, user_shell, etc.
projects_shell,
clouds_shell,
regions_shell,
devices_shell,
hosts_shell,
cells_shell,
]

View File

@ -1,12 +0,0 @@
# 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.
"""Test suite for Craton client and shell."""

View File

@ -1,23 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright 2010-2011 OpenStack Foundation
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
#
# 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.
"""Base TestCase for all cratonclient tests."""
from oslotest import base
class TestCase(base.BaseTestCase):
"""Test case base class for all unit tests."""

View File

@ -1,19 +0,0 @@
================================
cratonclient Betamax Cassettes
================================
This directory contains the cassettes that were recorded by Betamax_ for
integration level tests of the python-cratonclient library.
For more information about these cassettes, please refer to the Betamax
documentation_. For specific information about what information is stored in a
cassette and its structure, please read `"What is a cassette?"`_
.. links
.. _Betamax:
https://pypi.org/project/betamax
.. _documentation:
https://betamax.readthedocs.io/en/latest/
.. _"What is a cassette?":
https://betamax.readthedocs.io/en/latest/cassettes.html

View File

@ -1,200 +0,0 @@
http_interactions:
- recorded_at: '2017-03-21T15:20:14'
request:
body:
encoding: utf-8
string: "{\n \"name\": \"cells-cloud-TestCells-test_create\"\n}"
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '45'
Content-Type: application/json
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: POST
uri: <craton-url>/clouds
response:
body:
encoding: null
string: "{\n \"project_id\": \"<craton-demo-project>\",\n \"name\": \"cells-cloud-TestCells-test_create\"\
,\n \"note\": null,\n \"updated_at\": null,\n \"variables\": {},\n \"\
created_at\": \"2017-03-21T00:10:38.118921\",\n \"id\": 14\n}"
headers:
Content-Length: '221'
Content-Type: application/json
Date: Tue, 21 Mar 2017 00:10:38 GMT
Location: <craton-url>/clouds/14
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-f9b76b9e-566d-4280-a969-06e60741e34c
status:
code: 201
message: CREATED
url: <craton-url>/clouds
- recorded_at: '2017-03-21T15:20:14'
request:
body:
encoding: utf-8
string: "{\n \"name\": \"cells-region-TestCells-test_create\",\n \"cloud_id\"\
: 14\n}"
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '62'
Content-Type: application/json
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: POST
uri: <craton-url>/regions
response:
body:
encoding: null
string: "{\n \"project_id\": \"<craton-demo-project>\",\n \"name\": \"cells-region-TestCells-test_create\"\
,\n \"note\": null,\n \"updated_at\": null,\n \"variables\": {},\n \"\
created_at\": \"2017-03-21T00:10:38.212043\",\n \"id\": 10,\n \"cloud_id\"\
: 14\n}"
headers:
Content-Length: '240'
Content-Type: application/json
Date: Tue, 21 Mar 2017 00:10:38 GMT
Location: <craton-url>/regions/10
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-689fd261-4625-4b28-b973-54c3ff7b9025
status:
code: 201
message: CREATED
url: <craton-url>/regions
- recorded_at: '2017-03-21T15:20:14'
request:
body:
encoding: utf-8
string: "{\n \"variables\": {\n \"a\": \"b\"\n },\n \"name\": \"cell-0\"\
,\n \"cloud_id\": 14,\n \"region_id\": 10,\n \"note\": \"This is a test\
\ cell. There are many like it, but this is mine\"\n}"
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '149'
Content-Type: application/json
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: POST
uri: <craton-url>/cells
response:
body:
encoding: null
string: "{\n \"project_id\": \"<craton-demo-project>\",\n \"name\": \"cell-0\"\
,\n \"note\": \"This is a test cell. There are many like it, but this is\
\ mine\",\n \"updated_at\": null,\n \"variables\": {\n \"a\": \"b\"\n\
\ },\n \"created_at\": \"2017-03-21T00:10:38.345855\",\n \"id\": 30,\n\
\ \"region_id\": 10,\n \"cloud_id\": 14\n}"
headers:
Content-Length: '306'
Content-Type: application/json
Date: Tue, 21 Mar 2017 00:10:38 GMT
Location: <craton-url>/cells/30
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-d5262b2f-63bd-4eb5-80a2-d0d5a9d7c79e
status:
code: 201
message: CREATED
url: <craton-url>/cells
- recorded_at: '2017-03-21T15:20:14'
request:
body:
encoding: utf-8
string: ''
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '0'
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: DELETE
uri: <craton-url>/cells/30
response:
body:
encoding: null
string: ''
headers:
Content-Length: '0'
Content-Type: application/json
Date: Tue, 21 Mar 2017 00:10:38 GMT
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-6431152a-df0a-461b-8983-7a92eb58939b
status:
code: 204
message: NO CONTENT
url: <craton-url>/cells/30
- recorded_at: '2017-03-21T15:20:14'
request:
body:
encoding: utf-8
string: ''
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '0'
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: DELETE
uri: <craton-url>/regions/10
response:
body:
encoding: null
string: ''
headers:
Content-Length: '0'
Content-Type: application/json
Date: Tue, 21 Mar 2017 00:10:38 GMT
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-92e9871b-7c2b-49ad-a778-4ec9f8f7047d
status:
code: 204
message: NO CONTENT
url: <craton-url>/regions/10
- recorded_at: '2017-03-21T15:20:14'
request:
body:
encoding: utf-8
string: ''
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '0'
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: DELETE
uri: <craton-url>/clouds/14
response:
body:
encoding: null
string: ''
headers:
Content-Length: '0'
Content-Type: application/json
Date: Tue, 21 Mar 2017 00:10:38 GMT
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-8c1c3e29-5d12-40a0-b7df-6ecf991e431b
status:
code: 204
message: NO CONTENT
url: <craton-url>/clouds/14
recorded_with: betamax/0.8.0

View File

@ -1,227 +0,0 @@
http_interactions:
- recorded_at: '2017-03-21T15:20:14'
request:
body:
encoding: utf-8
string: "{\n \"name\": \"cells-cloud-TestCells-test_delete\"\n}"
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '45'
Content-Type: application/json
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: POST
uri: <craton-url>/clouds
response:
body:
encoding: null
string: "{\n \"project_id\": \"<craton-demo-project>\",\n \"name\": \"cells-cloud-TestCells-test_delete\"\
,\n \"note\": null,\n \"updated_at\": null,\n \"variables\": {},\n \"\
created_at\": \"2017-03-21T00:10:38.237204\",\n \"id\": 15\n}"
headers:
Content-Length: '221'
Content-Type: application/json
Date: Tue, 21 Mar 2017 00:10:38 GMT
Location: <craton-url>/clouds/15
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-095849cf-b6ae-4c5f-a616-491a4381d9a7
status:
code: 201
message: CREATED
url: <craton-url>/clouds
- recorded_at: '2017-03-21T15:20:14'
request:
body:
encoding: utf-8
string: "{\n \"name\": \"cells-region-TestCells-test_delete\",\n \"cloud_id\"\
: 15\n}"
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '62'
Content-Type: application/json
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: POST
uri: <craton-url>/regions
response:
body:
encoding: null
string: "{\n \"project_id\": \"<craton-demo-project>\",\n \"name\": \"cells-region-TestCells-test_delete\"\
,\n \"note\": null,\n \"updated_at\": null,\n \"variables\": {},\n \"\
created_at\": \"2017-03-21T00:10:38.370396\",\n \"id\": 11,\n \"cloud_id\"\
: 15\n}"
headers:
Content-Length: '240'
Content-Type: application/json
Date: Tue, 21 Mar 2017 00:10:38 GMT
Location: <craton-url>/regions/11
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-2d162165-4187-4d44-bea3-9c1760438c60
status:
code: 201
message: CREATED
url: <craton-url>/regions
- recorded_at: '2017-03-21T15:20:14'
request:
body:
encoding: utf-8
string: "{\n \"name\": \"cell-to-delete\",\n \"cloud_id\": 15,\n \"region_id\"\
: 11\n}"
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '59'
Content-Type: application/json
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: POST
uri: <craton-url>/cells
response:
body:
encoding: null
string: "{\n \"project_id\": \"<craton-demo-project>\",\n \"name\": \"cell-to-delete\"\
,\n \"note\": null,\n \"updated_at\": null,\n \"variables\": {},\n \"\
created_at\": \"2017-03-21T00:10:38.487204\",\n \"id\": 33,\n \"region_id\"\
: 11,\n \"cloud_id\": 15\n}"
headers:
Content-Length: '239'
Content-Type: application/json
Date: Tue, 21 Mar 2017 00:10:38 GMT
Location: <craton-url>/cells/33
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-81fee3a8-7f8c-4424-b8d3-d790b294efd6
status:
code: 201
message: CREATED
url: <craton-url>/cells
- recorded_at: '2017-03-21T15:20:14'
request:
body:
encoding: utf-8
string: ''
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '0'
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: DELETE
uri: <craton-url>/cells/33
response:
body:
encoding: null
string: ''
headers:
Content-Length: '0'
Content-Type: application/json
Date: Tue, 21 Mar 2017 00:10:38 GMT
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-755ac568-87ef-4a36-ae4c-9639e8306df7
status:
code: 204
message: NO CONTENT
url: <craton-url>/cells/33
- recorded_at: '2017-03-21T15:20:14'
request:
body:
encoding: utf-8
string: ''
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: GET
uri: <craton-url>/cells/33
response:
body:
encoding: utf-8
string: '{"message": "Not Found", "status": 404}'
headers:
Content-Length: '46'
Content-Type: text/html; charset=utf-8
Date: Tue, 21 Mar 2017 00:10:38 GMT
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-67c08f6b-a2f5-4ce9-a12e-c34119d9f521
status:
code: 404
message: NOT FOUND
url: <craton-url>/cells/33
- recorded_at: '2017-03-21T15:20:14'
request:
body:
encoding: utf-8
string: ''
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '0'
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: DELETE
uri: <craton-url>/regions/11
response:
body:
encoding: null
string: ''
headers:
Content-Length: '0'
Content-Type: application/json
Date: Tue, 21 Mar 2017 00:10:38 GMT
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-c97e15dc-9562-4e9e-8a73-9e63fc7e2951
status:
code: 204
message: NO CONTENT
url: <craton-url>/regions/11
- recorded_at: '2017-03-21T15:20:15'
request:
body:
encoding: utf-8
string: ''
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '0'
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: DELETE
uri: <craton-url>/clouds/15
response:
body:
encoding: null
string: ''
headers:
Content-Length: '0'
Content-Type: application/json
Date: Tue, 21 Mar 2017 00:10:38 GMT
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-0dfe349e-fe39-40c5-bfab-ccca0456f142
status:
code: 204
message: NO CONTENT
url: <craton-url>/clouds/15
recorded_with: betamax/0.8.0

View File

@ -1,234 +0,0 @@
http_interactions:
- recorded_at: '2017-03-21T15:20:14'
request:
body:
encoding: utf-8
string: "{\n \"name\": \"cells-cloud-TestCells-test_update_existing_cell\"\n\
}"
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '59'
Content-Type: application/json
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: POST
uri: <craton-url>/clouds
response:
body:
encoding: null
string: "{\n \"project_id\": \"<craton-demo-project>\",\n \"name\": \"cells-cloud-TestCells-test_update_existing_cell\"\
,\n \"note\": null,\n \"updated_at\": null,\n \"variables\": {},\n \"\
created_at\": \"2017-03-21T00:10:38.318952\",\n \"id\": 16\n}"
headers:
Content-Length: '235'
Content-Type: application/json
Date: Tue, 21 Mar 2017 00:10:38 GMT
Location: <craton-url>/clouds/16
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-5b08fd6a-d2c2-4ec1-bddc-c92c2d544e2f
status:
code: 201
message: CREATED
url: <craton-url>/clouds
- recorded_at: '2017-03-21T15:20:14'
request:
body:
encoding: utf-8
string: "{\n \"name\": \"cells-region-TestCells-test_update_existing_cell\"\
,\n \"cloud_id\": 16\n}"
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '76'
Content-Type: application/json
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: POST
uri: <craton-url>/regions
response:
body:
encoding: null
string: "{\n \"project_id\": \"<craton-demo-project>\",\n \"name\": \"cells-region-TestCells-test_update_existing_cell\"\
,\n \"note\": null,\n \"updated_at\": null,\n \"variables\": {},\n \"\
created_at\": \"2017-03-21T00:10:38.443485\",\n \"id\": 12,\n \"cloud_id\"\
: 16\n}"
headers:
Content-Length: '254'
Content-Type: application/json
Date: Tue, 21 Mar 2017 00:10:38 GMT
Location: <craton-url>/regions/12
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-0b5cffb5-81f1-4580-847a-8d282a3479ae
status:
code: 201
message: CREATED
url: <craton-url>/regions
- recorded_at: '2017-03-21T15:20:14'
request:
body:
encoding: utf-8
string: "{\n \"variables\": {\n \"out-with\": \"the-old\"\n },\n \"name\"\
: \"cell-to-update\",\n \"cloud_id\": 16,\n \"region_id\": 12,\n \"note\"\
: \"Original note\"\n}"
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '122'
Content-Type: application/json
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: POST
uri: <craton-url>/cells
response:
body:
encoding: null
string: "{\n \"project_id\": \"<craton-demo-project>\",\n \"name\": \"cell-to-update\"\
,\n \"note\": \"Original note\",\n \"updated_at\": null,\n \"variables\"\
: {\n \"out-with\": \"the-old\"\n },\n \"created_at\": \"2017-03-21T00:10:38.584541\"\
,\n \"id\": 36,\n \"region_id\": 12,\n \"cloud_id\": 16\n}"
headers:
Content-Length: '279'
Content-Type: application/json
Date: Tue, 21 Mar 2017 00:10:38 GMT
Location: <craton-url>/cells/36
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-091847d6-f965-4ee1-a898-0bbb61c1dd87
status:
code: 201
message: CREATED
url: <craton-url>/cells
- recorded_at: '2017-03-21T15:20:14'
request:
body:
encoding: utf-8
string: "{\n \"note\": \"Updated note.\"\n}"
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '25'
Content-Type: application/json
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: PUT
uri: <craton-url>/cells/36
response:
body:
encoding: null
string: "{\n \"project_id\": \"<craton-demo-project>\",\n \"name\": \"cell-to-update\"\
,\n \"note\": \"Updated note.\",\n \"updated_at\": \"2017-03-21T00:10:38.693373\"\
,\n \"created_at\": \"2017-03-21T00:10:38.000000\",\n \"id\": 36,\n \"\
region_id\": 12,\n \"cloud_id\": 16\n}"
headers:
Content-Length: '255'
Content-Type: application/json
Date: Tue, 21 Mar 2017 00:10:38 GMT
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-893bb83f-8327-4047-8dd3-d304f16812b1
status:
code: 200
message: OK
url: <craton-url>/cells/36
- recorded_at: '2017-03-21T15:20:14'
request:
body:
encoding: utf-8
string: ''
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '0'
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: DELETE
uri: <craton-url>/cells/36
response:
body:
encoding: null
string: ''
headers:
Content-Length: '0'
Content-Type: application/json
Date: Tue, 21 Mar 2017 00:10:38 GMT
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-31e44ca6-df4c-4a07-88b1-2b5e6afd06e0
status:
code: 204
message: NO CONTENT
url: <craton-url>/cells/36
- recorded_at: '2017-03-21T15:20:15'
request:
body:
encoding: utf-8
string: ''
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '0'
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: DELETE
uri: <craton-url>/regions/12
response:
body:
encoding: null
string: ''
headers:
Content-Length: '0'
Content-Type: application/json
Date: Tue, 21 Mar 2017 00:10:38 GMT
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-d89882d3-a917-4d72-8028-29e8f9638397
status:
code: 204
message: NO CONTENT
url: <craton-url>/regions/12
- recorded_at: '2017-03-21T15:20:15'
request:
body:
encoding: utf-8
string: ''
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '0'
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: DELETE
uri: <craton-url>/clouds/16
response:
body:
encoding: null
string: ''
headers:
Content-Length: '0'
Content-Type: application/json
Date: Tue, 21 Mar 2017 00:10:39 GMT
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-d1881451-1653-4c6d-b533-d5dc59382939
status:
code: 204
message: NO CONTENT
url: <craton-url>/clouds/16
recorded_with: betamax/0.8.0

View File

@ -1,68 +0,0 @@
http_interactions:
- recorded_at: '2017-03-21T15:37:24'
request:
body:
encoding: utf-8
string: "{\n \"variables\": {\n \"cloud-var\": \"var-value\"\n },\n \"\
note\": \"This is a test cloud.\",\n \"name\": \"cloud-creation\"\n}"
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '100'
Content-Type: application/json
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: POST
uri: <craton-url>/clouds
response:
body:
encoding: null
string: "{\n \"updated_at\": null,\n \"project_id\": \"<craton-demo-project>\"\
,\n \"note\": \"This is a test cloud.\",\n \"id\": 51,\n \"created_at\"\
: \"2017-03-21T00:27:49.238178\",\n \"variables\": {\n \"cloud-var\":\
\ \"var-value\"\n },\n \"name\": \"cloud-creation\"\n}"
headers:
Content-Length: '253'
Content-Type: application/json
Date: Tue, 21 Mar 2017 00:27:49 GMT
Location: <craton-url>/clouds/51
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-3bd28970-bd1a-465a-9598-d42db902336e
status:
code: 201
message: CREATED
url: <craton-url>/clouds
- recorded_at: '2017-03-21T15:37:24'
request:
body:
encoding: utf-8
string: ''
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '0'
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: DELETE
uri: <craton-url>/clouds/51
response:
body:
encoding: null
string: ''
headers:
Content-Length: '0'
Content-Type: application/json
Date: Tue, 21 Mar 2017 00:27:49 GMT
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-50ddeb39-ff4e-4070-8f13-f2664e27075f
status:
code: 204
message: NO CONTENT
url: <craton-url>/clouds/51
recorded_with: betamax/0.8.0

View File

@ -1,95 +0,0 @@
http_interactions:
- recorded_at: '2017-03-21T15:37:24'
request:
body:
encoding: utf-8
string: "{\n \"name\": \"cloud-deletion\"\n}"
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '26'
Content-Type: application/json
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: POST
uri: <craton-url>/clouds
response:
body:
encoding: null
string: "{\n \"updated_at\": null,\n \"project_id\": \"<craton-demo-project>\"\
,\n \"note\": null,\n \"id\": 54,\n \"created_at\": \"2017-03-21T00:27:49.348024\"\
,\n \"variables\": {},\n \"name\": \"cloud-deletion\"\n}"
headers:
Content-Length: '202'
Content-Type: application/json
Date: Tue, 21 Mar 2017 00:27:49 GMT
Location: <craton-url>/clouds/54
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-f495907c-a15f-4645-8c1b-6fe6c5364cf1
status:
code: 201
message: CREATED
url: <craton-url>/clouds
- recorded_at: '2017-03-21T15:37:24'
request:
body:
encoding: utf-8
string: ''
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '0'
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: DELETE
uri: <craton-url>/clouds/54
response:
body:
encoding: null
string: ''
headers:
Content-Length: '0'
Content-Type: application/json
Date: Tue, 21 Mar 2017 00:27:49 GMT
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-176a55f2-30a5-48a0-b606-66d7785e385b
status:
code: 204
message: NO CONTENT
url: <craton-url>/clouds/54
- recorded_at: '2017-03-21T15:37:24'
request:
body:
encoding: utf-8
string: ''
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: GET
uri: <craton-url>/clouds/54
response:
body:
encoding: utf-8
string: '{"status": 404, "message": "Not Found"}'
headers:
Content-Length: '46'
Content-Type: text/html; charset=utf-8
Date: Tue, 21 Mar 2017 00:27:49 GMT
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-247795f2-7474-49c6-8e18-5390c12deac3
status:
code: 404
message: NOT FOUND
url: <craton-url>/clouds/54
recorded_with: betamax/0.8.0

View File

@ -1,101 +0,0 @@
http_interactions:
- recorded_at: '2017-03-21T15:37:23'
request:
body:
encoding: utf-8
string: "{\n \"note\": \"Original note.\",\n \"name\": \"cloud-to-update\"\
\n}"
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '53'
Content-Type: application/json
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: POST
uri: <craton-url>/clouds
response:
body:
encoding: null
string: "{\n \"updated_at\": null,\n \"project_id\": \"<craton-demo-project>\"\
,\n \"note\": \"Original note.\",\n \"id\": 44,\n \"created_at\": \"2017-03-21T00:27:48.950481\"\
,\n \"variables\": {},\n \"name\": \"cloud-to-update\"\n}"
headers:
Content-Length: '215'
Content-Type: application/json
Date: Tue, 21 Mar 2017 00:27:48 GMT
Location: <craton-url>/clouds/44
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-fa3c730f-aae8-463a-90a6-eecb670dff0d
status:
code: 201
message: CREATED
url: <craton-url>/clouds
- recorded_at: '2017-03-21T15:37:23'
request:
body:
encoding: utf-8
string: "{\n \"note\": \"Updated note.\",\n \"name\": \"updated-cloud\"\n}"
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '50'
Content-Type: application/json
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: PUT
uri: <craton-url>/clouds/44
response:
body:
encoding: null
string: "{\n \"updated_at\": \"2017-03-21T00:27:49.011515\",\n \"project_id\"\
: \"<craton-demo-project>\",\n \"note\": \"Updated note.\",\n \"id\": 44,\n\
\ \"created_at\": \"2017-03-21T00:27:48.000000\",\n \"name\": \"updated-cloud\"\
\n}"
headers:
Content-Length: '217'
Content-Type: application/json
Date: Tue, 21 Mar 2017 00:27:49 GMT
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-5acfb733-4eee-4a77-8227-97a7781227c9
status:
code: 200
message: OK
url: <craton-url>/clouds/44
- recorded_at: '2017-03-21T15:37:23'
request:
body:
encoding: utf-8
string: ''
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '0'
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: DELETE
uri: <craton-url>/clouds/44
response:
body:
encoding: null
string: ''
headers:
Content-Length: '0'
Content-Type: application/json
Date: Tue, 21 Mar 2017 00:27:49 GMT
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-88a3a81c-f0bc-400f-8459-322890873ba8
status:
code: 204
message: NO CONTENT
url: <craton-url>/clouds/44
recorded_with: betamax/0.8.0

View File

@ -1,201 +0,0 @@
http_interactions:
- recorded_at: '2017-03-21T14:50:37'
request:
body:
encoding: utf-8
string: "{\n \"name\": \"cloud-TestHosts-test_create\"\n}"
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '39'
Content-Type: application/json
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: POST
uri: <craton-url>/clouds
response:
body:
encoding: null
string: "{\n \"variables\": {},\n \"id\": 6,\n \"name\": \"cloud-TestHosts-test_create\"\
,\n \"note\": null,\n \"created_at\": \"2017-03-20T23:40:58.854217\",\n\
\ \"updated_at\": null,\n \"project_id\": \"<craton-demo-project>\"\n}"
headers:
Content-Length: '214'
Content-Type: application/json
Date: Mon, 20 Mar 2017 23:40:58 GMT
Location: <craton-url>/clouds/6
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-1993267c-c04e-482d-a2f7-5a3199f45fe3
status:
code: 201
message: CREATED
url: <craton-url>/clouds
- recorded_at: '2017-03-21T14:50:37'
request:
body:
encoding: utf-8
string: "{\n \"name\": \"region-TestHosts-test_create\",\n \"cloud_id\": 6\n\
}"
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '55'
Content-Type: application/json
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: POST
uri: <craton-url>/regions
response:
body:
encoding: null
string: "{\n \"variables\": {},\n \"id\": 2,\n \"name\": \"region-TestHosts-test_create\"\
,\n \"note\": null,\n \"created_at\": \"2017-03-20T23:40:58.893218\",\n\
\ \"updated_at\": null,\n \"cloud_id\": 6,\n \"project_id\": \"<craton-demo-project>\"\
\n}"
headers:
Content-Length: '232'
Content-Type: application/json
Date: Mon, 20 Mar 2017 23:40:58 GMT
Location: <craton-url>/regions/2
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-aa293966-07d6-4832-b294-68d277e7266c
status:
code: 201
message: CREATED
url: <craton-url>/regions
- recorded_at: '2017-03-21T14:50:37'
request:
body:
encoding: utf-8
string: "{\n \"region_id\": 2,\n \"device_type\": \"server\",\n \"name\"\
: \"host-0\",\n \"cloud_id\": 6,\n \"ip_address\": \"127.0.1.0\"\n}"
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '101'
Content-Type: application/json
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: POST
uri: <craton-url>/hosts
response:
body:
encoding: null
string: "{\n \"id\": 2,\n \"ip_address\": \"127.0.1.0\",\n \"region_id\"\
: 2,\n \"active\": true,\n \"cloud_id\": 6,\n \"project_id\": \"<craton-demo-project>\"\
,\n \"variables\": {},\n \"parent_id\": null,\n \"updated_at\": null,\n\
\ \"name\": \"host-0\",\n \"cell_id\": null,\n \"device_type\": \"server\"\
,\n \"note\": null,\n \"links\": [\n {\n \"href\": \"<craton-url>/regions/2\"\
,\n \"rel\": \"up\"\n }\n ],\n \"created_at\": \"2017-03-20T23:40:58.952662\"\
\n}"
headers:
Content-Length: '442'
Content-Type: application/json
Date: Mon, 20 Mar 2017 23:40:58 GMT
Location: <craton-url>/hosts/2
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-98391187-c6e9-4002-936c-ba3b93ee4081
status:
code: 201
message: CREATED
url: <craton-url>/hosts
- recorded_at: '2017-03-21T14:50:37'
request:
body:
encoding: utf-8
string: ''
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '0'
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: DELETE
uri: <craton-url>/hosts/2
response:
body:
encoding: null
string: ''
headers:
Content-Length: '0'
Content-Type: application/json
Date: Mon, 20 Mar 2017 23:40:59 GMT
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-949fad55-ce5e-47fb-8277-13d6f88286a2
status:
code: 204
message: NO CONTENT
url: <craton-url>/hosts/2
- recorded_at: '2017-03-21T14:50:37'
request:
body:
encoding: utf-8
string: ''
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '0'
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: DELETE
uri: <craton-url>/regions/2
response:
body:
encoding: null
string: ''
headers:
Content-Length: '0'
Content-Type: application/json
Date: Mon, 20 Mar 2017 23:40:59 GMT
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-dbed3d00-6002-493f-84d0-40b572f500f1
status:
code: 204
message: NO CONTENT
url: <craton-url>/regions/2
- recorded_at: '2017-03-21T14:50:37'
request:
body:
encoding: utf-8
string: ''
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '0'
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: DELETE
uri: <craton-url>/clouds/6
response:
body:
encoding: null
string: ''
headers:
Content-Length: '0'
Content-Type: application/json
Date: Mon, 20 Mar 2017 23:40:59 GMT
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-f70acbe0-739f-4bba-9f56-707032a3211d
status:
code: 204
message: NO CONTENT
url: <craton-url>/clouds/6
recorded_with: betamax/0.8.0

View File

@ -1,231 +0,0 @@
http_interactions:
- recorded_at: '2017-03-21T14:50:37'
request:
body:
encoding: utf-8
string: "{\n \"name\": \"cloud-TestHosts-test_delete\"\n}"
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '39'
Content-Type: application/json
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: POST
uri: <craton-url>/clouds
response:
body:
encoding: null
string: "{\n \"variables\": {},\n \"id\": 10,\n \"name\": \"cloud-TestHosts-test_delete\"\
,\n \"note\": null,\n \"created_at\": \"2017-03-20T23:40:59.563744\",\n\
\ \"updated_at\": null,\n \"project_id\": \"<craton-demo-project>\"\n}"
headers:
Content-Length: '215'
Content-Type: application/json
Date: Mon, 20 Mar 2017 23:40:59 GMT
Location: <craton-url>/clouds/10
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-8c61f8a2-83bb-466e-8cdc-b24b38e7de74
status:
code: 201
message: CREATED
url: <craton-url>/clouds
- recorded_at: '2017-03-21T14:50:37'
request:
body:
encoding: utf-8
string: "{\n \"name\": \"region-TestHosts-test_delete\",\n \"cloud_id\": 10\n\
}"
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '56'
Content-Type: application/json
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: POST
uri: <craton-url>/regions
response:
body:
encoding: null
string: "{\n \"variables\": {},\n \"id\": 6,\n \"name\": \"region-TestHosts-test_delete\"\
,\n \"note\": null,\n \"created_at\": \"2017-03-20T23:40:59.702986\",\n\
\ \"updated_at\": null,\n \"cloud_id\": 10,\n \"project_id\": \"<craton-demo-project>\"\
\n}"
headers:
Content-Length: '233'
Content-Type: application/json
Date: Mon, 20 Mar 2017 23:40:59 GMT
Location: <craton-url>/regions/6
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-6f6bf38e-9cb4-48ac-b915-e0fec77c5d98
status:
code: 201
message: CREATED
url: <craton-url>/regions
- recorded_at: '2017-03-21T14:50:38'
request:
body:
encoding: utf-8
string: "{\n \"region_id\": 6,\n \"device_type\": \"server\",\n \"name\"\
: \"host-to-delete\",\n \"cloud_id\": 10,\n \"ip_address\": \"127.0.1.0\"\
\n}"
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '110'
Content-Type: application/json
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: POST
uri: <craton-url>/hosts
response:
body:
encoding: null
string: "{\n \"id\": 7,\n \"ip_address\": \"127.0.1.0\",\n \"region_id\"\
: 6,\n \"active\": true,\n \"cloud_id\": 10,\n \"project_id\": \"<craton-demo-project>\"\
,\n \"variables\": {},\n \"parent_id\": null,\n \"updated_at\": null,\n\
\ \"name\": \"host-to-delete\",\n \"cell_id\": null,\n \"device_type\"\
: \"server\",\n \"note\": null,\n \"links\": [\n {\n \"href\": \"\
<craton-url>/regions/6\",\n \"rel\": \"up\"\n }\n ],\n \"created_at\"\
: \"2017-03-20T23:40:59.817351\"\n}"
headers:
Content-Length: '451'
Content-Type: application/json
Date: Mon, 20 Mar 2017 23:40:59 GMT
Location: <craton-url>/hosts/7
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-0a63c2bf-492e-4bc0-a49a-438bc81b7656
status:
code: 201
message: CREATED
url: <craton-url>/hosts
- recorded_at: '2017-03-21T14:50:38'
request:
body:
encoding: utf-8
string: ''
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '0'
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: DELETE
uri: <craton-url>/hosts/7
response:
body:
encoding: null
string: ''
headers:
Content-Length: '0'
Content-Type: application/json
Date: Mon, 20 Mar 2017 23:40:59 GMT
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-269e2521-d1d5-4312-a415-b2e8b7a91e6e
status:
code: 204
message: NO CONTENT
url: <craton-url>/hosts/7
- recorded_at: '2017-03-21T14:50:38'
request:
body:
encoding: utf-8
string: ''
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: GET
uri: <craton-url>/hosts/7
response:
body:
encoding: utf-8
string: '{"status": 404, "message": "Not Found"}'
headers:
Content-Length: '46'
Content-Type: text/html; charset=utf-8
Date: Mon, 20 Mar 2017 23:41:00 GMT
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-3ae97e93-0cca-4e97-afad-3ac4312d19b1
status:
code: 404
message: NOT FOUND
url: <craton-url>/hosts/7
- recorded_at: '2017-03-21T14:50:38'
request:
body:
encoding: utf-8
string: ''
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '0'
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: DELETE
uri: <craton-url>/regions/6
response:
body:
encoding: null
string: ''
headers:
Content-Length: '0'
Content-Type: application/json
Date: Mon, 20 Mar 2017 23:41:00 GMT
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-dc103d8f-ddc7-45a9-a675-2a20518b2a68
status:
code: 204
message: NO CONTENT
url: <craton-url>/regions/6
- recorded_at: '2017-03-21T14:50:38'
request:
body:
encoding: utf-8
string: ''
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '0'
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: DELETE
uri: <craton-url>/clouds/10
response:
body:
encoding: null
string: ''
headers:
Content-Length: '0'
Content-Type: application/json
Date: Mon, 20 Mar 2017 23:41:00 GMT
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-332fa9fc-e12f-46e9-922b-6ec1ee9bc2b3
status:
code: 204
message: NO CONTENT
url: <craton-url>/clouds/10
recorded_with: betamax/0.8.0

View File

@ -1,239 +0,0 @@
http_interactions:
- recorded_at: '2017-03-21T14:50:37'
request:
body:
encoding: utf-8
string: "{\n \"name\": \"cloud-TestHosts-test_update\"\n}"
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '39'
Content-Type: application/json
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: POST
uri: <craton-url>/clouds
response:
body:
encoding: null
string: "{\n \"variables\": {},\n \"id\": 9,\n \"name\": \"cloud-TestHosts-test_update\"\
,\n \"note\": null,\n \"created_at\": \"2017-03-20T23:40:59.516122\",\n\
\ \"updated_at\": null,\n \"project_id\": \"<craton-demo-project>\"\n}"
headers:
Content-Length: '214'
Content-Type: application/json
Date: Mon, 20 Mar 2017 23:40:59 GMT
Location: <craton-url>/clouds/9
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-a1f5018e-9b49-4346-8f74-efee8339b882
status:
code: 201
message: CREATED
url: <craton-url>/clouds
- recorded_at: '2017-03-21T14:50:37'
request:
body:
encoding: utf-8
string: "{\n \"name\": \"region-TestHosts-test_update\",\n \"cloud_id\": 9\n\
}"
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '55'
Content-Type: application/json
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: POST
uri: <craton-url>/regions
response:
body:
encoding: null
string: "{\n \"variables\": {},\n \"id\": 5,\n \"name\": \"region-TestHosts-test_update\"\
,\n \"note\": null,\n \"created_at\": \"2017-03-20T23:40:59.653669\",\n\
\ \"updated_at\": null,\n \"cloud_id\": 9,\n \"project_id\": \"<craton-demo-project>\"\
\n}"
headers:
Content-Length: '232'
Content-Type: application/json
Date: Mon, 20 Mar 2017 23:40:59 GMT
Location: <craton-url>/regions/5
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-c89e94bc-aa5a-4622-9d53-b39dfd35af5b
status:
code: 201
message: CREATED
url: <craton-url>/regions
- recorded_at: '2017-03-21T14:50:38'
request:
body:
encoding: utf-8
string: "{\n \"region_id\": 5,\n \"device_type\": \"server\",\n \"name\"\
: \"host-0\",\n \"cloud_id\": 9,\n \"ip_address\": \"127.0.1.0\"\n}"
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '101'
Content-Type: application/json
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: POST
uri: <craton-url>/hosts
response:
body:
encoding: null
string: "{\n \"id\": 5,\n \"ip_address\": \"127.0.1.0\",\n \"region_id\"\
: 5,\n \"active\": true,\n \"cloud_id\": 9,\n \"project_id\": \"<craton-demo-project>\"\
,\n \"variables\": {},\n \"parent_id\": null,\n \"updated_at\": null,\n\
\ \"name\": \"host-0\",\n \"cell_id\": null,\n \"device_type\": \"server\"\
,\n \"note\": null,\n \"links\": [\n {\n \"href\": \"<craton-url>/regions/5\"\
,\n \"rel\": \"up\"\n }\n ],\n \"created_at\": \"2017-03-20T23:40:59.766218\"\
\n}"
headers:
Content-Length: '442'
Content-Type: application/json
Date: Mon, 20 Mar 2017 23:40:59 GMT
Location: <craton-url>/hosts/5
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-2f98aecc-8f92-4ff4-9837-1b455cd36f86
status:
code: 201
message: CREATED
url: <craton-url>/hosts
- recorded_at: '2017-03-21T14:50:38'
request:
body:
encoding: utf-8
string: "{\n \"note\": \"This is an updated note\",\n \"ip_address\": \"127.0.1.1\"\
\n}"
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '62'
Content-Type: application/json
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: PUT
uri: <craton-url>/hosts/5
response:
body:
encoding: null
string: "{\n \"id\": 5,\n \"ip_address\": \"127.0.1.1\",\n \"region_id\"\
: 5,\n \"active\": true,\n \"cloud_id\": 9,\n \"project_id\": \"<craton-demo-project>\"\
,\n \"parent_id\": null,\n \"updated_at\": \"2017-03-20T23:40:59.877079\"\
,\n \"name\": \"host-0\",\n \"cell_id\": null,\n \"device_type\": \"server\"\
,\n \"note\": \"This is an updated note\",\n \"links\": [\n {\n \
\ \"href\": \"<craton-url>/regions/5\",\n \"rel\": \"up\"\n }\n ],\n\
\ \"created_at\": \"2017-03-20T23:40:59.000000\"\n}"
headers:
Content-Length: '468'
Content-Type: application/json
Date: Mon, 20 Mar 2017 23:40:59 GMT
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-38ce18bc-7de7-436f-a6a4-944b906effe3
status:
code: 200
message: OK
url: <craton-url>/hosts/5
- recorded_at: '2017-03-21T14:50:38'
request:
body:
encoding: utf-8
string: ''
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '0'
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: DELETE
uri: <craton-url>/hosts/5
response:
body:
encoding: null
string: ''
headers:
Content-Length: '0'
Content-Type: application/json
Date: Mon, 20 Mar 2017 23:40:59 GMT
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-e2c4967f-aca1-4065-8e66-42efb79e6754
status:
code: 204
message: NO CONTENT
url: <craton-url>/hosts/5
- recorded_at: '2017-03-21T14:50:38'
request:
body:
encoding: utf-8
string: ''
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '0'
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: DELETE
uri: <craton-url>/regions/5
response:
body:
encoding: null
string: ''
headers:
Content-Length: '0'
Content-Type: application/json
Date: Mon, 20 Mar 2017 23:41:00 GMT
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-6bca4f72-9c55-4bf8-affd-25492391be45
status:
code: 204
message: NO CONTENT
url: <craton-url>/regions/5
- recorded_at: '2017-03-21T14:50:38'
request:
body:
encoding: utf-8
string: ''
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '0'
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: DELETE
uri: <craton-url>/clouds/9
response:
body:
encoding: null
string: ''
headers:
Content-Length: '0'
Content-Type: application/json
Date: Mon, 20 Mar 2017 23:41:00 GMT
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-de348783-f7ce-488b-a1b1-28ee8b7f3f37
status:
code: 204
message: NO CONTENT
url: <craton-url>/clouds/9
recorded_with: betamax/0.8.0

View File

@ -1,163 +0,0 @@
http_interactions:
- recorded_at: '2017-03-21T15:52:13'
request:
body:
encoding: utf-8
string: "{\n \"name\": \"cloud-TestRegions-test_create\"\n}"
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '41'
Content-Type: application/json
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: POST
uri: <craton-url>/clouds
response:
body:
encoding: null
string: "{\n \"variables\": {},\n \"created_at\": \"2017-03-21T00:42:39.791662\"\
,\n \"note\": null,\n \"updated_at\": null,\n \"project_id\": \"<craton-demo-project>\"\
,\n \"name\": \"cloud-TestRegions-test_create\",\n \"id\": 147\n}"
headers:
Content-Length: '218'
Content-Type: application/json
Date: Tue, 21 Mar 2017 00:42:39 GMT
Location: <craton-url>/clouds/147
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-6f0519d5-bcb7-4925-b263-78d6b7b8b1b4
status:
code: 201
message: CREATED
url: <craton-url>/clouds
- recorded_at: '2017-03-21T15:52:13'
request:
body:
encoding: utf-8
string: "{\n \"cloud_id\": 147,\n \"name\": \"region-creation\"\n}"
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '44'
Content-Type: application/json
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: POST
uri: <craton-url>/regions
response:
body:
encoding: null
string: "{\n \"variables\": {},\n \"cloud_id\": 147,\n \"created_at\": \"\
2017-03-21T00:42:39.839331\",\n \"note\": null,\n \"project_id\": \"<craton-demo-project>\"\
,\n \"id\": 15,\n \"name\": \"region-creation\",\n \"updated_at\": null\n\
}"
headers:
Content-Length: '222'
Content-Type: application/json
Date: Tue, 21 Mar 2017 00:42:39 GMT
Location: <craton-url>/regions/15
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-a337c517-cdf8-48a8-97fd-0313443c525d
status:
code: 201
message: CREATED
url: <craton-url>/regions
- recorded_at: '2017-03-21T15:52:13'
request:
body:
encoding: utf-8
string: ''
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: GET
uri: <craton-url>/regions/15
response:
body:
encoding: null
string: "{\n \"variables\": {},\n \"cloud_id\": 147,\n \"created_at\": \"\
2017-03-21T00:42:39.000000\",\n \"note\": null,\n \"project_id\": \"<craton-demo-project>\"\
,\n \"id\": 15,\n \"name\": \"region-creation\",\n \"updated_at\": null\n\
}"
headers:
Content-Length: '222'
Content-Type: application/json
Date: Tue, 21 Mar 2017 00:42:39 GMT
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-e5ac546a-10e3-4a4f-8dd6-89026ef5266f
status:
code: 200
message: OK
url: <craton-url>/regions/15
- recorded_at: '2017-03-21T15:52:13'
request:
body:
encoding: utf-8
string: ''
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '0'
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: DELETE
uri: <craton-url>/regions/15
response:
body:
encoding: null
string: ''
headers:
Content-Length: '0'
Content-Type: application/json
Date: Tue, 21 Mar 2017 00:42:39 GMT
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-45859feb-f346-4561-a3e4-bcb54d2bb23d
status:
code: 204
message: NO CONTENT
url: <craton-url>/regions/15
- recorded_at: '2017-03-21T15:52:13'
request:
body:
encoding: utf-8
string: ''
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '0'
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: DELETE
uri: <craton-url>/clouds/147
response:
body:
encoding: null
string: ''
headers:
Content-Length: '0'
Content-Type: application/json
Date: Tue, 21 Mar 2017 00:42:39 GMT
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-00deb216-f14e-44f8-b4cb-fa900c619de7
status:
code: 204
message: NO CONTENT
url: <craton-url>/clouds/147
recorded_with: betamax/0.8.0

View File

@ -1,161 +0,0 @@
http_interactions:
- recorded_at: '2017-03-21T15:52:13'
request:
body:
encoding: utf-8
string: "{\n \"name\": \"cloud-TestRegions-test_delete\"\n}"
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '41'
Content-Type: application/json
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: POST
uri: <craton-url>/clouds
response:
body:
encoding: null
string: "{\n \"variables\": {},\n \"created_at\": \"2017-03-21T00:42:40.048289\"\
,\n \"note\": null,\n \"updated_at\": null,\n \"id\": 148,\n \"name\"\
: \"cloud-TestRegions-test_delete\",\n \"project_id\": \"<craton-demo-project>\"\
\n}"
headers:
Content-Length: '218'
Content-Type: application/json
Date: Tue, 21 Mar 2017 00:42:40 GMT
Location: <craton-url>/clouds/148
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-e0b1ea02-0854-485d-91eb-7a04face6c95
status:
code: 201
message: CREATED
url: <craton-url>/clouds
- recorded_at: '2017-03-21T15:52:13'
request:
body:
encoding: utf-8
string: "{\n \"cloud_id\": 148,\n \"name\": \"region-creation\"\n}"
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '44'
Content-Type: application/json
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: POST
uri: <craton-url>/regions
response:
body:
encoding: null
string: "{\n \"variables\": {},\n \"cloud_id\": 148,\n \"created_at\": \"\
2017-03-21T00:42:40.088796\",\n \"note\": null,\n \"updated_at\": null,\n\
\ \"id\": 16,\n \"name\": \"region-creation\",\n \"project_id\": \"<craton-demo-project>\"\
\n}"
headers:
Content-Length: '222'
Content-Type: application/json
Date: Tue, 21 Mar 2017 00:42:40 GMT
Location: <craton-url>/regions/16
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-c789660d-bf96-4bc2-a94d-4d8f8e5396d9
status:
code: 201
message: CREATED
url: <craton-url>/regions
- recorded_at: '2017-03-21T15:52:13'
request:
body:
encoding: utf-8
string: ''
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '0'
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: DELETE
uri: <craton-url>/regions/16
response:
body:
encoding: null
string: ''
headers:
Content-Length: '0'
Content-Type: application/json
Date: Tue, 21 Mar 2017 00:42:40 GMT
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-6814ce07-367f-4548-bcaa-354d24ed412e
status:
code: 204
message: NO CONTENT
url: <craton-url>/regions/16
- recorded_at: '2017-03-21T15:52:13'
request:
body:
encoding: utf-8
string: ''
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: GET
uri: <craton-url>/regions/16
response:
body:
encoding: utf-8
string: '{"message": "Not Found", "status": 404}'
headers:
Content-Length: '46'
Content-Type: text/html; charset=utf-8
Date: Tue, 21 Mar 2017 00:42:40 GMT
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-2cf37f2b-64cb-4b71-99f7-369f3fda6d33
status:
code: 404
message: NOT FOUND
url: <craton-url>/regions/16
- recorded_at: '2017-03-21T15:52:13'
request:
body:
encoding: utf-8
string: ''
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '0'
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: DELETE
uri: <craton-url>/clouds/148
response:
body:
encoding: null
string: ''
headers:
Content-Length: '0'
Content-Type: application/json
Date: Tue, 21 Mar 2017 00:42:40 GMT
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-03c85c4a-b1a2-4c8e-a59c-ff3548a25212
status:
code: 204
message: NO CONTENT
url: <craton-url>/clouds/148
recorded_with: betamax/0.8.0

View File

@ -1,167 +0,0 @@
http_interactions:
- recorded_at: '2017-03-21T15:52:15'
request:
body:
encoding: utf-8
string: "{\n \"name\": \"cloud-TestRegions-test_update\"\n}"
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '41'
Content-Type: application/json
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: POST
uri: <craton-url>/clouds
response:
body:
encoding: null
string: "{\n \"variables\": {},\n \"created_at\": \"2017-03-21T00:42:41.637962\"\
,\n \"note\": null,\n \"updated_at\": null,\n \"id\": 151,\n \"name\"\
: \"cloud-TestRegions-test_update\",\n \"project_id\": \"<craton-demo-project>\"\
\n}"
headers:
Content-Length: '218'
Content-Type: application/json
Date: Tue, 21 Mar 2017 00:42:41 GMT
Location: <craton-url>/clouds/151
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-2da1f63d-caf8-4ae2-acc1-5cc3c55ae6d9
status:
code: 201
message: CREATED
url: <craton-url>/clouds
- recorded_at: '2017-03-21T15:52:15'
request:
body:
encoding: utf-8
string: "{\n \"cloud_id\": 151,\n \"name\": \"region-to-update\"\n}"
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '45'
Content-Type: application/json
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: POST
uri: <craton-url>/regions
response:
body:
encoding: null
string: "{\n \"variables\": {},\n \"cloud_id\": 151,\n \"created_at\": \"\
2017-03-21T00:42:41.721637\",\n \"note\": null,\n \"updated_at\": null,\n\
\ \"id\": 61,\n \"name\": \"region-to-update\",\n \"project_id\": \"<craton-demo-project>\"\
\n}"
headers:
Content-Length: '223'
Content-Type: application/json
Date: Tue, 21 Mar 2017 00:42:41 GMT
Location: <craton-url>/regions/61
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-fb85c056-6722-4174-bb27-24e390f6583e
status:
code: 201
message: CREATED
url: <craton-url>/regions
- recorded_at: '2017-03-21T15:52:15'
request:
body:
encoding: utf-8
string: "{\n \"name\": \"region_updated\",\n \"note\": \"Here I add my note.\"\
\n}"
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '57'
Content-Type: application/json
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: PUT
uri: <craton-url>/regions/61
response:
body:
encoding: null
string: "{\n \"cloud_id\": 151,\n \"created_at\": \"2017-03-21T00:42:41.000000\"\
,\n \"note\": \"Here I add my note.\",\n \"updated_at\": \"2017-03-21T00:42:41.811053\"\
,\n \"project_id\": \"<craton-demo-project>\",\n \"name\": \"region_updated\"\
,\n \"id\": 61\n}"
headers:
Content-Length: '243'
Content-Type: application/json
Date: Tue, 21 Mar 2017 00:42:41 GMT
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-80453771-42f3-49ee-9bc2-ea9343fe3a9d
status:
code: 200
message: OK
url: <craton-url>/regions/61
- recorded_at: '2017-03-21T15:52:15'
request:
body:
encoding: utf-8
string: ''
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '0'
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: DELETE
uri: <craton-url>/regions/61
response:
body:
encoding: null
string: ''
headers:
Content-Length: '0'
Content-Type: application/json
Date: Tue, 21 Mar 2017 00:42:41 GMT
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-5f9e3a5c-ceed-4fff-8883-b235d5a646c6
status:
code: 204
message: NO CONTENT
url: <craton-url>/regions/61
- recorded_at: '2017-03-21T15:52:15'
request:
body:
encoding: utf-8
string: ''
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: '0'
User-Agent: python-cratonclient/0.0.1
X-Auth-Project: <craton-demo-project>
X-Auth-Token: <craton-demo-token>
X-Auth-User: <craton-demo-username>
method: DELETE
uri: <craton-url>/clouds/151
response:
body:
encoding: null
string: ''
headers:
Content-Length: '0'
Content-Type: application/json
Date: Tue, 21 Mar 2017 00:42:41 GMT
Server: WSGIServer/0.2 CPython/3.5.2
x-openstack-request-id: req-0b8747e5-1455-4748-95f7-3115c831a735
status:
code: 204
message: NO CONTENT
url: <craton-url>/clouds/151
recorded_with: betamax/0.8.0

View File

@ -1 +0,0 @@
"""Integration tests for the cratonclient module."""

View File

@ -1,137 +0,0 @@
# -*- 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.
"""Module containing the base logic for cratonclient integration tests."""
import os
import betamax
from betamax_matchers import json_body
from keystoneauth1.fixture import keystoneauth_betamax as ksabetamax
from cratonclient import auth
from cratonclient import exceptions
from cratonclient.tests import base
from cratonclient.v1 import client
# NOTE(sigmavirus24): This allows us to use ``'json-body'`` as a matcher below
betamax.Betamax.register_request_matcher(json_body.JSONBodyMatcher)
envget = os.environ.get
CRATON_DEMO_USERNAME = envget('CRATON_DEMO_USERNAME', 'demo')
CRATON_DEMO_TOKEN = envget('CRATON_DEMO_TOKEN', 'demo')
CRATON_DEMO_PROJECT = envget('CRATON_DEMO_PROJECT',
'b9f10eca66ac4c279c139d01e65f96b5')
CRATON_ROOT_USERNAME = envget('CRATON_ROOT_USERNAME', 'root')
CRATON_ROOT_TOKEN = envget('CRATON_ROOT_TOKEN', 'root')
CRATON_ROOT_PROJECT = envget('CRATON_ROOT_PROJECT',
'b9f10eca66ac4c279c139d01e65f96b5')
CRATON_URL = envget('CRATON_URL', 'http://127.0.0.1:8080/v1')
class BetamaxTestCase(base.TestCase):
"""This sets up Betamax with Keystoneauth1 fixture for integration tests.
This relies on existing keystoneauth1 integration with the Betamax library
to make recording integration tests easier.
"""
CASSETTE_LIBRARY_DIR = 'cratonclient/tests/cassettes/'
def generate_cassette_name(self):
"""Generate a cassette name for the current test."""
full_test_name = self.id()
module, test_class, test_method = full_test_name.rsplit('.', 2)
return test_class + '-' + test_method
def setUp(self):
"""Set up betamax fixture for cratonclient."""
super(BetamaxTestCase, self).setUp()
self.cassette_name = self.generate_cassette_name()
self.record_mode = envget('BETAMAX_RECORD_MODE', 'once')
self.url = CRATON_URL
self.betamax_fixture = self.useFixture(ksabetamax.BetamaxFixture(
cassette_name=self.cassette_name,
cassette_library_dir=self.CASSETTE_LIBRARY_DIR,
record=self.record_mode,
))
self.demo_credentials = {
'username': CRATON_DEMO_USERNAME,
'token': CRATON_DEMO_TOKEN,
'project': CRATON_DEMO_PROJECT,
}
self.root_credentials = {
'username': CRATON_ROOT_USERNAME,
'token': CRATON_ROOT_TOKEN,
'project': CRATON_ROOT_PROJECT,
}
def assertNotFound(self, func, item_id):
"""Assert that the item referenced by item_id 404s."""
self.assertRaises(exceptions.NotFound, func, item_id)
def cleanupHost(self, host):
"""Add a cleanup task for the host."""
self.addCleanup(self.client.hosts.delete, host.id)
return host
def cleanupCloud(self, cloud):
"""Add a cleanup task for the cloud."""
self.addCleanup(self.client.clouds.delete, cloud.id)
return cloud
def cleanupRegion(self, region):
"""Add a cleanup task for the region."""
self.addCleanup(self.client.regions.delete, region.id)
return region
def cleanupCell(self, cell):
"""Add a cleanup task for the cell."""
self.addCleanup(self.client.cells.delete, cell.id)
return cell
def create_client(self, username, token, project):
"""Create a Craton client using Craton Auth."""
self.session = auth.craton_auth(
username=username,
token=token,
project_id=project,
)
self.client = client.Client(self.session, self.url)
def create_demo_client(self):
"""Set up cratonclient with the demo user."""
self.create_client(**self.demo_credentials)
with betamax.Betamax.configure() as config:
config.define_cassette_placeholder(
'<craton-demo-username>', CRATON_DEMO_USERNAME,
)
config.define_cassette_placeholder(
'<craton-demo-token>', CRATON_DEMO_TOKEN,
)
config.define_cassette_placeholder(
'<craton-demo-project>', CRATON_DEMO_PROJECT,
)
config.define_cassette_placeholder(
'<craton-root-username>', CRATON_ROOT_USERNAME,
)
config.define_cassette_placeholder(
'<craton-root-token>', CRATON_ROOT_TOKEN,
)
config.define_cassette_placeholder(
'<craton-root-project>', CRATON_ROOT_PROJECT,
)
config.define_cassette_placeholder(
'<craton-url>', CRATON_URL,
)

View File

@ -1 +0,0 @@
"""Integration tests for the cratonclient.shell module."""

View File

@ -1,143 +0,0 @@
# 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.
"""Resources for the shell integration tests."""
from argparse import Namespace
import mock
import six
from cratonclient.shell import main
from cratonclient.tests import base
from cratonclient.v1 import variables
class ShellTestCase(base.TestCase):
"""Test case base class for all shell unit tests."""
def shell(self, arg_str, exitcodes=(0,)):
"""Main function for exercising the craton shell."""
with mock.patch('sys.stdout', new=six.StringIO()) as mock_stdout, \
mock.patch('sys.stderr', new=six.StringIO()) as mock_stderr:
try:
main_shell = main.CratonShell()
main_shell.main(arg_str.split())
except SystemExit:
pass
return (mock_stdout.getvalue(), mock_stderr.getvalue())
class VariablesTestCase(base.TestCase):
"""Test Host Variable shell calls."""
def setUp(self):
"""Basic set up for all tests in this suite."""
super(VariablesTestCase, self).setUp()
self.resources = '{}s'.format(self.resource)
self.resource_url = 'http://127.0.0.1/v1/{}/{}' \
.format(self.resources, self.resource_id)
self.variables_url = '{}/variables'.format(self.resource_url)
self.test_args = Namespace(id=self.resource_id, formatter=mock.Mock())
# NOTE(thomasem): Make all calls seem like they come from CLI args
self.stdin_patcher = \
mock.patch('cratonclient.common.cliutils.sys.stdin')
self.patched_stdin = self.stdin_patcher.start()
self.patched_stdin.isatty.return_value = True
# NOTE(thomasem): Mock out a session object to assert resulting API
# calls
self.mock_session = mock.Mock()
self.mock_get_response = self.mock_session.get.return_value
self.mock_put_response = self.mock_session.put.return_value
self.mock_delete_response = self.mock_session.delete.return_value
self.mock_delete_response.status_code = 204
# NOTE(thomasem): Mock out a client to assert craton Python API calls
self.client = mock.Mock()
mock_resource = \
getattr(self.client, self.resources).get.return_value
mock_resource.variables = variables.VariableManager(
self.mock_session, self.resource_url
)
def tearDown(self):
"""Clean up between tests."""
super(VariablesTestCase, self).tearDown()
self.stdin_patcher.stop()
def _get_shell_func_for(self, suffix):
return getattr(
self.shell,
'do_{}_vars_{}'.format(self.resource, suffix)
)
def test_do_vars_get_gets_correct_resource(self):
"""Assert the proper resource is retrieved when calling get."""
self.mock_get_response.json.return_value = \
{"variables": {"foo": "bar"}}
self._get_shell_func_for('get')(self.client, self.test_args)
getattr(self.client, self.resources).get.assert_called_once_with(
vars(self.test_args)['id'])
def test_do_vars_delete_gets_correct_resource(self):
"""Assert the proper resource is retrieved when calling delete."""
self.test_args.variables = ['foo', 'bar']
self._get_shell_func_for('delete')(self.client, self.test_args)
getattr(self.client, self.resources).get.assert_called_once_with(
vars(self.test_args)['id'])
def test_do_vars_update_gets_correct_resource(self):
"""Assert the proper resource is retrieved when calling update."""
self.test_args.variables = ['foo=', 'bar=']
mock_resp_json = {"variables": {"foo": "bar"}}
self.mock_get_response.json.return_value = mock_resp_json
self.mock_put_response.json.return_value = mock_resp_json
self._get_shell_func_for('set')(self.client, self.test_args)
getattr(self.client, self.resources).get.assert_called_once_with(
vars(self.test_args)['id'])
def test_do_vars_get_calls_session_get(self):
"""Assert the proper resource is retrieved when calling get."""
self.mock_get_response.json.return_value = \
{"variables": {"foo": "bar"}}
self._get_shell_func_for('get')(self.client, self.test_args)
self.mock_session.get.assert_called_once_with(self.variables_url)
def test_do_vars_delete_calls_session_delete(self):
"""Verify that <resource>-vars-delete calls expected session.delete."""
self.test_args.variables = ['foo', 'bar']
self._get_shell_func_for('delete')(self.client, self.test_args)
self.mock_session.delete.assert_called_once_with(
self.variables_url,
json=('foo', 'bar'),
params={},
)
def test_do_vars_update_calls_session_put(self):
"""Verify that <resource>-vars-delete calls expected session.delete."""
self.test_args.variables = ['foo=baz', 'bar=boo', 'test=']
mock_resp_json = {"variables": {"foo": "bar"}}
self.mock_get_response.json.return_value = mock_resp_json
self.mock_put_response.json.return_value = mock_resp_json
self._get_shell_func_for('set')(self.client, self.test_args)
self.mock_session.delete.assert_called_once_with(
self.variables_url,
json=('test',),
params={},
)
self.mock_session.put.assert_called_once_with(
self.variables_url,
json={'foo': 'baz', 'bar': 'boo'}
)

View File

@ -1,12 +0,0 @@
# 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.
"""Test suite for Craton client's v1 API shell."""

View File

@ -1,338 +0,0 @@
# 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.
"""Tests for `cratonclient.shell.v1.cells_shell` module."""
import mock
import re
from argparse import Namespace
from testtools import matchers
from cratonclient import exceptions as exc
from cratonclient.shell.v1 import cells_shell
from cratonclient.tests.integration.shell import base
from cratonclient.v1 import cells
class TestCellsShell(base.ShellTestCase):
"""Test our craton cells shell commands."""
re_options = re.DOTALL | re.MULTILINE
cell_valid_fields = None
cell_invalid_fields = None
def setUp(self):
"""Setup required test fixtures."""
super(TestCellsShell, self).setUp()
self.cell_valid_kwargs = {
'project_id': 1,
'region_id': 1,
'name': 'mock_cell',
}
self.cell_valid_fields = Namespace(**self.cell_valid_kwargs)
self.cell_valid_fields.formatter = mock.Mock()
self.cell_invalid_kwargs = {
'project_id': 1,
'region_id': 1,
'name': 'mock_cell',
'invalid_foo': 'ignored',
}
self.cell_invalid_fields = Namespace(**self.cell_invalid_kwargs)
self.cell_invalid_fields.formatter = mock.Mock()
@mock.patch('cratonclient.v1.cells.CellManager.list')
def test_cell_list_success(self, mock_list):
"""Verify that no arguments prints out all project cells."""
self.shell('cell-list -r 1')
self.assertTrue(mock_list.called)
@mock.patch('cratonclient.v1.cells.CellManager.list')
def test_cell_list_parse_param_success(self, mock_list):
"""Verify that success of parsing a subcommand argument."""
self.shell('cell-list -r 1 --limit 0')
self.assertTrue(mock_list.called)
@mock.patch('cratonclient.v1.cells.CellManager.list')
def test_cell_list_limit_0_success(self, mock_list):
"""Verify that --limit 0 prints out all project cells."""
self.shell('cell-list -r 1 --limit 0')
mock_list.assert_called_once_with(
limit=0,
sort_dir='asc',
region_id=1,
autopaginate=False,
marker=None,
)
@mock.patch('cratonclient.v1.cells.CellManager.list')
def test_cell_list_limit_positive_num_success(self, mock_list):
"""Verify --limit X, where X is a positive integer, succeeds.
The command will print out X number of project cells.
"""
self.shell('cell-list -r 1 --limit 1')
mock_list.assert_called_once_with(
limit=1,
sort_dir='asc',
region_id=1,
autopaginate=False,
marker=None,
)
def test_cell_list_limit_negative_num_failure(self):
"""Verify --limit X, where X is a negative integer, fails.
The command will cause a Command Error message response.
"""
self.assertRaises(exc.CommandError,
self.shell,
'cell-list -r 1 --limit -1')
@mock.patch('cratonclient.v1.cells.CellManager.list')
def test_cell_list_detail_success(self, mock_list):
"""Verify --detail argument successfully pass detail to Client."""
self.shell('cell-list -r 1 --detail')
mock_list.assert_called_once_with(
detail=True,
region_id=1,
sort_dir='asc',
autopaginate=False,
marker=None,
)
@mock.patch('cratonclient.v1.cells.CellManager.list')
def test_cell_list_fields_success(self, mock_list):
"""Verify --fields argument successfully passed to Client."""
self.shell('cell-list -r 1 --fields id name')
mock_list.assert_called_once_with(
sort_dir='asc',
region_id=1,
autopaginate=False,
marker=None,
)
@mock.patch('cratonclient.v1.cells.CellManager.list')
def test_cell_list_sort_key_field_key_success(self, mock_list):
"""Verify --sort-key arguments successfully passed to Client."""
self.shell('cell-list -r 1 --sort-key name')
mock_list.assert_called_once_with(
sort_key='name',
sort_dir='asc',
region_id=1,
autopaginate=False,
marker=None,
)
def test_cell_list_sort_key_invalid(self):
"""Verify --sort-key with invalid args, fails with Command Error."""
self.assertRaises(exc.CommandError,
self.shell,
'cell-list -r 1 --sort-key invalid')
@mock.patch('cratonclient.v1.cells.CellManager.list')
def test_cell_list_sort_dir_asc_success(self, mock_list):
"""Verify --sort-dir asc successfully passed to Client."""
self.shell('cell-list -r 1 --sort-key name --sort-dir asc')
mock_list.assert_called_once_with(
sort_key='name',
sort_dir='asc',
region_id=1,
autopaginate=False,
marker=None,
)
@mock.patch('cratonclient.v1.cells.CellManager.list')
def test_cell_list_sort_dir_desc_success(self, mock_list):
"""Verify --sort-dir desc successfully passed to Client."""
self.shell('cell-list -r 1 --sort-key name --sort-dir desc')
mock_list.assert_called_once_with(
sort_key='name',
sort_dir='desc',
region_id=1,
autopaginate=False,
marker=None,
)
@mock.patch('cratonclient.v1.cells.CellManager.list')
def test_cell_list_does_not_require_region_id(self, cell_list):
"""Verify -r/--region are not required to list cells."""
self.shell('cell-list --limit 10')
cell_list.assert_called_once_with(
sort_dir='asc',
autopaginate=False,
limit=10,
marker=None,
)
def test_cell_list_sort_dir_invalid_value(self):
"""Verify --sort-dir with invalid args, fails with Command Error."""
(_, error) = self.shell(
'cell-list -r 1 --sort-key name --sort-dir invalid'
)
self.assertIn("invalid choice: 'invalid'", error)
@mock.patch('cratonclient.v1.cells.CellManager.list')
def test_cell_list_with_vars_success(self, mock_list):
"""Verify --vars arguments successfully passed to Client."""
self.shell('cell-list --vars a:b')
mock_list.assert_called_once_with(
vars='a:b',
marker=None,
sort_dir='asc',
autopaginate=False,
)
mock_list.reset_mock()
@mock.patch('cratonclient.v1.cells.CellManager.list')
def test_cell_list_with_multiple_vars_success(self, mock_list):
"""Verify multiple --vars arguments successfully passed to Client."""
self.shell('cell-list --vars=a:b --vars=c:d')
mock_list.assert_called_once_with(
vars='a:b,c:d',
marker=None,
sort_dir='asc',
autopaginate=False,
)
mock_list.reset_mock()
def test_cell_create_missing_required_args(self):
"""Verify that missing required args results in error message."""
expected_responses = [
'.*?^usage: craton cell-create',
'.*?^craton cell-create: error:.*$'
]
stdout, stderr = self.shell('cell-create')
actual_output = stdout + stderr
for r in expected_responses:
self.assertThat(actual_output,
matchers.MatchesRegex(r, self.re_options))
@mock.patch('cratonclient.v1.cells.CellManager.create')
def test_do_cell_create_calls_cell_manager_with_fields(self, mock_create):
"""Verify that do cell create calls CellManager create."""
client = mock.Mock()
client.cells = cells.CellManager(
mock.ANY, 'http://127.0.0.1/',
region_id=mock.ANY,
)
cells_shell.do_cell_create(client, self.cell_valid_fields)
mock_create.assert_called_once_with(**self.cell_valid_kwargs)
@mock.patch('cratonclient.v1.cells.CellManager.create')
def test_do_cell_create_ignores_unknown_fields(self, mock_create):
"""Verify that do cell create ignores unknown field."""
client = mock.Mock()
client.cells = cells.CellManager(
mock.ANY, 'http://127.0.0.1/',
region_id=mock.ANY,
)
cells_shell.do_cell_create(client, self.cell_invalid_fields)
mock_create.assert_called_once_with(**self.cell_valid_kwargs)
def test_cell_update_missing_required_args(self):
"""Verify that missing required args results in error message."""
expected_responses = [
'.*?^usage: craton cell-update',
'.*?^craton cell-update: error:.*$',
]
stdout, stderr = self.shell('cell-update')
actual_output = stdout + stderr
for r in expected_responses:
self.assertThat(actual_output,
matchers.MatchesRegex(r, self.re_options))
@mock.patch('cratonclient.v1.cells.CellManager.update')
def test_do_cell_update_calls_cell_manager_with_fields(self, mock_update):
"""Verify that do cell update calls CellManager update."""
client = mock.Mock()
client.cells = cells.CellManager(
mock.ANY, 'http://127.0.0.1/',
region_id=mock.ANY,
)
valid_input = Namespace(region=1,
id=1,
name='mock_cell',
formatter=mock.Mock())
cells_shell.do_cell_update(client, valid_input)
mock_update.assert_called_once_with(1, name='mock_cell')
@mock.patch('cratonclient.v1.cells.CellManager.update')
def test_do_cell_update_ignores_unknown_fields(self, mock_update):
"""Verify that do cell update ignores unknown field."""
client = mock.Mock()
client.cells = cells.CellManager(
mock.ANY, 'http://127.0.0.1/',
region_id=mock.ANY,
)
invalid_input = Namespace(region=1,
id=1,
name='mock_cell',
invalid=True,
formatter=mock.Mock())
cells_shell.do_cell_update(client, invalid_input)
mock_update.assert_called_once_with(1, name='mock_cell')
def test_cell_show_missing_required_args(self):
"""Verify that missing required args results in error message."""
expected_responses = [
'.*?^usage: craton cell-show',
'.*?^craton cell-show: error:.*$',
]
stdout, stderr = self.shell('cell-show')
actual_output = stdout + stderr
for r in expected_responses:
self.assertThat(actual_output,
matchers.MatchesRegex(r, self.re_options))
@mock.patch('cratonclient.v1.cells.CellManager.get')
def test_do_cell_show_calls_cell_manager_with_fields(self, mock_get):
"""Verify that do cell show calls CellManager get."""
client = mock.Mock()
client.cells = cells.CellManager(
mock.ANY, 'http://127.0.0.1/',
region_id=mock.ANY,
)
test_args = Namespace(id=1, formatter=mock.Mock())
cells_shell.do_cell_show(client, test_args)
mock_get.assert_called_once_with(vars(test_args)['id'])
def test_cell_delete_missing_required_args(self):
"""Verify that missing required args results in error message."""
expected_responses = [
'.*?^usage: craton cell-delete',
'.*?^craton cell-delete: error:.*$',
]
stdout, stderr = self.shell('cell-delete')
for r in expected_responses:
self.assertThat((stdout + stderr),
matchers.MatchesRegex(r, self.re_options))
@mock.patch('cratonclient.v1.cells.CellManager.delete')
def test_do_cell_delete_calls_cell_manager_with_fields(self, mock_delete):
"""Verify that do cell delete calls CellManager delete."""
client = mock.Mock()
client.cells = cells.CellManager(
mock.ANY, 'http://127.0.0.1/',
region_id=mock.ANY,
)
test_args = Namespace(id=1, region=1)
cells_shell.do_cell_delete(client, test_args)
mock_delete.assert_called_once_with(vars(test_args)['id'])
class TestCellsVarsShell(base.VariablesTestCase):
"""Test Cell Variable shell calls."""
resource = 'cell'
resource_id = '1'
shell = cells_shell

View File

@ -1,171 +0,0 @@
# 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.
"""Tests for `cratonclient.shell.v1.clouds_shell` module."""
import mock
import re
from argparse import Namespace
from testtools import matchers
from cratonclient.shell.v1 import clouds_shell
from cratonclient.tests.integration.shell import base
from cratonclient.v1 import clouds
class TestCloudsShell(base.ShellTestCase):
"""Test craton clouds shell commands."""
re_options = re.DOTALL | re.MULTILINE
cloud_valid_fields = None
cloud_invalid_fields = None
def setUp(self):
"""Setup required test fixtures."""
super(TestCloudsShell, self).setUp()
self.cloud_valid_kwargs = {
'project_id': 1,
'id': 1,
'name': 'mock_cloud',
}
self.cloud_invalid_kwargs = {
'project_id': 1,
'id': 1,
'name': 'mock_cloud',
'invalid_foo': 'ignored',
}
self.cloud_valid_fields = Namespace(**self.cloud_valid_kwargs)
self.cloud_valid_fields.formatter = mock.Mock()
self.cloud_invalid_fields = Namespace(**self.cloud_invalid_kwargs)
self.cloud_invalid_fields.formatter = mock.Mock()
def test_cloud_create_missing_required_args(self):
"""Verify that missing required args results in error message."""
expected_responses = [
'.*?^usage: craton cloud-create',
'.*?^craton cloud-create: error:.*$'
]
stdout, stderr = self.shell('cloud-create')
actual_output = stdout + stderr
for r in expected_responses:
self.assertThat(actual_output,
matchers.MatchesRegex(r, self.re_options))
@mock.patch('cratonclient.v1.clouds.CloudManager.create')
def test_do_cloud_create_calls_cloud_manager(self, mock_create):
"""Verify that do cloud create calls CloudManager create."""
client = mock.Mock()
session = mock.Mock()
session.project_id = 1
client.clouds = clouds.CloudManager(session, 'http://127.0.0.1/')
clouds_shell.do_cloud_create(client, self.cloud_valid_fields)
mock_create.assert_called_once_with(**self.cloud_valid_kwargs)
@mock.patch('cratonclient.v1.clouds.CloudManager.create')
def test_do_cloud_create_ignores_unknown_fields(self, mock_create):
"""Verify that do cloud create ignores unknown field."""
client = mock.Mock()
session = mock.Mock()
session.project_id = 1
client.clouds = clouds.CloudManager(session, 'http://127.0.0.1/')
clouds_shell.do_cloud_create(client, self.cloud_invalid_fields)
mock_create.assert_called_once_with(**self.cloud_valid_kwargs)
def test_cloud_show_missing_required_args(self):
"""Verify that missing required args results in error message."""
expected_responses = [
'.*?^usage: craton cloud-show',
'.*?^craton cloud-show: error:.*$',
]
stdout, stderr = self.shell('cloud-show')
actual_output = stdout + stderr
for r in expected_responses:
self.assertThat(actual_output,
matchers.MatchesRegex(r, self.re_options))
@mock.patch('cratonclient.v1.clouds.CloudManager.get')
def test_do_cloud_show_calls_cloud_manager_with_fields(self, mock_get):
"""Verify that do cloud show calls CloudManager get."""
client = mock.Mock()
session = mock.Mock()
session.project_id = 1
client.clouds = clouds.CloudManager(session, 'http://127.0.0.1/')
test_args = Namespace(id=1, formatter=mock.Mock())
clouds_shell.do_cloud_show(client, test_args)
mock_get.assert_called_once_with(vars(test_args)['id'])
def test_cloud_delete_missing_required_args(self):
"""Verify that missing required args results in error message."""
expected_responses = [
'.*?^usage: craton cloud-delete',
'.*?^craton cloud-delete: error:.*$',
]
stdout, stderr = self.shell('cloud-delete')
for r in expected_responses:
self.assertThat((stdout + stderr),
matchers.MatchesRegex(r, self.re_options))
@mock.patch('cratonclient.v1.clouds.CloudManager.delete')
def test_do_cloud_delete_calls_cloud_manager(self, mock_delete):
"""Verify that do cloud delete calls CloudManager delete."""
client = mock.Mock()
session = mock.Mock()
session.project_id = 1
client.clouds = clouds.CloudManager(session, 'http://127.0.0.1/')
test_args = Namespace(id=1)
clouds_shell.do_cloud_delete(client, test_args)
mock_delete.assert_called_once_with(vars(test_args)['id'])
def test_cloud_update_missing_required_args(self):
"""Verify that missing required args results in error message."""
expected_responses = [
'.*?^usage: craton cloud-update',
'.*?^craton cloud-update: error:.*$',
]
stdout, stderr = self.shell('cloud-update')
for r in expected_responses:
self.assertThat((stdout + stderr),
matchers.MatchesRegex(r, self.re_options))
@mock.patch('cratonclient.v1.clouds.CloudManager.update')
def test_do_cloud_update_calls_cloud_manager(self, mock_update):
"""Verify that do cloud update calls CloudManager update."""
client = mock.Mock()
session = mock.Mock()
session.project_id = 1
client.clouds = clouds.CloudManager(session, 'http://127.0.0.1/')
valid_input = Namespace(id=1,
name='mock_cloud',
formatter=mock.Mock())
clouds_shell.do_cloud_update(client, valid_input)
mock_update.assert_called_once_with(1, name='mock_cloud')
@mock.patch('cratonclient.v1.clouds.CloudManager.update')
def test_do_cloud_update_ignores_unknown_fields(self, mock_update):
"""Verify that do cloud update ignores unknown field."""
client = mock.Mock()
session = mock.Mock()
session.project_id = 1
client.clouds = clouds.CloudManager(session, 'http://127.0.0.1/')
invalid_input = Namespace(id=1,
name='mock_cloud',
invalid=True,
formatter=mock.Mock())
clouds_shell.do_cloud_update(client, invalid_input)
mock_update.assert_called_once_with(1, name='mock_cloud')
class TestCloudsVarsShell(base.VariablesTestCase):
"""Test Cloud Variable shell calls."""
resource = 'cloud'
resource_id = '1'
shell = clouds_shell

View File

@ -1,182 +0,0 @@
# 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.
"""Tests for `cratonclient.shell.v1.devices_shell` module."""
import mock
import re
from cratonclient import exceptions as exc
from cratonclient.tests.integration.shell import base
class TestDevicesShell(base.ShellTestCase):
"""Test our craton devices shell commands."""
re_options = re.DOTALL | re.MULTILINE
@mock.patch('cratonclient.v1.devices.DeviceManager.list')
def test_device_list_success(self, mock_list):
"""Verify that no arguments prints out all project devices."""
self.shell('device-list')
self.assertTrue(mock_list.called)
@mock.patch('cratonclient.v1.devices.DeviceManager.list')
def test_device_list_parse_param_success(self, mock_list):
"""Verify that success of parsing a subcommand argument."""
self.shell('device-list --limit 0')
self.assertTrue(mock_list.called)
@mock.patch('cratonclient.v1.devices.DeviceManager.list')
def test_device_list_limit_0_success(self, mock_list):
"""Verify that --limit 0 prints out all project devices."""
self.shell('device-list --limit 0')
mock_list.assert_called_once_with(
limit=0,
sort_dir='asc',
descendants=False,
marker=None,
autopaginate=False,
)
@mock.patch('cratonclient.v1.devices.DeviceManager.list')
def test_device_list_limit_positive_num_success(self, mock_list):
"""Verify --limit X, where X is a positive integer, succeeds.
The command will print out X number of project devices.
"""
self.shell('device-list --limit 1')
mock_list.assert_called_once_with(
limit=1,
sort_dir='asc',
descendants=False,
marker=None,
autopaginate=False,
)
def test_device_list_limit_negative_num_failure(self):
"""Verify --limit X, where X is a negative integer, fails.
The command will cause a Command Error message response.
"""
self.assertRaises(exc.CommandError,
self.shell,
'device-list -r 1 --limit -1')
@mock.patch('cratonclient.v1.devices.DeviceManager.list')
def test_device_list_cell_success(self, mock_list):
"""Verify --cell arguments successfully pass cell to Client."""
for cell_arg in ['-c', '--cell']:
self.shell('device-list {0} 1'.format(cell_arg))
mock_list.assert_called_once_with(
cell_id=1,
sort_dir='asc',
descendants=False,
marker=None,
autopaginate=False,
)
mock_list.reset_mock()
@mock.patch('cratonclient.v1.devices.DeviceManager.list')
def test_device_list_fields_success(self, mock_list):
"""Verify --fields argument successfully passed to Client."""
self.shell('device-list --fields id name')
mock_list.assert_called_once_with(
sort_dir='asc',
descendants=False,
marker=None,
autopaginate=False,
)
@mock.patch('cratonclient.v1.devices.DeviceManager.list')
def test_device_list_sort_keys_field_key_success(self, mock_list):
"""Verify --sort-key arguments successfully passed to Client."""
self.shell('device-list --sort-key cell_id')
mock_list.assert_called_once_with(
sort_keys='cell_id',
sort_dir='asc',
descendants=False,
marker=None,
autopaginate=False,
)
def test_device_list_sort_keys_invalid(self):
"""Verify --sort-key with invalid args, fails with Command Error."""
self.assertRaises(exc.CommandError,
self.shell,
'device-list --sort-key invalid')
@mock.patch('cratonclient.v1.devices.DeviceManager.list')
def test_device_list_sort_dir_not_passed_without_sort_key(self, mock_list):
"""Verify --sort-dir arg ignored without --sort-key."""
self.shell('device-list --sort-dir desc')
mock_list.assert_called_once_with(
sort_dir='desc',
descendants=False,
marker=None,
autopaginate=False,
)
@mock.patch('cratonclient.v1.devices.DeviceManager.list')
def test_device_list_sort_dir_asc_success(self, mock_list):
"""Verify --sort-dir asc successfully passed to Client."""
self.shell('device-list --sort-key name --sort-dir asc')
mock_list.assert_called_once_with(
sort_keys='name',
sort_dir='asc',
descendants=False,
marker=None,
autopaginate=False,
)
@mock.patch('cratonclient.v1.devices.DeviceManager.list')
def test_device_list_sort_dir_desc_success(self, mock_list):
"""Verify --sort-dir desc successfully passed to Client."""
self.shell('device-list --sort-key name --sort-dir desc')
mock_list.assert_called_once_with(
sort_keys='name',
sort_dir='desc',
descendants=False,
marker=None,
autopaginate=False,
)
def test_device_list_sort_dir_invalid_value(self):
"""Verify --sort-dir with invalid args, fails with Command Error."""
(_, error) = self.shell(
'device-list -r 1 --sort-key name --sort-dir invalid'
)
self.assertIn("invalid choice: 'invalid'", error)
@mock.patch('cratonclient.v1.devices.DeviceManager.list')
def test_device_list_filter_by_parent_success(self, mock_list):
"""Verify --parent ID successfully passed to Client."""
self.shell('device-list --parent 12345')
mock_list.assert_called_once_with(
sort_dir='asc',
descendants=False,
parent_id=12345,
marker=None,
autopaginate=False,
)
@mock.patch('cratonclient.v1.devices.DeviceManager.list')
def test_device_list_filter_by_parent_descendants_success(self, mock_list):
"""Verify --parent ID successfully passed to Client."""
self.shell('device-list --parent 12345 --descendants')
mock_list.assert_called_once_with(
sort_dir='asc',
parent_id=12345,
descendants=True,
marker=None,
autopaginate=False,
)

View File

@ -1,399 +0,0 @@
# 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.
"""Tests for `cratonclient.shell.v1.hosts_shell` module."""
import mock
import re
from argparse import Namespace
from testtools import matchers
from cratonclient import exceptions as exc
from cratonclient.shell.v1 import hosts_shell
from cratonclient.tests.integration.shell import base
from cratonclient.v1 import hosts
class TestHostsShell(base.ShellTestCase):
"""Test our craton hosts shell commands."""
re_options = re.DOTALL | re.MULTILINE
host_valid_fields = None
host_invalid_fields = None
def setUp(self):
"""Setup required test fixtures."""
super(TestHostsShell, self).setUp()
self.host_valid_kwargs = {
'project_id': 1,
'region_id': 1,
'name': 'mock_host',
'ip_address': '127.0.0.1',
'active': True,
}
self.host_invalid_kwargs = {
'project_id': 1,
'region_id': 1,
'name': 'mock_host',
'ip_address': '127.0.0.1',
'active': True,
'invalid_foo': 'ignored',
}
self.host_valid_fields = Namespace(**self.host_valid_kwargs)
self.host_valid_fields.formatter = mock.Mock()
self.host_invalid_fields = Namespace(**self.host_invalid_kwargs)
self.host_invalid_fields.formatter = mock.Mock()
@mock.patch('cratonclient.v1.hosts.HostManager.list')
def test_host_list_success(self, mock_list):
"""Verify that no arguments prints out all project hosts."""
self.shell('host-list -r 1')
self.assertTrue(mock_list.called)
@mock.patch('cratonclient.v1.hosts.HostManager.list')
def test_host_list_parse_param_success(self, mock_list):
"""Verify that success of parsing a subcommand argument."""
self.shell('host-list -r 1 --limit 0')
self.assertTrue(mock_list.called)
@mock.patch('cratonclient.v1.hosts.HostManager.list')
def test_host_list_limit_0_success(self, mock_list):
"""Verify that --limit 0 prints out all project hosts."""
self.shell('host-list -r 1 --limit 0')
mock_list.assert_called_once_with(
limit=0,
sort_dir='asc',
region_id=1,
marker=None,
autopaginate=False,
)
@mock.patch('cratonclient.v1.hosts.HostManager.list')
def test_host_list_limit_positive_num_success(self, mock_list):
"""Verify --limit X, where X is a positive integer, succeeds.
The command will print out X number of project hosts.
"""
self.shell('host-list -r 1 --limit 1')
mock_list.assert_called_once_with(
limit=1,
sort_dir='asc',
region_id=1,
marker=None,
autopaginate=False,
)
@mock.patch('cratonclient.v1.hosts.HostManager.list')
def test_host_list_does_not_require_region(self, host_list):
"""Verify -r/--region is not required to list hosts."""
self.shell('host-list --limit 10')
host_list.assert_called_once_with(
limit=10,
sort_dir='asc',
marker=None,
autopaginate=False,
)
def test_host_list_limit_negative_num_failure(self):
"""Verify --limit X, where X is a negative integer, fails.
The command will cause a Command Error message response.
"""
self.assertRaises(exc.CommandError,
self.shell,
'host-list -r 1 --limit -1')
@mock.patch('cratonclient.v1.hosts.HostManager.list')
def test_host_list_cell_success(self, mock_list):
"""Verify --cell arguments successfully pass cell to Client."""
for cell_arg in ['-c', '--cell']:
self.shell('host-list -r 1 {0} 1'.format(cell_arg))
mock_list.assert_called_once_with(
cell_id=1,
sort_dir='asc',
region_id=1,
marker=None,
autopaginate=False,
)
mock_list.reset_mock()
@mock.patch('cratonclient.v1.hosts.HostManager.list')
def test_host_list_vars_success(self, mock_list):
"""Verify --vars arguments successfully pass cell to Client."""
self.shell('host-list -r 1 --vars a:b')
mock_list.assert_called_once_with(
vars='a:b',
sort_dir='asc',
region_id=1,
marker=None,
autopaginate=False,
)
mock_list.reset_mock()
@mock.patch('cratonclient.v1.hosts.HostManager.list')
def test_host_list_ip_success(self, mock_list):
"""Verify --ip arguments successfully pass cell to Client."""
self.shell('host-list -r 1 --ip 10.10.1.1')
mock_list.assert_called_once_with(
ip_address='10.10.1.1',
sort_dir='asc',
region_id=1,
marker=None,
autopaginate=False,
)
mock_list.reset_mock()
@mock.patch('cratonclient.v1.hosts.HostManager.list')
def test_host_list_label_success(self, mock_list):
"""Verify --label arguments successfully pass cell to Client."""
self.shell('host-list -r 1 --label compute')
mock_list.assert_called_once_with(
label='compute',
sort_dir='asc',
region_id=1,
marker=None,
autopaginate=False,
)
mock_list.reset_mock()
@mock.patch('cratonclient.v1.hosts.HostManager.list')
def test_host_list_device_type_success(self, mock_list):
"""Verify --device-type arguments successfully pass cell to Client."""
self.shell('host-list -r 1 --device-type compute')
mock_list.assert_called_once_with(
device_type='compute',
sort_dir='asc',
region_id=1,
marker=None,
autopaginate=False,
)
mock_list.reset_mock()
@mock.patch('cratonclient.v1.hosts.HostManager.list')
def test_host_list_detail_success(self, mock_list):
"""Verify --detail argument successfully pass detail to Client."""
self.shell('host-list -r 1 --detail')
mock_list.assert_called_once_with(
detail=True,
sort_dir='asc',
region_id=1,
marker=None,
autopaginate=False,
)
@mock.patch('cratonclient.v1.hosts.HostManager.list')
def test_host_list_fields_success(self, mock_list):
"""Verify --fields argument successfully passed to Client."""
self.shell('host-list -r 1 --fields id name')
mock_list.assert_called_once_with(
sort_dir='asc',
region_id=1,
marker=None,
autopaginate=False,
)
@mock.patch('cratonclient.v1.hosts.HostManager.list')
def test_host_list_sort_key_field_key_success(self, mock_list):
"""Verify --sort-key arguments successfully passed to Client."""
self.shell('host-list -r 1 --sort-key cell_id')
mock_list.assert_called_once_with(
sort_key='cell_id',
sort_dir='asc',
region_id=1,
marker=None,
autopaginate=False,
)
def test_host_list_sort_key_invalid(self):
"""Verify --sort-key with invalid args, fails with Command Error."""
self.assertRaises(exc.CommandError,
self.shell,
'host-list -r 1 --sort-key invalid')
@mock.patch('cratonclient.v1.hosts.HostManager.list')
def test_host_list_sort_dir_not_passed_without_sort_key(self, mock_list):
"""Verify --sort-dir arg ignored without --sort-key."""
self.shell('host-list -r 1 --sort-dir desc')
mock_list.assert_called_once_with(
sort_dir='desc',
region_id=1,
marker=None,
autopaginate=False,
)
@mock.patch('cratonclient.v1.hosts.HostManager.list')
def test_host_list_sort_dir_asc_success(self, mock_list):
"""Verify --sort-dir asc successfully passed to Client."""
self.shell('host-list -r 1 --sort-key name --sort-dir asc')
mock_list.assert_called_once_with(
sort_key='name',
sort_dir='asc',
region_id=1,
marker=None,
autopaginate=False,
)
@mock.patch('cratonclient.v1.hosts.HostManager.list')
def test_host_list_sort_dir_desc_success(self, mock_list):
"""Verify --sort-dir desc successfully passed to Client."""
self.shell('host-list -r 1 --sort-key name --sort-dir desc')
mock_list.assert_called_once_with(
sort_key='name',
sort_dir='desc',
region_id=1,
marker=None,
autopaginate=False,
)
def test_host_list_sort_dir_invalid_value(self):
"""Verify --sort-dir with invalid args, fails with Command Error."""
(_, error) = self.shell(
'host-list -r 1 --sort-key name --sort-dir invalid'
)
self.assertIn("invalid choice: 'invalid'", error)
def test_host_create_missing_required_args(self):
"""Verify that missing required args results in error message."""
expected_responses = [
'.*?^usage: craton host-create',
'.*?^craton host-create: error:.*$'
]
stdout, stderr = self.shell('host-create')
actual_output = stdout + stderr
for r in expected_responses:
self.assertThat(actual_output,
matchers.MatchesRegex(r, self.re_options))
@mock.patch('cratonclient.v1.hosts.HostManager.create')
def test_do_host_create_calls_host_manager_with_fields(self, mock_create):
"""Verify that do host create calls HostManager create."""
client = mock.Mock()
client.hosts = hosts.HostManager(
mock.ANY, 'http://127.0.0.1/',
region_id=mock.ANY,
)
hosts_shell.do_host_create(client, self.host_valid_fields)
mock_create.assert_called_once_with(**self.host_valid_kwargs)
@mock.patch('cratonclient.v1.hosts.HostManager.create')
def test_do_host_create_ignores_unknown_fields(self, mock_create):
"""Verify that do host create ignores unknown field."""
client = mock.Mock()
client.hosts = hosts.HostManager(
mock.ANY, 'http://127.0.0.1/',
region_id=mock.ANY,
)
hosts_shell.do_host_create(client, self.host_invalid_fields)
mock_create.assert_called_once_with(**self.host_valid_kwargs)
def test_host_update_missing_required_args(self):
"""Verify that missing required args results in error message."""
expected_responses = [
'.*?^usage: craton host-update',
'.*?^craton host-update: error:.*$',
]
stdout, stderr = self.shell('host-update')
actual_output = stdout + stderr
for r in expected_responses:
self.assertThat(actual_output,
matchers.MatchesRegex(r, self.re_options))
@mock.patch('cratonclient.v1.hosts.HostManager.update')
def test_do_host_update_calls_host_manager_with_fields(self, mock_update):
"""Verify that do host update calls HostManager update."""
client = mock.Mock()
client.hosts = hosts.HostManager(
mock.ANY, 'http://127.0.0.1/',
region_id=mock.ANY,
)
valid_input = Namespace(region=1,
id=1,
name='mock_host',
formatter=mock.Mock())
hosts_shell.do_host_update(client, valid_input)
mock_update.assert_called_once_with(1, name='mock_host')
@mock.patch('cratonclient.v1.hosts.HostManager.update')
def test_do_host_update_ignores_unknown_fields(self, mock_update):
"""Verify that do host update ignores unknown field."""
client = mock.Mock()
client.hosts = hosts.HostManager(
mock.ANY, 'http://127.0.0.1/',
region_id=mock.ANY,
)
invalid_input = Namespace(region=1,
id=1,
name='mock_host',
formatter=mock.Mock(),
invalid=True)
hosts_shell.do_host_update(client, invalid_input)
mock_update.assert_called_once_with(1, name='mock_host')
def test_host_show_missing_required_args(self):
"""Verify that missing required args results in error message."""
expected_responses = [
'.*?^usage: craton host-show',
'.*?^craton host-show: error:.*$',
]
stdout, stderr = self.shell('host-show')
actual_output = stdout + stderr
for r in expected_responses:
self.assertThat(actual_output,
matchers.MatchesRegex(r, self.re_options))
@mock.patch('cratonclient.v1.hosts.HostManager.get')
def test_do_host_show_calls_host_manager_with_fields(self, mock_get):
"""Verify that do host show calls HostManager get."""
client = mock.Mock()
client.hosts = hosts.HostManager(
mock.ANY, 'http://127.0.0.1/',
region_id=mock.ANY,
)
test_args = Namespace(id=1, region=1)
formatter = test_args.formatter = mock.Mock()
formatter.configure.return_value = formatter
hosts_shell.do_host_show(client, test_args)
mock_get.assert_called_once_with(vars(test_args)['id'])
self.assertTrue(formatter.handle.called)
self.assertEqual(1, formatter.handle.call_count)
def test_host_delete_missing_required_args(self):
"""Verify that missing required args results in error message."""
expected_responses = [
'.*?^usage: craton host-delete',
'.*?^craton host-delete: error:.*$',
]
stdout, stderr = self.shell('host-delete')
for r in expected_responses:
self.assertThat((stdout + stderr),
matchers.MatchesRegex(r, self.re_options))
@mock.patch('cratonclient.v1.hosts.HostManager.delete')
def test_do_host_delete_calls_host_manager_with_fields(self, mock_delete):
"""Verify that do host delete calls HostManager delete."""
client = mock.Mock()
client.hosts = hosts.HostManager(
mock.ANY, 'http://127.0.0.1/',
region_id=mock.ANY,
)
test_args = Namespace(id=1, region=1)
hosts_shell.do_host_delete(client, test_args)
mock_delete.assert_called_once_with(vars(test_args)['id'])
class TestHostsVarsShell(base.VariablesTestCase):
"""Test Host Variable shell calls."""
resource = 'host'
resource_id = '1'
shell = hosts_shell

View File

@ -1,117 +0,0 @@
# 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.
"""Tests for `cratonclient.shell.main` module."""
import mock
import re
from testtools import matchers
from cratonclient.shell import main
from cratonclient.tests.integration.shell import base
class TestMainShell(base.ShellTestCase):
"""Test our craton main shell."""
re_options = re.DOTALL | re.MULTILINE
@mock.patch('cratonclient.shell.main.CratonShell.main')
def test_main_returns_successfully(self, cratonShellMainMock):
"""Verify that main returns as expected."""
cratonShellMainMock.return_value = 0
self.assertEqual(main.main(), 0)
def test_print_help_no_args(self):
"""Verify that no arguments prints out help by default."""
required_help_responses = [
'.*?^usage: craton',
'.*?^See "craton help COMMAND" '
'for help on a specific command.',
]
stdout, stderr = self.shell('')
for r in required_help_responses:
self.assertThat((stdout + stderr),
matchers.MatchesRegex(r, self.re_options))
def test_print_help_with_args(self):
"""Verify that help command(s) prints out help text correctly."""
required_help_responses = [
'.*?^usage: craton',
'.*?^See "craton help COMMAND" '
'for help on a specific command.',
]
for help_args in ['-h', '--help']:
stdout, stderr = self.shell(help_args)
for r in required_help_responses:
self.assertThat((stdout + stderr),
matchers.MatchesRegex(r, self.re_options))
@mock.patch('cratonclient.v1.client.Client')
def test_main_craton_url(self, mock_client):
"""Verify that craton-url command is used for client connection."""
self.shell('--craton-url http://localhost:9999/ host-list -r 1')
mock_client.assert_called_with(mock.ANY, 'http://localhost:9999/')
@mock.patch('cratonclient.session.Session')
@mock.patch('cratonclient.v1.client.Client')
def test_main_craton_project_id(self, mock_client, mock_session):
"""Verify --os-project-id command is used for client connection."""
self.shell('--os-project-id 99 host-list -r 1')
mock_session.assert_called_with(username=mock.ANY,
token=mock.ANY,
project_id='99')
mock_client.assert_called_with(mock.ANY, mock.ANY)
@mock.patch('cratonclient.session.Session')
@mock.patch('cratonclient.v1.client.Client')
def test_main_os_username(self, mock_client, mock_session):
"""Verify --os-username command is used for client connection."""
self.shell('--os-username test host-list -r 1')
mock_session.assert_called_with(username='test',
token=mock.ANY,
project_id=mock.ANY)
mock_client.assert_called_with(mock.ANY, mock.ANY)
@mock.patch('cratonclient.session.Session')
@mock.patch('cratonclient.v1.client.Client')
def test_main_os_password(self, mock_client, mock_session):
"""Verify --os-password command is used for client connection."""
self.shell('--os-password test host-list -r 1')
mock_session.assert_called_with(username=mock.ANY,
token='test',
project_id=mock.ANY)
mock_client.assert_called_with(mock.ANY, mock.ANY)
@mock.patch('cratonclient.shell.main.CratonShell.main')
def test_main_catches_exception(self, cratonShellMainMock):
"""Verify exceptions will be caught and shell will exit properly."""
cratonShellMainMock.side_effect = Exception(mock.Mock(status=404),
'some error')
self.assertRaises(SystemExit, main.main)
@mock.patch('cratonclient.shell.v1.hosts_shell.do_host_create')
def test_main_routes_sub_command(self, mock_create):
"""Verify main shell calls correct subcommand."""
url = '--craton-url test_url'
username = '--os-username test_name'
pw = '--os-password test_pw'
proj_id = '--os-project-id 1'
self.shell('{} {} {} {} host-create'.format(url,
username,
pw,
proj_id))
self.assertTrue(mock_create.called)

View File

@ -1,200 +0,0 @@
# 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.
"""Tests for `cratonclient.shell.v1.projects_shell` module."""
import mock
import re
from argparse import Namespace
from testtools import matchers
from cratonclient import exceptions as exc
from cratonclient.shell.v1 import projects_shell
from cratonclient.tests.integration.shell import base
from cratonclient.v1 import projects
class TestProjectsShell(base.ShellTestCase):
"""Test our craton projects shell commands."""
re_options = re.DOTALL | re.MULTILINE
project_valid_fields = None
project_invalid_fields = None
def setUp(self):
"""Setup required test fixtures."""
super(TestProjectsShell, self).setUp()
self.project_valid_kwargs = {
'name': 'mock_project',
}
self.project_invalid_kwargs = {
'name': 'mock_project',
'invalid_foo': 'ignored',
}
self.project_valid_fields = Namespace(**self.project_valid_kwargs)
self.project_invalid_fields = Namespace(**self.project_invalid_kwargs)
self.project_valid_fields.formatter = mock.Mock()
self.project_invalid_fields.formatter = mock.Mock()
@mock.patch('cratonclient.v1.projects.ProjectManager.list')
def test_project_list_success(self, mock_list):
"""Verify that no arguments prints out all project projects."""
self.shell('project-list')
self.assertTrue(mock_list.called)
@mock.patch('cratonclient.v1.projects.ProjectManager.list')
def test_project_list_parse_param_success(self, mock_list):
"""Verify that success of parsing a subcommand argument."""
self.shell('project-list --limit 0')
self.assertTrue(mock_list.called)
@mock.patch('cratonclient.v1.projects.ProjectManager.list')
def test_project_list_limit_0_success(self, mock_list):
"""Verify that --limit 0 prints out all project projects."""
self.shell('project-list --limit 0')
mock_list.assert_called_once_with(
limit=0,
marker=None,
autopaginate=False,
)
@mock.patch('cratonclient.v1.projects.ProjectManager.list')
def test_project_list_limit_positive_num_success(self, mock_list):
"""Verify --limit X, where X is a positive integer, succeeds.
The command will print out X number of project projects.
"""
self.shell('project-list --limit 1')
mock_list.assert_called_once_with(
limit=1,
marker=None,
autopaginate=False,
)
def test_project_list_limit_negative_num_failure(self):
"""Verify --limit X, where X is a negative integer, fails.
The command will cause a Command Error message response.
"""
self.assertRaises(exc.CommandError,
self.shell,
'project-list --limit -1')
@mock.patch('cratonclient.v1.projects.ProjectManager.list')
def test_project_list_detail_success(self, mock_list):
"""Verify --detail argument successfully pass detail to Client."""
self.shell('project-list --detail')
mock_list.assert_called_once_with(
marker=None,
autopaginate=False,
)
@mock.patch('cratonclient.v1.projects.ProjectManager.list')
def test_project_list_fields_success(self, mock_list):
"""Verify --fields argument successfully passed to Client."""
self.shell('project-list --fields id name')
mock_list.assert_called_once_with(
marker=None,
autopaginate=False,
)
def test_project_create_missing_required_args(self):
"""Verify that missing required args results in error message."""
expected_responses = [
'.*?^usage: craton project-create',
'.*?^craton project-create: error:.*$'
]
stdout, stderr = self.shell('project-create')
actual_output = stdout + stderr
for r in expected_responses:
self.assertThat(actual_output,
matchers.MatchesRegex(r, self.re_options))
@mock.patch('cratonclient.v1.projects.ProjectManager.create')
def test_do_project_create_calls_project_manager_with_fields(self,
mock_create):
"""Verify that do project create calls ProjectManager create."""
client = mock.Mock()
client.projects = projects.ProjectManager(
mock.ANY, 'http://127.0.0.1/',
region_id=mock.ANY,
)
projects_shell.do_project_create(client, self.project_valid_fields)
mock_create.assert_called_once_with(**self.project_valid_kwargs)
@mock.patch('cratonclient.v1.projects.ProjectManager.create')
def test_do_project_create_ignores_unknown_fields(self, mock_create):
"""Verify that do project create ignores unknown field."""
client = mock.Mock()
client.projects = projects.ProjectManager(
mock.ANY, 'http://127.0.0.1/',
region_id=mock.ANY,
)
projects_shell.do_project_create(client, self.project_invalid_fields)
mock_create.assert_called_once_with(**self.project_valid_kwargs)
def test_project_show_missing_required_args(self):
"""Verify that missing required args results in error message."""
expected_responses = [
'.*?^usage: craton project-show',
'.*?^craton project-show: error:.*$',
]
stdout, stderr = self.shell('project-show')
actual_output = stdout + stderr
for r in expected_responses:
self.assertThat(actual_output,
matchers.MatchesRegex(r, self.re_options))
@mock.patch('cratonclient.v1.projects.ProjectManager.get')
def test_do_project_show_calls_project_manager_with_fields(self, mock_get):
"""Verify that do project show calls ProjectManager get."""
client = mock.Mock()
client.projects = projects.ProjectManager(
mock.ANY, 'http://127.0.0.1/',
region_id=mock.ANY,
)
test_args = Namespace(id=1, formatter=mock.Mock())
projects_shell.do_project_show(client, test_args)
mock_get.assert_called_once_with(1)
def test_project_delete_missing_required_args(self):
"""Verify that missing required args results in error message."""
expected_responses = [
'.*?^usage: craton project-delete',
'.*?^craton project-delete: error:.*$',
]
stdout, stderr = self.shell('project-delete')
for r in expected_responses:
self.assertThat((stdout + stderr),
matchers.MatchesRegex(r, self.re_options))
@mock.patch('cratonclient.v1.projects.ProjectManager.delete')
def test_do_project_delete_calls_project_manager_with_fields(self,
mock_delete):
"""Verify that do project delete calls ProjectManager delete."""
client = mock.Mock()
client.projects = projects.ProjectManager(
mock.ANY, 'http://127.0.0.1/',
region_id=mock.ANY,
)
test_args = Namespace(id=1, region=1)
projects_shell.do_project_delete(client, test_args)
mock_delete.assert_called_once_with(vars(test_args)['id'])
class TestProjectsVarsShell(base.VariablesTestCase):
"""Test Project Variable shell calls."""
resource = 'project'
resource_id = 'c9f10eca-66ac-4c27-9c13-9d01e65f96b4'
shell = projects_shell

View File

@ -1,182 +0,0 @@
# 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.
"""Tests for `cratonclient.shell.v1.regions_shell` module."""
import mock
import re
from argparse import Namespace
from testtools import matchers
from cratonclient.shell.v1 import regions_shell
from cratonclient.tests.integration.shell import base
from cratonclient.v1 import regions
class TestRegionsShell(base.ShellTestCase):
"""Test craton regions shell commands."""
re_options = re.DOTALL | re.MULTILINE
region_valid_fields = None
region_invalid_fields = None
def setUp(self):
"""Setup required test fixtures."""
super(TestRegionsShell, self).setUp()
self.region_valid_kwargs = {
'project_id': 1,
'id': 1,
'name': 'mock_region',
}
self.region_invalid_kwargs = {
'project_id': 1,
'id': 1,
'name': 'mock_region',
'invalid_foo': 'ignored',
}
self.region_valid_fields = Namespace(**self.region_valid_kwargs)
self.region_invalid_fields = Namespace(**self.region_invalid_kwargs)
self.region_valid_fields.formatter = mock.Mock()
self.region_invalid_fields.formatter = mock.Mock()
def test_region_create_missing_required_args(self):
"""Verify that missing required args results in error message."""
expected_responses = [
'.*?^usage: craton region-create',
'.*?^craton region-create: error:.*$'
]
stdout, stderr = self.shell('region-create')
actual_output = stdout + stderr
for r in expected_responses:
self.assertThat(actual_output,
matchers.MatchesRegex(r, self.re_options))
@mock.patch('cratonclient.v1.regions.RegionManager.create')
def test_do_region_create_calls_region_manager(self, mock_create):
"""Verify that do region create calls RegionManager create."""
client = mock.Mock()
session = mock.Mock()
session.project_id = 1
client.regions = regions.RegionManager(session, 'http://127.0.0.1/')
regions_shell.do_region_create(client, self.region_valid_fields)
mock_create.assert_called_once_with(**self.region_valid_kwargs)
@mock.patch('cratonclient.v1.regions.RegionManager.create')
def test_do_region_create_ignores_unknown_fields(self, mock_create):
"""Verify that do region create ignores unknown field."""
client = mock.Mock()
session = mock.Mock()
session.project_id = 1
client.regions = regions.RegionManager(session, 'http://127.0.0.1/')
regions_shell.do_region_create(client, self.region_invalid_fields)
mock_create.assert_called_once_with(**self.region_valid_kwargs)
def test_region_show_missing_required_args(self):
"""Verify that missing required args results in error message."""
expected_responses = [
'.*?^usage: craton region-show',
'.*?^craton region-show: error:.*$',
]
stdout, stderr = self.shell('region-show')
actual_output = stdout + stderr
for r in expected_responses:
self.assertThat(actual_output,
matchers.MatchesRegex(r, self.re_options))
@mock.patch('cratonclient.v1.regions.RegionManager.get')
def test_do_region_show_calls_region_manager_with_fields(self, mock_get):
"""Verify that do region show calls RegionManager get."""
client = mock.Mock()
session = mock.Mock()
session.project_id = 1
client.regions = regions.RegionManager(session, 'http://127.0.0.1/')
test_args = Namespace(id=1, formatter=mock.Mock())
regions_shell.do_region_show(client, test_args)
mock_get.assert_called_once_with(1)
def test_region_delete_missing_required_args(self):
"""Verify that missing required args results in error message."""
expected_responses = [
'.*?^usage: craton region-delete',
'.*?^craton region-delete: error:.*$',
]
stdout, stderr = self.shell('region-delete')
for r in expected_responses:
self.assertThat((stdout + stderr),
matchers.MatchesRegex(r, self.re_options))
@mock.patch('cratonclient.v1.regions.RegionManager.delete')
def test_do_region_delete_calls_region_manager(self, mock_delete):
"""Verify that do region delete calls RegionManager delete."""
client = mock.Mock()
session = mock.Mock()
session.project_id = 1
client.regions = regions.RegionManager(session, 'http://127.0.0.1/')
test_args = Namespace(id=1)
regions_shell.do_region_delete(client, test_args)
mock_delete.assert_called_once_with(vars(test_args)['id'])
def test_region_update_missing_required_args(self):
"""Verify that missing required args results in error message."""
expected_responses = [
'.*?^usage: craton region-update',
'.*?^craton region-update: error:.*$',
]
stdout, stderr = self.shell('region-update')
for r in expected_responses:
self.assertThat((stdout + stderr),
matchers.MatchesRegex(r, self.re_options))
@mock.patch('cratonclient.v1.regions.RegionManager.update')
def test_do_region_update_calls_region_manager(self, mock_update):
"""Verify that do region update calls RegionManager update."""
client = mock.Mock()
session = mock.Mock()
session.project_id = 1
client.regions = regions.RegionManager(session, 'http://127.0.0.1/')
valid_input = Namespace(id=1,
name='mock_region',
formatter=mock.Mock())
regions_shell.do_region_update(client, valid_input)
mock_update.assert_called_once_with(1, name='mock_region')
@mock.patch('cratonclient.v1.regions.RegionManager.update')
def test_do_region_update_ignores_unknown_fields(self, mock_update):
"""Verify that do region update ignores unknown field."""
client = mock.Mock()
session = mock.Mock()
session.project_id = 1
client.regions = regions.RegionManager(session, 'http://127.0.0.1/')
invalid_input = Namespace(id=1,
name='mock_region',
invalid=True,
formatter=mock.Mock())
regions_shell.do_region_update(client, invalid_input)
mock_update.assert_called_once_with(1, name='mock_region')
@mock.patch('cratonclient.v1.regions.RegionManager.list')
def test_region_list_with_vars_success(self, mock_list):
"""Verify --vars arguments successfully passed to Client."""
self.shell('region-list --vars a:b')
mock_list.assert_called_once_with(
vars='a:b',
marker=None,
autopaginate=False,
)
mock_list.reset_mock()
class TestRegionsVarsShell(base.VariablesTestCase):
"""Test Region Variable shell calls."""
resource = 'region'
resource_id = '1'
shell = regions_shell

View File

@ -1,59 +0,0 @@
# Copyright (c) 2016 Rackspace
#
# 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.
"""Integration tests for the cratonclient.auth module."""
from oslo_utils import uuidutils
from keystoneauth1.identity.v3 import password as ksa_password
from keystoneauth1 import session as ksa_session
from cratonclient import auth
from cratonclient import session
from cratonclient.tests import base
PROJECT_ID = uuidutils.generate_uuid()
class TestAuth(base.TestCase):
"""Integration tests for the auth module functions."""
def test_craton_auth_configures_craton_session(self):
"""Verify the configuration of a cratonclient Session."""
new_session = auth.craton_auth(
username='demo',
token='demo',
project_id=PROJECT_ID,
)
self.assertIsInstance(new_session, session.Session)
keystone_session = new_session._session
self.assertIsInstance(keystone_session, ksa_session.Session)
self.assertIsInstance(keystone_session.auth, auth.CratonAuth)
def test_keystone_auth_configures_craton_session(self):
"""Verify the configuration of a cratonclient Session."""
new_session = auth.keystone_auth(
auth_url='https://identity.openstack.org/v3',
username='admin',
password='adminPassword',
project_id=PROJECT_ID,
project_domain_name='Default',
user_domain_name='Default',
)
self.assertIsInstance(new_session, session.Session)
keystone_session = new_session._session
self.assertIsInstance(keystone_session, ksa_session.Session)
self.assertIsInstance(keystone_session.auth, ksa_password.Password)

View File

@ -1,175 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright 2010-2011 OpenStack Foundation
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
#
# 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.
"""Integration tests for the cratonclient.crud module members."""
import mock
from cratonclient import crud
from cratonclient import exceptions as exc
from cratonclient import session
from cratonclient.tests import base
class TestCrudIntegration(base.TestCase):
"""Integration tests for CRUDClient and Resource classes."""
def setUp(self):
"""Create necessary test resources prior to each test."""
super(TestCrudIntegration, self).setUp()
self.session = mock.Mock()
self.craton_session = session.Session(session=self.session)
self.client = self.create_client()
def create_client(self, **kwargs):
"""Create and configure a basic CRUDClient."""
client = crud.CRUDClient(
session=self.craton_session,
url='http://example.com/v1/',
**kwargs
)
client.base_path = '/test'
client.key = 'test_key'
client.resource_class = crud.Resource
return client
def create_response(self,
status_code=200,
headers={},
json_return_value=None):
"""Create and configure a mock Response object."""
response = mock.Mock(
status_code=status_code,
headers=headers,
)
response.json.return_value = json_return_value
return response
def test_create(self):
"""Verify our create makes it to the underlying session correctly."""
self.session.request.return_value = self.create_response(
status_code=201,
json_return_value={'name': 'Test', 'id': 1234},
)
resource = self.client.create(
nested_attr={'fake': 'data'},
shallow_attr='first-level',
)
self.session.request.assert_called_once_with(
method='POST',
url='http://example.com/v1/test',
json={'nested_attr': {'fake': 'data'},
'shallow_attr': 'first-level'},
endpoint_filter={'service_type': 'fleet_management'},
)
self.assertIsInstance(resource, crud.Resource)
self.assertEqual('Test', resource.name)
self.assertEqual(1234, resource.id)
def test_successful_delete(self):
"""Verify our delete returns True for a successful delete."""
self.session.request.return_value = self.create_response(
status_code=204,
)
self.assertTrue(self.client.delete(1))
def test_not_successful_delete(self):
"""Verify our delete returns False for a failed delete."""
self.session.request.return_value = self.create_response(
status_code=404,
)
self.assertRaises(exc.NotFound, self.client.delete, 1)
def test_delete_request(self):
"""Verify our delete request."""
self.session.request.return_value = self.create_response(
status_code=204,
)
self.client.delete(1)
self.session.request.assert_called_once_with(
method='DELETE',
url='http://example.com/v1/test/1',
json=None,
params={},
endpoint_filter={'service_type': 'fleet_management'},
)
def test_get(self):
"""Verify the request to retrieve an item."""
self.session.request.return_value = self.create_response(
status_code=200,
json_return_value={'name': 'Test', 'id': 1234},
)
resource = self.client.get(1)
self.session.request.assert_called_once_with(
method='GET',
url='http://example.com/v1/test/1',
endpoint_filter={'service_type': 'fleet_management'},
)
self.assertIsInstance(resource, crud.Resource)
self.assertEqual('Test', resource.name)
self.assertEqual(1234, resource.id)
def test_list(self):
"""Verify the request to list a resource."""
self.session.request.return_value = self.create_response(
status_code=200,
json_return_value={
'test_keys': [{'name': 'Test', 'id': 1234}],
'links': [],
},
)
resources = list(self.client.list(filter_by='some-attribute'))
self.session.request.assert_called_once_with(
method='GET',
url='http://example.com/v1/test',
params={'filter_by': 'some-attribute'},
endpoint_filter={'service_type': 'fleet_management'},
)
for resource in resources:
self.assertIsInstance(resource, crud.Resource)
self.assertEqual('Test', resource.name)
self.assertEqual(1234, resource.id)
def test_update(self):
"""Verify the request to update a resource."""
self.session.request.return_value = self.create_response(
status_code=200,
json_return_value={'name': 'Test', 'id': 1234},
)
resource = self.client.update(1234, name='New Test')
self.session.request.assert_called_once_with(
method='PUT',
url='http://example.com/v1/test/1234',
json={'name': 'New Test'},
endpoint_filter={'service_type': 'fleet_management'},
)
self.assertIsInstance(resource, crud.Resource)
self.assertEqual('Test', resource.name)
self.assertEqual(1234, resource.id)

View File

@ -1 +0,0 @@
"""Integration tests for Python API client for v1 API."""

View File

@ -1,121 +0,0 @@
# -*- 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.
"""Module containing the cratonclient.v1.cells integration tests."""
from cratonclient.tests.integration import base
class TestCells(base.BetamaxTestCase):
"""CellsManager integration tests."""
def setUp(self):
"""Prepare our cells test case."""
super(TestCells, self).setUp()
self.create_demo_client()
self.cloud = self.cleanupCloud(self.client.clouds.create(
name='cells-cloud-{}'.format(self.cassette_name)
))
self.region = self.cleanupRegion(self.client.regions.create(
name='cells-region-{}'.format(self.cassette_name),
cloud_id=self.cloud.id,
))
def test_create(self):
"""Test creation of a cell via the API."""
note = 'This is a test cell. There are many like it, but this is mine'
cell = self.cleanupCell(self.client.cells.create(
name='cell-0',
region_id=self.region.id,
cloud_id=self.cloud.id,
note=note,
variables={'a': 'b'},
))
self.assertEqual('cell-0', cell.name)
self.assertEqual(self.region.id, cell.region_id)
self.assertEqual(self.cloud.id, cell.cloud_id)
self.assertEqual(note, cell.note)
def test_delete(self):
"""Test deleting a cell after creating it."""
cell = self.client.cells.create(
name='cell-to-delete',
region_id=self.region.id,
cloud_id=self.cloud.id,
)
self.assertEqual('cell-to-delete', cell.name)
self.assertTrue(self.client.cells.delete(cell.id))
self.assertNotFound(self.client.cells.get, cell.id)
def test_autopagination_when_listing(self):
"""Verify the client autopaginates lists of cells."""
note_str = 'This was created automatically for pagination. ({}/63)'
for i in range(0, 63):
self.cleanupCell(self.client.cells.create(
name='pagination-cell-{}'.format(i),
region_id=self.region.id,
cloud_id=self.cloud.id,
note=note_str.format(i),
))
cells = list(self.client.cells.list())
self.assertEqual(63, len(cells))
def test_manual_pagination(self):
"""Verify manual pagination of cells."""
note_str = 'This was created automatically for pagination. ({}/63)'
for i in range(0, 63):
self.cleanupCell(self.client.cells.create(
name='pagination-cell-{}'.format(i),
region_id=self.region.id,
cloud_id=self.cloud.id,
note=note_str.format(i),
))
cells = list(self.client.cells.list(autopaginate=False))
self.assertEqual(30, len(cells))
next_page = list(self.client.cells.list(
marker=cells[-1].id,
autopaginate=False,
))
self.assertEqual(30, len(next_page))
last_page = list(self.client.cells.list(
marker=next_page[-1].id,
autopaginate=False,
))
self.assertEqual(3, len(last_page))
def test_update_existing_cell(self):
"""Verify we can update a cell."""
cell = self.cleanupCell(self.client.cells.create(
name='cell-to-update',
region_id=self.region.id,
cloud_id=self.cloud.id,
note='Original note',
variables={'out-with': 'the-old'},
))
self.assertEqual('cell-to-update', cell.name)
self.assertEqual('Original note', cell.note)
updated_cell = self.client.cells.update(
item_id=cell.id,
note='Updated note.',
)
self.assertEqual(cell.id, updated_cell.id)
self.assertEqual(cell.name, updated_cell.name)
self.assertEqual('Updated note.', updated_cell.note)

View File

@ -1,103 +0,0 @@
# -*- 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.
"""The integration tests for the cratonclient.v1.clouds."""
from cratonclient.tests.integration import base
class TestClouds(base.BetamaxTestCase):
"""CloudsManager integration tests."""
def setUp(self):
"""Prepare our clouds manager test case."""
super(TestClouds, self).setUp()
self.create_demo_client()
def test_create(self):
"""Test cloud creation via the API."""
note = 'This is a test cloud.'
cloud = self.cleanupCloud(self.client.clouds.create(
name='cloud-creation',
note=note,
variables={'cloud-var': 'var-value'},
))
self.assertEqual('cloud-creation', cloud.name)
self.assertEqual(note, cloud.note)
self.assertEqual({'cloud-var': 'var-value'},
cloud.to_dict()['variables'])
def test_delete(self):
"""Verify the client can delete a cloud."""
cloud = self.client.clouds.create(name='cloud-deletion')
self.assertEqual('cloud-deletion', cloud.name)
self.assertTrue(self.client.clouds.delete(cloud.id))
self.assertNotFound(self.client.clouds.get, cloud.id)
def test_autopagination_when_listing(self):
"""Verify the client autopaginates lists of clouds."""
note_str = 'This cloud was created to test pagination. ({}/62)'
for i in range(0, 62):
self.cleanupCloud(self.client.clouds.create(
name='cloud-{}'.format(i),
note=note_str.format(i),
))
cells = list(self.client.clouds.list())
self.assertEqual(62, len(cells))
def test_manual_pagination(self):
"""Verify manual pagination of /v1/clouds."""
note_str = 'This cloud was created to test pagination. ({}/62)'
for i in range(0, 62):
self.cleanupCloud(self.client.clouds.create(
name='cloud-{}'.format(i),
note=note_str.format(i),
))
first_page = list(self.client.clouds.list(autopaginate=False))
self.assertEqual(30, len(first_page))
next_page = list(self.client.clouds.list(
autopaginate=False,
marker=first_page[-1].id,
))
self.assertEqual(30, len(next_page))
last_page = list(self.client.clouds.list(
autopaginate=False,
marker=next_page[-1].id,
))
self.assertEqual(2, len(last_page))
def test_update_existing_cloud(self):
"""Test that the client allows a cloud to be deleted."""
cloud = self.cleanupCloud(self.client.clouds.create(
name='cloud-to-update',
note='Original note.',
))
self.assertEqual('cloud-to-update', cloud.name)
self.assertEqual('Original note.', cloud.note)
updated_cloud = self.client.clouds.update(
cloud.id,
name='updated-cloud',
note='Updated note.',
)
self.assertEqual(cloud.id, updated_cloud.id)
self.assertEqual('updated-cloud', updated_cloud.name)
self.assertEqual('Updated note.', updated_cloud.note)

View File

@ -1,79 +0,0 @@
# -*- 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.
"""The integration tests for the cratonclient.v1.devices."""
from cratonclient.tests.integration import base
class TestDevices(base.BetamaxTestCase):
"""DevicesManager integration tests."""
def cleanupCloud(self, cloud):
"""Add a cleanup task for this cloud."""
self.addCleanup(self.client.clouds.delete, cloud.id)
return cloud
def cleanupRegion(self, region):
"""Add a cleanup task for this region."""
self.addCleanup(self.client.regions.delete, region.id)
return region
def cleanupCell(self, cell):
"""Add a cleanup task for this cell."""
self.addCleanup(self.client.cells.delete, cell.id)
return cell
def cleanupHost(self, host):
"""Add a cleanup task for this host."""
self.addCleanup(self.client.hosts.delete, host.id)
return host
def setUp(self):
"""Set up our demo user client."""
super(TestDevices, self).setUp()
self.create_demo_client()
test_name = self.cassette_name.split('-', 1)[-1]
self.cloud = self.cleanupCloud(self.client.clouds.create(
name='cloud_{}'.format(test_name),
))
self.region = self.cleanupRegion(self.client.regions.create(
name='region_{}'.format(test_name),
cloud_id=self.cloud.id,
))
self.cells = [
self.cleanupCell(self.client.cells.create(
name='cell_{}_{}'.format(test_name, i),
region_id=self.region.id,
cloud_id=self.cloud.id,
))
for i in range(4)
]
self.hosts = [
self.cleanupHost(self.client.hosts.create(
name='host_{}_{}'.format(test_name, i),
cell_id=self.cells[i % 4].id,
region_id=self.region.id,
cloud_id=self.cloud.id,
device_type='server',
ip_address='127.0.1.{}'.format(i),
))
for i in range(35)
]
# NOTE(sigmavirus24): The API does not presently support
# /v1/network-devices
# self.network_devices = [
# self.cleanupNetworkDevice(self.client.network_devices.create(
# ))
# for i in range(35)
# ]

View File

@ -1,107 +0,0 @@
# -*- 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.
"""Module containing the cratonclient.v1.hosts integration tests."""
from cratonclient.tests.integration import base
class TestHosts(base.BetamaxTestCase):
"""HostsManager integration tests."""
def setUp(self):
"""Prepare our hosts test case."""
super(TestHosts, self).setUp()
self.create_demo_client()
self.cloud = self.cleanupCloud(self.client.clouds.create(
name='cloud-{}'.format(self.cassette_name),
))
self.region = self.cleanupRegion(self.client.regions.create(
name='region-{}'.format(self.cassette_name),
cloud_id=self.cloud.id,
))
def test_create(self):
"""Test creation of hosts via the Python API."""
host = self.cleanupHost(self.client.hosts.create(
name='host-0',
ip_address='127.0.1.0',
device_type='server',
region_id=self.region.id,
cloud_id=self.cloud.id,
))
self.assertEqual('host-0', host.name)
def test_delete(self):
"""Test deletion of a host via the Python API."""
host = self.client.hosts.create(
name='host-to-delete',
ip_address='127.0.1.0',
device_type='server',
region_id=self.region.id,
cloud_id=self.cloud.id,
)
self.assertTrue(self.client.hosts.delete(host.id))
self.assertNotFound(self.client.hosts.get, host.id)
def test_list_autopaginates(self):
"""Verify listing of hosts via the Python API."""
for i in range(0, 62):
self.cleanupHost(self.client.hosts.create(
name='host-{}'.format(i),
ip_address='127.0.1.{}'.format(i),
device_type='server',
region_id=self.region.id,
cloud_id=self.cloud.id,
))
hosts = list(self.client.hosts.list())
self.assertEqual(62, len(hosts))
def test_list_only_retrieves_first_page(self):
"""Verify the behaviour of not auto-paginating listing."""
for i in range(0, 32):
self.cleanupHost(self.client.hosts.create(
name='host-{}'.format(i),
ip_address='127.0.1.{}'.format(i),
device_type='server',
region_id=self.region.id,
cloud_id=self.cloud.id,
))
hosts = list(self.client.hosts.list(autopaginate=False))
self.assertEqual(30, len(hosts))
def test_update(self):
"""Verify the ability to update a host."""
host = self.cleanupHost(self.client.hosts.create(
name='host-0',
ip_address='127.0.1.0',
device_type='server',
region_id=self.region.id,
cloud_id=self.cloud.id,
))
self.assertTrue(host.active)
updated_host = self.client.hosts.update(
item_id=host.id,
note='This is an updated note',
ip_address='127.0.1.1',
)
self.assertEqual('host-0', updated_host.name)
self.assertEqual(host.id, updated_host.id)
self.assertEqual('This is an updated note', updated_host.note)

View File

@ -1,106 +0,0 @@
# -*- 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.
"""Module containing the cratonclient.v1.regions integration tests."""
from cratonclient.tests.integration import base
class TestRegions(base.BetamaxTestCase):
"""Tests for v1 RegionsManager."""
def setUp(self):
"""Prepare our test case for regions."""
super(TestRegions, self).setUp()
self.create_demo_client()
self.cloud = self.cleanupCloud(self.client.clouds.create(
name='cloud-{}'.format(self.cassette_name),
))
def test_create(self):
"""Verify creation of regions via the API."""
region = self.cleanupRegion(self.client.regions.create(
name='region-creation',
cloud_id=self.cloud.id,
))
self.assertEqual('region-creation', region.name)
self.assertEqual(self.cloud.id, region.cloud_id)
same_region = self.client.regions.get(region.id)
self.assertEqual(region.id, same_region.id)
self.assertEqual(region.name, same_region.name)
self.assertEqual(region.cloud_id, same_region.cloud_id)
def test_delete(self):
"""Verify deletion of regions via the API."""
region = self.client.regions.create(
name='region-creation',
cloud_id=self.cloud.id,
)
self.assertTrue(self.client.regions.delete(region.id))
self.assertNotFound(self.client.regions.get, region.id)
def test_list_autopaginates(self):
"""Verify autopagination when listing regions."""
for i in range(64):
self.cleanupRegion(self.client.regions.create(
name='regions-autopaginate-{}'.format(i),
cloud_id=self.cloud.id,
))
regions = list(self.client.regions.list())
self.assertEqual(64, len(regions))
def test_manual_pagination(self):
"""Verify manual pagination of regions."""
for i in range(64):
self.cleanupRegion(self.client.regions.create(
name='regions-manual-list-{}'.format(i),
cloud_id=self.cloud.id,
))
first_page = list(self.client.regions.list(autopaginate=False))
self.assertEqual(30, len(first_page))
next_page = list(self.client.regions.list(
autopaginate=False,
marker=first_page[-1].id,
))
self.assertEqual(30, len(next_page))
last_page = list(self.client.regions.list(
autopaginate=False,
marker=next_page[-1].id,
))
self.assertEqual(4, len(last_page))
def test_update(self):
"""Verify the ability to update a given region."""
region = self.cleanupRegion(self.client.regions.create(
name='region-to-update',
cloud_id=self.cloud.id,
))
self.assertTrue('region-to-update', region.name)
self.assertIsNone(region.note)
updated_region = self.client.regions.update(
region.id,
name='region_updated',
note='Here I add my note.',
)
self.assertEqual(region.id, updated_region.id)
self.assertNotEqual(region.name, updated_region.name)
self.assertEqual('region_updated', updated_region.name)
self.assertEqual('Here I add my note.', updated_region.note)

View File

@ -1,12 +0,0 @@
# 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.
"""Unit tests for cratonclient."""

View File

@ -1 +0,0 @@
"""Unit tests for cratonclient.common submodules."""

View File

@ -1,134 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright 2010-2011 OpenStack Foundation
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
#
# 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.
"""Unit tests for the cratonclient.crud module members."""
import mock
from cratonclient.common import cliutils
from cratonclient.tests import base
class TestCLIUtils(base.TestCase):
"""Test for the CRUDClient class."""
def test_convert_arg_value_bool(self):
"""Assert bool conversion."""
trues = ['true', 'TRUE', 'True', 'trUE']
falses = ['false', 'FALSE', 'False', 'falSe']
for true in trues:
self.assertTrue(cliutils.convert_arg_value(true))
for false in falses:
self.assertFalse(cliutils.convert_arg_value(false))
def test_convert_arg_value_none(self):
"""Assert None conversion."""
nones = ['none', 'null', 'NULL', 'None', 'NONE']
for none in nones:
self.assertIsNone(cliutils.convert_arg_value(none))
def test_convert_arg_value_integer(self):
"""Assert integer conversion."""
ints = ['1', '10', '145']
for integer in ints:
value = cliutils.convert_arg_value(integer)
self.assertTrue(isinstance(value, int))
def test_convert_arg_value_float(self):
"""Assert float conversion."""
floats = ['5.234', '1.000', '1.0001', '224.1234']
for num in floats:
value = cliutils.convert_arg_value(num)
self.assertTrue(isinstance(value, float))
def test_convert_arg_value_string(self):
"""Assert string conversion."""
strings = ["hello", "path/to/thing", "sp#cial!", "heyy:this:works"]
for string in strings:
value = cliutils.convert_arg_value(string)
self.assertTrue(isinstance(value, str))
def test_convert_arg_value_escapes(self):
"""Assert escaped conversion works to afford literal values."""
escapes = ['"007"', '"1"', '"1.0"', '"False"', '"True"', '"None"']
for escaped in escapes:
value = cliutils.convert_arg_value(escaped)
self.assertTrue(isinstance(value, str))
@mock.patch('cratonclient.common.cliutils.sys.stdin')
def test_variable_updates_from_args(self, mock_stdin):
"""Assert cliutils.variable_updates(...) when using arguments."""
test_data = ["foo=bar", "test=", "baz=1", "bumbleywump=cucumberpatch"]
mock_stdin.isatty.return_value = True
expected_updates = {
"foo": "bar",
"baz": 1,
"bumbleywump": "cucumberpatch"
}
expected_deletes = ["test"]
updates, deletes = cliutils.variable_updates(test_data)
self.assertEqual(expected_updates, updates)
self.assertEqual(expected_deletes, deletes)
@mock.patch('cratonclient.common.cliutils.sys.stdin')
def test_variable_updates_from_stdin(self, mock_stdin):
"""Assert cliutils.variable_updates(...) when using stdin."""
mock_stdin.isatty.return_value = False
mock_stdin.read.return_value = \
'{"foo": {"bar": "baz"}, "bumbleywump": "cucumberpatch"}'
expected_updates = {
"foo": {
"bar": "baz"
},
"bumbleywump": "cucumberpatch"
}
updates, deletes = cliutils.variable_updates([])
self.assertEqual(expected_updates, updates)
self.assertEqual([], deletes)
@mock.patch('cratonclient.common.cliutils.sys.stdin')
def test_variable_deletes_from_args(self, mock_stdin):
"""Assert cliutils.variable_deletes(...) when using arguments."""
test_data = ["foo", "test", "baz"]
mock_stdin.isatty.return_value = True
expected_deletes = test_data
deletes = cliutils.variable_deletes(test_data)
self.assertEqual(expected_deletes, deletes)
@mock.patch('cratonclient.common.cliutils.sys.stdin')
def test_variable_deletes_from_stdin(self, mock_stdin):
"""Assert cliutils.variable_deletes(...) when using stdin."""
mock_stdin.isatty.return_value = False
mock_stdin.read.return_value = \
'["foo", "test", "baz"]'
expected_deletes = ["foo", "test", "baz"]
deletes = cliutils.variable_deletes([])
self.assertEqual(expected_deletes, deletes)

View File

@ -1,12 +0,0 @@
# 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.
"""Test module for the cratonclient.formatters submodule."""

View File

@ -1,74 +0,0 @@
# 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.
"""Base TestCase class for Formatter tests."""
import argparse
import mock
import six
from cratonclient.tests import base
class FormatterTestCase(base.TestCase):
"""Base-level Formatter TestCase class."""
def patch_stdout(self, autostart=True):
"""Patch sys.stdout and capture all output to it.
This will automatically start the patch by default.
:param bool autostart:
Start patching sys.stdout immediately or delay that until later.
"""
self.stdout_patcher = mock.patch('sys.stdout', new=six.StringIO())
if autostart:
self.stdout = self.stdout_patcher.start()
self.addCleanup(self.unpatch_stdout)
def unpatch_stdout(self):
"""Safely unpatch standard out."""
if getattr(self.stdout_patcher, 'is_local', None) is not None:
self.stdout_patcher.stop()
def stripped_stdout(self):
"""Return the newline stripped standard-out captured string."""
stdout = self.stdout.getvalue().rstrip('\n')
self.unpatch_stdout()
return stdout
def args_for(self, **kwargs):
"""Return an instantiated argparse Namsepace.
Using the specified keyword arguments, create and return a Namespace
object from argparse for testing purposes.
:returns:
Instantiated namespace.
:rtype:
argparse.Namespace
"""
return argparse.Namespace(**kwargs)
def resource_info(self, **kwargs):
"""Return a dictionary with resource information.
:returns:
Dictionary with basic id and name as well as the provided keyword
arguments.
:rtype:
dict
"""
info = {
'id': 1,
'name': 'Test Resource',
}
info.update(kwargs)
return info

View File

@ -1,63 +0,0 @@
# 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.
"""Tests for the base formatter class."""
import argparse
import mock
from cratonclient import crud
from cratonclient.formatters import base
from cratonclient.tests import base as testbase
class TestBaseFormatterInstantiation(testbase.TestCase):
"""Tests for cratonclient.formatters.base.Formatter creation."""
def test_instantiation_calls_after_init(self):
"""Verify we call our postinitialization hook."""
with mock.patch.object(base.Formatter, 'after_init') as after_init:
base.Formatter(mock.Mock())
after_init.assert_called_once_with()
def test_stores_namespace_object(self):
"""Verify we store our parsed CLI arguments."""
namespace = argparse.Namespace()
formatter = base.Formatter(namespace)
self.assertIs(namespace, formatter.args)
class TestBaseFormatter(testbase.TestCase):
"""Tests for cratonclient.formatters.base.Formatter behaviour."""
def setUp(self):
"""Create test resources."""
super(TestBaseFormatter, self).setUp()
self.formatter = base.Formatter(argparse.Namespace())
def test_handle_detects_resources(self):
"""Verify we handle instances explicitly."""
resource = crud.Resource(mock.Mock(), {"id": 1234})
method = 'handle_instance'
with mock.patch.object(self.formatter, method) as handle_instance:
self.formatter.handle(resource)
handle_instance.assert_called_once_with(resource)
def test_handle_detects_iterables(self):
"""Verify we handle generators explicitly."""
method = 'handle_generator'
iterable = iter([])
with mock.patch.object(self.formatter, method) as handle_generator:
self.formatter.handle(iterable)
handle_generator.assert_called_once_with(iterable)

View File

@ -1,69 +0,0 @@
# 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.
"""JSON formatter unit tests."""
import json
import mock
from cratonclient import crud
from cratonclient.formatters import json_format
from cratonclient.tests.unit.formatters import base
class TestValidFormatting(base.FormatterTestCase):
"""Validate the JSON formatter's console output."""
def setUp(self):
"""Initialize our instance prior to each test."""
super(TestValidFormatting, self).setUp()
self.formatter = json_format.Formatter(self.args_for())
self.patch_stdout()
def load_json(self, stdout):
"""Load JSON data from standard-out capture.
If there's an error decoding the JSON output, fail the test
automatically.
"""
try:
return json.loads(stdout)
except ValueError as err:
self.fail('Encountered a ValueError: %s' % err)
def test_instance_handling_creates_valid_json(self):
"""Verify the printed data is valid JSON."""
info = self.resource_info()
instance = crud.Resource(mock.Mock(), info, loaded=True)
self.formatter.handle(instance)
parsed = self.load_json(self.stripped_stdout())
self.assertDictEqual(info, parsed)
def test_empty_generator_handling(self):
"""Verify we simply print an empty list."""
self.formatter.handle(iter([]))
parsed = self.load_json(self.stripped_stdout())
self.assertEqual([], parsed)
def test_generator_of_a_single_resource(self):
"""Verify we print the single list appropriately."""
info = self.resource_info()
self.formatter.handle(iter([crud.Resource(mock.Mock(), info, True)]))
parsed = self.load_json(self.stripped_stdout())
self.assertListEqual([info], parsed)
def test_generator_of_more_than_one_resouurce(self):
"""Verify we handle multiple items in a generator correctly."""
info_dicts = [self.resource_info(id=i) for i in range(10)]
self.formatter.handle(crud.Resource(mock.Mock(), info, True)
for info in info_dicts)
parsed = self.load_json(self.stripped_stdout())
self.assertListEqual(info_dicts, parsed)

View File

@ -1,229 +0,0 @@
# 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.
"""Tests for the pretty-table formatter."""
import mock
import prettytable
from cratonclient import crud
from cratonclient.formatters import table
from cratonclient.tests.unit.formatters import base
class TestTableFormatter(base.FormatterTestCase):
"""Tests for cratonclient.formatters.table.Formatter."""
def setUp(self):
"""Prepare test case for tests."""
super(TestTableFormatter, self).setUp()
self.print_patcher = mock.patch('cratonclient.formatters.table.print')
self.formatter = table.Formatter(mock.Mock())
def test_initialization(self):
"""Verify we set up defaults for our PrettyTable formatter."""
self.assertEqual([], self.formatter.fields)
self.assertEqual({}, self.formatter.formatters)
self.assertIsNone(self.formatter.sortby_index)
self.assertEqual(set([]), self.formatter.mixed_case_fields)
self.assertEqual([], self.formatter.field_labels)
self.assertEqual("Property", self.formatter.dict_property)
self.assertEqual("Value", self.formatter.dict_value)
self.assertEqual(0, self.formatter.wrap)
# Case 0: "Everything" that isn't one of the special cases below
def test_configure(self):
"""Verify we can configure our formatter.
There are a few special pieces of logic. For the simpler cases, we can
just exercise those branches here.
"""
self.formatter.configure(
mixed_case_fields=['Foo', 'Bar'],
dict_property='Field',
dict_value='Stored Value',
wrap=72,
# NOTE(sigmavirus24): This value isn't accurate for formatters
formatters={'foo': 'bar'},
)
self.assertEqual({'Foo', 'Bar'}, self.formatter.mixed_case_fields)
self.assertEqual('Field', self.formatter.dict_property)
self.assertEqual('Stored Value', self.formatter.dict_value)
self.assertEqual(72, self.formatter.wrap)
self.assertDictEqual({'foo': 'bar'}, self.formatter.formatters)
# Assert defaults remain unchanged
self.assertEqual([], self.formatter.fields)
self.assertEqual([], self.formatter.field_labels)
self.assertIsNone(self.formatter.sortby_index)
# Case 1: Just fields
def test_configure_fields_only(self):
"""Verify the logic for configuring fields."""
self.formatter.configure(fields=['id', 'name'])
self.assertListEqual(['id', 'name'], self.formatter.fields)
self.assertListEqual(['Id', 'Name'], self.formatter.field_labels)
# Case 2: fields + field_labels
def test_configure_fields_and_field_labels(self):
"""Verify the behaviour for specifying both fields and field_labels.
When we specify both arguments, we need to ensure they're the same
length. This demonstrates that we can specify different lists of the
same length and one won't override the other.
"""
self.formatter.configure(fields=['id', 'name'],
field_labels=['name', 'id'])
self.assertListEqual(['id', 'name'], self.formatter.fields)
self.assertListEqual(['name', 'id'], self.formatter.field_labels)
# Case 3: fields + field_labels different length
def test_configure_incongruent_fields_and_field_labels(self):
"""Verify we check the length of fields and field_labels."""
self.assertRaises(
ValueError,
self.formatter.configure,
fields=['id', 'name', 'extra'],
field_labels=['id', 'name'],
)
self.assertRaises(
ValueError,
self.formatter.configure,
fields=['id', 'name'],
field_labels=['id', 'name', 'extra'],
)
# Case 4: sortby_index is None
def test_configure_null_sortby_index(self):
"""Verify we can configure sortby_index to be None.
In this case, the user does not want the table rows sorted.
"""
self.formatter.configure(sortby_index=None)
self.assertIsNone(self.formatter.sortby_index)
# Case 5: sortby_index is an integer
def test_configure_sortby_index_non_negative_int(self):
"""Verify we can configure sortby_index with an int."""
self.formatter.configure(
fields=['id', 'name'],
sortby_index=1,
)
self.assertEqual(1, self.formatter.sortby_index)
# Case 6: sortby_index is a string of digits
def test_configure_sortby_index_int_str(self):
"""Verify we can configure sortby_index with a str.
It makes sense to also allow for strings of integers. This test
ensures that they come out as integers on the other side.
"""
self.formatter.configure(
fields=['id', 'name'],
sortby_index='1',
)
self.assertEqual(1, self.formatter.sortby_index)
# Case 7: sortby_index is negative
def test_configure_sortby_index_negative_int(self):
"""Verify we cannot configure sortby_index with a negative value.
This will verify that we can neither pass negative integers nor
strings with negative integer values.
"""
self.assertRaises(
ValueError,
self.formatter.configure,
fields=['id', 'name'],
sortby_index='-1',
)
self.assertRaises(
ValueError,
self.formatter.configure,
fields=['id', 'name'],
sortby_index='-1',
)
# Case 8: sortby_index exceeds length of self.field_labels
def test_configure_sortby_index_too_large_int(self):
"""Verify we can not use an index larger than the labels."""
self.assertRaises(
ValueError,
self.formatter.configure,
fields=['id', 'name'],
sortby_index=3,
)
def test_sortby_kwargs(self):
"""Verify sortby_kwargs relies on sortby_index."""
self.formatter.field_labels = ['id', 'created_at']
self.formatter.sortby_index = 0
self.assertDictEqual({'sortby': 'id'}, self.formatter.sortby_kwargs())
self.formatter.sortby_index = 1
self.assertDictEqual({'sortby': 'created_at'},
self.formatter.sortby_kwargs())
self.formatter.sortby_index = None
self.assertDictEqual({}, self.formatter.sortby_kwargs())
def test_build_table(self):
"""Verify that we build our table and auto-align it."""
table = self.formatter.build_table(['id', 'created_at'])
self.assertIsInstance(table, prettytable.PrettyTable)
self.assertDictEqual({'created_at': 'l', 'id': 'l'}, table.align)
def test_build_table_with_labels(self):
"""Verify we pass along our field labels to our table."""
with mock.patch('prettytable.PrettyTable') as PrettyTable:
self.formatter.build_table(['id', 'created_at'])
PrettyTable.assert_called_once_with(['id', 'created_at'])
def test_handle_instance(self):
"""Verify our handling of resource instances."""
resource = crud.Resource(mock.Mock(), self.resource_info())
self.print_ = self.print_patcher.start()
mocktable = mock.Mock()
mocktable.get_string.return_value = ''
with mock.patch('prettytable.PrettyTable') as PrettyTable:
PrettyTable.return_value = mocktable
self.formatter.handle_instance(resource)
self.print_patcher.stop()
PrettyTable.assert_called_once_with(["Property", "Value"])
self.assertListEqual([
mock.call(['id', 1]),
mock.call(['name', 'Test Resource']),
], mocktable.add_row.call_args_list)
self.print_.assert_called_once_with('')
def test_handle_generator(self):
"""Verify how we handle generators of instances."""
info_list = [self.resource_info(id=i) for i in range(15)]
self.print_ = self.print_patcher.start()
mocktable = mock.Mock()
mocktable.get_string.return_value = ''
self.formatter.configure(fields=['id', 'Name'])
with mock.patch('prettytable.PrettyTable') as PrettyTable:
PrettyTable.return_value = mocktable
self.formatter.handle_generator(crud.Resource(mock.Mock(), info)
for info in info_list)
PrettyTable.assert_called_once_with(['Id', 'Name'])
self.assertListEqual(
[mock.call([i, 'Test Resource']) for i in range(15)],
mocktable.add_row.call_args_list,
)
mocktable.get_string.assert_called_once_with()
self.print_.assert_called_once_with('')

View File

@ -1 +0,0 @@
"""Unit tests for cratonclient.shell submodules."""

View File

@ -1,56 +0,0 @@
# -*- 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.
"""Base class for shell unit tests."""
import argparse
import mock
from cratonclient import exceptions
from cratonclient.tests import base
class TestShellCommand(base.TestCase):
"""Base class for shell command unit tests."""
def setUp(self):
"""Initialize test fixtures."""
super(TestShellCommand, self).setUp()
self.formatter = mock.Mock()
self.formatter.configure.return_value = self.formatter
self.craton_client = mock.Mock()
self.inventory = mock.Mock()
self.craton_client.inventory.return_value = self.inventory
def assertRaisesCommandErrorWith(self, func, args):
"""Assert the shell command raises CommandError."""
self.assertRaises(
exceptions.CommandError,
func, self.craton_client, args,
)
def args_for(self, **kwargs):
"""Return a Namespace object with the specified kwargs."""
kwargs.setdefault('formatter', self.formatter)
return argparse.Namespace(**kwargs)
def assertNothingWasCalled(self):
"""Assert nothing was called on the formatter."""
self.assertListEqual([], self.craton_client.mock_calls)
self.assertFalse(self.formatter.configure.called)
self.assertFalse(self.formatter.handle.called)
def assertFieldsEqualTo(self, expected_fields):
"""Assert the sorted fields parameter is equal expected_fields."""
kwargs = self.formatter.configure.call_args[1]
self.assertListEqual(expected_fields, kwargs['fields'])

View File

@ -1,171 +0,0 @@
# 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.
"""Tests for the cratonclient.shell.main module."""
import argparse
import sys
import mock
import six
import cratonclient
from cratonclient.shell import main
from cratonclient.tests import base
class TestEntryPoint(base.TestCase):
"""Tests for the craton shell entry-point."""
def setUp(self):
"""Patch out the CratonShell class."""
super(TestEntryPoint, self).setUp()
self.class_mock = mock.patch('cratonclient.shell.main.CratonShell')
self.craton_shell = self.class_mock.start()
self.addCleanup(self.class_mock.stop)
self.print_mock = mock.patch('cratonclient.shell.main.print')
self.print_func = self.print_mock.start()
self.addCleanup(self.print_mock.stop)
self.sys_exit_mock = mock.patch('sys.exit')
self.sys_exit = self.sys_exit_mock.start()
self.addCleanup(self.sys_exit_mock.stop)
def test_entry_point_creates_a_shell_instance(self):
"""Verify that our main entry-point uses CratonShell."""
CratonShell = self.craton_shell
main.main()
CratonShell.assert_called_once_with()
def test_entry_point_calls_shell_main_method(self):
"""Verify we call the main method on our CratonShell instance."""
shell_instance = mock.Mock()
self.craton_shell.return_value = shell_instance
main.main()
self.assertTrue(shell_instance.main.called)
def test_entry_point_converts_args_to_text(self):
"""Verify we call the main method with a list of text objects."""
shell_instance = mock.Mock()
self.craton_shell.return_value = shell_instance
main.main()
# NOTE(sigmavirus24): call_args is a tuple of positional arguments and
# keyword arguments, so since we pass a list positionally, we want the
# first of the positional arguments.
arglist = shell_instance.main.call_args[0][0]
self.assertTrue(
all(isinstance(arg, six.text_type) for arg in arglist)
)
def test_entry_point_handles_all_exceptions(self):
"""Verify that we handle unexpected exceptions and print a message."""
shell_instance = mock.Mock()
shell_instance.main.side_effect = ValueError
self.craton_shell.return_value = shell_instance
main.main()
self.print_func.assert_called_once_with(
"ERROR: ",
file=sys.stderr,
)
class TestCratonShell(base.TestCase):
"""Tests for the CratonShell class."""
def setUp(self):
"""Create an instance of CratonShell for each test."""
super(TestCratonShell, self).setUp()
self.shell = main.CratonShell()
def test_get_base_parser(self):
"""Verify how we construct our basic Argument Parser."""
with mock.patch('argparse.ArgumentParser') as ArgumentParser:
parser = self.shell.get_base_parser()
self.assertEqual(ArgumentParser.return_value, parser)
ArgumentParser.assert_called_once_with(
prog='craton',
description=('Main shell for parsing arguments directed toward '
'Craton.'),
epilog='See "craton help COMMAND" for help on a specific command.',
add_help=False,
formatter_class=argparse.HelpFormatter,
)
def test_get_base_parser_sets_default_options(self):
"""Verify how we construct our basic Argument Parser."""
with mock.patch('cratonclient.common.cliutils.env') as env:
env.return_value = ''
with mock.patch('argparse.ArgumentParser'):
parser = self.shell.get_base_parser()
self.assertEqual([
mock.call(
'-h', '--help', action='store_true', help=argparse.SUPPRESS,
),
mock.call(
'--version', action='version',
version=cratonclient.__version__,
),
mock.call(
'--format', default='default', choices=['default', 'json'],
help=mock.ANY,
),
mock.call(
'--craton-url', default='',
help='The base URL of the running Craton service. '
'Defaults to env[CRATON_URL].',
),
mock.call(
'--craton-version', default='',
type=int,
help='The version of the Craton API to use. '
'Defaults to env[CRATON_VERSION].'
),
mock.call(
'--os-project-id', default='',
help='The project ID used to authenticate to Craton. '
'Defaults to env[OS_PROJECT_ID].',
),
mock.call(
'--os-username', default='',
help='The username used to authenticate to Craton. '
'Defaults to env[OS_USERNAME].',
),
mock.call(
'--os-password', default='',
help='The password used to authenticate to Craton. '
'Defaults to env[OS_PASSWORD].',
),
],
parser.add_argument.call_args_list,
)
def test_get_base_parser_retrieves_environment_values(self):
"""Verify the environment variables that are requested."""
with mock.patch('cratonclient.common.cliutils.env') as env:
self.shell.get_base_parser()
self.assertEqual([
mock.call('CRATON_URL'),
mock.call('CRATON_VERSION', default=1),
mock.call('OS_PROJECT_ID'),
mock.call('OS_USERNAME'),
mock.call('OS_PASSWORD'),
],
env.call_args_list,
)

View File

@ -1 +0,0 @@
"""Unit tests for cratonclient.shell.v1 submodules."""

View File

@ -1,393 +0,0 @@
# -*- 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.
"""Tests for the shell functions for the cells resource."""
import mock
from cratonclient import exceptions
from cratonclient.shell.v1 import cells_shell
from cratonclient.tests.unit.shell import base
class TestDoShellShow(base.TestShellCommand):
"""Unit tests for the cell show command."""
def test_simple_usage(self):
"""Verify the behaviour of do_cell_show."""
args = self.args_for(
region=123,
id=456,
)
cells_shell.do_cell_show(self.craton_client, args)
self.craton_client.cells.get.assert_called_once_with(456)
self.formatter.configure.assert_called_once_with(wrap=72)
self.assertEqual(1, self.formatter.handle.call_count)
class TestDoCellList(base.TestShellCommand):
"""Unit tests for the cell list command."""
def assertNothingWasCalled(self):
"""Assert inventory, list, and print_list were not called."""
super(TestDoCellList, self).assertNothingWasCalled()
self.assertFalse(self.formatter.configure.called)
self.assertFalse(self.formatter.handle.called)
def args_for(self, **kwargs):
"""Generate the default argument list for cell-list."""
kwargs.setdefault('region', 123)
kwargs.setdefault('cloud', None)
kwargs.setdefault('detail', False)
kwargs.setdefault('limit', None)
kwargs.setdefault('sort_key', None)
kwargs.setdefault('sort_dir', 'asc')
kwargs.setdefault('fields', cells_shell.DEFAULT_CELL_FIELDS)
kwargs.setdefault('marker', None)
kwargs.setdefault('all', False)
kwargs.setdefault('vars', None)
return super(TestDoCellList, self).args_for(**kwargs)
def test_with_defaults(self):
"""Verify the behaviour of do_cell_list with mostly default values."""
args = self.args_for()
cells_shell.do_cell_list(self.craton_client, args)
self.craton_client.cells.list.assert_called_once_with(
sort_dir='asc',
region_id=123,
autopaginate=False,
marker=None,
)
self.assertFieldsEqualTo(cells_shell.DEFAULT_CELL_FIELDS)
def test_with_cloud_id(self):
"""Verify the behaviour of do_cell_list with mostly default values."""
args = self.args_for(cloud=456)
cells_shell.do_cell_list(self.craton_client, args)
self.craton_client.cells.list.assert_called_once_with(
sort_dir='asc',
cloud_id=456,
region_id=123,
autopaginate=False,
marker=None,
)
self.assertFieldsEqualTo(cells_shell.DEFAULT_CELL_FIELDS)
def test_negative_limit(self):
"""Ensure we raise an exception for negative limits."""
args = self.args_for(limit=-1)
self.assertRaisesCommandErrorWith(cells_shell.do_cell_list, args)
self.assertNothingWasCalled()
def test_positive_limit(self):
"""Verify that we pass positive limits to the call to list."""
args = self.args_for(limit=5)
cells_shell.do_cell_list(self.craton_client, args)
self.craton_client.cells.list.assert_called_once_with(
limit=5,
sort_dir='asc',
region_id=123,
autopaginate=False,
marker=None,
)
self.assertFieldsEqualTo(cells_shell.DEFAULT_CELL_FIELDS)
def test_valid_sort_key(self):
"""Verify that we pass on our sort key."""
args = self.args_for(sort_key='name')
cells_shell.do_cell_list(self.craton_client, args)
self.craton_client.cells.list.assert_called_once_with(
sort_dir='asc',
sort_key='name',
region_id=123,
autopaginate=False,
marker=None,
)
self.assertFieldsEqualTo(cells_shell.DEFAULT_CELL_FIELDS)
def test_invalid_sort_key(self):
"""Verify that do not we pass on our sort key."""
args = self.args_for(sort_key='fake-sort-key')
self.assertRaisesCommandErrorWith(cells_shell.do_cell_list, args)
self.assertNothingWasCalled()
def test_detail(self):
"""Verify the behaviour of specifying --detail."""
args = self.args_for(detail=True)
cells_shell.do_cell_list(self.craton_client, args)
self.craton_client.cells.list.assert_called_once_with(
sort_dir='asc',
detail=True,
region_id=123,
autopaginate=False,
marker=None,
)
self.assertFieldsEqualTo(cells_shell.CELL_FIELDS)
def test_raises_exception_with_detail_and_fields(self):
"""Verify that we fail when users specify --detail and --fields."""
args = self.args_for(
detail=True,
fields=['id', 'name'],
)
self.assertRaisesCommandErrorWith(cells_shell.do_cell_list, args)
self.assertNothingWasCalled()
def test_fields(self):
"""Verify that we print out specific fields."""
args = self.args_for(fields=['id', 'name', 'note'])
cells_shell.do_cell_list(self.craton_client, args)
self.craton_client.cells.list.assert_called_once_with(
sort_dir='asc',
region_id=123,
autopaginate=False,
marker=None,
)
self.assertFieldsEqualTo(['id', 'name', 'note'])
def test_invalid_fields(self):
"""Verify that we error out with invalid fields."""
args = self.args_for(fields=['uuid', 'not-name', 'nate'])
self.assertRaisesCommandErrorWith(cells_shell.do_cell_list, args)
self.assertNothingWasCalled()
def test_with_multiple_vars(self):
"""Verify that we pass vars filters to cell list."""
args = self.args_for(vars=[['a:b'], ['c:d']])
cells_shell.do_cell_list(self.craton_client, args)
self.craton_client.cells.list.assert_called_once_with(
vars='a:b,c:d',
region_id=123,
marker=None,
sort_dir='asc',
autopaginate=False,
)
self.assertFieldsEqualTo(cells_shell.DEFAULT_CELL_FIELDS)
def test_autopaginate(self):
"""Verify that autopagination works."""
args = self.args_for(all=True)
cells_shell.do_cell_list(self.craton_client, args)
self.craton_client.cells.list.assert_called_once_with(
sort_dir='asc',
region_id=123,
limit=100,
autopaginate=True,
marker=None,
)
def test_autopagination_overrides_limit(self):
"""Verify that --all overrides --limit."""
args = self.args_for(all=True, limit=10)
cells_shell.do_cell_list(self.craton_client, args)
self.craton_client.cells.list.assert_called_once_with(
sort_dir='asc',
region_id=123,
limit=100,
autopaginate=True,
marker=None,
)
class TestDoCellCreate(base.TestShellCommand):
"""Unit tests for the cell create command."""
def args_for(self, **kwargs):
"""Generate the default args for cell-create."""
kwargs.setdefault('name', 'New Cell')
kwargs.setdefault('region_id', 123)
kwargs.setdefault('note', None)
return super(TestDoCellCreate, self).args_for(**kwargs)
def test_create_without_note(self):
"""Verify our parameters to cells.create."""
args = self.args_for()
cells_shell.do_cell_create(self.craton_client, args)
self.craton_client.cells.create.assert_called_once_with(
name='New Cell',
region_id=123,
)
self.formatter.configure.assert_called_once_with(wrap=72)
self.assertEqual(1, self.formatter.handle.call_count)
def test_create_with_note(self):
"""Verify that we include the note argument when present."""
args = self.args_for(note='This is a note')
cells_shell.do_cell_create(self.craton_client, args)
self.craton_client.cells.create.assert_called_once_with(
name='New Cell',
region_id=123,
note='This is a note',
)
self.formatter.configure.assert_called_once_with(wrap=72)
self.assertEqual(1, self.formatter.handle.call_count)
class TestDoCellUpdate(base.TestShellCommand):
"""Unit tests for the cell update command."""
def args_for(self, **kwargs):
"""Generate arguments for cell-update command."""
kwargs.setdefault('id', 123)
kwargs.setdefault('name', None)
kwargs.setdefault('region_id', None)
kwargs.setdefault('note', None)
return super(TestDoCellUpdate, self).args_for(**kwargs)
def test_update_without_name_region_or_note_fails(self):
"""Verify we raise a command error when there's nothing to update."""
args = self.args_for()
self.assertRaisesCommandErrorWith(cells_shell.do_cell_update, args)
self.assertNothingWasCalled()
def test_update_with_name(self):
"""Verify we update with only the new name."""
args = self.args_for(name='New name')
cells_shell.do_cell_update(self.craton_client, args)
self.craton_client.cells.update.assert_called_once_with(
123,
name='New name',
)
self.formatter.configure.assert_called_once_with(wrap=72)
self.assertEqual(1, self.formatter.handle.call_count)
def test_update_with_new_region(self):
"""Verify we update with only the new region id."""
args = self.args_for(region_id=678)
cells_shell.do_cell_update(self.craton_client, args)
self.craton_client.cells.update.assert_called_once_with(
123,
region_id=678,
)
self.formatter.configure.assert_called_once_with(wrap=72)
self.assertEqual(1, self.formatter.handle.call_count)
def test_update_with_new_note(self):
"""Verify we update with only the new note text."""
args = self.args_for(note='A new note')
cells_shell.do_cell_update(self.craton_client, args)
self.craton_client.cells.update.assert_called_once_with(
123,
note='A new note',
)
self.formatter.configure.assert_called_once_with(wrap=72)
self.assertEqual(1, self.formatter.handle.call_count)
def test_update_with_everything(self):
"""Verify we update with everything."""
args = self.args_for(
name='A new name for a new region',
region_id=678,
note='A new note',
)
cells_shell.do_cell_update(self.craton_client, args)
self.craton_client.cells.update.assert_called_once_with(
123,
name='A new name for a new region',
region_id=678,
note='A new note',
)
self.formatter.configure.assert_called_once_with(wrap=72)
self.assertEqual(1, self.formatter.handle.call_count)
class TestDoCellDelete(base.TestShellCommand):
"""Tests for the do_cell_delete command."""
def setUp(self):
"""Initialize our print mock."""
super(TestDoCellDelete, self).setUp()
self.print_func_mock = mock.patch(
'cratonclient.shell.v1.cells_shell.print'
)
self.print_func = self.print_func_mock.start()
def tearDown(self):
"""Clean up our print mock."""
super(TestDoCellDelete, self).tearDown()
self.print_func_mock.stop()
def test_successful(self):
"""Verify the message we print when successful."""
self.craton_client.cells.delete.return_value = True
args = self.args_for(
id=456,
)
cells_shell.do_cell_delete(self.craton_client, args)
self.craton_client.cells.delete.assert_called_once_with(456)
self.print_func.assert_called_once_with(
'Cell 456 was successfully deleted.'
)
def test_failed(self):
"""Verify the message we print when deletion fails."""
self.craton_client.cells.delete.return_value = False
args = self.args_for(
id=456,
)
cells_shell.do_cell_delete(self.craton_client, args)
self.craton_client.cells.delete.assert_called_once_with(456)
self.print_func.assert_called_once_with(
'Cell 456 was not deleted.'
)
def test_failed_with_exception(self):
"""Verify the message we print when deletion fails."""
self.craton_client.cells.delete.side_effect = exceptions.NotFound
args = self.args_for(
region=123,
id=456,
)
self.assertRaisesCommandErrorWith(cells_shell.do_cell_delete, args)
self.craton_client.cells.delete.assert_called_once_with(456)
self.assertFalse(self.print_func.called)

View File

@ -1,271 +0,0 @@
# -*- 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.
"""Tests for the shell functions for the clouds resource."""
import mock
from cratonclient import exceptions
from cratonclient.shell.v1 import clouds_shell
from cratonclient.tests.unit.shell import base
class TestDoCloudShow(base.TestShellCommand):
"""Unit tests for the cloud-show command."""
def test_prints_cloud_data(self):
"""Verify we print the data for the cloud."""
args = self.args_for(id=1234)
clouds_shell.do_cloud_show(self.craton_client, args)
self.craton_client.clouds.get.assert_called_once_with(1234)
self.formatter.configure.assert_called_once_with(wrap=72)
self.assertEqual(1, self.formatter.handle.call_count)
class TestDoCloudCreate(base.TestShellCommand):
"""Unit tests for the cloud-create command."""
def args_for(self, **kwargs):
"""Generate arguments for cloud-create."""
kwargs.setdefault('name', 'New cloud')
kwargs.setdefault('note', None)
return super(TestDoCloudCreate, self).args_for(**kwargs)
def test_accepts_only_required_arguments(self):
"""Verify operation with only --name provided."""
args = self.args_for()
clouds_shell.do_cloud_create(self.craton_client, args)
self.craton_client.clouds.create.assert_called_once_with(
name='New cloud',
)
self.formatter.configure.assert_called_once_with(wrap=72)
self.assertEqual(1, self.formatter.handle.call_count)
def test_accepts_optional_arguments(self):
"""Verify operation with --note passed as well."""
args = self.args_for(note='This is a note')
clouds_shell.do_cloud_create(self.craton_client, args)
self.craton_client.clouds.create.assert_called_once_with(
name='New cloud',
note='This is a note',
)
self.formatter.configure.assert_called_once_with(wrap=72)
self.assertEqual(1, self.formatter.handle.call_count)
class TestDoCloudUpdate(base.TestShellCommand):
"""Unit tests for cloud-update command."""
def args_for(self, **kwargs):
"""Generate arguments for cloud-update."""
kwargs.setdefault('id', 12345)
kwargs.setdefault('name', None)
kwargs.setdefault('note', None)
return super(TestDoCloudUpdate, self).args_for(**kwargs)
def test_nothing_to_update_raises_error(self):
"""Verify specifying nothing raises a CommandError."""
args = self.args_for()
self.assertRaisesCommandErrorWith(
clouds_shell.do_cloud_update,
args,
)
self.assertNothingWasCalled()
def test_name_is_updated(self):
"""Verify the name attribute update is sent."""
args = self.args_for(name='A New Name')
clouds_shell.do_cloud_update(self.craton_client, args)
self.craton_client.clouds.update.assert_called_once_with(
12345,
name='A New Name',
)
self.formatter.configure.assert_called_once_with(wrap=72)
self.assertEqual(1, self.formatter.handle.call_count)
def test_note_is_updated(self):
"""Verify the note attribute is updated."""
args = self.args_for(note='A New Note')
clouds_shell.do_cloud_update(self.craton_client, args)
self.craton_client.clouds.update.assert_called_once_with(
12345,
note='A New Note',
)
self.formatter.configure.assert_called_once_with(wrap=72)
self.assertEqual(1, self.formatter.handle.call_count)
def test_everything_is_updated(self):
"""Verify the note and name are updated."""
args = self.args_for(
note='A New Note',
name='A New Name',
)
clouds_shell.do_cloud_update(self.craton_client, args)
self.craton_client.clouds.update.assert_called_once_with(
12345,
note='A New Note',
name='A New Name',
)
self.formatter.configure.assert_called_once_with(wrap=72)
self.assertEqual(1, self.formatter.handle.call_count)
class TestDoCloudDelete(base.TestShellCommand):
"""Unit tests for the cloud-delete command."""
def setUp(self):
"""Mock the print function."""
super(TestDoCloudDelete, self).setUp()
self.print_mock = mock.patch(
'cratonclient.shell.v1.clouds_shell.print'
)
self.print_func = self.print_mock.start()
def tearDown(self):
"""Clean up our print function mock."""
super(TestDoCloudDelete, self).tearDown()
self.print_mock.stop()
def args_for(self, **kwargs):
"""Generate args for the cloud-delete command."""
kwargs.setdefault('id', 123456)
return super(TestDoCloudDelete, self).args_for(**kwargs)
def test_successful(self):
"""Verify successful deletion."""
self.craton_client.clouds.delete.return_value = True
args = self.args_for()
clouds_shell.do_cloud_delete(self.craton_client, args)
self.craton_client.clouds.delete.assert_called_once_with(123456)
self.print_func.assert_called_once_with(
'Cloud 123456 was successfully deleted.'
)
def test_failed(self):
"""Verify failed deletion."""
self.craton_client.clouds.delete.return_value = False
args = self.args_for()
clouds_shell.do_cloud_delete(self.craton_client, args)
self.craton_client.clouds.delete.assert_called_once_with(123456)
self.print_func.assert_called_once_with(
'Cloud 123456 was not deleted.'
)
def test_failed_with_exception(self):
"""Verify we raise a CommandError on client exceptions."""
self.craton_client.clouds.delete.side_effect = exceptions.NotFound
args = self.args_for()
self.assertRaisesCommandErrorWith(clouds_shell.do_cloud_delete, args)
self.craton_client.clouds.delete.assert_called_once_with(123456)
self.assertFalse(self.print_func.called)
class TestDoCloudList(base.TestShellCommand):
"""Test cloud-list command."""
def args_for(self, **kwargs):
"""Generate the default argument list for cloud-list."""
kwargs.setdefault('detail', False)
kwargs.setdefault('limit', None)
kwargs.setdefault('fields', clouds_shell.DEFAULT_CLOUD_FIELDS)
kwargs.setdefault('marker', None)
kwargs.setdefault('all', False)
return super(TestDoCloudList, self).args_for(**kwargs)
def test_with_defaults(self):
"""Test cloud-list with default values."""
args = self.args_for()
clouds_shell.do_cloud_list(self.craton_client, args)
self.assertFieldsEqualTo(clouds_shell.DEFAULT_CLOUD_FIELDS)
def test_negative_limit(self):
"""Ensure we raise an exception for negative limits."""
args = self.args_for(limit=-1)
self.assertRaisesCommandErrorWith(clouds_shell.do_cloud_list, args)
def test_positive_limit(self):
"""Verify that we pass positive limits to the call to list."""
args = self.args_for(limit=5)
clouds_shell.do_cloud_list(self.craton_client, args)
self.craton_client.clouds.list.assert_called_once_with(
limit=5,
marker=None,
autopaginate=False,
)
self.assertFieldsEqualTo(clouds_shell.DEFAULT_CLOUD_FIELDS)
def test_fields(self):
"""Verify that we print out specific fields."""
args = self.args_for(fields=['id', 'name', 'note'])
clouds_shell.do_cloud_list(self.craton_client, args)
self.assertFieldsEqualTo(['id', 'name', 'note'])
def test_invalid_fields(self):
"""Verify that we error out with invalid fields."""
args = self.args_for(fields=['uuid', 'not-name', 'nate'])
self.assertRaisesCommandErrorWith(clouds_shell.do_cloud_list, args)
self.assertNothingWasCalled()
def test_autopagination(self):
"""Verify autopagination is controlled by --all."""
args = self.args_for(all=True)
clouds_shell.do_cloud_list(self.craton_client, args)
self.craton_client.clouds.list.assert_called_once_with(
limit=100,
marker=None,
autopaginate=True,
)
def test_autopagination_overrides_limit(self):
"""Verify --all overrides --limit."""
args = self.args_for(all=True, limit=35)
clouds_shell.do_cloud_list(self.craton_client, args)
self.craton_client.clouds.list.assert_called_once_with(
limit=100,
marker=None,
autopaginate=True,
)
def test_marker_pass_through(self):
"""Verify we pass our marker through to the client."""
args = self.args_for(marker=31)
clouds_shell.do_cloud_list(self.craton_client, args)
self.craton_client.clouds.list.assert_called_once_with(
marker=31,
autopaginate=False,
)

View File

@ -1,235 +0,0 @@
# -*- 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.
"""Tests for the shell functions for the devices resource."""
from cratonclient.shell.v1 import devices_shell
from cratonclient.tests.unit.shell import base
class TestDoDeviceList(base.TestShellCommand):
"""Unit tests for the device list command."""
def args_for(self, **kwargs):
"""Generate a Namespace for do_device_list."""
kwargs.setdefault('detail', False)
kwargs.setdefault('cloud', None)
kwargs.setdefault('region', None)
kwargs.setdefault('cell', None)
kwargs.setdefault('parent', None)
kwargs.setdefault('descendants', False)
kwargs.setdefault('active', None)
kwargs.setdefault('limit', None)
kwargs.setdefault('sort_key', None)
kwargs.setdefault('sort_dir', 'asc')
kwargs.setdefault('fields', devices_shell.DEFAULT_DEVICE_FIELDS)
kwargs.setdefault('marker', None)
kwargs.setdefault('all', False)
return super(TestDoDeviceList, self).args_for(**kwargs)
def test_only_required_parameters(self):
"""Verify the behaviour with the minimum number of params."""
args = self.args_for()
devices_shell.do_device_list(self.craton_client, args)
self.craton_client.devices.list.assert_called_once_with(
descendants=False,
sort_dir='asc',
autopaginate=False,
marker=None,
)
self.assertFieldsEqualTo(devices_shell.DEFAULT_DEVICE_FIELDS)
def test_with_parent_id(self):
"""Verify that we include the parent_id in the params."""
args = self.args_for(parent=789)
devices_shell.do_device_list(self.craton_client, args)
self.craton_client.devices.list.assert_called_once_with(
descendants=False,
parent_id=789,
sort_dir='asc',
autopaginate=False,
marker=None,
)
self.assertFieldsEqualTo(devices_shell.DEFAULT_DEVICE_FIELDS)
def test_with_parent_id_and_descendants(self):
"""Verify that the parent_id and descendants is in the params."""
args = self.args_for(parent=789, descendants=False)
devices_shell.do_device_list(self.craton_client, args)
self.craton_client.devices.list.assert_called_once_with(
descendants=False,
parent_id=789,
sort_dir='asc',
autopaginate=False,
marker=None,
)
self.assertFieldsEqualTo(devices_shell.DEFAULT_DEVICE_FIELDS)
def test_with_region_id(self):
"""Verify that we include the region_id in the params."""
args = self.args_for(region=789)
devices_shell.do_device_list(self.craton_client, args)
self.craton_client.devices.list.assert_called_once_with(
descendants=False,
region_id=789,
sort_dir='asc',
autopaginate=False,
marker=None,
)
self.assertFieldsEqualTo(devices_shell.DEFAULT_DEVICE_FIELDS)
def test_with_cell_id(self):
"""Verify that we include the cell_id in the params."""
args = self.args_for(cell=789)
devices_shell.do_device_list(self.craton_client, args)
self.craton_client.devices.list.assert_called_once_with(
descendants=False,
cell_id=789,
sort_dir='asc',
autopaginate=False,
marker=None,
)
self.assertFieldsEqualTo(devices_shell.DEFAULT_DEVICE_FIELDS)
def test_with_cloud_id(self):
"""Verify that we include the cell_id in the params."""
args = self.args_for(cloud=123)
devices_shell.do_device_list(self.craton_client, args)
self.craton_client.devices.list.assert_called_once_with(
descendants=False,
sort_dir='asc',
cloud_id=123,
autopaginate=False,
marker=None,
)
self.assertFieldsEqualTo(devices_shell.DEFAULT_DEVICE_FIELDS)
def test_with_limit(self):
"""Verify the behaviour with --limit specified."""
args = self.args_for(limit=20)
devices_shell.do_device_list(self.craton_client, args)
self.craton_client.devices.list.assert_called_once_with(
descendants=False,
limit=20,
sort_dir='asc',
autopaginate=False,
marker=None,
)
self.assertFieldsEqualTo(devices_shell.DEFAULT_DEVICE_FIELDS)
def test_negative_limit_raises_command_error(self):
"""Verify that we forbid negative limit values."""
args = self.args_for(limit=-10)
self.assertRaisesCommandErrorWith(devices_shell.do_device_list, args)
self.assertNothingWasCalled()
def test_fields(self):
"""Verify that we can specify custom fields."""
args = self.args_for(fields=['id', 'name', 'cell_id'])
devices_shell.do_device_list(self.craton_client, args)
self.craton_client.devices.list.assert_called_once_with(
descendants=False,
sort_dir='asc',
autopaginate=False,
marker=None,
)
self.assertFieldsEqualTo(['id', 'name', 'cell_id'])
def test_invalid_sort_key(self):
"""Verify that we disallow invalid sort keys."""
args = self.args_for(sort_key='my-fake-sort-key')
self.assertRaisesCommandErrorWith(
devices_shell.do_device_list, args
)
self.assertNothingWasCalled()
def test_sort_key(self):
"""Verify we pass sort_key to our list call."""
args = self.args_for(sort_key='ip_address')
devices_shell.do_device_list(self.craton_client, args)
self.craton_client.devices.list.assert_called_once_with(
descendants=False,
sort_keys='ip_address',
sort_dir='asc',
autopaginate=False,
marker=None,
)
def test_invalid_fields_raise_command_error(self):
"""Verify sending an invalid field raises a CommandError."""
args = self.args_for(fields=['fake-field', 'id'])
self.assertRaisesCommandErrorWith(
devices_shell.do_device_list, args,
)
self.assertNothingWasCalled()
def test_autopagination(self):
"""Verify autopagination is controlled by --all."""
args = self.args_for(all=True)
devices_shell.do_device_list(self.craton_client, args)
self.craton_client.devices.list.assert_called_once_with(
descendants=False,
sort_dir='asc',
limit=100,
marker=None,
autopaginate=True,
)
def test_autopagination_overrides_limit(self):
"""Verify --all overrides --limit."""
args = self.args_for(all=True, limit=30)
devices_shell.do_device_list(self.craton_client, args)
self.craton_client.devices.list.assert_called_once_with(
descendants=False,
sort_dir='asc',
limit=100,
marker=None,
autopaginate=True,
)
def test_marker_pass_through(self):
"""Verify we pass our marker through to the client."""
args = self.args_for(marker=42)
devices_shell.do_device_list(self.craton_client, args)
self.craton_client.devices.list.assert_called_once_with(
descendants=False,
sort_dir='asc',
marker=42,
autopaginate=False,
)

View File

@ -1,565 +0,0 @@
# -*- 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.
"""Tests for the shell functions for the hosts resource."""
import mock
from cratonclient import exceptions
from cratonclient.shell.v1 import hosts_shell
from cratonclient.tests.unit.shell import base
class TestDoHostShow(base.TestShellCommand):
"""Unit tests for the host show command."""
def test_print_host_data(self):
"""Verify we print info for the specified host."""
args = self.args_for(
region=135,
id=246,
)
hosts_shell.do_host_show(self.craton_client, args)
self.craton_client.hosts.get.assert_called_once_with(246)
class TestDoHostList(base.TestShellCommand):
"""Unit tests for the host list command."""
def args_for(self, **kwargs):
"""Generate a Namespace for do_host_list."""
kwargs.setdefault('cloud', None)
kwargs.setdefault('region', 246)
kwargs.setdefault('cell', None)
kwargs.setdefault('detail', False)
kwargs.setdefault('limit', None)
kwargs.setdefault('sort_key', None)
kwargs.setdefault('sort_dir', 'asc')
kwargs.setdefault('fields', hosts_shell.DEFAULT_HOST_FIELDS)
kwargs.setdefault('marker', None)
kwargs.setdefault('all', False)
kwargs.setdefault('vars', None)
kwargs.setdefault('label', None)
kwargs.setdefault('device_type', None)
kwargs.setdefault('ip', None)
return super(TestDoHostList, self).args_for(**kwargs)
def test_only_required_parameters(self):
"""Verify the behaviour with the minimum number of params."""
args = self.args_for()
hosts_shell.do_host_list(self.craton_client, args)
self.craton_client.hosts.list.assert_called_once_with(
sort_dir='asc',
region_id=246,
autopaginate=False,
marker=None,
)
self.assertFieldsEqualTo(hosts_shell.DEFAULT_HOST_FIELDS)
def test_with_cell_id(self):
"""Verify that we include the cell_id in the params."""
args = self.args_for(cell=789)
hosts_shell.do_host_list(self.craton_client, args)
self.craton_client.hosts.list.assert_called_once_with(
cell_id=789,
sort_dir='asc',
region_id=246,
autopaginate=False,
marker=None,
)
self.assertFieldsEqualTo(hosts_shell.DEFAULT_HOST_FIELDS)
def test_with_cloud_id(self):
"""Verify that we include the cell_id in the params."""
args = self.args_for(cloud=123)
hosts_shell.do_host_list(self.craton_client, args)
self.craton_client.hosts.list.assert_called_once_with(
sort_dir='asc',
cloud_id=123,
region_id=246,
autopaginate=False,
marker=None,
)
self.assertFieldsEqualTo(hosts_shell.DEFAULT_HOST_FIELDS)
def test_with_detail(self):
"""Verify the behaviour of specifying --detail."""
args = self.args_for(detail=True)
hosts_shell.do_host_list(self.craton_client, args)
self.craton_client.hosts.list.assert_called_once_with(
detail=True,
sort_dir='asc',
region_id=246,
autopaginate=False,
marker=None,
)
self.assertFieldsEqualTo(hosts_shell.HOST_FIELDS)
def test_with_limit(self):
"""Verify the behaviour with --limit specified."""
args = self.args_for(limit=20)
hosts_shell.do_host_list(self.craton_client, args)
self.craton_client.hosts.list.assert_called_once_with(
limit=20,
sort_dir='asc',
region_id=246,
autopaginate=False,
marker=None,
)
self.assertFieldsEqualTo(hosts_shell.DEFAULT_HOST_FIELDS)
def test_negative_limit_raises_command_error(self):
"""Verify that we forbid negative limit values."""
args = self.args_for(limit=-10)
self.assertRaisesCommandErrorWith(hosts_shell.do_host_list, args)
self.assertNothingWasCalled()
def test_with_vars(self):
"""Verify the behaviour with --vars specified."""
args = self.args_for(vars='a:b')
hosts_shell.do_host_list(self.craton_client, args)
self.craton_client.hosts.list.assert_called_once_with(
vars='a:b',
sort_dir='asc',
region_id=246,
autopaginate=False,
marker=None,
)
self.assertFieldsEqualTo(hosts_shell.DEFAULT_HOST_FIELDS)
def test_with_label(self):
"""Verify the behaviour with --label specified."""
args = self.args_for(label='compute')
hosts_shell.do_host_list(self.craton_client, args)
self.craton_client.hosts.list.assert_called_once_with(
label='compute',
sort_dir='asc',
region_id=246,
autopaginate=False,
marker=None,
)
self.assertFieldsEqualTo(hosts_shell.DEFAULT_HOST_FIELDS)
def test_with_device_type(self):
"""Verify the behaviour with --device-type specified."""
args = self.args_for(device_type='compute')
hosts_shell.do_host_list(self.craton_client, args)
self.craton_client.hosts.list.assert_called_once_with(
device_type='compute',
sort_dir='asc',
region_id=246,
autopaginate=False,
marker=None,
)
self.assertFieldsEqualTo(hosts_shell.DEFAULT_HOST_FIELDS)
def test_with_ip(self):
"""Verify the behaviour with --ip specified."""
args = self.args_for(ip='10.10.1.1')
hosts_shell.do_host_list(self.craton_client, args)
self.craton_client.hosts.list.assert_called_once_with(
ip_address='10.10.1.1',
sort_dir='asc',
region_id=246,
autopaginate=False,
marker=None,
)
self.assertFieldsEqualTo(hosts_shell.DEFAULT_HOST_FIELDS)
def test_fields(self):
"""Verify that we can specify custom fields."""
args = self.args_for(fields=['id', 'name', 'cell_id'])
hosts_shell.do_host_list(self.craton_client, args)
self.craton_client.hosts.list.assert_called_once_with(
sort_dir='asc',
region_id=246,
autopaginate=False,
marker=None,
)
self.assertFieldsEqualTo(['id', 'name', 'cell_id'])
def test_invalid_sort_key(self):
"""Verify that we disallow invalid sort keys."""
args = self.args_for(sort_key='my-fake-sort-key')
self.assertRaisesCommandErrorWith(
hosts_shell.do_host_list, args
)
self.assertNothingWasCalled()
def test_sort_key(self):
"""Verify we pass sort_key to our list call."""
args = self.args_for(sort_key='ip_address')
hosts_shell.do_host_list(self.craton_client, args)
self.craton_client.hosts.list.assert_called_once_with(
sort_key='ip_address',
sort_dir='asc',
region_id=246,
autopaginate=False,
marker=None,
)
def test_fields_and_detail_raise_command_error(self):
"""Verify combining fields and detail cause an error."""
args = self.args_for(detail=True, fields=['id', 'name', 'ip_address'])
self.assertRaisesCommandErrorWith(
hosts_shell.do_host_list, args,
)
self.assertNothingWasCalled()
def test_invalid_fields_raise_command_error(self):
"""Verify sending an invalid field raises a CommandError."""
args = self.args_for(fields=['fake-field', 'id'])
self.assertRaisesCommandErrorWith(
hosts_shell.do_host_list, args,
)
self.assertNothingWasCalled()
def test_autopagination(self):
"""Verify autopagination is controlled by --all."""
args = self.args_for(all=True)
hosts_shell.do_host_list(self.craton_client, args)
self.craton_client.hosts.list.assert_called_once_with(
region_id=246,
sort_dir='asc',
limit=100,
marker=None,
autopaginate=True,
)
def test_autopagination_overrides_limit(self):
"""Verify --all overrides --limit."""
args = self.args_for(all=True, limit=30)
hosts_shell.do_host_list(self.craton_client, args)
self.craton_client.hosts.list.assert_called_once_with(
region_id=246,
sort_dir='asc',
limit=100,
marker=None,
autopaginate=True,
)
def test_marker_pass_through(self):
"""Verify we pass our marker through to the client."""
args = self.args_for(marker=42)
hosts_shell.do_host_list(self.craton_client, args)
self.craton_client.hosts.list.assert_called_once_with(
region_id=246,
sort_dir='asc',
marker=42,
autopaginate=False,
)
class TestDoHostCreate(base.TestShellCommand):
"""Tests for the do_host_create shell command."""
def args_for(self, **kwargs):
"""Generate the Namespace object needed for host create."""
kwargs.setdefault('region', 123)
kwargs.setdefault('name', 'test-hostname')
kwargs.setdefault('ip_address', '10.0.1.10')
kwargs.setdefault('region_id', 123)
kwargs.setdefault('cell_id', 246)
kwargs.setdefault('device_type', 'host')
kwargs.setdefault('active', True)
kwargs.setdefault('note', None)
kwargs.setdefault('labels', [])
return super(TestDoHostCreate, self).args_for(**kwargs)
def test_only_the_required_arguments(self):
"""Verify that the required arguments are passed appropriately."""
args = self.args_for()
hosts_shell.do_host_create(self.craton_client, args)
self.craton_client.hosts.create.assert_called_once_with(
name='test-hostname',
ip_address='10.0.1.10',
cell_id=246,
device_type='host',
active=True,
region_id=123,
)
self.formatter.configure.assert_called_once_with(wrap=72)
self.formatter.handle.assert_called_once_with(
self.craton_client.hosts.create.return_value
)
def test_with_a_note(self):
"""Verify that we pass along the note."""
args = self.args_for(note='This is a note.')
hosts_shell.do_host_create(self.craton_client, args)
self.craton_client.hosts.create.assert_called_once_with(
name='test-hostname',
ip_address='10.0.1.10',
cell_id=246,
device_type='host',
active=True,
region_id=123,
note='This is a note.',
)
self.formatter.configure.assert_called_once_with(wrap=72)
self.formatter.handle.assert_called_once_with(
self.craton_client.hosts.create.return_value
)
def test_with_labels(self):
"""Verify that we pass along our labels."""
args = self.args_for(labels=['label-0', 'label-1'])
hosts_shell.do_host_create(self.craton_client, args)
self.craton_client.hosts.create.assert_called_once_with(
name='test-hostname',
ip_address='10.0.1.10',
cell_id=246,
device_type='host',
active=True,
region_id=123,
labels=['label-0', 'label-1'],
)
self.formatter.configure.assert_called_once_with(wrap=72)
self.formatter.handle.assert_called_once_with(
self.craton_client.hosts.create.return_value
)
class TestDoHostUpdate(base.TestShellCommand):
"""Tests host-update shell command."""
def setUp(self):
"""Also patch out the print function."""
super(TestDoHostUpdate, self).setUp()
self.print_mocker = mock.patch(
'cratonclient.shell.v1.hosts_shell.print'
)
self.print_mock = self.print_mocker.start()
self.craton_client.hosts.update.return_value = mock.Mock(id=246)
def tearDown(self):
"""Stop mocking print."""
super(TestDoHostUpdate, self).tearDown()
self.print_mocker.stop()
def args_for(self, **kwargs):
"""Generate arguments for host-update command."""
kwargs.setdefault('region', 123)
kwargs.setdefault('id', 246)
kwargs.setdefault('name', None)
kwargs.setdefault('ip_address', None)
kwargs.setdefault('region_id', None)
kwargs.setdefault('cell_id', None)
kwargs.setdefault('active', True)
kwargs.setdefault('note', None)
kwargs.setdefault('labels', [])
return super(TestDoHostUpdate, self).args_for(**kwargs)
def test_with_basic_required_parameters(self):
"""Verify the basic update call works."""
args = self.args_for()
hosts_shell.do_host_update(self.craton_client, args)
self.craton_client.hosts.update.assert_called_once_with(
246,
active=True,
)
self.print_mock.assert_called_once_with(
'Host 246 has been successfully updated.'
)
self.formatter.configure.assert_called_once_with(wrap=72)
self.formatter.handle.assert_called_once_with(
self.craton_client.hosts.update.return_value
)
def test_with_name(self):
"""Verify the new name is passed along."""
args = self.args_for(name='New name')
hosts_shell.do_host_update(self.craton_client, args)
self.craton_client.hosts.update.assert_called_once_with(
246,
name='New name',
active=True,
)
self.print_mock.assert_called_once_with(
'Host 246 has been successfully updated.'
)
self.formatter.configure.assert_called_once_with(wrap=72)
self.formatter.handle.assert_called_once_with(
self.craton_client.hosts.update.return_value
)
def test_with_ip_address(self):
"""Verify the new IP Address is passed along."""
args = self.args_for(ip_address='10.1.0.10')
hosts_shell.do_host_update(self.craton_client, args)
self.craton_client.hosts.update.assert_called_once_with(
246,
ip_address='10.1.0.10',
active=True,
)
self.print_mock.assert_called_once_with(
'Host 246 has been successfully updated.'
)
self.formatter.configure.assert_called_once_with(wrap=72)
self.formatter.handle.assert_called_once_with(
self.craton_client.hosts.update.return_value
)
def test_disable_host(self):
"""Verify active is passed even when False."""
args = self.args_for(active=False)
hosts_shell.do_host_update(self.craton_client, args)
self.craton_client.hosts.update.assert_called_once_with(
246,
active=False,
)
self.print_mock.assert_called_once_with(
'Host 246 has been successfully updated.'
)
self.formatter.configure.assert_called_once_with(wrap=72)
self.formatter.handle.assert_called_once_with(
self.craton_client.hosts.update.return_value
)
def test_optional_parameters(self):
"""Verify all optional parameters are passed along when specified."""
args = self.args_for(
name='New name',
ip_address='10.1.1.1',
region_id=789,
cell_id=101,
note='A note about a host',
labels=['label1', 'label2'],
)
hosts_shell.do_host_update(self.craton_client, args)
self.craton_client.hosts.update.assert_called_once_with(
246,
active=True,
name='New name',
ip_address='10.1.1.1',
region_id=789,
cell_id=101,
note='A note about a host',
labels=['label1', 'label2'],
)
self.print_mock.assert_called_once_with(
'Host 246 has been successfully updated.'
)
self.formatter.configure.assert_called_once_with(wrap=72)
self.formatter.handle.assert_called_once_with(
self.craton_client.hosts.update.return_value
)
class TestDoHostDelete(base.TestShellCommand):
"""Tests for the host-delete shell command."""
def setUp(self):
"""Set-up a print function mock."""
super(TestDoHostDelete, self).setUp()
self.print_mocker = mock.patch(
'cratonclient.shell.v1.hosts_shell.print'
)
self.print_mock = self.print_mocker.start()
def tearDown(self):
"""Clean up the print function mock."""
super(TestDoHostDelete, self).tearDown()
self.print_mocker.stop()
def test_successful(self):
"""Verify we print our successful message."""
self.craton_client.hosts.delete.return_value = True
args = self.args_for(
region=123,
id=246,
)
hosts_shell.do_host_delete(self.craton_client, args)
self.craton_client.hosts.delete.assert_called_once_with(246)
self.print_mock.assert_called_once_with(
'Host 246 was successfully deleted.'
)
def test_failed(self):
"""Verify the message we print when deletion fails."""
self.craton_client.hosts.delete.return_value = False
args = self.args_for(
region=123,
id=246,
)
hosts_shell.do_host_delete(self.craton_client, args)
self.craton_client.hosts.delete.assert_called_once_with(246)
self.print_mock.assert_called_once_with(
'Host 246 was not deleted.'
)
def test_failed_with_exception(self):
"""Verify we raise a CommandError on client exceptions."""
self.craton_client.hosts.delete.side_effect = exceptions.NotFound
args = self.args_for(
region=123,
id=246,
)
self.assertRaisesCommandErrorWith(hosts_shell.do_host_delete, args)
self.craton_client.hosts.delete.assert_called_once_with(246)
self.assertFalse(self.print_mock.called)

View File

@ -1,265 +0,0 @@
# -*- 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.
"""Tests for the shell functions for the projects resource."""
import uuid
import mock
from cratonclient import exceptions
from cratonclient.shell.v1 import projects_shell
from cratonclient.tests.unit.shell import base
class TestDoShellShow(base.TestShellCommand):
"""Unit tests for the project show command."""
def test_simple_usage(self):
"""Verify the behaviour of do_project_show."""
args = self.args_for(
region=123,
id=456,
)
projects_shell.do_project_show(self.craton_client, args)
self.craton_client.projects.get.assert_called_once_with(456)
self.formatter.configure.assert_called_once_with(wrap=72)
self.assertEqual(1, self.formatter.handle.call_count)
class TestDoProjectList(base.TestShellCommand):
"""Unit tests for the project list command."""
def assertNothingWasCalled(self):
"""Assert inventory, list, and print_list were not called."""
super(TestDoProjectList, self).assertNothingWasCalled()
self.assertFalse(self.formatter.configure.called)
self.assertFalse(self.formatter.handle.called)
def args_for(self, **kwargs):
"""Generate the default argument list for project-list."""
kwargs.setdefault('name', None)
kwargs.setdefault('limit', None)
kwargs.setdefault('detail', False)
kwargs.setdefault('fields', projects_shell.DEFAULT_PROJECT_FIELDS)
kwargs.setdefault('marker', None)
kwargs.setdefault('all', False)
return super(TestDoProjectList, self).args_for(**kwargs)
def test_with_defaults(self):
"""Verify behaviour of do_project_list with mostly default values."""
args = self.args_for()
projects_shell.do_project_list(self.craton_client, args)
self.craton_client.projects.list.assert_called_once_with(
marker=None,
autopaginate=False,
)
self.assertFieldsEqualTo(projects_shell.DEFAULT_PROJECT_FIELDS)
def test_negative_limit(self):
"""Ensure we raise an exception for negative limits."""
args = self.args_for(limit=-1)
self.assertRaisesCommandErrorWith(projects_shell.do_project_list,
args)
self.assertNothingWasCalled()
def test_positive_limit(self):
"""Verify that we pass positive limits to the call to list."""
args = self.args_for(limit=5)
projects_shell.do_project_list(self.craton_client, args)
self.craton_client.projects.list.assert_called_once_with(
limit=5,
autopaginate=False,
marker=None,
)
self.assertFieldsEqualTo(projects_shell.DEFAULT_PROJECT_FIELDS)
def test_detail(self):
"""Verify the behaviour of specifying --detail."""
args = self.args_for(detail=True)
projects_shell.do_project_list(self.craton_client, args)
self.craton_client.projects.list.assert_called_once_with(
marker=None,
autopaginate=False,
)
self.assertFieldsEqualTo(projects_shell.PROJECT_FIELDS)
def test_list_name(self):
"""Verify the behaviour of specifying --detail."""
args = self.args_for(name='project_1')
projects_shell.do_project_list(self.craton_client, args)
self.craton_client.projects.list.assert_called_once_with(
name='project_1',
autopaginate=False,
marker=None,
)
self.assertFieldsEqualTo(projects_shell.DEFAULT_PROJECT_FIELDS)
def test_raises_exception_with_detail_and_fields(self):
"""Verify that we fail when users specify --detail and --fields."""
args = self.args_for(
detail=True,
fields=['name', 'id'],
)
self.assertRaisesCommandErrorWith(projects_shell.do_project_list, args)
self.assertNothingWasCalled()
def test_fields(self):
"""Verify that we print out specific fields."""
args = self.args_for(fields=['name', 'id'])
projects_shell.do_project_list(self.craton_client, args)
self.craton_client.projects.list.assert_called_once_with(
autopaginate=False,
marker=None,
)
self.assertFieldsEqualTo(['name', 'id'])
def test_invalid_fields(self):
"""Verify that we error out with invalid fields."""
args = self.args_for(fields=['uuid', 'not-name', 'nate'])
self.assertRaisesCommandErrorWith(projects_shell.do_project_list, args)
self.assertNothingWasCalled()
def test_autopagination(self):
"""Verify autopagination is controlled by --all."""
args = self.args_for(all=True)
projects_shell.do_project_list(self.craton_client, args)
self.craton_client.projects.list.assert_called_once_with(
limit=100,
autopaginate=True,
marker=None,
)
def test_autopagination_overrides_limit(self):
"""Verify --all overrides --limit."""
args = self.args_for(all=True, limit=25)
projects_shell.do_project_list(self.craton_client, args)
self.craton_client.projects.list.assert_called_once_with(
limit=100,
autopaginate=True,
marker=None,
)
def test_marker_support(self):
"""Verify we pass through the marker."""
project_id = uuid.uuid4().hex
args = self.args_for(marker=project_id)
projects_shell.do_project_list(self.craton_client, args)
self.craton_client.projects.list.assert_called_once_with(
autopaginate=False,
marker=project_id,
)
class TestDoProjectCreate(base.TestShellCommand):
"""Unit tests for the project create command."""
def args_for(self, **kwargs):
"""Generate the default args for project-create."""
kwargs.setdefault('name', 'New Project')
return super(TestDoProjectCreate, self).args_for(**kwargs)
def test_create(self):
"""Verify our parameters to projects.create."""
args = self.args_for()
projects_shell.do_project_create(self.craton_client, args)
self.craton_client.projects.create.assert_called_once_with(
name='New Project'
)
self.formatter.configure.assert_called_once_with(wrap=72)
self.assertEqual(1, self.formatter.handle.call_count)
class TestDoProjectDelete(base.TestShellCommand):
"""Tests for the do_project_delete command."""
def setUp(self):
"""Initialize our print mock."""
super(TestDoProjectDelete, self).setUp()
self.print_func_mock = mock.patch(
'cratonclient.shell.v1.projects_shell.print'
)
self.print_func = self.print_func_mock.start()
self.project_id = uuid.uuid4()
def tearDown(self):
"""Clean up our print mock."""
super(TestDoProjectDelete, self).tearDown()
self.print_func_mock.stop()
def test_successful(self):
"""Verify the message we print when successful."""
self.craton_client.projects.delete.return_value = True
args = self.args_for(
id=self.project_id,
)
projects_shell.do_project_delete(self.craton_client, args)
self.craton_client.projects.delete.assert_called_once_with(
self.project_id)
self.print_func.assert_called_once_with(
'Project {} was successfully deleted.'.format(self.project_id)
)
def test_failed(self):
"""Verify the message we print when deletion fails."""
self.craton_client.projects.delete.return_value = False
args = self.args_for(
id=self.project_id,
)
projects_shell.do_project_delete(self.craton_client, args)
self.craton_client.projects.delete.assert_called_once_with(
self.project_id)
self.print_func.assert_called_once_with(
'Project {} was not deleted.'.format(self.project_id)
)
def test_failed_with_exception(self):
"""Verify the message we print when deletion fails."""
self.craton_client.projects.delete.side_effect = exceptions.NotFound
args = self.args_for(
region=123,
id=self.project_id,
)
self.assertRaisesCommandErrorWith(projects_shell.do_project_delete,
args)
self.craton_client.projects.delete.assert_called_once_with(
self.project_id)
self.assertFalse(self.print_func.called)

View File

@ -1,314 +0,0 @@
# -*- 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.
"""Tests for the shell functions for the regions resource."""
import mock
from cratonclient import exceptions
from cratonclient.shell.v1 import regions_shell
from cratonclient.tests.unit.shell import base
class TestDoRegionShow(base.TestShellCommand):
"""Unit tests for the region-show command."""
def test_prints_region_data(self):
"""Verify we print the data for the region."""
args = self.args_for(id=1234)
regions_shell.do_region_show(self.craton_client, args)
self.craton_client.regions.get.assert_called_once_with(1234)
self.formatter.configure.assert_called_once_with(wrap=72)
self.assertEqual(1, self.formatter.handle.call_count)
class TestDoRegionCreate(base.TestShellCommand):
"""Unit tests for the region-create command."""
def args_for(self, **kwargs):
"""Generate arguments for region-create."""
kwargs.setdefault('name', 'New region')
kwargs.setdefault('cloud_id', 1)
kwargs.setdefault('note', None)
return super(TestDoRegionCreate, self).args_for(**kwargs)
def test_accepts_only_required_arguments(self):
"""Verify operation with only --name provided."""
args = self.args_for()
regions_shell.do_region_create(self.craton_client, args)
self.craton_client.regions.create.assert_called_once_with(
name='New region',
cloud_id=1,
)
self.formatter.configure.assert_called_once_with(wrap=72)
self.assertEqual(1, self.formatter.handle.call_count)
def test_accepts_optional_arguments(self):
"""Verify operation with --note passed as well."""
args = self.args_for(note='This is a note')
regions_shell.do_region_create(self.craton_client, args)
self.craton_client.regions.create.assert_called_once_with(
name='New region',
cloud_id=1,
note='This is a note',
)
self.formatter.configure.assert_called_once_with(wrap=72)
self.assertEqual(1, self.formatter.handle.call_count)
class TestDoRegionUpdate(base.TestShellCommand):
"""Unit tests for region-update command."""
def args_for(self, **kwargs):
"""Generate arguments for region-update."""
kwargs.setdefault('id', 12345)
kwargs.setdefault('cloud_id', None)
kwargs.setdefault('name', None)
kwargs.setdefault('note', None)
return super(TestDoRegionUpdate, self).args_for(**kwargs)
def test_nothing_to_update_raises_error(self):
"""Verify specifying nothing raises a CommandError."""
args = self.args_for()
self.assertRaisesCommandErrorWith(
regions_shell.do_region_update,
args,
)
self.assertFalse(self.craton_client.regions.update.called)
self.assertFalse(self.formatter.configure.called)
self.assertFalse(self.formatter.handle.called)
def test_name_is_updated(self):
"""Verify the name attribute update is sent."""
args = self.args_for(name='A New Name')
regions_shell.do_region_update(self.craton_client, args)
self.craton_client.regions.update.assert_called_once_with(
12345,
name='A New Name',
)
self.formatter.configure.assert_called_once_with(wrap=72)
self.assertEqual(1, self.formatter.handle.call_count)
def test_note_is_updated(self):
"""Verify the note attribute is updated."""
args = self.args_for(note='A New Note')
regions_shell.do_region_update(self.craton_client, args)
self.craton_client.regions.update.assert_called_once_with(
12345,
note='A New Note',
)
self.formatter.configure.assert_called_once_with(wrap=72)
self.assertEqual(1, self.formatter.handle.call_count)
def test_everything_is_updated(self):
"""Verify the note and name are updated."""
args = self.args_for(
note='A New Note',
name='A New Name',
cloud_id=2,
)
regions_shell.do_region_update(self.craton_client, args)
self.craton_client.regions.update.assert_called_once_with(
12345,
note='A New Note',
name='A New Name',
cloud_id=2,
)
self.formatter.configure.assert_called_once_with(wrap=72)
self.assertEqual(1, self.formatter.handle.call_count)
class TestDoRegionDelete(base.TestShellCommand):
"""Unit tests for the region-delete command."""
def setUp(self):
"""Mock the print function."""
super(TestDoRegionDelete, self).setUp()
self.print_mock = mock.patch(
'cratonclient.shell.v1.regions_shell.print'
)
self.print_func = self.print_mock.start()
def tearDown(self):
"""Clean up our print function mock."""
super(TestDoRegionDelete, self).tearDown()
self.print_mock.stop()
def args_for(self, **kwargs):
"""Generate args for the region-delete command."""
kwargs.setdefault('id', 123456)
return super(TestDoRegionDelete, self).args_for(**kwargs)
def test_successful(self):
"""Verify successful deletion."""
self.craton_client.regions.delete.return_value = True
args = self.args_for()
regions_shell.do_region_delete(self.craton_client, args)
self.craton_client.regions.delete.assert_called_once_with(123456)
self.print_func.assert_called_once_with(
'Region 123456 was successfully deleted.'
)
def test_failed(self):
"""Verify failed deletion."""
self.craton_client.regions.delete.return_value = False
args = self.args_for()
regions_shell.do_region_delete(self.craton_client, args)
self.craton_client.regions.delete.assert_called_once_with(123456)
self.print_func.assert_called_once_with(
'Region 123456 was not deleted.'
)
def test_failed_with_exception(self):
"""Verify we raise a CommandError on client exceptions."""
self.craton_client.regions.delete.side_effect = exceptions.NotFound
args = self.args_for()
self.assertRaisesCommandErrorWith(regions_shell.do_region_delete, args)
self.craton_client.regions.delete.assert_called_once_with(123456)
self.assertFalse(self.print_func.called)
class TestDoRegionList(base.TestShellCommand):
"""Test region-list command."""
def args_for(self, **kwargs):
"""Generate the default argument list for region-list."""
kwargs.setdefault('detail', False)
kwargs.setdefault('cloud', None)
kwargs.setdefault('limit', None)
kwargs.setdefault('fields', regions_shell.DEFAULT_REGION_FIELDS)
kwargs.setdefault('marker', None)
kwargs.setdefault('all', False)
kwargs.setdefault('vars', None)
return super(TestDoRegionList, self).args_for(**kwargs)
def test_with_defaults(self):
"""Test region-list with default values."""
args = self.args_for()
regions_shell.do_region_list(self.craton_client, args)
self.assertFieldsEqualTo(regions_shell.DEFAULT_REGION_FIELDS)
def test_with_cloud_id(self):
"""Test region-list with default values."""
args = self.args_for(cloud=123)
regions_shell.do_region_list(self.craton_client, args)
self.craton_client.regions.list.assert_called_once_with(
cloud_id=123,
marker=None,
autopaginate=False,
)
self.assertFieldsEqualTo(regions_shell.DEFAULT_REGION_FIELDS)
def test_with_vars(self):
"""Verify that we pass vars filters to region list."""
args = self.args_for(vars=[['a:b']])
regions_shell.do_region_list(self.craton_client, args)
self.craton_client.regions.list.assert_called_once_with(
vars='a:b',
marker=None,
autopaginate=False,
)
self.assertFieldsEqualTo(regions_shell.DEFAULT_REGION_FIELDS)
def test_with_multiple_vars(self):
"""Verify that we pass multiple vars filters to region list."""
args = self.args_for(vars=[['a:b'], ['c:d']])
regions_shell.do_region_list(self.craton_client, args)
self.craton_client.regions.list.assert_called_once_with(
vars='a:b,c:d',
marker=None,
autopaginate=False,
)
self.assertFieldsEqualTo(regions_shell.DEFAULT_REGION_FIELDS)
def test_negative_limit(self):
"""Ensure we raise an exception for negative limits."""
args = self.args_for(limit=-1)
self.assertRaisesCommandErrorWith(regions_shell.do_region_list, args)
def test_positive_limit(self):
"""Verify that we pass positive limits to the call to list."""
args = self.args_for(limit=5)
regions_shell.do_region_list(self.craton_client, args)
self.craton_client.regions.list.assert_called_once_with(
limit=5,
marker=None,
autopaginate=False,
)
self.assertFieldsEqualTo(regions_shell.DEFAULT_REGION_FIELDS)
def test_fields(self):
"""Verify that we print out specific fields."""
args = self.args_for(fields=['name', 'id', 'note'])
regions_shell.do_region_list(self.craton_client, args)
self.assertFieldsEqualTo(['name', 'id', 'note'])
def test_invalid_fields(self):
"""Verify that we error out with invalid fields."""
args = self.args_for(fields=['uuid', 'not-name', 'nate'])
self.assertRaisesCommandErrorWith(regions_shell.do_region_list, args)
self.assertNothingWasCalled()
def test_autopagination(self):
"""Verify autopagination is controlled by --all."""
args = self.args_for(all=True)
regions_shell.do_region_list(self.craton_client, args)
self.craton_client.regions.list.assert_called_once_with(
limit=100,
marker=None,
autopaginate=True,
)
def test_autopagination_overrides_limit(self):
"""Verify --all overrides --limit."""
args = self.args_for(all=True, limit=35)
regions_shell.do_region_list(self.craton_client, args)
self.craton_client.regions.list.assert_called_once_with(
limit=100,
marker=None,
autopaginate=True,
)
def test_marker_pass_through(self):
"""Verify we pass our marker through to the client."""
args = self.args_for(marker=31)
regions_shell.do_region_list(self.craton_client, args)
self.craton_client.regions.list.assert_called_once_with(
marker=31,
autopaginate=False,
)

View File

@ -1,197 +0,0 @@
# Copyright (c) 2016 Rackspace
#
# 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.
"""Unit tests for the cratonclient.auth module."""
import mock
from oslo_utils import uuidutils
from cratonclient import auth
from cratonclient.tests import base
USERNAME = 'test'
TOKEN = 'fake-token'
PROJECT_ID = uuidutils.generate_uuid()
class TestCreateSessionWith(base.TestCase):
""""Tests for the create_session_with function."""
def setUp(self):
"""Set up mocks to test the create_session_with function."""
super(TestCreateSessionWith, self).setUp()
self._session_mock = mock.patch('cratonclient.session.Session')
self.session_class = self._session_mock.start()
self.addCleanup(self._session_mock.stop)
self._ksa_session_mock = mock.patch('keystoneauth1.session.Session')
self.ksa_session_class = self._ksa_session_mock.start()
self.addCleanup(self._ksa_session_mock.stop)
def test_creates_sessions(self):
"""Verify we create cratonclient and keystoneauth Sesssions."""
auth_plugin = mock.Mock()
auth.create_session_with(auth_plugin, True)
self.ksa_session_class.assert_called_once_with(
auth=auth_plugin,
verify=True,
)
self.session_class.assert_called_once_with(
session=self.ksa_session_class.return_value
)
class TestCratonAuth(base.TestCase):
"""Tests for the craton_auth function."""
def setUp(self):
"""Set up mocks to test the craton_auth function."""
super(TestCratonAuth, self).setUp()
self._create_session_with_mock = mock.patch(
'cratonclient.auth.create_session_with'
)
self.create_session_with = self._create_session_with_mock.start()
self.addCleanup(self._create_session_with_mock.stop)
self._craton_auth_mock = mock.patch('cratonclient.auth.CratonAuth')
self.craton_auth_class = self._craton_auth_mock.start()
self.addCleanup(self._craton_auth_mock.stop)
def test_creates_craton_auth_ksa_plugin(self):
"""Verify we create a new instance of CratonAuth."""
auth.craton_auth(
username='demo',
token='demo',
project_id=PROJECT_ID,
)
self.craton_auth_class.assert_called_once_with(
username='demo',
token='demo',
project_id=PROJECT_ID,
)
def test_calls_create_session_with(self):
"""Verify we call create_session_with using the right parameters."""
auth.craton_auth(
username='demo',
token='demo',
project_id=PROJECT_ID,
verify=False,
)
self.create_session_with.assert_called_once_with(
self.craton_auth_class.return_value, False
)
class TestKeystoneAuth(base.TestCase):
"""Tests for the keystone_auth function."""
def setUp(self):
"""Set up mocks to test the keystone_auth function."""
super(TestKeystoneAuth, self).setUp()
self._create_session_with_mock = mock.patch(
'cratonclient.auth.create_session_with'
)
self.create_session_with = self._create_session_with_mock.start()
self.addCleanup(self._create_session_with_mock.stop)
self._ksa_password_mock = mock.patch(
'keystoneauth1.identity.v3.password.Password'
)
self.ksa_password_class = self._ksa_password_mock.start()
self.addCleanup(self._ksa_password_mock.stop)
def test_creates_ksa_password_plugin(self):
"""Verify we create a Password keystoneauth plugin."""
auth.keystone_auth(
auth_url='https://identity.openstack.org/v3',
username='admin',
password='adminPassword',
project_name='admin',
project_domain_name='Default',
user_domain_name='Default',
)
self.ksa_password_class.assert_called_once_with(
auth_url='https://identity.openstack.org/v3',
username='admin',
password='adminPassword',
project_name='admin',
project_domain_name='Default',
user_domain_name='Default',
project_id=None,
project_domain_id=None,
user_domain_id=None,
)
def test_calls_create_session_with(self):
"""Verify we call create_session_with using the right parameters."""
auth.keystone_auth(
auth_url='https://identity.openstack.org/v3',
username='admin',
password='adminPassword',
project_name='admin',
project_domain_name='Default',
user_domain_name='Default',
verify=False,
)
self.create_session_with.assert_called_once_with(
self.ksa_password_class.return_value, False
)
class TestCratonAuthPlugin(base.TestCase):
"""Craton authentication keystoneauth plugin tests."""
def test_stores_authentication_details(self):
"""Verify that our plugin stores auth details."""
plugin = auth.CratonAuth(
username=USERNAME,
project_id=PROJECT_ID,
token=TOKEN,
)
self.assertEqual(USERNAME, plugin.username)
self.assertEqual(PROJECT_ID, plugin.project_id)
self.assertEqual(TOKEN, plugin.token)
def test_generates_appropriate_headers(self):
"""Verify we generate the X-Auth-* headers."""
fake_session = object()
plugin = auth.CratonAuth(
username=USERNAME,
project_id=PROJECT_ID,
token=TOKEN,
)
self.assertDictEqual(
{
'X-Auth-Token': TOKEN,
'X-Auth-User': USERNAME,
'X-Auth-Project': '{}'.format(PROJECT_ID),
},
plugin.get_headers(fake_session)
)
def test_stores_token(self):
"""Verify get_token returns our token."""
fake_session = object()
plugin = auth.CratonAuth(
username=USERNAME,
project_id=PROJECT_ID,
token=TOKEN,
)
self.assertEqual(TOKEN, plugin.get_token(fake_session))

View File

@ -1,326 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright 2010-2011 OpenStack Foundation
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
#
# 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.
"""Unit tests for the cratonclient.crud module members."""
import mock
from cratonclient import crud
from cratonclient.tests import base
class TestCRUDClient(base.TestCase):
"""Test for the CRUDClient class."""
def setUp(self):
"""Create necessary test resources prior to each test."""
super(TestCRUDClient, self).setUp()
self.session = mock.Mock()
self.resource_spec = mock.Mock(spec=crud.Resource)
self.client = self.create_client()
def create_client(self, **kwargs):
"""Create and configure a basic CRUDClient."""
client = crud.CRUDClient(self.session, 'http://example.com/v1/',
**kwargs)
client.base_path = '/test'
client.key = 'test_key'
client.resource_class = self.resource_spec
return client
def test_strips_trailing_forward_slash_from_url(self):
"""Verify the client strips the trailing / in a URL."""
self.assertEqual('http://example.com/v1', self.client.url)
def test_builds_url_correctly_with_no_path_args(self):
"""Verify the generated URL from CRUDClient#build_url without args."""
self.assertEqual(
'http://example.com/v1/test',
self.client.build_url(),
)
def test_builds_url_correctly_with_key(self):
"""Verify the generated URL from CRUDClient#build_url with key."""
self.assertEqual(
'http://example.com/v1/test/1',
self.client.build_url({'test_key_id': '1'}),
)
def test_builds_url_correctly_with_path_args(self):
"""Verify the generated URL from CRUDClient#build_url with args."""
self.assertEqual(
'http://example.com/v1/test/1',
self.client.build_url({
'test_key_id': '1',
'extra_arg': 'foo',
}),
)
def test_build_url_allows_base_path_override(self):
"""Verify we can override the client's base_path attribute."""
self.assertEqual(
'http://example.com/v1/override/1',
self.client.build_url({
'test_key_id': '1',
'base_path': '/override',
}),
)
def test_merge_request_arguments(self):
"""Verify we include extra request arguments."""
client = self.create_client(extra_id=4321)
request_args = {}
client.merge_request_arguments(request_args, skip_merge=False)
self.assertEqual({'extra_id': 4321}, request_args)
def test_merge_request_arguments_skips_merging(self):
"""Verify we include extra request arguments."""
client = self.create_client(extra_id=4321)
request_args = {}
client.merge_request_arguments(request_args, skip_merge=True)
self.assertEqual({}, request_args)
def test_create_generates_a_post_request(self):
"""Verify that using our create method will POST to our service."""
response = self.session.post.return_value = mock.Mock()
response.json.return_value = {'name': 'fake-name', 'id': 1}
self.client.create(name='fake-name')
self.session.post.assert_called_once_with(
'http://example.com/v1/test',
json={'name': 'fake-name'},
)
self.resource_spec.assert_called_once_with(
self.client,
{'name': 'fake-name', 'id': 1},
loaded=True,
)
def test_delete_generates_a_delete_request(self):
"""Verify that using our delete method will send DELETE."""
response = self.session.delete.return_value = mock.Mock()
response.status_code = 204
self.client.delete(test_key_id='1')
self.session.delete.assert_called_once_with(
'http://example.com/v1/test/1',
json=None,
params={}
)
self.assertFalse(self.resource_spec.called)
def test_delete_generates_a_delete_request_positionally(self):
"""Verify passing the id positionally works as well."""
response = self.session.delete.return_value = mock.Mock()
response.status_code = 204
self.client.delete(1)
self.session.delete.assert_called_once_with(
'http://example.com/v1/test/1',
json=None,
params={}
)
self.assertFalse(self.resource_spec.called)
def test_get_generates_a_get_request(self):
"""Verify that using our get method will GET from our service."""
response = self.session.get.return_value = mock.Mock()
response.json.return_value = {'name': 'fake-name', 'id': 1}
self.client.get(test_key_id=1)
self.session.get.assert_called_once_with(
'http://example.com/v1/test/1',
)
self.resource_spec.assert_called_once_with(
self.client,
{'name': 'fake-name', 'id': 1},
loaded=True,
)
def test_get_generates_a_get_request_positionally(self):
"""Verify passing the id positionally works as well."""
response = self.session.get.return_value = mock.Mock()
response.json.return_value = {'name': 'fake-name', 'id': 1}
self.client.get(1)
self.session.get.assert_called_once_with(
'http://example.com/v1/test/1',
)
self.resource_spec.assert_called_once_with(
self.client,
{'name': 'fake-name', 'id': 1},
loaded=True,
)
def test_list_generates_a_get_request(self):
"""Verify that using our list method will GET from our service."""
response = mock.Mock()
items = [{'name': 'fake-name', 'id': 1}]
self.session.paginate.return_value = iter([(response, items)])
next(self.client.list(sort='asc'))
self.session.paginate.assert_called_once_with(
'http://example.com/v1/test',
autopaginate=True,
items_key='test_keys',
params={'sort': 'asc'},
nested=False,
)
self.resource_spec.assert_called_once_with(
self.client,
{'name': 'fake-name', 'id': 1},
loaded=True,
)
def test_update_generates_a_put_request(self):
"""Verify that using our update method will PUT to our service."""
response = self.session.put.return_value = mock.Mock()
response.json.return_value = {'name': 'fake-name', 'id': 1}
self.client.update(test_key_id='1', name='new-name')
self.session.put.assert_called_once_with(
'http://example.com/v1/test/1',
json={'name': 'new-name'},
)
self.resource_spec.assert_called_once_with(
self.client,
{'name': 'fake-name', 'id': 1},
loaded=True,
)
def test_update_generates_a_put_request_positionally(self):
"""Verify passing the id positionally works as well."""
response = self.session.put.return_value = mock.Mock()
response.json.return_value = {'name': 'fake-name', 'id': 1}
self.client.update(1, name='new-name')
self.session.put.assert_called_once_with(
'http://example.com/v1/test/1',
json={'name': 'new-name'},
)
self.resource_spec.assert_called_once_with(
self.client,
{'name': 'fake-name', 'id': 1},
loaded=True,
)
class TestResource(base.TestCase):
"""Tests for our crud.Resource object."""
def setUp(self):
"""Create necessary fixture data for our Resource tests."""
super(TestResource, self).setUp()
self.manager = mock.Mock()
self.info = {'name': 'fake-name', 'id': 1234}
self.resource = crud.Resource(self.manager, self.info)
def test_data_storage(self):
"""Verify we store our info privately."""
self.assertEqual(self.info, self.resource._info)
def test_manager(self):
"""Verify we store the manager passed in."""
self.assertIs(self.manager, self.resource.manager)
def test_human_id_is_false(self):
"""Test that None is returned when HUMAN_ID is False."""
self.assertIsNone(self.resource.human_id)
def test_human_id_is_true(self):
"""Verify we return our human-readable name."""
self.resource.HUMAN_ID = True
self.assertEqual('fake-name', self.resource.human_id)
def test_info_is_converted_to_attributes(self):
"""Verify we add info data as attributes."""
self.assertEqual('fake-name', getattr(self.resource, 'name'))
self.assertEqual(1234, getattr(self.resource, 'id'))
def test_retrieves_info_when_not_loaded(self):
"""Verify the resource tries to retrieve data from the service."""
self.manager.get.return_value._info = {'non_existent': 'foo'}
self.assertEqual('foo', self.resource.non_existent)
self.manager.get.assert_called_once_with(1234)
def test_raises_attributeerror_for_missing_attributes_when_loaded(self):
"""Verify we raise an AttributeError for missing attributes."""
self.resource.set_loaded(True)
self.assertRaises(
AttributeError,
getattr, self.resource, 'non_existent',
)
def test_equality(self):
"""Verify we check for equality correctly."""
manager = mock.Mock()
info = {'name': 'fake-name', 'id': 1234}
new_resource = crud.Resource(manager, info)
self.assertEqual(new_resource, self.resource)
def test_to_dict_clones(self):
"""Prove that we return a new dictionary from to_dict."""
self.assertIsNot(self.info, self.resource.to_dict())
def test_to_dict_equality(self):
"""Prove that the new dictionary is equal."""
self.assertEqual(self.info, self.resource.to_dict())
def test_delete_calls_manager_delete(self):
"""Verify the manager's delete method is called."""
self.resource.delete()
self.manager.delete.assert_called_once_with(1234)
def test_defaults_to_unloaded(self):
"""Verify by default a Resource is not loaded."""
self.assertFalse(self.resource.is_loaded())
def test_set_loaded_updates_loaded_status(self):
"""Verify set_loaded updates our loaded status."""
self.resource.set_loaded(True)
self.assertTrue(self.resource.is_loaded())
self.resource.set_loaded(False)
self.assertFalse(self.resource.is_loaded())
def test_get_updates_with_new_info(self):
"""Verify we change the attribute values for new info."""
self.manager.get.return_value._info = {'name': 'new-name'}
self.resource.get()
self.assertTrue(self.resource.is_loaded())
self.assertEqual('new-name', self.resource.name)
def test_get_updates_for_no_new_info(self):
"""Verify we don't add new details when there's nothing to add."""
self.manager.get.return_value = None
self.resource.get()
self.assertTrue(self.resource.is_loaded())
self.assertEqual('fake-name', self.resource.name)

View File

@ -1,67 +0,0 @@
# -*- 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.
"""Unit tests for cratonclient.exceptions."""
from cratonclient import exceptions as exc
from cratonclient.tests import base
import mock
class TestExceptions(base.TestCase):
"""Tests for our exception handling convenience functions."""
client_error_statuses = [
400, 401, 403, 404, 405, 406, 407, 409, 410, 411, 412, 413, 414, 415,
416, 422,
]
server_error_statuses = [
500,
]
def mock_keystoneauth_exception_from(self, status_code):
"""Create a fake keystoneauth1 exception with a response attribute."""
exception = mock.Mock()
exception.response = self.mock_response_from(status_code)
exception.http_status = status_code
return exception
def mock_response_from(self, status_code):
"""Create a mock requests.Response object."""
response = mock.Mock()
response.status_code = status_code
return response
def test_error_from_4xx(self):
"""Verify error_from's behvaiour for 4xx status codes."""
for status in self.client_error_statuses:
response = self.mock_response_from(status)
self.assertIsInstance(exc.error_from(response),
exc.HTTPClientError)
def test_error_from_5xx(self):
"""Verify error_from's behvaiour for 5xx status codes."""
for status in self.server_error_statuses:
response = self.mock_response_from(status)
self.assertIsInstance(exc.error_from(response),
exc.HTTPServerError)
def test_raise_from(self):
"""Verify raise_from handles keystoneauth1 exceptions."""
for status in (self.client_error_statuses +
self.server_error_statuses):
ksaexception = self.mock_keystoneauth_exception_from(status)
exception = exc.raise_from(ksaexception)
self.assertIs(ksaexception, exception.original_exception)

View File

@ -1,221 +0,0 @@
# -*- 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.
"""Session specific unit tests."""
from itertools import chain
from operator import itemgetter
from keystoneauth1 import session as ksa_session
import mock
from cratonclient import auth
from cratonclient import session
from cratonclient.tests import base
TEST_USERNAME_0 = 'test'
TEST_PROJECT_0 = 1
TEST_TOKEN_0 = 'fake-token'
class TestCratonAuth(base.TestCase):
"""Craton authentication keystoneauth plugin tests."""
def test_stores_authentication_details(self):
"""Verify that our plugin stores auth details."""
plugin = auth.CratonAuth(username=TEST_USERNAME_0,
project_id=TEST_PROJECT_0,
token=TEST_TOKEN_0)
self.assertEqual(TEST_USERNAME_0, plugin.username)
self.assertEqual(TEST_PROJECT_0, plugin.project_id)
self.assertEqual(TEST_TOKEN_0, plugin.token)
def test_generates_appropriate_headers(self):
"""Verify we generate the X-Auth-* headers."""
fake_session = object()
plugin = auth.CratonAuth(username=TEST_USERNAME_0,
project_id=TEST_PROJECT_0,
token=TEST_TOKEN_0)
self.assertDictEqual(
{
'X-Auth-Token': TEST_TOKEN_0,
'X-Auth-User': TEST_USERNAME_0,
'X-Auth-Project': '{}'.format(TEST_PROJECT_0),
},
plugin.get_headers(fake_session)
)
def test_stores_token(self):
"""Verify get_token returns our token."""
fake_session = object()
plugin = auth.CratonAuth(username=TEST_USERNAME_0,
project_id=TEST_PROJECT_0,
token=TEST_TOKEN_0)
self.assertEqual(TEST_TOKEN_0, plugin.get_token(fake_session))
class TestSession(base.TestCase):
"""Unit tests for cratonclient's Session abstraction."""
@staticmethod
def create_response(items, next_link):
"""Create a mock mimicing requests.Response."""
response = mock.Mock()
response.status_code = 200
response.json.return_value = {
'items': items,
'links': [{
'rel': 'next',
'href': next_link,
}],
}
return response
def test_creates_keystoneauth_session(self):
"""Verify we default to keystoneauth sessions and semantics."""
craton_session = session.Session(username=TEST_USERNAME_0,
project_id=TEST_PROJECT_0,
token=TEST_TOKEN_0)
self.assertIsInstance(craton_session._session, ksa_session.Session)
def test_will_use_the_existing_session(self):
"""Verify we don't overwrite an existing session object."""
ksa_session_obj = ksa_session.Session()
craton_session = session.Session(session=ksa_session_obj)
self.assertIs(ksa_session_obj, craton_session._session)
def test_paginate_stops_with_first_empty_list(self):
"""Verify the behaviour of Session#paginate."""
response = self.create_response(
[], 'http://example.com/v1/items?limit=30&marker=foo'
)
mock_session = mock.Mock()
mock_session.request.return_value = response
craton_session = session.Session(session=mock_session)
paginated_items = list(craton_session.paginate(
url='http://example.com/v1/items',
items_key='items',
autopaginate=True,
))
self.assertListEqual([(response, [])], paginated_items)
mock_session.request.assert_called_once_with(
method='GET',
url='http://example.com/v1/items',
endpoint_filter={'service_type': 'fleet_management'},
)
def test_paginate_follows_until_an_empty_list(self):
"""Verify that Session#paginate follows links."""
responses = [
self.create_response(
[{'id': _id}],
'http://example.com/v1/items?limit=30&marker={}'.format(_id),
) for _id in ['foo', 'bar', 'bogus']
]
responses.append(self.create_response([], ''))
mock_session = mock.Mock()
mock_session.request.side_effect = responses
craton_session = session.Session(session=mock_session)
paginated_items = list(craton_session.paginate(
url='http://example.com/v1/items',
items_key='items',
autopaginate=True,
))
self.assertEqual(4, len(paginated_items))
self.assertListEqual(
mock_session.request.call_args_list,
[
mock.call(
method='GET',
url='http://example.com/v1/items',
endpoint_filter={'service_type': 'fleet_management'},
),
mock.call(
method='GET',
url='http://example.com/v1/items?limit=30&marker=foo',
endpoint_filter={'service_type': 'fleet_management'},
),
mock.call(
method='GET',
url='http://example.com/v1/items?limit=30&marker=bar',
endpoint_filter={'service_type': 'fleet_management'},
),
mock.call(
method='GET',
url='http://example.com/v1/items?limit=30&marker=bogus',
endpoint_filter={'service_type': 'fleet_management'},
),
],
)
def test_paginate_nested_response(self):
"""Verify Session#paginate can extract nested lists."""
responses = [
self.create_response(
{
"items-sub-type-1": [
{"id": 1},
],
"items-sub-type-2": [
{"id": 2},
],
},
"http://example.com/v1/items?limit=30&marker=2"
),
self.create_response(
{
"items-sub-type-1": [],
"items-sub-type-2": [],
},
""
),
]
mock_session = mock.Mock()
mock_session.request.side_effect = responses
craton_session = session.Session(session=mock_session)
paginated_items = list(craton_session.paginate(
url='http://example.com/v1/items',
items_key='items',
autopaginate=True,
nested=True,
))
self.assertEqual(2, len(paginated_items))
resp_items = sorted(
chain(*(resp[1] for resp in paginated_items)), key=itemgetter("id")
)
self.assertListEqual([{"id": 1}, {"id": 2}], resp_items)
self.assertListEqual(
mock_session.request.call_args_list,
[
mock.call(
method='GET',
url='http://example.com/v1/items',
endpoint_filter={'service_type': 'fleet_management'},
),
mock.call(
method='GET',
url='http://example.com/v1/items?limit=30&marker=2',
endpoint_filter={'service_type': 'fleet_management'},
),
]
)

View File

@ -1 +0,0 @@
"""Unit tests for cratonclient.v1 submodules."""

View File

@ -1,39 +0,0 @@
# 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.
"""Tests for `cratonclient.v1.clouds` module."""
from cratonclient import crud
from cratonclient.tests import base
from cratonclient.v1 import clouds
import mock
class TestCloud(base.TestCase):
"""Tests for the Cloud Resource."""
def test_is_a_resource_instance(self):
"""Verify that a Cloud instance is an instance of a Resource."""
manager = mock.Mock()
manager.extra_request_kwargs = {}
self.assertIsInstance(clouds.Cloud(manager, {"id": 1234}),
crud.Resource)
class TestCloudManager(base.TestCase):
"""Tests for the CloudManager class."""
def test_is_a_crudclient(self):
"""Verify our CloudManager is a CRUDClient."""
session = mock.Mock()
cloud_mgr = clouds.CloudManager(session, '')
self.assertIsInstance(cloud_mgr, crud.CRUDClient)

Some files were not shown because too many files have changed in this diff Show More