Retire python-cratonclient
Remove everything, add a README with explanation. Change-Id: I9530985e91423a451f2f7d9f5a37d06de2b8a473
This commit is contained in:
parent
adad8bdc7e
commit
98234f46ab
@ -1,7 +0,0 @@
|
||||
[run]
|
||||
branch = True
|
||||
source = cratonclient
|
||||
omit = cratonclient/openstack/*
|
||||
|
||||
[report]
|
||||
ignore_errors = True
|
55
.gitignore
vendored
55
.gitignore
vendored
@ -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?
|
@ -1,4 +0,0 @@
|
||||
[gerrit]
|
||||
host=review.openstack.org
|
||||
port=29418
|
||||
project=openstack/python-cratonclient.git
|
3
.mailmap
3
.mailmap
@ -1,3 +0,0 @@
|
||||
# Format is:
|
||||
# <preferred e-mail> <other e-mail 1>
|
||||
# <preferred e-mail> <other e-mail 2>
|
@ -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
|
14
.travis.yml
14
.travis.yml
@ -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
|
@ -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
|
@ -1,4 +0,0 @@
|
||||
python-cratonclient Style Commandments
|
||||
===============================================
|
||||
|
||||
Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/
|
176
LICENSE
176
LICENSE
@ -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.
|
||||
|
@ -1,6 +0,0 @@
|
||||
include AUTHORS
|
||||
include ChangeLog
|
||||
exclude .gitignore
|
||||
exclude .gitreview
|
||||
|
||||
global-exclude *.pyc
|
25
README.rst
25
README.rst
@ -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.
|
||||
|
@ -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()
|
@ -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
|
@ -1 +0,0 @@
|
||||
"""Common Craton common classes and functions."""
|
@ -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
|
@ -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)
|
@ -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)
|
@ -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."""
|
@ -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."
|
||||
)
|
@ -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(']')
|
@ -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)
|
@ -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
|
@ -1 +0,0 @@
|
||||
"""Command-line application that interfaces with Craton API."""
|
@ -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()
|
@ -1 +0,0 @@
|
||||
"""Shell libraries for version 1 of Craton's API."""
|
@ -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'))
|
@ -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'))
|
@ -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)
|
@ -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'))
|
@ -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'))
|
@ -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'))
|
@ -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,
|
||||
]
|
@ -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."""
|
@ -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."""
|
@ -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
|
File diff suppressed because it is too large
Load Diff
@ -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
|
@ -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
|
File diff suppressed because it is too large
Load Diff
@ -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
|
File diff suppressed because it is too large
Load Diff
@ -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
|
@ -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
|
File diff suppressed because it is too large
Load Diff
@ -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
|
@ -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
|
@ -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
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -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
|
@ -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
|
@ -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
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -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
|
@ -1 +0,0 @@
|
||||
"""Integration tests for the cratonclient module."""
|
@ -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,
|
||||
)
|
@ -1 +0,0 @@
|
||||
"""Integration tests for the cratonclient.shell module."""
|
@ -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'}
|
||||
)
|
@ -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."""
|
@ -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
|
@ -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
|
@ -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,
|
||||
)
|
@ -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
|
@ -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)
|
@ -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
|
@ -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
|
@ -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)
|
@ -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)
|
@ -1 +0,0 @@
|
||||
"""Integration tests for Python API client for v1 API."""
|
@ -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)
|
@ -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)
|
@ -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)
|
||||
# ]
|
@ -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)
|
@ -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)
|
@ -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."""
|
@ -1 +0,0 @@
|
||||
"""Unit tests for cratonclient.common submodules."""
|
@ -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)
|
@ -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."""
|
@ -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
|
@ -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)
|
@ -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)
|
@ -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('')
|
@ -1 +0,0 @@
|
||||
"""Unit tests for cratonclient.shell submodules."""
|
@ -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'])
|
@ -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,
|
||||
)
|
@ -1 +0,0 @@
|
||||
"""Unit tests for cratonclient.shell.v1 submodules."""
|
@ -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)
|
@ -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,
|
||||
)
|
@ -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,
|
||||
)
|
@ -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)
|
@ -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)
|
@ -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,
|
||||
)
|
@ -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))
|
@ -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)
|
@ -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)
|
@ -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'},
|
||||
),
|
||||
]
|
||||
)
|
@ -1 +0,0 @@
|
||||
"""Unit tests for cratonclient.v1 submodules."""
|
@ -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
Loading…
Reference in New Issue
Block a user