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:
Tony Breeds 2017-09-12 16:01:40 -06:00
parent 2e4f22bb54
commit 895f9f3a19
92 changed files with 14 additions and 12479 deletions

View File

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

55
.gitignore vendored
View File

@ -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

View File

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

View File

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

View File

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

View File

@ -1,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
View File

@ -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.

14
README Normal file
View File

@ -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.

View File

@ -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

View File

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

View File

@ -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}

View File

@ -1 +0,0 @@
.. include:: ../../CONTRIBUTING.rst

View File

@ -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`

View File

@ -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

View File

@ -1 +0,0 @@
.. include:: ../../README.rst

View File

@ -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.

View File

@ -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()

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)
)

View File

@ -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()

View File

@ -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())

View File

@ -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

View File

@ -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/')

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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()

View File

@ -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)

View File

@ -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))

View File

@ -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)

View File

@ -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()

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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()

View File

@ -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
)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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()

View File

@ -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)

View File

@ -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()

View File

@ -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)

View File

@ -1,117 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
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)

View File

@ -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)

View File

@ -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()

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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'

View File

@ -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

View File

@ -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']))

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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'

View File

@ -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

View File

@ -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)

View File

@ -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')})

View File

@ -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)

View File

@ -1,143 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
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)

View File

@ -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,
]

View File

@ -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

View File

@ -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)

View File

@ -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')

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
View File

@ -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