Retire Packaging Deb project repos
This commit is part of a series to retire the Packaging Deb project. Step 2 is to remove all content from the project repos, replacing it with a README notification where to find ongoing work, and how to recover the repo if needed at some future point (as in https://docs.openstack.org/infra/manual/drivers.html#retiring-a-project). Change-Id: Ifa0c3f4b80add1eac91a4750dc34de1cda97c595
This commit is contained in:
parent
2e4f22bb54
commit
895f9f3a19
|
@ -1,7 +0,0 @@
|
|||
[run]
|
||||
branch = True
|
||||
source = magnumclient
|
||||
omit = magnumclient/tests/*
|
||||
|
||||
[report]
|
||||
ignore_errors = True
|
|
@ -1,55 +0,0 @@
|
|||
*.py[cod]
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Packages
|
||||
*.egg*
|
||||
dist
|
||||
build
|
||||
eggs
|
||||
parts
|
||||
bin
|
||||
var
|
||||
sdist
|
||||
develop-eggs
|
||||
.installed.cfg
|
||||
lib
|
||||
lib64
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
.coverage
|
||||
.tox
|
||||
cover
|
||||
cover-master
|
||||
nosetests.xml
|
||||
.testrepository
|
||||
.venv
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
|
||||
# Mr Developer
|
||||
.mr.developer.cfg
|
||||
.project
|
||||
.pydevproject
|
||||
.idea
|
||||
|
||||
# Complexity
|
||||
output/*.html
|
||||
output/*/index.html
|
||||
|
||||
# Sphinx
|
||||
doc/build
|
||||
|
||||
# pbr generates these
|
||||
AUTHORS
|
||||
ChangeLog
|
||||
|
||||
# Editors
|
||||
*~
|
||||
.*.swp
|
||||
*.DS_Store
|
|
@ -1,4 +0,0 @@
|
|||
[gerrit]
|
||||
host=review.openstack.org
|
||||
port=29418
|
||||
project=openstack/python-magnumclient.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
|
|
@ -1,16 +0,0 @@
|
|||
If you would like to contribute to the development of OpenStack,
|
||||
you must follow the steps in this page:
|
||||
|
||||
https://docs.openstack.org/infra/manual/developers.html
|
||||
|
||||
Once those steps have been completed, changes to OpenStack
|
||||
should be submitted for review via the Gerrit tool, following
|
||||
the workflow documented at:
|
||||
|
||||
https://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-magnumclient
|
202
LICENSE
202
LICENSE
|
@ -1,202 +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.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
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.
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
This project is no longer maintained.
|
||||
|
||||
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".
|
||||
|
||||
For ongoing work on maintaining OpenStack packages in the Debian
|
||||
distribution, please see the Debian OpenStack packaging team at
|
||||
https://wiki.debian.org/OpenStack/.
|
||||
|
||||
For any further questions, please email
|
||||
openstack-dev@lists.openstack.org or join #openstack-dev on
|
||||
Freenode.
|
40
README.rst
40
README.rst
|
@ -1,40 +0,0 @@
|
|||
========================
|
||||
Team and repository tags
|
||||
========================
|
||||
|
||||
.. image:: http://governance.openstack.org/badges/python-magnumclient.svg
|
||||
:target: http://governance.openstack.org/reference/tags/index.html
|
||||
|
||||
.. Change things from this point on
|
||||
|
||||
Python bindings to the Magnum API
|
||||
=================================
|
||||
|
||||
.. image:: https://img.shields.io/pypi/v/python-magnumclient.svg
|
||||
:target: https://pypi.python.org/pypi/python-magnumclient/
|
||||
:alt: Latest Version
|
||||
|
||||
.. image:: https://img.shields.io/pypi/dm/python-magnumclient.svg
|
||||
:target: https://pypi.python.org/pypi/python-magnumclient/
|
||||
:alt: Downloads
|
||||
|
||||
This is a client library for Magnum built on the Magnum API. It
|
||||
provides a Python API (the ``magnumclient`` module) and a command-line
|
||||
tool (``magnum``).
|
||||
|
||||
Development takes place via the usual OpenStack processes as outlined
|
||||
in the `developer guide
|
||||
<https://docs.openstack.org/infra/manual/developers.html>`_.
|
||||
|
||||
* License: Apache License, Version 2.0
|
||||
* `PyPi`_ - package installation
|
||||
* `Online Documentation`_
|
||||
* `Launchpad project`_ - release management
|
||||
* `Bugs`_ - issue tracking
|
||||
* `Source`_
|
||||
|
||||
.. _PyPi: https://pypi.python.org/pypi/python-magnumclient
|
||||
.. _Online Documentation: https://docs.openstack.org/python-magnumclient/latest/
|
||||
.. _Launchpad project: https://launchpad.net/python-magnumclient
|
||||
.. _Bugs: https://bugs.launchpad.net/python-magnumclient
|
||||
.. _Source: https://git.openstack.org/cgit/openstack/python-magnumclient
|
|
@ -1,85 +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.
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.abspath('../..'))
|
||||
# -- General configuration ----------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
#'sphinx.ext.intersphinx',
|
||||
'openstackdocstheme'
|
||||
]
|
||||
|
||||
# autodoc generation is a bit aggressive and a nuisance when doing heavy
|
||||
# text edit cycles.
|
||||
# execute "export SPHINX_DEBUG=1" in your terminal to disable
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'python-magnumclient'
|
||||
copyright = u'2013, OpenStack Foundation'
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
add_module_names = True
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
modindex_common_prefix = ['magnumclient.']
|
||||
|
||||
# -- Options for HTML output --------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. Major themes that come with
|
||||
# Sphinx are currently 'default' and 'sphinxdoc'.
|
||||
# html_theme_path = ["."]
|
||||
html_theme = 'openstackdocs'
|
||||
# html_static_path = ['static']
|
||||
|
||||
# openstackdocstheme options
|
||||
repository_name = 'openstack/python-magnumclient'
|
||||
bug_project = 'python-magnumclient'
|
||||
bug_tag = ''
|
||||
html_last_updated_fmt = '%Y-%m-%d %H:%M'
|
||||
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = '%sdoc' % project
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass
|
||||
# [howto/manual]).
|
||||
latex_documents = [
|
||||
('index',
|
||||
'%s.tex' % project,
|
||||
u'%s Documentation' % project,
|
||||
u'OpenStack Foundation', 'manual'),
|
||||
]
|
||||
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
#intersphinx_mapping = {'http://docs.python.org/': None}
|
|
@ -1 +0,0 @@
|
|||
.. include:: ../../CONTRIBUTING.rst
|
|
@ -1,19 +0,0 @@
|
|||
Welcome to python-magnumclient's documentation!
|
||||
==============================================
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
readme
|
||||
installation
|
||||
usage
|
||||
contributing
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
|
@ -1,12 +0,0 @@
|
|||
============
|
||||
Installation
|
||||
============
|
||||
|
||||
At the command line::
|
||||
|
||||
$ pip install python-magnumclient
|
||||
|
||||
Or, if you have virtualenvwrapper installed::
|
||||
|
||||
$ mkvirtualenv python-magnumclient
|
||||
$ pip install python-magnumclient
|
|
@ -1 +0,0 @@
|
|||
.. include:: ../../README.rst
|
|
@ -1,60 +0,0 @@
|
|||
Python bindings to the OpenStack Magnum API
|
||||
===========================================
|
||||
|
||||
This is a client for the OpenStack Magnum API. It includes a Python
|
||||
API (the :mod:`magnumclient` module) and a command-line script
|
||||
(installed as :program:`magnum`).
|
||||
|
||||
Python API
|
||||
==========
|
||||
|
||||
To use python-magnumclient in a project, create a client instance
|
||||
using the keystoneauth session API::
|
||||
|
||||
from keystoneauth1.identity import v3
|
||||
from keystoneauth1 import session
|
||||
from keystoneclient.v3 import client
|
||||
|
||||
from magnumclient.client import Client
|
||||
|
||||
magnum_endpoint = "http://magnum.example.com:9511/v1"
|
||||
|
||||
auth = v3.Password(auth_url='http://my.keystone.com:5000/v3',
|
||||
username='myuser',
|
||||
password='mypassword',
|
||||
project_name='myproject',
|
||||
user_domain_id='default',
|
||||
project_domain_id='default')
|
||||
sess = session.Session(auth=auth)
|
||||
|
||||
magnum = Client('1', endpoint_override=magnum_endpoint, session=sess)
|
||||
magnum.clusters.list()
|
||||
|
||||
For more information on keystoneauth API, see `Using Sessions`_.
|
||||
|
||||
.. _Using Sessions: https://docs.openstack.org/keystoneauth/latest/using-sessions.html
|
||||
|
||||
Command-line tool
|
||||
=================
|
||||
|
||||
In order to use the CLI, you must provide your OpenStack username,
|
||||
password, project name, user domain ID, project domain ID, and auth
|
||||
endpoint. Use the corresponding configuration options (--os-username,
|
||||
--os-password, --os-project-name, --os-project-domain-id,
|
||||
--os-user-domain-id, and --os-auth-url) or set them in environment
|
||||
variables::
|
||||
|
||||
export OS_USERNAME=myuser
|
||||
export OS_PASSWORD=mypassword
|
||||
export OS_PROJECT_NAME=myproject
|
||||
export OS_USER_DOMAIN_ID=default
|
||||
export OS_PROJECT_DOMAIN_ID=default
|
||||
export OS_AUTH_URL=http://my.keystone.com:5000/v3
|
||||
|
||||
From there, all shell commands take the form::
|
||||
|
||||
magnum <command> [arguments...]
|
||||
|
||||
Run :program:`magnum help` to see a complete listing of available
|
||||
commands. Run :program:`magnum help <command>` to get detailed help
|
||||
for that command.
|
|
@ -1,19 +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.
|
||||
|
||||
import pbr.version
|
||||
|
||||
|
||||
__version__ = pbr.version.VersionInfo(
|
||||
'python-magnumclient').version_string()
|
|
@ -1,24 +0,0 @@
|
|||
# Copyright (c) 2015 IBM Corp.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from magnumclient.v1 import client
|
||||
|
||||
|
||||
def Client(version='1', **kwargs):
|
||||
"""Factory function to create a new container service client."""
|
||||
if version != '1':
|
||||
raise ValueError(
|
||||
"magnum only has one API version. Valid values for 'version'"
|
||||
" are '1'")
|
||||
return client.Client(**kwargs)
|
|
@ -1,108 +0,0 @@
|
|||
# Copyright 2010 Jacob Kaplan-Moss
|
||||
# Copyright 2011 OpenStack Foundation
|
||||
# Copyright 2012 Grid Dynamics
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 utilities to build API operation managers and objects on top of.
|
||||
"""
|
||||
|
||||
import copy
|
||||
|
||||
|
||||
class Resource(object):
|
||||
"""Base class for OpenStack resources (tenant, user, etc.).
|
||||
|
||||
This is pretty much just a bag for attributes.
|
||||
"""
|
||||
|
||||
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
|
||||
|
||||
def __repr__(self):
|
||||
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)
|
||||
|
||||
def _add_details(self, info):
|
||||
for (k, v) in info.items():
|
||||
try:
|
||||
setattr(self, k, v)
|
||||
self._info[k] = v
|
||||
except AttributeError:
|
||||
# In this case we already defined the attribute on the class
|
||||
pass
|
||||
|
||||
def __getattr__(self, k):
|
||||
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):
|
||||
if not isinstance(other, Resource):
|
||||
return NotImplemented
|
||||
# two resources of different types are not equal
|
||||
if not isinstance(other, self.__class__):
|
||||
return False
|
||||
if hasattr(self, 'id') and hasattr(other, 'id'):
|
||||
return self.id == other.id
|
||||
return self._info == other._info
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def is_loaded(self):
|
||||
return self._loaded
|
||||
|
||||
def set_loaded(self, val):
|
||||
self._loaded = val
|
||||
|
||||
def to_dict(self):
|
||||
return copy.deepcopy(self._info)
|
|
@ -1,472 +0,0 @@
|
|||
# Copyright 2010 Jacob Kaplan-Moss
|
||||
# Copyright 2011 Nebula, Inc.
|
||||
# Copyright 2013 Alessio Ababilov
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 definitions.
|
||||
"""
|
||||
|
||||
########################################################################
|
||||
#
|
||||
# THIS MODULE IS DEPRECATED
|
||||
#
|
||||
# Please refer to
|
||||
# https://etherpad.openstack.org/p/kilo-magnumclient-library-proposals for
|
||||
# the discussion leading to this deprecation.
|
||||
#
|
||||
# We recommend checking out the python-openstacksdk project
|
||||
# (https://launchpad.net/python-openstacksdk) instead.
|
||||
#
|
||||
########################################################################
|
||||
|
||||
import inspect
|
||||
import sys
|
||||
|
||||
import six
|
||||
|
||||
from magnumclient.i18n import _
|
||||
|
||||
|
||||
class ClientException(Exception):
|
||||
"""The base exception class for all exceptions this library raises."""
|
||||
pass
|
||||
|
||||
|
||||
class ValidationError(ClientException):
|
||||
"""Error in validation on API client side."""
|
||||
pass
|
||||
|
||||
|
||||
class UnsupportedVersion(ClientException):
|
||||
"""User is trying to use an unsupported version of the API."""
|
||||
pass
|
||||
|
||||
|
||||
class CommandError(ClientException):
|
||||
"""Error in CLI tool."""
|
||||
pass
|
||||
|
||||
|
||||
class AuthorizationFailure(ClientException):
|
||||
"""Cannot authorize API client."""
|
||||
pass
|
||||
|
||||
|
||||
class ConnectionError(ClientException):
|
||||
"""Cannot connect to API service."""
|
||||
pass
|
||||
|
||||
|
||||
class ConnectionRefused(ConnectionError):
|
||||
"""Connection refused while trying to connect to API service."""
|
||||
pass
|
||||
|
||||
|
||||
class AuthPluginOptionsMissing(AuthorizationFailure):
|
||||
"""Auth plugin misses some options."""
|
||||
def __init__(self, opt_names):
|
||||
super(AuthPluginOptionsMissing, self).__init__(
|
||||
_("Authentication failed. Missing options: %s") %
|
||||
", ".join(opt_names))
|
||||
self.opt_names = opt_names
|
||||
|
||||
|
||||
class AuthSystemNotFound(AuthorizationFailure):
|
||||
"""User has specified an AuthSystem that is not installed."""
|
||||
def __init__(self, auth_system):
|
||||
super(AuthSystemNotFound, self).__init__(
|
||||
_("AuthSystemNotFound: %r") % auth_system)
|
||||
self.auth_system = auth_system
|
||||
|
||||
|
||||
class EndpointException(ClientException):
|
||||
"""Something is rotten in Service Catalog."""
|
||||
pass
|
||||
|
||||
|
||||
class EndpointNotFound(EndpointException):
|
||||
"""Could not find requested endpoint in Service Catalog."""
|
||||
pass
|
||||
|
||||
|
||||
class AmbiguousEndpoints(EndpointException):
|
||||
"""Found more than one matching endpoint in Service Catalog."""
|
||||
def __init__(self, endpoints=None):
|
||||
super(AmbiguousEndpoints, self).__init__(
|
||||
_("AmbiguousEndpoints: %r") % endpoints)
|
||||
self.endpoints = endpoints
|
||||
|
||||
|
||||
class HttpError(ClientException):
|
||||
"""The base exception class for all HTTP exceptions."""
|
||||
http_status = 0
|
||||
message = _("HTTP Error")
|
||||
|
||||
def __init__(self, message=None, details=None,
|
||||
response=None, request_id=None,
|
||||
url=None, method=None, http_status=None):
|
||||
self.http_status = http_status or self.http_status
|
||||
self.message = message or self.message
|
||||
self.details = details
|
||||
self.request_id = request_id
|
||||
self.response = response
|
||||
self.url = url
|
||||
self.method = method
|
||||
formatted_string = "%s (HTTP %s)" % (self.message, self.http_status)
|
||||
if request_id:
|
||||
formatted_string += " (Request-ID: %s)" % request_id
|
||||
super(HttpError, self).__init__(formatted_string)
|
||||
|
||||
|
||||
class HTTPRedirection(HttpError):
|
||||
"""HTTP Redirection."""
|
||||
message = _("HTTP Redirection")
|
||||
|
||||
|
||||
class HTTPClientError(HttpError):
|
||||
"""Client-side HTTP error.
|
||||
|
||||
Exception for cases in which the client seems to have erred.
|
||||
"""
|
||||
message = _("HTTP Client Error")
|
||||
|
||||
|
||||
class HttpServerError(HttpError):
|
||||
"""Server-side HTTP error.
|
||||
|
||||
Exception for cases in which the server is aware that it has
|
||||
erred or is incapable of performing the request.
|
||||
"""
|
||||
message = _("HTTP Server Error")
|
||||
|
||||
|
||||
class MultipleChoices(HTTPRedirection):
|
||||
"""HTTP 300 - Multiple Choices.
|
||||
|
||||
Indicates multiple options for the resource that the client may follow.
|
||||
"""
|
||||
|
||||
http_status = 300
|
||||
message = _("Multiple Choices")
|
||||
|
||||
|
||||
class BadRequest(HTTPClientError):
|
||||
"""HTTP 400 - Bad Request.
|
||||
|
||||
The request cannot be fulfilled due to bad syntax.
|
||||
"""
|
||||
http_status = 400
|
||||
message = _("Bad Request")
|
||||
|
||||
|
||||
class Unauthorized(HTTPClientError):
|
||||
"""HTTP 401 - Unauthorized.
|
||||
|
||||
Similar to 403 Forbidden, but specifically for use when authentication
|
||||
is required and has failed or has not yet been provided.
|
||||
"""
|
||||
http_status = 401
|
||||
message = _("Unauthorized")
|
||||
|
||||
|
||||
class PaymentRequired(HTTPClientError):
|
||||
"""HTTP 402 - Payment Required.
|
||||
|
||||
Reserved for future use.
|
||||
"""
|
||||
http_status = 402
|
||||
message = _("Payment Required")
|
||||
|
||||
|
||||
class Forbidden(HTTPClientError):
|
||||
"""HTTP 403 - Forbidden.
|
||||
|
||||
The request was a valid request, but the server is refusing to respond
|
||||
to it.
|
||||
"""
|
||||
http_status = 403
|
||||
message = _("Forbidden")
|
||||
|
||||
|
||||
class NotFound(HTTPClientError):
|
||||
"""HTTP 404 - Not Found.
|
||||
|
||||
The requested resource could not be found but may be available again
|
||||
in the future.
|
||||
"""
|
||||
http_status = 404
|
||||
message = _("Not Found")
|
||||
|
||||
|
||||
class MethodNotAllowed(HTTPClientError):
|
||||
"""HTTP 405 - Method Not Allowed.
|
||||
|
||||
A request was made of a resource using a request method not supported
|
||||
by that resource.
|
||||
"""
|
||||
http_status = 405
|
||||
message = _("Method Not Allowed")
|
||||
|
||||
|
||||
class NotAcceptable(HTTPClientError):
|
||||
"""HTTP 406 - Not Acceptable.
|
||||
|
||||
The requested resource is only capable of generating content not
|
||||
acceptable according to the Accept headers sent in the request.
|
||||
"""
|
||||
http_status = 406
|
||||
message = _("Not Acceptable")
|
||||
|
||||
|
||||
class ProxyAuthenticationRequired(HTTPClientError):
|
||||
"""HTTP 407 - Proxy Authentication Required.
|
||||
|
||||
The client must first authenticate itself with the proxy.
|
||||
"""
|
||||
http_status = 407
|
||||
message = _("Proxy Authentication Required")
|
||||
|
||||
|
||||
class RequestTimeout(HTTPClientError):
|
||||
"""HTTP 408 - Request Timeout.
|
||||
|
||||
The server timed out waiting for the request.
|
||||
"""
|
||||
http_status = 408
|
||||
message = _("Request Timeout")
|
||||
|
||||
|
||||
class Conflict(HTTPClientError):
|
||||
"""HTTP 409 - Conflict.
|
||||
|
||||
Indicates that the request could not be processed because of conflict
|
||||
in the request, such as an edit conflict.
|
||||
"""
|
||||
http_status = 409
|
||||
message = _("Conflict")
|
||||
|
||||
|
||||
class Gone(HTTPClientError):
|
||||
"""HTTP 410 - Gone.
|
||||
|
||||
Indicates that the resource requested is no longer available and will
|
||||
not be available again.
|
||||
"""
|
||||
http_status = 410
|
||||
message = _("Gone")
|
||||
|
||||
|
||||
class LengthRequired(HTTPClientError):
|
||||
"""HTTP 411 - Length Required.
|
||||
|
||||
The request did not specify the length of its content, which is
|
||||
required by the requested resource.
|
||||
"""
|
||||
http_status = 411
|
||||
message = _("Length Required")
|
||||
|
||||
|
||||
class PreconditionFailed(HTTPClientError):
|
||||
"""HTTP 412 - Precondition Failed.
|
||||
|
||||
The server does not meet one of the preconditions that the requester
|
||||
put on the request.
|
||||
"""
|
||||
http_status = 412
|
||||
message = _("Precondition Failed")
|
||||
|
||||
|
||||
class RequestEntityTooLarge(HTTPClientError):
|
||||
"""HTTP 413 - Request Entity Too Large.
|
||||
|
||||
The request is larger than the server is willing or able to process.
|
||||
"""
|
||||
http_status = 413
|
||||
message = _("Request Entity Too Large")
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
try:
|
||||
self.retry_after = int(kwargs.pop('retry_after'))
|
||||
except (KeyError, ValueError):
|
||||
self.retry_after = 0
|
||||
|
||||
super(RequestEntityTooLarge, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class RequestUriTooLong(HTTPClientError):
|
||||
"""HTTP 414 - Request-URI Too Long.
|
||||
|
||||
The URI provided was too long for the server to process.
|
||||
"""
|
||||
http_status = 414
|
||||
message = _("Request-URI Too Long")
|
||||
|
||||
|
||||
class UnsupportedMediaType(HTTPClientError):
|
||||
"""HTTP 415 - Unsupported Media Type.
|
||||
|
||||
The request entity has a media type which the server or resource does
|
||||
not support.
|
||||
"""
|
||||
http_status = 415
|
||||
message = _("Unsupported Media Type")
|
||||
|
||||
|
||||
class RequestedRangeNotSatisfiable(HTTPClientError):
|
||||
"""HTTP 416 - Requested Range Not Satisfiable.
|
||||
|
||||
The client has asked for a portion of the file, but the server cannot
|
||||
supply that portion.
|
||||
"""
|
||||
http_status = 416
|
||||
message = _("Requested Range Not Satisfiable")
|
||||
|
||||
|
||||
class ExpectationFailed(HTTPClientError):
|
||||
"""HTTP 417 - Expectation Failed.
|
||||
|
||||
The server cannot meet the requirements of the Expect request-header field.
|
||||
"""
|
||||
http_status = 417
|
||||
message = _("Expectation Failed")
|
||||
|
||||
|
||||
class UnprocessableEntity(HTTPClientError):
|
||||
"""HTTP 422 - Unprocessable Entity.
|
||||
|
||||
The request was well-formed but was unable to be followed due to semantic
|
||||
errors.
|
||||
"""
|
||||
http_status = 422
|
||||
message = _("Unprocessable Entity")
|
||||
|
||||
|
||||
class InternalServerError(HttpServerError):
|
||||
"""HTTP 500 - Internal Server Error.
|
||||
|
||||
A generic error message, given when no more specific message is suitable.
|
||||
"""
|
||||
http_status = 500
|
||||
message = _("Internal Server Error")
|
||||
|
||||
|
||||
# NotImplemented is a python keyword.
|
||||
class HttpNotImplemented(HttpServerError):
|
||||
"""HTTP 501 - Not Implemented.
|
||||
|
||||
The server either does not recognize the request method, or it lacks
|
||||
the ability to fulfill the request.
|
||||
"""
|
||||
http_status = 501
|
||||
message = _("Not Implemented")
|
||||
|
||||
|
||||
class BadGateway(HttpServerError):
|
||||
"""HTTP 502 - Bad Gateway.
|
||||
|
||||
The server was acting as a gateway or proxy and received an invalid
|
||||
response from the upstream server.
|
||||
"""
|
||||
http_status = 502
|
||||
message = _("Bad Gateway")
|
||||
|
||||
|
||||
class ServiceUnavailable(HttpServerError):
|
||||
"""HTTP 503 - Service Unavailable.
|
||||
|
||||
The server is currently unavailable.
|
||||
"""
|
||||
http_status = 503
|
||||
message = _("Service Unavailable")
|
||||
|
||||
|
||||
class GatewayTimeout(HttpServerError):
|
||||
"""HTTP 504 - Gateway Timeout.
|
||||
|
||||
The server was acting as a gateway or proxy and did not receive a timely
|
||||
response from the upstream server.
|
||||
"""
|
||||
http_status = 504
|
||||
message = _("Gateway Timeout")
|
||||
|
||||
|
||||
class HttpVersionNotSupported(HttpServerError):
|
||||
"""HTTP 505 - HttpVersion Not Supported.
|
||||
|
||||
The server does not support the HTTP protocol version used in the request.
|
||||
"""
|
||||
http_status = 505
|
||||
message = _("HTTP Version Not Supported")
|
||||
|
||||
|
||||
# _code_map contains all the classes that have http_status attribute.
|
||||
_code_map = dict(
|
||||
(getattr(obj, 'http_status', None), obj)
|
||||
for name, obj in vars(sys.modules[__name__]).items()
|
||||
if inspect.isclass(obj) and getattr(obj, 'http_status', False)
|
||||
)
|
||||
|
||||
|
||||
def from_response(response, method, url):
|
||||
"""Returns an instance of :class:`HttpError` or subclass based on response.
|
||||
|
||||
:param response: instance of `requests.Response` class
|
||||
:param method: HTTP method used for request
|
||||
:param url: URL used for request
|
||||
"""
|
||||
|
||||
req_id = response.headers.get("x-openstack-request-id")
|
||||
# NOTE(hdd) true for older versions of nova and cinder
|
||||
if not req_id:
|
||||
req_id = response.headers.get("x-compute-request-id")
|
||||
kwargs = {
|
||||
"http_status": response.status_code,
|
||||
"response": response,
|
||||
"method": method,
|
||||
"url": url,
|
||||
"request_id": req_id,
|
||||
}
|
||||
if "retry-after" in response.headers:
|
||||
kwargs["retry_after"] = response.headers["retry-after"]
|
||||
|
||||
content_type = response.headers.get("Content-Type", "")
|
||||
if content_type.startswith("application/json"):
|
||||
try:
|
||||
body = response.json()
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
if isinstance(body, dict):
|
||||
error = body.get(list(body)[0])
|
||||
if isinstance(error, dict):
|
||||
kwargs["message"] = (error.get("message") or
|
||||
error.get("faultstring"))
|
||||
kwargs["details"] = (error.get("details") or
|
||||
six.text_type(body))
|
||||
elif content_type.startswith("text/"):
|
||||
kwargs["details"] = getattr(response, 'text', '')
|
||||
|
||||
try:
|
||||
cls = _code_map[response.status_code]
|
||||
except KeyError:
|
||||
if 500 <= response.status_code < 600:
|
||||
cls = HttpServerError
|
||||
elif 400 <= response.status_code < 500:
|
||||
cls = HTTPClientError
|
||||
else:
|
||||
cls = HttpError
|
||||
return cls(**kwargs)
|
|
@ -1,149 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2012 OpenStack LLC.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 utilities to build API operation managers and objects on top of.
|
||||
"""
|
||||
|
||||
import copy
|
||||
|
||||
import six.moves.urllib.parse as urlparse
|
||||
|
||||
from magnumclient.common.apiclient import base
|
||||
|
||||
|
||||
def getid(obj):
|
||||
"""Wrapper to get object's ID.
|
||||
|
||||
Abstracts the common pattern of allowing both an object or an
|
||||
object's ID (UUID) as a parameter when dealing with relationships.
|
||||
"""
|
||||
try:
|
||||
return obj.id
|
||||
except AttributeError:
|
||||
return obj
|
||||
|
||||
|
||||
class Manager(object):
|
||||
"""Provides CRUD operations with a particular API."""
|
||||
resource_class = None
|
||||
|
||||
def __init__(self, api):
|
||||
self.api = api
|
||||
|
||||
def _create(self, url, body):
|
||||
resp, body = self.api.json_request('POST', url, body=body)
|
||||
if body:
|
||||
return self.resource_class(self, body)
|
||||
|
||||
def _format_body_data(self, body, response_key):
|
||||
if response_key:
|
||||
try:
|
||||
data = body[response_key]
|
||||
except KeyError:
|
||||
return []
|
||||
else:
|
||||
data = body
|
||||
|
||||
if not isinstance(data, list):
|
||||
data = [data]
|
||||
|
||||
return data
|
||||
|
||||
def _list_pagination(self, url, response_key=None, obj_class=None,
|
||||
limit=None):
|
||||
"""Retrieve a list of items.
|
||||
|
||||
The Magnum API is configured to return a maximum number of
|
||||
items per request, (FIXME: see Magnum's api.max_limit option). This
|
||||
iterates over the 'next' link (pagination) in the responses,
|
||||
to get the number of items specified by 'limit'. If 'limit'
|
||||
is None this function will continue pagination until there are
|
||||
no more values to be returned.
|
||||
|
||||
:param url: a partial URL, e.g. '/nodes'
|
||||
:param response_key: the key to be looked up in response
|
||||
dictionary, e.g. 'nodes'
|
||||
:param obj_class: class for constructing the returned objects.
|
||||
:param limit: maximum number of items to return. If None returns
|
||||
everything.
|
||||
|
||||
"""
|
||||
if obj_class is None:
|
||||
obj_class = self.resource_class
|
||||
|
||||
if limit is not None:
|
||||
limit = int(limit)
|
||||
|
||||
object_list = []
|
||||
object_count = 0
|
||||
limit_reached = False
|
||||
while url:
|
||||
resp, body = self.api.json_request('GET', url)
|
||||
data = self._format_body_data(body, response_key)
|
||||
for obj in data:
|
||||
object_list.append(obj_class(self, obj, loaded=True))
|
||||
object_count += 1
|
||||
if limit and object_count >= limit:
|
||||
# break the for loop
|
||||
limit_reached = True
|
||||
break
|
||||
|
||||
# break the while loop and return
|
||||
if limit_reached:
|
||||
break
|
||||
|
||||
url = body.get('next')
|
||||
if url:
|
||||
# NOTE(lucasagomes): We need to edit the URL to remove
|
||||
# the scheme and netloc
|
||||
url_parts = list(urlparse.urlparse(url))
|
||||
url_parts[0] = url_parts[1] = ''
|
||||
url = urlparse.urlunparse(url_parts)
|
||||
|
||||
return object_list
|
||||
|
||||
def _list(self, url, response_key=None, obj_class=None, body=None):
|
||||
resp, body = self.api.json_request('GET', url)
|
||||
|
||||
if obj_class is None:
|
||||
obj_class = self.resource_class
|
||||
|
||||
data = self._format_body_data(body, response_key)
|
||||
return [obj_class(self, res, loaded=True) for res in data if res]
|
||||
|
||||
def _update(self, url, body=None, method='PATCH', response_key=None):
|
||||
if body:
|
||||
resp, resp_body = self.api.json_request(method, url, body=body)
|
||||
else:
|
||||
resp, resp_body = self.api.raw_request(method, url)
|
||||
# PATCH/PUT requests may not return a body
|
||||
if resp_body:
|
||||
return self.resource_class(self, resp_body)
|
||||
|
||||
def _delete(self, url):
|
||||
self.api.raw_request('DELETE', url)
|
||||
|
||||
|
||||
class Resource(base.Resource):
|
||||
"""Represents a particular instance of an object (tenant, user, etc).
|
||||
|
||||
This is pretty much just a bag for attributes.
|
||||
"""
|
||||
|
||||
def to_dict(self):
|
||||
return copy.deepcopy(self._info)
|
|
@ -1,490 +0,0 @@
|
|||
# Copyright 2012 Red Hat, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
# W0603: Using the global statement
|
||||
# W0621: Redefining name %s from outer scope
|
||||
# pylint: disable=W0603,W0621
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import getpass
|
||||
import inspect
|
||||
import os
|
||||
import sys
|
||||
import textwrap
|
||||
|
||||
import decorator
|
||||
from magnumclient.common.apiclient import exceptions
|
||||
from oslo_utils import encodeutils
|
||||
from oslo_utils import strutils
|
||||
import prettytable
|
||||
import six
|
||||
from six import moves
|
||||
|
||||
from magnumclient.i18n import _
|
||||
|
||||
|
||||
NAME_DEPRECATION_BASE = ('%sThe --name parameter is deprecated and '
|
||||
'will be removed in a future release. Use the '
|
||||
'<name> positional parameter %s.')
|
||||
|
||||
NAME_DEPRECATION_HELP = NAME_DEPRECATION_BASE % ('', 'instead')
|
||||
|
||||
NAME_DEPRECATION_WARNING = NAME_DEPRECATION_BASE % (
|
||||
'WARNING: ', 'to avoid seeing this message')
|
||||
|
||||
|
||||
def deprecation_message(preamble, new_name):
|
||||
msg = ('%s This parameter is deprecated and will be removed in a future '
|
||||
'release. Use --%s instead.' % (preamble, new_name))
|
||||
return msg
|
||||
|
||||
|
||||
class MissingArgs(Exception):
|
||||
"""Supplied arguments are not sufficient for calling a function."""
|
||||
def __init__(self, missing):
|
||||
self.missing = missing
|
||||
msg = _("Missing arguments: %s") % ", ".join(missing)
|
||||
super(MissingArgs, self).__init__(msg)
|
||||
|
||||
|
||||
class DuplicateArgs(Exception):
|
||||
"""More than one of the same argument type was passed."""
|
||||
def __init__(self, param, dupes):
|
||||
msg = _('Duplicate "%(param)s" arguments: %(dupes)s') % {
|
||||
'param': param, 'dupes': ", ".join(dupes)}
|
||||
super(DuplicateArgs, self).__init__(msg)
|
||||
|
||||
|
||||
def validate_args(fn, *args, **kwargs):
|
||||
"""Check that the supplied args are sufficient for calling a function.
|
||||
|
||||
>>> validate_args(lambda a: None)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
MissingArgs: Missing argument(s): a
|
||||
>>> validate_args(lambda a, b, c, d: None, 0, c=1)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
MissingArgs: Missing argument(s): b, d
|
||||
|
||||
:param fn: the function to check
|
||||
:param arg: the positional arguments supplied
|
||||
:param kwargs: the keyword arguments supplied
|
||||
"""
|
||||
argspec = inspect.getargspec(fn)
|
||||
|
||||
num_defaults = len(argspec.defaults or [])
|
||||
required_args = argspec.args[:len(argspec.args) - num_defaults]
|
||||
|
||||
def isbound(method):
|
||||
return getattr(method, '__self__', None) is not None
|
||||
|
||||
if isbound(fn):
|
||||
required_args.pop(0)
|
||||
|
||||
missing = [arg for arg in required_args if arg not in kwargs]
|
||||
missing = missing[len(args):]
|
||||
if missing:
|
||||
raise MissingArgs(missing)
|
||||
|
||||
|
||||
def validate_name_args(positional_name, optional_name):
|
||||
if optional_name:
|
||||
print(NAME_DEPRECATION_WARNING)
|
||||
if positional_name and optional_name:
|
||||
raise DuplicateArgs("<name>", (positional_name, optional_name))
|
||||
|
||||
|
||||
def deprecated(message):
|
||||
"""Decorator for marking a call as deprecated by printing a given message.
|
||||
|
||||
Example:
|
||||
>>> @deprecated("Bay functions are deprecated and should be replaced by "
|
||||
... "calls to cluster")
|
||||
... def bay_create(args):
|
||||
... pass
|
||||
"""
|
||||
@decorator.decorator
|
||||
def wrapper(func, *args, **kwargs):
|
||||
print(message)
|
||||
return func(*args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
|
||||
def deprecation_map(dep_map):
|
||||
"""Decorator for applying a map of deprecating arguments to a function.
|
||||
|
||||
The map connects deprecating arguments and their replacements. The
|
||||
shell.py script uses this map to create mutually exclusive argument groups
|
||||
in argparse and also prints a deprecation warning telling the user to
|
||||
switch to the updated argument.
|
||||
|
||||
NOTE: This decorator MUST be the outermost in the chain of argument
|
||||
decorators to work correctly.
|
||||
|
||||
Example usage:
|
||||
>>> @deprecation_map({ "old-argument": "new-argument" })
|
||||
... @args("old-argument", required=True)
|
||||
... @args("new-argument", required=True)
|
||||
... def do_command_line_stuff():
|
||||
... pass
|
||||
"""
|
||||
def _decorator(func):
|
||||
if not hasattr(func, 'arguments'):
|
||||
return func
|
||||
|
||||
func.deprecated_groups = []
|
||||
for old_param, new_param in dep_map.items():
|
||||
old_info, new_info = None, None
|
||||
required = False
|
||||
for (args, kwargs) in func.arguments:
|
||||
if old_param in args:
|
||||
old_info = (args, kwargs)
|
||||
# Old arguments shouldn't be required if they were not
|
||||
# previously, so prioritize old requirement
|
||||
if 'required' in kwargs:
|
||||
required = kwargs['required']
|
||||
# Set to false so argparse doesn't get angry
|
||||
kwargs['required'] = False
|
||||
elif new_param in args:
|
||||
new_info = (args, kwargs)
|
||||
kwargs['required'] = False
|
||||
if old_info and new_info:
|
||||
break
|
||||
# Add a tuple of (old, new, required), which in turn is:
|
||||
# ((old_args, old_kwargs), (new_args, new_kwargs), required)
|
||||
func.deprecated_groups.append((old_info, new_info, required))
|
||||
# Remove arguments that would be duplicated by the groups we made
|
||||
func.arguments.remove(old_info)
|
||||
func.arguments.remove(new_info)
|
||||
|
||||
return func
|
||||
return _decorator
|
||||
|
||||
|
||||
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):
|
||||
add_arg(func, *args, **kwargs)
|
||||
return func
|
||||
return _decorator
|
||||
|
||||
|
||||
def env(*args, **kwargs):
|
||||
"""Returns 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 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 unauthenticated(func):
|
||||
"""Adds 'unauthenticated' attribute to decorated function.
|
||||
|
||||
Usage:
|
||||
|
||||
>>> @unauthenticated
|
||||
... def mymethod(f):
|
||||
... pass
|
||||
"""
|
||||
func.unauthenticated = True
|
||||
return func
|
||||
|
||||
|
||||
def isunauthenticated(func):
|
||||
"""Checks if the function does not require authentication.
|
||||
|
||||
Mark such functions with the `@unauthenticated` decorator.
|
||||
|
||||
:returns: bool
|
||||
"""
|
||||
return getattr(func, 'unauthenticated', False)
|
||||
|
||||
|
||||
def print_list(objs, fields, formatters=None, sortby_index=0,
|
||||
mixed_case_fields=None, field_labels=None):
|
||||
"""Print a list or objects as a table, one row per object.
|
||||
|
||||
:param objs: iterable of :class:`Resource`
|
||||
:param fields: attributes that correspond to columns, in order
|
||||
:param formatters: `dict` of callables for field formatting
|
||||
:param sortby_index: index of the field for sorting table rows
|
||||
:param mixed_case_fields: fields corresponding to object attributes that
|
||||
have mixed case names (e.g., 'serverId')
|
||||
:param field_labels: Labels to use in the heading of the table, default to
|
||||
fields.
|
||||
"""
|
||||
formatters = formatters or {}
|
||||
mixed_case_fields = mixed_case_fields or []
|
||||
field_labels = field_labels or fields
|
||||
if len(field_labels) != len(fields):
|
||||
raise ValueError(_("Field labels list %(labels)s has different number "
|
||||
"of elements than fields list %(fields)s"),
|
||||
{'labels': field_labels, 'fields': fields})
|
||||
|
||||
if sortby_index is None:
|
||||
kwargs = {}
|
||||
else:
|
||||
kwargs = {'sortby': field_labels[sortby_index]}
|
||||
pt = prettytable.PrettyTable(field_labels)
|
||||
pt.align = 'l'
|
||||
|
||||
for o in objs:
|
||||
row = []
|
||||
for field in fields:
|
||||
data = '-'
|
||||
if field in formatters:
|
||||
data = formatters[field](o)
|
||||
else:
|
||||
if field in mixed_case_fields:
|
||||
field_name = field.replace(' ', '_')
|
||||
else:
|
||||
field_name = field.lower().replace(' ', '_')
|
||||
data = getattr(o, field_name, '')
|
||||
if data is None:
|
||||
data = '-'
|
||||
row.append(data)
|
||||
pt.add_row(row)
|
||||
|
||||
if six.PY3:
|
||||
print(encodeutils.safe_encode(pt.get_string(**kwargs)).decode())
|
||||
else:
|
||||
print(encodeutils.safe_encode(pt.get_string(**kwargs)))
|
||||
|
||||
|
||||
def keys_and_vals_to_strs(dictionary):
|
||||
"""Recursively convert a dictionary's keys and values to strings.
|
||||
|
||||
:param dictionary: dictionary whose keys/vals are to be converted to strs
|
||||
"""
|
||||
def to_str(k_or_v):
|
||||
if isinstance(k_or_v, dict):
|
||||
return keys_and_vals_to_strs(k_or_v)
|
||||
elif isinstance(k_or_v, six.text_type):
|
||||
return str(k_or_v)
|
||||
else:
|
||||
return k_or_v
|
||||
return dict((to_str(k), to_str(v)) for k, v in dictionary.items())
|
||||
|
||||
|
||||
def print_dict(dct, dict_property="Property", wrap=0):
|
||||
"""Print a `dict` as a table of two columns.
|
||||
|
||||
:param dct: `dict` to print
|
||||
:param dict_property: name of the first column
|
||||
:param wrap: wrapping for the second column
|
||||
"""
|
||||
pt = prettytable.PrettyTable([dict_property, 'Value'])
|
||||
pt.align = 'l'
|
||||
for k, v in dct.items():
|
||||
# convert dict to str to check length
|
||||
if isinstance(v, dict):
|
||||
v = six.text_type(keys_and_vals_to_strs(v))
|
||||
if wrap > 0:
|
||||
v = textwrap.fill(six.text_type(v), wrap)
|
||||
# if value has a newline, add in multiple rows
|
||||
# e.g. fault with stacktrace
|
||||
if v and isinstance(v, six.string_types) and r'\n' in v:
|
||||
lines = v.strip().split(r'\n')
|
||||
col1 = k
|
||||
for line in lines:
|
||||
pt.add_row([col1, line])
|
||||
col1 = ''
|
||||
elif isinstance(v, list):
|
||||
val = str([str(i) for i in v])
|
||||
if val is None:
|
||||
val = '-'
|
||||
pt.add_row([k, val])
|
||||
else:
|
||||
if v is None:
|
||||
v = '-'
|
||||
pt.add_row([k, v])
|
||||
|
||||
if six.PY3:
|
||||
print(encodeutils.safe_encode(pt.get_string()).decode())
|
||||
else:
|
||||
print(encodeutils.safe_encode(pt.get_string()))
|
||||
|
||||
|
||||
def get_password(max_password_prompts=3):
|
||||
"""Read password from TTY."""
|
||||
verify = strutils.bool_from_string(env("OS_VERIFY_PASSWORD"))
|
||||
pw = None
|
||||
if hasattr(sys.stdin, "isatty") and sys.stdin.isatty():
|
||||
# Check for Ctrl-D
|
||||
try:
|
||||
for __ in moves.range(max_password_prompts):
|
||||
pw1 = getpass.getpass("OS Password: ")
|
||||
if verify:
|
||||
pw2 = getpass.getpass("Please verify: ")
|
||||
else:
|
||||
pw2 = pw1
|
||||
if pw1 == pw2 and pw1:
|
||||
pw = pw1
|
||||
break
|
||||
except EOFError:
|
||||
pass
|
||||
return pw
|
||||
|
||||
|
||||
def service_type(stype):
|
||||
"""Adds 'service_type' attribute to decorated function.
|
||||
|
||||
Usage:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@service_type('volume')
|
||||
def mymethod(f):
|
||||
...
|
||||
"""
|
||||
def inner(f):
|
||||
f.service_type = stype
|
||||
return f
|
||||
return inner
|
||||
|
||||
|
||||
def get_service_type(f):
|
||||
"""Retrieves service type from function."""
|
||||
return getattr(f, 'service_type', None)
|
||||
|
||||
|
||||
def pretty_choice_list(l):
|
||||
return ', '.join("'%s'" % i for i in l)
|
||||
|
||||
|
||||
def exit(msg=''):
|
||||
if msg:
|
||||
print(msg, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def _format_field_name(attr):
|
||||
"""Format an object attribute in a human-friendly way."""
|
||||
# Split at ':' and leave the extension name as-is.
|
||||
parts = attr.rsplit(':', 1)
|
||||
name = parts[-1].replace('_', ' ')
|
||||
# Don't title() on mixed case
|
||||
if name.isupper() or name.islower():
|
||||
name = name.title()
|
||||
parts[-1] = name
|
||||
return ': '.join(parts)
|
||||
|
||||
|
||||
def make_field_formatter(attr, filters=None):
|
||||
"""Given an object attribute.
|
||||
|
||||
Return a formatted field name and a formatter suitable for passing to
|
||||
print_list.
|
||||
Optionally pass a dict mapping attribute names to a function. The function
|
||||
will be passed the value of the attribute and should return the string to
|
||||
display.
|
||||
"""
|
||||
|
||||
filter_ = None
|
||||
if filters:
|
||||
filter_ = filters.get(attr)
|
||||
|
||||
def get_field(obj):
|
||||
field = getattr(obj, attr, '')
|
||||
if field and filter_:
|
||||
field = filter_(field)
|
||||
return field
|
||||
|
||||
name = _format_field_name(attr)
|
||||
formatter = get_field
|
||||
return name, formatter
|
||||
|
||||
|
||||
def _get_list_table_columns_and_formatters(fields, objs, exclude_fields=(),
|
||||
filters=None):
|
||||
"""Check and add fields to output columns.
|
||||
|
||||
If there is any value in fields that not an attribute of obj,
|
||||
CommandError will be raised.
|
||||
If fields has duplicate values (case sensitive), we will make them unique
|
||||
and ignore duplicate ones.
|
||||
:param fields: A list of string contains the fields to be printed.
|
||||
:param objs: An list of object which will be used to check if field is
|
||||
valid or not. Note, we don't check fields if obj is None or
|
||||
empty.
|
||||
:param exclude_fields: A tuple of string which contains the fields to be
|
||||
excluded.
|
||||
:param filters: A dictionary defines how to get value from fields, this
|
||||
is useful when field's value is a complex object such as
|
||||
dictionary.
|
||||
:return: columns, formatters.
|
||||
columns is a list of string which will be used as table header.
|
||||
formatters is a dictionary specifies how to display the value
|
||||
of the field.
|
||||
They can be [], {}.
|
||||
:raise: magnumclient.common.apiclient.exceptions.CommandError.
|
||||
"""
|
||||
|
||||
if objs and isinstance(objs, list):
|
||||
obj = objs[0]
|
||||
else:
|
||||
obj = None
|
||||
fields = None
|
||||
|
||||
columns = []
|
||||
formatters = {}
|
||||
|
||||
if fields:
|
||||
non_existent_fields = []
|
||||
exclude_fields = set(exclude_fields)
|
||||
|
||||
for field in fields.split(','):
|
||||
if not hasattr(obj, field):
|
||||
non_existent_fields.append(field)
|
||||
continue
|
||||
if field in exclude_fields:
|
||||
continue
|
||||
field_title, formatter = make_field_formatter(field, filters)
|
||||
columns.append(field_title)
|
||||
formatters[field_title] = formatter
|
||||
exclude_fields.add(field)
|
||||
|
||||
if non_existent_fields:
|
||||
raise exceptions.CommandError(
|
||||
_("Non-existent fields are specified: %s") %
|
||||
non_existent_fields
|
||||
)
|
||||
return columns, formatters
|
|
@ -1,430 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2012 OpenStack LLC.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import copy
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import socket
|
||||
import ssl
|
||||
|
||||
from keystoneauth1 import adapter
|
||||
from oslo_utils import importutils
|
||||
import six
|
||||
import six.moves.urllib.parse as urlparse
|
||||
|
||||
from magnumclient import exceptions
|
||||
|
||||
osprofiler_web = importutils.try_import("osprofiler.web")
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
USER_AGENT = 'python-magnumclient'
|
||||
CHUNKSIZE = 1024 * 64 # 64kB
|
||||
|
||||
API_VERSION = '/v1'
|
||||
DEFAULT_API_VERSION = 'latest'
|
||||
|
||||
|
||||
def _extract_error_json(body):
|
||||
"""Return error_message from the HTTP response body."""
|
||||
error_json = {}
|
||||
try:
|
||||
body_json = json.loads(body)
|
||||
if 'error_message' in body_json:
|
||||
raw_msg = body_json['error_message']
|
||||
error_json = json.loads(raw_msg)
|
||||
elif 'error' in body_json:
|
||||
error_body = body_json['error']
|
||||
error_json = {'faultstring': error_body['title'],
|
||||
'debuginfo': error_body['message']}
|
||||
else:
|
||||
error_body = body_json['errors'][0]
|
||||
error_json = {'faultstring': error_body['title']}
|
||||
if 'detail' in error_body:
|
||||
error_json['debuginfo'] = error_body['detail']
|
||||
elif 'description' in error_body:
|
||||
error_json['debuginfo'] = error_body['description']
|
||||
|
||||
except ValueError:
|
||||
return {}
|
||||
|
||||
return error_json
|
||||
|
||||
|
||||
class HTTPClient(object):
|
||||
|
||||
def __init__(self, endpoint, api_version=DEFAULT_API_VERSION, **kwargs):
|
||||
self.endpoint = endpoint
|
||||
self.auth_token = kwargs.get('token')
|
||||
self.auth_ref = kwargs.get('auth_ref')
|
||||
self.api_version = api_version
|
||||
self.connection_params = self.get_connection_params(endpoint, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def get_connection_params(endpoint, **kwargs):
|
||||
parts = urlparse.urlparse(endpoint)
|
||||
|
||||
# trim API version and trailing slash from endpoint
|
||||
path = parts.path
|
||||
path = path.rstrip('/').rstrip(API_VERSION)
|
||||
|
||||
_args = (parts.hostname, parts.port, path)
|
||||
_kwargs = {'timeout': (float(kwargs.get('timeout'))
|
||||
if kwargs.get('timeout') else 600)}
|
||||
|
||||
if parts.scheme == 'https':
|
||||
_class = VerifiedHTTPSConnection
|
||||
_kwargs['ca_file'] = kwargs.get('ca_file', None)
|
||||
_kwargs['cert_file'] = kwargs.get('cert_file', None)
|
||||
_kwargs['key_file'] = kwargs.get('key_file', None)
|
||||
_kwargs['insecure'] = kwargs.get('insecure', False)
|
||||
elif parts.scheme == 'http':
|
||||
_class = six.moves.http_client.HTTPConnection
|
||||
else:
|
||||
msg = 'Unsupported scheme: %s' % parts.scheme
|
||||
raise exceptions.EndpointException(msg)
|
||||
|
||||
return (_class, _args, _kwargs)
|
||||
|
||||
def get_connection(self):
|
||||
_class = self.connection_params[0]
|
||||
return _class(*self.connection_params[1][0:2],
|
||||
**self.connection_params[2])
|
||||
|
||||
def log_curl_request(self, method, url, kwargs):
|
||||
curl = ['curl -i -X %s' % method]
|
||||
|
||||
for (key, value) in kwargs['headers'].items():
|
||||
header = '-H \'%s: %s\'' % (key, value)
|
||||
curl.append(header)
|
||||
|
||||
conn_params_fmt = [
|
||||
('key_file', '--key %s'),
|
||||
('cert_file', '--cert %s'),
|
||||
('ca_file', '--cacert %s'),
|
||||
]
|
||||
for (key, fmt) in conn_params_fmt:
|
||||
value = self.connection_params[2].get(key)
|
||||
if value:
|
||||
curl.append(fmt % value)
|
||||
|
||||
if self.connection_params[2].get('insecure'):
|
||||
curl.append('-k')
|
||||
|
||||
if 'body' in kwargs:
|
||||
curl.append('-d \'%s\'' % kwargs['body'])
|
||||
|
||||
curl.append('%s/%s' % (self.endpoint, url.lstrip(API_VERSION)))
|
||||
LOG.debug(' '.join(curl))
|
||||
|
||||
@staticmethod
|
||||
def log_http_response(resp, body=None):
|
||||
status = (resp.version / 10.0, resp.status, resp.reason)
|
||||
dump = ['\nHTTP/%.1f %s %s' % status]
|
||||
dump.extend(['%s: %s' % (k, v) for k, v in resp.getheaders()])
|
||||
dump.append('')
|
||||
if body:
|
||||
dump.extend([body, ''])
|
||||
LOG.debug('\n'.join(dump))
|
||||
|
||||
def _make_connection_url(self, url):
|
||||
(_class, _args, _kwargs) = self.connection_params
|
||||
base_url = _args[2]
|
||||
return '%s/%s' % (base_url, url.lstrip('/'))
|
||||
|
||||
def _http_request(self, url, method, **kwargs):
|
||||
"""Send an http request with the specified characteristics.
|
||||
|
||||
Wrapper around httplib.HTTP(S)Connection.request to handle tasks such
|
||||
as setting headers and error handling.
|
||||
"""
|
||||
# Copy the kwargs so we can reuse the original in case of redirects
|
||||
kwargs['headers'] = copy.deepcopy(kwargs.get('headers', {}))
|
||||
kwargs['headers'].setdefault('User-Agent', USER_AGENT)
|
||||
if self.api_version:
|
||||
version_string = 'container-infra %s' % self.api_version
|
||||
kwargs['headers'].setdefault(
|
||||
'OpenStack-API-Version', version_string)
|
||||
if self.auth_token:
|
||||
kwargs['headers'].setdefault('X-Auth-Token', self.auth_token)
|
||||
|
||||
self.log_curl_request(method, url, kwargs)
|
||||
conn = self.get_connection()
|
||||
|
||||
try:
|
||||
conn_url = self._make_connection_url(url)
|
||||
conn.request(method, conn_url, **kwargs)
|
||||
resp = conn.getresponse()
|
||||
except socket.gaierror as e:
|
||||
message = ("Error finding address for %(url)s: %(e)s"
|
||||
% dict(url=url, e=e))
|
||||
raise exceptions.EndpointNotFound(message)
|
||||
except (socket.error, socket.timeout) as e:
|
||||
endpoint = self.endpoint
|
||||
message = ("Error communicating with %(endpoint)s %(e)s"
|
||||
% dict(endpoint=endpoint, e=e))
|
||||
raise exceptions.ConnectionRefused(message)
|
||||
|
||||
body_iter = ResponseBodyIterator(resp)
|
||||
|
||||
# Read body into string if it isn't obviously image data
|
||||
body_str = None
|
||||
if resp.getheader('content-type', None) != 'application/octet-stream':
|
||||
# decoding byte to string is necessary for Python 3.4 compatibility
|
||||
# this issues has not been found with Python 3.4 unit tests
|
||||
# because the test creates a fake http response of type str
|
||||
# the if statement satisfies test (str) and real (bytes) behavior
|
||||
body_list = [
|
||||
chunk.decode("utf-8") if isinstance(chunk, bytes)
|
||||
else chunk for chunk in body_iter
|
||||
]
|
||||
body_str = ''.join(body_list)
|
||||
self.log_http_response(resp, body_str)
|
||||
body_iter = six.StringIO(body_str)
|
||||
else:
|
||||
self.log_http_response(resp)
|
||||
|
||||
if 400 <= resp.status < 600:
|
||||
LOG.warning("Request returned failure status.")
|
||||
error_json = _extract_error_json(body_str)
|
||||
raise exceptions.from_response(
|
||||
resp, error_json.get('faultstring'),
|
||||
error_json.get('debuginfo'), method, url)
|
||||
elif resp.status in (301, 302, 305):
|
||||
# Redirected. Reissue the request to the new location.
|
||||
return self._http_request(resp['location'], method, **kwargs)
|
||||
elif resp.status == 300:
|
||||
raise exceptions.from_response(resp, method=method, url=url)
|
||||
|
||||
return resp, body_iter
|
||||
|
||||
def json_request(self, method, url, **kwargs):
|
||||
kwargs.setdefault('headers', {})
|
||||
kwargs['headers'].setdefault('Content-Type', 'application/json')
|
||||
kwargs['headers'].setdefault('Accept', 'application/json')
|
||||
|
||||
if 'body' in kwargs:
|
||||
kwargs['body'] = json.dumps(kwargs['body'])
|
||||
|
||||
resp, body_iter = self._http_request(url, method, **kwargs)
|
||||
content_type = resp.getheader('content-type', None)
|
||||
|
||||
if resp.status == 204 or resp.status == 205 or content_type is None:
|
||||
return resp, list()
|
||||
|
||||
if 'application/json' in content_type:
|
||||
body = ''.join([chunk for chunk in body_iter])
|
||||
try:
|
||||
body = json.loads(body)
|
||||
except ValueError:
|
||||
LOG.error('Could not decode response body as JSON')
|
||||
else:
|
||||
body = None
|
||||
|
||||
return resp, body
|
||||
|
||||
def raw_request(self, method, url, **kwargs):
|
||||
kwargs.setdefault('headers', {})
|
||||
kwargs['headers'].setdefault('Content-Type',
|
||||
'application/octet-stream')
|
||||
return self._http_request(url, method, **kwargs)
|
||||
|
||||
|
||||
class VerifiedHTTPSConnection(six.moves.http_client.HTTPSConnection):
|
||||
"""httplib-compatibile connection using client-side SSL authentication
|
||||
|
||||
:see http://code.activestate.com/recipes/
|
||||
577548-https-httplib-client-connection-with-certificate-v/
|
||||
"""
|
||||
|
||||
def __init__(self, host, port, key_file=None, cert_file=None,
|
||||
ca_file=None, timeout=None, insecure=False):
|
||||
six.moves.http_client.HTTPSConnection.__init__(self, host, port,
|
||||
key_file=key_file,
|
||||
cert_file=cert_file)
|
||||
self.key_file = key_file
|
||||
self.cert_file = cert_file
|
||||
if ca_file is not None:
|
||||
self.ca_file = ca_file
|
||||
else:
|
||||
self.ca_file = self.get_system_ca_file()
|
||||
self.timeout = timeout
|
||||
self.insecure = insecure
|
||||
|
||||
def connect(self):
|
||||
"""Connect to a host on a given (SSL) port.
|
||||
|
||||
If ca_file is pointing somewhere, use it to check Server Certificate.
|
||||
|
||||
Redefined/copied and extended from httplib.py:1105 (Python 2.6.x).
|
||||
This is needed to pass cert_reqs=ssl.CERT_REQUIRED as parameter to
|
||||
ssl.wrap_socket(), which forces SSL to check server certificate against
|
||||
our client certificate.
|
||||
"""
|
||||
sock = socket.create_connection((self.host, self.port), self.timeout)
|
||||
|
||||
if self._tunnel_host:
|
||||
self.sock = sock
|
||||
self._tunnel()
|
||||
|
||||
if self.insecure is True:
|
||||
kwargs = {'cert_reqs': ssl.CERT_NONE}
|
||||
else:
|
||||
kwargs = {'cert_reqs': ssl.CERT_REQUIRED, 'ca_certs': self.ca_file}
|
||||
|
||||
if self.cert_file:
|
||||
kwargs['certfile'] = self.cert_file
|
||||
if self.key_file:
|
||||
kwargs['keyfile'] = self.key_file
|
||||
|
||||
self.sock = ssl.wrap_socket(sock, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def get_system_ca_file():
|
||||
"""Return path to system default CA file."""
|
||||
# Standard CA file locations for Debian/Ubuntu, RedHat/Fedora,
|
||||
# Suse, FreeBSD/OpenBSD
|
||||
ca_path = ['/etc/ssl/certs/ca-certificates.crt',
|
||||
'/etc/pki/tls/certs/ca-bundle.crt',
|
||||
'/etc/ssl/ca-bundle.pem',
|
||||
'/etc/ssl/cert.pem']
|
||||
for ca in ca_path:
|
||||
if os.path.exists(ca):
|
||||
return ca
|
||||
return None
|
||||
|
||||
|
||||
class SessionClient(adapter.LegacyJsonAdapter):
|
||||
"""HTTP client based on Keystone client session."""
|
||||
|
||||
def __init__(self, user_agent=USER_AGENT, logger=LOG,
|
||||
api_version=DEFAULT_API_VERSION, *args, **kwargs):
|
||||
self.user_agent = USER_AGENT
|
||||
self.api_version = api_version
|
||||
super(SessionClient, self).__init__(*args, **kwargs)
|
||||
|
||||
def _http_request(self, url, method, **kwargs):
|
||||
if url.startswith(API_VERSION):
|
||||
url = url[len(API_VERSION):]
|
||||
|
||||
kwargs.setdefault('user_agent', self.user_agent)
|
||||
kwargs.setdefault('auth', self.auth)
|
||||
kwargs.setdefault('endpoint_override', self.endpoint_override)
|
||||
|
||||
# Copy the kwargs so we can reuse the original in case of redirects
|
||||
kwargs['headers'] = copy.deepcopy(kwargs.get('headers', {}))
|
||||
kwargs['headers'].setdefault('User-Agent', self.user_agent)
|
||||
# NOTE(tovin07): osprofiler_web.get_trace_id_headers does not add any
|
||||
# headers in case if osprofiler is not initialized.
|
||||
if osprofiler_web:
|
||||
kwargs['headers'].update(osprofiler_web.get_trace_id_headers())
|
||||
if self.api_version:
|
||||
version_string = 'container-infra %s' % self.api_version
|
||||
kwargs['headers'].setdefault(
|
||||
'OpenStack-API-Version', version_string)
|
||||
|
||||
endpoint_filter = kwargs.setdefault('endpoint_filter', {})
|
||||
endpoint_filter.setdefault('interface', self.interface)
|
||||
endpoint_filter.setdefault('service_type', self.service_type)
|
||||
endpoint_filter.setdefault('region_name', self.region_name)
|
||||
|
||||
resp = self.session.request(url, method,
|
||||
raise_exc=False, **kwargs)
|
||||
|
||||
if 400 <= resp.status_code < 600:
|
||||
error_json = _extract_error_json(resp.content)
|
||||
raise exceptions.from_response(
|
||||
resp, error_json.get('faultstring'),
|
||||
error_json.get('debuginfo'), method, url)
|
||||
elif resp.status_code in (301, 302, 305):
|
||||
# Redirected. Reissue the request to the new location.
|
||||
location = resp.headers.get('location')
|
||||
resp = self._http_request(location, method, **kwargs)
|
||||
elif resp.status_code == 300:
|
||||
raise exceptions.from_response(resp, method=method, url=url)
|
||||
return resp
|
||||
|
||||
def json_request(self, method, url, **kwargs):
|
||||
kwargs.setdefault('headers', {})
|
||||
kwargs['headers'].setdefault('Content-Type', 'application/json')
|
||||
kwargs['headers'].setdefault('Accept', 'application/json')
|
||||
if 'body' in kwargs:
|
||||
kwargs['data'] = json.dumps(kwargs.pop('body'))
|
||||
|
||||
resp = self._http_request(url, method, **kwargs)
|
||||
body = resp.content
|
||||
content_type = resp.headers.get('content-type', None)
|
||||
status = resp.status_code
|
||||
if status == 204 or status == 205 or content_type is None:
|
||||
return resp, list()
|
||||
if 'application/json' in content_type:
|
||||
try:
|
||||
body = resp.json()
|
||||
except ValueError:
|
||||
LOG.error('Could not decode response body as JSON')
|
||||
else:
|
||||
body = None
|
||||
|
||||
return resp, body
|
||||
|
||||
def raw_request(self, method, url, **kwargs):
|
||||
kwargs.setdefault('headers', {})
|
||||
kwargs['headers'].setdefault('Content-Type',
|
||||
'application/octet-stream')
|
||||
return self._http_request(url, method, **kwargs)
|
||||
|
||||
|
||||
class ResponseBodyIterator(object):
|
||||
"""A class that acts as an iterator over an HTTP response."""
|
||||
|
||||
def __init__(self, resp):
|
||||
self.resp = resp
|
||||
|
||||
def __iter__(self):
|
||||
while True:
|
||||
yield self.next()
|
||||
|
||||
def __bool__(self):
|
||||
return hasattr(self, 'items')
|
||||
|
||||
__nonzero__ = __bool__ # Python 2.x compatibility
|
||||
|
||||
def next(self):
|
||||
chunk = self.resp.read(CHUNKSIZE)
|
||||
if chunk:
|
||||
return chunk
|
||||
else:
|
||||
raise StopIteration()
|
||||
|
||||
|
||||
def _construct_http_client(*args, **kwargs):
|
||||
session = kwargs.pop('session', None)
|
||||
auth = kwargs.pop('auth', None)
|
||||
|
||||
if session:
|
||||
service_type = kwargs.pop('service_type', 'baremetal')
|
||||
interface = kwargs.pop('endpoint_type', None)
|
||||
region_name = kwargs.pop('region_name', None)
|
||||
return SessionClient(session=session,
|
||||
auth=auth,
|
||||
interface=interface,
|
||||
service_type=service_type,
|
||||
region_name=region_name,
|
||||
service_name=None,
|
||||
user_agent='python-magnumclient')
|
||||
else:
|
||||
return HTTPClient(*args, **kwargs)
|
|
@ -1,144 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2012 OpenStack LLC.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import json
|
||||
|
||||
from magnumclient import exceptions as exc
|
||||
from magnumclient.i18n import _
|
||||
|
||||
|
||||
def common_filters(marker=None, limit=None, sort_key=None, sort_dir=None):
|
||||
"""Generate common filters for any list request.
|
||||
|
||||
:param marker: entity ID from which to start returning entities.
|
||||
:param limit: maximum number of entities to return.
|
||||
:param sort_key: field to use for sorting.
|
||||
:param sort_dir: direction of sorting: 'asc' or 'desc'.
|
||||
:returns: list of string filters.
|
||||
"""
|
||||
filters = []
|
||||
if isinstance(limit, int):
|
||||
filters.append('limit=%s' % limit)
|
||||
if marker is not None:
|
||||
filters.append('marker=%s' % marker)
|
||||
if sort_key is not None:
|
||||
filters.append('sort_key=%s' % sort_key)
|
||||
if sort_dir is not None:
|
||||
filters.append('sort_dir=%s' % sort_dir)
|
||||
return filters
|
||||
|
||||
|
||||
def split_and_deserialize(string):
|
||||
"""Split and try to JSON deserialize a string.
|
||||
|
||||
Gets a string with the KEY=VALUE format, split it (using '=' as the
|
||||
separator) and try to JSON deserialize the VALUE.
|
||||
:returns: A tuple of (key, value).
|
||||
"""
|
||||
try:
|
||||
key, value = string.split("=", 1)
|
||||
except ValueError:
|
||||
raise exc.CommandError(_('Attributes must be a list of '
|
||||
'PATH=VALUE not "%s"') % string)
|
||||
try:
|
||||
value = json.loads(value)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
return (key, value)
|
||||
|
||||
|
||||
def args_array_to_patch(op, attributes):
|
||||
patch = []
|
||||
for attr in attributes:
|
||||
# Sanitize
|
||||
if not attr.startswith('/'):
|
||||
attr = '/' + attr
|
||||
if op in ['add', 'replace']:
|
||||
path, value = split_and_deserialize(attr)
|
||||
patch.append({'op': op, 'path': path, 'value': value})
|
||||
|
||||
elif op == "remove":
|
||||
# For remove only the key is needed
|
||||
patch.append({'op': op, 'path': attr})
|
||||
else:
|
||||
raise exc.CommandError(_('Unknown PATCH operation: %s') % op)
|
||||
return patch
|
||||
|
||||
|
||||
def handle_labels(labels):
|
||||
labels = format_labels(labels)
|
||||
if 'mesos_slave_executor_env_file' in labels:
|
||||
environment_variables_data = handle_json_from_file(
|
||||
labels['mesos_slave_executor_env_file'])
|
||||
labels['mesos_slave_executor_env_variables'] = json.dumps(
|
||||
environment_variables_data)
|
||||
return labels
|
||||
|
||||
|
||||
def format_labels(lbls, parse_comma=True):
|
||||
'''Reformat labels into dict of format expected by the API.'''
|
||||
|
||||
if not lbls:
|
||||
return {}
|
||||
|
||||
if parse_comma:
|
||||
# expect multiple invocations of --labels but fall back
|
||||
# to either , or ; delimited if only one --labels is specified
|
||||
if len(lbls) == 1 and lbls[0].count('=') > 1:
|
||||
lbls = lbls[0].replace(';', ',').split(',')
|
||||
|
||||
labels = {}
|
||||
for l in lbls:
|
||||
try:
|
||||
(k, v) = l.split(('='), 1)
|
||||
except ValueError:
|
||||
raise exc.CommandError(_('labels must be a list of KEY=VALUE '
|
||||
'not %s') % l)
|
||||
if k not in labels:
|
||||
labels[k] = v
|
||||
else:
|
||||
labels[k] += ",%s" % v
|
||||
|
||||
return labels
|
||||
|
||||
|
||||
def print_list_field(field):
|
||||
return lambda obj: ', '.join(getattr(obj, field))
|
||||
|
||||
|
||||
def handle_json_from_file(json_arg):
|
||||
"""Attempts to read JSON file by the file url.
|
||||
|
||||
:param json_arg: May be a file name containing the JSON.
|
||||
:returns: A list or dictionary parsed from JSON.
|
||||
"""
|
||||
|
||||
try:
|
||||
with open(json_arg, 'r') as f:
|
||||
json_arg = f.read().strip()
|
||||
json_arg = json.loads(json_arg)
|
||||
except IOError as e:
|
||||
err = _("Cannot get JSON from file '%(file)s'. "
|
||||
"Error: %(err)s") % {'err': e, 'file': json_arg}
|
||||
raise exc.InvalidAttribute(err)
|
||||
except ValueError as e:
|
||||
err = (_("For JSON: '%(string)s', error: '%(err)s'") %
|
||||
{'err': e, 'string': json_arg})
|
||||
raise exc.InvalidAttribute(err)
|
||||
|
||||
return json_arg
|
|
@ -1,74 +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.
|
||||
|
||||
from magnumclient.common.apiclient import exceptions
|
||||
from magnumclient.common.apiclient.exceptions import * # noqa
|
||||
|
||||
|
||||
# NOTE(akurilin): This alias is left here since v.0.1.3 to support backwards
|
||||
# compatibility.
|
||||
InvalidEndpoint = EndpointException
|
||||
CommunicationError = ConnectionRefused
|
||||
HTTPBadRequest = BadRequest
|
||||
HTTPInternalServerError = InternalServerError
|
||||
HTTPNotFound = NotFound
|
||||
HTTPServiceUnavailable = ServiceUnavailable
|
||||
|
||||
|
||||
class AmbiguousAuthSystem(ClientException):
|
||||
"""Could not obtain token and endpoint using provided credentials."""
|
||||
pass
|
||||
|
||||
# Alias for backwards compatibility
|
||||
AmbigiousAuthSystem = AmbiguousAuthSystem
|
||||
|
||||
|
||||
class InvalidAttribute(ClientException):
|
||||
pass
|
||||
|
||||
|
||||
def from_response(response, message=None, traceback=None, method=None,
|
||||
url=None):
|
||||
"""Return an HttpError instance based on response from httplib/requests."""
|
||||
|
||||
error_body = {}
|
||||
if message:
|
||||
error_body['message'] = message
|
||||
if traceback:
|
||||
error_body['details'] = traceback
|
||||
|
||||
if hasattr(response, 'status') and not hasattr(response, 'status_code'):
|
||||
# NOTE(akurilin): These modifications around response object give
|
||||
# ability to get all necessary information in method `from_response`
|
||||
# from common code, which expecting response object from `requests`
|
||||
# library instead of object from `httplib/httplib2` library.
|
||||
response.status_code = response.status
|
||||
response.headers = {
|
||||
'Content-Type': response.getheader('content-type', "")}
|
||||
|
||||
if hasattr(response, 'status_code'):
|
||||
# NOTE(hongbin): This allows SessionClient to handle faultstring.
|
||||
response.json = lambda: {'error': error_body}
|
||||
|
||||
if (response.headers.get('Content-Type', '').startswith('text/') and
|
||||
not hasattr(response, 'text')):
|
||||
# NOTE(clif_h): There seems to be a case in the
|
||||
# common.apiclient.exceptions module where if the
|
||||
# content-type of the response is text/* then it expects
|
||||
# the response to have a 'text' attribute, but that
|
||||
# doesn't always seem to necessarily be the case.
|
||||
# This is to work around that problem.
|
||||
response.text = ''
|
||||
|
||||
return exceptions.from_response(response, method, url)
|
|
@ -1,35 +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.
|
||||
|
||||
"""oslo_i18n integration module for magnumclient.
|
||||
|
||||
See https://docs.openstack.org/oslo.i18n/latest/user/usage.html .
|
||||
|
||||
"""
|
||||
|
||||
import oslo_i18n
|
||||
|
||||
|
||||
_translators = oslo_i18n.TranslatorFactory(domain='magnumclient')
|
||||
|
||||
# The primary translation function using the well-known name "_"
|
||||
_ = _translators.primary
|
||||
|
||||
# Translators for log levels.
|
||||
#
|
||||
# The abbreviated names are meant to reflect the usual use of a short
|
||||
# name like '_'. The "L" is for "log" and the other letter comes from
|
||||
# the level.
|
||||
_LI = _translators.log_info
|
||||
_LW = _translators.log_warning
|
||||
_LE = _translators.log_error
|
||||
_LC = _translators.log_critical
|
|
@ -1,56 +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.
|
||||
#
|
||||
|
||||
import logging
|
||||
|
||||
from osc_lib import utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_API_VERSION = '1'
|
||||
API_VERSION_OPTION = 'os_container_infra_api_version'
|
||||
API_NAME = 'container_infra'
|
||||
API_VERSIONS = {
|
||||
'1': 'magnumclient.v1.client.Client',
|
||||
}
|
||||
|
||||
|
||||
def make_client(instance):
|
||||
"""Returns a magnum client."""
|
||||
magnum_client = utils.get_client_class(
|
||||
API_NAME,
|
||||
instance._api_version[API_NAME],
|
||||
API_VERSIONS)
|
||||
LOG.debug('Instantiating magnum client: %s', magnum_client)
|
||||
|
||||
client = magnum_client(session=instance.session,
|
||||
region_name=instance._region_name,
|
||||
interface=instance._interface,
|
||||
insecure=instance._insecure,
|
||||
ca_cert=instance._cacert)
|
||||
return client
|
||||
|
||||
|
||||
def build_option_parser(parser):
|
||||
"""Hook to add global options"""
|
||||
|
||||
parser.add_argument(
|
||||
'--os-container-infra-api-version',
|
||||
metavar='<container-infra-api-version>',
|
||||
default=utils.env(
|
||||
'OS_CONTAINER_INFRA_API_VERSION',
|
||||
default=DEFAULT_API_VERSION),
|
||||
help='Container-Infra API version, default=' +
|
||||
DEFAULT_API_VERSION +
|
||||
' (Env: OS_CONTAINER_INFRA_API_VERSION)')
|
||||
return parser
|
|
@ -1,65 +0,0 @@
|
|||
# Copyright 2016 EasyStack. All rights reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
|
||||
from magnumclient.i18n import _
|
||||
|
||||
from osc_lib.command import command
|
||||
from osc_lib import utils
|
||||
from oslo_log import log as logging
|
||||
|
||||
|
||||
class ListTemplateCluster(command.Lister):
|
||||
"""List Cluster Templates."""
|
||||
|
||||
log = logging.getLogger(__name__ + ".ListTemplateCluster")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ListTemplateCluster, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'--limit',
|
||||
metavar='<limit>',
|
||||
type=int,
|
||||
help=_('Maximum number of cluster templates to return'))
|
||||
parser.add_argument(
|
||||
'--sort-key',
|
||||
metavar='<sort-key>',
|
||||
help=_('Column to sort results by'))
|
||||
parser.add_argument(
|
||||
'--sort-dir',
|
||||
metavar='<sort-dir>',
|
||||
choices=['desc', 'asc'],
|
||||
help=_('Direction to sort. "asc" or "desc".'))
|
||||
parser.add_argument(
|
||||
'--fields',
|
||||
default=None,
|
||||
metavar='<fields>',
|
||||
help=_('Comma-separated list of fields to display. '
|
||||
'Available fields: uuid, name, coe, image_id, public, '
|
||||
'link, apiserver_port, server_type, tls_disabled, '
|
||||
'registry_enabled'
|
||||
)
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.log.debug("take_action(%s)", parsed_args)
|
||||
|
||||
mag_client = self.app.client_manager.container_infra
|
||||
columns = ['uuid', 'name']
|
||||
cts = mag_client.cluster_templates.list()
|
||||
return (
|
||||
columns,
|
||||
(utils.get_item_properties(ct, columns) for ct in cts)
|
||||
)
|
|
@ -1,650 +0,0 @@
|
|||
# Copyright 2014
|
||||
# The Cloudscaling Group, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
|
||||
###
|
||||
# This code is taken from python-novaclient. Goal is minimal modification.
|
||||
###
|
||||
|
||||
"""
|
||||
Command-line interface to the OpenStack Magnum API.
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
from oslo_utils import encodeutils
|
||||
from oslo_utils import importutils
|
||||
from oslo_utils import strutils
|
||||
import six
|
||||
|
||||
profiler = importutils.try_import("osprofiler.profiler")
|
||||
|
||||
HAS_KEYRING = False
|
||||
all_errors = ValueError
|
||||
try:
|
||||
import keyring
|
||||
HAS_KEYRING = True
|
||||
try:
|
||||
if isinstance(keyring.get_keyring(), keyring.backend.GnomeKeyring):
|
||||
import gnomekeyring
|
||||
all_errors = (ValueError,
|
||||
gnomekeyring.IOError,
|
||||
gnomekeyring.NoKeyringDaemonError)
|
||||
except Exception:
|
||||
pass
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
from magnumclient.common import cliutils
|
||||
from magnumclient import exceptions as exc
|
||||
from magnumclient.i18n import _
|
||||
from magnumclient.v1 import client as client_v1
|
||||
from magnumclient.v1 import shell as shell_v1
|
||||
from magnumclient import version
|
||||
|
||||
LATEST_API_VERSION = ('1', 'latest')
|
||||
DEFAULT_INTERFACE = 'public'
|
||||
DEFAULT_SERVICE_TYPE = 'container-infra'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def positive_non_zero_float(text):
|
||||
if text is None:
|
||||
return None
|
||||
try:
|
||||
value = float(text)
|
||||
except ValueError:
|
||||
msg = "%s must be a float" % text
|
||||
raise argparse.ArgumentTypeError(msg)
|
||||
if value <= 0:
|
||||
msg = "%s must be greater than 0" % text
|
||||
raise argparse.ArgumentTypeError(msg)
|
||||
return value
|
||||
|
||||
|
||||
class MagnumClientArgumentParser(argparse.ArgumentParser):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(MagnumClientArgumentParser, self).__init__(*args, **kwargs)
|
||||
|
||||
def error(self, message):
|
||||
"""error(message: string)
|
||||
|
||||
Prints a usage message incorporating the message to stderr and
|
||||
exits.
|
||||
"""
|
||||
self.print_usage(sys.stderr)
|
||||
# FIXME(lzyeval): if changes occur in argparse.ArgParser._check_value
|
||||
choose_from = ' (choose from'
|
||||
progparts = self.prog.partition(' ')
|
||||
self.exit(2, "error: %(errmsg)s\nTry '%(mainp)s help %(subp)s'"
|
||||
" for more information.\n" %
|
||||
{'errmsg': message.split(choose_from)[0],
|
||||
'mainp': progparts[0],
|
||||
'subp': progparts[2]})
|
||||
|
||||
|
||||
class OpenStackMagnumShell(object):
|
||||
|
||||
def get_base_parser(self):
|
||||
parser = MagnumClientArgumentParser(
|
||||
prog='magnum',
|
||||
description=__doc__.strip(),
|
||||
epilog='See "magnum help COMMAND" '
|
||||
'for help on a specific command.',
|
||||
add_help=False,
|
||||
formatter_class=OpenStackHelpFormatter,
|
||||
)
|
||||
|
||||
# Global arguments
|
||||
parser.add_argument('-h', '--help',
|
||||
action='store_true',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--version',
|
||||
action='version',
|
||||
version=version.version_info.version_string())
|
||||
|
||||
parser.add_argument('--debug',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help=_("Print debugging output."))
|
||||
|
||||
parser.add_argument('--os-cache',
|
||||
default=strutils.bool_from_string(
|
||||
cliutils.env('OS_CACHE', default=False)),
|
||||
action='store_true',
|
||||
help=_("Use the auth token cache. Defaults to "
|
||||
"False if env[OS_CACHE] is not set."))
|
||||
|
||||
parser.add_argument('--os-region-name',
|
||||
metavar='<region-name>',
|
||||
default=os.environ.get('OS_REGION_NAME'),
|
||||
help=_('Region name. '
|
||||
'Default=env[OS_REGION_NAME].'))
|
||||
|
||||
|
||||
# TODO(mattf) - add get_timings support to Client
|
||||
# parser.add_argument('--timings',
|
||||
# default=False,
|
||||
# action='store_true',
|
||||
# help="Print call timing info")
|
||||
|
||||
# TODO(mattf) - use timeout
|
||||
# parser.add_argument('--timeout',
|
||||
# default=600,
|
||||
# metavar='<seconds>',
|
||||
# type=positive_non_zero_float,
|
||||
# help="Set HTTP call timeout (in seconds)")
|
||||
|
||||
parser.add_argument('--os-auth-url',
|
||||
metavar='<auth-auth-url>',
|
||||
default=cliutils.env('OS_AUTH_URL', default=None),
|
||||
help=_('Defaults to env[OS_AUTH_URL].'))
|
||||
|
||||
parser.add_argument('--os-user-id',
|
||||
metavar='<auth-user-id>',
|
||||
default=cliutils.env('OS_USER_ID', default=None),
|
||||
help=_('Defaults to env[OS_USER_ID].'))
|
||||
|
||||
parser.add_argument('--os-username',
|
||||
metavar='<auth-username>',
|
||||
default=cliutils.env('OS_USERNAME', default=None),
|
||||
help=_('Defaults to env[OS_USERNAME].'))
|
||||
|
||||
parser.add_argument('--os-user-domain-id',
|
||||
metavar='<auth-user-domain-id>',
|
||||
default=cliutils.env('OS_USER_DOMAIN_ID',
|
||||
default=None),
|
||||
help=_('Defaults to env[OS_USER_DOMAIN_ID].'))
|
||||
|
||||
parser.add_argument('--os-user-domain-name',
|
||||
metavar='<auth-user-domain-name>',
|
||||
default=cliutils.env('OS_USER_DOMAIN_NAME',
|
||||
default=None),
|
||||
help=_('Defaults to env[OS_USER_DOMAIN_NAME].'))
|
||||
|
||||
parser.add_argument('--os-project-id',
|
||||
metavar='<auth-project-id>',
|
||||
default=cliutils.env('OS_PROJECT_ID',
|
||||
default=None),
|
||||
help=_('Defaults to env[OS_PROJECT_ID].'))
|
||||
|
||||
parser.add_argument('--os-project-name',
|
||||
metavar='<auth-project-name>',
|
||||
default=cliutils.env('OS_PROJECT_NAME',
|
||||
default=None),
|
||||
help=_('Defaults to env[OS_PROJECT_NAME].'))
|
||||
|
||||
parser.add_argument('--os-tenant-id',
|
||||
metavar='<auth-tenant-id>',
|
||||
default=cliutils.env('OS_TENANT_ID',
|
||||
default=None),
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-tenant-name',
|
||||
metavar='<auth-tenant-name>',
|
||||
default=cliutils.env('OS_TENANT_NAME',
|
||||
default=None),
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-project-domain-id',
|
||||
metavar='<auth-project-domain-id>',
|
||||
default=cliutils.env('OS_PROJECT_DOMAIN_ID',
|
||||
default=None),
|
||||
help=_('Defaults to env[OS_PROJECT_DOMAIN_ID].'))
|
||||
|
||||
parser.add_argument('--os-project-domain-name',
|
||||
metavar='<auth-project-domain-name>',
|
||||
default=cliutils.env('OS_PROJECT_DOMAIN_NAME',
|
||||
default=None),
|
||||
help=_('Defaults to '
|
||||
'env[OS_PROJECT_DOMAIN_NAME].'))
|
||||
|
||||
parser.add_argument('--os-token',
|
||||
metavar='<auth-token>',
|
||||
default=cliutils.env('OS_TOKEN', default=None),
|
||||
help=_('Defaults to env[OS_TOKEN].'))
|
||||
|
||||
parser.add_argument('--os-password',
|
||||
metavar='<auth-password>',
|
||||
default=cliutils.env('OS_PASSWORD',
|
||||
default=None),
|
||||
help=_('Defaults to env[OS_PASSWORD].'))
|
||||
|
||||
parser.add_argument('--service-type',
|
||||
metavar='<service-type>',
|
||||
help=_('Defaults to container-infra for all '
|
||||
'actions.'))
|
||||
parser.add_argument('--service_type',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--endpoint-type',
|
||||
metavar='<endpoint-type>',
|
||||
default=cliutils.env('OS_ENDPOINT_TYPE',
|
||||
default=None),
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-endpoint-type',
|
||||
metavar='<os-endpoint-type>',
|
||||
default=cliutils.env('OS_ENDPOINT_TYPE',
|
||||
default=None),
|
||||
help=_('Defaults to env[OS_ENDPOINT_TYPE]'))
|
||||
|
||||
parser.add_argument('--os-interface',
|
||||
metavar='<os-interface>',
|
||||
default=cliutils.env(
|
||||
'OS_INTERFACE',
|
||||
default=DEFAULT_INTERFACE),
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-cloud',
|
||||
metavar='<auth-cloud>',
|
||||
default=cliutils.env('OS_CLOUD', default=None),
|
||||
help=_('Defaults to env[OS_CLOUD].'))
|
||||
|
||||
# NOTE(dtroyer): We can't add --endpoint_type here due to argparse
|
||||
# thinking usage-list --end is ambiguous; but it
|
||||
# works fine with only --endpoint-type present
|
||||
# Go figure. I'm leaving this here for doc purposes.
|
||||
# parser.add_argument('--endpoint_type',
|
||||
# help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--magnum-api-version',
|
||||
metavar='<magnum-api-ver>',
|
||||
default=cliutils.env(
|
||||
'MAGNUM_API_VERSION',
|
||||
default='latest'),
|
||||
help=_('Accepts "api", '
|
||||
'defaults to env[MAGNUM_API_VERSION].'))
|
||||
parser.add_argument('--magnum_api_version',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-cacert',
|
||||
metavar='<ca-certificate>',
|
||||
default=cliutils.env('OS_CACERT', default=None),
|
||||
help=_('Specify a CA bundle file to use in '
|
||||
'verifying a TLS (https) server '
|
||||
'certificate. Defaults to env[OS_CACERT].'))
|
||||
|
||||
parser.add_argument('--os-endpoint-override',
|
||||
metavar='<endpoint-override>',
|
||||
default=cliutils.env('OS_ENDPOINT_OVERRIDE',
|
||||
default=None),
|
||||
help=_("Use this API endpoint instead of the "
|
||||
"Service Catalog."))
|
||||
parser.add_argument('--bypass-url',
|
||||
metavar='<bypass-url>',
|
||||
default=cliutils.env('BYPASS_URL', default=None),
|
||||
dest='bypass_url',
|
||||
help=argparse.SUPPRESS)
|
||||
parser.add_argument('--bypass_url',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--insecure',
|
||||
default=cliutils.env('MAGNUMCLIENT_INSECURE',
|
||||
default=False),
|
||||
action='store_true',
|
||||
help=_("Do not verify https connections"))
|
||||
|
||||
if profiler:
|
||||
parser.add_argument('--profile',
|
||||
metavar='HMAC_KEY',
|
||||
default=cliutils.env('OS_PROFILE',
|
||||
default=None),
|
||||
help='HMAC key to use for encrypting context '
|
||||
'data for performance profiling of operation. '
|
||||
'This key should be the value of the HMAC key '
|
||||
'configured for the OSprofiler middleware in '
|
||||
'magnum; it is specified in the Magnum '
|
||||
'configuration file at '
|
||||
'"/etc/magnum/magnum.conf". '
|
||||
'Without the key, profiling will not be '
|
||||
'triggered even if OSprofiler is enabled on '
|
||||
'the server side.')
|
||||
|
||||
return parser
|
||||
|
||||
def get_subcommand_parser(self, version):
|
||||
parser = self.get_base_parser()
|
||||
|
||||
self.subcommands = {}
|
||||
subparsers = parser.add_subparsers(metavar='<subcommand>')
|
||||
|
||||
try:
|
||||
actions_modules = {
|
||||
'1': shell_v1.COMMAND_MODULES
|
||||
}[version]
|
||||
except KeyError:
|
||||
actions_modules = shell_v1.COMMAND_MODULES
|
||||
|
||||
for actions_module in actions_modules:
|
||||
self._find_actions(subparsers, actions_module)
|
||||
self._find_actions(subparsers, self)
|
||||
|
||||
self._add_bash_completion_subparser(subparsers)
|
||||
|
||||
return parser
|
||||
|
||||
def _add_bash_completion_subparser(self, subparsers):
|
||||
subparser = (
|
||||
subparsers.add_parser('bash_completion',
|
||||
add_help=False,
|
||||
formatter_class=OpenStackHelpFormatter)
|
||||
)
|
||||
self.subcommands['bash_completion'] = subparser
|
||||
subparser.set_defaults(func=self.do_bash_completion)
|
||||
|
||||
def _find_actions(self, subparsers, actions_module):
|
||||
for attr in (a for a in dir(actions_module) if a.startswith('do_')):
|
||||
# I prefer to be hyphen-separated instead of underscores.
|
||||
command = attr[3:].replace('_', '-')
|
||||
callback = getattr(actions_module, attr)
|
||||
desc = callback.__doc__ or ''
|
||||
action_help = desc.strip()
|
||||
arguments = getattr(callback, 'arguments', [])
|
||||
group_args = getattr(callback, 'deprecated_groups', [])
|
||||
|
||||
subparser = (
|
||||
subparsers.add_parser(command,
|
||||
help=action_help,
|
||||
description=desc,
|
||||
add_help=False,
|
||||
formatter_class=OpenStackHelpFormatter)
|
||||
)
|
||||
subparser.add_argument('-h', '--help',
|
||||
action='help',
|
||||
help=argparse.SUPPRESS,)
|
||||
self.subcommands[command] = subparser
|
||||
|
||||
for (old_info, new_info, req) in group_args:
|
||||
group = subparser.add_mutually_exclusive_group(required=req)
|
||||
group.add_argument(*old_info[0], **old_info[1])
|
||||
group.add_argument(*new_info[0], **new_info[1])
|
||||
|
||||
for (args, kwargs) in arguments:
|
||||
subparser.add_argument(*args, **kwargs)
|
||||
subparser.set_defaults(func=callback)
|
||||
|
||||
def setup_debugging(self, debug):
|
||||
if debug:
|
||||
streamformat = "%(levelname)s (%(module)s:%(lineno)d) %(message)s"
|
||||
# Set up the root logger to debug so that the submodules can
|
||||
# print debug messages
|
||||
logging.basicConfig(level=logging.DEBUG,
|
||||
format=streamformat)
|
||||
else:
|
||||
streamformat = "%(levelname)s %(message)s"
|
||||
logging.basicConfig(level=logging.CRITICAL,
|
||||
format=streamformat)
|
||||
|
||||
def _check_version(self, api_version):
|
||||
if api_version == 'latest':
|
||||
return LATEST_API_VERSION
|
||||
else:
|
||||
try:
|
||||
versions = tuple(int(i) for i in api_version.split('.'))
|
||||
except ValueError:
|
||||
versions = ()
|
||||
if len(versions) == 1:
|
||||
# Default value of magnum_api_version is '1'.
|
||||
# If user not specify the value of api version, not passing
|
||||
# headers at all.
|
||||
magnum_api_version = None
|
||||
elif len(versions) == 2:
|
||||
magnum_api_version = api_version
|
||||
# In the case of '1.0'
|
||||
if versions[1] == 0:
|
||||
magnum_api_version = None
|
||||
else:
|
||||
msg = _("The requested API version %(ver)s is an unexpected "
|
||||
"format. Acceptable formats are 'X', 'X.Y', or the "
|
||||
"literal string '%(latest)s'."
|
||||
) % {'ver': api_version, 'latest': 'latest'}
|
||||
raise exc.CommandError(msg)
|
||||
|
||||
api_major_version = versions[0]
|
||||
return (api_major_version, magnum_api_version)
|
||||
|
||||
def _ensure_auth_info(self, args):
|
||||
if not cliutils.isunauthenticated(args.func):
|
||||
if (not (args.os_token and
|
||||
(args.os_auth_url or args.os_endpoint_override)) and
|
||||
not args.os_cloud
|
||||
):
|
||||
|
||||
if not (args.os_username or args.os_user_id):
|
||||
raise exc.CommandError(
|
||||
"You must provide a username via either --os-username "
|
||||
"or via env[OS_USERNAME]"
|
||||
)
|
||||
if not args.os_password:
|
||||
raise exc.CommandError(
|
||||
"You must provide a password via either "
|
||||
"--os-password, env[OS_PASSWORD], or prompted "
|
||||
"response"
|
||||
)
|
||||
if (not args.os_project_name and not args.os_project_id):
|
||||
raise exc.CommandError(
|
||||
"You must provide a project name or project id via "
|
||||
"--os-project-name, --os-project-id, "
|
||||
"env[OS_PROJECT_NAME] or env[OS_PROJECT_ID]"
|
||||
)
|
||||
if not args.os_auth_url:
|
||||
raise exc.CommandError(
|
||||
"You must provide an auth url via either "
|
||||
"--os-auth-url or via env[OS_AUTH_URL]"
|
||||
)
|
||||
|
||||
def main(self, argv):
|
||||
|
||||
# NOTE(Christoph Jansen): With Python 3.4 argv somehow becomes a Map.
|
||||
# This hack fixes it.
|
||||
argv = list(argv)
|
||||
|
||||
# Parse args once to find version and debug settings
|
||||
parser = self.get_base_parser()
|
||||
(options, args) = parser.parse_known_args(argv)
|
||||
self.setup_debugging(options.debug)
|
||||
|
||||
# NOTE(dtroyer): Hackery to handle --endpoint_type due to argparse
|
||||
# thinking usage-list --end is ambiguous; but it
|
||||
# works fine with only --endpoint-type present
|
||||
# Go figure.
|
||||
if '--endpoint_type' in argv:
|
||||
spot = argv.index('--endpoint_type')
|
||||
argv[spot] = '--endpoint-type'
|
||||
|
||||
# build available subcommands based on version
|
||||
(api_major_version, magnum_api_version) = (
|
||||
self._check_version(options.magnum_api_version))
|
||||
|
||||
subcommand_parser = (
|
||||
self.get_subcommand_parser(api_major_version)
|
||||
)
|
||||
self.parser = subcommand_parser
|
||||
|
||||
if options.help or not argv:
|
||||
subcommand_parser.print_help()
|
||||
return 0
|
||||
|
||||
args = subcommand_parser.parse_args(argv)
|
||||
|
||||
# Short-circuit and deal with help right away.
|
||||
# NOTE(jamespage): args.func is not guaranteed with python >= 3.4
|
||||
if not hasattr(args, 'func') or args.func == self.do_help:
|
||||
self.do_help(args)
|
||||
return 0
|
||||
elif args.func == self.do_bash_completion:
|
||||
self.do_bash_completion(args)
|
||||
return 0
|
||||
|
||||
if not args.service_type:
|
||||
args.service_type = DEFAULT_SERVICE_TYPE
|
||||
|
||||
if args.bypass_url:
|
||||
args.os_endpoint_override = args.bypass_url
|
||||
|
||||
args.os_project_id = (args.os_project_id or args.os_tenant_id)
|
||||
args.os_project_name = (args.os_project_name or args.os_tenant_name)
|
||||
|
||||
self._ensure_auth_info(args)
|
||||
|
||||
try:
|
||||
client = {
|
||||
'1': client_v1,
|
||||
}[api_major_version]
|
||||
except KeyError:
|
||||
client = client_v1
|
||||
|
||||
args.os_endpoint_type = (args.os_endpoint_type or args.endpoint_type)
|
||||
if args.os_endpoint_type:
|
||||
args.os_interface = args.os_endpoint_type
|
||||
|
||||
if args.os_interface.endswith('URL'):
|
||||
args.os_interface = args.os_interface[:-3]
|
||||
|
||||
kwargs = {}
|
||||
if profiler:
|
||||
kwargs["profile"] = args.profile
|
||||
|
||||
self.cs = client.Client(
|
||||
cloud=args.os_cloud,
|
||||
user_id=args.os_user_id,
|
||||
username=args.os_username,
|
||||
password=args.os_password,
|
||||
auth_token=args.os_token,
|
||||
project_id=args.os_project_id,
|
||||
project_name=args.os_project_name,
|
||||
user_domain_id=args.os_user_domain_id,
|
||||
user_domain_name=args.os_user_domain_name,
|
||||
project_domain_id=args.os_project_domain_id,
|
||||
project_domain_name=args.os_project_domain_name,
|
||||
auth_url=args.os_auth_url,
|
||||
service_type=args.service_type,
|
||||
region_name=args.os_region_name,
|
||||
magnum_url=args.os_endpoint_override,
|
||||
interface=args.os_interface,
|
||||
insecure=args.insecure,
|
||||
api_version=args.magnum_api_version,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
self._check_deprecation(args.func, argv)
|
||||
try:
|
||||
args.func(self.cs, args)
|
||||
except (cliutils.DuplicateArgs, cliutils.MissingArgs):
|
||||
self.do_help(args)
|
||||
raise
|
||||
|
||||
if profiler and args.profile:
|
||||
trace_id = profiler.get().get_base_id()
|
||||
print("To display trace use the command:\n\n"
|
||||
" osprofiler trace show --html %s " % trace_id)
|
||||
|
||||
def _check_deprecation(self, func, argv):
|
||||
if not hasattr(func, 'deprecated_groups'):
|
||||
return
|
||||
|
||||
for (old_info, new_info, required) in func.deprecated_groups:
|
||||
old_param = old_info[0][0]
|
||||
new_param = new_info[0][0]
|
||||
old_value, new_value = None, None
|
||||
for i in range(len(argv)):
|
||||
cur_arg = argv[i]
|
||||
if cur_arg == old_param:
|
||||
old_value = argv[i + 1]
|
||||
elif cur_arg == new_param[0]:
|
||||
new_value = argv[i + 1]
|
||||
|
||||
if old_value and not new_value:
|
||||
print(
|
||||
'WARNING: The %s parameter is deprecated and will be '
|
||||
'removed in a future release. Use the %s parameter to '
|
||||
'avoid seeing this message.'
|
||||
% (old_param, new_param))
|
||||
|
||||
def _dump_timings(self, timings):
|
||||
class Tyme(object):
|
||||
def __init__(self, url, seconds):
|
||||
self.url = url
|
||||
self.seconds = seconds
|
||||
results = [Tyme(url, end - start) for url, start, end in timings]
|
||||
total = 0.0
|
||||
for tyme in results:
|
||||
total += tyme.seconds
|
||||
results.append(Tyme("Total", total))
|
||||
cliutils.print_list(results, ["url", "seconds"], sortby_index=None)
|
||||
|
||||
def do_bash_completion(self, _args):
|
||||
"""Prints arguments for bash-completion.
|
||||
|
||||
Prints all of the commands and options to stdout so that the
|
||||
magnum.bash_completion script doesn't have to hard code them.
|
||||
"""
|
||||
commands = set()
|
||||
options = set()
|
||||
for sc_str, sc in self.subcommands.items():
|
||||
commands.add(sc_str)
|
||||
for option in sc._optionals._option_string_actions.keys():
|
||||
options.add(option)
|
||||
|
||||
commands.remove('bash-completion')
|
||||
commands.remove('bash_completion')
|
||||
print(' '.join(commands | options))
|
||||
|
||||
@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."""
|
||||
# NOTE(jamespage): args.command is not guaranteed with python >= 3.4
|
||||
command = getattr(args, 'command', '')
|
||||
|
||||
if 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()
|
||||
|
||||
|
||||
# I'm picky about my shell help.
|
||||
class OpenStackHelpFormatter(argparse.HelpFormatter):
|
||||
def start_section(self, heading):
|
||||
# Title-case the headings
|
||||
heading = '%s%s' % (heading[0].upper(), heading[1:])
|
||||
super(OpenStackHelpFormatter, self).start_section(heading)
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
OpenStackMagnumShell().main(map(encodeutils.safe_decode, sys.argv[1:]))
|
||||
|
||||
except Exception as e:
|
||||
logger.debug(e, exc_info=1)
|
||||
print("ERROR: %s" % encodeutils.safe_encode(six.text_type(e)),
|
||||
file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -1,54 +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.
|
||||
|
||||
import os
|
||||
|
||||
import fixtures
|
||||
import testtools
|
||||
|
||||
_TRUE_VALUES = ('true', '1', 'yes')
|
||||
|
||||
|
||||
class TestCase(testtools.TestCase):
|
||||
|
||||
"""Test case base class for all unit tests."""
|
||||
|
||||
def setUp(self):
|
||||
"""Run before each test method to initialize test environment."""
|
||||
|
||||
super(TestCase, self).setUp()
|
||||
test_timeout = os.environ.get('OS_TEST_TIMEOUT', 60)
|
||||
try:
|
||||
test_timeout = int(test_timeout)
|
||||
except ValueError:
|
||||
# If timeout value is invalid, set a default timeout.
|
||||
test_timeout = 60
|
||||
if test_timeout <= 0:
|
||||
test_timeout = 60
|
||||
self.useFixture(fixtures.Timeout(test_timeout, gentle=True))
|
||||
|
||||
self.useFixture(fixtures.NestedTempfile())
|
||||
self.useFixture(fixtures.TempHomeDir())
|
||||
|
||||
if os.environ.get('OS_STDOUT_CAPTURE') in _TRUE_VALUES:
|
||||
stdout = self.useFixture(fixtures.StringStream('stdout')).stream
|
||||
self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
|
||||
if os.environ.get('OS_STDERR_CAPTURE') in _TRUE_VALUES:
|
||||
stderr = self.useFixture(fixtures.StringStream('stderr')).stream
|
||||
self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
|
||||
|
||||
self.log_fixture = self.useFixture(fixtures.FakeLogger())
|
|
@ -1,27 +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.
|
||||
#
|
||||
|
||||
import argparse
|
||||
import mock
|
||||
|
||||
from openstackclient.tests import utils
|
||||
|
||||
|
||||
class TestMagnumClientOSCV1(utils.TestCommand):
|
||||
|
||||
def setUp(self):
|
||||
super(TestMagnumClientOSCV1, self).setUp()
|
||||
self.namespace = argparse.Namespace()
|
||||
self.app.client_manager.session = mock.Mock()
|
||||
self.app.client_manager.magnumclient = mock.Mock()
|
||||
self.magnumclient = self.app.client_manager.magnumclient
|
|
@ -1,38 +0,0 @@
|
|||
# Copyright (c) 2015 IBM Corp.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import mock
|
||||
import testtools
|
||||
|
||||
from magnumclient import client
|
||||
|
||||
|
||||
class ClientTest(testtools.TestCase):
|
||||
|
||||
@mock.patch('magnumclient.v1.client.Client')
|
||||
def test_no_version_argument(self, mock_magnum_client):
|
||||
client.Client(auth_token='mytoken', magnum_url='http://myurl/')
|
||||
mock_magnum_client.assert_called_with(
|
||||
auth_token='mytoken', magnum_url='http://myurl/')
|
||||
|
||||
@mock.patch('magnumclient.v1.client.Client')
|
||||
def test_valid_version_argument(self, mock_magnum_client):
|
||||
client.Client(version='1', magnum_url='http://myurl/')
|
||||
mock_magnum_client.assert_called_with(magnum_url='http://myurl/')
|
||||
|
||||
@mock.patch('magnumclient.v1.client.Client')
|
||||
def test_invalid_version_argument(self, mock_magnum_client):
|
||||
self.assertRaises(
|
||||
ValueError,
|
||||
client.Client, version='2', magnum_url='http://myurl/')
|
|
@ -1,458 +0,0 @@
|
|||
# Copyright 2015 OpenStack LLC.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import json
|
||||
|
||||
import mock
|
||||
import six
|
||||
import socket
|
||||
|
||||
from magnumclient.common.apiclient.exceptions import GatewayTimeout
|
||||
from magnumclient.common.apiclient.exceptions import MultipleChoices
|
||||
from magnumclient.common import httpclient as http
|
||||
from magnumclient import exceptions as exc
|
||||
from magnumclient.tests import utils
|
||||
|
||||
NORMAL_ERROR = 0
|
||||
ERROR_DICT = 1
|
||||
ERROR_LIST_WITH_DETAIL = 2
|
||||
ERROR_LIST_WITH_DESC = 3
|
||||
|
||||
|
||||
def _get_error_body(faultstring=None, debuginfo=None, err_type=NORMAL_ERROR):
|
||||
if err_type == NORMAL_ERROR:
|
||||
error_body = {
|
||||
'faultstring': faultstring,
|
||||
'debuginfo': debuginfo
|
||||
}
|
||||
raw_error_body = json.dumps(error_body)
|
||||
body = {'error_message': raw_error_body}
|
||||
elif err_type == ERROR_DICT:
|
||||
body = {'error': {'title': faultstring, 'message': debuginfo}}
|
||||
elif err_type == ERROR_LIST_WITH_DETAIL:
|
||||
main_body = {'title': faultstring, 'detail': debuginfo}
|
||||
body = {'errors': [main_body]}
|
||||
elif err_type == ERROR_LIST_WITH_DESC:
|
||||
main_body = {'title': faultstring, 'description': debuginfo}
|
||||
body = {'errors': [main_body]}
|
||||
raw_body = json.dumps(body)
|
||||
return raw_body
|
||||
|
||||
|
||||
HTTP_CLASS = six.moves.http_client.HTTPConnection
|
||||
HTTPS_CLASS = http.VerifiedHTTPSConnection
|
||||
DEFAULT_TIMEOUT = 600
|
||||
|
||||
|
||||
class HttpClientTest(utils.BaseTestCase):
|
||||
|
||||
def test_url_generation_trailing_slash_in_base(self):
|
||||
client = http.HTTPClient('http://localhost/')
|
||||
url = client._make_connection_url('/v1/resources')
|
||||
self.assertEqual('/v1/resources', url)
|
||||
|
||||
def test_url_generation_without_trailing_slash_in_base(self):
|
||||
client = http.HTTPClient('http://localhost')
|
||||
url = client._make_connection_url('/v1/resources')
|
||||
self.assertEqual('/v1/resources', url)
|
||||
|
||||
def test_url_generation_prefix_slash_in_path(self):
|
||||
client = http.HTTPClient('http://localhost/')
|
||||
url = client._make_connection_url('/v1/resources')
|
||||
self.assertEqual('/v1/resources', url)
|
||||
|
||||
def test_url_generation_without_prefix_slash_in_path(self):
|
||||
client = http.HTTPClient('http://localhost')
|
||||
url = client._make_connection_url('v1/resources')
|
||||
self.assertEqual('/v1/resources', url)
|
||||
|
||||
def test_server_exception_empty_body(self):
|
||||
error_body = _get_error_body()
|
||||
fake_resp = utils.FakeResponse({'content-type': 'application/json'},
|
||||
six.StringIO(error_body),
|
||||
version=1,
|
||||
status=500)
|
||||
client = http.HTTPClient('http://localhost/')
|
||||
client.get_connection = (
|
||||
lambda *a, **kw: utils.FakeConnection(fake_resp))
|
||||
|
||||
error = self.assertRaises(exc.InternalServerError,
|
||||
client.json_request,
|
||||
'GET', '/v1/resources')
|
||||
self.assertEqual('Internal Server Error (HTTP 500)', str(error))
|
||||
|
||||
def test_server_exception_msg_only(self):
|
||||
error_msg = 'test error msg'
|
||||
error_body = _get_error_body(error_msg, err_type=ERROR_DICT)
|
||||
fake_resp = utils.FakeResponse({'content-type': 'application/json'},
|
||||
six.StringIO(error_body),
|
||||
version=1,
|
||||
status=500)
|
||||
client = http.HTTPClient('http://localhost/')
|
||||
client.get_connection = (
|
||||
lambda *a, **kw: utils.FakeConnection(fake_resp))
|
||||
|
||||
error = self.assertRaises(exc.InternalServerError,
|
||||
client.json_request,
|
||||
'GET', '/v1/resources')
|
||||
self.assertEqual(error_msg + ' (HTTP 500)', str(error))
|
||||
|
||||
def test_server_exception_msg_and_traceback(self):
|
||||
error_msg = 'another test error'
|
||||
error_trace = ("\"Traceback (most recent call last):\\n\\n "
|
||||
"File \\\"/usr/local/lib/python2.7/...")
|
||||
error_body = _get_error_body(error_msg, error_trace,
|
||||
ERROR_LIST_WITH_DESC)
|
||||
fake_resp = utils.FakeResponse({'content-type': 'application/json'},
|
||||
six.StringIO(error_body),
|
||||
version=1,
|
||||
status=500)
|
||||
client = http.HTTPClient('http://localhost/')
|
||||
client.get_connection = (
|
||||
lambda *a, **kw: utils.FakeConnection(fake_resp))
|
||||
|
||||
error = self.assertRaises(exc.InternalServerError,
|
||||
client.json_request,
|
||||
'GET', '/v1/resources')
|
||||
|
||||
self.assertEqual(
|
||||
'%(error)s (HTTP 500)\n%(trace)s' % {'error': error_msg,
|
||||
'trace': error_trace},
|
||||
"%(error)s\n%(details)s" % {'error': str(error),
|
||||
'details': str(error.details)})
|
||||
|
||||
def test_server_exception_address(self):
|
||||
endpoint = 'https://magnum-host:6385'
|
||||
client = http.HTTPClient(endpoint, token='foobar', insecure=True,
|
||||
ca_file='/path/to/ca_file')
|
||||
client.get_connection = (
|
||||
lambda *a, **kw: utils.FakeConnection(exc=socket.gaierror))
|
||||
|
||||
self.assertRaises(exc.EndpointNotFound, client.json_request,
|
||||
'GET', '/v1/resources', body='farboo')
|
||||
|
||||
def test_server_exception_socket(self):
|
||||
client = http.HTTPClient('http://localhost/', token='foobar')
|
||||
client.get_connection = (
|
||||
lambda *a, **kw: utils.FakeConnection(exc=socket.error))
|
||||
|
||||
self.assertRaises(exc.ConnectionRefused, client.json_request,
|
||||
'GET', '/v1/resources')
|
||||
|
||||
def test_server_exception_endpoint(self):
|
||||
endpoint = 'https://magnum-host:6385'
|
||||
client = http.HTTPClient(endpoint, token='foobar', insecure=True,
|
||||
ca_file='/path/to/ca_file')
|
||||
client.get_connection = (
|
||||
lambda *a, **kw: utils.FakeConnection(exc=socket.gaierror))
|
||||
|
||||
self.assertRaises(exc.EndpointNotFound, client.json_request,
|
||||
'GET', '/v1/resources', body='farboo')
|
||||
|
||||
def test_get_connection(self):
|
||||
endpoint = 'https://magnum-host:6385'
|
||||
client = http.HTTPClient(endpoint)
|
||||
conn = client.get_connection()
|
||||
self.assertTrue(conn, http.VerifiedHTTPSConnection)
|
||||
|
||||
def test_get_connection_exception(self):
|
||||
endpoint = 'http://magnum-host:6385/'
|
||||
expected = (HTTP_CLASS,
|
||||
('magnum-host', 6385, ''),
|
||||
{'timeout': DEFAULT_TIMEOUT})
|
||||
params = http.HTTPClient.get_connection_params(endpoint)
|
||||
self.assertEqual(expected, params)
|
||||
|
||||
def test_get_connection_params_with_ssl(self):
|
||||
endpoint = 'https://magnum-host:6385'
|
||||
expected = (HTTPS_CLASS,
|
||||
('magnum-host', 6385, ''),
|
||||
{
|
||||
'timeout': DEFAULT_TIMEOUT,
|
||||
'ca_file': None,
|
||||
'cert_file': None,
|
||||
'key_file': None,
|
||||
'insecure': False,
|
||||
})
|
||||
params = http.HTTPClient.get_connection_params(endpoint)
|
||||
self.assertEqual(expected, params)
|
||||
|
||||
def test_get_connection_params_with_ssl_params(self):
|
||||
endpoint = 'https://magnum-host:6385'
|
||||
ssl_args = {
|
||||
'ca_file': '/path/to/ca_file',
|
||||
'cert_file': '/path/to/cert_file',
|
||||
'key_file': '/path/to/key_file',
|
||||
'insecure': True,
|
||||
}
|
||||
|
||||
expected_kwargs = {'timeout': DEFAULT_TIMEOUT}
|
||||
expected_kwargs.update(ssl_args)
|
||||
expected = (HTTPS_CLASS,
|
||||
('magnum-host', 6385, ''),
|
||||
expected_kwargs)
|
||||
params = http.HTTPClient.get_connection_params(endpoint, **ssl_args)
|
||||
self.assertEqual(expected, params)
|
||||
|
||||
def test_get_connection_params_with_timeout(self):
|
||||
endpoint = 'http://magnum-host:6385'
|
||||
expected = (HTTP_CLASS,
|
||||
('magnum-host', 6385, ''),
|
||||
{'timeout': 300.0})
|
||||
params = http.HTTPClient.get_connection_params(endpoint, timeout=300)
|
||||
self.assertEqual(expected, params)
|
||||
|
||||
def test_get_connection_params_with_version(self):
|
||||
endpoint = 'http://magnum-host:6385/v1'
|
||||
expected = (HTTP_CLASS,
|
||||
('magnum-host', 6385, ''),
|
||||
{'timeout': DEFAULT_TIMEOUT})
|
||||
params = http.HTTPClient.get_connection_params(endpoint)
|
||||
self.assertEqual(expected, params)
|
||||
|
||||
def test_get_connection_params_with_version_trailing_slash(self):
|
||||
endpoint = 'http://magnum-host:6385/v1/'
|
||||
expected = (HTTP_CLASS,
|
||||
('magnum-host', 6385, ''),
|
||||
{'timeout': DEFAULT_TIMEOUT})
|
||||
params = http.HTTPClient.get_connection_params(endpoint)
|
||||
self.assertEqual(expected, params)
|
||||
|
||||
def test_get_connection_params_with_subpath(self):
|
||||
endpoint = 'http://magnum-host:6385/magnum'
|
||||
expected = (HTTP_CLASS,
|
||||
('magnum-host', 6385, '/magnum'),
|
||||
{'timeout': DEFAULT_TIMEOUT})
|
||||
params = http.HTTPClient.get_connection_params(endpoint)
|
||||
self.assertEqual(expected, params)
|
||||
|
||||
def test_get_connection_params_with_subpath_trailing_slash(self):
|
||||
endpoint = 'http://magnum-host:6385/magnum/'
|
||||
expected = (HTTP_CLASS,
|
||||
('magnum-host', 6385, '/magnum'),
|
||||
{'timeout': DEFAULT_TIMEOUT})
|
||||
params = http.HTTPClient.get_connection_params(endpoint)
|
||||
self.assertEqual(expected, params)
|
||||
|
||||
def test_get_connection_params_with_subpath_version(self):
|
||||
endpoint = 'http://magnum-host:6385/magnum/v1'
|
||||
expected = (HTTP_CLASS,
|
||||
('magnum-host', 6385, '/magnum'),
|
||||
{'timeout': DEFAULT_TIMEOUT})
|
||||
params = http.HTTPClient.get_connection_params(endpoint)
|
||||
self.assertEqual(expected, params)
|
||||
|
||||
def test_get_connection_params_with_subpath_version_trailing_slash(self):
|
||||
endpoint = 'http://magnum-host:6385/magnum/v1/'
|
||||
expected = (HTTP_CLASS,
|
||||
('magnum-host', 6385, '/magnum'),
|
||||
{'timeout': DEFAULT_TIMEOUT})
|
||||
params = http.HTTPClient.get_connection_params(endpoint)
|
||||
self.assertEqual(expected, params)
|
||||
|
||||
def test_get_connection_params_with_unsupported_scheme(self):
|
||||
endpoint = 'foo://magnum-host:6385/magnum/v1/'
|
||||
self.assertRaises(exc.EndpointException,
|
||||
http.HTTPClient.get_connection_params, endpoint)
|
||||
|
||||
def test_401_unauthorized_exception(self):
|
||||
error_body = _get_error_body(err_type=ERROR_LIST_WITH_DETAIL)
|
||||
fake_resp = utils.FakeResponse({'content-type': 'text/plain'},
|
||||
six.StringIO(error_body),
|
||||
version=1,
|
||||
status=401)
|
||||
client = http.HTTPClient('http://localhost/')
|
||||
client.get_connection = (lambda *a,
|
||||
**kw: utils.FakeConnection(fake_resp))
|
||||
|
||||
self.assertRaises(exc.Unauthorized, client.json_request,
|
||||
'GET', '/v1/resources')
|
||||
|
||||
def test_server_redirect_exception(self):
|
||||
fake_redirect_resp = utils.FakeResponse(
|
||||
{'content-type': 'application/octet-stream'},
|
||||
'foo', version=1, status=301)
|
||||
fake_resp = utils.FakeResponse(
|
||||
{'content-type': 'application/octet-stream'},
|
||||
'bar', version=1, status=300)
|
||||
client = http.HTTPClient('http://localhost/')
|
||||
conn = utils.FakeConnection(fake_redirect_resp,
|
||||
redirect_resp=fake_resp)
|
||||
client.get_connection = (lambda *a, **kw: conn)
|
||||
|
||||
self.assertRaises(MultipleChoices, client.json_request,
|
||||
'GET', '/v1/resources')
|
||||
|
||||
def test_server_body_undecode_json(self):
|
||||
err = "foo"
|
||||
fake_resp = utils.FakeResponse(
|
||||
{'content-type': 'application/json'},
|
||||
six.StringIO(err), version=1, status=200)
|
||||
client = http.HTTPClient('http://localhost/')
|
||||
conn = utils.FakeConnection(fake_resp)
|
||||
client.get_connection = (lambda *a, **kw: conn)
|
||||
|
||||
resp, body = client.json_request('GET', '/v1/resources')
|
||||
|
||||
self.assertEqual(resp, fake_resp)
|
||||
self.assertEqual(err, body)
|
||||
|
||||
def test_server_success_body_app(self):
|
||||
fake_resp = utils.FakeResponse(
|
||||
{'content-type': 'application/octet-stream'},
|
||||
'bar', version=1, status=200)
|
||||
client = http.HTTPClient('http://localhost/')
|
||||
conn = utils.FakeConnection(fake_resp)
|
||||
client.get_connection = (lambda *a, **kw: conn)
|
||||
|
||||
resp, body = client.json_request('GET', '/v1/resources')
|
||||
|
||||
self.assertEqual(resp, fake_resp)
|
||||
self.assertIsNone(body)
|
||||
|
||||
def test_server_success_body_none(self):
|
||||
fake_resp = utils.FakeResponse(
|
||||
{'content-type': None},
|
||||
six.StringIO('bar'), version=1, status=200)
|
||||
client = http.HTTPClient('http://localhost/')
|
||||
conn = utils.FakeConnection(fake_resp)
|
||||
client.get_connection = (lambda *a, **kw: conn)
|
||||
|
||||
resp, body = client.json_request('GET', '/v1/resources')
|
||||
|
||||
self.assertEqual(resp, fake_resp)
|
||||
self.assertIsInstance(body, list)
|
||||
|
||||
def test_server_success_body_json(self):
|
||||
err = _get_error_body()
|
||||
fake_resp = utils.FakeResponse(
|
||||
{'content-type': 'application/json'},
|
||||
six.StringIO(err), version=1, status=200)
|
||||
client = http.HTTPClient('http://localhost/')
|
||||
conn = utils.FakeConnection(fake_resp)
|
||||
client.get_connection = (lambda *a, **kw: conn)
|
||||
|
||||
resp, body = client.json_request('GET', '/v1/resources')
|
||||
|
||||
self.assertEqual(resp, fake_resp)
|
||||
self.assertEqual(json.dumps(body), err)
|
||||
|
||||
def test_raw_request(self):
|
||||
fake_resp = utils.FakeResponse(
|
||||
{'content-type': 'application/octet-stream'},
|
||||
'bar', version=1, status=200)
|
||||
client = http.HTTPClient('http://localhost/')
|
||||
conn = utils.FakeConnection(fake_resp)
|
||||
client.get_connection = (lambda *a, **kw: conn)
|
||||
|
||||
resp, body = client.raw_request('GET', '/v1/resources')
|
||||
|
||||
self.assertEqual(resp, fake_resp)
|
||||
self.assertIsInstance(body, http.ResponseBodyIterator)
|
||||
|
||||
|
||||
class SessionClientTest(utils.BaseTestCase):
|
||||
|
||||
def test_server_exception_msg_and_traceback(self):
|
||||
error_msg = 'another test error'
|
||||
error_trace = ("\"Traceback (most recent call last):\\n\\n "
|
||||
"File \\\"/usr/local/lib/python2.7/...")
|
||||
error_body = _get_error_body(error_msg, error_trace)
|
||||
|
||||
fake_session = utils.FakeSession({'Content-Type': 'application/json'},
|
||||
error_body,
|
||||
500)
|
||||
|
||||
client = http.SessionClient(session=fake_session)
|
||||
|
||||
error = self.assertRaises(exc.InternalServerError,
|
||||
client.json_request,
|
||||
'GET', '/v1/resources')
|
||||
|
||||
self.assertEqual(
|
||||
'%(error)s (HTTP 500)\n%(trace)s' % {'error': error_msg,
|
||||
'trace': error_trace},
|
||||
"%(error)s\n%(details)s" % {'error': str(error),
|
||||
'details': str(error.details)})
|
||||
|
||||
def test_server_exception_empty_body(self):
|
||||
error_body = _get_error_body()
|
||||
|
||||
fake_session = utils.FakeSession({'Content-Type': 'application/json'},
|
||||
error_body,
|
||||
500)
|
||||
|
||||
client = http.SessionClient(session=fake_session)
|
||||
|
||||
error = self.assertRaises(exc.InternalServerError,
|
||||
client.json_request,
|
||||
'GET', '/v1/resources')
|
||||
|
||||
self.assertEqual('Internal Server Error (HTTP 500)', str(error))
|
||||
|
||||
def test_bypass_url(self):
|
||||
fake_response = utils.FakeSessionResponse(
|
||||
{}, content="", status_code=201)
|
||||
fake_session = mock.MagicMock()
|
||||
fake_session.request.side_effect = [fake_response]
|
||||
|
||||
client = http.SessionClient(
|
||||
session=fake_session, endpoint_override='http://magnum')
|
||||
|
||||
client.json_request('GET', '/v1/bays')
|
||||
self.assertEqual(
|
||||
fake_session.request.call_args[1]['endpoint_override'],
|
||||
'http://magnum'
|
||||
)
|
||||
|
||||
def test_exception(self):
|
||||
fake_response = utils.FakeSessionResponse(
|
||||
{}, content="", status_code=504)
|
||||
fake_session = mock.MagicMock()
|
||||
fake_session.request.side_effect = [fake_response]
|
||||
client = http.SessionClient(
|
||||
session=fake_session, endpoint_override='http://magnum')
|
||||
self.assertRaises(GatewayTimeout,
|
||||
client.json_request,
|
||||
'GET', '/v1/resources')
|
||||
|
||||
def test_construct_http_client_return_httpclient(self):
|
||||
client = http._construct_http_client('http://localhost/')
|
||||
|
||||
self.assertIsInstance(client, http.HTTPClient)
|
||||
|
||||
def test_construct_http_client_return_sessionclient(self):
|
||||
fake_session = mock.MagicMock()
|
||||
client = http._construct_http_client(session=fake_session)
|
||||
|
||||
self.assertIsInstance(client, http.SessionClient)
|
||||
|
||||
def test_raw_request(self):
|
||||
fake_response = utils.FakeSessionResponse(
|
||||
{'content-type': 'application/octet-stream'},
|
||||
content="", status_code=200)
|
||||
fake_session = mock.MagicMock()
|
||||
fake_session.request.side_effect = [fake_response]
|
||||
|
||||
client = http.SessionClient(
|
||||
session=fake_session, endpoint_override='http://magnum')
|
||||
|
||||
resp = client.raw_request('GET', '/v1/bays')
|
||||
|
||||
self.assertEqual(
|
||||
fake_session.request.call_args[1]['headers']['Content-Type'],
|
||||
'application/octet-stream'
|
||||
)
|
||||
self.assertEqual(fake_response, resp)
|
|
@ -1,27 +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.
|
||||
|
||||
"""
|
||||
test_magnumclient
|
||||
----------------------------------
|
||||
Tests for `magnumclient` module.
|
||||
"""
|
||||
|
||||
from magnumclient.tests import base
|
||||
|
||||
|
||||
class TestMagnumclient(base.TestCase):
|
||||
|
||||
def test_something(self):
|
||||
pass
|
|
@ -1,350 +0,0 @@
|
|||
# Copyright 2015 NEC Corporation. All rights reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import re
|
||||
import sys
|
||||
|
||||
import argparse
|
||||
import fixtures
|
||||
from keystoneauth1 import fixture
|
||||
import mock
|
||||
import six
|
||||
from testtools import matchers
|
||||
|
||||
from magnumclient import exceptions
|
||||
import magnumclient.shell
|
||||
from magnumclient.tests import utils
|
||||
|
||||
FAKE_ENV = {'OS_USERNAME': 'username',
|
||||
'OS_PASSWORD': 'password',
|
||||
'OS_PROJECT_NAME': 'project_name',
|
||||
'OS_AUTH_URL': 'http://no.where/v2.0'}
|
||||
|
||||
FAKE_ENV2 = {'OS_USER_ID': 'user_id',
|
||||
'OS_PASSWORD': 'password',
|
||||
'OS_PROJECT_ID': 'project_id',
|
||||
'OS_AUTH_URL': 'http://no.where/v2.0'}
|
||||
|
||||
FAKE_ENV3 = {'OS_USERNAME': 'username',
|
||||
'OS_PASSWORD': 'password',
|
||||
'OS_PROJECT_ID': 'project_id',
|
||||
'OS_AUTH_URL': 'http://no.where/v2.0'}
|
||||
|
||||
FAKE_ENV4 = {'OS_USERNAME': 'username',
|
||||
'OS_PASSWORD': 'password',
|
||||
'OS_PROJECT_ID': 'project_id',
|
||||
'OS_USER_DOMAIN_NAME': 'Default',
|
||||
'OS_PROJECT_DOMAIN_NAME': 'Default',
|
||||
'OS_AUTH_URL': 'http://no.where/v3'}
|
||||
|
||||
|
||||
def _create_ver_list(versions):
|
||||
return {'versions': {'values': versions}}
|
||||
|
||||
|
||||
class ParserTest(utils.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ParserTest, self).setUp()
|
||||
self.parser = magnumclient.shell.MagnumClientArgumentParser()
|
||||
|
||||
def test_ambiguous_option(self):
|
||||
self.parser.add_argument('--tic')
|
||||
self.parser.add_argument('--tac')
|
||||
try:
|
||||
self.parser.parse_args(['--t'])
|
||||
except SystemExit as err:
|
||||
self.assertEqual(2, err.code)
|
||||
else:
|
||||
self.fail('SystemExit not raised')
|
||||
|
||||
|
||||
class ShellTest(utils.TestCase):
|
||||
AUTH_URL = utils.FAKE_ENV['OS_AUTH_URL']
|
||||
|
||||
_msg_no_tenant_project = ("You must provide a project name or project id"
|
||||
" via --os-project-name, --os-project-id,"
|
||||
" env[OS_PROJECT_NAME] or env[OS_PROJECT_ID]")
|
||||
|
||||
def setUp(self):
|
||||
super(ShellTest, self).setUp()
|
||||
self.nc_util = mock.patch(
|
||||
'magnumclient.common.cliutils.isunauthenticated').start()
|
||||
self.nc_util.return_value = False
|
||||
|
||||
def test_positive_non_zero_float(self):
|
||||
output = magnumclient.shell.positive_non_zero_float(None)
|
||||
self.assertIsNone(output)
|
||||
|
||||
output = magnumclient.shell.positive_non_zero_float(5)
|
||||
self.assertEqual(5, output)
|
||||
|
||||
output = magnumclient.shell.positive_non_zero_float(5.0)
|
||||
self.assertEqual(5.0, output)
|
||||
|
||||
self.assertRaises(argparse.ArgumentTypeError,
|
||||
magnumclient.shell.positive_non_zero_float,
|
||||
"Invalid")
|
||||
|
||||
self.assertRaises(argparse.ArgumentTypeError,
|
||||
magnumclient.shell.positive_non_zero_float, -1)
|
||||
|
||||
self.assertRaises(argparse.ArgumentTypeError,
|
||||
magnumclient.shell.positive_non_zero_float, 0)
|
||||
|
||||
def test_help_unknown_command(self):
|
||||
self.assertRaises(exceptions.CommandError, self.shell, 'help foofoo')
|
||||
|
||||
def test_help(self):
|
||||
required = [
|
||||
'.*?^usage: ',
|
||||
'.*?^See "magnum help COMMAND" for help on a specific command',
|
||||
]
|
||||
stdout, stderr = self.shell('help')
|
||||
for r in required:
|
||||
self.assertThat((stdout + stderr),
|
||||
matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE))
|
||||
|
||||
def test_help_on_subcommand(self):
|
||||
required = [
|
||||
'.*?^usage: magnum bay-create',
|
||||
'.*?^Create a bay.',
|
||||
'.*?^Optional arguments:',
|
||||
]
|
||||
stdout, stderr = self.shell('help bay-create')
|
||||
for r in required:
|
||||
self.assertThat((stdout + stderr),
|
||||
matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE))
|
||||
|
||||
def test_help_no_options(self):
|
||||
required = [
|
||||
'.*?^usage: ',
|
||||
'.*?^See "magnum help COMMAND" for help on a specific command',
|
||||
]
|
||||
stdout, stderr = self.shell('')
|
||||
for r in required:
|
||||
self.assertThat((stdout + stderr),
|
||||
matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE))
|
||||
|
||||
def test_bash_completion(self):
|
||||
stdout, stderr = self.shell('bash-completion')
|
||||
# just check we have some output
|
||||
required = [
|
||||
'.*help',
|
||||
'.*bay-show',
|
||||
'.*--bay']
|
||||
for r in required:
|
||||
self.assertThat((stdout + stderr),
|
||||
matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE))
|
||||
|
||||
def test_no_username(self):
|
||||
required = ('You must provide a username via'
|
||||
' either --os-username or via env[OS_USERNAME]')
|
||||
self.make_env(exclude='OS_USERNAME')
|
||||
try:
|
||||
self.shell('bay-list')
|
||||
except exceptions.CommandError as message:
|
||||
self.assertEqual(required, message.args[0])
|
||||
else:
|
||||
self.fail('CommandError not raised')
|
||||
|
||||
def test_no_user_id(self):
|
||||
required = ('You must provide a username via'
|
||||
' either --os-username or via env[OS_USERNAME]')
|
||||
self.make_env(exclude='OS_USER_ID', fake_env=FAKE_ENV2)
|
||||
try:
|
||||
self.shell('bay-list')
|
||||
except exceptions.CommandError as message:
|
||||
self.assertEqual(required, message.args[0])
|
||||
else:
|
||||
self.fail('CommandError not raised')
|
||||
|
||||
def test_no_project_name(self):
|
||||
required = self._msg_no_tenant_project
|
||||
self.make_env(exclude='OS_PROJECT_NAME')
|
||||
try:
|
||||
self.shell('bay-list')
|
||||
except exceptions.CommandError as message:
|
||||
self.assertEqual(required, message.args[0])
|
||||
else:
|
||||
self.fail('CommandError not raised')
|
||||
|
||||
def test_no_project_id(self):
|
||||
required = self._msg_no_tenant_project
|
||||
self.make_env(exclude='OS_PROJECT_ID', fake_env=FAKE_ENV3)
|
||||
try:
|
||||
self.shell('bay-list')
|
||||
except exceptions.CommandError as message:
|
||||
self.assertEqual(required, message.args[0])
|
||||
else:
|
||||
self.fail('CommandError not raised')
|
||||
|
||||
def test_no_password(self):
|
||||
required = ('You must provide a password via either --os-password, '
|
||||
'env[OS_PASSWORD], or prompted response')
|
||||
self.make_env(exclude='OS_PASSWORD', fake_env=FAKE_ENV3)
|
||||
try:
|
||||
self.shell('bay-list')
|
||||
except exceptions.CommandError as message:
|
||||
self.assertEqual(required, message.args[0])
|
||||
else:
|
||||
self.fail('CommandError not raised')
|
||||
|
||||
def test_no_auth_url(self):
|
||||
required = ("You must provide an auth url via either "
|
||||
"--os-auth-url or via env[OS_AUTH_URL]")
|
||||
self.make_env(exclude='OS_AUTH_URL')
|
||||
try:
|
||||
self.shell('bay-list')
|
||||
except exceptions.CommandError as message:
|
||||
self.assertEqual(required, message.args[0])
|
||||
else:
|
||||
self.fail('CommandError not raised')
|
||||
|
||||
# FIXME(madhuri) Remove this harcoded v1 Client class.
|
||||
# In future, when a new version of API will
|
||||
# introduce, this needs to be dynamic then.
|
||||
@mock.patch('magnumclient.v1.client.Client')
|
||||
def test_service_type(self, mock_client):
|
||||
self.make_env()
|
||||
self.shell('bay-list')
|
||||
_, client_kwargs = mock_client.call_args_list[0]
|
||||
self.assertEqual('container-infra', client_kwargs['service_type'])
|
||||
|
||||
@mock.patch('magnumclient.v1.bays_shell.do_bay_list')
|
||||
@mock.patch('magnumclient.v1.client.ksa_session')
|
||||
def test_insecure(self, mock_session, mock_bay_list):
|
||||
self.make_env()
|
||||
self.shell('--insecure bay-list')
|
||||
_, session_kwargs = mock_session.Session.call_args_list[0]
|
||||
self.assertEqual(False, session_kwargs['verify'])
|
||||
|
||||
@mock.patch('sys.argv', ['magnum'])
|
||||
@mock.patch('sys.stdout', six.StringIO())
|
||||
@mock.patch('sys.stderr', six.StringIO())
|
||||
def test_main_noargs(self):
|
||||
# Ensure that main works with no command-line arguments
|
||||
try:
|
||||
magnumclient.shell.main()
|
||||
except SystemExit:
|
||||
self.fail('Unexpected SystemExit')
|
||||
|
||||
# We expect the normal usage as a result
|
||||
self.assertIn('Command-line interface to the OpenStack Magnum API',
|
||||
sys.stdout.getvalue())
|
||||
|
||||
def _expected_client_kwargs(self):
|
||||
return {
|
||||
'password': 'password', 'auth_token': None,
|
||||
'auth_url': self.AUTH_URL, 'profile': None,
|
||||
'cloud': None, 'interface': 'public',
|
||||
'insecure': False, 'magnum_url': None,
|
||||
'project_id': None, 'project_name': 'project_name',
|
||||
'project_domain_id': None, 'project_domain_name': None,
|
||||
'region_name': None, 'service_type': 'container-infra',
|
||||
'user_id': None, 'username': 'username',
|
||||
'user_domain_id': None, 'user_domain_name': None,
|
||||
'api_version': 'latest'
|
||||
}
|
||||
|
||||
@mock.patch('magnumclient.v1.client.Client')
|
||||
def _test_main_region(self, command, expected_region_name, mock_client):
|
||||
self.shell(command)
|
||||
expected_args = self._expected_client_kwargs()
|
||||
expected_args['region_name'] = expected_region_name
|
||||
mock_client.assert_called_once_with(**expected_args)
|
||||
|
||||
def test_main_option_region(self):
|
||||
self.make_env()
|
||||
self._test_main_region('--os-region-name=myregion bay-list',
|
||||
'myregion')
|
||||
|
||||
def test_main_env_region(self):
|
||||
fake_env = dict(utils.FAKE_ENV, OS_REGION_NAME='myregion')
|
||||
self.make_env(fake_env=fake_env)
|
||||
self._test_main_region('bay-list', 'myregion')
|
||||
|
||||
def test_main_no_region(self):
|
||||
self.make_env()
|
||||
self._test_main_region('bay-list', None)
|
||||
|
||||
@mock.patch('magnumclient.v1.client.Client')
|
||||
def test_main_endpoint_public(self, mock_client):
|
||||
self.make_env()
|
||||
self.shell('--endpoint-type publicURL bay-list')
|
||||
expected_args = self._expected_client_kwargs()
|
||||
expected_args['interface'] = 'public'
|
||||
mock_client.assert_called_once_with(**expected_args)
|
||||
|
||||
@mock.patch('magnumclient.v1.client.Client')
|
||||
def test_main_endpoint_internal(self, mock_client):
|
||||
self.make_env()
|
||||
self.shell('--endpoint-type internalURL bay-list')
|
||||
expected_args = self._expected_client_kwargs()
|
||||
expected_args['interface'] = 'internal'
|
||||
mock_client.assert_called_once_with(**expected_args)
|
||||
|
||||
@mock.patch('magnumclient.v1.client.Client')
|
||||
def test_main_os_cloud(self, mock_client):
|
||||
expected_cloud = 'default'
|
||||
self.shell('--os-cloud %s bay-list' % expected_cloud)
|
||||
expected_args = self._expected_client_kwargs()
|
||||
expected_args['cloud'] = expected_cloud
|
||||
expected_args['username'] = None
|
||||
expected_args['password'] = None
|
||||
expected_args['project_name'] = None
|
||||
expected_args['auth_url'] = None
|
||||
mock_client.assert_called_once_with(**expected_args)
|
||||
|
||||
@mock.patch('magnumclient.v1.client.Client')
|
||||
def test_main_env_os_cloud(self, mock_client):
|
||||
expected_cloud = 'default'
|
||||
self.make_env(fake_env={'OS_CLOUD': expected_cloud})
|
||||
self.shell('bay-list')
|
||||
expected_args = self._expected_client_kwargs()
|
||||
expected_args['cloud'] = expected_cloud
|
||||
expected_args['username'] = None
|
||||
expected_args['password'] = None
|
||||
expected_args['project_name'] = None
|
||||
expected_args['auth_url'] = None
|
||||
mock_client.assert_called_once_with(**expected_args)
|
||||
|
||||
|
||||
class ShellTestKeystoneV3(ShellTest):
|
||||
AUTH_URL = 'http://no.where/v3'
|
||||
|
||||
def make_env(self, exclude=None, fake_env=FAKE_ENV):
|
||||
if 'OS_AUTH_URL' in fake_env:
|
||||
fake_env.update({'OS_AUTH_URL': self.AUTH_URL})
|
||||
env = dict((k, v) for k, v in fake_env.items() if k != exclude)
|
||||
self.useFixture(fixtures.MonkeyPatch('os.environ', env))
|
||||
|
||||
def register_keystone_discovery_fixture(self, mreq):
|
||||
v3_url = "http://no.where/v3"
|
||||
v3_version = fixture.V3Discovery(v3_url)
|
||||
mreq.register_uri(
|
||||
'GET', v3_url, json=_create_ver_list([v3_version]),
|
||||
status_code=200)
|
||||
|
||||
@mock.patch('magnumclient.v1.client.Client')
|
||||
def test_main_endpoint_public(self, mock_client):
|
||||
self.make_env(fake_env=FAKE_ENV4)
|
||||
self.shell('--endpoint-type publicURL bay-list')
|
||||
expected_args = self._expected_client_kwargs()
|
||||
expected_args['interface'] = 'public'
|
||||
expected_args['project_id'] = 'project_id'
|
||||
expected_args['project_name'] = None
|
||||
expected_args['project_domain_name'] = 'Default'
|
||||
expected_args['user_domain_name'] = 'Default'
|
||||
mock_client.assert_called_once_with(**expected_args)
|
|
@ -1,279 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2013 OpenStack LLC.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import collections
|
||||
import mock
|
||||
from oslo_serialization import jsonutils as json
|
||||
import six
|
||||
import six.moves.builtins as __builtin__
|
||||
import tempfile
|
||||
|
||||
from magnumclient.common import cliutils
|
||||
from magnumclient.common import utils
|
||||
from magnumclient import exceptions as exc
|
||||
from magnumclient.tests import utils as test_utils
|
||||
|
||||
|
||||
class CommonFiltersTest(test_utils.BaseTestCase):
|
||||
def test_limit(self):
|
||||
result = utils.common_filters(limit=42)
|
||||
self.assertEqual(['limit=42'], result)
|
||||
|
||||
def test_limit_0(self):
|
||||
result = utils.common_filters(limit=0)
|
||||
self.assertEqual(['limit=0'], result)
|
||||
|
||||
def test_limit_negative_number(self):
|
||||
result = utils.common_filters(limit=-2)
|
||||
self.assertEqual(['limit=-2'], result)
|
||||
|
||||
def test_other(self):
|
||||
for key in ('marker', 'sort_key', 'sort_dir'):
|
||||
result = utils.common_filters(**{key: 'test'})
|
||||
self.assertEqual(['%s=test' % key], result)
|
||||
|
||||
|
||||
class SplitAndDeserializeTest(test_utils.BaseTestCase):
|
||||
|
||||
def test_split_and_deserialize(self):
|
||||
ret = utils.split_and_deserialize('str=foo')
|
||||
self.assertEqual(('str', 'foo'), ret)
|
||||
|
||||
ret = utils.split_and_deserialize('int=1')
|
||||
self.assertEqual(('int', 1), ret)
|
||||
|
||||
ret = utils.split_and_deserialize('bool=false')
|
||||
self.assertEqual(('bool', False), ret)
|
||||
|
||||
ret = utils.split_and_deserialize('list=[1, "foo", 2]')
|
||||
self.assertEqual(('list', [1, "foo", 2]), ret)
|
||||
|
||||
ret = utils.split_and_deserialize('dict={"foo": 1}')
|
||||
self.assertEqual(('dict', {"foo": 1}), ret)
|
||||
|
||||
ret = utils.split_and_deserialize('str_int="1"')
|
||||
self.assertEqual(('str_int', "1"), ret)
|
||||
|
||||
def test_split_and_deserialize_fail(self):
|
||||
self.assertRaises(exc.CommandError,
|
||||
utils.split_and_deserialize, 'foo:bar')
|
||||
|
||||
|
||||
class ArgsArrayToPatchTest(test_utils.BaseTestCase):
|
||||
|
||||
def test_args_array_to_patch(self):
|
||||
my_args = {
|
||||
'attributes': ['str=foo', 'int=1', 'bool=true',
|
||||
'list=[1, 2, 3]', 'dict={"foo": "bar"}'],
|
||||
'op': 'add',
|
||||
}
|
||||
patch = utils.args_array_to_patch(my_args['op'],
|
||||
my_args['attributes'])
|
||||
self.assertEqual([{'op': 'add', 'value': 'foo', 'path': '/str'},
|
||||
{'op': 'add', 'value': 1, 'path': '/int'},
|
||||
{'op': 'add', 'value': True, 'path': '/bool'},
|
||||
{'op': 'add', 'value': [1, 2, 3], 'path': '/list'},
|
||||
{'op': 'add', 'value': {"foo": "bar"},
|
||||
'path': '/dict'}], patch)
|
||||
|
||||
def test_args_array_to_patch_format_error(self):
|
||||
my_args = {
|
||||
'attributes': ['foobar'],
|
||||
'op': 'add',
|
||||
}
|
||||
self.assertRaises(exc.CommandError, utils.args_array_to_patch,
|
||||
my_args['op'], my_args['attributes'])
|
||||
|
||||
def test_args_array_to_patch_remove(self):
|
||||
my_args = {
|
||||
'attributes': ['/foo', 'extra/bar'],
|
||||
'op': 'remove',
|
||||
}
|
||||
patch = utils.args_array_to_patch(my_args['op'],
|
||||
my_args['attributes'])
|
||||
self.assertEqual([{'op': 'remove', 'path': '/foo'},
|
||||
{'op': 'remove', 'path': '/extra/bar'}], patch)
|
||||
|
||||
def test_args_array_to_patch_invalid_op(self):
|
||||
my_args = {
|
||||
'attributes': ['/foo', 'extra/bar'],
|
||||
'op': 'invalid',
|
||||
}
|
||||
self.assertRaises(exc.CommandError, utils.args_array_to_patch,
|
||||
my_args['op'], my_args['attributes'])
|
||||
|
||||
|
||||
class FormatLabelsTest(test_utils.BaseTestCase):
|
||||
|
||||
def test_format_label_none(self):
|
||||
self.assertEqual({}, utils.format_labels(None))
|
||||
|
||||
def test_format_labels(self):
|
||||
l = utils.format_labels([
|
||||
'K1=V1,K2=V2,'
|
||||
'K3=V3,K4=V4,'
|
||||
'K5=V5'])
|
||||
self.assertEqual({'K1': 'V1',
|
||||
'K2': 'V2',
|
||||
'K3': 'V3',
|
||||
'K4': 'V4',
|
||||
'K5': 'V5'
|
||||
}, l)
|
||||
|
||||
def test_format_labels_semicolon(self):
|
||||
l = utils.format_labels([
|
||||
'K1=V1;K2=V2;'
|
||||
'K3=V3;K4=V4;'
|
||||
'K5=V5'])
|
||||
self.assertEqual({'K1': 'V1',
|
||||
'K2': 'V2',
|
||||
'K3': 'V3',
|
||||
'K4': 'V4',
|
||||
'K5': 'V5'
|
||||
}, l)
|
||||
|
||||
def test_format_labels_mix_commas_semicolon(self):
|
||||
l = utils.format_labels([
|
||||
'K1=V1,K2=V2,'
|
||||
'K3=V3;K4=V4,'
|
||||
'K5=V5'])
|
||||
self.assertEqual({'K1': 'V1',
|
||||
'K2': 'V2',
|
||||
'K3': 'V3',
|
||||
'K4': 'V4',
|
||||
'K5': 'V5'
|
||||
}, l)
|
||||
|
||||
def test_format_labels_split(self):
|
||||
l = utils.format_labels([
|
||||
'K1=V1,'
|
||||
'K2=V22222222222222222222222222222'
|
||||
'222222222222222222222222222,'
|
||||
'K3=3.3.3.3'])
|
||||
self.assertEqual({'K1': 'V1',
|
||||
'K2': 'V22222222222222222222222222222'
|
||||
'222222222222222222222222222',
|
||||
'K3': '3.3.3.3'}, l)
|
||||
|
||||
def test_format_labels_multiple(self):
|
||||
l = utils.format_labels([
|
||||
'K1=V1',
|
||||
'K2=V22222222222222222222222222222'
|
||||
'222222222222222222222222222',
|
||||
'K3=3.3.3.3'])
|
||||
self.assertEqual({'K1': 'V1',
|
||||
'K2': 'V22222222222222222222222222222'
|
||||
'222222222222222222222222222',
|
||||
'K3': '3.3.3.3'}, l)
|
||||
|
||||
def test_format_labels_multiple_colon_values(self):
|
||||
l = utils.format_labels([
|
||||
'K1=V1',
|
||||
'K2=V2,V22,V222,V2222',
|
||||
'K3=3.3.3.3'])
|
||||
self.assertEqual({'K1': 'V1',
|
||||
'K2': 'V2,V22,V222,V2222',
|
||||
'K3': '3.3.3.3'}, l)
|
||||
|
||||
def test_format_labels_parse_comma_false(self):
|
||||
l = utils.format_labels(
|
||||
['K1=V1,K2=2.2.2.2,K=V'],
|
||||
parse_comma=False)
|
||||
self.assertEqual({'K1': 'V1,K2=2.2.2.2,K=V'}, l)
|
||||
|
||||
def test_format_labels_multiple_values_per_labels(self):
|
||||
l = utils.format_labels([
|
||||
'K1=V1',
|
||||
'K1=V2'])
|
||||
self.assertEqual({'K1': 'V1,V2'}, l)
|
||||
|
||||
def test_format_label_special_label(self):
|
||||
labels = ['K1=V1,K22.2.2.2']
|
||||
l = utils.format_labels(
|
||||
labels,
|
||||
parse_comma=True)
|
||||
self.assertEqual({'K1': 'V1,K22.2.2.2'}, l)
|
||||
|
||||
def test_format_multiple_bad_label(self):
|
||||
labels = ['K1=V1', 'K22.2.2.2']
|
||||
ex = self.assertRaises(exc.CommandError,
|
||||
utils.format_labels, labels)
|
||||
self.assertEqual('labels must be a list of KEY=VALUE '
|
||||
'not K22.2.2.2', str(ex))
|
||||
|
||||
|
||||
class CliUtilsTest(test_utils.BaseTestCase):
|
||||
|
||||
def test_keys_and_vals_to_strs(self):
|
||||
dict_in = {six.u('a'): six.u('1'),
|
||||
six.u('b'): {six.u('x'): 1,
|
||||
'y': six.u('2'),
|
||||
six.u('z'): six.u('3')},
|
||||
'c': 7}
|
||||
|
||||
dict_exp = collections.OrderedDict([
|
||||
('a', '1'),
|
||||
('b', collections.OrderedDict([
|
||||
('x', 1),
|
||||
('y', '2'),
|
||||
('z', '3')])),
|
||||
('c', 7)])
|
||||
|
||||
dict_out = cliutils.keys_and_vals_to_strs(dict_in)
|
||||
dict_act = collections.OrderedDict([
|
||||
('a', dict_out['a']),
|
||||
('b', collections.OrderedDict(sorted(dict_out['b'].items()))),
|
||||
('c', dict_out['c'])])
|
||||
|
||||
self.assertEqual(six.text_type(dict_exp), six.text_type(dict_act))
|
||||
|
||||
|
||||
class HandleJsonFromFileTest(test_utils.BaseTestCase):
|
||||
|
||||
def test_handle_json_from_file_bad_json(self):
|
||||
contents = 'foo'
|
||||
with tempfile.NamedTemporaryFile(mode='w') as f:
|
||||
f.write(contents)
|
||||
f.flush()
|
||||
self.assertRaisesRegex(exc.InvalidAttribute,
|
||||
'For JSON',
|
||||
utils.handle_json_from_file, f.name)
|
||||
|
||||
def test_handle_json_from_file_valid_file(self):
|
||||
contents = '{"step": "upgrade", "interface": "deploy"}'
|
||||
|
||||
with tempfile.NamedTemporaryFile(mode='w') as f:
|
||||
f.write(contents)
|
||||
f.flush()
|
||||
steps = utils.handle_json_from_file(f.name)
|
||||
|
||||
self.assertEqual(json.loads(contents), steps)
|
||||
|
||||
@mock.patch.object(__builtin__, 'open', autospec=True)
|
||||
def test_handle_json_from_file_open_fail(self, mock_open):
|
||||
mock_file_object = mock.MagicMock()
|
||||
mock_file_handle = mock.MagicMock()
|
||||
mock_file_handle.__enter__.return_value = mock_file_object
|
||||
mock_open.return_value = mock_file_handle
|
||||
mock_file_object.read.side_effect = IOError
|
||||
|
||||
with tempfile.NamedTemporaryFile(mode='w') as f:
|
||||
self.assertRaisesRegex(exc.InvalidAttribute,
|
||||
"from file",
|
||||
utils.handle_json_from_file, f.name)
|
||||
mock_open.assert_called_once_with(f.name, 'r')
|
||||
mock_file_object.read.assert_called_once_with()
|
|
@ -1,192 +0,0 @@
|
|||
# Copyright 2012 OpenStack LLC.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import copy
|
||||
import datetime
|
||||
import os
|
||||
import sys
|
||||
|
||||
import fixtures
|
||||
import six
|
||||
import testtools
|
||||
|
||||
from magnumclient.common import httpclient as http
|
||||
from magnumclient import shell
|
||||
|
||||
FAKE_ENV = {'OS_USERNAME': 'username',
|
||||
'OS_PASSWORD': 'password',
|
||||
'OS_PROJECT_NAME': 'project_name',
|
||||
'OS_AUTH_URL': 'http://no.where/v2.0'}
|
||||
|
||||
|
||||
class BaseTestCase(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(BaseTestCase, self).setUp()
|
||||
self.useFixture(fixtures.FakeLogger())
|
||||
|
||||
|
||||
class FakeAPI(object):
|
||||
def __init__(self, responses):
|
||||
self.responses = responses
|
||||
self.calls = []
|
||||
|
||||
def _request(self, method, url, headers=None, body=None):
|
||||
call = (method, url, headers or {}, body)
|
||||
self.calls.append(call)
|
||||
return self.responses[url][method]
|
||||
|
||||
def raw_request(self, *args, **kwargs):
|
||||
response = self._request(*args, **kwargs)
|
||||
body_iter = http.ResponseBodyIterator(six.StringIO(response[1]))
|
||||
return FakeResponse(response[0]), body_iter
|
||||
|
||||
def json_request(self, *args, **kwargs):
|
||||
response = self._request(*args, **kwargs)
|
||||
return FakeResponse(response[0]), response[1]
|
||||
|
||||
|
||||
class FakeConnection(object):
|
||||
def __init__(self, response=None, **kwargs):
|
||||
self._response = six.moves.queue.Queue()
|
||||
self._response.put(response)
|
||||
self._last_request = None
|
||||
self._exc = kwargs['exc'] if 'exc' in kwargs else None
|
||||
if 'redirect_resp' in kwargs:
|
||||
self._response.put(kwargs['redirect_resp'])
|
||||
|
||||
def request(self, method, conn_url, **kwargs):
|
||||
self._last_request = (method, conn_url, kwargs)
|
||||
if self._exc:
|
||||
raise self._exc
|
||||
|
||||
def setresponse(self, response):
|
||||
self._response = response
|
||||
|
||||
def getresponse(self):
|
||||
return self._response.get()
|
||||
|
||||
|
||||
class FakeResponse(object):
|
||||
def __init__(self, headers, body=None, version=None, status=None,
|
||||
reason=None):
|
||||
"""Fake object to help testing.
|
||||
|
||||
:param headers: dict representing HTTP response headers
|
||||
:param body: file-like object
|
||||
"""
|
||||
self.headers = headers
|
||||
self.body = body
|
||||
self.version = version
|
||||
self.status = status
|
||||
self.reason = reason
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key is 'location':
|
||||
return 'fake_url'
|
||||
else:
|
||||
return None
|
||||
|
||||
def getheaders(self):
|
||||
return copy.deepcopy(self.headers).items()
|
||||
|
||||
def getheader(self, key, default):
|
||||
return self.headers.get(key, default)
|
||||
|
||||
def read(self, amt):
|
||||
return self.body.read(amt)
|
||||
|
||||
|
||||
class FakeServiceCatalog(object):
|
||||
def url_for(self, endpoint_type, service_type, attr=None,
|
||||
filter_value=None):
|
||||
if attr == 'region' and filter_value:
|
||||
return 'http://regionhost:6385/v1/f14b41234'
|
||||
else:
|
||||
return 'http://localhost:6385/v1/f14b41234'
|
||||
|
||||
|
||||
class FakeKeystone(object):
|
||||
service_catalog = FakeServiceCatalog()
|
||||
timestamp = datetime.datetime.utcnow() + datetime.timedelta(days=5)
|
||||
|
||||
def __init__(self, auth_token):
|
||||
self.auth_token = auth_token
|
||||
self.auth_ref = {
|
||||
'token': {'expires': FakeKeystone.timestamp.strftime(
|
||||
'%Y-%m-%dT%H:%M:%S.%f'),
|
||||
'id': 'd1a541311782870742235'}
|
||||
}
|
||||
|
||||
|
||||
class TestCase(testtools.TestCase):
|
||||
TEST_REQUEST_BASE = {
|
||||
'verify': True,
|
||||
}
|
||||
|
||||
def setUp(self):
|
||||
super(TestCase, self).setUp()
|
||||
if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or
|
||||
os.environ.get('OS_STDOUT_CAPTURE') == '1'):
|
||||
stdout = self.useFixture(fixtures.StringStream('stdout')).stream
|
||||
self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
|
||||
if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or
|
||||
os.environ.get('OS_STDERR_CAPTURE') == '1'):
|
||||
stderr = self.useFixture(fixtures.StringStream('stderr')).stream
|
||||
self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
|
||||
|
||||
def make_env(self, exclude=None, fake_env=FAKE_ENV):
|
||||
env = dict((k, v) for k, v in fake_env.items() if k != exclude)
|
||||
self.useFixture(fixtures.MonkeyPatch('os.environ', env))
|
||||
|
||||
def shell(self, argstr, exitcodes=(0,)):
|
||||
orig = sys.stdout
|
||||
orig_stderr = sys.stderr
|
||||
try:
|
||||
sys.stdout = six.StringIO()
|
||||
sys.stderr = six.StringIO()
|
||||
_shell = shell.OpenStackMagnumShell()
|
||||
_shell.main(argstr.split())
|
||||
except SystemExit:
|
||||
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||
self.assertIn(exc_value.code, exitcodes)
|
||||
finally:
|
||||
stdout = sys.stdout.getvalue()
|
||||
sys.stdout.close()
|
||||
sys.stdout = orig
|
||||
stderr = sys.stderr.getvalue()
|
||||
sys.stderr.close()
|
||||
sys.stderr = orig_stderr
|
||||
return (stdout, stderr)
|
||||
|
||||
|
||||
class FakeSessionResponse(object):
|
||||
|
||||
def __init__(self, headers, content=None, status_code=None):
|
||||
self.headers = headers
|
||||
self.content = content
|
||||
self.status_code = status_code
|
||||
|
||||
|
||||
class FakeSession(object):
|
||||
|
||||
def __init__(self, headers, content=None, status_code=None):
|
||||
self.headers = headers
|
||||
self.content = content
|
||||
self.status_code = status_code
|
||||
|
||||
def request(self, url, method, **kwargs):
|
||||
return FakeSessionResponse(self.headers, self.content,
|
||||
self.status_code)
|
|
@ -1,107 +0,0 @@
|
|||
# Copyright 2015 NEC Corporation. All rights reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import re
|
||||
|
||||
import mock
|
||||
from testtools import matchers
|
||||
|
||||
from magnumclient.tests import utils
|
||||
|
||||
FAKE_ENV = {'OS_USERNAME': 'username',
|
||||
'OS_PASSWORD': 'password',
|
||||
'OS_PROJECT_NAME': 'project_name',
|
||||
'OS_AUTH_URL': 'http://no.where/v2.0',
|
||||
'BYPASS_URL': 'http://magnum'}
|
||||
|
||||
|
||||
class TestCommandLineArgument(utils.TestCase):
|
||||
_unrecognized_arg_error = [
|
||||
'.*?^usage: ',
|
||||
'.*?^error: unrecognized arguments:',
|
||||
".*?^Try 'magnum help ' for more information.",
|
||||
]
|
||||
|
||||
_mandatory_group_arg_error = [
|
||||
'.*?^usage: ',
|
||||
'.*?^error: one of the arguments',
|
||||
".*?^Try 'magnum help ",
|
||||
]
|
||||
|
||||
_too_many_group_arg_error = [
|
||||
'.*?^usage: ',
|
||||
'.*?^error: (argument \-\-[a-z\-]*: not allowed with argument )',
|
||||
".*?^Try 'magnum help ",
|
||||
]
|
||||
|
||||
_mandatory_arg_error = [
|
||||
'.*?^usage: ',
|
||||
'.*?^error: (the following arguments|argument)',
|
||||
".*?^Try 'magnum help ",
|
||||
]
|
||||
|
||||
_duplicate_name_arg_error = [
|
||||
'.*?^usage: ',
|
||||
'.*?^error: (Duplicate "<name>" arguments:)',
|
||||
".*?^Try 'magnum help ",
|
||||
]
|
||||
|
||||
_deprecated_warning = [
|
||||
'.*(WARNING: The \-\-[a-z\-]* parameter is deprecated)+',
|
||||
('.*(Use the [\<\-a-z\-\>]* (positional )*parameter to avoid seeing '
|
||||
'this message)+')
|
||||
]
|
||||
|
||||
_few_argument_error = [
|
||||
'.*?^usage: magnum ',
|
||||
'.*?^error: (the following arguments|too few arguments)',
|
||||
".*?^Try 'magnum help ",
|
||||
]
|
||||
|
||||
_invalid_value_error = [
|
||||
'.*?^usage: ',
|
||||
'.*?^error: argument .*: invalid .* value:',
|
||||
".*?^Try 'magnum help ",
|
||||
]
|
||||
|
||||
_bay_status_error = [
|
||||
'.*?^Bay status for',
|
||||
]
|
||||
|
||||
def setUp(self):
|
||||
super(TestCommandLineArgument, self).setUp()
|
||||
self.make_env(fake_env=FAKE_ENV)
|
||||
session_client = mock.patch(
|
||||
'magnumclient.common.httpclient.SessionClient')
|
||||
session_client.start()
|
||||
loader = mock.patch('keystoneauth1.loading.get_plugin_loader')
|
||||
loader.start()
|
||||
session = mock.patch('keystoneauth1.session.Session')
|
||||
session.start()
|
||||
|
||||
self.addCleanup(session_client.stop)
|
||||
self.addCleanup(loader.stop)
|
||||
self.addCleanup(session.stop)
|
||||
|
||||
def _test_arg_success(self, command, keyword=None):
|
||||
stdout, stderr = self.shell(command)
|
||||
if keyword:
|
||||
self.assertIn(keyword, (stdout + stderr))
|
||||
|
||||
def _test_arg_failure(self, command, error_msg):
|
||||
stdout, stderr = self.shell(command, (2,))
|
||||
for line in error_msg:
|
||||
self.assertThat((stdout + stderr),
|
||||
matchers.MatchesRegex(line,
|
||||
re.DOTALL | re.MULTILINE))
|
|
@ -1,362 +0,0 @@
|
|||
# Copyright 2015 IBM Corp.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import copy
|
||||
|
||||
import testtools
|
||||
from testtools import matchers
|
||||
|
||||
from magnumclient import exceptions
|
||||
from magnumclient.tests import utils
|
||||
from magnumclient.v1 import baymodels
|
||||
|
||||
BAYMODEL1 = {'id': 123,
|
||||
'uuid': '66666666-7777-8888-9999-000000000001',
|
||||
'name': 'baymodel1',
|
||||
'image_id': 'baymodel1-image',
|
||||
'master_flavor_id': 'm1.tiny',
|
||||
'flavor_id': 'm1.small',
|
||||
'keypair_id': 'keypair1',
|
||||
'external_network_id': 'd1f02cfb-d27f-4068-9332-84d907cb0e21',
|
||||
'fixed_network': 'private',
|
||||
'fixed_subnet': 'private-subnet',
|
||||
'network_driver': 'libnetwork',
|
||||
'volume_driver': 'rexray',
|
||||
'dns_nameserver': '8.8.1.1',
|
||||
'docker_volume_size': '71',
|
||||
'docker_storage_driver': 'devicemapper',
|
||||
'coe': 'swarm',
|
||||
'http_proxy': 'http_proxy',
|
||||
'https_proxy': 'https_proxy',
|
||||
'no_proxy': 'no_proxy',
|
||||
'labels': 'key1=val1,key11=val11',
|
||||
'tls_disabled': False,
|
||||
'public': False,
|
||||
'registry_enabled': False,
|
||||
'master_lb_enabled': True,
|
||||
'floating_ip_enabled': True,
|
||||
}
|
||||
|
||||
BAYMODEL2 = {'id': 124,
|
||||
'uuid': '66666666-7777-8888-9999-000000000002',
|
||||
'name': 'baymodel2',
|
||||
'image_id': 'baymodel2-image',
|
||||
'flavor_id': 'm2.small',
|
||||
'master_flavor_id': 'm2.tiny',
|
||||
'keypair_id': 'keypair2',
|
||||
'external_network_id': 'd1f02cfb-d27f-4068-9332-84d907cb0e22',
|
||||
'fixed_network': 'private2',
|
||||
'network_driver': 'flannel',
|
||||
'volume_driver': 'cinder',
|
||||
'dns_nameserver': '8.8.1.2',
|
||||
'docker_volume_size': '50',
|
||||
'docker_storage_driver': 'overlay',
|
||||
'coe': 'kubernetes',
|
||||
'labels': 'key2=val2,key22=val22',
|
||||
'tls_disabled': True,
|
||||
'public': True,
|
||||
'registry_enabled': True}
|
||||
|
||||
CREATE_BAYMODEL = copy.deepcopy(BAYMODEL1)
|
||||
del CREATE_BAYMODEL['id']
|
||||
del CREATE_BAYMODEL['uuid']
|
||||
|
||||
UPDATED_BAYMODEL = copy.deepcopy(BAYMODEL1)
|
||||
NEW_NAME = 'newbay'
|
||||
UPDATED_BAYMODEL['name'] = NEW_NAME
|
||||
|
||||
fake_responses = {
|
||||
'/v1/baymodels':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{'baymodels': [BAYMODEL1, BAYMODEL2]},
|
||||
),
|
||||
'POST': (
|
||||
{},
|
||||
CREATE_BAYMODEL,
|
||||
),
|
||||
},
|
||||
'/v1/baymodels/%s' % BAYMODEL1['id']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
BAYMODEL1
|
||||
),
|
||||
'DELETE': (
|
||||
{},
|
||||
None,
|
||||
),
|
||||
'PATCH': (
|
||||
{},
|
||||
UPDATED_BAYMODEL,
|
||||
),
|
||||
},
|
||||
'/v1/baymodels/%s' % BAYMODEL1['name']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
BAYMODEL1
|
||||
),
|
||||
'DELETE': (
|
||||
{},
|
||||
None,
|
||||
),
|
||||
'PATCH': (
|
||||
{},
|
||||
UPDATED_BAYMODEL,
|
||||
),
|
||||
},
|
||||
'/v1/baymodels/detail':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{'baymodels': [BAYMODEL1, BAYMODEL2]},
|
||||
),
|
||||
},
|
||||
'/v1/baymodels/?limit=2':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{'baymodels': [BAYMODEL1, BAYMODEL2]},
|
||||
),
|
||||
},
|
||||
'/v1/baymodels/?marker=%s' % BAYMODEL2['uuid']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{'baymodels': [BAYMODEL1, BAYMODEL2]},
|
||||
),
|
||||
},
|
||||
'/v1/baymodels/?limit=2&marker=%s' % BAYMODEL2['uuid']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{'baymodels': [BAYMODEL1, BAYMODEL2]},
|
||||
),
|
||||
},
|
||||
'/v1/baymodels/?sort_dir=asc':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{'baymodels': [BAYMODEL1, BAYMODEL2]},
|
||||
),
|
||||
},
|
||||
'/v1/baymodels/?sort_key=uuid':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{'baymodels': [BAYMODEL1, BAYMODEL2]},
|
||||
),
|
||||
},
|
||||
'/v1/baymodels/?sort_key=uuid&sort_dir=desc':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{'baymodels': [BAYMODEL2, BAYMODEL1]},
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class BayModelManagerTest(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(BayModelManagerTest, self).setUp()
|
||||
self.api = utils.FakeAPI(fake_responses)
|
||||
self.mgr = baymodels.BayModelManager(self.api)
|
||||
|
||||
def test_baymodel_list(self):
|
||||
baymodels = self.mgr.list()
|
||||
expect = [
|
||||
('GET', '/v1/baymodels', {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertThat(baymodels, matchers.HasLength(2))
|
||||
|
||||
def _test_baymodel_list_with_filters(
|
||||
self, limit=None, marker=None,
|
||||
sort_key=None, sort_dir=None,
|
||||
detail=False, expect=[]):
|
||||
baymodels_filter = self.mgr.list(limit=limit, marker=marker,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir,
|
||||
detail=detail)
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertThat(baymodels_filter, matchers.HasLength(2))
|
||||
|
||||
def test_baymodel_list_with_detail(self):
|
||||
expect = [
|
||||
('GET', '/v1/baymodels/detail', {}, None),
|
||||
]
|
||||
self._test_baymodel_list_with_filters(
|
||||
detail=True,
|
||||
expect=expect)
|
||||
|
||||
def test_baymodel_list_with_limit(self):
|
||||
expect = [
|
||||
('GET', '/v1/baymodels/?limit=2', {}, None),
|
||||
]
|
||||
self._test_baymodel_list_with_filters(
|
||||
limit=2,
|
||||
expect=expect)
|
||||
|
||||
def test_baymodel_list_with_marker(self):
|
||||
expect = [
|
||||
('GET', '/v1/baymodels/?marker=%s' % BAYMODEL2['uuid'], {}, None),
|
||||
]
|
||||
self._test_baymodel_list_with_filters(
|
||||
marker=BAYMODEL2['uuid'],
|
||||
expect=expect)
|
||||
|
||||
def test_baymodel_list_with_marker_limit(self):
|
||||
expect = [
|
||||
('GET', '/v1/baymodels/?limit=2&marker=%s' % BAYMODEL2['uuid'],
|
||||
{}, None),
|
||||
]
|
||||
self._test_baymodel_list_with_filters(
|
||||
limit=2, marker=BAYMODEL2['uuid'],
|
||||
expect=expect)
|
||||
|
||||
def test_baymodel_list_with_sort_dir(self):
|
||||
expect = [
|
||||
('GET', '/v1/baymodels/?sort_dir=asc', {}, None),
|
||||
]
|
||||
self._test_baymodel_list_with_filters(
|
||||
sort_dir='asc',
|
||||
expect=expect)
|
||||
|
||||
def test_baymodel_list_with_sort_key(self):
|
||||
expect = [
|
||||
('GET', '/v1/baymodels/?sort_key=uuid', {}, None),
|
||||
]
|
||||
self._test_baymodel_list_with_filters(
|
||||
sort_key='uuid',
|
||||
expect=expect)
|
||||
|
||||
def test_baymodel_list_with_sort_key_dir(self):
|
||||
expect = [
|
||||
('GET', '/v1/baymodels/?sort_key=uuid&sort_dir=desc', {}, None),
|
||||
]
|
||||
self._test_baymodel_list_with_filters(
|
||||
sort_key='uuid', sort_dir='desc',
|
||||
expect=expect)
|
||||
|
||||
def test_baymodel_show_by_id(self):
|
||||
baymodel = self.mgr.get(BAYMODEL1['id'])
|
||||
expect = [
|
||||
('GET', '/v1/baymodels/%s' % BAYMODEL1['id'], {}, None)
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(BAYMODEL1['name'], baymodel.name)
|
||||
self.assertEqual(BAYMODEL1['image_id'], baymodel.image_id)
|
||||
self.assertEqual(BAYMODEL1['docker_volume_size'],
|
||||
baymodel.docker_volume_size)
|
||||
self.assertEqual(BAYMODEL1['docker_storage_driver'],
|
||||
baymodel.docker_storage_driver)
|
||||
self.assertEqual(BAYMODEL1['fixed_network'], baymodel.fixed_network)
|
||||
self.assertEqual(BAYMODEL1['fixed_subnet'], baymodel.fixed_subnet)
|
||||
self.assertEqual(BAYMODEL1['coe'], baymodel.coe)
|
||||
self.assertEqual(BAYMODEL1['http_proxy'], baymodel.http_proxy)
|
||||
self.assertEqual(BAYMODEL1['https_proxy'], baymodel.https_proxy)
|
||||
self.assertEqual(BAYMODEL1['no_proxy'], baymodel.no_proxy)
|
||||
self.assertEqual(BAYMODEL1['network_driver'], baymodel.network_driver)
|
||||
self.assertEqual(BAYMODEL1['volume_driver'], baymodel.volume_driver)
|
||||
self.assertEqual(BAYMODEL1['labels'], baymodel.labels)
|
||||
self.assertEqual(BAYMODEL1['tls_disabled'], baymodel.tls_disabled)
|
||||
self.assertEqual(BAYMODEL1['public'], baymodel.public)
|
||||
self.assertEqual(BAYMODEL1['registry_enabled'],
|
||||
baymodel.registry_enabled)
|
||||
self.assertEqual(BAYMODEL1['master_lb_enabled'],
|
||||
baymodel.master_lb_enabled)
|
||||
self.assertEqual(BAYMODEL1['floating_ip_enabled'],
|
||||
baymodel.floating_ip_enabled)
|
||||
|
||||
def test_baymodel_show_by_name(self):
|
||||
baymodel = self.mgr.get(BAYMODEL1['name'])
|
||||
expect = [
|
||||
('GET', '/v1/baymodels/%s' % BAYMODEL1['name'], {}, None)
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(BAYMODEL1['name'], baymodel.name)
|
||||
self.assertEqual(BAYMODEL1['image_id'], baymodel.image_id)
|
||||
self.assertEqual(BAYMODEL1['docker_volume_size'],
|
||||
baymodel.docker_volume_size)
|
||||
self.assertEqual(BAYMODEL1['docker_storage_driver'],
|
||||
baymodel.docker_storage_driver)
|
||||
self.assertEqual(BAYMODEL1['fixed_network'], baymodel.fixed_network)
|
||||
self.assertEqual(BAYMODEL1['fixed_subnet'], baymodel.fixed_subnet)
|
||||
self.assertEqual(BAYMODEL1['coe'], baymodel.coe)
|
||||
self.assertEqual(BAYMODEL1['http_proxy'], baymodel.http_proxy)
|
||||
self.assertEqual(BAYMODEL1['https_proxy'], baymodel.https_proxy)
|
||||
self.assertEqual(BAYMODEL1['no_proxy'], baymodel.no_proxy)
|
||||
self.assertEqual(BAYMODEL1['network_driver'], baymodel.network_driver)
|
||||
self.assertEqual(BAYMODEL1['volume_driver'], baymodel.volume_driver)
|
||||
self.assertEqual(BAYMODEL1['labels'], baymodel.labels)
|
||||
self.assertEqual(BAYMODEL1['tls_disabled'], baymodel.tls_disabled)
|
||||
self.assertEqual(BAYMODEL1['public'], baymodel.public)
|
||||
self.assertEqual(BAYMODEL1['registry_enabled'],
|
||||
baymodel.registry_enabled)
|
||||
self.assertEqual(BAYMODEL1['master_lb_enabled'],
|
||||
baymodel.master_lb_enabled)
|
||||
self.assertEqual(BAYMODEL1['floating_ip_enabled'],
|
||||
baymodel.floating_ip_enabled)
|
||||
|
||||
def test_baymodel_create(self):
|
||||
baymodel = self.mgr.create(**CREATE_BAYMODEL)
|
||||
expect = [
|
||||
('POST', '/v1/baymodels', {}, CREATE_BAYMODEL),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertTrue(baymodel)
|
||||
self.assertEqual(BAYMODEL1['docker_volume_size'],
|
||||
baymodel.docker_volume_size)
|
||||
self.assertEqual(BAYMODEL1['docker_storage_driver'],
|
||||
baymodel.docker_storage_driver)
|
||||
|
||||
def test_baymodel_create_fail(self):
|
||||
CREATE_BAYMODEL_FAIL = copy.deepcopy(CREATE_BAYMODEL)
|
||||
CREATE_BAYMODEL_FAIL["wrong_key"] = "wrong"
|
||||
self.assertRaisesRegex(exceptions.InvalidAttribute,
|
||||
("Key must be in %s" %
|
||||
','.join(baymodels.CREATION_ATTRIBUTES)),
|
||||
self.mgr.create, **CREATE_BAYMODEL_FAIL)
|
||||
self.assertEqual([], self.api.calls)
|
||||
|
||||
def test_baymodel_delete_by_id(self):
|
||||
baymodel = self.mgr.delete(BAYMODEL1['id'])
|
||||
expect = [
|
||||
('DELETE', '/v1/baymodels/%s' % BAYMODEL1['id'], {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertIsNone(baymodel)
|
||||
|
||||
def test_baymodel_delete_by_name(self):
|
||||
baymodel = self.mgr.delete(BAYMODEL1['name'])
|
||||
expect = [
|
||||
('DELETE', '/v1/baymodels/%s' % BAYMODEL1['name'], {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertIsNone(baymodel)
|
||||
|
||||
def test_baymodel_update(self):
|
||||
patch = {'op': 'replace',
|
||||
'value': NEW_NAME,
|
||||
'path': '/name'}
|
||||
baymodel = self.mgr.update(id=BAYMODEL1['id'], patch=patch)
|
||||
expect = [
|
||||
('PATCH', '/v1/baymodels/%s' % BAYMODEL1['id'], {}, patch),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(NEW_NAME, baymodel.name)
|
|
@ -1,599 +0,0 @@
|
|||
# Copyright 2015 NEC Corporation. All rights reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import mock
|
||||
|
||||
from magnumclient.common.apiclient import exceptions
|
||||
from magnumclient.tests.v1 import shell_test_base
|
||||
from magnumclient.v1.baymodels import BayModel
|
||||
|
||||
|
||||
class FakeBayModel(BayModel):
|
||||
def __init__(self, manager=None, info={}, **kwargs):
|
||||
BayModel.__init__(self, manager=manager, info=info)
|
||||
self.apiserver_port = kwargs.get('apiserver_port', None)
|
||||
self.uuid = kwargs.get('uuid', 'x')
|
||||
self.links = kwargs.get('links', [])
|
||||
self.server_type = kwargs.get('server_type', 'vm')
|
||||
self.image_id = kwargs.get('image_id', 'x')
|
||||
self.tls_disabled = kwargs.get('tls_disabled', False)
|
||||
self.registry_enabled = kwargs.get('registry_enabled', False)
|
||||
self.coe = kwargs.get('coe', 'x')
|
||||
self.public = kwargs.get('public', False)
|
||||
self.name = kwargs.get('name', 'x')
|
||||
|
||||
|
||||
class ShellTest(shell_test_base.TestCommandLineArgument):
|
||||
|
||||
def _get_expected_args_list(self, limit=None, sort_dir=None,
|
||||
sort_key=None, detail=False):
|
||||
expected_args = {}
|
||||
expected_args['limit'] = limit
|
||||
expected_args['sort_dir'] = sort_dir
|
||||
expected_args['sort_key'] = sort_key
|
||||
expected_args['detail'] = detail
|
||||
|
||||
return expected_args
|
||||
|
||||
def _get_expected_args(self, image_id, external_network_id, coe,
|
||||
master_flavor_id=None, name=None,
|
||||
keypair_id=None, fixed_network=None,
|
||||
fixed_subnet=None, network_driver=None,
|
||||
volume_driver=None, dns_nameserver='8.8.8.8',
|
||||
flavor_id='m1.medium',
|
||||
docker_storage_driver='devicemapper',
|
||||
docker_volume_size=None, http_proxy=None,
|
||||
https_proxy=None, no_proxy=None, labels={},
|
||||
tls_disabled=False, public=False,
|
||||
master_lb_enabled=False, server_type='vm',
|
||||
floating_ip_enabled=True,
|
||||
registry_enabled=False):
|
||||
|
||||
expected_args = {}
|
||||
expected_args['image_id'] = image_id
|
||||
expected_args['external_network_id'] = external_network_id
|
||||
expected_args['coe'] = coe
|
||||
expected_args['master_flavor_id'] = master_flavor_id
|
||||
expected_args['name'] = name
|
||||
expected_args['keypair_id'] = keypair_id
|
||||
expected_args['fixed_network'] = fixed_network
|
||||
expected_args['fixed_subnet'] = fixed_subnet
|
||||
expected_args['network_driver'] = network_driver
|
||||
expected_args['volume_driver'] = volume_driver
|
||||
expected_args['dns_nameserver'] = dns_nameserver
|
||||
expected_args['flavor_id'] = flavor_id
|
||||
expected_args['docker_volume_size'] = docker_volume_size
|
||||
expected_args['docker_storage_driver'] = docker_storage_driver
|
||||
expected_args['http_proxy'] = http_proxy
|
||||
expected_args['https_proxy'] = https_proxy
|
||||
expected_args['no_proxy'] = no_proxy
|
||||
expected_args['labels'] = labels
|
||||
expected_args['tls_disabled'] = tls_disabled
|
||||
expected_args['public'] = public
|
||||
expected_args['master_lb_enabled'] = master_lb_enabled
|
||||
expected_args['server_type'] = server_type
|
||||
expected_args['floating_ip_enabled'] = floating_ip_enabled
|
||||
expected_args['registry_enabled'] = registry_enabled
|
||||
|
||||
return expected_args
|
||||
|
||||
@mock.patch('magnumclient.v1.baymodels.BayModelManager.create')
|
||||
def test_baymodel_create_success(self, mock_create):
|
||||
self._test_arg_success('baymodel-create '
|
||||
'--name test '
|
||||
'--image-id test_image '
|
||||
'--keypair-id test_keypair '
|
||||
'--external-network-id test_net '
|
||||
'--coe swarm '
|
||||
'--dns-nameserver test_dns '
|
||||
'--flavor-id test_flavor '
|
||||
'--fixed-network private '
|
||||
'--fixed-subnet private-subnet '
|
||||
'--volume-driver test_volume '
|
||||
'--network-driver test_driver '
|
||||
'--labels key=val '
|
||||
'--master-flavor-id test_flavor '
|
||||
'--docker-volume-size 10 '
|
||||
'--docker-storage-driver devicemapper '
|
||||
'--public '
|
||||
'--server-type vm '
|
||||
'--master-lb-enabled '
|
||||
'--floating-ip-enabled ')
|
||||
expected_args = \
|
||||
self._get_expected_args(name='test', image_id='test_image',
|
||||
keypair_id='test_keypair', coe='swarm',
|
||||
external_network_id='test_net',
|
||||
dns_nameserver='test_dns', public=True,
|
||||
flavor_id='test_flavor',
|
||||
master_flavor_id='test_flavor',
|
||||
fixed_network='private',
|
||||
fixed_subnet='private-subnet',
|
||||
server_type='vm',
|
||||
network_driver='test_driver',
|
||||
volume_driver='test_volume',
|
||||
docker_storage_driver='devicemapper',
|
||||
docker_volume_size=10,
|
||||
master_lb_enabled=True,
|
||||
floating_ip_enabled=True,
|
||||
labels={'key': 'val'})
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
self._test_arg_success('baymodel-create '
|
||||
'--keypair-id test_keypair '
|
||||
'--external-network-id test_net '
|
||||
'--image-id test_image '
|
||||
'--coe kubernetes '
|
||||
'--name test '
|
||||
'--server-type vm')
|
||||
expected_args = \
|
||||
self._get_expected_args(name='test', image_id='test_image',
|
||||
keypair_id='test_keypair',
|
||||
coe='kubernetes',
|
||||
external_network_id='test_net',
|
||||
server_type='vm')
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
@mock.patch('magnumclient.v1.baymodels.BayModelManager.create')
|
||||
def test_baymodel_create_success_no_servertype(self, mock_create):
|
||||
self._test_arg_success('baymodel-create '
|
||||
'--name test '
|
||||
'--image-id test_image '
|
||||
'--keypair-id test_keypair '
|
||||
'--external-network-id test_net '
|
||||
'--coe swarm '
|
||||
'--dns-nameserver test_dns '
|
||||
'--flavor-id test_flavor '
|
||||
'--fixed-network public '
|
||||
'--network-driver test_driver '
|
||||
'--labels key=val '
|
||||
'--master-flavor-id test_flavor '
|
||||
'--docker-volume-size 10 '
|
||||
'--docker-storage-driver devicemapper '
|
||||
'--public ')
|
||||
expected_args = \
|
||||
self._get_expected_args(name='test', image_id='test_image',
|
||||
keypair_id='test_keypair', coe='swarm',
|
||||
external_network_id='test_net',
|
||||
dns_nameserver='test_dns', public=True,
|
||||
flavor_id='test_flavor',
|
||||
master_flavor_id='test_flavor',
|
||||
fixed_network='public',
|
||||
network_driver='test_driver',
|
||||
docker_storage_driver='devicemapper',
|
||||
docker_volume_size=10,
|
||||
labels={'key': 'val'})
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
self._test_arg_success('baymodel-create '
|
||||
'--keypair-id test_keypair '
|
||||
'--external-network-id test_net '
|
||||
'--image-id test_image '
|
||||
'--coe kubernetes '
|
||||
'--name test ')
|
||||
|
||||
expected_args = \
|
||||
self._get_expected_args(name='test', image_id='test_image',
|
||||
keypair_id='test_keypair',
|
||||
coe='kubernetes',
|
||||
external_network_id='test_net')
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
@mock.patch('magnumclient.v1.baymodels.BayModelManager.create')
|
||||
def test_baymodel_create_success_with_registry_enabled(
|
||||
self, mock_create):
|
||||
self._test_arg_success('baymodel-create '
|
||||
'--name test '
|
||||
'--network-driver test_driver '
|
||||
'--keypair-id test_keypair '
|
||||
'--external-network-id test_net '
|
||||
'--image-id test_image '
|
||||
'--coe swarm '
|
||||
'--registry-enabled')
|
||||
expected_args = \
|
||||
self._get_expected_args(name='test', image_id='test_image',
|
||||
keypair_id='test_keypair', coe='swarm',
|
||||
external_network_id='test_net',
|
||||
network_driver='test_driver',
|
||||
registry_enabled=True)
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
@mock.patch('magnumclient.v1.baymodels.BayModelManager.create')
|
||||
def test_baymodel_create_public_success(self, mock_create):
|
||||
self._test_arg_success('baymodel-create '
|
||||
'--name test --network-driver test_driver '
|
||||
'--keypair-id test_keypair '
|
||||
'--external-network-id test_net '
|
||||
'--image-id test_image '
|
||||
'--coe swarm '
|
||||
'--public '
|
||||
'--server-type vm')
|
||||
expected_args = \
|
||||
self._get_expected_args(name='test', image_id='test_image',
|
||||
keypair_id='test_keypair', coe='swarm',
|
||||
external_network_id='test_net',
|
||||
public=True, server_type='vm',
|
||||
network_driver='test_driver')
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
@mock.patch('magnumclient.v1.baymodels.BayModelManager.create')
|
||||
def test_baymodel_create_success_with_master_flavor(self, mock_create):
|
||||
self._test_arg_success('baymodel-create '
|
||||
'--name test '
|
||||
'--image-id test_image '
|
||||
'--keypair-id test_keypair '
|
||||
'--external-network-id test_net '
|
||||
'--coe swarm '
|
||||
'--dns-nameserver test_dns '
|
||||
'--master-flavor-id test_flavor')
|
||||
expected_args = \
|
||||
self._get_expected_args(name='test', image_id='test_image',
|
||||
keypair_id='test_keypair', coe='swarm',
|
||||
external_network_id='test_net',
|
||||
dns_nameserver='test_dns',
|
||||
master_flavor_id='test_flavor')
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
@mock.patch('magnumclient.v1.baymodels.BayModelManager.create')
|
||||
def test_baymodel_create_docker_vol_size_success(self, mock_create):
|
||||
self._test_arg_success('baymodel-create '
|
||||
'--name test --docker-volume-size 4514 '
|
||||
'--keypair-id test_keypair '
|
||||
'--external-network-id test_net '
|
||||
'--image-id test_image '
|
||||
'--coe swarm '
|
||||
'--server-type vm')
|
||||
expected_args = \
|
||||
self._get_expected_args(name='test', image_id='test_image',
|
||||
keypair_id='test_keypair', coe='swarm',
|
||||
external_network_id='test_net',
|
||||
server_type='vm',
|
||||
docker_volume_size=4514)
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
@mock.patch('magnumclient.v1.baymodels.BayModelManager.create')
|
||||
def test_baymodel_create_docker_storage_driver_success(self, mock_create):
|
||||
self._test_arg_success('baymodel-create '
|
||||
'--name test '
|
||||
'--keypair-id test_keypair '
|
||||
'--external-network-id test_net '
|
||||
'--image-id test_image '
|
||||
'--docker-storage-driver devicemapper '
|
||||
'--coe swarm'
|
||||
)
|
||||
expected_args = \
|
||||
self._get_expected_args(name='test', image_id='test_image',
|
||||
keypair_id='test_keypair', coe='swarm',
|
||||
external_network_id='test_net',
|
||||
docker_storage_driver='devicemapper')
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
@mock.patch('magnumclient.v1.baymodels.BayModelManager.create')
|
||||
def test_baymodel_create_fixed_network_success(self, mock_create):
|
||||
self._test_arg_success('baymodel-create '
|
||||
'--name test --fixed-network private '
|
||||
'--keypair-id test_keypair '
|
||||
'--external-network-id test_net '
|
||||
'--image-id test_image '
|
||||
'--coe swarm '
|
||||
'--server-type vm')
|
||||
expected_args = \
|
||||
self._get_expected_args(name='test', image_id='test_image',
|
||||
keypair_id='test_keypair', coe='swarm',
|
||||
fixed_network='private',
|
||||
external_network_id='test_net',
|
||||
server_type='vm')
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
@mock.patch('magnumclient.v1.baymodels.BayModelManager.create')
|
||||
def test_baymodel_create_network_driver_success(self, mock_create):
|
||||
self._test_arg_success('baymodel-create '
|
||||
'--name test --network-driver test_driver '
|
||||
'--keypair-id test_keypair '
|
||||
'--external-network-id test_net '
|
||||
'--image-id test_image '
|
||||
'--coe swarm '
|
||||
'--server-type vm')
|
||||
expected_args = \
|
||||
self._get_expected_args(name='test', image_id='test_image',
|
||||
keypair_id='test_keypair', coe='swarm',
|
||||
external_network_id='test_net',
|
||||
server_type='vm',
|
||||
network_driver='test_driver')
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
@mock.patch('magnumclient.v1.baymodels.BayModelManager.create')
|
||||
def test_baymodel_create_volume_driver_success(self, mock_create):
|
||||
self._test_arg_success('baymodel-create '
|
||||
'--name test --volume-driver test_volume '
|
||||
'--keypair-id test_keypair '
|
||||
'--external-network-id test_net '
|
||||
'--image-id test_image '
|
||||
'--coe swarm '
|
||||
'--server-type vm')
|
||||
expected_args = \
|
||||
self._get_expected_args(name='test', image_id='test_image',
|
||||
keypair_id='test_keypair', coe='swarm',
|
||||
external_network_id='test_net',
|
||||
server_type='vm',
|
||||
volume_driver='test_volume')
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
@mock.patch('magnumclient.v1.baymodels.BayModelManager.create')
|
||||
def test_baymodel_create_http_proxy_success(self, mock_create):
|
||||
self._test_arg_success('baymodel-create '
|
||||
'--name test --fixed-network private '
|
||||
'--keypair-id test_keypair '
|
||||
'--external-network-id test_net '
|
||||
'--image-id test_image '
|
||||
'--coe swarm '
|
||||
'--http-proxy http_proxy '
|
||||
'--server-type vm')
|
||||
expected_args = \
|
||||
self._get_expected_args(name='test', image_id='test_image',
|
||||
keypair_id='test_keypair', coe='swarm',
|
||||
external_network_id='test_net',
|
||||
fixed_network='private',
|
||||
server_type='vm',
|
||||
http_proxy='http_proxy')
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
@mock.patch('magnumclient.v1.baymodels.BayModelManager.create')
|
||||
def test_baymodel_create_https_proxy_success(self, mock_create):
|
||||
self._test_arg_success('baymodel-create '
|
||||
'--name test --fixed-network private '
|
||||
'--keypair-id test_keypair '
|
||||
'--external-network-id test_net '
|
||||
'--image-id test_image '
|
||||
'--coe swarm '
|
||||
'--https-proxy https_proxy '
|
||||
'--server-type vm')
|
||||
expected_args = \
|
||||
self._get_expected_args(name='test', image_id='test_image',
|
||||
keypair_id='test_keypair', coe='swarm',
|
||||
external_network_id='test_net',
|
||||
fixed_network='private',
|
||||
server_type='vm',
|
||||
https_proxy='https_proxy')
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
@mock.patch('magnumclient.v1.baymodels.BayModelManager.create')
|
||||
def test_baymodel_create_no_proxy_success(self, mock_create):
|
||||
self._test_arg_success('baymodel-create '
|
||||
'--name test --fixed-network private '
|
||||
'--keypair-id test_keypair '
|
||||
'--external-network-id test_net '
|
||||
'--image-id test_image '
|
||||
'--coe swarm '
|
||||
'--no-proxy no_proxy '
|
||||
'--server-type vm')
|
||||
expected_args = \
|
||||
self._get_expected_args(name='test', image_id='test_image',
|
||||
keypair_id='test_keypair', coe='swarm',
|
||||
external_network_id='test_net',
|
||||
fixed_network='private',
|
||||
server_type='vm',
|
||||
no_proxy='no_proxy')
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
@mock.patch('magnumclient.v1.baymodels.BayModelManager.create')
|
||||
def test_baymodel_create_labels_success(self, mock_create):
|
||||
self._test_arg_success('baymodel-create '
|
||||
'--name test '
|
||||
'--labels key=val '
|
||||
'--keypair-id test_keypair '
|
||||
'--external-network-id test_net '
|
||||
'--image-id test_image '
|
||||
'--coe swarm '
|
||||
'--server-type vm')
|
||||
expected_args = \
|
||||
self._get_expected_args(name='test', image_id='test_image',
|
||||
keypair_id='test_keypair', coe='swarm',
|
||||
external_network_id='test_net',
|
||||
server_type='vm',
|
||||
labels={'key': 'val'})
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
@mock.patch('magnumclient.v1.baymodels.BayModelManager.create')
|
||||
def test_baymodel_create_separate_labels_success(self, mock_create):
|
||||
self._test_arg_success('baymodel-create '
|
||||
'--name test '
|
||||
'--labels key1=val1 '
|
||||
'--labels key2=val2 '
|
||||
'--keypair-id test_keypair '
|
||||
'--external-network-id test_net '
|
||||
'--image-id test_image '
|
||||
'--coe swarm '
|
||||
'--server-type vm')
|
||||
expected_args = \
|
||||
self._get_expected_args(name='test', image_id='test_image',
|
||||
keypair_id='test_keypair', coe='swarm',
|
||||
external_network_id='test_net',
|
||||
server_type='vm',
|
||||
labels={'key1': 'val1', 'key2': 'val2'})
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
@mock.patch('magnumclient.v1.baymodels.BayModelManager.create')
|
||||
def test_baymodel_create_combined_labels_success(self, mock_create):
|
||||
self._test_arg_success('baymodel-create '
|
||||
'--name test '
|
||||
'--labels key1=val1,key2=val2 '
|
||||
'--keypair-id test_keypair '
|
||||
'--external-network-id test_net '
|
||||
'--image-id test_image '
|
||||
'--coe swarm '
|
||||
'--server-type vm')
|
||||
expected_args = \
|
||||
self._get_expected_args(name='test', image_id='test_image',
|
||||
keypair_id='test_keypair', coe='swarm',
|
||||
external_network_id='test_net',
|
||||
server_type='vm',
|
||||
labels={'key1': 'val1', 'key2': 'val2'})
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
@mock.patch('magnumclient.v1.baymodels.BayModelManager.create')
|
||||
def test_baymodel_create_failure_few_arg(self, mock_create):
|
||||
self._test_arg_failure('baymodel-create '
|
||||
'--name test', self._mandatory_arg_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
self._test_arg_failure('baymodel-create '
|
||||
'--image-id test', self._mandatory_arg_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
self._test_arg_failure('baymodel-create '
|
||||
'--keypair-id test', self._mandatory_arg_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
self._test_arg_failure('baymodel-create '
|
||||
'--external-network-id test',
|
||||
self._mandatory_arg_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
self._test_arg_failure('baymodel-create '
|
||||
'--coe test', self._mandatory_arg_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
self._test_arg_failure('baymodel-create '
|
||||
'--server-type test', self._mandatory_arg_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
self._test_arg_failure('baymodel-create', self._mandatory_arg_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.baymodels.BayModelManager.get')
|
||||
def test_baymodel_show_success(self, mock_show):
|
||||
self._test_arg_success('baymodel-show xxx')
|
||||
mock_show.assert_called_once_with('xxx')
|
||||
|
||||
@mock.patch('magnumclient.v1.baymodels.BayModelManager.get')
|
||||
def test_baymodel_show_failure_no_arg(self, mock_show):
|
||||
self._test_arg_failure('baymodel-show', self._few_argument_error)
|
||||
mock_show.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.baymodels.BayModelManager.delete')
|
||||
def test_baymodel_delete_success(self, mock_delete):
|
||||
self._test_arg_success('baymodel-delete xxx')
|
||||
mock_delete.assert_called_once_with('xxx')
|
||||
|
||||
@mock.patch('magnumclient.v1.baymodels.BayModelManager.delete')
|
||||
def test_baymodel_delete_multiple_id_success(self, mock_delete):
|
||||
self._test_arg_success('baymodel-delete xxx xyz')
|
||||
calls = [mock.call('xxx'), mock.call('xyz')]
|
||||
mock_delete.assert_has_calls(calls)
|
||||
|
||||
@mock.patch('magnumclient.v1.baymodels.BayModelManager.delete')
|
||||
def test_baymodel_delete_failure_no_arg(self, mock_delete):
|
||||
self._test_arg_failure('baymodel-delete', self._few_argument_error)
|
||||
mock_delete.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.baymodels.BayModelManager.update')
|
||||
def test_baymodel_update_success(self, mock_update):
|
||||
self._test_arg_success('baymodel-update test add test=test')
|
||||
patch = [{'op': 'add', 'path': '/test', 'value': 'test'}]
|
||||
mock_update.assert_called_once_with('test', patch)
|
||||
|
||||
@mock.patch('magnumclient.v1.baymodels.BayModelManager.update')
|
||||
def test_baymodel_update_success_many_attribute(self, mock_update):
|
||||
self._test_arg_success('baymodel-update test '
|
||||
'add test=test test1=test1')
|
||||
patch = [{'op': 'add', 'path': '/test', 'value': 'test'},
|
||||
{'op': 'add', 'path': '/test1', 'value': 'test1'}]
|
||||
mock_update.assert_called_once_with('test', patch)
|
||||
|
||||
@mock.patch('magnumclient.v1.baymodels.BayModelManager.update')
|
||||
def test_baymodel_update_failure_wrong_op(self, mock_update):
|
||||
_error_msg = [
|
||||
'.*?^usage: magnum baymodel-update ',
|
||||
'.*?^error: argument <op>: invalid choice: ',
|
||||
".*?^Try 'magnum help baymodel-update' for more information."
|
||||
]
|
||||
self._test_arg_failure('baymodel-update test wrong test=test',
|
||||
_error_msg)
|
||||
mock_update.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.baymodels.BayModelManager.update')
|
||||
def test_baymodel_update_failure_few_args(self, mock_update):
|
||||
_error_msg = [
|
||||
'.*?^usage: magnum baymodel-update ',
|
||||
'.*?^error: (the following arguments|too few arguments)',
|
||||
".*?^Try 'magnum help baymodel-update' for more information."
|
||||
]
|
||||
self._test_arg_failure('baymodel-update', _error_msg)
|
||||
mock_update.assert_not_called()
|
||||
|
||||
self._test_arg_failure('baymodel-update test', _error_msg)
|
||||
mock_update.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.baymodels.BayModelManager.list')
|
||||
def test_baymodel_list_success(self, mock_list):
|
||||
self._test_arg_success('baymodel-list')
|
||||
expected_args = self._get_expected_args_list()
|
||||
mock_list.assert_called_once_with(**expected_args)
|
||||
|
||||
@mock.patch('magnumclient.v1.baymodels.BayModelManager.list')
|
||||
def test_baymodel_list_success_with_arg(self, mock_list):
|
||||
self._test_arg_success('baymodel-list '
|
||||
'--limit 1 '
|
||||
'--sort-dir asc '
|
||||
'--sort-key uuid')
|
||||
expected_args = self._get_expected_args_list(1, 'asc', 'uuid')
|
||||
mock_list.assert_called_once_with(**expected_args)
|
||||
|
||||
@mock.patch('magnumclient.v1.baymodels.BayModelManager.list')
|
||||
def test_baymodel_list_success_detailed(self, mock_list):
|
||||
self._test_arg_success('baymodel-list '
|
||||
'--detail')
|
||||
expected_args = self._get_expected_args_list(detail=True)
|
||||
mock_list.assert_called_once_with(**expected_args)
|
||||
|
||||
@mock.patch('magnumclient.v1.baymodels.BayModelManager.list')
|
||||
def test_baymodel_list_ignored_duplicated_field(self, mock_list):
|
||||
mock_list.return_value = [FakeBayModel()]
|
||||
self._test_arg_success('baymodel-list --fields coe,coe,coe,name,name',
|
||||
keyword='\n| uuid | name | Coe |\n')
|
||||
# Output should be
|
||||
# +------+------+-----+
|
||||
# | uuid | name | Coe |
|
||||
# +------+------+-----+
|
||||
# | x | x | x |
|
||||
# +------+------+-----+
|
||||
expected_args = self._get_expected_args_list()
|
||||
mock_list.assert_called_once_with(**expected_args)
|
||||
|
||||
@mock.patch('magnumclient.v1.baymodels.BayModelManager.list')
|
||||
def test_baymodel_list_failure_with_invalid_field(self, mock_list):
|
||||
mock_list.return_value = [FakeBayModel()]
|
||||
_error_msg = [".*?^Non-existent fields are specified: ['xxx','zzz']"]
|
||||
self.assertRaises(exceptions.CommandError,
|
||||
self._test_arg_failure,
|
||||
'baymodel-list --fields xxx,coe,zzz',
|
||||
_error_msg)
|
||||
expected_args = self._get_expected_args_list()
|
||||
mock_list.assert_called_once_with(**expected_args)
|
||||
|
||||
@mock.patch('magnumclient.v1.baymodels.BayModelManager.list')
|
||||
def test_baymodel_list_failure_invalid_arg(self, mock_list):
|
||||
_error_msg = [
|
||||
'.*?^usage: magnum baymodel-list ',
|
||||
'.*?^error: argument --sort-dir: invalid choice: ',
|
||||
".*?^Try 'magnum help baymodel-list' for more information."
|
||||
]
|
||||
self._test_arg_failure('baymodel-list --sort-dir aaa', _error_msg)
|
||||
mock_list.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.baymodels.BayModelManager.list')
|
||||
def test_baymodel_list_failure(self, mock_list):
|
||||
self._test_arg_failure('baymodel-list --wrong',
|
||||
self._unrecognized_arg_error)
|
||||
mock_list.assert_not_called()
|
|
@ -1,317 +0,0 @@
|
|||
# Copyright 2015 IBM Corp.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import copy
|
||||
|
||||
import testtools
|
||||
from testtools import matchers
|
||||
|
||||
from magnumclient import exceptions
|
||||
from magnumclient.tests import utils
|
||||
from magnumclient.v1 import bays
|
||||
|
||||
|
||||
BAY1 = {'id': 123,
|
||||
'uuid': '66666666-7777-8888-9999-000000000001',
|
||||
'name': 'bay1',
|
||||
'baymodel_id': 'e74c40e0-d825-11e2-a28f-0800200c9a61',
|
||||
'stack_id': '5d12f6fd-a196-4bf0-ae4c-1f639a523a51',
|
||||
'api_address': '172.17.2.1',
|
||||
'node_addresses': ['172.17.2.3'],
|
||||
'node_count': 2,
|
||||
'master_count': 1,
|
||||
}
|
||||
BAY2 = {'id': 124,
|
||||
'uuid': '66666666-7777-8888-9999-000000000002',
|
||||
'name': 'bay2',
|
||||
'baymodel_id': 'e74c40e0-d825-11e2-a28f-0800200c9a62',
|
||||
'stack_id': '5d12f6fd-a196-4bf0-ae4c-1f639a523a52',
|
||||
'api_address': '172.17.2.2',
|
||||
'node_addresses': ['172.17.2.4'],
|
||||
'node_count': 2,
|
||||
'master_count': 1,
|
||||
}
|
||||
|
||||
CREATE_BAY = copy.deepcopy(BAY1)
|
||||
del CREATE_BAY['id']
|
||||
del CREATE_BAY['uuid']
|
||||
del CREATE_BAY['stack_id']
|
||||
del CREATE_BAY['api_address']
|
||||
del CREATE_BAY['node_addresses']
|
||||
|
||||
UPDATED_BAY = copy.deepcopy(BAY1)
|
||||
NEW_NAME = 'newbay'
|
||||
UPDATED_BAY['name'] = NEW_NAME
|
||||
|
||||
fake_responses = {
|
||||
'/v1/bays':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{'bays': [BAY1, BAY2]},
|
||||
),
|
||||
'POST': (
|
||||
{},
|
||||
CREATE_BAY,
|
||||
),
|
||||
},
|
||||
'/v1/bays/%s' % BAY1['id']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
BAY1
|
||||
),
|
||||
'DELETE': (
|
||||
{},
|
||||
None,
|
||||
),
|
||||
'PATCH': (
|
||||
{},
|
||||
UPDATED_BAY,
|
||||
),
|
||||
},
|
||||
'/v1/bays/%s/?rollback=True' % BAY1['id']:
|
||||
{
|
||||
'PATCH': (
|
||||
{},
|
||||
UPDATED_BAY,
|
||||
),
|
||||
},
|
||||
'/v1/bays/%s' % BAY1['name']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
BAY1
|
||||
),
|
||||
'DELETE': (
|
||||
{},
|
||||
None,
|
||||
),
|
||||
'PATCH': (
|
||||
{},
|
||||
UPDATED_BAY,
|
||||
),
|
||||
},
|
||||
'/v1/bays/?limit=2':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{'bays': [BAY1, BAY2]},
|
||||
),
|
||||
},
|
||||
'/v1/bays/?marker=%s' % BAY2['uuid']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{'bays': [BAY1, BAY2]},
|
||||
),
|
||||
},
|
||||
'/v1/bays/?limit=2&marker=%s' % BAY2['uuid']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{'bays': [BAY1, BAY2]},
|
||||
),
|
||||
},
|
||||
'/v1/bays/?sort_dir=asc':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{'bays': [BAY1, BAY2]},
|
||||
),
|
||||
},
|
||||
'/v1/bays/?sort_key=uuid':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{'bays': [BAY1, BAY2]},
|
||||
),
|
||||
},
|
||||
'/v1/bays/?sort_key=uuid&sort_dir=desc':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{'bays': [BAY2, BAY1]},
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class BayManagerTest(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(BayManagerTest, self).setUp()
|
||||
self.api = utils.FakeAPI(fake_responses)
|
||||
self.mgr = bays.BayManager(self.api)
|
||||
|
||||
def test_bay_list(self):
|
||||
bays = self.mgr.list()
|
||||
expect = [
|
||||
('GET', '/v1/bays', {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertThat(bays, matchers.HasLength(2))
|
||||
|
||||
def _test_bay_list_with_filters(self, limit=None, marker=None,
|
||||
sort_key=None, sort_dir=None,
|
||||
detail=False, expect=[]):
|
||||
bays_filter = self.mgr.list(limit=limit, marker=marker,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir,
|
||||
detail=detail)
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertThat(bays_filter, matchers.HasLength(2))
|
||||
|
||||
def test_bay_list_with_limit(self):
|
||||
expect = [
|
||||
('GET', '/v1/bays/?limit=2', {}, None),
|
||||
]
|
||||
self._test_bay_list_with_filters(
|
||||
limit=2,
|
||||
expect=expect)
|
||||
|
||||
def test_bay_list_with_marker(self):
|
||||
expect = [
|
||||
('GET', '/v1/bays/?marker=%s' % BAY2['uuid'], {}, None),
|
||||
]
|
||||
self._test_bay_list_with_filters(
|
||||
marker=BAY2['uuid'],
|
||||
expect=expect)
|
||||
|
||||
def test_bay_list_with_marker_limit(self):
|
||||
expect = [
|
||||
('GET', '/v1/bays/?limit=2&marker=%s' % BAY2['uuid'], {}, None),
|
||||
]
|
||||
self._test_bay_list_with_filters(
|
||||
limit=2, marker=BAY2['uuid'],
|
||||
expect=expect)
|
||||
|
||||
def test_bay_list_with_sort_dir(self):
|
||||
expect = [
|
||||
('GET', '/v1/bays/?sort_dir=asc', {}, None),
|
||||
]
|
||||
self._test_bay_list_with_filters(
|
||||
sort_dir='asc',
|
||||
expect=expect)
|
||||
|
||||
def test_bay_list_with_sort_key(self):
|
||||
expect = [
|
||||
('GET', '/v1/bays/?sort_key=uuid', {}, None),
|
||||
]
|
||||
self._test_bay_list_with_filters(
|
||||
sort_key='uuid',
|
||||
expect=expect)
|
||||
|
||||
def test_bay_list_with_sort_key_dir(self):
|
||||
expect = [
|
||||
('GET', '/v1/bays/?sort_key=uuid&sort_dir=desc', {}, None),
|
||||
]
|
||||
self._test_bay_list_with_filters(
|
||||
sort_key='uuid', sort_dir='desc',
|
||||
expect=expect)
|
||||
|
||||
def test_bay_show_by_id(self):
|
||||
bay = self.mgr.get(BAY1['id'])
|
||||
expect = [
|
||||
('GET', '/v1/bays/%s' % BAY1['id'], {}, None)
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(BAY1['name'], bay.name)
|
||||
self.assertEqual(BAY1['baymodel_id'], bay.baymodel_id)
|
||||
|
||||
def test_bay_show_by_name(self):
|
||||
bay = self.mgr.get(BAY1['name'])
|
||||
expect = [
|
||||
('GET', '/v1/bays/%s' % BAY1['name'], {}, None)
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(BAY1['name'], bay.name)
|
||||
self.assertEqual(BAY1['baymodel_id'], bay.baymodel_id)
|
||||
|
||||
def test_bay_create(self):
|
||||
bay = self.mgr.create(**CREATE_BAY)
|
||||
expect = [
|
||||
('POST', '/v1/bays', {}, CREATE_BAY),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertTrue(bay)
|
||||
|
||||
def test_bay_create_with_discovery_url(self):
|
||||
bay_with_discovery = dict()
|
||||
bay_with_discovery.update(CREATE_BAY)
|
||||
bay_with_discovery['discovery_url'] = 'discovery_url'
|
||||
bay = self.mgr.create(**bay_with_discovery)
|
||||
expect = [
|
||||
('POST', '/v1/bays', {}, bay_with_discovery),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertTrue(bay)
|
||||
|
||||
def test_bay_create_with_bay_create_timeout(self):
|
||||
bay_with_timeout = dict()
|
||||
bay_with_timeout.update(CREATE_BAY)
|
||||
bay_with_timeout['bay_create_timeout'] = '15'
|
||||
bay = self.mgr.create(**bay_with_timeout)
|
||||
expect = [
|
||||
('POST', '/v1/bays', {}, bay_with_timeout),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertTrue(bay)
|
||||
|
||||
def test_bay_create_fail(self):
|
||||
CREATE_BAY_FAIL = copy.deepcopy(CREATE_BAY)
|
||||
CREATE_BAY_FAIL["wrong_key"] = "wrong"
|
||||
self.assertRaisesRegex(exceptions.InvalidAttribute,
|
||||
("Key must be in %s" %
|
||||
','.join(bays.CREATION_ATTRIBUTES)),
|
||||
self.mgr.create, **CREATE_BAY_FAIL)
|
||||
self.assertEqual([], self.api.calls)
|
||||
|
||||
def test_bay_delete_by_id(self):
|
||||
bay = self.mgr.delete(BAY1['id'])
|
||||
expect = [
|
||||
('DELETE', '/v1/bays/%s' % BAY1['id'], {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertIsNone(bay)
|
||||
|
||||
def test_bay_delete_by_name(self):
|
||||
bay = self.mgr.delete(BAY1['name'])
|
||||
expect = [
|
||||
('DELETE', '/v1/bays/%s' % BAY1['name'], {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertIsNone(bay)
|
||||
|
||||
def test_bay_update(self):
|
||||
patch = {'op': 'replace',
|
||||
'value': NEW_NAME,
|
||||
'path': '/name'}
|
||||
bay = self.mgr.update(id=BAY1['id'], patch=patch)
|
||||
expect = [
|
||||
('PATCH', '/v1/bays/%s' % BAY1['id'], {}, patch),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(NEW_NAME, bay.name)
|
||||
|
||||
def test_bay_update_with_rollback(self):
|
||||
patch = {'op': 'replace',
|
||||
'value': NEW_NAME,
|
||||
'path': '/name'}
|
||||
bay = self.mgr.update(id=BAY1['id'], patch=patch, rollback=True)
|
||||
expect = [
|
||||
('PATCH', '/v1/bays/%s/?rollback=True' % BAY1['id'], {}, patch),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(NEW_NAME, bay.name)
|
|
@ -1,412 +0,0 @@
|
|||
# Copyright 2015 NEC Corporation. All rights reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import mock
|
||||
|
||||
from magnumclient import exceptions
|
||||
from magnumclient.tests.v1 import shell_test_base
|
||||
from magnumclient.tests.v1 import test_baymodels_shell
|
||||
from magnumclient.v1.bays import Bay
|
||||
|
||||
|
||||
class FakeBay(Bay):
|
||||
def __init__(self, manager=None, info={}, **kwargs):
|
||||
Bay.__init__(self, manager=manager, info=info)
|
||||
self.uuid = kwargs.get('uuid', 'x')
|
||||
self.name = kwargs.get('name', 'x')
|
||||
self.baymodel_id = kwargs.get('baymodel_id', 'x')
|
||||
self.stack_id = kwargs.get('stack_id', 'x')
|
||||
self.status = kwargs.get('status', 'x')
|
||||
self.master_count = kwargs.get('master_count', 1)
|
||||
self.node_count = kwargs.get('node_count', 1)
|
||||
self.links = kwargs.get('links', [])
|
||||
self.bay_create_timeout = kwargs.get('bay_create_timeout', 60)
|
||||
|
||||
|
||||
class FakeCert(object):
|
||||
def __init__(self, pem):
|
||||
self.pem = pem
|
||||
|
||||
|
||||
class ShellTest(shell_test_base.TestCommandLineArgument):
|
||||
|
||||
def _get_expected_args_list(self, marker=None, limit=None,
|
||||
sort_dir=None, sort_key=None):
|
||||
expected_args = {}
|
||||
expected_args['marker'] = marker
|
||||
expected_args['limit'] = limit
|
||||
expected_args['sort_dir'] = sort_dir
|
||||
expected_args['sort_key'] = sort_key
|
||||
|
||||
return expected_args
|
||||
|
||||
def _get_expected_args_create(self, baymodel_id, name=None,
|
||||
master_count=1, node_count=1,
|
||||
bay_create_timeout=60,
|
||||
discovery_url=None):
|
||||
expected_args = {}
|
||||
expected_args['name'] = name
|
||||
expected_args['baymodel_id'] = baymodel_id
|
||||
expected_args['master_count'] = master_count
|
||||
expected_args['node_count'] = node_count
|
||||
expected_args['bay_create_timeout'] = bay_create_timeout
|
||||
expected_args['discovery_url'] = discovery_url
|
||||
|
||||
return expected_args
|
||||
|
||||
@mock.patch('magnumclient.v1.bays.BayManager.list')
|
||||
def test_bay_list_success(self, mock_list):
|
||||
self._test_arg_success('bay-list')
|
||||
expected_args = self._get_expected_args_list()
|
||||
mock_list.assert_called_once_with(**expected_args)
|
||||
|
||||
@mock.patch('magnumclient.v1.bays.BayManager.list')
|
||||
def test_bay_list_success_with_arg(self, mock_list):
|
||||
self._test_arg_success('bay-list '
|
||||
'--marker some_uuid '
|
||||
'--limit 1 '
|
||||
'--sort-dir asc '
|
||||
'--sort-key uuid')
|
||||
expected_args = self._get_expected_args_list('some_uuid', 1,
|
||||
'asc', 'uuid')
|
||||
mock_list.assert_called_once_with(**expected_args)
|
||||
|
||||
@mock.patch('magnumclient.v1.bays.BayManager.list')
|
||||
def test_bay_list_ignored_duplicated_field(self, mock_list):
|
||||
mock_list.return_value = [FakeBay()]
|
||||
self._test_arg_success('bay-list --fields status,status,status,name',
|
||||
keyword=('\n| uuid | name | node_count | '
|
||||
'master_count | status |\n'))
|
||||
# Output should be
|
||||
# +------+------+------------+--------------+--------+
|
||||
# | uuid | name | node_count | master_count | status |
|
||||
# +------+------+------------+--------------+--------+
|
||||
# | x | x | x | x | x |
|
||||
# +------+------+------------+--------------+--------+
|
||||
expected_args = self._get_expected_args_list()
|
||||
mock_list.assert_called_once_with(**expected_args)
|
||||
|
||||
@mock.patch('magnumclient.v1.bays.BayManager.list')
|
||||
def test_bay_list_failure_with_invalid_field(self, mock_list):
|
||||
mock_list.return_value = [FakeBay()]
|
||||
_error_msg = [".*?^Non-existent fields are specified: ['xxx','zzz']"]
|
||||
self.assertRaises(exceptions.CommandError,
|
||||
self._test_arg_failure,
|
||||
'bay-list --fields xxx,stack_id,zzz,status',
|
||||
_error_msg)
|
||||
expected_args = self._get_expected_args_list()
|
||||
mock_list.assert_called_once_with(**expected_args)
|
||||
|
||||
@mock.patch('magnumclient.v1.bays.BayManager.list')
|
||||
def test_bay_list_failure_invalid_arg(self, mock_list):
|
||||
_error_msg = [
|
||||
'.*?^usage: magnum bay-list ',
|
||||
'.*?^error: argument --sort-dir: invalid choice: ',
|
||||
".*?^Try 'magnum help bay-list' for more information."
|
||||
]
|
||||
self._test_arg_failure('bay-list --sort-dir aaa', _error_msg)
|
||||
mock_list.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.bays.BayManager.list')
|
||||
def test_bay_list_failure(self, mock_list):
|
||||
self._test_arg_failure('bay-list --wrong',
|
||||
self._unrecognized_arg_error)
|
||||
mock_list.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.baymodels.BayModelManager.get')
|
||||
@mock.patch('magnumclient.v1.bays.BayManager.create')
|
||||
def test_bay_create_success(self, mock_create, mock_get):
|
||||
mock_baymodel = mock.MagicMock()
|
||||
mock_baymodel.uuid = 'xxx'
|
||||
mock_get.return_value = mock_baymodel
|
||||
self._test_arg_success('bay-create --name test --baymodel xxx '
|
||||
'--node-count 123 --timeout 15')
|
||||
expected_args = self._get_expected_args_create('xxx', name='test',
|
||||
node_count=123,
|
||||
bay_create_timeout=15)
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
self._test_arg_success('bay-create --baymodel xxx')
|
||||
expected_args = self._get_expected_args_create('xxx')
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
self._test_arg_success('bay-create --name test --baymodel xxx')
|
||||
expected_args = self._get_expected_args_create('xxx',
|
||||
name='test')
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
self._test_arg_success('bay-create --baymodel xxx --node-count 123')
|
||||
expected_args = self._get_expected_args_create('xxx',
|
||||
node_count=123)
|
||||
|
||||
self._test_arg_success('bay-create --baymodel xxx --node-count 123 '
|
||||
'--master-count 123')
|
||||
expected_args = self._get_expected_args_create('xxx',
|
||||
master_count=123,
|
||||
node_count=123)
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
self._test_arg_success('bay-create --baymodel xxx '
|
||||
'--timeout 15')
|
||||
expected_args = self._get_expected_args_create('xxx',
|
||||
bay_create_timeout=15)
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
@mock.patch('magnumclient.v1.baymodels.BayModelManager.get')
|
||||
@mock.patch('magnumclient.v1.bays.BayManager.get')
|
||||
def test_bay_show_baymodel_metadata(self, mock_bay, mock_baymodel):
|
||||
mock_bay.return_value = mock.MagicMock(baymodel_id=0)
|
||||
mock_baymodel.return_value = test_baymodels_shell.FakeBayModel(
|
||||
info={'links': 0, 'uuid': 0, 'id': 0, 'name': ''})
|
||||
|
||||
self._test_arg_success('bay-show --long x')
|
||||
mock_bay.assert_called_once_with('x')
|
||||
mock_baymodel.assert_called_once_with(0)
|
||||
|
||||
@mock.patch('magnumclient.v1.baymodels.BayModelManager.get')
|
||||
@mock.patch('magnumclient.v1.bays.BayManager.create')
|
||||
def test_bay_create_success_only_baymodel_arg(self, mock_create, mock_get):
|
||||
mock_baymodel = mock.MagicMock()
|
||||
mock_baymodel.uuid = 'xxx'
|
||||
mock_get.return_value = mock_baymodel
|
||||
self._test_arg_success('bay-create --baymodel xxx')
|
||||
expected_args = self._get_expected_args_create('xxx')
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
@mock.patch('magnumclient.v1.bays.BayManager.create')
|
||||
def test_bay_create_failure_only_name(self, mock_create):
|
||||
self._test_arg_failure('bay-create --name test',
|
||||
self._mandatory_arg_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.bays.BayManager.create')
|
||||
def test_bay_create_failure_only_node_count(self, mock_create):
|
||||
self._test_arg_failure('bay-create --node-count 1',
|
||||
self._mandatory_arg_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.bays.BayManager.create')
|
||||
def test_bay_create_failure_invalid_node_count(self, mock_create):
|
||||
self._test_arg_failure('bay-create --baymodel xxx --node-count test',
|
||||
self._invalid_value_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.bays.BayManager.create')
|
||||
def test_bay_create_failure_only_bay_create_timeout(self, mock_create):
|
||||
self._test_arg_failure('bay-create --timeout 15',
|
||||
self._mandatory_arg_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.bays.BayManager.create')
|
||||
def test_bay_create_failure_no_arg(self, mock_create):
|
||||
self._test_arg_failure('bay-create',
|
||||
self._mandatory_arg_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.bays.BayManager.create')
|
||||
def test_bay_create_failure_invalid_master_count(self, mock_create):
|
||||
self._test_arg_failure('bay-create --baymodel xxx --master-count test',
|
||||
self._invalid_value_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.bays.BayManager.delete')
|
||||
def test_bay_delete_success(self, mock_delete):
|
||||
self._test_arg_success('bay-delete xxx')
|
||||
mock_delete.assert_called_once_with('xxx')
|
||||
|
||||
@mock.patch('magnumclient.v1.bays.BayManager.delete')
|
||||
def test_bay_delete_multiple_id_success(self, mock_delete):
|
||||
self._test_arg_success('bay-delete xxx xyz')
|
||||
calls = [mock.call('xxx'), mock.call('xyz')]
|
||||
mock_delete.assert_has_calls(calls)
|
||||
|
||||
@mock.patch('magnumclient.v1.bays.BayManager.delete')
|
||||
def test_bay_delete_failure_no_arg(self, mock_delete):
|
||||
self._test_arg_failure('bay-delete', self._few_argument_error)
|
||||
mock_delete.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.bays.BayManager.get')
|
||||
def test_bay_show_success(self, mock_show):
|
||||
self._test_arg_success('bay-show xxx')
|
||||
mock_show.assert_called_once_with('xxx')
|
||||
|
||||
@mock.patch('magnumclient.v1.bays.BayManager.get')
|
||||
def test_bay_show_failure_no_arg(self, mock_show):
|
||||
self._test_arg_failure('bay-show', self._few_argument_error)
|
||||
mock_show.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.bays.BayManager.update')
|
||||
def test_bay_update_success(self, mock_update):
|
||||
self._test_arg_success('bay-update test add test=test')
|
||||
patch = [{'op': 'add', 'path': '/test', 'value': 'test'}]
|
||||
mock_update.assert_called_once_with('test', patch, False)
|
||||
|
||||
@mock.patch('magnumclient.v1.bays.BayManager.update')
|
||||
def test_bay_update_success_many_attribute(self, mock_update):
|
||||
self._test_arg_success('bay-update test add test=test test1=test1')
|
||||
patch = [{'op': 'add', 'path': '/test', 'value': 'test'},
|
||||
{'op': 'add', 'path': '/test1', 'value': 'test1'}]
|
||||
mock_update.assert_called_once_with('test', patch, False)
|
||||
|
||||
@mock.patch('magnumclient.v1.bays.BayManager.update')
|
||||
def test_bay_update_success_rollback(self, mock_update):
|
||||
self._test_arg_success('bay-update test add test=test --rollback')
|
||||
patch = [{'op': 'add', 'path': '/test', 'value': 'test'}]
|
||||
mock_update.assert_called_once_with('test', patch, True)
|
||||
|
||||
@mock.patch('magnumclient.v1.bays.BayManager.update')
|
||||
def test_bay_update_rollback_old_api_version(self, mock_update):
|
||||
self.assertRaises(
|
||||
exceptions.CommandError,
|
||||
self.shell,
|
||||
'--magnum-api-version 1.2 bay-update '
|
||||
'test add test=test --rollback')
|
||||
mock_update.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.bays.BayManager.update')
|
||||
def test_bay_update_failure_wrong_op(self, mock_update):
|
||||
_error_msg = [
|
||||
'.*?^usage: magnum bay-update ',
|
||||
'.*?^error: argument <op>: invalid choice: ',
|
||||
".*?^Try 'magnum help bay-update' for more information."
|
||||
]
|
||||
self._test_arg_failure('bay-update test wrong test=test', _error_msg)
|
||||
mock_update.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.bays.BayManager.update')
|
||||
def test_bay_update_failure_wrong_attribute(self, mock_update):
|
||||
_error_msg = [
|
||||
'.*?^ERROR: Attributes must be a list of PATH=VALUE'
|
||||
]
|
||||
self.assertRaises(exceptions.CommandError, self._test_arg_failure,
|
||||
'bay-update test add test', _error_msg)
|
||||
mock_update.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.bays.BayManager.update')
|
||||
def test_bay_update_failure_few_args(self, mock_update):
|
||||
_error_msg = [
|
||||
'.*?^usage: magnum bay-update ',
|
||||
'.*?^error: (the following arguments|too few arguments)',
|
||||
".*?^Try 'magnum help bay-update' for more information."
|
||||
]
|
||||
self._test_arg_failure('bay-update', _error_msg)
|
||||
mock_update.assert_not_called()
|
||||
|
||||
self._test_arg_failure('bay-update test', _error_msg)
|
||||
mock_update.assert_not_called()
|
||||
|
||||
self._test_arg_failure('bay-update test add', _error_msg)
|
||||
mock_update.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.baymodels.BayModelManager.get')
|
||||
@mock.patch('magnumclient.v1.bays.BayManager.get')
|
||||
def test_bay_config_success(self, mock_bay, mock_baymodel):
|
||||
mock_bay.return_value = FakeBay(status='UPDATE_COMPLETE')
|
||||
self._test_arg_success('bay-config xxx')
|
||||
mock_bay.assert_called_with('xxx')
|
||||
|
||||
mock_bay.return_value = FakeBay(status='CREATE_COMPLETE')
|
||||
self._test_arg_success('bay-config xxx')
|
||||
mock_bay.assert_called_with('xxx')
|
||||
|
||||
self._test_arg_success('bay-config --dir /tmp xxx')
|
||||
mock_bay.assert_called_with('xxx')
|
||||
|
||||
self._test_arg_success('bay-config --force xxx')
|
||||
mock_bay.assert_called_with('xxx')
|
||||
|
||||
self._test_arg_success('bay-config --dir /tmp --force xxx')
|
||||
mock_bay.assert_called_with('xxx')
|
||||
|
||||
@mock.patch('magnumclient.v1.baymodels.BayModelManager.get')
|
||||
@mock.patch('magnumclient.v1.bays.BayManager.get')
|
||||
def test_bay_config_failure_wrong_status(self, mock_bay, mock_baymodel):
|
||||
mock_bay.return_value = FakeBay(status='CREATE_IN_PROGRESS')
|
||||
self.assertRaises(exceptions.CommandError,
|
||||
self._test_arg_failure,
|
||||
'bay-config xxx',
|
||||
['.*?^Bay in status: '])
|
||||
|
||||
@mock.patch('magnumclient.v1.bays.BayManager.get')
|
||||
def test_bay_config_failure_no_arg(self, mock_bay):
|
||||
self._test_arg_failure('bay-config', self._few_argument_error)
|
||||
mock_bay.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.bays.BayManager.get')
|
||||
def test_bay_config_failure_wrong_arg(self, mock_bay):
|
||||
self._test_arg_failure('bay-config xxx yyy',
|
||||
self._unrecognized_arg_error)
|
||||
mock_bay.assert_not_called()
|
||||
|
||||
@mock.patch('os.path.exists')
|
||||
@mock.patch('magnumclient.v1.certificates.CertificateManager.create')
|
||||
@mock.patch('magnumclient.v1.certificates.CertificateManager.get')
|
||||
@mock.patch('magnumclient.v1.baymodels.BayModelManager.get')
|
||||
@mock.patch('magnumclient.v1.bays.BayManager.get')
|
||||
def _test_bay_config_success(self, mock_bay, mock_bm, mock_cert_get,
|
||||
mock_cert_create, mock_exists, coe, shell,
|
||||
tls_disable):
|
||||
cert = FakeCert(pem='foo bar')
|
||||
mock_exists.return_value = False
|
||||
mock_bay.return_value = FakeBay(status='CREATE_COMPLETE',
|
||||
info={'name': 'Kluster',
|
||||
'api_address': '127.0.0.1'},
|
||||
baymodel_id='fake_bm',
|
||||
uuid='fake_bay')
|
||||
mock_cert_get.return_value = cert
|
||||
mock_cert_create.return_value = cert
|
||||
mock_bm.return_value = test_baymodels_shell. \
|
||||
FakeBayModel(coe=coe, name='fake_bm', tls_disabled=tls_disable)
|
||||
with mock.patch.dict('os.environ', {'SHELL': shell}):
|
||||
self._test_arg_success('bay-config test_bay')
|
||||
|
||||
self.assertTrue(mock_exists.called)
|
||||
mock_bay.assert_called_once_with('test_bay')
|
||||
mock_bm.assert_called_once_with('fake_bm')
|
||||
if not tls_disable:
|
||||
mock_cert_create.assert_called_once_with(cluster_uuid='fake_bay',
|
||||
csr=mock.ANY)
|
||||
mock_cert_get.assert_called_once_with(cluster_uuid='fake_bay')
|
||||
|
||||
def test_bay_config_swarm_success_with_tls_csh(self):
|
||||
self._test_bay_config_success(coe='swarm', shell='csh',
|
||||
tls_disable=False)
|
||||
|
||||
def test_bay_config_swarm_success_with_tls_non_csh(self):
|
||||
self._test_bay_config_success(coe='swarm', shell='zsh',
|
||||
tls_disable=False)
|
||||
|
||||
def test_bay_config_swarm_success_without_tls_csh(self):
|
||||
self._test_bay_config_success(coe='swarm', shell='csh',
|
||||
tls_disable=True)
|
||||
|
||||
def test_bay_config_swarm_success_without_tls_non_csh(self):
|
||||
self._test_bay_config_success(coe='swarm', shell='zsh',
|
||||
tls_disable=True)
|
||||
|
||||
def test_bay_config_k8s_success_with_tls_csh(self):
|
||||
self._test_bay_config_success(coe='kubernetes', shell='csh',
|
||||
tls_disable=False)
|
||||
|
||||
def test_bay_config_k8s_success_with_tls_non_csh(self):
|
||||
self._test_bay_config_success(coe='kubernetes', shell='zsh',
|
||||
tls_disable=False)
|
||||
|
||||
def test_bay_config_k8s_success_without_tls_csh(self):
|
||||
self._test_bay_config_success(coe='kubernetes', shell='csh',
|
||||
tls_disable=True)
|
||||
|
||||
def test_bay_config_k8s_success_without_tls_non_csh(self):
|
||||
self._test_bay_config_success(coe='kubernetes', shell='zsh',
|
||||
tls_disable=True)
|
|
@ -1,115 +0,0 @@
|
|||
# Copyright 2015 IBM Corp.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import copy
|
||||
|
||||
import testtools
|
||||
|
||||
from magnumclient import exceptions
|
||||
from magnumclient.tests import utils
|
||||
from magnumclient.v1 import certificates
|
||||
|
||||
|
||||
CERT1 = {
|
||||
'cluster_uuid': '5d12f6fd-a196-4bf0-ae4c-1f639a523a53',
|
||||
'pem': 'fake-pem'
|
||||
}
|
||||
CERT2 = {
|
||||
'cluster_uuid': '5d12f6fd-a196-4bf0-ae4c-1f639a523a53',
|
||||
'pem': 'fake-pem',
|
||||
'csr': 'fake-csr',
|
||||
}
|
||||
|
||||
CREATE_CERT = {'cluster_uuid': '5d12f6fd-a196-4bf0-ae4c-1f639a523a53',
|
||||
'csr': 'fake-csr'}
|
||||
|
||||
CREATE_BACKWARDS_CERT = {
|
||||
'bay_uuid': '5d12f6fd-a196-4bf0-ae4c-1f639a523a53',
|
||||
'csr': 'fake-csr'
|
||||
}
|
||||
|
||||
fake_responses = {
|
||||
'/v1/certificates':
|
||||
{
|
||||
'POST': (
|
||||
{},
|
||||
CERT2,
|
||||
)
|
||||
},
|
||||
'/v1/certificates/%s' % CERT1['cluster_uuid']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
CERT1
|
||||
),
|
||||
'PATCH': (
|
||||
{},
|
||||
None,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class CertificateManagerTest(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(CertificateManagerTest, self).setUp()
|
||||
self.api = utils.FakeAPI(fake_responses)
|
||||
self.mgr = certificates.CertificateManager(self.api)
|
||||
|
||||
def test_cert_show_by_id(self):
|
||||
cert = self.mgr.get(CERT1['cluster_uuid'])
|
||||
expect = [
|
||||
('GET', '/v1/certificates/%s' % CERT1['cluster_uuid'], {}, None)
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(CERT1['cluster_uuid'], cert.cluster_uuid)
|
||||
self.assertEqual(CERT1['pem'], cert.pem)
|
||||
|
||||
def test_cert_create(self):
|
||||
cert = self.mgr.create(**CREATE_CERT)
|
||||
expect = [
|
||||
('POST', '/v1/certificates', {}, CREATE_CERT),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(CERT2['cluster_uuid'], cert.cluster_uuid)
|
||||
self.assertEqual(CERT2['pem'], cert.pem)
|
||||
self.assertEqual(CERT2['csr'], cert.csr)
|
||||
|
||||
def test_cert_create_backwards_compatibility(self):
|
||||
# Using a CREATION_ATTRIBUTE of bay_uuid and expecting a
|
||||
# cluster_uuid in return
|
||||
cert = self.mgr.create(**CREATE_BACKWARDS_CERT)
|
||||
expect = [
|
||||
('POST', '/v1/certificates', {}, CREATE_CERT),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(CERT2['cluster_uuid'], cert.cluster_uuid)
|
||||
self.assertEqual(CERT2['csr'], cert.csr)
|
||||
|
||||
def test_create_fail(self):
|
||||
create_cert_fail = copy.deepcopy(CREATE_CERT)
|
||||
create_cert_fail["wrong_key"] = "wrong"
|
||||
self.assertRaisesRegex(exceptions.InvalidAttribute,
|
||||
("Key must be in %s" %
|
||||
','.join(certificates.CREATION_ATTRIBUTES)),
|
||||
self.mgr.create, **create_cert_fail)
|
||||
self.assertEqual([], self.api.calls)
|
||||
|
||||
def test_rotate_ca(self):
|
||||
self.mgr.rotate_ca(cluster_uuid=CERT1['cluster_uuid'])
|
||||
expect = [
|
||||
('PATCH', '/v1/certificates/%s' % CERT1['cluster_uuid'], {}, None)
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
|
@ -1,170 +0,0 @@
|
|||
# Copyright 2015 NEC Corporation. All rights reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import mock
|
||||
|
||||
from magnumclient.common import cliutils as utils
|
||||
from magnumclient.tests.v1 import shell_test_base
|
||||
from magnumclient.v1 import certificates_shell
|
||||
|
||||
|
||||
class ShellTest(shell_test_base.TestCommandLineArgument):
|
||||
|
||||
@mock.patch('magnumclient.v1.bays.BayManager.get')
|
||||
@mock.patch('magnumclient.v1.certificates.CertificateManager.get')
|
||||
def test_ca_show_success(self, mock_cert_get, mock_bay_get):
|
||||
mockbay = mock.MagicMock()
|
||||
mockbay.status = "CREATE_COMPLETE"
|
||||
mockbay.uuid = "xxx"
|
||||
mock_bay_get.return_value = mockbay
|
||||
self._test_arg_success('ca-show '
|
||||
'--bay xxx')
|
||||
expected_args = {}
|
||||
expected_args['cluster_uuid'] = mockbay.uuid
|
||||
mock_cert_get.assert_called_once_with(**expected_args)
|
||||
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.get')
|
||||
@mock.patch('magnumclient.v1.certificates.CertificateManager.get')
|
||||
def test_cluster_ca_show_success(self, mock_cert_get, mock_cluster_get):
|
||||
mockcluster = mock.MagicMock()
|
||||
mockcluster.status = "CREATE_COMPLETE"
|
||||
mockcluster.uuid = "xxx"
|
||||
mock_cluster_get.return_value = mockcluster
|
||||
self._test_arg_success('ca-show '
|
||||
'--cluster xxx')
|
||||
expected_args = {}
|
||||
expected_args['cluster_uuid'] = mockcluster.uuid
|
||||
mock_cert_get.assert_called_once_with(**expected_args)
|
||||
|
||||
@mock.patch('os.path.isfile')
|
||||
@mock.patch('magnumclient.v1.bays.BayManager.get')
|
||||
@mock.patch('magnumclient.v1.certificates.CertificateManager.create')
|
||||
def test_ca_sign_success(
|
||||
self, mock_cert_create, mock_bay_get, mock_isfile):
|
||||
mock_isfile.return_value = True
|
||||
mockbay = mock.MagicMock()
|
||||
mockbay.status = "CREATE_COMPLETE"
|
||||
mockbay.uuid = "xxx"
|
||||
mock_bay_get.return_value = mockbay
|
||||
|
||||
fake_csr = 'fake-csr'
|
||||
mock_file = mock.mock_open(read_data=fake_csr)
|
||||
with mock.patch.object(certificates_shell, 'open', mock_file):
|
||||
self._test_arg_success('ca-sign '
|
||||
'--csr path/csr.pem '
|
||||
'--bay xxx')
|
||||
expected_args = {}
|
||||
expected_args['cluster_uuid'] = mockbay.uuid
|
||||
expected_args['csr'] = fake_csr
|
||||
mock_cert_create.assert_called_once_with(**expected_args)
|
||||
|
||||
@mock.patch('os.path.isfile')
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.get')
|
||||
@mock.patch('magnumclient.v1.certificates.CertificateManager.create')
|
||||
def test_cluster_ca_sign_success(
|
||||
self, mock_cert_create, mock_cluster_get, mock_isfile):
|
||||
mock_isfile.return_value = True
|
||||
mockcluster = mock.MagicMock()
|
||||
mockcluster.status = "CREATE_COMPLETE"
|
||||
mockcluster.uuid = "xxx"
|
||||
mock_cluster_get.return_value = mockcluster
|
||||
|
||||
fake_csr = 'fake-csr'
|
||||
mock_file = mock.mock_open(read_data=fake_csr)
|
||||
with mock.patch.object(certificates_shell, 'open', mock_file):
|
||||
self._test_arg_success('ca-sign '
|
||||
'--csr path/csr.pem '
|
||||
'--cluster xxx')
|
||||
expected_args = {}
|
||||
expected_args['cluster_uuid'] = mockcluster.uuid
|
||||
expected_args['csr'] = fake_csr
|
||||
mock_cert_create.assert_called_once_with(**expected_args)
|
||||
|
||||
@mock.patch('os.path.isfile')
|
||||
@mock.patch('magnumclient.v1.bays.BayManager.get')
|
||||
@mock.patch('magnumclient.v1.certificates.CertificateManager.create')
|
||||
def test_ca_sign_with_not_csr(
|
||||
self, mock_cert_create, mock_bay_get, mock_isfile):
|
||||
mock_isfile.return_value = False
|
||||
mockbay = mock.MagicMock()
|
||||
mockbay.status = "CREATE_COMPLETE"
|
||||
mock_bay_get.return_value = mockbay
|
||||
|
||||
fake_csr = 'fake-csr'
|
||||
mock_file = mock.mock_open(read_data=fake_csr)
|
||||
with mock.patch.object(certificates_shell, 'open', mock_file):
|
||||
self._test_arg_success('ca-sign '
|
||||
'--csr path/csr.pem '
|
||||
'--bay xxx')
|
||||
mock_isfile.assert_called_once_with('path/csr.pem')
|
||||
mock_file.assert_not_called()
|
||||
mock_cert_create.assert_not_called()
|
||||
|
||||
@mock.patch('os.path.isfile')
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.get')
|
||||
@mock.patch('magnumclient.v1.certificates.CertificateManager.create')
|
||||
def test_cluster_ca_sign_with_not_csr(
|
||||
self, mock_cert_create, mock_cluster_get, mock_isfile):
|
||||
mock_isfile.return_value = False
|
||||
mockcluster = mock.MagicMock()
|
||||
mockcluster.status = "CREATE_COMPLETE"
|
||||
mock_cluster_get.return_value = mockcluster
|
||||
|
||||
fake_csr = 'fake-csr'
|
||||
mock_file = mock.mock_open(read_data=fake_csr)
|
||||
with mock.patch.object(certificates_shell, 'open', mock_file):
|
||||
self._test_arg_success('ca-sign '
|
||||
'--csr path/csr.pem '
|
||||
'--cluster xxx')
|
||||
mock_isfile.assert_called_once_with('path/csr.pem')
|
||||
mock_file.assert_not_called()
|
||||
mock_cert_create.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.get')
|
||||
@mock.patch('magnumclient.v1.certificates.CertificateManager.get')
|
||||
def test_ca_show_failure_with_invalid_field(self, mock_cert_get,
|
||||
mock_cluster_get):
|
||||
_error_msg = [".*?^--cluster or --bay"]
|
||||
self.assertRaises(utils.MissingArgs,
|
||||
self._test_arg_failure,
|
||||
'ca-show',
|
||||
_error_msg)
|
||||
mock_cert_get.assert_not_called()
|
||||
mock_cluster_get.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.get')
|
||||
@mock.patch('magnumclient.v1.certificates.CertificateManager.rotate_ca')
|
||||
def test_ca_rotate(self, mock_rotate_ca, mock_cluster_get):
|
||||
mockcluster = mock.MagicMock()
|
||||
mockcluster.status = "CREATE_COMPLETE"
|
||||
mockcluster.uuid = "xxx"
|
||||
mock_cluster_get.return_value = mockcluster
|
||||
mock_rotate_ca.return_value = None
|
||||
self._test_arg_success('ca-rotate '
|
||||
'--cluster xxx')
|
||||
expected_args = {}
|
||||
expected_args['cluster_uuid'] = mockcluster.uuid
|
||||
mock_rotate_ca.assert_called_once_with(**expected_args)
|
||||
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.get')
|
||||
@mock.patch('magnumclient.v1.certificates.CertificateManager.rotate_ca')
|
||||
def test_ca_rotate_no_cluster_arg(self, mock_rotate_ca, mock_cluster_get):
|
||||
_error_msg = [
|
||||
(".*(error: argument --cluster is required|" # py27 compatibility
|
||||
"error: the following arguments are required: --cluster).*"),
|
||||
".*Try 'magnum help ca-rotate' for more information.*"
|
||||
]
|
||||
self._test_arg_failure('ca-rotate', _error_msg)
|
||||
mock_rotate_ca.assert_not_called()
|
||||
mock_cluster_get.assert_not_called()
|
|
@ -1,224 +0,0 @@
|
|||
# Copyright (c) 2015 Thales Services SAS
|
||||
#
|
||||
# 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.
|
||||
|
||||
import mock
|
||||
import testtools
|
||||
|
||||
from keystoneauth1.exceptions import catalog
|
||||
|
||||
from magnumclient.v1 import client
|
||||
|
||||
|
||||
class ClientInitializeTest(testtools.TestCase):
|
||||
|
||||
def _load_session_kwargs(self):
|
||||
return {
|
||||
'username': None,
|
||||
'project_id': None,
|
||||
'project_name': None,
|
||||
'auth_url': None,
|
||||
'password': None,
|
||||
'auth_type': 'password',
|
||||
'insecure': False,
|
||||
'user_domain_id': None,
|
||||
'user_domain_name': None,
|
||||
'project_domain_id': None,
|
||||
'project_domain_name': None,
|
||||
'auth_token': None,
|
||||
'timeout': 600,
|
||||
}
|
||||
|
||||
def _load_service_type_kwargs(self):
|
||||
return {
|
||||
'interface': 'public',
|
||||
'region_name': None,
|
||||
'service_name': None,
|
||||
'service_type': 'container-infra',
|
||||
}
|
||||
|
||||
def _session_client_kwargs(self, session):
|
||||
kwargs = self._load_service_type_kwargs()
|
||||
kwargs['endpoint_override'] = None
|
||||
kwargs['session'] = session
|
||||
kwargs['api_version'] = None
|
||||
|
||||
return kwargs
|
||||
|
||||
@mock.patch('magnumclient.common.httpclient.SessionClient')
|
||||
@mock.patch('magnumclient.v1.client._load_session')
|
||||
@mock.patch('magnumclient.v1.client._load_service_type',
|
||||
return_value='container-infra')
|
||||
def test_init_with_session(self,
|
||||
mock_load_service_type,
|
||||
mock_load_session,
|
||||
mock_http_client):
|
||||
session = mock.Mock()
|
||||
client.Client(session=session)
|
||||
mock_load_session.assert_not_called()
|
||||
mock_load_service_type.assert_called_once_with(
|
||||
session,
|
||||
**self._load_service_type_kwargs()
|
||||
)
|
||||
mock_http_client.assert_called_once_with(
|
||||
**self._session_client_kwargs(session)
|
||||
)
|
||||
|
||||
def _test_init_with_secret(self,
|
||||
init_func,
|
||||
mock_load_service_type,
|
||||
mock_load_session,
|
||||
mock_http_client,):
|
||||
expected_password = 'expected_password'
|
||||
session = mock.Mock()
|
||||
mock_load_session.return_value = session
|
||||
init_func(expected_password)
|
||||
load_session_args = self._load_session_kwargs()
|
||||
load_session_args['password'] = expected_password
|
||||
mock_load_session.assert_called_once_with(
|
||||
**load_session_args
|
||||
)
|
||||
mock_load_service_type.assert_called_once_with(
|
||||
session,
|
||||
**self._load_service_type_kwargs()
|
||||
)
|
||||
mock_http_client.assert_called_once_with(
|
||||
**self._session_client_kwargs(session)
|
||||
)
|
||||
|
||||
@mock.patch('magnumclient.common.httpclient.SessionClient')
|
||||
@mock.patch('magnumclient.v1.client._load_session')
|
||||
@mock.patch('magnumclient.v1.client._load_service_type',
|
||||
return_value='container-infra')
|
||||
def test_init_with_password(self,
|
||||
mock_load_service_type,
|
||||
mock_load_session,
|
||||
mock_http_client):
|
||||
self._test_init_with_secret(
|
||||
lambda x: client.Client(password=x),
|
||||
mock_load_service_type,
|
||||
mock_load_session,
|
||||
mock_http_client
|
||||
)
|
||||
|
||||
@mock.patch('magnumclient.common.httpclient.SessionClient')
|
||||
@mock.patch('magnumclient.v1.client._load_session')
|
||||
@mock.patch('magnumclient.v1.client._load_service_type',
|
||||
return_value='container-infra')
|
||||
def test_init_with_api_key(self,
|
||||
mock_load_service_type,
|
||||
mock_load_session,
|
||||
mock_http_client):
|
||||
self._test_init_with_secret(
|
||||
lambda x: client.Client(api_key=x),
|
||||
mock_load_service_type,
|
||||
mock_load_session,
|
||||
mock_http_client
|
||||
)
|
||||
|
||||
@mock.patch('magnumclient.common.httpclient.HTTPClient')
|
||||
def test_init_with_auth_token(self,
|
||||
mock_http_client,):
|
||||
expected_token = 'expected_password'
|
||||
expected_magnum_url = 'expected_magnum_url'
|
||||
expected_api_version = 'expected_api_version'
|
||||
expected_insecure = False
|
||||
expected_timeout = 600
|
||||
expected_kwargs = {'expected_key': 'expected_value'}
|
||||
client.Client(auth_token=expected_token,
|
||||
magnum_url=expected_magnum_url,
|
||||
api_version=expected_api_version,
|
||||
timeout=expected_timeout,
|
||||
insecure=expected_insecure,
|
||||
**expected_kwargs)
|
||||
|
||||
mock_http_client.assert_called_once_with(
|
||||
expected_magnum_url,
|
||||
token=expected_token,
|
||||
api_version=expected_api_version,
|
||||
timeout=expected_timeout,
|
||||
insecure=expected_insecure,
|
||||
**expected_kwargs)
|
||||
|
||||
def _test_init_with_interface(self,
|
||||
init_func,
|
||||
mock_load_service_type,
|
||||
mock_load_session,
|
||||
mock_http_client):
|
||||
expected_interface = 'admin'
|
||||
session = mock.Mock()
|
||||
mock_load_session.return_value = session
|
||||
init_func(expected_interface)
|
||||
mock_load_session.assert_called_once_with(
|
||||
**self._load_session_kwargs()
|
||||
)
|
||||
expected_kwargs = self._load_service_type_kwargs()
|
||||
expected_kwargs['interface'] = expected_interface
|
||||
mock_load_service_type.assert_called_once_with(
|
||||
session,
|
||||
**expected_kwargs
|
||||
)
|
||||
expected_kwargs = self._session_client_kwargs(session)
|
||||
expected_kwargs['interface'] = expected_interface
|
||||
mock_http_client.assert_called_once_with(
|
||||
**expected_kwargs
|
||||
)
|
||||
|
||||
@mock.patch('magnumclient.common.httpclient.SessionClient')
|
||||
@mock.patch('magnumclient.v1.client._load_session')
|
||||
@mock.patch('magnumclient.v1.client._load_service_type',
|
||||
return_value='container-infra')
|
||||
def test_init_with_interface(self,
|
||||
mock_load_service_type,
|
||||
mock_load_session,
|
||||
mock_http_client):
|
||||
self._test_init_with_interface(
|
||||
lambda x: client.Client(interface=x),
|
||||
mock_load_service_type,
|
||||
mock_load_session,
|
||||
mock_http_client
|
||||
)
|
||||
|
||||
@mock.patch('magnumclient.common.httpclient.SessionClient')
|
||||
@mock.patch('magnumclient.v1.client._load_session')
|
||||
@mock.patch('magnumclient.v1.client._load_service_type',
|
||||
return_value='container-infra')
|
||||
def test_init_with_endpoint_type(self,
|
||||
mock_load_service_type,
|
||||
mock_load_session,
|
||||
mock_http_client):
|
||||
self._test_init_with_interface(
|
||||
lambda x: client.Client(interface='public',
|
||||
endpoint_type=('%sURL' % x)),
|
||||
mock_load_service_type,
|
||||
mock_load_session,
|
||||
mock_http_client
|
||||
)
|
||||
|
||||
@mock.patch('magnumclient.common.httpclient.SessionClient')
|
||||
@mock.patch('magnumclient.v1.client._load_session')
|
||||
def test_init_with_legacy_service_type(self,
|
||||
mock_load_session,
|
||||
mock_http_client):
|
||||
session = mock.Mock()
|
||||
mock_load_session.return_value = session
|
||||
session.get_endpoint.side_effect = [
|
||||
catalog.EndpointNotFound(),
|
||||
mock.Mock()
|
||||
]
|
||||
client.Client(username='myuser', auth_url='authurl')
|
||||
expected_kwargs = self._session_client_kwargs(session)
|
||||
expected_kwargs['service_type'] = 'container'
|
||||
mock_http_client.assert_called_once_with(
|
||||
**expected_kwargs
|
||||
)
|
|
@ -1,346 +0,0 @@
|
|||
# Copyright 2015 IBM Corp
|
||||
#
|
||||
# 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.
|
||||
|
||||
import copy
|
||||
|
||||
import testtools
|
||||
from testtools import matchers
|
||||
|
||||
from magnumclient import exceptions
|
||||
from magnumclient.tests import utils
|
||||
from magnumclient.v1 import clusters
|
||||
|
||||
|
||||
CLUSTER1 = {'id': 123,
|
||||
'uuid': '66666666-7777-8888-9999-000000000001',
|
||||
'name': 'cluster1',
|
||||
'cluster_template_id': 'e74c40e0-d825-11e2-a28f-0800200c9a61',
|
||||
'stack_id': '5d12f6fd-a196-4bf0-ae4c-1f639a523a51',
|
||||
'api_address': '172.17.2.1',
|
||||
'node_addresses': ['172.17.2.3'],
|
||||
'node_count': 2,
|
||||
'master_count': 1,
|
||||
}
|
||||
CLUSTER2 = {'id': 124,
|
||||
'uuid': '66666666-7777-8888-9999-000000000002',
|
||||
'name': 'cluster2',
|
||||
'cluster_template_id': 'e74c40e0-d825-11e2-a28f-0800200c9a62',
|
||||
'stack_id': '5d12f6fd-a196-4bf0-ae4c-1f639a523a52',
|
||||
'api_address': '172.17.2.2',
|
||||
'node_addresses': ['172.17.2.4'],
|
||||
'node_count': 2,
|
||||
'master_count': 1,
|
||||
}
|
||||
|
||||
CREATE_CLUSTER = copy.deepcopy(CLUSTER1)
|
||||
del CREATE_CLUSTER['id']
|
||||
del CREATE_CLUSTER['uuid']
|
||||
del CREATE_CLUSTER['stack_id']
|
||||
del CREATE_CLUSTER['api_address']
|
||||
del CREATE_CLUSTER['node_addresses']
|
||||
|
||||
UPDATED_CLUSTER = copy.deepcopy(CLUSTER1)
|
||||
NEW_NAME = 'newcluster'
|
||||
UPDATED_CLUSTER['name'] = NEW_NAME
|
||||
|
||||
fake_responses = {
|
||||
'/v1/clusters':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{'clusters': [CLUSTER1, CLUSTER2]},
|
||||
),
|
||||
'POST': (
|
||||
{},
|
||||
CREATE_CLUSTER,
|
||||
),
|
||||
},
|
||||
'/v1/clusters/%s' % CLUSTER1['id']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
CLUSTER1
|
||||
),
|
||||
'DELETE': (
|
||||
{},
|
||||
None,
|
||||
),
|
||||
'PATCH': (
|
||||
{},
|
||||
UPDATED_CLUSTER,
|
||||
),
|
||||
},
|
||||
'/v1/clusters/%s/?rollback=True' % CLUSTER1['id']:
|
||||
{
|
||||
'PATCH': (
|
||||
{},
|
||||
UPDATED_CLUSTER,
|
||||
),
|
||||
},
|
||||
'/v1/clusters/%s' % CLUSTER1['name']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
CLUSTER1
|
||||
),
|
||||
'DELETE': (
|
||||
{},
|
||||
None,
|
||||
),
|
||||
'PATCH': (
|
||||
{},
|
||||
UPDATED_CLUSTER,
|
||||
),
|
||||
},
|
||||
'/v1/clusters/?limit=2':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{'clusters': [CLUSTER1, CLUSTER2]},
|
||||
),
|
||||
},
|
||||
'/v1/clusters/?marker=%s' % CLUSTER2['uuid']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{'clusters': [CLUSTER1, CLUSTER2]},
|
||||
),
|
||||
},
|
||||
'/v1/clusters/?limit=2&marker=%s' % CLUSTER2['uuid']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{'clusters': [CLUSTER1, CLUSTER2]},
|
||||
),
|
||||
},
|
||||
'/v1/clusters/?sort_dir=asc':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{'clusters': [CLUSTER1, CLUSTER2]},
|
||||
),
|
||||
},
|
||||
'/v1/clusters/?sort_key=uuid':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{'clusters': [CLUSTER1, CLUSTER2]},
|
||||
),
|
||||
},
|
||||
'/v1/clusters/?sort_key=uuid&sort_dir=desc':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{'clusters': [CLUSTER2, CLUSTER1]},
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class ClusterManagerTest(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ClusterManagerTest, self).setUp()
|
||||
self.api = utils.FakeAPI(fake_responses)
|
||||
self.mgr = clusters.ClusterManager(self.api)
|
||||
|
||||
def test_cluster_list(self):
|
||||
clusters = self.mgr.list()
|
||||
expect = [
|
||||
('GET', '/v1/clusters', {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertThat(clusters, matchers.HasLength(2))
|
||||
|
||||
def _test_cluster_list_with_filters(self, limit=None, marker=None,
|
||||
sort_key=None, sort_dir=None,
|
||||
detail=False, expect=[]):
|
||||
clusters_filter = self.mgr.list(limit=limit,
|
||||
marker=marker,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir,
|
||||
detail=detail)
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertThat(clusters_filter, matchers.HasLength(2))
|
||||
|
||||
def test_cluster_list_with_limit(self):
|
||||
expect = [
|
||||
('GET', '/v1/clusters/?limit=2', {}, None),
|
||||
]
|
||||
self._test_cluster_list_with_filters(
|
||||
limit=2,
|
||||
expect=expect)
|
||||
|
||||
def test_cluster_list_with_marker(self):
|
||||
expect = [
|
||||
('GET', '/v1/clusters/?marker=%s' % CLUSTER2['uuid'], {}, None),
|
||||
]
|
||||
self._test_cluster_list_with_filters(
|
||||
marker=CLUSTER2['uuid'],
|
||||
expect=expect)
|
||||
|
||||
def test_cluster_list_with_marker_limit(self):
|
||||
expect = [
|
||||
('GET', '/v1/clusters/?limit=2&marker=%s' % CLUSTER2['uuid'],
|
||||
{},
|
||||
None),
|
||||
]
|
||||
self._test_cluster_list_with_filters(
|
||||
limit=2, marker=CLUSTER2['uuid'],
|
||||
expect=expect)
|
||||
|
||||
def test_cluster_list_with_sort_dir(self):
|
||||
expect = [
|
||||
('GET', '/v1/clusters/?sort_dir=asc', {}, None),
|
||||
]
|
||||
self._test_cluster_list_with_filters(
|
||||
sort_dir='asc',
|
||||
expect=expect)
|
||||
|
||||
def test_cluster_list_with_sort_key(self):
|
||||
expect = [
|
||||
('GET', '/v1/clusters/?sort_key=uuid', {}, None),
|
||||
]
|
||||
self._test_cluster_list_with_filters(
|
||||
sort_key='uuid',
|
||||
expect=expect)
|
||||
|
||||
def test_cluster_list_with_sort_key_dir(self):
|
||||
expect = [
|
||||
('GET', '/v1/clusters/?sort_key=uuid&sort_dir=desc', {}, None),
|
||||
]
|
||||
self._test_cluster_list_with_filters(
|
||||
sort_key='uuid', sort_dir='desc',
|
||||
expect=expect)
|
||||
|
||||
def test_cluster_show_by_id(self):
|
||||
cluster = self.mgr.get(CLUSTER1['id'])
|
||||
expect = [
|
||||
('GET', '/v1/clusters/%s' % CLUSTER1['id'], {}, None)
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(CLUSTER1['name'], cluster.name)
|
||||
self.assertEqual(CLUSTER1['cluster_template_id'],
|
||||
cluster.cluster_template_id)
|
||||
|
||||
def test_cluster_show_by_name(self):
|
||||
cluster = self.mgr.get(CLUSTER1['name'])
|
||||
expect = [
|
||||
('GET', '/v1/clusters/%s' % CLUSTER1['name'], {}, None)
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(CLUSTER1['name'], cluster.name)
|
||||
self.assertEqual(CLUSTER1['cluster_template_id'],
|
||||
cluster.cluster_template_id)
|
||||
|
||||
def test_cluster_create(self):
|
||||
cluster = self.mgr.create(**CREATE_CLUSTER)
|
||||
expect = [
|
||||
('POST', '/v1/clusters', {}, CREATE_CLUSTER),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertTrue(cluster)
|
||||
|
||||
def test_cluster_create_with_keypair(self):
|
||||
cluster_with_keypair = dict()
|
||||
cluster_with_keypair.update(CREATE_CLUSTER)
|
||||
cluster_with_keypair['keypair'] = 'test_key'
|
||||
cluster = self.mgr.create(**cluster_with_keypair)
|
||||
expect = [
|
||||
('POST', '/v1/clusters', {}, cluster_with_keypair),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertTrue(cluster)
|
||||
|
||||
def test_cluster_create_with_docker_volume_size(self):
|
||||
cluster_with_volume_size = dict()
|
||||
cluster_with_volume_size.update(CREATE_CLUSTER)
|
||||
cluster_with_volume_size['docker_volume_size'] = 20
|
||||
cluster = self.mgr.create(**cluster_with_volume_size)
|
||||
expect = [
|
||||
('POST', '/v1/clusters', {}, cluster_with_volume_size),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertTrue(cluster)
|
||||
|
||||
def test_cluster_create_with_discovery_url(self):
|
||||
cluster_with_discovery = dict()
|
||||
cluster_with_discovery.update(CREATE_CLUSTER)
|
||||
cluster_with_discovery['discovery_url'] = 'discovery_url'
|
||||
cluster = self.mgr.create(**cluster_with_discovery)
|
||||
expect = [
|
||||
('POST', '/v1/clusters', {}, cluster_with_discovery),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertTrue(cluster)
|
||||
|
||||
def test_cluster_create_with_cluster_create_timeout(self):
|
||||
cluster_with_timeout = dict()
|
||||
cluster_with_timeout.update(CREATE_CLUSTER)
|
||||
cluster_with_timeout['create_timeout'] = '15'
|
||||
cluster = self.mgr.create(**cluster_with_timeout)
|
||||
expect = [
|
||||
('POST', '/v1/clusters', {}, cluster_with_timeout),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertTrue(cluster)
|
||||
|
||||
def test_cluster_create_fail(self):
|
||||
CREATE_CLUSTER_FAIL = copy.deepcopy(CREATE_CLUSTER)
|
||||
CREATE_CLUSTER_FAIL["wrong_key"] = "wrong"
|
||||
self.assertRaisesRegex(exceptions.InvalidAttribute,
|
||||
("Key must be in %s" %
|
||||
','.join(clusters.CREATION_ATTRIBUTES)),
|
||||
self.mgr.create, **CREATE_CLUSTER_FAIL)
|
||||
self.assertEqual([], self.api.calls)
|
||||
|
||||
def test_cluster_delete_by_id(self):
|
||||
cluster = self.mgr.delete(CLUSTER1['id'])
|
||||
expect = [
|
||||
('DELETE', '/v1/clusters/%s' % CLUSTER1['id'], {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertIsNone(cluster)
|
||||
|
||||
def test_cluster_delete_by_name(self):
|
||||
cluster = self.mgr.delete(CLUSTER1['name'])
|
||||
expect = [
|
||||
('DELETE', '/v1/clusters/%s' % CLUSTER1['name'], {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertIsNone(cluster)
|
||||
|
||||
def test_cluster_update(self):
|
||||
patch = {'op': 'replace',
|
||||
'value': NEW_NAME,
|
||||
'path': '/name'}
|
||||
cluster = self.mgr.update(id=CLUSTER1['id'], patch=patch)
|
||||
expect = [
|
||||
('PATCH', '/v1/clusters/%s' % CLUSTER1['id'], {}, patch),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(NEW_NAME, cluster.name)
|
||||
|
||||
def test_cluster_update_with_rollback(self):
|
||||
patch = {'op': 'replace',
|
||||
'value': NEW_NAME,
|
||||
'path': '/name'}
|
||||
cluster = self.mgr.update(id=CLUSTER1['id'], patch=patch,
|
||||
rollback=True)
|
||||
expect = [
|
||||
('PATCH', '/v1/clusters/%s/?rollback=True' % CLUSTER1['id'],
|
||||
{}, patch),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(NEW_NAME, cluster.name)
|
|
@ -1,520 +0,0 @@
|
|||
# Copyright 2015 NEC Corporation. All rights reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import mock
|
||||
|
||||
from magnumclient.common import cliutils
|
||||
from magnumclient import exceptions
|
||||
from magnumclient.tests.v1 import shell_test_base
|
||||
from magnumclient.tests.v1 import test_clustertemplates_shell
|
||||
from magnumclient.v1.clusters import Cluster
|
||||
|
||||
|
||||
class FakeCluster(Cluster):
|
||||
def __init__(self, manager=None, info={}, **kwargs):
|
||||
Cluster.__init__(self, manager=manager, info=info)
|
||||
self.uuid = kwargs.get('uuid', 'x')
|
||||
self.keypair = kwargs.get('keypair', 'x')
|
||||
self.docker_volume_size = kwargs.get('docker_volume_size', 3)
|
||||
self.name = kwargs.get('name', 'x')
|
||||
self.cluster_template_id = kwargs.get('cluster_template_id', 'x')
|
||||
self.stack_id = kwargs.get('stack_id', 'x')
|
||||
self.status = kwargs.get('status', 'x')
|
||||
self.master_count = kwargs.get('master_count', 1)
|
||||
self.node_count = kwargs.get('node_count', 1)
|
||||
self.links = kwargs.get('links', [])
|
||||
self.create_timeout = kwargs.get('create_timeout', 60)
|
||||
|
||||
|
||||
class FakeCert(object):
|
||||
def __init__(self, pem):
|
||||
self.pem = pem
|
||||
|
||||
|
||||
class ShellTest(shell_test_base.TestCommandLineArgument):
|
||||
|
||||
def _get_expected_args_list(self, marker=None, limit=None,
|
||||
sort_dir=None, sort_key=None):
|
||||
expected_args = {}
|
||||
expected_args['marker'] = marker
|
||||
expected_args['limit'] = limit
|
||||
expected_args['sort_dir'] = sort_dir
|
||||
expected_args['sort_key'] = sort_key
|
||||
|
||||
return expected_args
|
||||
|
||||
def _get_expected_args_create(self, cluster_template_id, name=None,
|
||||
master_count=1, node_count=1,
|
||||
create_timeout=60, keypair=None,
|
||||
docker_volume_size=None,
|
||||
discovery_url=None):
|
||||
expected_args = {}
|
||||
expected_args['name'] = name
|
||||
expected_args['cluster_template_id'] = cluster_template_id
|
||||
expected_args['master_count'] = master_count
|
||||
expected_args['node_count'] = node_count
|
||||
expected_args['create_timeout'] = create_timeout
|
||||
expected_args['discovery_url'] = discovery_url
|
||||
expected_args['keypair'] = keypair
|
||||
if docker_volume_size is not None:
|
||||
expected_args['docker_volume_size'] = docker_volume_size
|
||||
|
||||
return expected_args
|
||||
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.list')
|
||||
def test_cluster_list_success(self, mock_list):
|
||||
self._test_arg_success('cluster-list')
|
||||
expected_args = self._get_expected_args_list()
|
||||
mock_list.assert_called_once_with(**expected_args)
|
||||
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.list')
|
||||
def test_cluster_list_success_with_arg(self, mock_list):
|
||||
self._test_arg_success('cluster-list '
|
||||
'--marker some_uuid '
|
||||
'--limit 1 '
|
||||
'--sort-dir asc '
|
||||
'--sort-key uuid')
|
||||
expected_args = self._get_expected_args_list('some_uuid', 1,
|
||||
'asc', 'uuid')
|
||||
mock_list.assert_called_once_with(**expected_args)
|
||||
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.list')
|
||||
def test_cluster_list_ignored_duplicated_field(self, mock_list):
|
||||
mock_list.return_value = [FakeCluster()]
|
||||
self._test_arg_success(
|
||||
'cluster-list --fields status,status,status,name',
|
||||
keyword=('\n| uuid | name | keypair | docker_volume_size | '
|
||||
'node_count | master_count | status |\n'))
|
||||
expected_args = self._get_expected_args_list()
|
||||
mock_list.assert_called_once_with(**expected_args)
|
||||
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.list')
|
||||
def test_cluster_list_failure_with_invalid_field(self, mock_list):
|
||||
mock_list.return_value = [FakeCluster()]
|
||||
_error_msg = [".*?^Non-existent fields are specified: ['xxx','zzz']"]
|
||||
self.assertRaises(exceptions.CommandError,
|
||||
self._test_arg_failure,
|
||||
'cluster-list --fields xxx,stack_id,zzz,status',
|
||||
_error_msg)
|
||||
expected_args = self._get_expected_args_list()
|
||||
mock_list.assert_called_once_with(**expected_args)
|
||||
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.list')
|
||||
def test_cluster_list_failure_invalid_arg(self, mock_list):
|
||||
_error_msg = [
|
||||
'.*?^usage: magnum cluster-list ',
|
||||
'.*?^error: argument --sort-dir: invalid choice: ',
|
||||
".*?^Try 'magnum help cluster-list' for more information."
|
||||
]
|
||||
self._test_arg_failure('cluster-list --sort-dir aaa', _error_msg)
|
||||
mock_list.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.list')
|
||||
def test_cluster_list_failure(self, mock_list):
|
||||
self._test_arg_failure('cluster-list --wrong',
|
||||
self._unrecognized_arg_error)
|
||||
mock_list.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.cluster_templates.ClusterTemplateManager.get')
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.create')
|
||||
def test_cluster_create_success(self, mock_create, mock_get):
|
||||
mock_ct = mock.MagicMock()
|
||||
mock_ct.uuid = 'xxx'
|
||||
mock_get.return_value = mock_ct
|
||||
self._test_arg_success('cluster-create test '
|
||||
'--cluster-template xxx '
|
||||
'--node-count 123 --timeout 15')
|
||||
expected_args = self._get_expected_args_create('xxx', name='test',
|
||||
node_count=123,
|
||||
create_timeout=15)
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
self._test_arg_success('cluster-create --cluster-template xxx')
|
||||
expected_args = self._get_expected_args_create('xxx')
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
self._test_arg_success('cluster-create --cluster-template xxx '
|
||||
'--keypair x')
|
||||
expected_args = self._get_expected_args_create('xxx',
|
||||
keypair='x')
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
self._test_arg_success('cluster-create --cluster-template xxx '
|
||||
'--docker-volume-size 20')
|
||||
expected_args = self._get_expected_args_create('xxx',
|
||||
docker_volume_size=20)
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
self._test_arg_success('cluster-create test '
|
||||
'--cluster-template xxx')
|
||||
expected_args = self._get_expected_args_create('xxx',
|
||||
name='test')
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
self._test_arg_success('cluster-create --cluster-template xxx '
|
||||
'--node-count 123')
|
||||
expected_args = self._get_expected_args_create('xxx',
|
||||
node_count=123)
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
self._test_arg_success('cluster-create --cluster-template xxx '
|
||||
'--node-count 123 --master-count 123')
|
||||
expected_args = self._get_expected_args_create('xxx',
|
||||
master_count=123,
|
||||
node_count=123)
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
self._test_arg_success('cluster-create --cluster-template xxx '
|
||||
'--timeout 15')
|
||||
expected_args = self._get_expected_args_create('xxx',
|
||||
create_timeout=15)
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
@mock.patch('magnumclient.v1.cluster_templates.ClusterTemplateManager.get')
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.create')
|
||||
def test_cluster_create_deprecation_warnings(self, mock_create,
|
||||
mock_get):
|
||||
self._test_arg_failure('cluster-create --cluster-template xxx '
|
||||
'--keypair-id x',
|
||||
self._deprecated_warning)
|
||||
self.assertTrue(mock_create.called)
|
||||
self.assertTrue(mock_get.called)
|
||||
|
||||
self._test_arg_failure('cluster-create --cluster-template xxx '
|
||||
'--name foo ',
|
||||
self._deprecated_warning)
|
||||
self.assertTrue(mock_create.called)
|
||||
self.assertTrue(mock_get.called)
|
||||
|
||||
@mock.patch('magnumclient.v1.cluster_templates.ClusterTemplateManager.get')
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.create')
|
||||
def test_cluster_create_deprecation_errors(self,
|
||||
mock_create,
|
||||
mock_get):
|
||||
self._test_arg_failure('cluster-create --cluster-template xxx '
|
||||
'--keypair-id x --keypair x',
|
||||
self._too_many_group_arg_error)
|
||||
self.assertFalse(mock_create.called)
|
||||
self.assertFalse(mock_get.called)
|
||||
|
||||
@mock.patch('magnumclient.v1.cluster_templates.ClusterTemplateManager.get')
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.get')
|
||||
def test_cluster_show_clustertemplate_metadata(self,
|
||||
mock_cluster,
|
||||
mock_clustertemplate):
|
||||
mock_cluster.return_value = mock.MagicMock(cluster_template_id=0)
|
||||
|
||||
mock_clustertemplate.return_value = \
|
||||
test_clustertemplates_shell.FakeClusterTemplate(info={'links': 0,
|
||||
'uuid': 0,
|
||||
'id': 0,
|
||||
'name': ''})
|
||||
|
||||
self._test_arg_success('cluster-show --long x')
|
||||
mock_cluster.assert_called_once_with('x')
|
||||
mock_clustertemplate.assert_called_once_with(0)
|
||||
|
||||
@mock.patch('magnumclient.v1.cluster_templates.ClusterTemplateManager.get')
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.create')
|
||||
def _test_cluster_create_success(self, cmd, expected_args, expected_kwargs,
|
||||
mock_create, mock_get):
|
||||
mock_ct = mock.MagicMock()
|
||||
mock_ct.uuid = 'xxx'
|
||||
mock_get.return_value = mock_ct
|
||||
self._test_arg_success(cmd)
|
||||
expected = self._get_expected_args_create(*expected_args,
|
||||
**expected_kwargs)
|
||||
mock_create.assert_called_with(**expected)
|
||||
|
||||
def test_cluster_create_success_only_clustertemplate_arg(self):
|
||||
self._test_cluster_create_success(
|
||||
'cluster-create --cluster-template xxx',
|
||||
['xxx'],
|
||||
{})
|
||||
|
||||
@mock.patch('magnumclient.v1.cluster_templates.ClusterTemplateManager.get')
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.create')
|
||||
def test_cluster_create_success_only_positional_name(self,
|
||||
mock_create,
|
||||
mock_get):
|
||||
self._test_cluster_create_success(
|
||||
'cluster-create foo --cluster-template xxx',
|
||||
['xxx'],
|
||||
{'name': 'foo'})
|
||||
|
||||
@mock.patch('magnumclient.v1.cluster_templates.ClusterTemplateManager.get')
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.create')
|
||||
def test_cluster_create_success_only_optional_name(self,
|
||||
mock_create,
|
||||
mock_get):
|
||||
self._test_cluster_create_success(
|
||||
'cluster-create --name foo --cluster-template xxx',
|
||||
['xxx'],
|
||||
{'name': 'foo'})
|
||||
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.create')
|
||||
def test_cluster_create_failure_only_name(self, mock_create):
|
||||
self._test_arg_failure('cluster-create --name test',
|
||||
self._mandatory_arg_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.create')
|
||||
def test_cluster_create_failure_only_keypair(self, mock_create):
|
||||
self._test_arg_failure('cluster-create --keypair test',
|
||||
self._mandatory_arg_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.create')
|
||||
def test_cluster_create_failure_only_docker_volume_size(self, mock_create):
|
||||
self._test_arg_failure('cluster-create --docker_volume_size 20',
|
||||
self._mandatory_arg_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.create')
|
||||
def test_cluster_create_failure_only_node_count(self, mock_create):
|
||||
self._test_arg_failure('cluster-create --node-count 1',
|
||||
self._mandatory_arg_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.create')
|
||||
def test_cluster_create_failure_invalid_node_count(self, mock_create):
|
||||
self._test_arg_failure('cluster-create --cluster-template xxx '
|
||||
'--node-count test',
|
||||
self._invalid_value_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.create')
|
||||
def test_cluster_create_failure_only_cluster_create_timeout(self,
|
||||
mock_create):
|
||||
self._test_arg_failure('cluster-create --timeout 15',
|
||||
self._mandatory_arg_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.create')
|
||||
def test_cluster_create_failure_no_arg(self, mock_create):
|
||||
self._test_arg_failure('cluster-create',
|
||||
self._mandatory_arg_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.create')
|
||||
def test_cluster_create_failure_invalid_master_count(self, mock_create):
|
||||
self._test_arg_failure('cluster-create --cluster-template xxx '
|
||||
'--master-count test',
|
||||
self._invalid_value_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.create')
|
||||
def test_cluster_create_failure_duplicate_name(self, mock_create):
|
||||
self.assertRaises(cliutils.DuplicateArgs,
|
||||
self._test_arg_failure,
|
||||
'cluster-create foo --name bar '
|
||||
'--cluster-template xxx',
|
||||
self._duplicate_name_arg_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.delete')
|
||||
def test_cluster_delete_success(self, mock_delete):
|
||||
self._test_arg_success('cluster-delete xxx')
|
||||
mock_delete.assert_called_once_with('xxx')
|
||||
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.delete')
|
||||
def test_cluster_delete_multiple_id_success(self, mock_delete):
|
||||
self._test_arg_success('cluster-delete xxx xyz')
|
||||
calls = [mock.call('xxx'), mock.call('xyz')]
|
||||
mock_delete.assert_has_calls(calls)
|
||||
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.delete')
|
||||
def test_cluster_delete_failure_no_arg(self, mock_delete):
|
||||
self._test_arg_failure('cluster-delete', self._few_argument_error)
|
||||
mock_delete.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.get')
|
||||
def test_cluster_show_success(self, mock_show):
|
||||
self._test_arg_success('cluster-show xxx')
|
||||
mock_show.assert_called_once_with('xxx')
|
||||
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.get')
|
||||
def test_cluster_show_failure_no_arg(self, mock_show):
|
||||
self._test_arg_failure('cluster-show', self._few_argument_error)
|
||||
mock_show.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.update')
|
||||
def test_cluster_update_success(self, mock_update):
|
||||
self._test_arg_success('cluster-update test add test=test')
|
||||
patch = [{'op': 'add', 'path': '/test', 'value': 'test'}]
|
||||
mock_update.assert_called_once_with('test', patch, False)
|
||||
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.update')
|
||||
def test_cluster_update_success_many_attribute(self, mock_update):
|
||||
self._test_arg_success('cluster-update test add test=test test1=test1')
|
||||
patch = [{'op': 'add', 'path': '/test', 'value': 'test'},
|
||||
{'op': 'add', 'path': '/test1', 'value': 'test1'}]
|
||||
mock_update.assert_called_once_with('test', patch, False)
|
||||
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.update')
|
||||
def test_cluster_update_success_rollback(self, mock_update):
|
||||
self._test_arg_success('cluster-update test add test=test --rollback')
|
||||
patch = [{'op': 'add', 'path': '/test', 'value': 'test'}]
|
||||
mock_update.assert_called_once_with('test', patch, True)
|
||||
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.update')
|
||||
def test_cluster_update_rollback_old_api_version(self, mock_update):
|
||||
self.assertRaises(
|
||||
exceptions.CommandError,
|
||||
self.shell,
|
||||
'--magnum-api-version 1.2 cluster-update '
|
||||
'test add test=test --rollback')
|
||||
mock_update.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.update')
|
||||
def test_cluster_update_failure_wrong_op(self, mock_update):
|
||||
_error_msg = [
|
||||
'.*?^usage: magnum cluster-update ',
|
||||
'.*?^error: argument <op>: invalid choice: ',
|
||||
".*?^Try 'magnum help cluster-update' for more information."
|
||||
]
|
||||
self._test_arg_failure('cluster-update test wrong test=test',
|
||||
_error_msg)
|
||||
mock_update.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.update')
|
||||
def test_cluster_update_failure_wrong_attribute(self, mock_update):
|
||||
_error_msg = [
|
||||
'.*?^ERROR: Attributes must be a list of PATH=VALUE'
|
||||
]
|
||||
self.assertRaises(exceptions.CommandError, self._test_arg_failure,
|
||||
'cluster-update test add test', _error_msg)
|
||||
mock_update.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.update')
|
||||
def test_cluster_update_failure_few_args(self, mock_update):
|
||||
_error_msg = [
|
||||
'.*?^usage: magnum cluster-update ',
|
||||
'.*?^error: (the following arguments|too few arguments)',
|
||||
".*?^Try 'magnum help cluster-update' for more information."
|
||||
]
|
||||
self._test_arg_failure('cluster-update', _error_msg)
|
||||
mock_update.assert_not_called()
|
||||
|
||||
self._test_arg_failure('cluster-update test', _error_msg)
|
||||
mock_update.assert_not_called()
|
||||
|
||||
self._test_arg_failure('cluster-update test add', _error_msg)
|
||||
mock_update.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.cluster_templates.ClusterTemplateManager.get')
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.get')
|
||||
def test_cluster_config_success(self, mock_cluster, mock_clustertemplate):
|
||||
mock_cluster.return_value = FakeCluster(status='UPDATE_COMPLETE')
|
||||
self._test_arg_success('cluster-config xxx')
|
||||
mock_cluster.assert_called_with('xxx')
|
||||
|
||||
mock_cluster.return_value = FakeCluster(status='CREATE_COMPLETE')
|
||||
self._test_arg_success('cluster-config xxx')
|
||||
mock_cluster.assert_called_with('xxx')
|
||||
|
||||
self._test_arg_success('cluster-config --dir /tmp xxx')
|
||||
mock_cluster.assert_called_with('xxx')
|
||||
|
||||
self._test_arg_success('cluster-config --force xxx')
|
||||
mock_cluster.assert_called_with('xxx')
|
||||
|
||||
self._test_arg_success('cluster-config --dir /tmp --force xxx')
|
||||
mock_cluster.assert_called_with('xxx')
|
||||
|
||||
@mock.patch('magnumclient.v1.cluster_templates.ClusterTemplateManager.get')
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.get')
|
||||
def test_cluster_config_failure_wrong_status(self,
|
||||
mock_cluster,
|
||||
mock_clustertemplate):
|
||||
mock_cluster.return_value = FakeCluster(status='CREATE_IN_PROGRESS')
|
||||
self.assertRaises(exceptions.CommandError,
|
||||
self._test_arg_failure,
|
||||
'cluster-config xxx',
|
||||
['.*?^Cluster in status: '])
|
||||
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.get')
|
||||
def test_cluster_config_failure_no_arg(self, mock_cluster):
|
||||
self._test_arg_failure('cluster-config', self._few_argument_error)
|
||||
mock_cluster.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.get')
|
||||
def test_cluster_config_failure_wrong_arg(self, mock_cluster):
|
||||
self._test_arg_failure('cluster-config xxx yyy',
|
||||
self._unrecognized_arg_error)
|
||||
mock_cluster.assert_not_called()
|
||||
|
||||
@mock.patch('os.path.exists')
|
||||
@mock.patch('magnumclient.v1.certificates.CertificateManager.create')
|
||||
@mock.patch('magnumclient.v1.certificates.CertificateManager.get')
|
||||
@mock.patch('magnumclient.v1.cluster_templates.ClusterTemplateManager.get')
|
||||
@mock.patch('magnumclient.v1.clusters.ClusterManager.get')
|
||||
def _test_cluster_config_success(self, mock_cluster, mock_ct,
|
||||
mock_cert_get, mock_cert_create,
|
||||
mock_exists, coe, shell, tls_disable):
|
||||
cert = FakeCert(pem='foo bar')
|
||||
mock_exists.return_value = False
|
||||
mock_cluster.return_value = FakeCluster(status='CREATE_COMPLETE',
|
||||
info={
|
||||
'name': 'Kluster',
|
||||
'api_address': '10.0.0.1'},
|
||||
cluster_template_id='fake_ct',
|
||||
uuid='fake_cluster')
|
||||
mock_cert_get.return_value = cert
|
||||
mock_cert_create.return_value = cert
|
||||
mock_ct.return_value = test_clustertemplates_shell.\
|
||||
FakeClusterTemplate(coe=coe, name='fake_ct',
|
||||
tls_disabled=tls_disable)
|
||||
with mock.patch.dict('os.environ', {'SHELL': shell}):
|
||||
self._test_arg_success('cluster-config test_cluster')
|
||||
|
||||
self.assertTrue(mock_exists.called)
|
||||
mock_cluster.assert_called_once_with('test_cluster')
|
||||
mock_ct.assert_called_once_with('fake_ct')
|
||||
if not tls_disable:
|
||||
mock_cert_create.assert_called_once_with(
|
||||
cluster_uuid='fake_cluster', csr=mock.ANY)
|
||||
mock_cert_get.assert_called_once_with(cluster_uuid='fake_cluster')
|
||||
|
||||
def test_cluster_config_swarm_success_with_tls_csh(self):
|
||||
self._test_cluster_config_success(coe='swarm', shell='csh',
|
||||
tls_disable=False)
|
||||
|
||||
def test_cluster_config_swarm_success_with_tls_non_csh(self):
|
||||
self._test_cluster_config_success(coe='swarm', shell='zsh',
|
||||
tls_disable=False)
|
||||
|
||||
def test_cluster_config_swarm_success_without_tls_csh(self):
|
||||
self._test_cluster_config_success(coe='swarm', shell='csh',
|
||||
tls_disable=True)
|
||||
|
||||
def test_cluster_config_swarm_success_without_tls_non_csh(self):
|
||||
self._test_cluster_config_success(coe='swarm', shell='zsh',
|
||||
tls_disable=True)
|
||||
|
||||
def test_cluster_config_k8s_success_with_tls_csh(self):
|
||||
self._test_cluster_config_success(coe='kubernetes', shell='csh',
|
||||
tls_disable=False)
|
||||
|
||||
def test_cluster_config_k8s_success_with_tls_non_csh(self):
|
||||
self._test_cluster_config_success(coe='kubernetes', shell='zsh',
|
||||
tls_disable=False)
|
||||
|
||||
def test_cluster_config_k8s_success_without_tls_csh(self):
|
||||
self._test_cluster_config_success(coe='kubernetes', shell='csh',
|
||||
tls_disable=True)
|
||||
|
||||
def test_cluster_config_k8s_success_without_tls_non_csh(self):
|
||||
self._test_cluster_config_success(coe='kubernetes', shell='zsh',
|
||||
tls_disable=True)
|
|
@ -1,451 +0,0 @@
|
|||
# Copyright 2015 IBM Corp
|
||||
#
|
||||
# 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.
|
||||
|
||||
import copy
|
||||
|
||||
import testtools
|
||||
from testtools import matchers
|
||||
|
||||
from magnumclient import exceptions
|
||||
from magnumclient.tests import utils
|
||||
from magnumclient.v1 import cluster_templates
|
||||
|
||||
|
||||
CLUSTERTEMPLATE1 = {
|
||||
'id': 123,
|
||||
'uuid': '66666666-7777-8888-9999-000000000001',
|
||||
'name': 'clustertemplate1',
|
||||
'image_id': 'clustertemplate1-image',
|
||||
'master_flavor_id': 'm1.tiny',
|
||||
'flavor_id': 'm1.small',
|
||||
'external_network_id': 'd1f02cfb-d27f-4068-9332-84d907cb0e21',
|
||||
'fixed_network': 'private',
|
||||
'fixed_subnet': 'private-subnet',
|
||||
'network_driver': 'libnetwork',
|
||||
'volume_driver': 'rexray',
|
||||
'dns_nameserver': '8.8.1.1',
|
||||
'docker_volume_size': '71',
|
||||
'docker_storage_driver': 'devicemapper',
|
||||
'coe': 'swarm',
|
||||
'http_proxy': 'http_proxy',
|
||||
'https_proxy': 'https_proxy',
|
||||
'no_proxy': 'no_proxy',
|
||||
'labels': 'key1=val1,key11=val11',
|
||||
'tls_disabled': False,
|
||||
'public': False,
|
||||
'registry_enabled': False,
|
||||
'master_lb_enabled': True,
|
||||
'floating_ip_enabled': True
|
||||
}
|
||||
|
||||
CLUSTERTEMPLATE2 = {
|
||||
'id': 124,
|
||||
'uuid': '66666666-7777-8888-9999-000000000002',
|
||||
'name': 'clustertemplate2',
|
||||
'image_id': 'clustertemplate2-image',
|
||||
'flavor_id': 'm2.small',
|
||||
'master_flavor_id': 'm2.tiny',
|
||||
'external_network_id': 'd1f02cfb-d27f-4068-9332-84d907cb0e22',
|
||||
'fixed_network': 'private2',
|
||||
'network_driver': 'flannel',
|
||||
'volume_driver': 'cinder',
|
||||
'dns_nameserver': '8.8.1.2',
|
||||
'docker_volume_size': '71',
|
||||
'docker_storage_driver': 'overlay',
|
||||
'coe': 'kubernetes',
|
||||
'labels': 'key2=val2,key22=val22',
|
||||
'tls_disabled': True,
|
||||
'public': True,
|
||||
'registry_enabled': True}
|
||||
|
||||
CREATE_CLUSTERTEMPLATE = copy.deepcopy(CLUSTERTEMPLATE1)
|
||||
del CREATE_CLUSTERTEMPLATE['id']
|
||||
del CREATE_CLUSTERTEMPLATE['uuid']
|
||||
|
||||
UPDATED_CLUSTERTEMPLATE = copy.deepcopy(CLUSTERTEMPLATE1)
|
||||
NEW_NAME = 'newcluster'
|
||||
UPDATED_CLUSTERTEMPLATE['name'] = NEW_NAME
|
||||
|
||||
fake_responses = {
|
||||
'/v1/clustertemplates':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{'clustertemplates': [CLUSTERTEMPLATE1, CLUSTERTEMPLATE2]},
|
||||
),
|
||||
'POST': (
|
||||
{},
|
||||
CREATE_CLUSTERTEMPLATE,
|
||||
),
|
||||
},
|
||||
'/v1/clustertemplates/%s' % CLUSTERTEMPLATE1['id']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
CLUSTERTEMPLATE1
|
||||
),
|
||||
'DELETE': (
|
||||
{},
|
||||
None,
|
||||
),
|
||||
'PATCH': (
|
||||
{},
|
||||
UPDATED_CLUSTERTEMPLATE,
|
||||
),
|
||||
},
|
||||
'/v1/clustertemplates/%s' % CLUSTERTEMPLATE1['name']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
CLUSTERTEMPLATE1
|
||||
),
|
||||
'DELETE': (
|
||||
{},
|
||||
None,
|
||||
),
|
||||
'PATCH': (
|
||||
{},
|
||||
UPDATED_CLUSTERTEMPLATE,
|
||||
),
|
||||
},
|
||||
'/v1/clustertemplates/detail':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{'clustertemplates': [CLUSTERTEMPLATE1, CLUSTERTEMPLATE2]},
|
||||
),
|
||||
},
|
||||
'/v1/clustertemplates/?limit=2':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{'clustertemplates': [CLUSTERTEMPLATE1, CLUSTERTEMPLATE2]},
|
||||
),
|
||||
},
|
||||
'/v1/clustertemplates/?marker=%s' % CLUSTERTEMPLATE2['uuid']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{'clustertemplates': [CLUSTERTEMPLATE1, CLUSTERTEMPLATE2]},
|
||||
),
|
||||
},
|
||||
'/v1/clustertemplates/?limit=2&marker=%s' % CLUSTERTEMPLATE2['uuid']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{'clustertemplates': [CLUSTERTEMPLATE1, CLUSTERTEMPLATE2]},
|
||||
),
|
||||
},
|
||||
'/v1/clustertemplates/?sort_dir=asc':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{'clustertemplates': [CLUSTERTEMPLATE1, CLUSTERTEMPLATE2]},
|
||||
),
|
||||
},
|
||||
'/v1/clustertemplates/?sort_key=uuid':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{'clustertemplates': [CLUSTERTEMPLATE1, CLUSTERTEMPLATE2]},
|
||||
),
|
||||
},
|
||||
'/v1/clustertemplates/?sort_key=uuid&sort_dir=desc':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{'clustertemplates': [CLUSTERTEMPLATE2, CLUSTERTEMPLATE1]},
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class ClusterTemplateManagerTest(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ClusterTemplateManagerTest, self).setUp()
|
||||
self.api = utils.FakeAPI(fake_responses)
|
||||
self.mgr = cluster_templates.ClusterTemplateManager(self.api)
|
||||
|
||||
def test_clustertemplate_list(self):
|
||||
clustertemplates = self.mgr.list()
|
||||
expect = [
|
||||
('GET', '/v1/clustertemplates', {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertThat(clustertemplates, matchers.HasLength(2))
|
||||
|
||||
def _test_clustertemplate_list_with_filters(
|
||||
self, limit=None, marker=None,
|
||||
sort_key=None, sort_dir=None,
|
||||
detail=False, expect=[]):
|
||||
clustertemplates_filter = self.mgr.list(limit=limit,
|
||||
marker=marker,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir,
|
||||
detail=detail)
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertThat(clustertemplates_filter, matchers.HasLength(2))
|
||||
|
||||
def test_clustertemplate_list_with_detail(self):
|
||||
expect = [
|
||||
('GET', '/v1/clustertemplates/detail', {}, None),
|
||||
]
|
||||
self._test_clustertemplate_list_with_filters(
|
||||
detail=True,
|
||||
expect=expect)
|
||||
|
||||
def test_clustertemplate_list_with_limit(self):
|
||||
expect = [
|
||||
('GET', '/v1/clustertemplates/?limit=2', {}, None),
|
||||
]
|
||||
self._test_clustertemplate_list_with_filters(
|
||||
limit=2,
|
||||
expect=expect)
|
||||
|
||||
def test_clustertemplate_list_with_marker(self):
|
||||
expect = [
|
||||
('GET',
|
||||
'/v1/clustertemplates/?marker=%s' % CLUSTERTEMPLATE2['uuid'],
|
||||
{},
|
||||
None),
|
||||
]
|
||||
self._test_clustertemplate_list_with_filters(
|
||||
marker=CLUSTERTEMPLATE2['uuid'],
|
||||
expect=expect)
|
||||
|
||||
def test_clustertemplate_list_with_marker_limit(self):
|
||||
expect = [
|
||||
('GET',
|
||||
'/v1/clustertemplates/?limit=2&marker=%s' %
|
||||
CLUSTERTEMPLATE2['uuid'],
|
||||
{},
|
||||
None),
|
||||
]
|
||||
self._test_clustertemplate_list_with_filters(
|
||||
limit=2, marker=CLUSTERTEMPLATE2['uuid'],
|
||||
expect=expect)
|
||||
|
||||
def test_clustertemplate_list_with_sort_dir(self):
|
||||
expect = [
|
||||
('GET', '/v1/clustertemplates/?sort_dir=asc', {}, None),
|
||||
]
|
||||
self._test_clustertemplate_list_with_filters(
|
||||
sort_dir='asc',
|
||||
expect=expect)
|
||||
|
||||
def test_clustertemplate_list_with_sort_key(self):
|
||||
expect = [
|
||||
('GET', '/v1/clustertemplates/?sort_key=uuid', {}, None),
|
||||
]
|
||||
self._test_clustertemplate_list_with_filters(
|
||||
sort_key='uuid',
|
||||
expect=expect)
|
||||
|
||||
def test_clustertemplate_list_with_sort_key_dir(self):
|
||||
expect = [
|
||||
('GET',
|
||||
'/v1/clustertemplates/?sort_key=uuid&sort_dir=desc',
|
||||
{},
|
||||
None),
|
||||
]
|
||||
self._test_clustertemplate_list_with_filters(
|
||||
sort_key='uuid', sort_dir='desc',
|
||||
expect=expect)
|
||||
|
||||
def test_clustertemplate_show_by_id(self):
|
||||
cluster_template = self.mgr.get(CLUSTERTEMPLATE1['id'])
|
||||
expect = [
|
||||
('GET',
|
||||
'/v1/clustertemplates/%s' % CLUSTERTEMPLATE1['id'],
|
||||
{},
|
||||
None)
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(CLUSTERTEMPLATE1['name'],
|
||||
cluster_template.name)
|
||||
self.assertEqual(CLUSTERTEMPLATE1['image_id'],
|
||||
cluster_template.image_id)
|
||||
self.assertEqual(CLUSTERTEMPLATE1['docker_volume_size'],
|
||||
cluster_template.docker_volume_size)
|
||||
self.assertEqual(CLUSTERTEMPLATE1['docker_storage_driver'],
|
||||
cluster_template.docker_storage_driver)
|
||||
self.assertEqual(CLUSTERTEMPLATE1['fixed_network'],
|
||||
cluster_template.fixed_network)
|
||||
self.assertEqual(CLUSTERTEMPLATE1['fixed_subnet'],
|
||||
cluster_template.fixed_subnet)
|
||||
self.assertEqual(CLUSTERTEMPLATE1['coe'],
|
||||
cluster_template.coe)
|
||||
self.assertEqual(CLUSTERTEMPLATE1['http_proxy'],
|
||||
cluster_template.http_proxy)
|
||||
self.assertEqual(CLUSTERTEMPLATE1['https_proxy'],
|
||||
cluster_template.https_proxy)
|
||||
self.assertEqual(CLUSTERTEMPLATE1['no_proxy'],
|
||||
cluster_template.no_proxy)
|
||||
self.assertEqual(CLUSTERTEMPLATE1['network_driver'],
|
||||
cluster_template.network_driver)
|
||||
self.assertEqual(CLUSTERTEMPLATE1['volume_driver'],
|
||||
cluster_template.volume_driver)
|
||||
self.assertEqual(CLUSTERTEMPLATE1['labels'],
|
||||
cluster_template.labels)
|
||||
self.assertEqual(CLUSTERTEMPLATE1['tls_disabled'],
|
||||
cluster_template.tls_disabled)
|
||||
self.assertEqual(CLUSTERTEMPLATE1['public'],
|
||||
cluster_template.public)
|
||||
self.assertEqual(CLUSTERTEMPLATE1['registry_enabled'],
|
||||
cluster_template.registry_enabled)
|
||||
self.assertEqual(CLUSTERTEMPLATE1['master_lb_enabled'],
|
||||
cluster_template.master_lb_enabled)
|
||||
self.assertEqual(CLUSTERTEMPLATE1['floating_ip_enabled'],
|
||||
cluster_template.floating_ip_enabled)
|
||||
|
||||
def test_clustertemplate_show_by_name(self):
|
||||
cluster_template = self.mgr.get(CLUSTERTEMPLATE1['name'])
|
||||
expect = [
|
||||
('GET',
|
||||
'/v1/clustertemplates/%s' % CLUSTERTEMPLATE1['name'],
|
||||
{},
|
||||
None)
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(CLUSTERTEMPLATE1['name'],
|
||||
cluster_template.name)
|
||||
self.assertEqual(CLUSTERTEMPLATE1['image_id'],
|
||||
cluster_template.image_id)
|
||||
self.assertEqual(CLUSTERTEMPLATE1['docker_volume_size'],
|
||||
cluster_template.docker_volume_size)
|
||||
self.assertEqual(CLUSTERTEMPLATE1['docker_storage_driver'],
|
||||
cluster_template.docker_storage_driver)
|
||||
self.assertEqual(CLUSTERTEMPLATE1['fixed_network'],
|
||||
cluster_template.fixed_network)
|
||||
self.assertEqual(CLUSTERTEMPLATE1['fixed_subnet'],
|
||||
cluster_template.fixed_subnet)
|
||||
self.assertEqual(CLUSTERTEMPLATE1['coe'],
|
||||
cluster_template.coe)
|
||||
self.assertEqual(CLUSTERTEMPLATE1['http_proxy'],
|
||||
cluster_template.http_proxy)
|
||||
self.assertEqual(CLUSTERTEMPLATE1['https_proxy'],
|
||||
cluster_template.https_proxy)
|
||||
self.assertEqual(CLUSTERTEMPLATE1['no_proxy'],
|
||||
cluster_template.no_proxy)
|
||||
self.assertEqual(CLUSTERTEMPLATE1['network_driver'],
|
||||
cluster_template.network_driver)
|
||||
self.assertEqual(CLUSTERTEMPLATE1['volume_driver'],
|
||||
cluster_template.volume_driver)
|
||||
self.assertEqual(CLUSTERTEMPLATE1['labels'],
|
||||
cluster_template.labels)
|
||||
self.assertEqual(CLUSTERTEMPLATE1['tls_disabled'],
|
||||
cluster_template.tls_disabled)
|
||||
self.assertEqual(CLUSTERTEMPLATE1['public'],
|
||||
cluster_template.public)
|
||||
self.assertEqual(CLUSTERTEMPLATE1['registry_enabled'],
|
||||
cluster_template.registry_enabled)
|
||||
self.assertEqual(CLUSTERTEMPLATE1['master_lb_enabled'],
|
||||
cluster_template.master_lb_enabled)
|
||||
self.assertEqual(CLUSTERTEMPLATE1['floating_ip_enabled'],
|
||||
cluster_template.floating_ip_enabled)
|
||||
|
||||
def test_clustertemplate_create(self):
|
||||
cluster_template = self.mgr.create(**CREATE_CLUSTERTEMPLATE)
|
||||
expect = [
|
||||
('POST', '/v1/clustertemplates', {}, CREATE_CLUSTERTEMPLATE),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertTrue(cluster_template)
|
||||
self.assertEqual(CLUSTERTEMPLATE1['docker_volume_size'],
|
||||
cluster_template.docker_volume_size)
|
||||
self.assertEqual(CLUSTERTEMPLATE1['docker_storage_driver'],
|
||||
cluster_template.docker_storage_driver)
|
||||
|
||||
def test_clustertemplate_create_with_keypair(self):
|
||||
cluster_template_with_keypair = dict()
|
||||
cluster_template_with_keypair.update(CREATE_CLUSTERTEMPLATE)
|
||||
cluster_template_with_keypair['keypair_id'] = 'test_key'
|
||||
|
||||
cluster_template = self.mgr.create(**cluster_template_with_keypair)
|
||||
expect = [
|
||||
('POST', '/v1/clustertemplates', {},
|
||||
cluster_template_with_keypair),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertTrue(cluster_template)
|
||||
self.assertEqual(CLUSTERTEMPLATE1['docker_volume_size'],
|
||||
cluster_template.docker_volume_size)
|
||||
self.assertEqual(CLUSTERTEMPLATE1['docker_storage_driver'],
|
||||
cluster_template.docker_storage_driver)
|
||||
|
||||
def test_clustertemplate_create_with_docker_volume_size(self):
|
||||
cluster_template_with_docker_volume_size = dict()
|
||||
cluster_template_with_docker_volume_size.update(CREATE_CLUSTERTEMPLATE)
|
||||
cluster_template_with_docker_volume_size['docker_volume_size'] = 11
|
||||
|
||||
cluster_template = self.mgr.create(
|
||||
**cluster_template_with_docker_volume_size)
|
||||
expect = [
|
||||
('POST', '/v1/clustertemplates', {},
|
||||
cluster_template_with_docker_volume_size),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertTrue(cluster_template)
|
||||
self.assertEqual(CLUSTERTEMPLATE1['docker_volume_size'],
|
||||
cluster_template.docker_volume_size)
|
||||
self.assertEqual(CLUSTERTEMPLATE1['docker_storage_driver'],
|
||||
cluster_template.docker_storage_driver)
|
||||
|
||||
def test_clustertemplate_create_fail(self):
|
||||
CREATE_CLUSTERTEMPLATE_FAIL = copy.deepcopy(CREATE_CLUSTERTEMPLATE)
|
||||
CREATE_CLUSTERTEMPLATE_FAIL["wrong_key"] = "wrong"
|
||||
self.assertRaisesRegex(
|
||||
exceptions.InvalidAttribute,
|
||||
("Key must be in %s" %
|
||||
','.join(cluster_templates.CREATION_ATTRIBUTES)),
|
||||
self.mgr.create, **CREATE_CLUSTERTEMPLATE_FAIL)
|
||||
self.assertEqual([], self.api.calls)
|
||||
|
||||
def test_clustertemplate_delete_by_id(self):
|
||||
cluster_template = self.mgr.delete(CLUSTERTEMPLATE1['id'])
|
||||
expect = [
|
||||
('DELETE',
|
||||
'/v1/clustertemplates/%s' % CLUSTERTEMPLATE1['id'],
|
||||
{},
|
||||
None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertIsNone(cluster_template)
|
||||
|
||||
def test_clustertemplate_delete_by_name(self):
|
||||
cluster_template = self.mgr.delete(CLUSTERTEMPLATE1['name'])
|
||||
expect = [
|
||||
('DELETE',
|
||||
'/v1/clustertemplates/%s' % CLUSTERTEMPLATE1['name'],
|
||||
{},
|
||||
None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertIsNone(cluster_template)
|
||||
|
||||
def test_clustertemplate_update(self):
|
||||
patch = {'op': 'replace',
|
||||
'value': NEW_NAME,
|
||||
'path': '/name'}
|
||||
cluster_template = self.mgr.update(id=CLUSTERTEMPLATE1['id'],
|
||||
patch=patch)
|
||||
expect = [
|
||||
('PATCH',
|
||||
'/v1/clustertemplates/%s' % CLUSTERTEMPLATE1['id'],
|
||||
{},
|
||||
patch),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(NEW_NAME, cluster_template.name)
|
|
@ -1,787 +0,0 @@
|
|||
# Copyright 2015 NEC Corporation. All rights reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import mock
|
||||
|
||||
from magnumclient.common.apiclient import exceptions
|
||||
from magnumclient.tests.v1 import shell_test_base
|
||||
from magnumclient.v1.cluster_templates import ClusterTemplate
|
||||
|
||||
|
||||
class FakeClusterTemplate(ClusterTemplate):
|
||||
def __init__(self, manager=None, info={}, **kwargs):
|
||||
ClusterTemplate.__init__(self, manager=manager, info=info)
|
||||
self.apiserver_port = kwargs.get('apiserver_port', None)
|
||||
self.uuid = kwargs.get('uuid', 'x')
|
||||
self.links = kwargs.get('links', [])
|
||||
self.server_type = kwargs.get('server_type', 'vm')
|
||||
self.image_id = kwargs.get('image', 'x')
|
||||
self.tls_disabled = kwargs.get('tls_disabled', False)
|
||||
self.registry_enabled = kwargs.get('registry_enabled', False)
|
||||
self.coe = kwargs.get('coe', 'x')
|
||||
self.public = kwargs.get('public', False)
|
||||
self.name = kwargs.get('name', 'x')
|
||||
|
||||
|
||||
class ShellTest(shell_test_base.TestCommandLineArgument):
|
||||
|
||||
def _get_expected_args_list(self, limit=None, sort_dir=None,
|
||||
sort_key=None, detail=False):
|
||||
expected_args = {}
|
||||
expected_args['limit'] = limit
|
||||
expected_args['sort_dir'] = sort_dir
|
||||
expected_args['sort_key'] = sort_key
|
||||
expected_args['detail'] = detail
|
||||
|
||||
return expected_args
|
||||
|
||||
def _get_expected_args(self, image_id, external_network_id, coe,
|
||||
master_flavor_id=None, name=None,
|
||||
keypair_id=None, fixed_network=None,
|
||||
fixed_subnet=None, network_driver=None,
|
||||
volume_driver=None, dns_nameserver='8.8.8.8',
|
||||
flavor_id='m1.medium',
|
||||
docker_storage_driver='devicemapper',
|
||||
docker_volume_size=None, http_proxy=None,
|
||||
https_proxy=None, no_proxy=None, labels={},
|
||||
tls_disabled=False, public=False,
|
||||
master_lb_enabled=False, server_type='vm',
|
||||
floating_ip_enabled=True,
|
||||
registry_enabled=False,
|
||||
insecure_registry=None):
|
||||
|
||||
expected_args = {}
|
||||
expected_args['image_id'] = image_id
|
||||
expected_args['external_network_id'] = external_network_id
|
||||
expected_args['coe'] = coe
|
||||
expected_args['master_flavor_id'] = master_flavor_id
|
||||
expected_args['name'] = name
|
||||
expected_args['keypair_id'] = keypair_id
|
||||
expected_args['fixed_network'] = fixed_network
|
||||
expected_args['fixed_subnet'] = fixed_subnet
|
||||
expected_args['network_driver'] = network_driver
|
||||
expected_args['volume_driver'] = volume_driver
|
||||
expected_args['dns_nameserver'] = dns_nameserver
|
||||
expected_args['flavor_id'] = flavor_id
|
||||
expected_args['docker_volume_size'] = docker_volume_size
|
||||
expected_args['docker_storage_driver'] = docker_storage_driver
|
||||
expected_args['http_proxy'] = http_proxy
|
||||
expected_args['https_proxy'] = https_proxy
|
||||
expected_args['no_proxy'] = no_proxy
|
||||
expected_args['labels'] = labels
|
||||
expected_args['tls_disabled'] = tls_disabled
|
||||
expected_args['public'] = public
|
||||
expected_args['master_lb_enabled'] = master_lb_enabled
|
||||
expected_args['server_type'] = server_type
|
||||
expected_args['floating_ip_enabled'] = floating_ip_enabled
|
||||
expected_args['registry_enabled'] = registry_enabled
|
||||
expected_args['insecure_registry'] = insecure_registry
|
||||
|
||||
return expected_args
|
||||
|
||||
@mock.patch(
|
||||
'magnumclient.v1.cluster_templates.ClusterTemplateManager.create')
|
||||
def test_cluster_template_create_success(self, mock_create):
|
||||
self._test_arg_success('cluster-template-create '
|
||||
'--name test '
|
||||
'--image-id test_image '
|
||||
'--keypair-id test_keypair '
|
||||
'--external-network-id test_net '
|
||||
'--coe swarm '
|
||||
'--dns-nameserver test_dns '
|
||||
'--flavor-id test_flavor '
|
||||
'--fixed-network private '
|
||||
'--fixed-subnet private-subnet '
|
||||
'--volume-driver test_volume '
|
||||
'--network-driver test_driver '
|
||||
'--labels key=val '
|
||||
'--master-flavor-id test_flavor '
|
||||
'--docker-volume-size 10 '
|
||||
'--docker-storage-driver devicemapper '
|
||||
'--public '
|
||||
'--server-type vm '
|
||||
'--master-lb-enabled '
|
||||
'--floating-ip-enabled ')
|
||||
expected_args = \
|
||||
self._get_expected_args(name='test', image_id='test_image',
|
||||
keypair_id='test_keypair', coe='swarm',
|
||||
external_network_id='test_net',
|
||||
dns_nameserver='test_dns', public=True,
|
||||
flavor_id='test_flavor',
|
||||
master_flavor_id='test_flavor',
|
||||
fixed_network='private',
|
||||
fixed_subnet='private-subnet',
|
||||
server_type='vm',
|
||||
network_driver='test_driver',
|
||||
volume_driver='test_volume',
|
||||
docker_storage_driver='devicemapper',
|
||||
docker_volume_size=10,
|
||||
master_lb_enabled=True,
|
||||
floating_ip_enabled=True,
|
||||
labels={'key': 'val'})
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
self._test_arg_success('cluster-template-create '
|
||||
'--keypair-id test_keypair '
|
||||
'--external-network-id test_net '
|
||||
'--image-id test_image '
|
||||
'--coe kubernetes '
|
||||
'--name test '
|
||||
'--server-type vm')
|
||||
|
||||
expected_args = \
|
||||
self._get_expected_args(name='test', image_id='test_image',
|
||||
keypair_id='test_keypair',
|
||||
coe='kubernetes',
|
||||
external_network_id='test_net',
|
||||
server_type='vm')
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
@mock.patch(
|
||||
'magnumclient.v1.cluster_templates.ClusterTemplateManager.create')
|
||||
def test_cluster_template_create_success_no_servertype(self, mock_create):
|
||||
self._test_arg_success('cluster-template-create '
|
||||
'--name test '
|
||||
'--image-id test_image '
|
||||
'--keypair-id test_keypair '
|
||||
'--external-network-id test_net '
|
||||
'--coe swarm '
|
||||
'--dns-nameserver test_dns '
|
||||
'--flavor-id test_flavor '
|
||||
'--fixed-network public '
|
||||
'--network-driver test_driver '
|
||||
'--labels key=val '
|
||||
'--master-flavor-id test_flavor '
|
||||
'--docker-volume-size 10 '
|
||||
'--docker-storage-driver devicemapper '
|
||||
'--public ')
|
||||
expected_args = \
|
||||
self._get_expected_args(name='test', image_id='test_image',
|
||||
keypair_id='test_keypair', coe='swarm',
|
||||
external_network_id='test_net',
|
||||
dns_nameserver='test_dns', public=True,
|
||||
flavor_id='test_flavor',
|
||||
master_flavor_id='test_flavor',
|
||||
fixed_network='public',
|
||||
network_driver='test_driver',
|
||||
docker_storage_driver='devicemapper',
|
||||
docker_volume_size=10,
|
||||
labels={'key': 'val'})
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
self._test_arg_success('cluster-template-create '
|
||||
'--keypair-id test_keypair '
|
||||
'--external-network-id test_net '
|
||||
'--image-id test_image '
|
||||
'--coe kubernetes '
|
||||
'--name test ')
|
||||
|
||||
expected_args = \
|
||||
self._get_expected_args(name='test', image_id='test_image',
|
||||
keypair_id='test_keypair',
|
||||
coe='kubernetes',
|
||||
external_network_id='test_net')
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
@mock.patch(
|
||||
'magnumclient.v1.cluster_templates.ClusterTemplateManager.create')
|
||||
def test_cluster_template_create_success_with_registry_enabled(
|
||||
self, mock_create):
|
||||
self._test_arg_success('cluster-template-create '
|
||||
'--name test '
|
||||
'--network-driver test_driver '
|
||||
'--keypair-id test_keypair '
|
||||
'--external-network-id test_net '
|
||||
'--image-id test_image '
|
||||
'--coe swarm '
|
||||
'--registry-enabled')
|
||||
expected_args = \
|
||||
self._get_expected_args(name='test', image_id='test_image',
|
||||
keypair_id='test_keypair', coe='swarm',
|
||||
external_network_id='test_net',
|
||||
network_driver='test_driver',
|
||||
registry_enabled=True)
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
@mock.patch(
|
||||
'magnumclient.v1.cluster_templates.ClusterTemplateManager.create')
|
||||
def test_cluster_template_create_public_success(self, mock_create):
|
||||
self._test_arg_success('cluster-template-create '
|
||||
'--name test --network-driver test_driver '
|
||||
'--keypair-id test_keypair '
|
||||
'--external-network-id test_net '
|
||||
'--image-id test_image '
|
||||
'--coe swarm '
|
||||
'--public '
|
||||
'--server-type vm')
|
||||
expected_args = \
|
||||
self._get_expected_args(name='test', image_id='test_image',
|
||||
keypair_id='test_keypair', coe='swarm',
|
||||
external_network_id='test_net',
|
||||
public=True, server_type='vm',
|
||||
network_driver='test_driver')
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
@mock.patch(
|
||||
'magnumclient.v1.cluster_templates.ClusterTemplateManager.create')
|
||||
def test_cluster_template_create_success_with_master_flavor(self,
|
||||
mock_create):
|
||||
self._test_arg_success('cluster-template-create '
|
||||
'--name test '
|
||||
'--image-id test_image '
|
||||
'--keypair-id test_keypair '
|
||||
'--external-network-id test_net '
|
||||
'--coe swarm '
|
||||
'--dns-nameserver test_dns '
|
||||
'--master-flavor-id test_flavor')
|
||||
expected_args = \
|
||||
self._get_expected_args(name='test', image_id='test_image',
|
||||
keypair_id='test_keypair', coe='swarm',
|
||||
external_network_id='test_net',
|
||||
dns_nameserver='test_dns',
|
||||
master_flavor_id='test_flavor')
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
@mock.patch(
|
||||
'magnumclient.v1.cluster_templates.ClusterTemplateManager.create')
|
||||
def test_cluster_template_create_docker_vol_size_success(self,
|
||||
mock_create):
|
||||
self._test_arg_success('cluster-template-create '
|
||||
'--name test --docker-volume-size 4514 '
|
||||
'--keypair-id test_keypair '
|
||||
'--external-network-id test_net '
|
||||
'--image-id test_image '
|
||||
'--coe swarm '
|
||||
'--server-type vm')
|
||||
expected_args = \
|
||||
self._get_expected_args(name='test', image_id='test_image',
|
||||
keypair_id='test_keypair', coe='swarm',
|
||||
external_network_id='test_net',
|
||||
server_type='vm',
|
||||
docker_volume_size=4514)
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
@mock.patch(
|
||||
'magnumclient.v1.cluster_templates.ClusterTemplateManager.create')
|
||||
def test_cluster_template_create_docker_storage_driver_success(
|
||||
self, mock_create):
|
||||
self._test_arg_success('cluster-template-create '
|
||||
'--name test '
|
||||
'--keypair-id test_keypair '
|
||||
'--external-network-id test_net '
|
||||
'--image-id test_image '
|
||||
'--docker-storage-driver devicemapper '
|
||||
'--coe swarm'
|
||||
)
|
||||
expected_args = \
|
||||
self._get_expected_args(name='test', image_id='test_image',
|
||||
keypair_id='test_keypair', coe='swarm',
|
||||
external_network_id='test_net',
|
||||
docker_storage_driver='devicemapper')
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
@mock.patch(
|
||||
'magnumclient.v1.cluster_templates.ClusterTemplateManager.create')
|
||||
def test_cluster_template_create_fixed_network_success(self, mock_create):
|
||||
self._test_arg_success('cluster-template-create '
|
||||
'--name test --fixed-network private '
|
||||
'--keypair-id test_keypair '
|
||||
'--external-network-id test_net '
|
||||
'--image-id test_image '
|
||||
'--coe swarm '
|
||||
'--server-type vm')
|
||||
expected_args = \
|
||||
self._get_expected_args(name='test', image_id='test_image',
|
||||
keypair_id='test_keypair', coe='swarm',
|
||||
fixed_network='private',
|
||||
external_network_id='test_net',
|
||||
server_type='vm')
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
@mock.patch(
|
||||
'magnumclient.v1.cluster_templates.ClusterTemplateManager.create')
|
||||
def test_cluster_template_create_network_driver_success(self, mock_create):
|
||||
self._test_arg_success('cluster-template-create '
|
||||
'--name test --network-driver test_driver '
|
||||
'--keypair-id test_keypair '
|
||||
'--external-network-id test_net '
|
||||
'--image-id test_image '
|
||||
'--coe swarm '
|
||||
'--server-type vm')
|
||||
expected_args = \
|
||||
self._get_expected_args(name='test', image_id='test_image',
|
||||
keypair_id='test_keypair', coe='swarm',
|
||||
external_network_id='test_net',
|
||||
server_type='vm',
|
||||
network_driver='test_driver')
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
@mock.patch(
|
||||
'magnumclient.v1.cluster_templates.ClusterTemplateManager.create')
|
||||
def test_cluster_template_create_volume_driver_success(self, mock_create):
|
||||
self._test_arg_success('cluster-template-create '
|
||||
'--name test --volume-driver test_volume '
|
||||
'--keypair-id test_keypair '
|
||||
'--external-network-id test_net '
|
||||
'--image-id test_image '
|
||||
'--coe swarm '
|
||||
'--server-type vm')
|
||||
expected_args = \
|
||||
self._get_expected_args(name='test', image_id='test_image',
|
||||
keypair_id='test_keypair', coe='swarm',
|
||||
external_network_id='test_net',
|
||||
server_type='vm',
|
||||
volume_driver='test_volume')
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
@mock.patch(
|
||||
'magnumclient.v1.cluster_templates.ClusterTemplateManager.create')
|
||||
def test_cluster_template_create_http_proxy_success(self, mock_create):
|
||||
self._test_arg_success('cluster-template-create '
|
||||
'--name test --fixed-network private '
|
||||
'--keypair-id test_keypair '
|
||||
'--external-network-id test_net '
|
||||
'--image-id test_image '
|
||||
'--coe swarm '
|
||||
'--http-proxy http_proxy '
|
||||
'--server-type vm')
|
||||
expected_args = \
|
||||
self._get_expected_args(name='test', image_id='test_image',
|
||||
keypair_id='test_keypair', coe='swarm',
|
||||
external_network_id='test_net',
|
||||
fixed_network='private',
|
||||
server_type='vm',
|
||||
http_proxy='http_proxy')
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
@mock.patch(
|
||||
'magnumclient.v1.cluster_templates.ClusterTemplateManager.create')
|
||||
def test_cluster_template_create_https_proxy_success(self, mock_create):
|
||||
self._test_arg_success('cluster-template-create '
|
||||
'--name test --fixed-network private '
|
||||
'--keypair-id test_keypair '
|
||||
'--external-network-id test_net '
|
||||
'--image-id test_image '
|
||||
'--coe swarm '
|
||||
'--https-proxy https_proxy '
|
||||
'--server-type vm')
|
||||
expected_args = \
|
||||
self._get_expected_args(name='test', image_id='test_image',
|
||||
keypair_id='test_keypair', coe='swarm',
|
||||
external_network_id='test_net',
|
||||
fixed_network='private',
|
||||
server_type='vm',
|
||||
https_proxy='https_proxy')
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
@mock.patch(
|
||||
'magnumclient.v1.cluster_templates.ClusterTemplateManager.create')
|
||||
def test_cluster_template_create_no_proxy_success(self, mock_create):
|
||||
self._test_arg_success('cluster-template-create '
|
||||
'--name test --fixed-network private '
|
||||
'--keypair-id test_keypair '
|
||||
'--external-network-id test_net '
|
||||
'--image-id test_image '
|
||||
'--coe swarm '
|
||||
'--no-proxy no_proxy '
|
||||
'--server-type vm')
|
||||
expected_args = \
|
||||
self._get_expected_args(name='test', image_id='test_image',
|
||||
keypair_id='test_keypair', coe='swarm',
|
||||
external_network_id='test_net',
|
||||
fixed_network='private',
|
||||
server_type='vm',
|
||||
no_proxy='no_proxy')
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
@mock.patch(
|
||||
'magnumclient.v1.cluster_templates.ClusterTemplateManager.create')
|
||||
def test_cluster_template_create_labels_success(self, mock_create):
|
||||
self._test_arg_success('cluster-template-create '
|
||||
'--name test '
|
||||
'--labels key=val '
|
||||
'--keypair-id test_keypair '
|
||||
'--external-network-id test_net '
|
||||
'--image-id test_image '
|
||||
'--coe swarm '
|
||||
'--server-type vm')
|
||||
expected_args = \
|
||||
self._get_expected_args(name='test', image_id='test_image',
|
||||
keypair_id='test_keypair', coe='swarm',
|
||||
external_network_id='test_net',
|
||||
server_type='vm',
|
||||
labels={'key': 'val'})
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
@mock.patch(
|
||||
'magnumclient.v1.cluster_templates.ClusterTemplateManager.create')
|
||||
def test_cluster_template_create_separate_labels_success(self,
|
||||
mock_create):
|
||||
self._test_arg_success('cluster-template-create '
|
||||
'--name test '
|
||||
'--labels key1=val1 '
|
||||
'--labels key2=val2 '
|
||||
'--keypair-id test_keypair '
|
||||
'--external-network-id test_net '
|
||||
'--image-id test_image '
|
||||
'--coe swarm '
|
||||
'--server-type vm')
|
||||
expected_args = \
|
||||
self._get_expected_args(name='test', image_id='test_image',
|
||||
keypair_id='test_keypair', coe='swarm',
|
||||
external_network_id='test_net',
|
||||
server_type='vm',
|
||||
labels={'key1': 'val1', 'key2': 'val2'})
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
@mock.patch(
|
||||
'magnumclient.v1.cluster_templates.ClusterTemplateManager.create')
|
||||
def test_cluster_template_create_combined_labels_success(self,
|
||||
mock_create):
|
||||
self._test_arg_success('cluster-template-create '
|
||||
'--name test '
|
||||
'--labels key1=val1,key2=val2 '
|
||||
'--keypair-id test_keypair '
|
||||
'--external-network-id test_net '
|
||||
'--image-id test_image '
|
||||
'--coe swarm '
|
||||
'--server-type vm')
|
||||
expected_args = \
|
||||
self._get_expected_args(name='test', image_id='test_image',
|
||||
keypair_id='test_keypair', coe='swarm',
|
||||
external_network_id='test_net',
|
||||
server_type='vm',
|
||||
labels={'key1': 'val1', 'key2': 'val2'})
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
@mock.patch(
|
||||
'magnumclient.v1.cluster_templates.ClusterTemplateManager.create')
|
||||
def test_cluster_template_create_success_only_positional_name(self,
|
||||
mock_create):
|
||||
self._test_arg_success('cluster-template-create '
|
||||
'test '
|
||||
'--labels key1=val1,key2=val2 '
|
||||
'--keypair-id test_keypair '
|
||||
'--external-network-id test_net '
|
||||
'--image-id test_image '
|
||||
'--coe swarm '
|
||||
'--server-type vm')
|
||||
expected_args = \
|
||||
self._get_expected_args(name='test', image_id='test_image',
|
||||
keypair_id='test_keypair', coe='swarm',
|
||||
external_network_id='test_net',
|
||||
server_type='vm',
|
||||
labels={'key1': 'val1', 'key2': 'val2'})
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
@mock.patch(
|
||||
'magnumclient.v1.cluster_templates.ClusterTemplateManager.create')
|
||||
def test_cluster_template_create_failure_duplicate_name(self, mock_create):
|
||||
self._test_arg_failure('cluster-template-create '
|
||||
'foo --name test', self._mandatory_arg_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
@mock.patch(
|
||||
'magnumclient.v1.cluster_templates.ClusterTemplateManager.create')
|
||||
def test_cluster_template_create_failure_few_arg(self, mock_create):
|
||||
self._test_arg_failure('cluster-template-create '
|
||||
'--name test', self._mandatory_arg_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
self._test_arg_failure('cluster-template-create '
|
||||
'--image-id test', self._mandatory_arg_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
self._test_arg_failure('cluster-template-create '
|
||||
'--keypair-id test', self._mandatory_arg_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
self._test_arg_failure('cluster-template-create '
|
||||
'--external-network-id test',
|
||||
self._mandatory_arg_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
self._test_arg_failure('cluster-template-create '
|
||||
'--coe test',
|
||||
self._mandatory_group_arg_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
self._test_arg_failure('cluster-template-create '
|
||||
'--coe test '
|
||||
'--external-network test ',
|
||||
self._mandatory_group_arg_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
self._test_arg_failure('cluster-template-create '
|
||||
'--coe test '
|
||||
'--image test ',
|
||||
self._mandatory_group_arg_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
self._test_arg_failure('cluster-template-create '
|
||||
'--server-type test', self._mandatory_arg_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
self._test_arg_failure('cluster-template-create',
|
||||
self._mandatory_arg_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
@mock.patch(
|
||||
'magnumclient.v1.cluster_templates.ClusterTemplateManager.create')
|
||||
def test_cluster_template_create_deprecation_errors(self, mock_create):
|
||||
required_args = ('cluster-template-create '
|
||||
'--coe test --external-network public --image test ')
|
||||
self._test_arg_failure('cluster-template-create --coe test '
|
||||
'--external-network-id test '
|
||||
'--external-network test ',
|
||||
self._too_many_group_arg_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
self._test_arg_failure('cluster-template-create --coe test '
|
||||
'--image-id test '
|
||||
'--image test ',
|
||||
self._too_many_group_arg_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
self._test_arg_failure(required_args +
|
||||
'--flavor test --flavor-id test',
|
||||
self._too_many_group_arg_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
self._test_arg_failure(required_args +
|
||||
'--master-flavor test --master-flavor-id test',
|
||||
self._too_many_group_arg_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
self._test_arg_failure(required_args +
|
||||
'--keypair test --keypair-id test',
|
||||
self._too_many_group_arg_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
@mock.patch(
|
||||
'magnumclient.v1.cluster_templates.ClusterTemplateManager.create')
|
||||
def test_cluster_template_create_deprecation_warnings(self, mock_create):
|
||||
required_args = ('cluster-template-create '
|
||||
'--coe test --external-network public --image test ')
|
||||
self._test_arg_failure('cluster-template-create '
|
||||
'--coe test '
|
||||
'--external-network-id test '
|
||||
'--image test ',
|
||||
self._deprecated_warning)
|
||||
expected_args = \
|
||||
self._get_expected_args(image_id='test', coe='test',
|
||||
external_network_id='test')
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
self._test_arg_failure('cluster-template-create '
|
||||
'--coe test '
|
||||
'--external-network test '
|
||||
'--image-id test ',
|
||||
self._deprecated_warning)
|
||||
expected_args = \
|
||||
self._get_expected_args(image_id='test', coe='test',
|
||||
external_network_id='test')
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
self._test_arg_failure('cluster-template-create '
|
||||
'--coe test '
|
||||
'--external-network-id test '
|
||||
'--image-id test ',
|
||||
self._deprecated_warning)
|
||||
expected_args = \
|
||||
self._get_expected_args(image_id='test', coe='test',
|
||||
external_network_id='test')
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
self._test_arg_failure(required_args + '--keypair-id test',
|
||||
self._deprecated_warning)
|
||||
expected_args = \
|
||||
self._get_expected_args(image_id='test', coe='test',
|
||||
keypair_id='test',
|
||||
external_network_id='public')
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
self._test_arg_failure(required_args + '--flavor-id test',
|
||||
self._deprecated_warning)
|
||||
expected_args = \
|
||||
self._get_expected_args(image_id='test', coe='test',
|
||||
flavor_id='test',
|
||||
external_network_id='public')
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
self._test_arg_failure(required_args + '--master-flavor-id test',
|
||||
self._deprecated_warning)
|
||||
expected_args = \
|
||||
self._get_expected_args(image_id='test', coe='test',
|
||||
master_flavor_id='test',
|
||||
external_network_id='public')
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
self._test_arg_failure(required_args + '--name foo',
|
||||
self._deprecated_warning)
|
||||
expected_args = \
|
||||
self._get_expected_args(image_id='test', coe='test',
|
||||
name='foo',
|
||||
external_network_id='public')
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
@mock.patch('magnumclient.v1.cluster_templates.ClusterTemplateManager.get')
|
||||
def test_cluster_template_show_success(self, mock_show):
|
||||
self._test_arg_success('cluster-template-show xxx')
|
||||
mock_show.assert_called_once_with('xxx')
|
||||
|
||||
@mock.patch('magnumclient.v1.cluster_templates.ClusterTemplateManager.get')
|
||||
def test_cluster_template_show_failure_no_arg(self, mock_show):
|
||||
self._test_arg_failure('cluster-template-show',
|
||||
self._few_argument_error)
|
||||
mock_show.assert_not_called()
|
||||
|
||||
@mock.patch(
|
||||
'magnumclient.v1.cluster_templates.ClusterTemplateManager.delete')
|
||||
def test_cluster_template_delete_success(self, mock_delete):
|
||||
self._test_arg_success('cluster-template-delete xxx')
|
||||
mock_delete.assert_called_once_with('xxx')
|
||||
|
||||
@mock.patch(
|
||||
'magnumclient.v1.cluster_templates.ClusterTemplateManager.delete')
|
||||
def test_cluster_template_delete_multiple_id_success(self, mock_delete):
|
||||
self._test_arg_success('cluster-template-delete xxx xyz')
|
||||
calls = [mock.call('xxx'), mock.call('xyz')]
|
||||
mock_delete.assert_has_calls(calls)
|
||||
|
||||
@mock.patch(
|
||||
'magnumclient.v1.cluster_templates.ClusterTemplateManager.delete')
|
||||
def test_cluster_template_delete_failure_no_arg(self, mock_delete):
|
||||
self._test_arg_failure('cluster-template-delete',
|
||||
self._few_argument_error)
|
||||
mock_delete.assert_not_called()
|
||||
|
||||
@mock.patch(
|
||||
'magnumclient.v1.cluster_templates.ClusterTemplateManager.update')
|
||||
def test_cluster_template_update_success(self, mock_update):
|
||||
self._test_arg_success('cluster-template-update test add test=test')
|
||||
patch = [{'op': 'add', 'path': '/test', 'value': 'test'}]
|
||||
mock_update.assert_called_once_with('test', patch)
|
||||
|
||||
@mock.patch(
|
||||
'magnumclient.v1.cluster_templates.ClusterTemplateManager.update')
|
||||
def test_cluster_template_update_success_many_attribute(self, mock_update):
|
||||
self._test_arg_success('cluster-template-update test '
|
||||
'add test=test test1=test1')
|
||||
patch = [{'op': 'add', 'path': '/test', 'value': 'test'},
|
||||
{'op': 'add', 'path': '/test1', 'value': 'test1'}]
|
||||
mock_update.assert_called_once_with('test', patch)
|
||||
|
||||
@mock.patch(
|
||||
'magnumclient.v1.cluster_templates.ClusterTemplateManager.update')
|
||||
def test_cluster_template_update_failure_wrong_op(self, mock_update):
|
||||
_error_msg = [
|
||||
'.*?^usage: magnum cluster-template-update ',
|
||||
'.*?^error: argument <op>: invalid choice: ',
|
||||
".*?^Try 'magnum help cluster-template-update' "
|
||||
"for more information."
|
||||
]
|
||||
self._test_arg_failure('cluster-template-update test wrong test=test',
|
||||
_error_msg)
|
||||
mock_update.assert_not_called()
|
||||
|
||||
@mock.patch(
|
||||
'magnumclient.v1.cluster_templates.ClusterTemplateManager.update')
|
||||
def test_cluster_template_update_failure_few_args(self, mock_update):
|
||||
_error_msg = [
|
||||
'.*?^usage: magnum cluster-template-update ',
|
||||
'.*?^error: (the following arguments|too few arguments)',
|
||||
".*?^Try 'magnum help cluster-template-update' "
|
||||
"for more information."
|
||||
]
|
||||
self._test_arg_failure('cluster-template-update', _error_msg)
|
||||
mock_update.assert_not_called()
|
||||
|
||||
self._test_arg_failure('cluster-template-update test', _error_msg)
|
||||
mock_update.assert_not_called()
|
||||
|
||||
self._test_arg_failure('cluster-template-update test add', _error_msg)
|
||||
mock_update.assert_not_called()
|
||||
|
||||
@mock.patch(
|
||||
'magnumclient.v1.cluster_templates.ClusterTemplateManager.list')
|
||||
def test_cluster_template_list_success(self, mock_list):
|
||||
self._test_arg_success('cluster-template-list')
|
||||
expected_args = self._get_expected_args_list()
|
||||
mock_list.assert_called_once_with(**expected_args)
|
||||
|
||||
@mock.patch(
|
||||
'magnumclient.v1.cluster_templates.ClusterTemplateManager.list')
|
||||
def test_cluster_template_list_success_with_arg(self, mock_list):
|
||||
self._test_arg_success('cluster-template-list '
|
||||
'--limit 1 '
|
||||
'--sort-dir asc '
|
||||
'--sort-key uuid')
|
||||
expected_args = self._get_expected_args_list(1, 'asc', 'uuid')
|
||||
mock_list.assert_called_once_with(**expected_args)
|
||||
|
||||
@mock.patch(
|
||||
'magnumclient.v1.cluster_templates.ClusterTemplateManager.list')
|
||||
def test_cluster_template_list_success_detailed(self, mock_list):
|
||||
self._test_arg_success('cluster-template-list '
|
||||
'--detail')
|
||||
expected_args = self._get_expected_args_list(detail=True)
|
||||
mock_list.assert_called_once_with(**expected_args)
|
||||
|
||||
@mock.patch(
|
||||
'magnumclient.v1.cluster_templates.ClusterTemplateManager.list')
|
||||
def test_cluster_template_list_ignored_duplicated_field(self, mock_list):
|
||||
mock_list.return_value = [FakeClusterTemplate()]
|
||||
self._test_arg_success(
|
||||
'cluster-template-list --fields coe,coe,coe,name,name',
|
||||
keyword='\n| uuid | name | Coe |\n')
|
||||
# Output should be
|
||||
# +------+------+-----+
|
||||
# | uuid | name | Coe |
|
||||
# +------+------+-----+
|
||||
# | x | x | x |
|
||||
# +------+------+-----+
|
||||
expected_args = self._get_expected_args_list()
|
||||
mock_list.assert_called_once_with(**expected_args)
|
||||
|
||||
@mock.patch(
|
||||
'magnumclient.v1.cluster_templates.ClusterTemplateManager.list')
|
||||
def test_cluster_template_list_failure_with_invalid_field(self, mock_list):
|
||||
mock_list.return_value = [FakeClusterTemplate()]
|
||||
_error_msg = [".*?^Non-existent fields are specified: ['xxx','zzz']"]
|
||||
self.assertRaises(exceptions.CommandError,
|
||||
self._test_arg_failure,
|
||||
'cluster-template-list --fields xxx,coe,zzz',
|
||||
_error_msg)
|
||||
expected_args = self._get_expected_args_list()
|
||||
mock_list.assert_called_once_with(**expected_args)
|
||||
|
||||
@mock.patch(
|
||||
'magnumclient.v1.cluster_templates.ClusterTemplateManager.list')
|
||||
def test_cluster_template_list_failure_invalid_arg(self, mock_list):
|
||||
_error_msg = [
|
||||
'.*?^usage: magnum cluster-template-list ',
|
||||
'.*?^error: argument --sort-dir: invalid choice: ',
|
||||
".*?^Try 'magnum help cluster-template-list' for more information."
|
||||
]
|
||||
self._test_arg_failure('cluster-template-list --sort-dir aaa',
|
||||
_error_msg)
|
||||
mock_list.assert_not_called()
|
||||
|
||||
@mock.patch(
|
||||
'magnumclient.v1.cluster_templates.ClusterTemplateManager.list')
|
||||
def test_cluster_template_list_failure(self, mock_list):
|
||||
self._test_arg_failure('cluster-template-list --wrong',
|
||||
self._unrecognized_arg_error)
|
||||
mock_list.assert_not_called()
|
|
@ -1,161 +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.
|
||||
|
||||
import testtools
|
||||
from testtools import matchers
|
||||
|
||||
from magnumclient.tests import utils
|
||||
from magnumclient.v1 import mservices
|
||||
|
||||
|
||||
SERVICE1 = {'id': 123,
|
||||
'host': 'fake-host1',
|
||||
'binary': 'fake-bin1',
|
||||
'state': 'up',
|
||||
}
|
||||
SERVICE2 = {'id': 124,
|
||||
'host': 'fake-host2',
|
||||
'binary': 'fake-bin2',
|
||||
'state': 'down',
|
||||
}
|
||||
|
||||
fake_responses = {
|
||||
'/v1/mservices':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{'mservices': [SERVICE1, SERVICE2]},
|
||||
),
|
||||
},
|
||||
'/v1/mservices/?limit=2':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{'mservices': [SERVICE1, SERVICE2]},
|
||||
),
|
||||
},
|
||||
'/v1/mservices/?marker=%s' % SERVICE2['id']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{'mservices': [SERVICE1, SERVICE2]},
|
||||
),
|
||||
},
|
||||
'/v1/mservices/?limit=2&marker=%s' % SERVICE2['id']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{'mservices': [SERVICE2, SERVICE1]},
|
||||
),
|
||||
},
|
||||
'/v1/mservices/?sort_dir=asc':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{'mservices': [SERVICE1, SERVICE2]},
|
||||
),
|
||||
},
|
||||
'/v1/mservices/?sort_key=id':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{'mservices': [SERVICE1, SERVICE2]},
|
||||
),
|
||||
},
|
||||
'/v1/mservices/?sort_key=id&sort_dir=desc':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{'mservices': [SERVICE2, SERVICE1]},
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class MServiceManagerTest(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(MServiceManagerTest, self).setUp()
|
||||
self.api = utils.FakeAPI(fake_responses)
|
||||
self.mgr = mservices.MServiceManager(self.api)
|
||||
|
||||
def test_coe_service_list(self):
|
||||
mservices = self.mgr.list()
|
||||
expect = [
|
||||
('GET', '/v1/mservices', {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertThat(mservices, matchers.HasLength(2))
|
||||
|
||||
def _test_coe_service_list_with_filters(
|
||||
self, limit=None, marker=None,
|
||||
sort_key=None, sort_dir=None,
|
||||
detail=False, expect=[]):
|
||||
mservices_filter = self.mgr.list(limit=limit, marker=marker,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir,
|
||||
detail=detail)
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertThat(mservices_filter, matchers.HasLength(2))
|
||||
|
||||
def test_coe_service_list_with_limit(self):
|
||||
expect = [
|
||||
('GET', '/v1/mservices/?limit=2', {}, None),
|
||||
]
|
||||
self._test_coe_service_list_with_filters(
|
||||
limit=2,
|
||||
expect=expect)
|
||||
|
||||
def test_coe_service_list_with_marker(self):
|
||||
expect = [
|
||||
('GET', '/v1/mservices/?marker=%s' % SERVICE2['id'],
|
||||
{}, None),
|
||||
]
|
||||
self._test_coe_service_list_with_filters(
|
||||
marker=SERVICE2['id'],
|
||||
expect=expect)
|
||||
|
||||
def test_coe_service_list_with_marker_limit(self):
|
||||
expect = [
|
||||
('GET', '/v1/mservices/?limit=2&marker=%s' % SERVICE2['id'],
|
||||
{}, None),
|
||||
]
|
||||
self._test_coe_service_list_with_filters(
|
||||
limit=2, marker=SERVICE2['id'],
|
||||
expect=expect)
|
||||
|
||||
def test_coe_service_list_with_sort_dir(self):
|
||||
expect = [
|
||||
('GET', '/v1/mservices/?sort_dir=asc',
|
||||
{}, None),
|
||||
]
|
||||
self._test_coe_service_list_with_filters(
|
||||
sort_dir='asc',
|
||||
expect=expect)
|
||||
|
||||
def test_coe_service_list_with_sort_key(self):
|
||||
expect = [
|
||||
('GET', '/v1/mservices/?sort_key=id',
|
||||
{}, None),
|
||||
]
|
||||
self._test_coe_service_list_with_filters(
|
||||
sort_key='id',
|
||||
expect=expect)
|
||||
|
||||
def test_coe_service_list_with_sort_key_dir(self):
|
||||
expect = [
|
||||
('GET', '/v1/mservices/?sort_key=id&sort_dir=desc',
|
||||
{}, None),
|
||||
]
|
||||
self._test_coe_service_list_with_filters(
|
||||
sort_key='id', sort_dir='desc',
|
||||
expect=expect)
|
|
@ -1,31 +0,0 @@
|
|||
# Copyright 2015 NEC Corporation. All rights reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import mock
|
||||
|
||||
from magnumclient.tests.v1 import shell_test_base
|
||||
|
||||
|
||||
class ShellTest(shell_test_base.TestCommandLineArgument):
|
||||
|
||||
@mock.patch('magnumclient.v1.mservices.MServiceManager.list')
|
||||
def test_magnum_service_list_success(self, mock_list):
|
||||
self._test_arg_success('service-list')
|
||||
mock_list.assert_called_once_with()
|
||||
|
||||
@mock.patch('magnumclient.v1.mservices.MServiceManager.list')
|
||||
def test_magnum_service_list_failure(self, mock_list):
|
||||
self._test_arg_failure('service-list --wrong',
|
||||
self._unrecognized_arg_error)
|
||||
mock_list.assert_not_called()
|
|
@ -1,152 +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.
|
||||
|
||||
import copy
|
||||
|
||||
import testtools
|
||||
from testtools import matchers
|
||||
|
||||
from magnumclient.tests import utils
|
||||
from magnumclient.v1 import quotas
|
||||
|
||||
|
||||
QUOTA1 = {
|
||||
'id': 123,
|
||||
'resource': "Cluster",
|
||||
'hard_limit': 5,
|
||||
'project_id': 'abc'
|
||||
}
|
||||
|
||||
QUOTA2 = {
|
||||
'id': 124,
|
||||
'resource': "Cluster",
|
||||
'hard_limit': 10,
|
||||
'project_id': 'bcd'
|
||||
}
|
||||
|
||||
CREATE_QUOTA = copy.deepcopy(QUOTA1)
|
||||
del CREATE_QUOTA['id']
|
||||
|
||||
UPDATED_QUOTA = copy.deepcopy(QUOTA2)
|
||||
NEW_HARD_LIMIT = 20
|
||||
UPDATED_QUOTA['hard_limit'] = NEW_HARD_LIMIT
|
||||
|
||||
fake_responses = {
|
||||
'/v1/quotas?all_tenants=True':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{'quotas': [QUOTA1, QUOTA2]},
|
||||
),
|
||||
},
|
||||
'/v1/quotas':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{'quotas': [QUOTA1]},
|
||||
),
|
||||
'POST': (
|
||||
{},
|
||||
QUOTA1,
|
||||
),
|
||||
},
|
||||
'/v1/quotas/%(id)s/%(res)s' % {'id': QUOTA2['project_id'],
|
||||
'res': QUOTA2['resource']}:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
QUOTA2,
|
||||
),
|
||||
'PATCH': (
|
||||
{},
|
||||
UPDATED_QUOTA,
|
||||
),
|
||||
'DELETE': (
|
||||
{},
|
||||
None,
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class QuotasManagerTest(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(QuotasManagerTest, self).setUp()
|
||||
self.api = utils.FakeAPI(fake_responses)
|
||||
self.mgr = quotas.QuotasManager(self.api)
|
||||
|
||||
def test_list_quotas(self):
|
||||
quotas = self.mgr.list()
|
||||
expect = [
|
||||
('GET', '/v1/quotas', {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertThat(quotas, matchers.HasLength(1))
|
||||
|
||||
def test_list_quotas_all(self):
|
||||
quotas = self.mgr.list(all_tenants=True)
|
||||
expect = [
|
||||
('GET', '/v1/quotas?all_tenants=True', {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertThat(quotas, matchers.HasLength(2))
|
||||
|
||||
def test_show_project_resource_quota(self):
|
||||
expect = [
|
||||
('GET',
|
||||
'/v1/quotas/%(id)s/%(res)s' % {'id': QUOTA2['project_id'],
|
||||
'res': QUOTA2['resource']},
|
||||
{},
|
||||
None),
|
||||
]
|
||||
quotas = self.mgr.get(QUOTA2['project_id'], QUOTA2['resource'])
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
expected_quotas = QUOTA2
|
||||
self.assertEqual(expected_quotas, quotas._info)
|
||||
|
||||
def test_quota_create(self):
|
||||
quota = self.mgr.create(**CREATE_QUOTA)
|
||||
expect = [
|
||||
('POST', '/v1/quotas', {}, CREATE_QUOTA),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(QUOTA1, quota._info)
|
||||
|
||||
def test_quota_update(self):
|
||||
patch = {
|
||||
'resource': "Cluster",
|
||||
'hard_limit': NEW_HARD_LIMIT,
|
||||
'project_id': 'bcd'
|
||||
}
|
||||
quota = self.mgr.update(id=QUOTA2['project_id'],
|
||||
resource=QUOTA2['resource'],
|
||||
patch=patch)
|
||||
expect = [
|
||||
('PATCH', '/v1/quotas/%(id)s/%(res)s' % {
|
||||
'id': QUOTA2['project_id'],
|
||||
'res': QUOTA2['resource']}, {}, patch),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(NEW_HARD_LIMIT, quota.hard_limit)
|
||||
|
||||
def test_quota_delete(self):
|
||||
quota = self.mgr.delete(QUOTA2['project_id'], QUOTA2['resource'])
|
||||
expect = [
|
||||
('DELETE',
|
||||
'/v1/quotas/%(id)s/%(res)s' % {'id': QUOTA2['project_id'],
|
||||
'res': QUOTA2['resource']},
|
||||
{},
|
||||
None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertIsNone(quota)
|
|
@ -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.
|
||||
|
||||
import mock
|
||||
|
||||
from magnumclient.tests.v1 import shell_test_base
|
||||
|
||||
|
||||
class ShellTest(shell_test_base.TestCommandLineArgument):
|
||||
|
||||
def _get_expected_args_list(self, marker=None, limit=None, sort_dir=None,
|
||||
sort_key=None, all_tenants=False):
|
||||
expected_args = {}
|
||||
expected_args['marker'] = marker
|
||||
expected_args['limit'] = limit
|
||||
expected_args['sort_dir'] = sort_dir
|
||||
expected_args['sort_key'] = sort_key
|
||||
expected_args['all_tenants'] = False
|
||||
return expected_args
|
||||
|
||||
def _get_expected_args_create(self, project_id, resource, hard_limit):
|
||||
expected_args = {}
|
||||
expected_args['project_id'] = project_id
|
||||
expected_args['resource'] = resource
|
||||
expected_args['hard_limit'] = hard_limit
|
||||
return expected_args
|
||||
|
||||
@mock.patch('magnumclient.v1.quotas.QuotasManager.list')
|
||||
def test_quotas_list_success(self, mock_list):
|
||||
self._test_arg_success('quotas-list')
|
||||
expected_args = self._get_expected_args_list()
|
||||
mock_list.assert_called_once_with(**expected_args)
|
||||
|
||||
@mock.patch('magnumclient.v1.quotas.QuotasManager.list')
|
||||
def test_quotas_list_failure(self, mock_list):
|
||||
self._test_arg_failure('quotas-list --wrong',
|
||||
self._unrecognized_arg_error)
|
||||
mock_list.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.quotas.QuotasManager.create')
|
||||
def test_quotas_create_success(self, mock_create):
|
||||
self._test_arg_success('quotas-create --project-id abc '
|
||||
'--resource Cluster '
|
||||
'--hard-limit 15')
|
||||
expected_args = self._get_expected_args_create('abc', 'Cluster', 15)
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
@mock.patch('magnumclient.v1.quotas.QuotasManager.create')
|
||||
def test_quotas_create_failure_only_project_id(self, mock_create):
|
||||
self._test_arg_failure('quotas-create --project-id abc',
|
||||
self._mandatory_arg_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.quotas.QuotasManager.create')
|
||||
def test_quotas_create_failure_only_resource(self, mock_create):
|
||||
self._test_arg_failure('quotas-create --resource Cluster',
|
||||
self._mandatory_arg_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.quotas.QuotasManager.create')
|
||||
def test_quotas_create_failure_only_hard_limit(self, mock_create):
|
||||
self._test_arg_failure('quotas-create --hard-limit 10',
|
||||
self._mandatory_arg_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.quotas.QuotasManager.create')
|
||||
def test_quotas_create_failure_no_arg(self, mock_create):
|
||||
self._test_arg_failure('quotas-create',
|
||||
self._mandatory_arg_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.quotas.QuotasManager.delete')
|
||||
def test_quotas_delete_success(self, mock_delete):
|
||||
self._test_arg_success(
|
||||
'quotas-delete --project-id xxx --resource Cluster')
|
||||
mock_delete.assert_called_once_with('xxx', 'Cluster')
|
||||
|
||||
@mock.patch('magnumclient.v1.quotas.QuotasManager.delete')
|
||||
def test_quotas_delete_failure_no_project_id(self, mock_delete):
|
||||
self._test_arg_failure('quotas-delete --resource Cluster',
|
||||
self._mandatory_arg_error)
|
||||
mock_delete.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.quotas.QuotasManager.delete')
|
||||
def test_quotas_delete_failure_no_resource(self, mock_delete):
|
||||
self._test_arg_failure('quotas-delete --project-id xxx',
|
||||
self._mandatory_arg_error)
|
||||
mock_delete.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.quotas.QuotasManager.get')
|
||||
def test_quotas_show_success(self, mock_show):
|
||||
self._test_arg_success('quotas-show --project-id abc '
|
||||
'--resource Cluster')
|
||||
mock_show.assert_called_once_with('abc', 'Cluster')
|
||||
|
||||
@mock.patch('magnumclient.v1.quotas.QuotasManager.get')
|
||||
def test_quotas_show_failure_no_arg(self, mock_show):
|
||||
self._test_arg_failure('quotas-show',
|
||||
self._mandatory_arg_error)
|
||||
mock_show.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.quotas.QuotasManager.update')
|
||||
def test_quotas_update_success(self, mock_update):
|
||||
self._test_arg_success('quotas-update --project-id abc '
|
||||
'--resource Cluster '
|
||||
'--hard-limit 20')
|
||||
patch = {'project_id': 'abc', 'resource': 'Cluster', 'hard_limit': 20}
|
||||
mock_update.assert_called_once_with('abc', 'Cluster', patch)
|
|
@ -1,91 +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.
|
||||
|
||||
import testtools
|
||||
|
||||
from magnumclient.tests import utils
|
||||
from magnumclient.v1 import stats
|
||||
|
||||
|
||||
CLUSTER1 = {'id': 123,
|
||||
'uuid': '66666666-7777-8888-9999-000000000001',
|
||||
'name': 'cluster1',
|
||||
'cluster_template_id': 'e74c40e0-d825-11e2-a28f-0800200c9a61',
|
||||
'stack_id': '5d12f6fd-a196-4bf0-ae4c-1f639a523a51',
|
||||
'api_address': '172.17.2.1',
|
||||
'node_addresses': ['172.17.2.3'],
|
||||
'node_count': 2,
|
||||
'master_count': 1,
|
||||
'project_id': 'abc'
|
||||
}
|
||||
CLUSTER2 = {'id': 124,
|
||||
'uuid': '66666666-7777-8888-9999-000000000002',
|
||||
'name': 'cluster2',
|
||||
'cluster_template_id': 'e74c40e0-d825-11e2-a28f-0800200c9a62',
|
||||
'stack_id': '5d12f6fd-a196-4bf0-ae4c-1f639a523a52',
|
||||
'api_address': '172.17.2.2',
|
||||
'node_addresses': ['172.17.2.4'],
|
||||
'node_count': 2,
|
||||
'master_count': 1,
|
||||
'project_id': 'bcd'
|
||||
}
|
||||
|
||||
|
||||
nc = 'node_count'
|
||||
mc = 'master_count'
|
||||
C1 = CLUSTER1
|
||||
C2 = CLUSTER2
|
||||
fake_responses = {
|
||||
'/v1/stats':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{'clusters': 2, 'nodes': C1[nc] + C1[mc] + C2[nc] + C2[mc]},
|
||||
)
|
||||
},
|
||||
'/v1/stats?project_id=%s' % C2['project_id']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{'clusters': 1, 'nodes': C2[nc] + C2[mc]},
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class StatsManagerTest(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(StatsManagerTest, self).setUp()
|
||||
self.api = utils.FakeAPI(fake_responses)
|
||||
self.mgr = stats.StatsManager(self.api)
|
||||
|
||||
def test_stats(self):
|
||||
stats = self.mgr.list()
|
||||
expect = [
|
||||
('GET', '/v1/stats', {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
expected_stats = {'clusters': 2,
|
||||
'nodes': C1[nc] + C1[mc] + C2[nc] + C2[mc]}
|
||||
self.assertEqual(expected_stats, stats._info)
|
||||
|
||||
def test_stats_with_project_id(self):
|
||||
expect = [
|
||||
('GET', '/v1/stats?project_id=%s' % CLUSTER2['project_id'], {},
|
||||
None),
|
||||
]
|
||||
stats = self.mgr.list(project_id=CLUSTER2['project_id'])
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
expected_stats = {'clusters': 1,
|
||||
'nodes': C2[nc] + C2[mc]}
|
||||
self.assertEqual(expected_stats, stats._info)
|
|
@ -1,53 +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.
|
||||
|
||||
import mock
|
||||
|
||||
from magnumclient.tests.v1 import shell_test_base
|
||||
from magnumclient.v1.stats import Stats
|
||||
|
||||
|
||||
class FakeStats(Stats):
|
||||
def __init__(self, manager=None, info={}, **kwargs):
|
||||
Stats.__init__(self, manager=manager, info=info)
|
||||
self.clusters = kwargs.get('clusters', 0)
|
||||
self.nodes = kwargs.get('nodes', 0)
|
||||
|
||||
|
||||
class ShellTest(shell_test_base.TestCommandLineArgument):
|
||||
|
||||
def _get_expected_args_list(self, project_id=None):
|
||||
expected_args = {}
|
||||
expected_args['project_id'] = project_id
|
||||
return expected_args
|
||||
|
||||
@mock.patch(
|
||||
'magnumclient.v1.stats.StatsManager.list')
|
||||
def test_stats_get_success(self, mock_list):
|
||||
self._test_arg_success('stats-list')
|
||||
expected_args = self._get_expected_args_list()
|
||||
mock_list.assert_called_once_with(**expected_args)
|
||||
|
||||
@mock.patch(
|
||||
'magnumclient.v1.stats.StatsManager.list')
|
||||
def test_stats_get_success_with_arg(self, mock_list):
|
||||
self._test_arg_success('stats-list '
|
||||
'--project-id 111 ')
|
||||
expected_args = self._get_expected_args_list('111')
|
||||
mock_list.assert_called_once_with(**expected_args)
|
||||
|
||||
@mock.patch(
|
||||
'magnumclient.v1.stats.StatsManager.list')
|
||||
def test_stats_get_failure(self, mock_list):
|
||||
self._test_arg_failure('stats-list --wrong',
|
||||
self._unrecognized_arg_error)
|
||||
mock_list.assert_not_called()
|
|
@ -1,118 +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.
|
||||
|
||||
from magnumclient.common import base
|
||||
from magnumclient.common import utils
|
||||
from magnumclient import exceptions
|
||||
|
||||
|
||||
CREATION_ATTRIBUTES = ['name', 'image_id', 'flavor_id', 'master_flavor_id',
|
||||
'keypair_id', 'external_network_id', 'fixed_network',
|
||||
'fixed_subnet', 'dns_nameserver', 'docker_volume_size',
|
||||
'labels', 'coe', 'http_proxy', 'https_proxy',
|
||||
'no_proxy', 'network_driver', 'tls_disabled', 'public',
|
||||
'registry_enabled', 'volume_driver', 'server_type',
|
||||
'docker_storage_driver', 'master_lb_enabled',
|
||||
'floating_ip_enabled']
|
||||
|
||||
OUTPUT_ATTRIBUTES = CREATION_ATTRIBUTES + ['apiserver_port', 'created_at',
|
||||
'insecure_registry', 'links',
|
||||
'updated_at', 'cluster_distro',
|
||||
'uuid']
|
||||
|
||||
|
||||
class BaseModel(base.Resource):
|
||||
# model_name needs to be overridden by any derived class.
|
||||
# model_name should be capitalized and singular, e.g. "Cluster"
|
||||
model_name = ''
|
||||
|
||||
def __repr__(self):
|
||||
return "<" + self.__class__.model_name + "%s>" % self._info
|
||||
|
||||
|
||||
class BaseModelManager(base.Manager):
|
||||
# api_name needs to be overridden by any derived class.
|
||||
# api_name should be pluralized and lowercase, e.g. "clustertemplates", as
|
||||
# it shows up in the URL path: "/v1/{api_name}"
|
||||
api_name = ''
|
||||
|
||||
@classmethod
|
||||
def _path(cls, id=None):
|
||||
return '/v1/' + cls.api_name + \
|
||||
'/%s' % id if id else '/v1/' + cls.api_name
|
||||
|
||||
def list(self, limit=None, marker=None, sort_key=None,
|
||||
sort_dir=None, detail=False):
|
||||
"""Retrieve a list of baymodels.
|
||||
|
||||
:param marker: Optional, the UUID of a baymodel, eg the last
|
||||
baymodel from a previous result set. Return
|
||||
the next result set.
|
||||
:param limit: The maximum number of results to return per
|
||||
request, if:
|
||||
|
||||
1) limit > 0, the maximum number of baymodels to return.
|
||||
2) limit == 0, return the entire list of baymodels.
|
||||
3) limit param is NOT specified (None), the number of items
|
||||
returned respect the maximum imposed by the Magnum API
|
||||
(see Magnum's api.max_limit option).
|
||||
|
||||
:param sort_key: Optional, field used for sorting.
|
||||
|
||||
:param sort_dir: Optional, direction of sorting, either 'asc' (the
|
||||
default) or 'desc'.
|
||||
|
||||
:param detail: Optional, boolean whether to return detailed information
|
||||
about baymodels.
|
||||
|
||||
:returns: A list of baymodels.
|
||||
|
||||
"""
|
||||
if limit is not None:
|
||||
limit = int(limit)
|
||||
|
||||
filters = utils.common_filters(marker, limit, sort_key, sort_dir)
|
||||
|
||||
path = ''
|
||||
if detail:
|
||||
path += 'detail'
|
||||
if filters:
|
||||
path += '?' + '&'.join(filters)
|
||||
|
||||
if limit is None:
|
||||
return self._list(self._path(path), self.__class__.api_name)
|
||||
else:
|
||||
return self._list_pagination(self._path(path),
|
||||
self.__class__.api_name,
|
||||
limit=limit)
|
||||
|
||||
def get(self, id):
|
||||
try:
|
||||
return self._list(self._path(id))[0]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
def create(self, **kwargs):
|
||||
new = {}
|
||||
for (key, value) in kwargs.items():
|
||||
if key in CREATION_ATTRIBUTES:
|
||||
new[key] = value
|
||||
else:
|
||||
raise exceptions.InvalidAttribute(
|
||||
"Key must be in %s" % ",".join(CREATION_ATTRIBUTES))
|
||||
return self._create(self._path(), new)
|
||||
|
||||
def delete(self, id):
|
||||
return self._delete(self._path(id))
|
||||
|
||||
def update(self, id, patch):
|
||||
return self._update(self._path(id), patch)
|
|
@ -1,111 +0,0 @@
|
|||
# Copyright 2014 NEC Corporation. All rights reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from magnumclient.common import base
|
||||
from magnumclient.common import utils
|
||||
from magnumclient import exceptions
|
||||
|
||||
|
||||
# Derived classes may append their own custom attributes to this default list
|
||||
CREATION_ATTRIBUTES = ['name', 'node_count', 'discovery_url', 'master_count']
|
||||
|
||||
|
||||
class BaseTemplate(base.Resource):
|
||||
# template_name must be overridden by any derived class.
|
||||
# template_name should be an uppercase plural, e.g. "Clusters"
|
||||
template_name = ''
|
||||
|
||||
def __repr__(self):
|
||||
return "<" + self.__class__.template_name + " %s>" % self._info
|
||||
|
||||
|
||||
class BaseTemplateManager(base.Manager):
|
||||
# template_name must be overridden by any derived class.
|
||||
# template_name should be a lowercase plural, e.g. "clusters"
|
||||
template_name = ''
|
||||
|
||||
@classmethod
|
||||
def _path(cls, id=None):
|
||||
return '/v1/' + cls.template_name + \
|
||||
'/%s' % id if id else '/v1/' + cls.template_name
|
||||
|
||||
def list(self, limit=None, marker=None, sort_key=None,
|
||||
sort_dir=None, detail=False):
|
||||
"""Retrieve a list of bays.
|
||||
|
||||
:param marker: Optional, the UUID of a bay, eg the last
|
||||
bay from a previous result set. Return
|
||||
the next result set.
|
||||
:param limit: The maximum number of results to return per
|
||||
request, if:
|
||||
|
||||
1) limit > 0, the maximum number of bays to return.
|
||||
2) limit == 0, return the entire list of bays.
|
||||
3) limit param is NOT specified (None), the number of items
|
||||
returned respect the maximum imposed by the Magnum API
|
||||
(see Magnum's api.max_limit option).
|
||||
|
||||
:param sort_key: Optional, field used for sorting.
|
||||
|
||||
:param sort_dir: Optional, direction of sorting, either 'asc' (the
|
||||
default) or 'desc'.
|
||||
|
||||
:param detail: Optional, boolean whether to return detailed information
|
||||
about bays.
|
||||
|
||||
:returns: A list of bays.
|
||||
|
||||
"""
|
||||
if limit is not None:
|
||||
limit = int(limit)
|
||||
|
||||
filters = utils.common_filters(marker, limit, sort_key, sort_dir)
|
||||
|
||||
path = ''
|
||||
if detail:
|
||||
path += 'detail'
|
||||
if filters:
|
||||
path += '?' + '&'.join(filters)
|
||||
|
||||
if limit is None:
|
||||
return self._list(self._path(path), self.__class__.template_name)
|
||||
else:
|
||||
return self._list_pagination(self._path(path),
|
||||
self.__class__.template_name,
|
||||
limit=limit)
|
||||
|
||||
def get(self, id):
|
||||
try:
|
||||
return self._list(self._path(id))[0]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
def create(self, **kwargs):
|
||||
new = {}
|
||||
for (key, value) in kwargs.items():
|
||||
if key in CREATION_ATTRIBUTES:
|
||||
new[key] = value
|
||||
else:
|
||||
raise exceptions.InvalidAttribute(
|
||||
"Key must be in %s" % ",".join(CREATION_ATTRIBUTES))
|
||||
return self._create(self._path(), new)
|
||||
|
||||
def delete(self, id):
|
||||
return self._delete(self._path(id))
|
||||
|
||||
def update(self, id, patch, rollback=False):
|
||||
url = self._path(id)
|
||||
if rollback:
|
||||
url += '/?rollback=True'
|
||||
return self._update(url, patch)
|
|
@ -1,25 +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.
|
||||
|
||||
from magnumclient.v1 import basemodels
|
||||
|
||||
|
||||
CREATION_ATTRIBUTES = basemodels.CREATION_ATTRIBUTES
|
||||
|
||||
|
||||
class BayModel(basemodels.BaseModel):
|
||||
model_name = "BayModel"
|
||||
|
||||
|
||||
class BayModelManager(basemodels.BaseModelManager):
|
||||
api_name = "baymodels"
|
||||
resource_class = BayModel
|
|
@ -1,265 +0,0 @@
|
|||
# Copyright 2015 NEC Corporation. All rights reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from magnumclient.common import cliutils as utils
|
||||
from magnumclient.common import utils as magnum_utils
|
||||
from magnumclient.i18n import _
|
||||
from magnumclient.v1 import basemodels
|
||||
|
||||
|
||||
DEPRECATION_MESSAGE = (
|
||||
'WARNING: Baymodel commands are deprecated and will be removed in a future'
|
||||
' release.\nUse cluster commands to avoid seeing this message.')
|
||||
|
||||
|
||||
def _show_baymodel(baymodel):
|
||||
del baymodel._info['links']
|
||||
utils.print_dict(baymodel._info)
|
||||
|
||||
|
||||
@utils.arg('--name',
|
||||
metavar='<name>',
|
||||
help=_('Name of the baymodel to create.'))
|
||||
@utils.arg('--image-id',
|
||||
required=True,
|
||||
metavar='<image-id>',
|
||||
help=_('The name or UUID of the base image to customize for'
|
||||
' the bay.'))
|
||||
@utils.arg('--keypair-id',
|
||||
required=True,
|
||||
metavar='<keypair-id>',
|
||||
help=_('The name of the SSH keypair to load into the'
|
||||
' Bay nodes.'))
|
||||
@utils.arg('--external-network-id',
|
||||
required=True,
|
||||
metavar='<external-network-id>',
|
||||
help=_('The external Neutron network ID to connect to this bay'
|
||||
' model.'))
|
||||
@utils.arg('--coe',
|
||||
required=True,
|
||||
metavar='<coe>',
|
||||
help=_('Specify the Container Orchestration Engine to use.'))
|
||||
@utils.arg('--fixed-network',
|
||||
metavar='<fixed-network>',
|
||||
help=_('The private Neutron network name to connect to this bay'
|
||||
' model.'))
|
||||
@utils.arg('--fixed-subnet',
|
||||
metavar='<fixed-subnet>',
|
||||
help=_('The private Neutron subnet name to connect to bay.'))
|
||||
@utils.arg('--network-driver',
|
||||
metavar='<network-driver>',
|
||||
help=_('The network driver name for instantiating container'
|
||||
' networks.'))
|
||||
@utils.arg('--volume-driver',
|
||||
metavar='<volume-driver>',
|
||||
help=_('The volume driver name for instantiating container'
|
||||
' volume.'))
|
||||
@utils.arg('--dns-nameserver',
|
||||
metavar='<dns-nameserver>',
|
||||
default='8.8.8.8',
|
||||
help=_('The DNS nameserver to use for this baymodel.'))
|
||||
@utils.arg('--flavor-id',
|
||||
metavar='<flavor-id>',
|
||||
default='m1.medium',
|
||||
help=_('The nova flavor id to use when launching the bay.'))
|
||||
@utils.arg('--master-flavor-id',
|
||||
metavar='<master-flavor-id>',
|
||||
help=_('The nova flavor id to use when launching the master node '
|
||||
'of the bay.'))
|
||||
@utils.arg('--docker-volume-size',
|
||||
metavar='<docker-volume-size>',
|
||||
type=int,
|
||||
help=_('Specify the number of size in GB '
|
||||
'for the docker volume to use.'))
|
||||
@utils.arg('--docker-storage-driver',
|
||||
metavar='<docker-storage-driver>',
|
||||
default='devicemapper',
|
||||
help=_('Select a docker storage driver. Supported: devicemapper, '
|
||||
'overlay. Default: devicemapper'))
|
||||
@utils.arg('--http-proxy',
|
||||
metavar='<http-proxy>',
|
||||
help=_('The http_proxy address to use for nodes in bay.'))
|
||||
@utils.arg('--https-proxy',
|
||||
metavar='<https-proxy>',
|
||||
help=_('The https_proxy address to use for nodes in bay.'))
|
||||
@utils.arg('--no-proxy',
|
||||
metavar='<no-proxy>',
|
||||
help=_('The no_proxy address to use for nodes in bay.'))
|
||||
@utils.arg('--labels', metavar='<KEY1=VALUE1,KEY2=VALUE2;KEY3=VALUE3...>',
|
||||
action='append', default=[],
|
||||
help=_('Arbitrary labels in the form of key=value pairs '
|
||||
'to associate with a baymodel. '
|
||||
'May be used multiple times.'))
|
||||
@utils.arg('--tls-disabled',
|
||||
action='store_true', default=False,
|
||||
help=_('Disable TLS in the Bay.'))
|
||||
@utils.arg('--public',
|
||||
action='store_true', default=False,
|
||||
help=_('Make baymodel public.'))
|
||||
@utils.arg('--registry-enabled',
|
||||
action='store_true', default=False,
|
||||
help=_('Enable docker registry in the Bay'))
|
||||
@utils.arg('--server-type',
|
||||
metavar='<server-type>',
|
||||
default='vm',
|
||||
help=_('Specify the server type to be used '
|
||||
'for example vm. For this release '
|
||||
'default server type will be vm.'))
|
||||
@utils.arg('--master-lb-enabled',
|
||||
action='store_true', default=False,
|
||||
help=_('Indicates whether created bays should have a load balancer '
|
||||
'for master nodes or not.'))
|
||||
@utils.arg('--floating-ip-enabled',
|
||||
action='store_true', default=True,
|
||||
help=_('Indicates whether created bays should have a floating ip'
|
||||
'or not.'))
|
||||
@utils.deprecated(DEPRECATION_MESSAGE)
|
||||
def do_baymodel_create(cs, args):
|
||||
"""Create a baymodel.
|
||||
|
||||
(Deprecated in favor of cluster-template-create.)
|
||||
"""
|
||||
opts = {}
|
||||
opts['name'] = args.name
|
||||
opts['flavor_id'] = args.flavor_id
|
||||
opts['master_flavor_id'] = args.master_flavor_id
|
||||
opts['image_id'] = args.image_id
|
||||
opts['keypair_id'] = args.keypair_id
|
||||
opts['external_network_id'] = args.external_network_id
|
||||
opts['fixed_network'] = args.fixed_network
|
||||
opts['fixed_subnet'] = args.fixed_subnet
|
||||
opts['network_driver'] = args.network_driver
|
||||
opts['volume_driver'] = args.volume_driver
|
||||
opts['dns_nameserver'] = args.dns_nameserver
|
||||
opts['docker_volume_size'] = args.docker_volume_size
|
||||
opts['docker_storage_driver'] = args.docker_storage_driver
|
||||
opts['coe'] = args.coe
|
||||
opts['http_proxy'] = args.http_proxy
|
||||
opts['https_proxy'] = args.https_proxy
|
||||
opts['no_proxy'] = args.no_proxy
|
||||
opts['labels'] = magnum_utils.handle_labels(args.labels)
|
||||
opts['tls_disabled'] = args.tls_disabled
|
||||
opts['public'] = args.public
|
||||
opts['registry_enabled'] = args.registry_enabled
|
||||
opts['server_type'] = args.server_type
|
||||
opts['master_lb_enabled'] = args.master_lb_enabled
|
||||
opts['floating_ip_enabled'] = args.floating_ip_enabled
|
||||
|
||||
baymodel = cs.baymodels.create(**opts)
|
||||
_show_baymodel(baymodel)
|
||||
|
||||
|
||||
@utils.arg('baymodels',
|
||||
metavar='<baymodels>',
|
||||
nargs='+',
|
||||
help=_('ID or name of the (baymodel)s to delete.'))
|
||||
@utils.deprecated(DEPRECATION_MESSAGE)
|
||||
def do_baymodel_delete(cs, args):
|
||||
"""Delete specified baymodel.
|
||||
|
||||
(Deprecated in favor of cluster-template-delete.)
|
||||
"""
|
||||
for baymodel in args.baymodels:
|
||||
try:
|
||||
cs.baymodels.delete(baymodel)
|
||||
print("Request to delete baymodel %s has been accepted." %
|
||||
baymodel)
|
||||
except Exception as e:
|
||||
print("Delete for baymodel %(baymodel)s failed: %(e)s" %
|
||||
{'baymodel': baymodel, 'e': e})
|
||||
|
||||
|
||||
@utils.arg('baymodel',
|
||||
metavar='<baymodel>',
|
||||
help=_('ID or name of the baymodel to show.'))
|
||||
@utils.deprecated(DEPRECATION_MESSAGE)
|
||||
def do_baymodel_show(cs, args):
|
||||
"""Show details about the given baymodel.
|
||||
|
||||
(Deprecated in favor of cluster-template-show.)
|
||||
"""
|
||||
baymodel = cs.baymodels.get(args.baymodel)
|
||||
_show_baymodel(baymodel)
|
||||
|
||||
|
||||
@utils.arg('--limit',
|
||||
metavar='<limit>',
|
||||
type=int,
|
||||
help=_('Maximum number of baymodels to return'))
|
||||
@utils.arg('--sort-key',
|
||||
metavar='<sort-key>',
|
||||
help=_('Column to sort results by'))
|
||||
@utils.arg('--sort-dir',
|
||||
metavar='<sort-dir>',
|
||||
choices=['desc', 'asc'],
|
||||
help=_('Direction to sort. "asc" or "desc".'))
|
||||
@utils.arg('--fields',
|
||||
default=None,
|
||||
metavar='<fields>',
|
||||
help=_('Comma-separated list of fields to display. '
|
||||
'Available fields: uuid, name, coe, image_id, public, link, '
|
||||
'apiserver_port, server_type, tls_disabled, registry_enabled'
|
||||
)
|
||||
)
|
||||
@utils.arg('--detail',
|
||||
action='store_true', default=False,
|
||||
help=_('Show detailed information about the baymodels.')
|
||||
)
|
||||
@utils.deprecated(DEPRECATION_MESSAGE)
|
||||
def do_baymodel_list(cs, args):
|
||||
"""Print a list of baymodels.
|
||||
|
||||
(Deprecated in favor of cluster-template-list.)
|
||||
"""
|
||||
nodes = cs.baymodels.list(limit=args.limit,
|
||||
sort_key=args.sort_key,
|
||||
sort_dir=args.sort_dir,
|
||||
detail=args.detail)
|
||||
if args.detail:
|
||||
columns = basemodels.OUTPUT_ATTRIBUTES
|
||||
else:
|
||||
columns = ['uuid', 'name']
|
||||
columns += utils._get_list_table_columns_and_formatters(
|
||||
args.fields, nodes,
|
||||
exclude_fields=(c.lower() for c in columns))[0]
|
||||
utils.print_list(nodes, columns,
|
||||
{'versions': magnum_utils.print_list_field('versions')},
|
||||
sortby_index=None)
|
||||
|
||||
|
||||
@utils.arg('baymodel', metavar='<baymodel>',
|
||||
help=_("UUID or name of baymodel"))
|
||||
@utils.arg(
|
||||
'op',
|
||||
metavar='<op>',
|
||||
choices=['add', 'replace', 'remove'],
|
||||
help=_("Operations: 'add', 'replace' or 'remove'"))
|
||||
@utils.arg(
|
||||
'attributes',
|
||||
metavar='<path=value>',
|
||||
nargs='+',
|
||||
action='append',
|
||||
default=[],
|
||||
help=_("Attributes to add/replace or remove "
|
||||
"(only PATH is necessary on remove)"))
|
||||
@utils.deprecated(DEPRECATION_MESSAGE)
|
||||
def do_baymodel_update(cs, args):
|
||||
"""Updates one or more baymodel attributes.
|
||||
|
||||
(Deprecated in favor of cluster-template-update.)
|
||||
"""
|
||||
patch = magnum_utils.args_array_to_patch(args.op, args.attributes[0])
|
||||
|
||||
baymodel = cs.baymodels.update(args.baymodel, patch)
|
||||
_show_baymodel(baymodel)
|
|
@ -1,29 +0,0 @@
|
|||
# Copyright 2014 NEC Corporation. All rights reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from magnumclient.v1 import baseunit
|
||||
|
||||
|
||||
CREATION_ATTRIBUTES = baseunit.CREATION_ATTRIBUTES
|
||||
CREATION_ATTRIBUTES.append('baymodel_id')
|
||||
CREATION_ATTRIBUTES.append('bay_create_timeout')
|
||||
|
||||
|
||||
class Bay(baseunit.BaseTemplate):
|
||||
template_name = "Bays"
|
||||
|
||||
|
||||
class BayManager(baseunit.BaseTemplateManager):
|
||||
resource_class = Bay
|
||||
template_name = 'bays'
|
|
@ -1,366 +0,0 @@
|
|||
# Copyright 2015 NEC Corporation. All rights reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from magnumclient.common import cliutils as utils
|
||||
from magnumclient.common import utils as magnum_utils
|
||||
from magnumclient import exceptions
|
||||
from magnumclient.i18n import _
|
||||
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography import x509
|
||||
from cryptography.x509.oid import NameOID
|
||||
import os
|
||||
|
||||
|
||||
DEPRECATION_MESSAGE = (
|
||||
'WARNING: Bay commands are deprecated and will be removed in a future '
|
||||
'release.\nUse cluster commands to avoid seeing this message.')
|
||||
|
||||
|
||||
def _show_bay(bay):
|
||||
del bay._info['links']
|
||||
utils.print_dict(bay._info)
|
||||
|
||||
|
||||
@utils.arg('--marker',
|
||||
metavar='<marker>',
|
||||
default=None,
|
||||
help=_('The last bay UUID of the previous page; '
|
||||
'displays list of bays after "marker".'))
|
||||
@utils.arg('--limit',
|
||||
metavar='<limit>',
|
||||
type=int,
|
||||
help=_('Maximum number of bays to return.'))
|
||||
@utils.arg('--sort-key',
|
||||
metavar='<sort-key>',
|
||||
help=_('Column to sort results by.'))
|
||||
@utils.arg('--sort-dir',
|
||||
metavar='<sort-dir>',
|
||||
choices=['desc', 'asc'],
|
||||
help=_('Direction to sort. "asc" or "desc".'))
|
||||
@utils.arg('--fields',
|
||||
default=None,
|
||||
metavar='<fields>',
|
||||
help=_('Comma-separated list of fields to display. '
|
||||
'Available fields: uuid, name, baymodel_id, stack_id, '
|
||||
'status, master_count, node_count, links, bay_create_timeout'
|
||||
)
|
||||
)
|
||||
@utils.deprecated(DEPRECATION_MESSAGE)
|
||||
def do_bay_list(cs, args):
|
||||
"""Print a list of available bays.
|
||||
|
||||
(Deprecated in favor of cluster-list.)
|
||||
"""
|
||||
bays = cs.bays.list(marker=args.marker, limit=args.limit,
|
||||
sort_key=args.sort_key,
|
||||
sort_dir=args.sort_dir)
|
||||
columns = ['uuid', 'name', 'node_count', 'master_count', 'status']
|
||||
columns += utils._get_list_table_columns_and_formatters(
|
||||
args.fields, bays,
|
||||
exclude_fields=(c.lower() for c in columns))[0]
|
||||
utils.print_list(bays, columns,
|
||||
{'versions': magnum_utils.print_list_field('versions')},
|
||||
sortby_index=None)
|
||||
|
||||
|
||||
@utils.deprecated(DEPRECATION_MESSAGE)
|
||||
@utils.arg('--name',
|
||||
metavar='<name>',
|
||||
help=_('Name of the bay to create.'))
|
||||
@utils.arg('--baymodel',
|
||||
required=True,
|
||||
metavar='<baymodel>',
|
||||
help=_('ID or name of the baymodel.'))
|
||||
@utils.arg('--node-count',
|
||||
metavar='<node-count>',
|
||||
type=int,
|
||||
default=1,
|
||||
help=_('The bay node count.'))
|
||||
@utils.arg('--master-count',
|
||||
metavar='<master-count>',
|
||||
type=int,
|
||||
default=1,
|
||||
help=_('The number of master nodes for the bay.'))
|
||||
@utils.arg('--discovery-url',
|
||||
metavar='<discovery-url>',
|
||||
help=_('Specifies custom discovery url for node discovery.'))
|
||||
@utils.arg('--timeout',
|
||||
metavar='<timeout>',
|
||||
type=int,
|
||||
default=60,
|
||||
help=_('The timeout for bay creation in minutes. The default '
|
||||
'is 60 minutes.'))
|
||||
def do_bay_create(cs, args):
|
||||
"""Create a bay.
|
||||
|
||||
(Deprecated in favor of cluster-create.)
|
||||
"""
|
||||
baymodel = cs.baymodels.get(args.baymodel)
|
||||
|
||||
opts = {}
|
||||
opts['name'] = args.name
|
||||
opts['baymodel_id'] = baymodel.uuid
|
||||
opts['node_count'] = args.node_count
|
||||
opts['master_count'] = args.master_count
|
||||
opts['discovery_url'] = args.discovery_url
|
||||
opts['bay_create_timeout'] = args.timeout
|
||||
try:
|
||||
bay = cs.bays.create(**opts)
|
||||
# support for non-async in 1.1
|
||||
if args.magnum_api_version and args.magnum_api_version == '1.1':
|
||||
_show_bay(bay)
|
||||
else:
|
||||
uuid = str(bay._info['uuid'])
|
||||
print("Request to create bay %s has been accepted." % uuid)
|
||||
except Exception as e:
|
||||
print("Create for bay %s failed: %s" %
|
||||
(opts['name'], e))
|
||||
|
||||
|
||||
@utils.arg('bay',
|
||||
metavar='<bay>',
|
||||
nargs='+',
|
||||
help=_('ID or name of the (bay)s to delete.'))
|
||||
@utils.deprecated(DEPRECATION_MESSAGE)
|
||||
def do_bay_delete(cs, args):
|
||||
"""Delete specified bay.
|
||||
|
||||
(Deprecated in favor of cluster-delete.)
|
||||
"""
|
||||
for id in args.bay:
|
||||
try:
|
||||
cs.bays.delete(id)
|
||||
print("Request to delete bay %s has been accepted." %
|
||||
id)
|
||||
except Exception as e:
|
||||
print("Delete for bay %(bay)s failed: %(e)s" %
|
||||
{'bay': id, 'e': e})
|
||||
|
||||
|
||||
@utils.arg('bay',
|
||||
metavar='<bay>',
|
||||
help=_('ID or name of the bay to show.'))
|
||||
@utils.arg('--long',
|
||||
action='store_true', default=False,
|
||||
help=_('Display extra associated Baymodel info.'))
|
||||
@utils.deprecated(DEPRECATION_MESSAGE)
|
||||
def do_bay_show(cs, args):
|
||||
"""Show details about the given bay.
|
||||
|
||||
(Deprecated in favor of cluster-show.)
|
||||
"""
|
||||
bay = cs.bays.get(args.bay)
|
||||
if args.long:
|
||||
baymodel = cs.baymodels.get(bay.baymodel_id)
|
||||
del baymodel._info['links'], baymodel._info['uuid']
|
||||
|
||||
for key in baymodel._info:
|
||||
if 'baymodel_' + key not in bay._info:
|
||||
bay._info['baymodel_' + key] = baymodel._info[key]
|
||||
_show_bay(bay)
|
||||
|
||||
|
||||
@utils.arg('bay', metavar='<bay>', help=_("UUID or name of bay"))
|
||||
@utils.arg('--rollback',
|
||||
action='store_true', default=False,
|
||||
help=_('Rollback bay on update failure.'))
|
||||
@utils.arg(
|
||||
'op',
|
||||
metavar='<op>',
|
||||
choices=['add', 'replace', 'remove'],
|
||||
help=_("Operations: 'add', 'replace' or 'remove'"))
|
||||
@utils.arg(
|
||||
'attributes',
|
||||
metavar='<path=value>',
|
||||
nargs='+',
|
||||
action='append',
|
||||
default=[],
|
||||
help=_("Attributes to add/replace or remove "
|
||||
"(only PATH is necessary on remove)"))
|
||||
@utils.deprecated(DEPRECATION_MESSAGE)
|
||||
def do_bay_update(cs, args):
|
||||
"""Update information about the given bay.
|
||||
|
||||
(Deprecated in favor of cluster-update.)
|
||||
"""
|
||||
if args.rollback and args.magnum_api_version and \
|
||||
args.magnum_api_version in ('1.0', '1.1', '1.2'):
|
||||
raise exceptions.CommandError(
|
||||
"Rollback is not supported in API v%s. "
|
||||
"Please use API v1.3+." % args.magnum_api_version)
|
||||
patch = magnum_utils.args_array_to_patch(args.op, args.attributes[0])
|
||||
bay = cs.bays.update(args.bay, patch, args.rollback)
|
||||
if args.magnum_api_version and args.magnum_api_version == '1.1':
|
||||
_show_bay(bay)
|
||||
else:
|
||||
print("Request to update bay %s has been accepted." % args.bay)
|
||||
|
||||
|
||||
@utils.arg('bay',
|
||||
metavar='<bay>',
|
||||
help=_('ID or name of the bay to retrieve config.'))
|
||||
@utils.arg('--dir',
|
||||
metavar='<dir>',
|
||||
default='.',
|
||||
help=_('Directory to save the certificate and config files.'))
|
||||
@utils.arg('--force',
|
||||
action='store_true', default=False,
|
||||
help=_('Overwrite files if existing.'))
|
||||
@utils.deprecated(DEPRECATION_MESSAGE)
|
||||
def do_bay_config(cs, args):
|
||||
"""Configure native client to access bay.
|
||||
|
||||
You can source the output of this command to get the native client of the
|
||||
corresponding COE configured to access the bay.
|
||||
|
||||
Example: eval $(magnum bay-config <bay-name>).
|
||||
|
||||
(Deprecated in favor of cluster-config.)
|
||||
"""
|
||||
args.dir = os.path.abspath(args.dir)
|
||||
bay = cs.bays.get(args.bay)
|
||||
if bay.status not in ('CREATE_COMPLETE', 'UPDATE_COMPLETE'):
|
||||
raise exceptions.CommandError("Bay in status %s" % bay.status)
|
||||
baymodel = cs.baymodels.get(bay.baymodel_id)
|
||||
opts = {
|
||||
'cluster_uuid': bay.uuid,
|
||||
}
|
||||
|
||||
if not baymodel.tls_disabled:
|
||||
tls = _generate_csr_and_key()
|
||||
tls['ca'] = cs.certificates.get(**opts).pem
|
||||
opts['csr'] = tls['csr']
|
||||
tls['cert'] = cs.certificates.create(**opts).pem
|
||||
for k in ('key', 'cert', 'ca'):
|
||||
fname = "%s/%s.pem" % (args.dir, k)
|
||||
if os.path.exists(fname) and not args.force:
|
||||
raise Exception("File %s exists, aborting." % fname)
|
||||
else:
|
||||
f = open(fname, "w")
|
||||
f.write(tls[k])
|
||||
f.close()
|
||||
|
||||
print(_config_bay(bay, baymodel, cfg_dir=args.dir, force=args.force))
|
||||
|
||||
|
||||
def _config_bay(bay, baymodel, cfg_dir, force=False):
|
||||
"""Return and write configuration for the given bay."""
|
||||
if baymodel.coe == 'kubernetes':
|
||||
return _config_bay_kubernetes(bay, baymodel, cfg_dir, force)
|
||||
elif baymodel.coe == 'swarm':
|
||||
return _config_bay_swarm(bay, baymodel, cfg_dir, force)
|
||||
|
||||
|
||||
def _config_bay_kubernetes(bay, baymodel, cfg_dir, force=False):
|
||||
"""Return and write configuration for the given kubernetes bay."""
|
||||
cfg_file = "%s/config" % cfg_dir
|
||||
if baymodel.tls_disabled:
|
||||
cfg = ("apiVersion: v1\n"
|
||||
"clusters:\n"
|
||||
"- cluster:\n"
|
||||
" server: %(api_address)s\n"
|
||||
" name: %(name)s\n"
|
||||
"contexts:\n"
|
||||
"- context:\n"
|
||||
" cluster: %(name)s\n"
|
||||
" user: %(name)s\n"
|
||||
" name: default/%(name)s\n"
|
||||
"current-context: default/%(name)s\n"
|
||||
"kind: Config\n"
|
||||
"preferences: {}\n"
|
||||
"users:\n"
|
||||
"- name: %(name)s'\n"
|
||||
% {'name': bay.name, 'api_address': bay.api_address})
|
||||
else:
|
||||
cfg = ("apiVersion: v1\n"
|
||||
"clusters:\n"
|
||||
"- cluster:\n"
|
||||
" certificate-authority: ca.pem\n"
|
||||
" server: %(api_address)s\n"
|
||||
" name: %(name)s\n"
|
||||
"contexts:\n"
|
||||
"- context:\n"
|
||||
" cluster: %(name)s\n"
|
||||
" user: %(name)s\n"
|
||||
" name: default/%(name)s\n"
|
||||
"current-context: default/%(name)s\n"
|
||||
"kind: Config\n"
|
||||
"preferences: {}\n"
|
||||
"users:\n"
|
||||
"- name: %(name)s\n"
|
||||
" user:\n"
|
||||
" client-certificate: cert.pem\n"
|
||||
" client-key: key.pem\n"
|
||||
% {'name': bay.name, 'api_address': bay.api_address})
|
||||
|
||||
if os.path.exists(cfg_file) and not force:
|
||||
raise exceptions.CommandError("File %s exists, aborting." % cfg_file)
|
||||
else:
|
||||
f = open(cfg_file, "w")
|
||||
f.write(cfg)
|
||||
f.close()
|
||||
if 'csh' in os.environ['SHELL']:
|
||||
return "setenv KUBECONFIG %s\n" % cfg_file
|
||||
else:
|
||||
return "export KUBECONFIG=%s\n" % cfg_file
|
||||
|
||||
|
||||
def _config_bay_swarm(bay, baymodel, cfg_dir, force=False):
|
||||
"""Return and write configuration for the given swarm bay."""
|
||||
tls = "" if baymodel.tls_disabled else True
|
||||
if 'csh' in os.environ['SHELL']:
|
||||
result = ("setenv DOCKER_HOST %(docker_host)s\n"
|
||||
"setenv DOCKER_CERT_PATH %(cfg_dir)s\n"
|
||||
"setenv DOCKER_TLS_VERIFY %(tls)s\n"
|
||||
% {'docker_host': bay.api_address,
|
||||
'cfg_dir': cfg_dir,
|
||||
'tls': tls}
|
||||
)
|
||||
else:
|
||||
result = ("export DOCKER_HOST=%(docker_host)s\n"
|
||||
"export DOCKER_CERT_PATH=%(cfg_dir)s\n"
|
||||
"export DOCKER_TLS_VERIFY=%(tls)s\n"
|
||||
% {'docker_host': bay.api_address,
|
||||
'cfg_dir': cfg_dir,
|
||||
'tls': tls}
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _generate_csr_and_key():
|
||||
"""Return a dict with a new csr and key."""
|
||||
key = rsa.generate_private_key(
|
||||
public_exponent=65537,
|
||||
key_size=2048,
|
||||
backend=default_backend())
|
||||
|
||||
csr = x509.CertificateSigningRequestBuilder().subject_name(x509.Name([
|
||||
x509.NameAttribute(NameOID.COMMON_NAME, u"Magnum User"),
|
||||
])).sign(key, hashes.SHA256(), default_backend())
|
||||
|
||||
result = {
|
||||
'csr': csr.public_bytes(
|
||||
encoding=serialization.Encoding.PEM).decode("utf-8"),
|
||||
'key': key.private_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PrivateFormat.TraditionalOpenSSL,
|
||||
encryption_algorithm=serialization.NoEncryption()).decode("utf-8"),
|
||||
}
|
||||
|
||||
return result
|
|
@ -1,53 +0,0 @@
|
|||
# Copyright 2015 Rackspace, Inc. All rights reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from magnumclient.common import base
|
||||
from magnumclient import exceptions
|
||||
|
||||
|
||||
CREATION_ATTRIBUTES = ['cluster_uuid', 'csr']
|
||||
|
||||
|
||||
class Certificate(base.Resource):
|
||||
def __repr__(self):
|
||||
return "<Certificate %s>" % self._info
|
||||
|
||||
|
||||
class CertificateManager(base.Manager):
|
||||
resource_class = Certificate
|
||||
|
||||
@staticmethod
|
||||
def _path(id=None):
|
||||
return '/v1/certificates/%s' % id if id else '/v1/certificates'
|
||||
|
||||
def get(self, cluster_uuid):
|
||||
try:
|
||||
return self._list(self._path(cluster_uuid))[0]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
def create(self, **kwargs):
|
||||
new = {}
|
||||
for (key, value) in kwargs.items():
|
||||
if key in CREATION_ATTRIBUTES:
|
||||
new[key] = value
|
||||
elif key == 'bay_uuid':
|
||||
new['cluster_uuid'] = value
|
||||
else:
|
||||
raise exceptions.InvalidAttribute(
|
||||
"Key must be in %s" % ",".join(CREATION_ATTRIBUTES))
|
||||
return self._create(self._path(), new)
|
||||
|
||||
def rotate_ca(self, **kwargs):
|
||||
return self._update(self._path(id=kwargs['cluster_uuid']))
|
|
@ -1,100 +0,0 @@
|
|||
# Copyright 2015 NEC Corporation. All rights reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import os.path
|
||||
|
||||
from magnumclient.common import cliutils as utils
|
||||
from magnumclient.i18n import _
|
||||
|
||||
|
||||
DEPRECATION_MESSAGE = (
|
||||
'WARNING: The bay parameter is deprecated and will be removed in a future '
|
||||
'release.\nUse the cluster parameter to avoid seeing this message.')
|
||||
|
||||
|
||||
def _show_cert(certificate):
|
||||
print(certificate.pem)
|
||||
|
||||
|
||||
def _get_target_uuid(cs, args):
|
||||
target = None
|
||||
if args.cluster:
|
||||
target = cs.clusters.get(args.cluster)
|
||||
elif args.bay:
|
||||
print(DEPRECATION_MESSAGE)
|
||||
target = cs.bays.get(args.bay)
|
||||
else:
|
||||
raise utils.MissingArgs(['--cluster or --bay'])
|
||||
return target.uuid
|
||||
|
||||
|
||||
@utils.arg('--bay',
|
||||
required=False,
|
||||
metavar='<bay>',
|
||||
help=_('ID or name of the bay.'))
|
||||
@utils.arg('--cluster',
|
||||
required=False,
|
||||
metavar='<cluster>',
|
||||
help=_('ID or name of the cluster.'))
|
||||
def do_ca_show(cs, args):
|
||||
"""Show details about the CA certificate for a bay or cluster."""
|
||||
opts = {
|
||||
'cluster_uuid': _get_target_uuid(cs, args)
|
||||
}
|
||||
|
||||
cert = cs.certificates.get(**opts)
|
||||
_show_cert(cert)
|
||||
|
||||
|
||||
@utils.arg('--csr',
|
||||
metavar='<csr>',
|
||||
help=_('File path of the csr file to send to Magnum'
|
||||
' to get signed.'))
|
||||
@utils.arg('--bay',
|
||||
required=False,
|
||||
metavar='<bay>',
|
||||
help=_('ID or name of the bay.'))
|
||||
@utils.arg('--cluster',
|
||||
required=False,
|
||||
metavar='<cluster>',
|
||||
help=_('ID or name of the cluster.'))
|
||||
def do_ca_sign(cs, args):
|
||||
"""Generate the CA certificate for a bay or cluster."""
|
||||
opts = {
|
||||
'cluster_uuid': _get_target_uuid(cs, args)
|
||||
}
|
||||
|
||||
if args.csr is None or not os.path.isfile(args.csr):
|
||||
print('A CSR must be provided.')
|
||||
return
|
||||
|
||||
with open(args.csr, 'r') as f:
|
||||
opts['csr'] = f.read()
|
||||
|
||||
cert = cs.certificates.create(**opts)
|
||||
_show_cert(cert)
|
||||
|
||||
|
||||
@utils.arg('--cluster',
|
||||
required=True,
|
||||
metavar='<cluster>',
|
||||
help=_('ID or name of the cluster.'))
|
||||
def do_ca_rotate(cs, args):
|
||||
"""Rotate the CA certificate for a bay or cluster to revoke access."""
|
||||
cluster = cs.clusters.get(args.cluster)
|
||||
opts = {
|
||||
'cluster_uuid': cluster.uuid
|
||||
}
|
||||
|
||||
cs.certificates.rotate_ca(**opts)
|
|
@ -1,217 +0,0 @@
|
|||
# Copyright 2014
|
||||
# The Cloudscaling Group, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from keystoneauth1.exceptions import catalog
|
||||
from keystoneauth1 import session as ksa_session
|
||||
import os_client_config
|
||||
from oslo_utils import importutils
|
||||
|
||||
from magnumclient.common import httpclient
|
||||
from magnumclient.v1 import baymodels
|
||||
from magnumclient.v1 import bays
|
||||
from magnumclient.v1 import certificates
|
||||
from magnumclient.v1 import cluster_templates
|
||||
from magnumclient.v1 import clusters
|
||||
from magnumclient.v1 import mservices
|
||||
from magnumclient.v1 import quotas
|
||||
from magnumclient.v1 import stats
|
||||
|
||||
profiler = importutils.try_import("osprofiler.profiler")
|
||||
|
||||
|
||||
DEFAULT_SERVICE_TYPE = 'container-infra'
|
||||
LEGACY_DEFAULT_SERVICE_TYPE = 'container'
|
||||
|
||||
|
||||
def _load_session(cloud=None, insecure=False, timeout=None, **kwargs):
|
||||
cloud_config = os_client_config.OpenStackConfig()
|
||||
cloud_config = cloud_config.get_one_cloud(
|
||||
cloud=cloud,
|
||||
verify=not insecure,
|
||||
**kwargs)
|
||||
verify, cert = cloud_config.get_requests_verify_args()
|
||||
|
||||
auth = cloud_config.get_auth()
|
||||
session = ksa_session.Session(
|
||||
auth=auth, verify=verify, cert=cert,
|
||||
timeout=timeout)
|
||||
|
||||
return session
|
||||
|
||||
|
||||
def _load_service_type(session,
|
||||
service_type=None, service_name=None,
|
||||
interface=None, region_name=None):
|
||||
try:
|
||||
# Trigger an auth error so that we can throw the exception
|
||||
# we always have
|
||||
session.get_endpoint(
|
||||
service_type=service_type,
|
||||
service_name=service_name,
|
||||
interface=interface,
|
||||
region_name=region_name)
|
||||
except catalog.EndpointNotFound:
|
||||
service_type = LEGACY_DEFAULT_SERVICE_TYPE
|
||||
try:
|
||||
session.get_endpoint(
|
||||
service_type=service_type,
|
||||
service_name=service_name,
|
||||
interface=interface,
|
||||
region_name=region_name)
|
||||
except Exception as e:
|
||||
raise RuntimeError(str(e))
|
||||
except Exception as e:
|
||||
raise RuntimeError(str(e))
|
||||
|
||||
return service_type
|
||||
|
||||
|
||||
def _load_session_client(session=None, endpoint_override=None, username=None,
|
||||
project_id=None, project_name=None,
|
||||
auth_url=None, password=None, auth_type=None,
|
||||
insecure=None, user_domain_id=None,
|
||||
user_domain_name=None, project_domain_id=None,
|
||||
project_domain_name=None, auth_token=None,
|
||||
timeout=None, service_type=None, service_name=None,
|
||||
interface=None, region_name=None, api_version=None,
|
||||
**kwargs):
|
||||
if not session:
|
||||
session = _load_session(
|
||||
username=username,
|
||||
project_id=project_id,
|
||||
project_name=project_name,
|
||||
auth_url=auth_url,
|
||||
password=password,
|
||||
auth_type=auth_type,
|
||||
insecure=insecure,
|
||||
user_domain_id=user_domain_id,
|
||||
user_domain_name=user_domain_name,
|
||||
project_domain_id=project_domain_id,
|
||||
project_domain_name=project_domain_name,
|
||||
auth_token=auth_token,
|
||||
timeout=timeout,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
if not endpoint_override:
|
||||
service_type = _load_service_type(
|
||||
session,
|
||||
service_type=service_type,
|
||||
service_name=service_name,
|
||||
interface=interface,
|
||||
region_name=region_name,
|
||||
)
|
||||
|
||||
return httpclient.SessionClient(
|
||||
service_type=service_type,
|
||||
service_name=service_name,
|
||||
interface=interface,
|
||||
region_name=region_name,
|
||||
session=session,
|
||||
endpoint_override=endpoint_override,
|
||||
api_version=api_version,
|
||||
)
|
||||
|
||||
|
||||
class Client(object):
|
||||
def __init__(self, username=None, api_key=None, project_id=None,
|
||||
project_name=None, auth_url=None, magnum_url=None,
|
||||
endpoint_type=None, endpoint_override=None,
|
||||
service_type=DEFAULT_SERVICE_TYPE,
|
||||
region_name=None, input_auth_token=None,
|
||||
session=None, password=None, auth_type='password',
|
||||
interface=None, service_name=None, insecure=False,
|
||||
user_domain_id=None, user_domain_name=None,
|
||||
project_domain_id=None, project_domain_name=None,
|
||||
auth_token=None, timeout=600, api_version=None,
|
||||
**kwargs):
|
||||
|
||||
# We have to keep the api_key are for backwards compat, but let's
|
||||
# remove it from the rest of our code since it's not a keystone
|
||||
# concept
|
||||
if not password:
|
||||
password = api_key
|
||||
# Backwards compat for people assing in input_auth_token
|
||||
if input_auth_token:
|
||||
auth_token = input_auth_token
|
||||
# Backwards compat for people assing in endpoint_type
|
||||
if endpoint_type:
|
||||
interface = endpoint_type
|
||||
|
||||
# osc sometimes give 'None' value
|
||||
if not interface:
|
||||
interface = 'public'
|
||||
|
||||
if interface.endswith('URL'):
|
||||
interface = interface[:-3]
|
||||
|
||||
# fix (yolanda): os-cloud-config is using endpoint_override
|
||||
# instead of magnum_url
|
||||
if magnum_url and not endpoint_override:
|
||||
endpoint_override = magnum_url
|
||||
|
||||
if endpoint_override and auth_token:
|
||||
self.http_client = httpclient.HTTPClient(
|
||||
endpoint_override,
|
||||
token=auth_token,
|
||||
api_version=api_version,
|
||||
timeout=timeout,
|
||||
insecure=insecure,
|
||||
**kwargs
|
||||
)
|
||||
else:
|
||||
self.http_client = _load_session_client(
|
||||
session=session,
|
||||
endpoint_override=endpoint_override,
|
||||
username=username,
|
||||
project_id=project_id,
|
||||
project_name=project_name,
|
||||
auth_url=auth_url,
|
||||
password=password,
|
||||
auth_type=auth_type,
|
||||
insecure=insecure,
|
||||
user_domain_id=user_domain_id,
|
||||
user_domain_name=user_domain_name,
|
||||
project_domain_id=project_domain_id,
|
||||
project_domain_name=project_domain_name,
|
||||
auth_token=auth_token,
|
||||
timeout=timeout,
|
||||
service_type=service_type,
|
||||
service_name=service_name,
|
||||
interface=interface,
|
||||
region_name=region_name,
|
||||
api_version=api_version,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
self.bays = bays.BayManager(self.http_client)
|
||||
self.clusters = clusters.ClusterManager(self.http_client)
|
||||
self.certificates = certificates.CertificateManager(self.http_client)
|
||||
self.baymodels = baymodels.BayModelManager(self.http_client)
|
||||
self.cluster_templates = \
|
||||
cluster_templates.ClusterTemplateManager(self.http_client)
|
||||
self.mservices = mservices.MServiceManager(self.http_client)
|
||||
|
||||
profile = kwargs.pop("profile", None)
|
||||
if profiler and profile:
|
||||
# Initialize the root of the future trace: the created trace ID
|
||||
# will be used as the very first parent to which all related
|
||||
# traces will be bound to. The given HMAC key must correspond to
|
||||
# the one set in magnum-api magnum.conf, otherwise the latter
|
||||
# will fail to check the request signature and will skip
|
||||
# initialization of osprofiler on the server side.
|
||||
profiler.init(profile)
|
||||
self.stats = stats.StatsManager(self.http_client)
|
||||
self.quotas = quotas.QuotasManager(self.http_client)
|
|
@ -1,26 +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.
|
||||
|
||||
from magnumclient.v1 import basemodels
|
||||
|
||||
|
||||
CREATION_ATTRIBUTES = basemodels.CREATION_ATTRIBUTES
|
||||
CREATION_ATTRIBUTES.append('insecure_registry')
|
||||
|
||||
|
||||
class ClusterTemplate(basemodels.BaseModel):
|
||||
model_name = "ClusterTemplate"
|
||||
|
||||
|
||||
class ClusterTemplateManager(basemodels.BaseModelManager):
|
||||
api_name = "clustertemplates"
|
||||
resource_class = ClusterTemplate
|
|
@ -1,307 +0,0 @@
|
|||
# Copyright 2015 NEC Corporation. All rights reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from magnumclient.common import cliutils as utils
|
||||
from magnumclient.common import utils as magnum_utils
|
||||
from magnumclient.i18n import _
|
||||
from magnumclient.v1 import basemodels
|
||||
|
||||
|
||||
# Maps old parameter names to their new names and whether they are required
|
||||
DEPRECATING_PARAMS = {
|
||||
"--external-network-id": "--external-network",
|
||||
"--flavor-id": "--flavor",
|
||||
"--image-id": "--image",
|
||||
"--keypair-id": "--keypair",
|
||||
"--master-flavor-id": "--master-flavor",
|
||||
}
|
||||
|
||||
|
||||
def _show_cluster_template(cluster_template):
|
||||
del cluster_template._info['links']
|
||||
utils.print_dict(cluster_template._info)
|
||||
|
||||
|
||||
@utils.deprecation_map(DEPRECATING_PARAMS)
|
||||
@utils.arg('positional_name',
|
||||
metavar='<name>',
|
||||
nargs='?',
|
||||
default=None,
|
||||
help=_('Name of the cluster template to create.'))
|
||||
@utils.arg('--name',
|
||||
metavar='<name>',
|
||||
default=None,
|
||||
help=(_('Name of the cluster template to create. %s') %
|
||||
utils.NAME_DEPRECATION_HELP))
|
||||
@utils.arg('--image-id',
|
||||
dest='image',
|
||||
required=True,
|
||||
metavar='<image>',
|
||||
help=utils.deprecation_message(
|
||||
'The name or UUID of the base image to customize for the '
|
||||
'Cluster.', 'image'))
|
||||
@utils.arg('--image',
|
||||
dest='image',
|
||||
required=True,
|
||||
metavar='<image>',
|
||||
help=_('The name or UUID of the base image to customize for the '
|
||||
'Cluster.'))
|
||||
@utils.arg('--keypair-id',
|
||||
dest='keypair',
|
||||
metavar='<keypair>',
|
||||
help=utils.deprecation_message(
|
||||
'The name of the SSH keypair to load into the '
|
||||
'Cluster nodes.', 'keypair'))
|
||||
@utils.arg('--keypair',
|
||||
dest='keypair',
|
||||
metavar='<keypair>',
|
||||
help=_('The name of the SSH keypair to load into the '
|
||||
'Cluster nodes.'))
|
||||
@utils.arg('--external-network-id',
|
||||
dest='external_network',
|
||||
required=True,
|
||||
metavar='<external-network>',
|
||||
help=utils.deprecation_message(
|
||||
'The external Neutron network name or UUID to connect to '
|
||||
'this Cluster Template.', 'external-network'))
|
||||
@utils.arg('--external-network',
|
||||
dest='external_network',
|
||||
required=True,
|
||||
metavar='<external-network>',
|
||||
help=_('The external Neutron network name or UUID to connect to '
|
||||
'this Cluster Template.'))
|
||||
@utils.arg('--coe',
|
||||
required=True,
|
||||
metavar='<coe>',
|
||||
help=_('Specify the Container Orchestration Engine to use.'))
|
||||
@utils.arg('--fixed-network',
|
||||
metavar='<fixed-network>',
|
||||
help=_('The private Neutron network name to connect to this Cluster'
|
||||
' model.'))
|
||||
@utils.arg('--fixed-subnet',
|
||||
metavar='<fixed-subnet>',
|
||||
help=_('The private Neutron subnet name to connect to Cluster.'))
|
||||
@utils.arg('--network-driver',
|
||||
metavar='<network-driver>',
|
||||
help=_('The network driver name for instantiating container'
|
||||
' networks.'))
|
||||
@utils.arg('--volume-driver',
|
||||
metavar='<volume-driver>',
|
||||
help=_('The volume driver name for instantiating container'
|
||||
' volume.'))
|
||||
@utils.arg('--dns-nameserver',
|
||||
metavar='<dns-nameserver>',
|
||||
default='8.8.8.8',
|
||||
help=_('The DNS nameserver to use for this cluster template.'))
|
||||
@utils.arg('--flavor-id',
|
||||
dest='flavor',
|
||||
metavar='<flavor>',
|
||||
default='m1.medium',
|
||||
help=utils.deprecation_message(
|
||||
'The nova flavor name or UUID to use when launching the '
|
||||
'Cluster.', 'flavor'))
|
||||
@utils.arg('--flavor',
|
||||
dest='flavor',
|
||||
metavar='<flavor>',
|
||||
default='m1.medium',
|
||||
help=_('The nova flavor name or UUID to use when launching the '
|
||||
'Cluster.'))
|
||||
@utils.arg('--master-flavor-id',
|
||||
dest='master_flavor',
|
||||
metavar='<master-flavor>',
|
||||
help=utils.deprecation_message(
|
||||
'The nova flavor name or UUID to use when launching the master'
|
||||
' node of the Cluster.', 'master-flavor'))
|
||||
@utils.arg('--master-flavor',
|
||||
dest='master_flavor',
|
||||
metavar='<master-flavor>',
|
||||
help=_('The nova flavor name or UUID to use when launching the'
|
||||
' master node of the Cluster.'))
|
||||
@utils.arg('--docker-volume-size',
|
||||
metavar='<docker-volume-size>',
|
||||
type=int,
|
||||
help=_('Specify the number of size in GB '
|
||||
'for the docker volume to use.'))
|
||||
@utils.arg('--docker-storage-driver',
|
||||
metavar='<docker-storage-driver>',
|
||||
default='devicemapper',
|
||||
help=_('Select a docker storage driver. Supported: devicemapper, '
|
||||
'overlay. Default: devicemapper'))
|
||||
@utils.arg('--http-proxy',
|
||||
metavar='<http-proxy>',
|
||||
help=_('The http_proxy address to use for nodes in Cluster.'))
|
||||
@utils.arg('--https-proxy',
|
||||
metavar='<https-proxy>',
|
||||
help=_('The https_proxy address to use for nodes in Cluster.'))
|
||||
@utils.arg('--no-proxy',
|
||||
metavar='<no-proxy>',
|
||||
help=_('The no_proxy address to use for nodes in Cluster.'))
|
||||
@utils.arg('--labels', metavar='<KEY1=VALUE1,KEY2=VALUE2;KEY3=VALUE3...>',
|
||||
action='append', default=[],
|
||||
help=_('Arbitrary labels in the form of key=value pairs '
|
||||
'to associate with a cluster template. '
|
||||
'May be used multiple times.'))
|
||||
@utils.arg('--tls-disabled',
|
||||
action='store_true', default=False,
|
||||
help=_('Disable TLS in the Cluster.'))
|
||||
@utils.arg('--public',
|
||||
action='store_true', default=False,
|
||||
help=_('Make cluster template public.'))
|
||||
@utils.arg('--registry-enabled',
|
||||
action='store_true', default=False,
|
||||
help=_('Enable docker registry in the Cluster'))
|
||||
@utils.arg('--server-type',
|
||||
metavar='<server-type>',
|
||||
default='vm',
|
||||
help=_('Specify the server type to be used '
|
||||
'for example vm. For this release '
|
||||
'default server type will be vm.'))
|
||||
@utils.arg('--master-lb-enabled',
|
||||
action='store_true', default=False,
|
||||
help=_('Indicates whether created Clusters should have a load '
|
||||
'balancer for master nodes or not.'))
|
||||
@utils.arg('--floating-ip-enabled',
|
||||
action='store_true', default=True,
|
||||
help=_('Indicates whether created Clusters should have a '
|
||||
'floating ip or not.'))
|
||||
@utils.arg('--insecure-registry',
|
||||
metavar='<insecure-registry>',
|
||||
help='url of docker registry')
|
||||
def do_cluster_template_create(cs, args):
|
||||
"""Create a cluster template."""
|
||||
args.command = 'cluster-template-create'
|
||||
|
||||
utils.validate_name_args(args.positional_name, args.name)
|
||||
|
||||
opts = {}
|
||||
opts['name'] = args.positional_name or args.name
|
||||
opts['flavor_id'] = args.flavor
|
||||
opts['master_flavor_id'] = args.master_flavor
|
||||
opts['image_id'] = args.image
|
||||
opts['keypair_id'] = args.keypair
|
||||
opts['external_network_id'] = args.external_network
|
||||
opts['fixed_network'] = args.fixed_network
|
||||
opts['fixed_subnet'] = args.fixed_subnet
|
||||
opts['network_driver'] = args.network_driver
|
||||
opts['volume_driver'] = args.volume_driver
|
||||
opts['dns_nameserver'] = args.dns_nameserver
|
||||
opts['docker_volume_size'] = args.docker_volume_size
|
||||
opts['docker_storage_driver'] = args.docker_storage_driver
|
||||
opts['coe'] = args.coe
|
||||
opts['http_proxy'] = args.http_proxy
|
||||
opts['https_proxy'] = args.https_proxy
|
||||
opts['no_proxy'] = args.no_proxy
|
||||
opts['labels'] = magnum_utils.handle_labels(args.labels)
|
||||
opts['tls_disabled'] = args.tls_disabled
|
||||
opts['public'] = args.public
|
||||
opts['registry_enabled'] = args.registry_enabled
|
||||
opts['server_type'] = args.server_type
|
||||
opts['master_lb_enabled'] = args.master_lb_enabled
|
||||
opts['floating_ip_enabled'] = args.floating_ip_enabled
|
||||
opts['insecure_registry'] = args.insecure_registry
|
||||
|
||||
cluster_template = cs.cluster_templates.create(**opts)
|
||||
_show_cluster_template(cluster_template)
|
||||
|
||||
|
||||
@utils.arg('cluster_templates',
|
||||
metavar='<cluster_templates>',
|
||||
nargs='+',
|
||||
help=_('ID or name of the (cluster template)s to delete.'))
|
||||
def do_cluster_template_delete(cs, args):
|
||||
"""Delete specified cluster template."""
|
||||
for cluster_template in args.cluster_templates:
|
||||
try:
|
||||
cs.cluster_templates.delete(cluster_template)
|
||||
print("Request to delete cluster template %s has been accepted." %
|
||||
cluster_template)
|
||||
except Exception as e:
|
||||
print("Delete for cluster template "
|
||||
"%(cluster_template)s failed: %(e)s" %
|
||||
{'cluster_template': cluster_template, 'e': e})
|
||||
|
||||
|
||||
@utils.arg('cluster_template',
|
||||
metavar='<cluster_template>',
|
||||
help=_('ID or name of the cluster template to show.'))
|
||||
def do_cluster_template_show(cs, args):
|
||||
"""Show details about the given cluster template."""
|
||||
cluster_template = cs.cluster_templates.get(args.cluster_template)
|
||||
_show_cluster_template(cluster_template)
|
||||
|
||||
|
||||
@utils.arg('--limit',
|
||||
metavar='<limit>',
|
||||
type=int,
|
||||
help=_('Maximum number of cluster templates to return'))
|
||||
@utils.arg('--sort-key',
|
||||
metavar='<sort-key>',
|
||||
help=_('Column to sort results by'))
|
||||
@utils.arg('--sort-dir',
|
||||
metavar='<sort-dir>',
|
||||
choices=['desc', 'asc'],
|
||||
help=_('Direction to sort. "asc" or "desc".'))
|
||||
@utils.arg('--fields',
|
||||
default=None,
|
||||
metavar='<fields>',
|
||||
help=_('Comma-separated list of fields to display. '
|
||||
'Available fields: uuid, name, coe, image_id, public, link, '
|
||||
'apiserver_port, server_type, tls_disabled, registry_enabled'
|
||||
)
|
||||
)
|
||||
@utils.arg('--detail',
|
||||
action='store_true', default=False,
|
||||
help=_('Show detailed information about the cluster templates.')
|
||||
)
|
||||
def do_cluster_template_list(cs, args):
|
||||
"""Print a list of cluster templates."""
|
||||
nodes = cs.cluster_templates.list(limit=args.limit,
|
||||
sort_key=args.sort_key,
|
||||
sort_dir=args.sort_dir,
|
||||
detail=args.detail)
|
||||
if args.detail:
|
||||
columns = basemodels.OUTPUT_ATTRIBUTES
|
||||
else:
|
||||
columns = ['uuid', 'name']
|
||||
columns += utils._get_list_table_columns_and_formatters(
|
||||
args.fields, nodes,
|
||||
exclude_fields=(c.lower() for c in columns))[0]
|
||||
utils.print_list(nodes, columns,
|
||||
{'versions': magnum_utils.print_list_field('versions')},
|
||||
sortby_index=None)
|
||||
|
||||
|
||||
@utils.arg('cluster_template',
|
||||
metavar='<cluster_template>',
|
||||
help=_("UUID or name of cluster template"))
|
||||
@utils.arg(
|
||||
'op',
|
||||
metavar='<op>',
|
||||
choices=['add', 'replace', 'remove'],
|
||||
help=_("Operations: 'add', 'replace' or 'remove'"))
|
||||
@utils.arg(
|
||||
'attributes',
|
||||
metavar='<path=value>',
|
||||
nargs='+',
|
||||
action='append',
|
||||
default=[],
|
||||
help=_("Attributes to add/replace or remove "
|
||||
"(only PATH is necessary on remove)"))
|
||||
def do_cluster_template_update(cs, args):
|
||||
"""Updates one or more cluster template attributes."""
|
||||
patch = magnum_utils.args_array_to_patch(args.op, args.attributes[0])
|
||||
|
||||
cluster_template = cs.cluster_templates.update(args.cluster_template,
|
||||
patch)
|
||||
_show_cluster_template(cluster_template)
|
|
@ -1,31 +0,0 @@
|
|||
# Copyright 2014 NEC Corporation. All rights reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from magnumclient.v1 import baseunit
|
||||
|
||||
|
||||
CREATION_ATTRIBUTES = baseunit.CREATION_ATTRIBUTES
|
||||
CREATION_ATTRIBUTES.append('cluster_template_id')
|
||||
CREATION_ATTRIBUTES.append('create_timeout')
|
||||
CREATION_ATTRIBUTES.append('keypair')
|
||||
CREATION_ATTRIBUTES.append('docker_volume_size')
|
||||
|
||||
|
||||
class Cluster(baseunit.BaseTemplate):
|
||||
template_name = "Clusters"
|
||||
|
||||
|
||||
class ClusterManager(baseunit.BaseTemplateManager):
|
||||
resource_class = Cluster
|
||||
template_name = 'clusters'
|
|
@ -1,393 +0,0 @@
|
|||
# Copyright 2015 NEC Corporation. All rights reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import os
|
||||
|
||||
from magnumclient.common import cliutils as utils
|
||||
from magnumclient.common import utils as magnum_utils
|
||||
from magnumclient import exceptions
|
||||
from magnumclient.i18n import _
|
||||
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography import x509
|
||||
from cryptography.x509.oid import NameOID
|
||||
|
||||
|
||||
# Maps old parameter names to their new names and whether they are required
|
||||
# e.g. keypair-id to keypair
|
||||
DEPRECATING_PARAMS = {
|
||||
"--keypair-id": "--keypair",
|
||||
}
|
||||
|
||||
|
||||
def _show_cluster(cluster):
|
||||
del cluster._info['links']
|
||||
utils.print_dict(cluster._info)
|
||||
|
||||
|
||||
@utils.arg('--marker',
|
||||
metavar='<marker>',
|
||||
default=None,
|
||||
help=_('The last cluster UUID of the previous page; '
|
||||
'displays list of clusters after "marker".'))
|
||||
@utils.arg('--limit',
|
||||
metavar='<limit>',
|
||||
type=int,
|
||||
help=_('Maximum number of clusters to return.'))
|
||||
@utils.arg('--sort-key',
|
||||
metavar='<sort-key>',
|
||||
help=_('Column to sort results by.'))
|
||||
@utils.arg('--sort-dir',
|
||||
metavar='<sort-dir>',
|
||||
choices=['desc', 'asc'],
|
||||
help=_('Direction to sort. "asc" or "desc".'))
|
||||
@utils.arg('--fields',
|
||||
default=None,
|
||||
metavar='<fields>',
|
||||
help=_('Comma-separated list of fields to display. '
|
||||
'Available fields: uuid, name, cluster_template_id, '
|
||||
'stack_id, status, master_count, node_count, links, '
|
||||
'create_timeout'
|
||||
)
|
||||
)
|
||||
def do_cluster_list(cs, args):
|
||||
"""Print a list of available clusters."""
|
||||
clusters = cs.clusters.list(marker=args.marker, limit=args.limit,
|
||||
sort_key=args.sort_key,
|
||||
sort_dir=args.sort_dir)
|
||||
columns = [
|
||||
'uuid', 'name', 'keypair', 'docker_volume_size', 'node_count',
|
||||
'master_count', 'status'
|
||||
]
|
||||
columns += utils._get_list_table_columns_and_formatters(
|
||||
args.fields, clusters,
|
||||
exclude_fields=(c.lower() for c in columns))[0]
|
||||
|
||||
utils.print_list(clusters, columns,
|
||||
{'versions': magnum_utils.print_list_field('versions')},
|
||||
sortby_index=None)
|
||||
|
||||
|
||||
@utils.deprecation_map(DEPRECATING_PARAMS)
|
||||
@utils.arg('positional_name',
|
||||
metavar='<name>',
|
||||
nargs='?',
|
||||
default=None,
|
||||
help=_('Name of the cluster to create.'))
|
||||
@utils.arg('--name',
|
||||
metavar='<name>',
|
||||
default=None,
|
||||
help=(_('Name of the cluster to create. %s') %
|
||||
utils.NAME_DEPRECATION_HELP))
|
||||
@utils.arg('--cluster-template',
|
||||
required=True,
|
||||
metavar='<cluster_template>',
|
||||
help=_('ID or name of the cluster template.'))
|
||||
@utils.arg('--keypair-id',
|
||||
dest='keypair',
|
||||
metavar='<keypair>',
|
||||
default=None,
|
||||
help=utils.deprecation_message(
|
||||
'Name of the keypair to use for this cluster.',
|
||||
'keypair'))
|
||||
@utils.arg('--keypair',
|
||||
dest='keypair',
|
||||
metavar='<keypair>',
|
||||
default=None,
|
||||
help=_('Name of the keypair to use for this cluster.'))
|
||||
@utils.arg('--docker-volume-size',
|
||||
metavar='<docker-volume-size>',
|
||||
type=int,
|
||||
help=_('The size in GB for the docker volume to use'))
|
||||
@utils.arg('--node-count',
|
||||
metavar='<node-count>',
|
||||
type=int,
|
||||
default=1,
|
||||
help=_('The cluster node count.'))
|
||||
@utils.arg('--master-count',
|
||||
metavar='<master-count>',
|
||||
type=int,
|
||||
default=1,
|
||||
help=_('The number of master nodes for the cluster.'))
|
||||
@utils.arg('--discovery-url',
|
||||
metavar='<discovery-url>',
|
||||
help=_('Specifies custom discovery url for node discovery.'))
|
||||
@utils.arg('--timeout',
|
||||
metavar='<timeout>',
|
||||
type=int,
|
||||
default=60,
|
||||
help=_('The timeout for cluster creation in minutes. The default '
|
||||
'is 60 minutes.'))
|
||||
def do_cluster_create(cs, args):
|
||||
"""Create a cluster."""
|
||||
args.command = 'cluster-create'
|
||||
|
||||
utils.validate_name_args(args.positional_name, args.name)
|
||||
|
||||
cluster_template = cs.cluster_templates.get(args.cluster_template)
|
||||
opts = dict()
|
||||
opts['name'] = args.positional_name or args.name
|
||||
opts['cluster_template_id'] = cluster_template.uuid
|
||||
opts['keypair'] = args.keypair
|
||||
if args.docker_volume_size is not None:
|
||||
opts['docker_volume_size'] = args.docker_volume_size
|
||||
opts['node_count'] = args.node_count
|
||||
opts['master_count'] = args.master_count
|
||||
opts['discovery_url'] = args.discovery_url
|
||||
opts['create_timeout'] = args.timeout
|
||||
|
||||
try:
|
||||
cluster = cs.clusters.create(**opts)
|
||||
# support for non-async in 1.1
|
||||
if args.magnum_api_version and args.magnum_api_version == '1.1':
|
||||
_show_cluster(cluster)
|
||||
else:
|
||||
uuid = str(cluster._info['uuid'])
|
||||
print("Request to create cluster %s has been accepted." % uuid)
|
||||
except Exception as e:
|
||||
print("Create for cluster %s failed: %s" %
|
||||
(opts['name'], e))
|
||||
|
||||
|
||||
@utils.arg('cluster',
|
||||
metavar='<cluster>',
|
||||
nargs='+',
|
||||
help=_('ID or name of the (cluster)s to delete.'))
|
||||
def do_cluster_delete(cs, args):
|
||||
"""Delete specified cluster."""
|
||||
for id in args.cluster:
|
||||
try:
|
||||
cs.clusters.delete(id)
|
||||
print("Request to delete cluster %s has been accepted." %
|
||||
id)
|
||||
except Exception as e:
|
||||
print("Delete for cluster %(cluster)s failed: %(e)s" %
|
||||
{'cluster': id, 'e': e})
|
||||
|
||||
|
||||
@utils.arg('cluster',
|
||||
metavar='<cluster>',
|
||||
help=_('ID or name of the cluster to show.'))
|
||||
@utils.arg('--long',
|
||||
action='store_true', default=False,
|
||||
help=_('Display extra associated cluster template info.'))
|
||||
def do_cluster_show(cs, args):
|
||||
"""Show details about the given cluster."""
|
||||
cluster = cs.clusters.get(args.cluster)
|
||||
if args.long:
|
||||
cluster_template = \
|
||||
cs.cluster_templates.get(cluster.cluster_template_id)
|
||||
del cluster_template._info['links'], cluster_template._info['uuid']
|
||||
|
||||
for key in cluster_template._info:
|
||||
if 'clustertemplate_' + key not in cluster._info:
|
||||
cluster._info['clustertemplate_' + key] = \
|
||||
cluster_template._info[key]
|
||||
_show_cluster(cluster)
|
||||
|
||||
|
||||
@utils.arg('cluster', metavar='<cluster>', help=_("UUID or name of cluster"))
|
||||
@utils.arg('--rollback',
|
||||
action='store_true', default=False,
|
||||
help=_('Rollback cluster on update failure.'))
|
||||
@utils.arg(
|
||||
'op',
|
||||
metavar='<op>',
|
||||
choices=['add', 'replace', 'remove'],
|
||||
help=_("Operations: 'add', 'replace' or 'remove'"))
|
||||
@utils.arg(
|
||||
'attributes',
|
||||
metavar='<path=value>',
|
||||
nargs='+',
|
||||
action='append',
|
||||
default=[],
|
||||
help=_("Attributes to add/replace or remove "
|
||||
"(only PATH is necessary on remove)"))
|
||||
def do_cluster_update(cs, args):
|
||||
"""Update information about the given cluster."""
|
||||
if args.rollback and args.magnum_api_version and \
|
||||
args.magnum_api_version in ('1.0', '1.1', '1.2'):
|
||||
raise exceptions.CommandError(
|
||||
"Rollback is not supported in API v%s. "
|
||||
"Please use API v1.3+." % args.magnum_api_version)
|
||||
patch = magnum_utils.args_array_to_patch(args.op, args.attributes[0])
|
||||
try:
|
||||
cluster = cs.clusters.update(args.cluster, patch, args.rollback)
|
||||
except Exception as e:
|
||||
print("ERROR: %s" % e.details)
|
||||
return
|
||||
|
||||
if args.magnum_api_version and args.magnum_api_version == '1.1':
|
||||
_show_cluster(cluster)
|
||||
else:
|
||||
print("Request to update cluster %s has been accepted." % args.cluster)
|
||||
|
||||
|
||||
@utils.arg('cluster',
|
||||
metavar='<cluster>',
|
||||
help=_('ID or name of the cluster to retrieve config.'))
|
||||
@utils.arg('--dir',
|
||||
metavar='<dir>',
|
||||
default='.',
|
||||
help=_('Directory to save the certificate and config files.'))
|
||||
@utils.arg('--force',
|
||||
action='store_true', default=False,
|
||||
help=_('Overwrite files if existing.'))
|
||||
def do_cluster_config(cs, args):
|
||||
"""Configure native client to access cluster.
|
||||
|
||||
You can source the output of this command to get the native client of the
|
||||
corresponding COE configured to access the cluster.
|
||||
|
||||
Example: eval $(magnum cluster-config <cluster-name>).
|
||||
"""
|
||||
args.dir = os.path.abspath(args.dir)
|
||||
cluster = cs.clusters.get(args.cluster)
|
||||
if cluster.status not in ('CREATE_COMPLETE', 'UPDATE_COMPLETE',
|
||||
'ROLLBACK_COMPLETE'):
|
||||
raise exceptions.CommandError("cluster in status %s" % cluster.status)
|
||||
cluster_template = cs.cluster_templates.get(cluster.cluster_template_id)
|
||||
opts = {
|
||||
'cluster_uuid': cluster.uuid,
|
||||
}
|
||||
|
||||
if not cluster_template.tls_disabled:
|
||||
tls = _generate_csr_and_key()
|
||||
tls['ca'] = cs.certificates.get(**opts).pem
|
||||
opts['csr'] = tls['csr']
|
||||
tls['cert'] = cs.certificates.create(**opts).pem
|
||||
for k in ('key', 'cert', 'ca'):
|
||||
fname = "%s/%s.pem" % (args.dir, k)
|
||||
if os.path.exists(fname) and not args.force:
|
||||
raise Exception("File %s exists, aborting." % fname)
|
||||
else:
|
||||
f = open(fname, "w")
|
||||
f.write(tls[k])
|
||||
f.close()
|
||||
|
||||
print(_config_cluster(cluster, cluster_template,
|
||||
cfg_dir=args.dir, force=args.force))
|
||||
|
||||
|
||||
def _config_cluster(cluster, cluster_template, cfg_dir, force=False):
|
||||
"""Return and write configuration for the given cluster."""
|
||||
if cluster_template.coe == 'kubernetes':
|
||||
return _config_cluster_kubernetes(cluster, cluster_template,
|
||||
cfg_dir, force)
|
||||
elif cluster_template.coe == 'swarm':
|
||||
return _config_cluster_swarm(cluster, cluster_template, cfg_dir, force)
|
||||
|
||||
|
||||
def _config_cluster_kubernetes(cluster, cluster_template,
|
||||
cfg_dir, force=False):
|
||||
"""Return and write configuration for the given kubernetes cluster."""
|
||||
cfg_file = "%s/config" % cfg_dir
|
||||
if cluster_template.tls_disabled:
|
||||
cfg = ("apiVersion: v1\n"
|
||||
"clusters:\n"
|
||||
"- cluster:\n"
|
||||
" server: %(api_address)s\n"
|
||||
" name: %(name)s\n"
|
||||
"contexts:\n"
|
||||
"- context:\n"
|
||||
" cluster: %(name)s\n"
|
||||
" user: %(name)s\n"
|
||||
" name: %(name)s\n"
|
||||
"current-context: %(name)s\n"
|
||||
"kind: Config\n"
|
||||
"preferences: {}\n"
|
||||
"users:\n"
|
||||
"- name: %(name)s'\n"
|
||||
% {'name': cluster.name, 'api_address': cluster.api_address})
|
||||
else:
|
||||
cfg = ("apiVersion: v1\n"
|
||||
"clusters:\n"
|
||||
"- cluster:\n"
|
||||
" certificate-authority: ca.pem\n"
|
||||
" server: %(api_address)s\n"
|
||||
" name: %(name)s\n"
|
||||
"contexts:\n"
|
||||
"- context:\n"
|
||||
" cluster: %(name)s\n"
|
||||
" user: %(name)s\n"
|
||||
" name: default/%(name)s\n"
|
||||
"current-context: default/%(name)s\n"
|
||||
"kind: Config\n"
|
||||
"preferences: {}\n"
|
||||
"users:\n"
|
||||
"- name: %(name)s\n"
|
||||
" user:\n"
|
||||
" client-certificate: cert.pem\n"
|
||||
" client-key: key.pem\n"
|
||||
% {'name': cluster.name, 'api_address': cluster.api_address})
|
||||
|
||||
if os.path.exists(cfg_file) and not force:
|
||||
raise exceptions.CommandError("File %s exists, aborting." % cfg_file)
|
||||
else:
|
||||
f = open(cfg_file, "w")
|
||||
f.write(cfg)
|
||||
f.close()
|
||||
if 'csh' in os.environ['SHELL']:
|
||||
return "setenv KUBECONFIG %s\n" % cfg_file
|
||||
else:
|
||||
return "export KUBECONFIG=%s\n" % cfg_file
|
||||
|
||||
|
||||
def _config_cluster_swarm(cluster, cluster_template, cfg_dir, force=False):
|
||||
"""Return and write configuration for the given swarm cluster."""
|
||||
tls = "" if cluster_template.tls_disabled else True
|
||||
if 'csh' in os.environ['SHELL']:
|
||||
result = ("setenv DOCKER_HOST %(docker_host)s\n"
|
||||
"setenv DOCKER_CERT_PATH %(cfg_dir)s\n"
|
||||
"setenv DOCKER_TLS_VERIFY %(tls)s\n"
|
||||
% {'docker_host': cluster.api_address,
|
||||
'cfg_dir': cfg_dir,
|
||||
'tls': tls}
|
||||
)
|
||||
else:
|
||||
result = ("export DOCKER_HOST=%(docker_host)s\n"
|
||||
"export DOCKER_CERT_PATH=%(cfg_dir)s\n"
|
||||
"export DOCKER_TLS_VERIFY=%(tls)s\n"
|
||||
% {'docker_host': cluster.api_address,
|
||||
'cfg_dir': cfg_dir,
|
||||
'tls': tls}
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _generate_csr_and_key():
|
||||
"""Return a dict with a new csr and key."""
|
||||
key = rsa.generate_private_key(
|
||||
public_exponent=65537,
|
||||
key_size=2048,
|
||||
backend=default_backend())
|
||||
|
||||
csr = x509.CertificateSigningRequestBuilder().subject_name(x509.Name([
|
||||
x509.NameAttribute(NameOID.COMMON_NAME, u"Magnum User"),
|
||||
])).sign(key, hashes.SHA256(), default_backend())
|
||||
|
||||
result = {
|
||||
'csr': csr.public_bytes(
|
||||
encoding=serialization.Encoding.PEM).decode("utf-8"),
|
||||
'key': key.private_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PrivateFormat.TraditionalOpenSSL,
|
||||
encryption_algorithm=serialization.NoEncryption()).decode("utf-8"),
|
||||
}
|
||||
|
||||
return result
|
|
@ -1,71 +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.
|
||||
|
||||
from magnumclient.common import base
|
||||
from magnumclient.common import utils
|
||||
|
||||
|
||||
class MService(base.Resource):
|
||||
def __repr__(self):
|
||||
return "<Service %s>" % self._info
|
||||
|
||||
|
||||
class MServiceManager(base.Manager):
|
||||
resource_class = MService
|
||||
|
||||
@staticmethod
|
||||
def _path(id=None):
|
||||
return '/v1/mservices/%s' % id if id else '/v1/mservices'
|
||||
|
||||
def list(self, marker=None, limit=None, sort_key=None,
|
||||
sort_dir=None, detail=False):
|
||||
"""Retrieve list of magnum services.
|
||||
|
||||
:param marker: Optional, the ID of a magnum service, eg the last
|
||||
services from a previous result set. Return
|
||||
the next result set.
|
||||
:param limit: The maximum number of results to return per
|
||||
request, if:
|
||||
|
||||
1) limit > 0, the maximum number of services to return.
|
||||
2) limit == 0, return the entire list of services.
|
||||
3) limit param is NOT specified (None), the number of items
|
||||
returned respect the maximum imposed by the Magnum API
|
||||
(see Magnum's api.max_limit option).
|
||||
|
||||
:param sort_key: Optional, field used for sorting.
|
||||
|
||||
:param sort_dir: Optional, direction of sorting, either 'asc' (the
|
||||
default) or 'desc'.
|
||||
|
||||
:param detail: Optional, boolean whether to return detailed information
|
||||
about services.
|
||||
|
||||
:returns: A list of services.
|
||||
"""
|
||||
|
||||
if limit is not None:
|
||||
limit = int(limit)
|
||||
|
||||
filters = utils.common_filters(marker, limit, sort_key, sort_dir)
|
||||
|
||||
path = ''
|
||||
if detail:
|
||||
path += 'detail'
|
||||
if filters:
|
||||
path += '?' + '&'.join(filters)
|
||||
|
||||
if limit is None:
|
||||
return self._list(self._path(path), "mservices")
|
||||
else:
|
||||
return self._list_pagination(self._path(path), "mservices",
|
||||
limit=limit)
|
|
@ -1,27 +0,0 @@
|
|||
# Copyright 2015 NEC Corporation. All rights reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
|
||||
from magnumclient.common import cliutils as utils
|
||||
from magnumclient.common import utils as magnum_utils
|
||||
|
||||
|
||||
def do_service_list(cs, args):
|
||||
"""Print a list of magnum services."""
|
||||
mservices = cs.mservices.list()
|
||||
columns = ('id', 'host', 'binary', 'state', 'disabled',
|
||||
'disabled_reason', 'created_at', 'updated_at')
|
||||
|
||||
utils.print_list(mservices, columns,
|
||||
{'versions': magnum_utils.print_list_field('versions')})
|
|
@ -1,78 +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.
|
||||
|
||||
from magnumclient.common import utils
|
||||
from magnumclient import exceptions
|
||||
from magnumclient.v1 import basemodels
|
||||
|
||||
|
||||
CREATION_ATTRIBUTES = ['project_id', 'resource', 'hard_limit']
|
||||
|
||||
|
||||
class Quotas(basemodels.BaseModel):
|
||||
model_name = "Quotas"
|
||||
|
||||
|
||||
class QuotasManager(basemodels.BaseModelManager):
|
||||
api_name = "quotas"
|
||||
resource_class = Quotas
|
||||
|
||||
@staticmethod
|
||||
def _path(id=None, resource=None):
|
||||
if not id:
|
||||
return '/v1/quotas'
|
||||
|
||||
return '/v1/quotas/%(id)s/%(res)s' % {'id': id, 'res': resource}
|
||||
|
||||
def list(self, limit=None, marker=None, sort_key=None,
|
||||
sort_dir=None, all_tenants=False):
|
||||
|
||||
if limit is not None:
|
||||
limit = int(limit)
|
||||
|
||||
filters = utils.common_filters(marker, limit, sort_key, sort_dir)
|
||||
|
||||
if all_tenants:
|
||||
filters.append('all_tenants=True')
|
||||
|
||||
path = self._path()
|
||||
if filters:
|
||||
path += '?' + '&'.join(filters)
|
||||
|
||||
if limit is None:
|
||||
return self._list(path, self.api_name)
|
||||
else:
|
||||
return self._list_pagination(path, self.api_name,
|
||||
limit=limit)
|
||||
|
||||
def get(self, id, resource):
|
||||
try:
|
||||
return self._list(self._path(id, resource))[0]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
def create(self, **kwargs):
|
||||
new = {}
|
||||
for (key, value) in kwargs.items():
|
||||
if key in CREATION_ATTRIBUTES:
|
||||
new[key] = value
|
||||
else:
|
||||
raise exceptions.InvalidAttribute(
|
||||
"Key must be in %s" % ",".join(CREATION_ATTRIBUTES))
|
||||
return self._create(self._path(), new)
|
||||
|
||||
def delete(self, id, resource):
|
||||
return self._delete(self._path(id, resource))
|
||||
|
||||
def update(self, id, resource, patch):
|
||||
url = self._path(id, resource)
|
||||
return self._update(url, patch)
|
|
@ -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.
|
||||
|
||||
from magnumclient.common import cliutils as utils
|
||||
from magnumclient.common import utils as magnum_utils
|
||||
from magnumclient.i18n import _
|
||||
|
||||
|
||||
def _show_quota(quota):
|
||||
utils.print_dict(quota._info)
|
||||
|
||||
|
||||
@utils.arg('--marker',
|
||||
metavar='<marker>',
|
||||
default=None,
|
||||
help=_('The last quota UUID of the previous page; '
|
||||
'displays list of quotas after "marker".'))
|
||||
@utils.arg('--limit',
|
||||
metavar='<limit>',
|
||||
type=int,
|
||||
help=_('Maximum number of quotas to return.'))
|
||||
@utils.arg('--sort-key',
|
||||
metavar='<sort-key>',
|
||||
help=_('Column to sort results by.'))
|
||||
@utils.arg('--sort-dir',
|
||||
metavar='<sort-dir>',
|
||||
choices=['desc', 'asc'],
|
||||
help=_('Direction to sort. "asc" or "desc".'))
|
||||
@utils.arg('--all-tenants',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help=_('Flag to indicate list all tenant quotas.'))
|
||||
def do_quotas_list(cs, args):
|
||||
"""Print a list of available quotas."""
|
||||
quotas = cs.quotas.list(marker=args.marker,
|
||||
limit=args.limit,
|
||||
sort_key=args.sort_key,
|
||||
sort_dir=args.sort_dir,
|
||||
all_tenants=args.all_tenants)
|
||||
columns = ['project_id', 'resource', 'hard_limit']
|
||||
utils.print_list(quotas, columns,
|
||||
{'versions': magnum_utils.print_list_field('versions')},
|
||||
sortby_index=None)
|
||||
|
||||
|
||||
@utils.arg('--project-id',
|
||||
required=True,
|
||||
metavar='<project-id>',
|
||||
help=_('Project Id.'))
|
||||
@utils.arg('--resource',
|
||||
required=True,
|
||||
metavar='<resource>',
|
||||
help=_('Resource name.'))
|
||||
@utils.arg('--hard-limit',
|
||||
metavar='<hard-limit>',
|
||||
type=int,
|
||||
default=1,
|
||||
help=_('Max resource limit.'))
|
||||
def do_quotas_create(cs, args):
|
||||
"""Create a quota."""
|
||||
|
||||
opts = dict()
|
||||
opts['project_id'] = args.project_id
|
||||
opts['resource'] = args.resource
|
||||
opts['hard_limit'] = args.hard_limit
|
||||
|
||||
try:
|
||||
quota = cs.quotas.create(**opts)
|
||||
_show_quota(quota)
|
||||
except Exception as e:
|
||||
print("Create quota for project_id %(id)s resource %(res)s failed: "
|
||||
"%(e)s" % {'id': args.project_id,
|
||||
'res': args.resource,
|
||||
'e': e.details})
|
||||
|
||||
|
||||
@utils.arg('--project-id',
|
||||
required=True,
|
||||
metavar='<project-id>',
|
||||
help=_('Project ID.'))
|
||||
@utils.arg('--resource',
|
||||
required=True,
|
||||
metavar='<resource>',
|
||||
help=_('Resource name'))
|
||||
def do_quotas_delete(cs, args):
|
||||
"""Delete specified resource quota."""
|
||||
try:
|
||||
cs.quotas.delete(args.project_id, args.resource)
|
||||
print("Request to delete quota for project id %(id)s and resource "
|
||||
"%(res)s has been accepted." % {
|
||||
'id': args.project_id, 'res': args.resource})
|
||||
except Exception as e:
|
||||
print("Quota delete failed for project id %(id)s and resource "
|
||||
"%(res)s :%(e)s" % {'id': args.project_id,
|
||||
'res': args.resource,
|
||||
'e': e.details})
|
||||
|
||||
|
||||
@utils.arg('--project-id',
|
||||
required=True,
|
||||
metavar='<project-id>',
|
||||
help=_('Project ID.'))
|
||||
@utils.arg('--resource',
|
||||
required=True,
|
||||
metavar='<resource>',
|
||||
help=_('Resource name'))
|
||||
def do_quotas_show(cs, args):
|
||||
"""Show details about the given project resource quota."""
|
||||
quota = cs.quotas.get(args.project_id, args.resource)
|
||||
_show_quota(quota)
|
||||
|
||||
|
||||
@utils.arg('--project-id',
|
||||
required=True,
|
||||
metavar='<project-id>',
|
||||
help=_('Project Id.'))
|
||||
@utils.arg('--resource',
|
||||
required=True,
|
||||
metavar='<resource>',
|
||||
help=_('Resource name.'))
|
||||
@utils.arg('--hard-limit',
|
||||
metavar='<hard-limit>',
|
||||
type=int,
|
||||
default=1,
|
||||
help=_('Max resource limit.'))
|
||||
def do_quotas_update(cs, args):
|
||||
"""Update information about the given project resource quota."""
|
||||
patch = dict()
|
||||
patch['project_id'] = args.project_id
|
||||
patch['resource'] = args.resource
|
||||
patch['hard_limit'] = args.hard_limit
|
||||
|
||||
quota = cs.quotas.update(args.project_id, args.resource, patch)
|
||||
_show_quota(quota)
|
|
@ -1,34 +0,0 @@
|
|||
# Copyright 2014
|
||||
# The Cloudscaling Group, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from magnumclient.v1 import baymodels_shell
|
||||
from magnumclient.v1 import bays_shell
|
||||
from magnumclient.v1 import certificates_shell
|
||||
from magnumclient.v1 import cluster_templates_shell
|
||||
from magnumclient.v1 import clusters_shell
|
||||
from magnumclient.v1 import mservices_shell
|
||||
from magnumclient.v1 import quotas_shell
|
||||
from magnumclient.v1 import stats_shell
|
||||
|
||||
COMMAND_MODULES = [
|
||||
baymodels_shell,
|
||||
bays_shell,
|
||||
certificates_shell,
|
||||
clusters_shell,
|
||||
cluster_templates_shell,
|
||||
mservices_shell,
|
||||
stats_shell,
|
||||
quotas_shell,
|
||||
]
|
|
@ -1,32 +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.
|
||||
|
||||
from magnumclient.common import base
|
||||
|
||||
|
||||
class Stats(base.Resource):
|
||||
def __repr__(self):
|
||||
return "<Stats %s>" % self._info
|
||||
|
||||
|
||||
class StatsManager(base.Manager):
|
||||
resource_class = Stats
|
||||
|
||||
@staticmethod
|
||||
def _path(id=None):
|
||||
return '/v1/stats?project_id=%s' % id if id else '/v1/stats'
|
||||
|
||||
def list(self, project_id=None):
|
||||
try:
|
||||
return self._list(self._path(project_id))[0]
|
||||
except IndexError:
|
||||
return None
|
|
@ -1,28 +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.
|
||||
|
||||
from magnumclient.common import cliutils as utils
|
||||
from magnumclient.i18n import _
|
||||
|
||||
|
||||
@utils.arg('--project-id',
|
||||
required=False,
|
||||
metavar='<project-id>',
|
||||
help=_('Project ID'))
|
||||
def do_stats_list(cs, args):
|
||||
"""Show stats for the given project_id"""
|
||||
opts = {
|
||||
'project_id': args.project_id
|
||||
}
|
||||
|
||||
stats = cs.stats.list(**opts)
|
||||
utils.print_dict(stats._info)
|
|
@ -1,18 +0,0 @@
|
|||
# Copyright 2014
|
||||
# The Cloudscaling Group, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from pbr import version
|
||||
|
||||
version_info = version.VersionInfo('python-magnumclient')
|
|
@ -1,18 +0,0 @@
|
|||
# The order of packages is significant, because pip processes them in the order
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
pbr!=2.1.0,>=2.0.0 # Apache-2.0
|
||||
|
||||
Babel!=2.4.0,>=2.3.4 # BSD
|
||||
six>=1.9.0 # MIT
|
||||
keystoneauth1>=3.0.1 # Apache-2.0
|
||||
stevedore>=1.20.0 # Apache-2.0
|
||||
requests>=2.14.2 # Apache-2.0
|
||||
oslo.i18n!=3.15.2,>=2.1.0 # Apache-2.0
|
||||
oslo.serialization!=2.19.1,>=1.10.0 # Apache-2.0
|
||||
oslo.utils>=3.20.0 # Apache-2.0
|
||||
os-client-config>=1.28.0 # Apache-2.0
|
||||
osc-lib>=1.7.0 # Apache-2.0
|
||||
PrettyTable<0.8,>=0.7.1 # BSD
|
||||
cryptography>=1.6 # BSD/Apache-2.0
|
||||
decorator>=3.4.0 # BSD
|
62
setup.cfg
62
setup.cfg
|
@ -1,62 +0,0 @@
|
|||
[metadata]
|
||||
name = python-magnumclient
|
||||
summary = Client library for Magnum API
|
||||
description-file =
|
||||
README.rst
|
||||
author = OpenStack
|
||||
author-email = openstack-dev@lists.openstack.org
|
||||
home-page = https://docs.openstack.org/python-magnumclient/latest/
|
||||
classifier =
|
||||
Environment :: OpenStack
|
||||
Intended Audience :: Information Technology
|
||||
Intended Audience :: System Administrators
|
||||
License :: OSI Approved :: Apache Software License
|
||||
Operating System :: POSIX :: Linux
|
||||
Programming Language :: Python
|
||||
Programming Language :: Python :: 2
|
||||
Programming Language :: Python :: 2.7
|
||||
Programming Language :: Python :: 3
|
||||
Programming Language :: Python :: 3.5
|
||||
|
||||
[files]
|
||||
packages =
|
||||
magnumclient
|
||||
|
||||
[entry_points]
|
||||
console_scripts =
|
||||
magnum = magnumclient.shell:main
|
||||
|
||||
openstack.cli.extension =
|
||||
container_infra = magnumclient.osc.plugin
|
||||
|
||||
openstack.container_infra.v1 =
|
||||
cluster_template_list = magnumclient.osc.v1.cluster_templates:ListTemplateCluster
|
||||
|
||||
[build_sphinx]
|
||||
source-dir = doc/source
|
||||
build-dir = doc/build
|
||||
all_files = 1
|
||||
|
||||
[upload_sphinx]
|
||||
upload-dir = doc/build/html
|
||||
|
||||
[compile_catalog]
|
||||
directory = magnumclient/locale
|
||||
domain = magnumclient
|
||||
|
||||
[update_catalog]
|
||||
domain = magnumclient
|
||||
output_dir = magnumclient/locale
|
||||
input_file = magnumclient/locale/magnumclient.pot
|
||||
|
||||
[extract_messages]
|
||||
keywords = _ gettext ngettext l_ lazy_gettext
|
||||
mapping_file = babel.cfg
|
||||
output_file = magnumclient/locale/magnumclient.pot
|
||||
|
||||
[wheel]
|
||||
universal = 1
|
||||
|
||||
[pbr]
|
||||
autodoc_index_modules = True
|
||||
warnerrors = True
|
29
setup.py
29
setup.py
|
@ -1,29 +0,0 @@
|
|||
# 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.
|
||||
|
||||
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
|
||||
import setuptools
|
||||
|
||||
# In python < 2.7.4, a lazy loading of package `pbr` will break
|
||||
# setuptools if some other modules registered functions in `atexit`.
|
||||
# solution from: http://bugs.python.org/issue15881#msg170215
|
||||
try:
|
||||
import multiprocessing # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
setuptools.setup(
|
||||
setup_requires=['pbr>=2.0.0'],
|
||||
pbr=True)
|
|
@ -1,17 +0,0 @@
|
|||
# The order of packages is significant, because pip processes them in the order
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
|
||||
bandit>=1.1.0 # Apache-2.0
|
||||
coverage!=4.4,>=4.0 # Apache-2.0
|
||||
fixtures>=3.0.0 # Apache-2.0/BSD
|
||||
python-openstackclient!=3.10.0,>=3.3.0 # Apache-2.0
|
||||
python-subunit>=0.0.18 # Apache-2.0/BSD
|
||||
sphinx>=1.6.2 # BSD
|
||||
openstackdocstheme>=1.11.0 # Apache-2.0
|
||||
oslotest>=1.10.0 # Apache-2.0
|
||||
osprofiler>=1.4.0 # Apache-2.0
|
||||
testrepository>=0.0.18 # Apache-2.0/BSD
|
||||
testscenarios>=0.4 # Apache-2.0/BSD
|
||||
testtools>=1.4.0 # MIT
|
||||
mock>=2.0 # BSD
|
|
@ -1,69 +0,0 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# 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.
|
||||
|
||||
ALLOWED_EXTRA_MISSING=0
|
||||
|
||||
show_diff () {
|
||||
head -1 $1
|
||||
diff -U 0 $1 $2 | sed 1,2d
|
||||
}
|
||||
|
||||
if ! git diff --exit-code || ! git diff --cached --exit-code
|
||||
then
|
||||
echo "There are uncommitted changes!"
|
||||
echo "Please clean git working directory and try again"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Checkout master and save coverage report
|
||||
git checkout HEAD^
|
||||
|
||||
baseline_report=$(mktemp -t magnumclient_coverageXXXXXXX)
|
||||
find . -type f -name "*.pyc" -delete && python setup.py testr --coverage --testr-args="$*"
|
||||
coverage report > $baseline_report
|
||||
mv cover cover-master
|
||||
cat $baseline_report
|
||||
baseline_missing=$(awk 'END { print $3 }' $baseline_report)
|
||||
|
||||
# Checkout back and save coverage report
|
||||
git checkout -
|
||||
|
||||
current_report=$(mktemp -t magnumclient_coverageXXXXXXX)
|
||||
find . -type f -name "*.pyc" -delete && python setup.py testr --coverage --testr-args="$*"
|
||||
coverage report > $current_report
|
||||
current_missing=$(awk 'END { print $3 }' $current_report)
|
||||
|
||||
# Show coverage details
|
||||
allowed_missing=$((baseline_missing+ALLOWED_EXTRA_MISSING))
|
||||
|
||||
echo "Allowed to introduce missing lines : ${ALLOWED_EXTRA_MISSING}"
|
||||
echo "Missing lines in master : ${baseline_missing}"
|
||||
echo "Missing lines in proposed change : ${current_missing}"
|
||||
|
||||
if [ $allowed_missing -ge $current_missing ]; then
|
||||
if [ $baseline_missing -lt $current_missing ]; then
|
||||
show_diff $baseline_report $current_report
|
||||
echo "We believe you can test your code with 100% coverage!"
|
||||
else
|
||||
echo "Thank you! You are awesome! Keep writing unit tests! :)"
|
||||
fi
|
||||
exit_code=0
|
||||
else
|
||||
show_diff $baseline_report $current_report
|
||||
echo "Please write more unit tests, we must maintain our test coverage :( "
|
||||
exit_code=1
|
||||
fi
|
||||
|
||||
rm $baseline_report $current_report
|
||||
exit $exit_code
|
|
@ -1,27 +0,0 @@
|
|||
_magnum_opts="" # lazy init
|
||||
_magnum_flags="" # lazy init
|
||||
_magnum_opts_exp="" # lazy init
|
||||
_magnum()
|
||||
{
|
||||
local cur prev nbc cflags
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||
|
||||
if [ "x$_magnum_opts" == "x" ] ; then
|
||||
nbc="`magnum bash-completion | sed -e "s/ *-h */ /" -e "s/ *-i */ /"`"
|
||||
_magnum_opts="`echo "$nbc" | sed -e "s/--[a-z0-9_-]*//g" -e "s/ */ /g"`"
|
||||
_magnum_flags="`echo " $nbc" | sed -e "s/ [^-][^-][a-z0-9_-]*//g" -e "s/ */ /g"`"
|
||||
_magnum_opts_exp="`echo "$_magnum_opts" | tr ' ' '|'`"
|
||||
fi
|
||||
|
||||
if [[ " ${COMP_WORDS[@]} " =~ " "($_magnum_opts_exp)" " && "$prev" != "help" ]] ; then
|
||||
COMPLETION_CACHE=~/.magnumclient/*/*-cache
|
||||
cflags="$_magnum_flags "$(cat $COMPLETION_CACHE 2> /dev/null | tr '\n' ' ')
|
||||
COMPREPLY=($(compgen -W "${cflags}" -- ${cur}))
|
||||
else
|
||||
COMPREPLY=($(compgen -W "${_magnum_opts}" -- ${cur}))
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
complete -F _magnum magnum
|
|
@ -1,55 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# Client constraint file contains this client version pin that is in conflict
|
||||
# with installing the client from source. We should replace the version pin in
|
||||
# the constraints file before applying it for from-source installation.
|
||||
|
||||
ZUUL_CLONER=/usr/zuul-env/bin/zuul-cloner
|
||||
BRANCH_NAME=master
|
||||
CLIENT_NAME=python-magnumclient
|
||||
requirements_installed=$(echo "import openstack_requirements" | python 2>/dev/null ; echo $?)
|
||||
|
||||
set -e
|
||||
|
||||
CONSTRAINTS_FILE=$1
|
||||
shift
|
||||
|
||||
install_cmd="pip install"
|
||||
mydir=$(mktemp -dt "$CLIENT_NAME-tox_install-XXXXXXX")
|
||||
trap "rm -rf $mydir" EXIT
|
||||
localfile=$mydir/upper-constraints.txt
|
||||
if [[ $CONSTRAINTS_FILE != http* ]]; then
|
||||
CONSTRAINTS_FILE=file://$CONSTRAINTS_FILE
|
||||
fi
|
||||
curl $CONSTRAINTS_FILE -k -o $localfile
|
||||
install_cmd="$install_cmd -c$localfile"
|
||||
|
||||
if [ $requirements_installed -eq 0 ]; then
|
||||
echo "ALREADY INSTALLED" > /tmp/tox_install.txt
|
||||
echo "Requirements already installed; using existing package"
|
||||
elif [ -x "$ZUUL_CLONER" ]; then
|
||||
echo "ZUUL CLONER" > /tmp/tox_install.txt
|
||||
pushd $mydir
|
||||
$ZUUL_CLONER --cache-dir \
|
||||
/opt/git \
|
||||
--branch $BRANCH_NAME \
|
||||
git://git.openstack.org \
|
||||
openstack/requirements
|
||||
cd openstack/requirements
|
||||
$install_cmd -e .
|
||||
popd
|
||||
else
|
||||
echo "PIP HARDCODE" > /tmp/tox_install.txt
|
||||
if [ -z "$REQUIREMENTS_PIP_LOCATION" ]; then
|
||||
REQUIREMENTS_PIP_LOCATION="git+https://git.openstack.org/openstack/requirements@$BRANCH_NAME#egg=requirements"
|
||||
fi
|
||||
$install_cmd -U -e ${REQUIREMENTS_PIP_LOCATION}
|
||||
fi
|
||||
|
||||
# This is the main purpose of the script: Allow local installation of
|
||||
# the current repo. It is listed in constraints file and thus any
|
||||
# install will be constrained and we need to unconstrain it.
|
||||
edit-constraints $localfile -- $CLIENT_NAME "-e file://$PWD#egg=$CLIENT_NAME"
|
||||
|
||||
$install_cmd -U $*
|
||||
exit $?
|
61
tox.ini
61
tox.ini
|
@ -1,61 +0,0 @@
|
|||
[tox]
|
||||
minversion = 1.6
|
||||
envlist = py35,py27,pypy,pep8
|
||||
skipsdist = True
|
||||
|
||||
[testenv]
|
||||
usedevelop = True
|
||||
install_command =
|
||||
{toxinidir}/tools/tox_install.sh {env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages}
|
||||
whitelist_externals = find
|
||||
setenv =
|
||||
VIRTUAL_ENV={envdir}
|
||||
PYTHONWARNINGS=default::DeprecationWarning
|
||||
deps = -r{toxinidir}/requirements.txt
|
||||
-r{toxinidir}/test-requirements.txt
|
||||
commands =
|
||||
find . -type f -name "*.py[c|o]" -delete
|
||||
python setup.py testr --slowest --testr-args='{posargs}'
|
||||
|
||||
[testenv:bandit]
|
||||
deps = -r{toxinidir}/test-requirements.txt
|
||||
commands = bandit -r magnumclient -x tests -n5 -ll
|
||||
|
||||
[testenv:pypy]
|
||||
deps = setuptools<3.2
|
||||
-r{toxinidir}/requirements.txt
|
||||
-r{toxinidir}/test-requirements.txt
|
||||
|
||||
[testenv:debug]
|
||||
commands = oslo_debug_helper -t magnumclient/tests {posargs}
|
||||
|
||||
[testenv:debug-py27]
|
||||
basepython = python2.7
|
||||
commands = oslo_debug_helper -t magnumclient/tests {posargs}
|
||||
|
||||
[testenv:debug-py35]
|
||||
basepython = python3.5
|
||||
commands = oslo_debug_helper -t magnumclient/tests {posargs}
|
||||
|
||||
[testenv:pep8]
|
||||
commands =
|
||||
flake8
|
||||
# Run security linter
|
||||
bandit -r magnumclient -x tests -n5 -ll
|
||||
|
||||
[testenv:venv]
|
||||
commands = {posargs}
|
||||
|
||||
[testenv:cover]
|
||||
commands = {toxinidir}/tools/cover.sh {posargs}
|
||||
|
||||
[flake8]
|
||||
# E123, E125 skipped as they are invalid PEP-8.
|
||||
|
||||
show-source = True
|
||||
ignore = E123,E125
|
||||
builtins = _
|
||||
exclude=.venv,.git,.tox,dist,doc,,*lib/python*,*egg,build
|
||||
|
||||
[hacking]
|
||||
import_exceptions = magnumclient._i18n
|
Loading…
Reference in New Issue