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 @@
|
|||||||
===============================
|
This project is no longer maintained.
|
||||||
python-cratonclient
|
|
||||||
===============================
|
|
||||||
|
|
||||||
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
|
For any further questions, please email
|
||||||
80 cols, so that distribution package maintainers can use it in their packages.
|
openstack-dev@lists.openstack.org or join #openstack-dev on
|
||||||
Note that this is a hard requirement.
|
Freenode.
|
||||||
|
|
||||||
* 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
|
|
||||||
|
@ -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…
x
Reference in New Issue
Block a user