Retire Senlin: remove repo content

Senlin project is retiring
- https://review.opendev.org/c/openstack/governance/+/919347

this commit remove the content of this project repo

Depends-On: https://review.opendev.org/c/openstack/project-config/+/919348/
Change-Id: Id24a51f183aeeb80f0d80b16759694e602b26824
This commit is contained in:
Ghanshyam Mann 2024-05-10 14:29:23 -07:00
parent eb23d5c98f
commit 5f53415593
131 changed files with 8 additions and 13774 deletions

67
.gitignore vendored
View File

@ -1,67 +0,0 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
*.eggs
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
cover/
htmlcov/
.tox/
.coverage
.coverage.*
.idea
.cache
.stestr/
coverage.xml
# Translations
*.mo
# Django stuff:
*.log
# Sphinx documentation
doc/build/
# PyBuilder
target/
# Files created by releasenotes build
releasenotes/build
# pbr generated files
AUTHORS
ChangeLog
# swap file
*.swp

View File

@ -1,3 +0,0 @@
[DEFAULT]
test_path=${OS_TEST_PATH:-./senlinclient/tests/unit}
top_dir=./

View File

@ -1,41 +0,0 @@
- job:
name: senlinclient-functional
parent: devstack-tox-functional
required-projects:
- openstack/python-senlinclient
- openstack/senlin
vars:
openrc_enable_export: true
devstack_plugins:
senlin: https://opendev.org/openstack/senlin
devstack_local_conf:
post-config:
$SENLIN_CONF:
DEFAULT:
cloud_backend: openstack_test
default_log_levels: >-
amqp=WARN,amqplib=WARN,sqlalchemy=WARN,oslo_messaging=WARN
,iso8601=WARN,requests.packages.urllib3.connectionpool=WARN
,urllib3.connectionpool=WARN
,requests.packages.urllib3.util.retry=WARN,urllib3.util.retry=WARN
,keystonemiddleware=WARN
,routes.middleware=WARN
,stevedore=WARN
,oslo_messaging._drivers.amqp=WARN
,oslo_messaging._drivers.amqpdriver=WARN
irrelevant-files:
- ^senlinclient/tests/unit/.*$
- ^setup.cfg$
- ^tools/.*$
- project:
templates:
- check-requirements
- openstack-python3-jobs
- openstackclient-plugin-jobs
- publish-openstack-docs-pti
- release-notes-jobs-python3
check:
jobs:
- senlinclient-functional:
voting: false

View File

@ -1,16 +0,0 @@
If you would like to contribute to the development of OpenStack,
you must follow the steps documented at:
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-senlinclient

175
LICENSE
View File

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

View File

@ -1,18 +1,10 @@
========================
Team and repository tags
========================
This project is no longer maintained.
.. image:: https://governance.openstack.org/tc/badges/python-senlinclient.svg
:target: https://governance.openstack.org/tc/reference/tags/index.html
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".
.. Change things from this point on
OpenStackClient Plugin for Senlin Clustering Service
====================================================
This is a client library for Senlin built on the Senlin clustering API. It
provides a plugin for the openstackclient command-line tool.
Development takes place via the usual OpenStack processes as outlined in the
`developer guide <https://docs.openstack.org/infra/manual/developers.html>`_.
The master repository is in `Git <https://opendev.org/openstack/python-senlinclient>`_.
For any further questions, please email
openstack-discuss@lists.openstack.org or join #openstack-dev on
OFTC.

23
TODO
View File

@ -1,23 +0,0 @@
High Priority
=============
- Support action_cancel
- Add support to HTTPS connection
* This means a cert and key option using plain HTTP package, while
it means using Transport when we switched to OpenStackSDK
- Add checking for token based authentication
- Add CLI argument checking, required vs optional
Middle Priority
===============
- Use code from https://review.opendev.org/#/c/95679/ to replace
_append_global_identity_args(parser)
- Add unit tests
- Figure out how to use access_info and reauthenticate parameters for
authentication.
- Support trust based authentication
Low Priority
============

View File

@ -1,5 +0,0 @@
# This file contains runtime (non-python) dependencies
# More info at: http://docs.openstack.org/infra/bindep/readme.html
# tools/misc-sanity-checks.sh validates .po[t] files
gettext [test]

2
doc/.gitignore vendored
View File

@ -1,2 +0,0 @@
build/
source/ref/

View File

@ -1,177 +0,0 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = build
# User-friendly check for sphinx-build
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
endif
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " xml to make Docutils-native XML files"
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/python-senlinclient.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/python-senlinclient.qhc"
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/python-senlinclient"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/python-senlinclient"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
latexpdfja:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through platex and dvipdfmx..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
xml:
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
@echo
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
pseudoxml:
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
@echo
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."

View File

@ -1,6 +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.
openstackdocstheme>=2.2.1 # Apache-2.0
sphinx>=2.0.0,!=2.1.0 # BSD
reno>=3.1.0 # Apache-2.0

View File

@ -1,88 +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.
===================
Senlin CLI man page
===================
SYNOPSIS
========
The Senlin clustering service doesn't provide its own command line tool
since Queens release. Users are supposed to use :program:`openstack cluster`
commands instead. The python-senlinclient project is an implementation of the
OpenStackClient (OSC) plugin that interacts with the Senlin clustering service.
:program:`openstack` [options] <command> [command-options]
:program:`openstack help cluster`
DESCRIPTION
===========
The :program:`openstack cluster` command line utility interacts with OpenStack Cluster
Service (Senlin).
In order to use the CLI, you must provide your OpenStack username, password,
project (historically called tenant), and auth endpoint. You can use
configuration options `--os-username`, `--os-password`, `--os-project-name`,
`--os-identity-api-version`, `-os-user-domain-name`, `--os-project-domain-name`
and `--os-auth-url` or set corresponding environment
variables::
export OS_USERNAME=user
export OS_PASSWORD=pass
export OS_PROJECT_NAME=myproject
export OS_IDENTITY_API_VERSION=3
export OS_AUTH_URL=http://auth.example.com:5000/v3
export OS_USER_DOMAIN_NAME=Default
export OS_PROJECT_DOMAIN_NAME=Default
OPTIONS
=======
To get a list of available commands and options run::
openstack help cluster
To get usage and options of a command::
openstack help cluster <command>
EXAMPLES
========
Get help for profile create command::
openstack help cluster profile create
List all the profiles::
openstack cluster profile list
Create new profile::
openstack cluster profile create --spec-file cirros_basic.yaml PF001
Show a specific profile details::
openstack cluster profile show PF001
Create a node::
openstack cluster node create --profile PF001 NODE001
For more information, please see the senlin documentation.
`https://docs.openstack.org/senlin/latest/tutorial/basics.html`

View File

@ -1,70 +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.
#
# python-senlinclient documentation build configuration file
# -- 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 = [
'openstackdocstheme',
]
# The content that will be inserted into the main body of an autoclass
# directive.
autoclass_content = 'both'
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The master toctree document.
master_doc = 'index'
# openstackdocstheme options
openstackdocs_repo_name = 'openstack/python-senlinclient'
openstackdocs_bug_project = 'python-senlinclient'
openstackdocs_bug_tag = ''
copyright = 'OpenStack Contributors'
# List of directories, relative to source directory, that shouldn't be searched
# for source files.
exclude_trees = []
# 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 = 'native'
# -- 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 = 'openstackdocs'
# -- Options for manual page output ------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'senlin', 'OpenStack Senlin command line client',
['OpenStack Contributors'], 1),
]

View File

@ -1,55 +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.
==================
SenlinClient Tests
==================
Unit Tests
==========
Senlinclient contains a suite of unit tests, in the senlinclient/tests/unit
directory.
Any proposed code change will be automatically rejected by the OpenStack
Jenkins server if the change causes unit test failures.
Running the tests
-----------------
There are a number of ways to run unit tests currently, and there's a
combination of frameworks used depending on what commands you use. The
preferred method is to use tox, which calls stestr via the tox.ini file.
To run all tests simply run::
tox
This will create a virtual environment, load all the packages from
test-requirements.txt and run all unit tests as well as run flake8 and hacking
checks against the code.
Note that you can inspect the tox.ini file to get more details on the available
options and what the test run does by default.
Running a subset of tests using tox
-----------------------------------
One common activity is to just run a single test, you can do this with tox
simply by specifying to just run py27 or py35 tests against a single test::
tox -epy27 senlinclient.tests.unit.v1.test_node.TestNodeList.test_node_list_defaults
Or all tests in the test_node.py file::
tox -epy27 senlinclient.tests.unit.v1.test_node
For more information on these options and how to run tests, please see the
`stestr documentation <https://stestr.readthedocs.io/en/latest/index.html>`_.

View File

@ -1,21 +0,0 @@
===============================================
Welcome to python-senlinclient's documentation!
===============================================
Contents
--------
.. toctree::
:maxdepth: 2
install/index
contributor/index
cli/index
Indices and tables
------------------
* :ref:`genindex`
* :ref:`search`

View File

@ -1,36 +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.
.. _guide-install:
============
Installation
============
If you are installing senlinclient from an OpenStack distribution, follow the
guide provided by the vendor.
If you prefer installing senlinclient from source repo, you can do it by
first get senlinclient code from OpenStack git repository.
::
$ cd /opt/stack
$ git clone https://opendev.org/openstack/python-senlinclient.git
Then execute the following command
::
$ cd python-senlinclient
$ sudo python setup.py install

View File

@ -1,6 +0,0 @@
---
fixes:
- |
[`bug 1814171 <https://bugs.launchpad.net/senlin/+bug/1814171>`_]
Fixed a bug so that cluster delete and node delete return action id
associated with the delete action.

View File

@ -1,4 +0,0 @@
---
other:
- The 'senlin' CLI will be removed in April 2017. This message is now
explicitly printed when senlin CLI commands are invoked.

View File

@ -1,5 +0,0 @@
---
features:
- A new command 'senlin cluster-collect' and its corresponding OSC plugin
command has been added. This new command can be used to aggregate a
specific property across a cluster.

View File

@ -1,4 +0,0 @@
---
fixes:
- The cluster policy list command was broken by new SDK changes and then
fixed. The 'enabled' field is now renamed to 'is_enabled'.

View File

@ -1,5 +0,0 @@
---
features:
- A new CLI command 'senlin cluster-run' and a new OSC plugin command
'openstack cluster run' have been added. Use the 'help' command to find
out how to use it.

View File

@ -1,5 +0,0 @@
---
upgrade:
- OSC commands for cluster scaling are changed from 'cluster scale in'
and 'cluster scale out' to 'cluster shrink' and 'cluster expand'
respectively.

View File

@ -1,5 +0,0 @@
---
features:
- The senlin CLI 'node-delete' and the OSC plugin command
'cluster node delete' now outputs the action IDs when successful. Error
messages are printed when appropriate.

View File

@ -1,6 +0,0 @@
---
upgrade:
- |
Python 2.7 support has been dropped. Last release of python-senlinclient
to support python 2.7 is OpenStack Train. The minimum version of Python now
supported by python-senlinclient is Python 3.6.

View File

@ -1,3 +0,0 @@
---
fixes:
- Fixed a bug that region name is not respected when connecting to cloud.

View File

@ -1,3 +0,0 @@
---
fixes:
- Fixed a bug that force deletion of cluster or node was not working.

View File

@ -1,3 +0,0 @@
---
fixes:
- Changed CURRENT_API_VERSION to "1.10".

View File

@ -1,4 +0,0 @@
---
features:
- The senlinclient now supports API micro-versioning. Current supported
version is 'clustering 1.2'.

View File

@ -1,3 +0,0 @@
---
features:
- Added command for node-check and node-recover.

View File

@ -1,7 +0,0 @@
---
other:
- Switched testr switch to stestr.
- Fixed tox python3 overrides.
- Followed the new PTI for document build.
- Fix tox python3 overrides.
- Removed pypy because pypy is no longer supported by oslo libraries.

View File

@ -1,4 +0,0 @@
---
features:
- A policy-validate command has been added to senlin command line.
OSC support is added as well.

View File

@ -1,4 +0,0 @@
---
features:
- A profile-validate command has been added to command line. It can be
used for validating the spec of a profile without creating it.

View File

@ -1,3 +0,0 @@
---
features:
- The support to python 3.5 has been verified and gated.

View File

@ -1,5 +0,0 @@
---
other:
- The receiver creation command (both senlin CLI and OSC plugin command)
now allow 'cluster' and 'action' to be left unspecified if the receiver
type is not 'webhook'.

View File

@ -1,4 +0,0 @@
---
upgrade:
- The `senlin` command line support is completely dropped. Users are expected
to use `openstack cluster` commands to interact with Senlin service.

View File

@ -1,10 +0,0 @@
---
fixes:
- Fix resource list operations for openstackclient.
- Add filter "is_enabled" for policy binding list.
- Fix policy binding operations including attach, detach and update.
- Remove unsupported sort key "user" for event-list.
- Fix metadata purging.
- Add "cluster_id" colume for openstack cluster event list.
- Support "global_project" arguments for action-list.
- Fix resource update operations.

View File

@ -1,10 +0,0 @@
---
features:
- Support node replace operation.
- Enhance the parameter check for "path" in cluster collect operation.
- Help message for metadata clean operations.
fixes:
- Fix incorrect description of profile/policy validate operations.
- Fix project_id and user_id show bug for profile/policy validate and
cluster policy show operations.
- Fix enabled option for senlin cluster-policy-detach command.

View File

@ -1,4 +0,0 @@
---
features:
- Improved functional test for python-senlinclient.
- Aded profile, policy, cluster and receiver functional test.

View File

@ -1,6 +0,0 @@
===========================
2023.1 Series Release Notes
===========================
.. release-notes::
:branch: stable/2023.1

View File

@ -1,6 +0,0 @@
===========================
2023.2 Series Release Notes
===========================
.. release-notes::
:branch: stable/2023.2

View File

@ -1,275 +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.
# Senlin Release Notes documentation build configuration file, created by
# sphinx-quickstart on Tue Nov 3 17:40:50 2015.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
# sys.path.insert(0, os.path.abspath('.'))
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'reno.sphinxext',
'openstackdocstheme',
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
# source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = 'Senlin Client Release Notes'
copyright = '2015, Senlin Developers'
# Release notes are version independent.
# The full version, including alpha/beta/rc tags.
release = ''
# The short X.Y version.
version = ''
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
# language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
# today = ''
# Else, today_fmt is used as the format for a strftime call.
# today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = []
# The reST default role (used for this markup: `text`) to use for all
# documents.
# default_role = None
# 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
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'native'
# A list of ignored prefixes for module index sorting.
# modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built documents.
# keep_warnings = False
# -- Options for HTML output ----------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'openstackdocs'
# openstackdocstheme options
openstackdocs_repo_name = 'openstack/python-senlinclient'
openstackdocs_bug_project = 'python-senlinclient'
openstackdocs_bug_tag = ''
openstackdocs_auto_name = False
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
# html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
# html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
# html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
# html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
# html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
# html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
# html_extra_path = []
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
# html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
# html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
# html_additional_pages = {}
# If false, no module index is generated.
# html_domain_indices = True
# If false, no index is generated.
# html_use_index = True
# If true, the index is split into individual pages for each letter.
# html_split_index = False
# If true, links to the reST sources are added to the pages.
# html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
# html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
# html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
# html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
# html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'SenlinClientReleaseNotesdoc'
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
# 'preamble': '',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
('index', 'SenlinClientReleaseNotes.tex',
'Senlin Client Release Notes Documentation',
'Senlin Developers', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
# latex_use_parts = False
# If true, show page references after internal links.
# latex_show_pagerefs = False
# If true, show URL addresses after external links.
# latex_show_urls = False
# Documents to append as an appendix to all manuals.
# latex_appendices = []
# If false, no module index is generated.
# latex_domain_indices = True
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'senlinclientreleasenotes',
'Senlin Client Release Notes Documentation',
['Senlin Developers'], 1)
]
# If true, show URL addresses after external links.
# man_show_urls = False
# -- Options for Texinfo output -------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
('index', 'SenlinClientReleaseNotes',
'Senlin Client Release Notes Documentation',
'Senlin Developers', 'SenlinClientReleaseNotes',
'One line description of project.',
'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
# texinfo_appendices = []
# If false, no module index is generated.
# texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
# texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node's menu.
# texinfo_no_detailmenu = False
# -- Options for Internationalization output ------------------------------
locale_dirs = ['locale/']

View File

@ -1,23 +0,0 @@
=============================
Senlin Client Release Notes
=============================
.. toctree::
:maxdepth: 1
unreleased
2023.2
2023.1
zed
yoga
xena
wallaby
victoria
ussuri
train
stein
rocky
queens
pike
ocata
newton

View File

@ -1,288 +0,0 @@
# Andi Chandler <andi@gowling.com>, 2017. #zanata
# Andi Chandler <andi@gowling.com>, 2018. #zanata
# Andi Chandler <andi@gowling.com>, 2022. #zanata
# Andi Chandler <andi@gowling.com>, 2023. #zanata
msgid ""
msgstr ""
"Project-Id-Version: Senlin Client Release Notes\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-02-28 05:32+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2023-07-28 12:45+0000\n"
"Last-Translator: Andi Chandler <andi@gowling.com>\n"
"Language-Team: English (United Kingdom)\n"
"Language: en_GB\n"
"X-Generator: Zanata 4.3.3\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
msgid "0.5.0"
msgstr "0.5.0"
msgid "1.0.0"
msgstr "1.0.0"
msgid "1.1.0"
msgstr "1.1.0"
msgid "1.10.0"
msgstr "1.10.0"
msgid "1.2.0"
msgstr "1.2.0"
msgid "1.6.0"
msgstr "1.6.0"
msgid "1.8.0"
msgstr "1.8.0"
msgid "2.0.0"
msgstr "2.0.0"
msgid "2023.1 Series Release Notes"
msgstr "2023.1 Series Release Notes"
msgid ""
"A new CLI command 'senlin cluster-run' and a new OSC plugin command "
"'openstack cluster run' have been added. Use the 'help' command to find out "
"how to use it."
msgstr ""
"A new CLI command 'senlin cluster-run' and a new OSC plugin command "
"'openstack cluster run' have been added. Use the 'help' command to find out "
"how to use it."
msgid ""
"A new command 'senlin cluster-collect' and its corresponding OSC plugin "
"command has been added. This new command can be used to aggregate a specific "
"property across a cluster."
msgstr ""
"A new command 'senlin cluster-collect' and its corresponding OSC plugin "
"command has been added. This new command can be used to aggregate a specific "
"property across a cluster."
msgid ""
"A policy-validate command has been added to senlin command line. OSC support "
"is added as well."
msgstr ""
"A policy-validate command has been added to Senlin command line. OSC support "
"is added as well."
msgid ""
"A profile-validate command has been added to command line. It can be used "
"for validating the spec of a profile without creating it."
msgstr ""
"A profile-validate command has been added to command line. It can be used "
"for validating the spec of a profile without creating it."
msgid "Add \"cluster_id\" colume for openstack cluster event list."
msgstr "Add \"cluster_id\" column for openstack cluster event list."
msgid "Add filter \"is_enabled\" for policy binding list."
msgstr "Add filter \"is_enabled\" for policy binding list."
msgid "Added command for node-check and node-recover."
msgstr "Added command for node-check and node-recover."
msgid "Aded profile, policy, cluster and receiver functional test."
msgstr "Added profile, policy, cluster and receiver functional test."
msgid "Bug Fixes"
msgstr "Bug Fixes"
msgid "Changed CURRENT_API_VERSION to \"1.10\"."
msgstr "Changed CURRENT_API_VERSION to \"1.10\"."
msgid "Current Series Release Notes"
msgstr "Current Series Release Notes"
msgid "Enhance the parameter check for \"path\" in cluster collect operation."
msgstr "Enhance the parameter check for \"path\" in cluster collect operation."
msgid "Fix enabled option for senlin cluster-policy-detach command."
msgstr "Fix enabled option for Senlin cluster-policy-detach command."
msgid "Fix incorrect description of profile/policy validate operations."
msgstr "Fix incorrect description of profile/policy validate operations."
msgid "Fix metadata purging."
msgstr "Fix metadata purging."
msgid "Fix policy binding operations including attach, detach and update."
msgstr "Fix policy binding operations including attach, detach and update."
msgid ""
"Fix project_id and user_id show bug for profile/policy validate and cluster "
"policy show operations."
msgstr ""
"Fix project_id and user_id show bug for profile/policy validate and cluster "
"policy show operations."
msgid "Fix resource list operations for openstackclient."
msgstr "Fix resource list operations for openstackclient."
msgid "Fix resource update operations."
msgstr "Fix resource update operations."
msgid "Fix tox python3 overrides."
msgstr "Fix tox python3 overrides."
msgid "Fixed a bug that force deletion of cluster or node was not working."
msgstr "Fixed a bug that force deletion of cluster or node was not working."
msgid "Fixed a bug that region name is not respected when connecting to cloud."
msgstr ""
"Fixed a bug that region name is not respected when connecting to cloud."
msgid "Fixed tox python3 overrides."
msgstr "Fixed tox python3 overrides."
msgid "Followed the new PTI for document build."
msgstr "Followed the new PTI for document build."
msgid "Help message for metadata clean operations."
msgstr "Help message for metadata clean operations."
msgid "Improved functional test for python-senlinclient."
msgstr "Improved functional test for python-senlinclient."
msgid "New Features"
msgstr "New Features"
msgid "Newton Series Release Notes"
msgstr "Newton Series Release Notes"
msgid ""
"OSC commands for cluster scaling are changed from 'cluster scale in' and "
"'cluster scale out' to 'cluster shrink' and 'cluster expand' respectively."
msgstr ""
"OSC commands for cluster scaling are changed from 'cluster scale in' and "
"'cluster scale out' to 'cluster shrink' and 'cluster expand' respectively."
msgid "Ocata Series Release Notes"
msgstr "Ocata Series Release Notes"
msgid "Other Notes"
msgstr "Other Notes"
msgid "Pike Series Release Notes"
msgstr "Pike Series Release Notes"
msgid ""
"Python 2.7 support has been dropped. Last release of python-senlinclient to "
"support python 2.7 is OpenStack Train. The minimum version of Python now "
"supported by python-senlinclient is Python 3.6."
msgstr ""
"Python 2.7 support has been dropped. Last release of python-senlinclient to "
"support python 2.7 is OpenStack Train. The minimum version of Python now "
"supported by python-senlinclient is Python 3.6."
msgid "Queens Series Release Notes"
msgstr "Queens Series Release Notes"
msgid "Remove unsupported sort key \"user\" for event-list."
msgstr "Remove unsupported sort key \"user\" for event-list."
msgid "Removed pypy because pypy is no longer supported by oslo libraries."
msgstr "Removed pypy because pypy is no longer supported by oslo libraries."
msgid "Rocky Series Release Notes"
msgstr "Rocky Series Release Notes"
msgid "Senlin Client Release Notes"
msgstr "Senlin Client Release Notes"
msgid "Stein Series Release Notes"
msgstr "Stein Series Release Notes"
msgid "Support \"global_project\" arguments for action-list."
msgstr "Support \"global_project\" arguments for action-list."
msgid "Support node replace operation."
msgstr "Support node replace operation."
msgid "Switched testr switch to stestr."
msgstr "Switched testr switch to stestr."
msgid ""
"The 'senlin' CLI will be removed in April 2017. This message is now "
"explicitly printed when senlin CLI commands are invoked."
msgstr ""
"The 'senlin' CLI will be removed in April 2017. This message is now "
"explicitly printed when Senlin CLI commands are invoked."
msgid ""
"The `senlin` command line support is completely dropped. Users are expected "
"to use `openstack cluster` commands to interact with Senlin service."
msgstr ""
"The `senlin` command line support is completely dropped. Users are expected "
"to use `openstack cluster` commands to interact with Senlin service."
msgid ""
"The cluster policy list command was broken by new SDK changes and then "
"fixed. The 'enabled' field is now renamed to 'is_enabled'."
msgstr ""
"The cluster policy list command was broken by new SDK changes and then "
"fixed. The 'enabled' field is now renamed to 'is_enabled'."
msgid ""
"The receiver creation command (both senlin CLI and OSC plugin command) now "
"allow 'cluster' and 'action' to be left unspecified if the receiver type is "
"not 'webhook'."
msgstr ""
"The receiver creation command (both Senlin CLI and OSC plugin command) now "
"allow 'cluster' and 'action' to be left unspecified if the receiver type is "
"not 'webhook'."
msgid ""
"The senlin CLI 'node-delete' and the OSC plugin command 'cluster node "
"delete' now outputs the action IDs when successful. Error messages are "
"printed when appropriate."
msgstr ""
"The Senlin CLI 'node-delete' and the OSC plugin command 'cluster node "
"delete' now outputs the action IDs when successful. Error messages are "
"printed when appropriate."
msgid ""
"The senlinclient now supports API micro-versioning. Current supported "
"version is 'clustering 1.2'."
msgstr ""
"The senlinclient now supports API micro-versioning. Current supported "
"version is 'clustering 1.2'."
msgid "The support to python 3.5 has been verified and gated."
msgstr "The support to python 3.5 has been verified and gated."
msgid "Train Series Release Notes"
msgstr "Train Series Release Notes"
msgid "Upgrade Notes"
msgstr "Upgrade Notes"
msgid "Ussuri Series Release Notes"
msgstr "Ussuri Series Release Notes"
msgid "Victoria Series Release Notes"
msgstr "Victoria Series Release Notes"
msgid "Wallaby Series Release Notes"
msgstr "Wallaby Series Release Notes"
msgid "Xena Series Release Notes"
msgstr "Xena Series Release Notes"
msgid "Yoga Series Release Notes"
msgstr "Yoga Series Release Notes"
msgid "Zed Series Release Notes"
msgstr "Zed Series Release Notes"
msgid ""
"[`bug 1814171 <https://bugs.launchpad.net/senlin/+bug/1814171>`_] Fixed a "
"bug so that cluster delete and node delete return action id associated with "
"the delete action."
msgstr ""
"[`bug 1814171 <https://bugs.launchpad.net/senlin/+bug/1814171>`_] Fixed a "
"bug so that cluster delete and node delete return action id associated with "
"the delete action."

View File

@ -1,42 +0,0 @@
# Gérald LONLAS <g.lonlas@gmail.com>, 2016. #zanata
msgid ""
msgstr ""
"Project-Id-Version: python-senlinclient\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-03-07 08:03+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2016-10-22 06:14+0000\n"
"Last-Translator: Gérald LONLAS <g.lonlas@gmail.com>\n"
"Language-Team: French\n"
"Language: fr\n"
"X-Generator: Zanata 4.3.3\n"
"Plural-Forms: nplurals=2; plural=(n > 1)\n"
msgid "0.5.0"
msgstr "0.5.0"
msgid "1.0.0"
msgstr "1.0.0"
msgid "Bug Fixes"
msgstr "Corrections de bugs"
msgid "Current Series Release Notes"
msgstr "Note de la release actuelle"
msgid "New Features"
msgstr "Nouvelles fonctionnalités"
msgid "Newton Series Release Notes"
msgstr "Note de release pour Newton"
msgid "Other Notes"
msgstr "Autres notes"
msgid "Senlin Client Release Notes"
msgstr "Note de release du Client Senlin"
msgid "Upgrade Notes"
msgstr "Notes de mises à jours"

View File

@ -1,40 +0,0 @@
# zzxwill <zzxwill@gmail.com>, 2016. #zanata
msgid ""
msgstr ""
"Project-Id-Version: python-senlinclient\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-03-07 08:03+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2016-06-25 02:41+0000\n"
"Last-Translator: zzxwill <zzxwill@gmail.com>\n"
"Language-Team: Chinese (China)\n"
"Language: zh_CN\n"
"X-Generator: Zanata 4.3.3\n"
"Plural-Forms: nplurals=1; plural=0\n"
msgid "0.5.0"
msgstr "0.5.0"
msgid "Added command for node-check and node-recover."
msgstr "已为node-check和node-recover添加了命令。"
msgid "Current Series Release Notes"
msgstr "当前版本发布说明"
msgid "New Features"
msgstr "新特性"
msgid ""
"OSC commands for cluster scaling are changed from 'cluster scale in' and "
"'cluster scale out' to 'cluster shrink' and 'cluster expand' respectively."
msgstr ""
"集群扩展的OSC命令分别从'cluster scale in'和'cluster scale out'改成了'cluster "
"shrink'和'cluster expand'。"
msgid "Senlin Client Release Notes"
msgstr "Senlin Client发布说明"
msgid "Upgrade Notes"
msgstr "升级说明"

View File

@ -1,6 +0,0 @@
===================================
Newton Series Release Notes
===================================
.. release-notes::
:branch: origin/stable/newton

View File

@ -1,6 +0,0 @@
===================================
Ocata Series Release Notes
===================================
.. release-notes::
:branch: origin/stable/ocata

View File

@ -1,6 +0,0 @@
===================================
Pike Series Release Notes
===================================
.. release-notes::
:branch: stable/pike

View File

@ -1,6 +0,0 @@
===================================
Queens Series Release Notes
===================================
.. release-notes::
:branch: stable/queens

View File

@ -1,6 +0,0 @@
===================================
Rocky Series Release Notes
===================================
.. release-notes::
:branch: stable/rocky

View File

@ -1,6 +0,0 @@
===================================
Stein Series Release Notes
===================================
.. release-notes::
:branch: stable/stein

View File

@ -1,6 +0,0 @@
==========================
Train Series Release Notes
==========================
.. release-notes::
:branch: stable/train

View File

@ -1,5 +0,0 @@
==============================
Current Series Release Notes
==============================
.. release-notes::

View File

@ -1,6 +0,0 @@
===========================
Ussuri Series Release Notes
===========================
.. release-notes::
:branch: stable/ussuri

View File

@ -1,6 +0,0 @@
=============================
Victoria Series Release Notes
=============================
.. release-notes::
:branch: stable/victoria

View File

@ -1,6 +0,0 @@
============================
Wallaby Series Release Notes
============================
.. release-notes::
:branch: stable/wallaby

View File

@ -1,6 +0,0 @@
=========================
Xena Series Release Notes
=========================
.. release-notes::
:branch: stable/xena

View File

@ -1,6 +0,0 @@
=========================
Yoga Series Release Notes
=========================
.. release-notes::
:branch: unmaintained/yoga

View File

@ -1,6 +0,0 @@
========================
Zed Series Release Notes
========================
.. release-notes::
:branch: stable/zed

View File

@ -1,19 +0,0 @@
# Requirements lower bounds listed here are our best effort to keep them up to
# date but we do not test them so no guarantee of having them all correct. If
# you find any incorrect lower bounds, let us know or propose a fix.
# 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
PrettyTable>=0.7.2 # BSD
keystoneauth1>=3.11.0 # Apache-2.0
openstacksdk>=0.24.0 # Apache-2.0
osc-lib>=1.11.0 # Apache-2.0
oslo.i18n>=3.15.3 # Apache-2.0
oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0
oslo.utils>=3.33.0 # Apache-2.0
python-heatclient>=1.10.0 # Apache-2.0
PyYAML>=5.3.1 # MIT
requests>=2.14.2 # Apache-2.0

View File

@ -1,16 +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 pbr.version
__version__ = pbr.version.VersionInfo('python-senlinclient').version_string()

View File

@ -1,23 +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 senlinclient.common import utils
def Client(api_ver, *args, **kwargs):
"""Import versioned client module.
:param api_ver: API version required.
"""
module = utils.import_versioned_module(api_ver, 'client')
cls = getattr(module, 'Client')
return cls(*args, **kwargs)

View File

@ -1,312 +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 keystoneauth1.exceptions import base as kae_base
from keystoneauth1.exceptions import http as kae_http
from openstack import exceptions as sdkexc
from oslo_serialization import jsonutils
from requests import exceptions as reqexc
from senlinclient.common.i18n import _
verbose = False
class BaseException(Exception):
"""An error occurred."""
def __init__(self, message=None):
self.message = message
def __str__(self):
return self.message or self.__class__.__doc__
class CommandError(BaseException):
"""Invalid usage of CLI."""
class FileFormatError(BaseException):
"""Illegal file format detected."""
class PollingExceededError(BaseException):
"""Desired resource state not achived within polling period."""
class HTTPException(BaseException):
"""Base exception for all HTTP-derived exceptions."""
code = 'N/A'
def __init__(self, error=None):
super(HTTPException, self).__init__(error)
try:
self.error = error
if 'error' not in self.error:
raise KeyError(_('Key "error" not exists'))
except KeyError:
# If key 'error' does not exist, self.message becomes
# no sense. In this case, we return doc of current
# exception class instead.
self.error = {'error': {'message': self.__class__.__doc__}}
except Exception:
self.error = {'error':
{'message': self.message or self.__class__.__doc__}}
def __str__(self):
message = self.error['error'].get('message', 'Internal Error')
if verbose:
traceback = self.error['error'].get('traceback', '')
return (_('ERROR: %(message)s\n%(traceback)s') %
{'message': message, 'traceback': traceback})
else:
code = self.error['error'].get('code', 'Unknown')
return _('ERROR(%(code)s): %(message)s') % {'code': code,
'message': message}
class ClientError(HTTPException):
pass
class ServerError(HTTPException):
pass
class HTTPBadRequest(ClientError):
# 400
pass
class HTTPUnauthorized(ClientError):
# 401
pass
class HTTPForbidden(ClientError):
# 403
pass
class HTTPNotFound(ClientError):
# 404
pass
class HTTPMethodNotAllowed(ClientError):
# 405
pass
class HTTPNotAcceptable(ClientError):
# 406
pass
class HTTPProxyAuthenticationRequired(ClientError):
# 407
pass
class HTTPRequestTimeout(ClientError):
# 408
pass
class HTTPConflict(ClientError):
# 409
pass
class HTTPGone(ClientError):
# 410
pass
class HTTPLengthRequired(ClientError):
# 411
pass
class HTTPPreconditionFailed(ClientError):
# 412
pass
class HTTPRequestEntityTooLarge(ClientError):
# 413
pass
class HTTPRequestURITooLong(ClientError):
# 414
pass
class HTTPUnsupportedMediaType(ClientError):
# 415
pass
class HTTPRequestRangeNotSatisfiable(ClientError):
# 416
pass
class HTTPExpectationFailed(ClientError):
# 417
pass
class HTTPInternalServerError(ServerError):
# 500
pass
class HTTPNotImplemented(ServerError):
# 501
pass
class HTTPBadGateway(ServerError):
# 502
pass
class HTTPServiceUnavailable(ServerError):
# 503
pass
class HTTPGatewayTimeout(ServerError):
# 504
pass
class HTTPVersionNotSupported(ServerError):
# 505
pass
class ConnectionRefused(HTTPException):
# 111
pass
_EXCEPTION_MAP = {
111: ConnectionRefused,
400: HTTPBadRequest,
401: HTTPUnauthorized,
403: HTTPForbidden,
404: HTTPNotFound,
405: HTTPMethodNotAllowed,
406: HTTPNotAcceptable,
407: HTTPProxyAuthenticationRequired,
408: HTTPRequestTimeout,
409: HTTPConflict,
410: HTTPGone,
411: HTTPLengthRequired,
412: HTTPPreconditionFailed,
413: HTTPRequestEntityTooLarge,
414: HTTPRequestURITooLong,
415: HTTPUnsupportedMediaType,
416: HTTPRequestRangeNotSatisfiable,
417: HTTPExpectationFailed,
500: HTTPInternalServerError,
501: HTTPNotImplemented,
502: HTTPBadGateway,
503: HTTPServiceUnavailable,
504: HTTPGatewayTimeout,
505: HTTPVersionNotSupported,
}
def parse_exception(exc):
"""Parse exception code and yield useful information.
:param exc: details of the exception.
"""
if isinstance(exc, sdkexc.HttpException):
if exc.details is None:
data = exc.response.json()
code = data.get('code', None)
message = data.get('message', None)
error = data.get('error', None)
if error:
record = {
'error': {
'code': exc.http_status,
'message': message or exc.message
}
}
else:
info = data.values()[0]
record = {
'error': {
'code': info.get('code', code),
'message': info.get('message', message)
}
}
else:
try:
record = jsonutils.loads(exc.details)
except Exception:
# If the exc.details is not in JSON format
record = {
'error': {
'code': exc.http_status,
'message': exc,
}
}
elif isinstance(exc, reqexc.RequestException):
# Exceptions that are not captured by SDK
record = {
'error': {
'code': exc.message[1].errno,
'message': exc.message[0],
}
}
elif isinstance(exc, str):
record = jsonutils.loads(exc)
# some exception from keystoneauth1 is not shaped by SDK
elif isinstance(exc, kae_http.HttpError):
record = {
'error': {
'code': exc.http_status,
'message': exc.message
}
}
elif isinstance(exc, kae_base.ClientException):
record = {
'error': {
# other exceptions from keystoneauth1 is an internal
# error to senlin, so set status code to 500
'code': 500,
'message': exc.message
}
}
else:
print(_('Unknown exception: %s') % exc)
return
try:
code = record['error']['code']
except KeyError as err:
print(_('Malformed exception record, missing field "%s"') % err)
print(_('Original error record: %s') % record)
return
if code in _EXCEPTION_MAP:
inst = _EXCEPTION_MAP.get(code)
raise inst(record)
else:
raise HTTPException(record)

View File

@ -1,51 +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 osc_lib.command import command
class RawFormat(command.ShowOne):
def produce_output(self, parsed_args, column_names, data):
if data is None:
return
self.formatter.emit_one(column_names, data,
self.app.stdout, parsed_args)
class JsonFormat(RawFormat):
@property
def formatter_default(self):
return 'json'
class YamlFormat(RawFormat):
@property
def formatter_default(self):
return 'yaml'
class ShellFormat(RawFormat):
@property
def formatter_default(self):
return 'shell'
class ValueFormat(RawFormat):
@property
def formatter_default(self):
return 'value'

View File

@ -1,24 +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.
See https://docs.openstack.org/oslo.i18n/latest/user/usage.html
"""
import oslo_i18n
_translators = oslo_i18n.TranslatorFactory(domain='senlinclient')
# The primary translation function using the well-known name "_"
_ = _translators.primary

View File

@ -1,236 +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 heatclient.common import template_utils
import logging
from openstack import exceptions as sdk_exc
from oslo_serialization import jsonutils
from oslo_utils import importutils
import prettytable
import time
import yaml
from senlinclient.common import exc
from senlinclient.common.i18n import _
log = logging.getLogger(__name__)
def import_versioned_module(version, submodule=None):
module = 'senlinclient.v%s' % version
if submodule:
module = '.'.join((module, submodule))
return importutils.import_module(module)
def format_nested_dict(d, fields, column_names):
if d is None:
return ''
pt = prettytable.PrettyTable(caching=False, print_empty=False,
header=True, field_names=column_names)
for n in column_names:
pt.align[n] = 'l'
keys = sorted(d.keys())
for field in keys:
value = d[field]
if not isinstance(value, str):
value = jsonutils.dumps(value, indent=2, ensure_ascii=False)
if value is None:
value = '-'
pt.add_row([field, value.strip('"')])
return pt.get_string()
def nested_dict_formatter(d, column_names):
return lambda o: format_nested_dict(o, d, column_names)
def json_formatter(js):
return jsonutils.dumps(js, indent=2, ensure_ascii=False)
def list_formatter(record):
return '\n'.join(record or [])
def print_action_result(rid, res):
if res[0] == "OK":
output = _("accepted by action %s") % res[1]
else:
output = _("failed due to '%s'") % res[1]
print(_(" %(cid)s: %(output)s") % {"cid": rid, "output": output})
def format_parameters(params, parse_semicolon=True):
"""Reformat parameters into dict of format expected by the API."""
if not params or params == ['{}']:
return {}
if parse_semicolon:
# expect multiple invocations of --parameters but fall back to ';'
# delimited if only one --parameters is specified
if len(params) == 1:
params = params[0].split(';')
parameters = {}
for p in params:
try:
(n, v) = p.split(('='), 1)
except ValueError:
msg = _('Malformed parameter(%s). Use the key=value format.') % p
raise exc.CommandError(msg)
if n not in parameters:
parameters[n] = v
else:
if not isinstance(parameters[n], list):
parameters[n] = [parameters[n]]
parameters[n].append(v)
return parameters
def format_json_parameter(param):
'''Return JSON dict from JSON formatted param.
:parameter param JSON formatted string
:return JSON dict
'''
if not param:
return {}
try:
return jsonutils.loads(param)
except ValueError:
msg = _('Malformed parameter(%s). Use the JSON format.') % param
raise exc.CommandError(msg)
def get_spec_content(filename):
with open(filename, 'r') as f:
try:
data = yaml.safe_load(f)
except Exception as ex:
raise exc.CommandError(_('The specified file is not a valid '
'YAML file: %s') % str(ex))
return data
def process_stack_spec(spec):
# Heat stack is a headache, because it demands for client side file
# content processing
try:
tmplfile = spec.get('template', None)
except AttributeError as ex:
raise exc.FileFormatError(_('The specified file is not a valid '
'YAML file: %s') % str(ex))
if not tmplfile:
raise exc.FileFormatError(_('No template found in the given '
'spec file'))
tpl_files, template = template_utils.get_template_contents(
template_file=tmplfile)
env_files, env = template_utils.process_multiple_environments_and_files(
env_paths=spec.get('environment', None))
new_spec = {
# TODO(Qiming): add context support
'disable_rollback': spec.get('disable_rollback', True),
'context': spec.get('context', {}),
'parameters': spec.get('parameters', {}),
'timeout': spec.get('timeout', 60),
'template': template,
'files': dict(list(tpl_files.items()) + list(env_files.items())),
'environment': env
}
return new_spec
def await_action(senlin_client, action_id,
poll_count_max=10, poll_interval=5):
def check_action():
try:
action = senlin_client.get_action(action_id)
except sdk_exc.ResourceNotFound:
raise exc.CommandError(_('Action not found: %s')
% action_id)
action_states = ['succeeded', 'failed', 'cancelled']
if action.status.lower() in action_states:
log.info("Action %s completed with status %s."
% (action.id, action.status))
return True
log.info("Awaiting action %s completion status (current: %s)."
% (action.id, action.status))
return False
_check(check_action, poll_count_max, poll_interval)
def await_cluster_status(senlin_client, cluster_id, statuses=None,
poll_count_max=10, poll_interval=5):
if not statuses or len(statuses) <= 0:
statuses = ['ACTIVE', 'ERROR', 'WARNING']
def check_status():
try:
cluster = senlin_client.get_cluster(cluster_id)
except sdk_exc.ResourceNotFound:
raise exc.CommandError(_('Cluster not found: %s') % cluster_id)
if cluster.status.lower() in [fs.lower() for fs in statuses]:
return True
log.info("Awaiting cluster status (desired: %s - current: %s)." %
(', '.join(statuses), cluster.status))
return False
_check(check_status, poll_count_max, poll_interval)
def await_cluster_delete(senlin_client, cluster_id,
poll_count_max=10, poll_interval=5):
def check_deleted():
try:
senlin_client.get_cluster(cluster_id)
except sdk_exc.ResourceNotFound:
log.info("Successfully deleted cluster %s." % cluster_id)
return True
log.info("Awaiting cluster deletion for %s." % cluster_id)
return False
_check(check_deleted, poll_count_max, poll_interval)
def _check(check_func, poll_count_max=10, poll_interval=5):
# a negative poll_count_max is considered indefinite
poll_increment = 1
if poll_count_max < 0:
poll_count_max = 1
poll_increment = 0
poll_count = 0
while poll_count < poll_count_max:
if check_func():
return
time.sleep(poll_interval)
poll_count += poll_increment
raise exc.PollingExceededError()

View File

@ -1,535 +0,0 @@
# zzxwill <zzxwill@gmail.com>, 2016. #zanata
msgid ""
msgstr ""
"Project-Id-Version: python-senlinclient VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2018-02-28 14:44+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2016-08-20 01:56+0000\n"
"Last-Translator: zzxwill <zzxwill@gmail.com>\n"
"Language-Team: Chinese (China)\n"
"Language: zh_CN\n"
"X-Generator: Zanata 4.3.3\n"
"Plural-Forms: nplurals=1; plural=0\n"
msgid ""
"A dictionary of parameters that will be passed to target action when the "
"receiver is triggered"
msgstr "字典类型的参数在receiver被触发时将传递给目标动作"
msgid ""
"A positive integer meaning the number of nodes to add, or a negative integer "
"indicating the number of nodes to remove"
msgstr "正整数意味着要添加的节点个数,或者一个负整数,表明要删除的节点个数"
msgid ""
"A value that is interpreted as the percentage of size adjustment. This value "
"can be positive or negative"
msgstr "一个值被解释为大小调整的百分比,这个值可以是正数,也可以是负数"
#, python-format
msgid "Action not found: %s"
msgstr "Action没找到%s"
msgid "Adjustment cannot be zero."
msgstr "调整值不能为0。"
msgid ""
"An integer specifying the number of nodes for adjustment when <PERCENTAGE> "
"is specified"
msgstr "当指定<PERCENTAGE>时,整数值表明节点调整的数目"
msgid "Are you sure you want to delete this cluster(s) [y/N]?"
msgstr "你确认你想删除这个集群吗[y/N]?"
msgid "Are you sure you want to delete this node(s) [y/N]?"
msgstr "你确认你想删除这个节点吗[y/N]?"
msgid "Are you sure you want to delete this policy(s) [y/N]?"
msgstr "你确认你想删除这个策略吗[y/N]?"
msgid "Are you sure you want to delete this profile(s) [y/N]?"
msgstr "你确认你想删除这个样版吗[y/N]?"
msgid "Are you sure you want to delete this receiver(s) [y/N]?"
msgstr "你确认你想删除这个receiver吗[y/N]?"
msgid "Cluster Id or Name for this node"
msgstr "该节点的集群Id或名称"
msgid "Cluster capacity must be larger than or equal to zero."
msgstr "集群的容量必须大于或等于0。"
msgid "Cluster creation timeout in seconds"
msgstr "集群创建超时时限(秒)"
#, python-format
msgid "Cluster not found: %s"
msgstr "集群没找到:%s"
msgid ""
"Desired capacity of the cluster. Default to min_size if min_size is "
"specified else 0."
msgstr "集群期望的容量如果min_size给定了默认是min_size否则是0。"
#, python-format
msgid "ERROR(%(code)s): %(message)s"
msgstr "错误(%(code)s): %(message)s"
#, python-format
msgid ""
"ERROR: %(message)s\n"
"%(traceback)s"
msgstr ""
"错误: %(message)s\n"
"%(traceback)s"
#, python-format
msgid "Event not found: %s"
msgstr "事件没有找到:%s"
#, python-format
msgid "Failed to delete %(count)s of the %(total)s specified policy(s)."
msgstr "删除%(total)s个给定的策略中的%(count)s个失败。"
#, python-format
msgid "Failed to delete %(count)s of the %(total)s specified profile(s)."
msgstr "删除%(total)s个给定的样版中的%(count)s个失败。"
#, python-format
msgid "Failed to delete %(count)s of the %(total)s specified receiver(s)."
msgstr "删除%(total)s个给定的receiver中的%(count)s个失败。"
msgid ""
"Filter parameters to apply on returned clusters. This can be specified "
"multiple times, or once with parameters separated by a semicolon. The valid "
"filter keys are: ['status', 'name']"
msgstr ""
"过滤参数以应用在返回的集群上。该操作可以指定多次,或者用分号分隔参数之后指定"
"一次。有效的过滤键是['status', 'name']"
msgid ""
"Filter parameters to apply on returned nodes. This can be specified multiple "
"times, or once with parameters separated by a semicolon. The valid filter "
"keys are: ['status','name']"
msgstr ""
"过滤参数以应用在返回的节点上。该参数可以指定多次,也可以用分号分隔参数之后执"
"行一次。有效的过滤键是['status','name']"
msgid ""
"Filter parameters to apply on returned policies. This can be specified "
"multiple times, or once with parameters separated by a semicolon. The valid "
"filter keys are: ['type', 'name']"
msgstr ""
"过滤参数以应用在返回的策略上。该参数可以指定多次,也可以用分号分隔参数之后执"
"行一次。有效的过滤键是['type', 'name']"
msgid "ID of event to display details for"
msgstr "显示事件详情的ID"
msgid "ID or name of cluster from which nodes are to be listed"
msgstr "集群的ID或名字该集群中的节点都将被列出来"
msgid "ID or name of cluster(s) to operate on."
msgstr "将要操作的集群的ID或名字。"
msgid "ID or name of new profile to use"
msgstr "将要使用的新样版的ID或名字"
msgid "ID or name of node(s) to check."
msgstr "将要检查的节点的ID或名字。"
msgid "ID or name of node(s) to recover."
msgstr "将要恢复的节点的ID或名字。"
msgid ""
"ID or name of nodes to be added; multiple nodes can be separated with \",\""
msgstr "待添加节点的ID或名称多个节点可以用“”分隔"
msgid "ID or name of policy to be attached"
msgstr "将要关联的策略的ID或名字"
msgid "ID or name of policy to be detached"
msgstr "将要解除关联的策略的ID或名字"
msgid "ID or name of policy to be updated"
msgstr "将被更新的策略的ID或名字"
msgid "ID or name of the cluster to query on"
msgstr "将要查询的集群的ID或名字"
msgid "ID or name of the policy to query on"
msgstr "将要查询的策略的ID或名字"
msgid "Include physical object details"
msgstr "包含物理对象的详情"
msgid ""
"Indicate that the cluster list should include clusters from all projects. "
"This option is subject to access policy checking. Default is False"
msgstr ""
"这表明集群列表应该包含所有项目的集群。该选项从属于访问策略检查默认是False"
msgid ""
"Indicate that the list should include policies from all projects. This "
"option is subject to access policy checking. Default is False"
msgstr ""
"这表明列表应该包含所有项目的策略。该选项从属于访问策略检查默认是False"
msgid ""
"Indicate that the list should include profiles from all projects. This "
"option is subject to access policy checking. Default is False"
msgstr ""
"这表明列表应该包含所有项目的样版。该选项从属于访问策略检查默认是False"
msgid ""
"Indicate that the list should include receivers from all projects. This "
"option is subject to access policy checking. Default is False"
msgstr ""
"这表明列表应该包含所有项目的receiver。该选项从属于访问策略检查默认是False"
msgid ""
"Indicate that this node list should include nodes from all projects. This "
"option is subject to access policy checking. Default is False"
msgstr ""
"这表明节点列表应该包含所有项目的节点。该选项从属于访问策略检查默认是False"
msgid "Key \"error\" not exists"
msgstr "键\"error\"不存在"
msgid "Limit the number of actions returned"
msgstr "限定action返回的个数"
msgid "Limit the number of clusters returned"
msgstr "限定集群返回的个数"
msgid "Limit the number of events returned"
msgstr "限定事件返回的个数"
msgid "Limit the number of nodes returned"
msgstr "限定节点返回的个数"
msgid "Limit the number of policies returned"
msgstr "限定策略返回的个数"
msgid "Limit the number of profiles returned"
msgstr "限定样版返回的个数"
msgid "Limit the number of receivers returned"
msgstr "限定receiver返回的个数"
#, python-format
msgid "Malformed exception record, missing field \"%s\""
msgstr "异常记录的格式不正确,缺少字段\"%s\""
#, python-format
msgid "Malformed parameter(%s). Use the key=value format."
msgstr "参数(%s)格式不正确使用key=value格式。"
msgid "Malformed parameter(status:ACTIVE). Use the key=value format."
msgstr "参数(status:ACTIVE)格式不正确使用key=value格式。"
msgid "Max size cannot be less than the specified capacity."
msgstr "最大值不能小于给定的容量。"
msgid "Max size of the cluster. Default to -1, means unlimited"
msgstr "集群容量上限。默认为-1表示无限制"
msgid ""
"Metadata values to be attached to the cluster. This can be specified "
"multiple times, or once with key-value pairs separated by a semicolon."
msgstr ""
"将要关联到集群的元数据。该元数据可以指定多次,也可以用分号分割键值对之后指定"
"一次。"
msgid ""
"Metadata values to be attached to the node. This can be specified multiple "
"times, or once with key-value pairs separated by a semicolon"
msgstr ""
"将要关联到节点的元数据。该元数据可以指定多次,也可以用分号分隔键值对之后指定"
"一次"
msgid ""
"Metadata values to be attached to the node. This can be specified multiple "
"times, or once with key-value pairs separated by a semicolon."
msgstr ""
"将要关联到节点的元数据。该元数据可以指定多次,也可以用分号分隔键值对之后指定"
"一次。"
msgid ""
"Metadata values to be attached to the profile. This can be specified "
"multiple times, or once with key-value pairs separated by a semicolon"
msgstr ""
"将要关联到样版的元数据。该元数据可以指定多次,也可以用分号分隔键值对之后指定"
"一次"
msgid "Min size cannot be larger than max size."
msgstr "最小值不能大于最大值。"
msgid "Min size cannot be larger than the specified capacity"
msgstr "最小值不能大于给定的容量"
msgid "Min size cannot be less than zero."
msgstr "最小值不能小于0。"
msgid "Min size of the cluster. Default to 0"
msgstr "集群容量下限。默认为0"
#, fuzzy
msgid "Min step is only used with percentage."
msgstr "最小步骤只能使用百分数。"
msgid "Missing 'properties' key in spec file."
msgstr "规格文件里缺少键'properties'。"
msgid "Missing 'type' key in spec file."
msgstr "规格文件里缺少键'type'。"
msgid "Missing 'version' key in spec file."
msgstr "规格文件里缺少键'version'。"
msgid "Name of the cluster to create"
msgstr "要创建的集群的名称"
msgid "Name of the node to create"
msgstr "要创建的节点的名称"
msgid "Name of the policy to create"
msgstr "要创建的策略的名称"
msgid "Name of the profile to create"
msgstr "要创建的样版的名称"
msgid "Name of the receiver to create"
msgstr "要创建的receiver的名称"
msgid "Name or ID of cluster to be updated"
msgstr "将要更新的集群的名称或ID"
msgid "Name or ID of cluster to nodes from"
msgstr "该集群的节点的名字或ID"
msgid "Name or ID of cluster to operate on"
msgstr "将要操作的集群的名字或ID"
msgid "Name or ID of cluster to query on"
msgstr "将要查询的集群的名字或ID"
msgid "Name or ID of cluster to show"
msgstr "将要显示的集群的名字或ID"
msgid "Name or ID of cluster(s) to delete."
msgstr "将要删除的集群的名称或ID。"
msgid "Name or ID of node to update"
msgstr "将要更新的节点的名字或ID"
msgid "Name or ID of node(s) to delete."
msgstr "待删除的节点的名称或ID。"
msgid ""
"Name or ID of nodes to be deleted; multiple nodes can be separated with \",\""
msgstr "待删除的节点的名称或ID多个节点可以用“”分隔"
msgid "Name or ID of policy(s) to delete"
msgstr "将要删除的策略的名称或ID"
msgid "Name or ID of profile(s) to delete"
msgstr "将要删除的样版的名称或ID"
msgid "Name or ID of receiver(s) to delete"
msgstr "要删除的receiver的名称或ID"
msgid "Name or ID of the action to show the details for"
msgstr "显示action详情的名字或ID"
msgid "Name or ID of the node to show the details for"
msgstr "显示节点详情的名字或ID。"
msgid "Name or ID of the policy to be updated"
msgstr "将要更新的策略的名称或ID"
msgid "Name or ID of the profile to update"
msgstr "将要更新的样版的名字或ID"
msgid "Name or ID of the receiver to show"
msgstr "待显示的receiver的名字或ID"
msgid "Name or Id of the policy to show"
msgstr "将要显示的策略的名字或ID"
msgid "New lower bound of cluster size"
msgstr "集群大小的新下限值"
msgid "New name for the cluster to update"
msgstr "将要更新的集群的新名称"
msgid "New name for the node"
msgstr "节点的新名称"
msgid "New name of the policy to be updated"
msgstr "将被更新的策略的新名字"
msgid "New timeout (in seconds) value for the cluster"
msgstr "集群新的超时时间(秒)"
msgid ""
"New upper bound of cluster size. A value of -1 indicates no upper limit on "
"cluster size"
msgstr "集群容量的上限值。默认为-1表示集群大小无限制"
msgid "No template found in the given spec file"
msgstr "给定的样版文件里找不到模板"
#, python-format
msgid "Node not found: %s"
msgstr "节点没有找到:%s"
msgid "Number of nodes to be added to the specified cluster"
msgstr "将要加入该集群中的节点的个数"
msgid "Number of nodes to be deleted from the specified cluster"
msgstr "将要从该集群中删除的节点的个数"
msgid "Only one of 'capacity', 'adjustment' and 'percentage' can be specified."
msgstr "只能指定'capacity', 'adjustment'和'percentage'中的一个值。"
msgid "Only return clusters that appear after the given cluster ID"
msgstr "仅仅返回给定集群ID后出现的集群"
msgid "Only return events that appear after the given event ID"
msgstr "仅仅返回给定事件ID后出现的事件"
msgid "Only return nodes that appear after the given node ID"
msgstr "仅仅返回给定节点ID后出现的节点"
#, python-format
msgid "Original error record: %s"
msgstr "初始错误记录:%s"
msgid "Percentage cannot be zero."
msgstr "百分数不能为0。"
#, python-format
msgid "Policy Type not found: %s"
msgstr "策略类型没找到:%s"
#, python-format
msgid "Policy not found: %s"
msgstr "策略没找到:%s"
msgid "Policy type to retrieve"
msgstr "将要获取的策略类型"
msgid "Print full IDs in list"
msgstr "将全部ID打印到列表里"
msgid "Profile Id or Name used for this node"
msgstr "用于该节点的样板的Id或名称"
#, python-format
msgid "Profile Type not found: %s"
msgstr "找不到样版类型:%s"
#, python-format
msgid "Profile not found: %s"
msgstr "找不到样版:%s"
msgid "Profile type to retrieve"
msgstr "将要获取的样版类型"
#, python-format
msgid "Receiver not found: %s"
msgstr "Receiver没找到%s"
msgid "Role for this node in the specific cluster"
msgstr "在给定集群里的该节点的角色"
msgid "Role for this node in the specific cluster."
msgstr "特定集群中的这个节点的角色。"
msgid "Skip yes/no prompt (assume yes)"
msgstr "跳过yes确认/没有提示默认是yes"
msgid ""
"Sorting option which is a string containing a list of keys separated by "
"commas. Each key can be optionally appended by a sort direction (:asc or :"
"desc). The valid sort keys are: ['name', 'status', 'init_at', 'created_at', "
"'updated_at']"
msgstr ""
"排序选项是一个字符串,该字符串包含了一系列用逗号分隔的键。每个键可以附加上一"
"个排序方向值(:asc或:desc这个排序方向值是可选的。有效的过滤键是"
"['name', 'status', 'init_at', 'created_at', 'updated_at']"
msgid ""
"Sorting option which is a string containing a list of keys separated by "
"commas. Each key can be optionally appended by a sort direction (:asc or :"
"desc). The valid sort keys are: ['name', 'target', 'action', 'created_at', "
"'status']"
msgstr ""
"排序选项是一个字符串,该字符串包含了一系列用逗号分隔的键。每个键可以附加上一"
"个排序方向值(:asc或:desc这个排序方向值是可选的。有效的过滤键是"
"['name', 'target', 'action', 'created_at', 'status']"
msgid ""
"Sorting option which is a string containing a list of keys separated by "
"commas. Each key can be optionally appended by a sort direction (:asc or :"
"desc). The valid sort keys are: ['name', 'type', 'action', 'cluster_id', "
"'created_at']"
msgstr ""
"排序选项是一个字符串,该字符串包含了一系列用逗号分隔的键。每个键可以附加上一"
"个排序方向值(:asc或:desc这个排序方向值是可选的。有效的过滤键是"
"['name', 'type', 'action', 'cluster_id', 'created_at']"
msgid ""
"Sorting option which is a string containing a list of keys separated by "
"commas. Each key can be optionally appended by a sort direction (:asc or :"
"desc). The valid sort keys are: ['type', 'name', 'created_at', 'updated_at']"
msgstr ""
"排序选项是一个字符串,该字符串包含了一系列用逗号分隔的键。每个键可以附加上一"
"个排序方向值(:asc或:desc这个排序方向值是可选的。有效的过滤键是"
"['type', 'name', 'created_at', 'updated_at']"
msgid ""
"Sorting option which is a string containing a list of keys separated by "
"commas. Each key can be optionally appended by a sort direction (:asc or :"
"desc). The valid sort_keys are:['type', 'name', 'created_at', 'updated_at']"
msgstr ""
"排序选项是一个字符串,该字符串包含了一系列用逗号分隔的键。每个键可以附加上一"
"个排序方向值(:asc或:desc这个排序方向值是可选的。有效的过滤键是['type', "
"'name', 'created_at', 'updated_at']"
msgid "The desired number of nodes of the cluster"
msgstr "该集群中期望的节点个数"
msgid "The new name for the profile"
msgstr "该样版的新名字"
msgid "The spec file used to create the policy"
msgstr "用于创建策略的规格文件"
msgid "The spec file used to create the profile"
msgstr "用于创建样版的规格文件"
#, python-format
msgid "The specified file is not a valid YAML file: %s"
msgstr "所提供的文件不是合法的YAML文件%s"
#, python-format
msgid "Unknown exception: %s"
msgstr "未知异常:%s"
msgid ""
"Whether events from all projects should be listed. Default to False. "
"Setting this to True may demand for an admin privilege"
msgstr ""
"是否所有项目的事件都应该列出来默认是False。设置该属性为True可能需要admin权"
"限"
msgid "Whether the policy should be enabled"
msgstr "该策略是否会使其生效"
msgid "Whether the policy should be enabled once attached. Default to True"
msgstr "策略一旦关联是否应该设置为有效默认是True"

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.
"""OpenStackClient plugin for Clustering service."""
import logging
from openstack.config import cloud_region
from openstack.config import defaults as config_defaults
from openstack import connection
from osc_lib import utils
LOG = logging.getLogger(__name__)
DEFAULT_CLUSTERING_API_VERSION = '1'
API_VERSION_OPTION = 'os_clustering_api_version'
API_NAME = 'clustering'
CURRENT_API_VERSION = '1.14'
def _make_key(service_type, key):
if not service_type:
return key
else:
service_type = service_type.lower().replace('-', '_')
return "_".join([service_type, key])
def _get_config_from_profile(profile, **kwargs):
# Deal with clients still trying to use legacy profile objects
region_name = None
for service in profile.get_services():
if service.region:
region_name = service.region
service_type = service.service_type
if service.interface:
key = _make_key(service_type, 'interface')
kwargs[key] = service.interface
if service.version:
version = service.version
if version.startswith('v'):
version = version[1:]
key = _make_key(service_type, 'api_version')
kwargs[key] = version
if service.api_version:
version = service.api_version
key = _make_key(service_type, 'default_microversion')
kwargs[key] = version
config_kwargs = config_defaults.get_defaults()
config_kwargs.update(kwargs)
config = cloud_region.CloudRegion(
region_name=region_name, config=config_kwargs)
return config
def create_connection(prof=None, cloud_region=None, **kwargs):
version_key = _make_key(API_NAME, 'api_version')
kwargs[version_key] = CURRENT_API_VERSION
if not cloud_region:
if prof:
cloud_region = _get_config_from_profile(prof, **kwargs)
else:
# If we got the CloudRegion from python-openstackclient and it doesn't
# already have a default microversion set, set it here.
microversion_key = _make_key(API_NAME, 'default_microversion')
cloud_region.config.setdefault(microversion_key, CURRENT_API_VERSION)
user_agent = kwargs.pop('user_agent', None)
app_name = kwargs.pop('app_name', None)
app_version = kwargs.pop('app_version', None)
if user_agent is not None and (not app_name and not app_version):
app_name, app_version = user_agent.split('/', 1)
return connection.Connection(
config=cloud_region,
app_name=app_name,
app_version=app_version, **kwargs)
def make_client(instance):
"""Returns a clustering proxy"""
# TODO(mordred) the ClientManager already has an OpenStackSDK connection,
# but it only has it once setup_auth has been called. For things that
# don't require auth, this is problematic, so we have to make our own.
# Use the CloudRegion stored on the ClientManager for now.
conn = create_connection(
cloud_region=instance._cli_options,
)
LOG.debug('Connection: %s', conn)
LOG.debug('Clustering client initialized using OpenStackSDK: %s',
conn.clustering)
return conn.clustering
def build_option_parser(parser):
"""Hook to add global options"""
parser.add_argument(
'--os-clustering-api-version',
metavar='<clustering-api-version>',
default=utils.env(
'OS_CLUSTERING_API_VERSION',
default=DEFAULT_CLUSTERING_API_VERSION),
help='Clustering API version, default=' +
DEFAULT_CLUSTERING_API_VERSION +
' (Env: OS_CLUSTERING_API_VERSION)')
return parser

View File

@ -1,176 +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 os
import time
from oslo_utils import uuidutils
from tempest.lib.cli import base
from tempest.lib.cli import output_parser
from tempest.lib import exceptions as tempest_lib_exc
class OpenStackClientTestBase(base.ClientTestBase):
"""Command line client base functions."""
def setUp(self):
super(OpenStackClientTestBase, self).setUp()
self.parser = output_parser
def _get_clients(self):
cli_dir = os.environ.get(
'OS_SENLINCLIENT_EXEC_DIR',
os.path.join(os.path.abspath('.'), '.tox/functional/bin'))
return base.CLIClient(
username=os.environ.get('OS_USERNAME'),
password=os.environ.get('OS_PASSWORD'),
tenant_name=os.environ.get('OS_PROJECT_NAME',
os.environ.get('OS_TENANT_NAME')),
uri=os.environ.get('OS_AUTH_URL'),
cli_dir=cli_dir)
def openstack(self, *args, **kwargs):
return self.clients.openstack(*args, **kwargs)
def show_to_dict(self, output):
obj = {}
items = self.parser.listing(output)
for item in items:
obj[item['Field']] = str(item['Value'])
return dict((self._key_name(k), v) for k, v in obj.items())
def _key_name(self, key):
return key.lower().replace(' ', '_')
def name_generate(self):
"""Generate randomized name for some entity."""
name = uuidutils.generate_uuid()[:8]
return name
def _get_profile_path(self, profile_name):
return os.path.join(os.path.dirname(os.path.realpath(__file__)),
'profiles/%s' % profile_name)
def _get_policy_path(self, policy_name):
return os.path.join(os.path.dirname(os.path.realpath(__file__)),
'policies/%s' % policy_name)
def wait_for_status(self, name, status, check_type, timeout=60,
poll_interval=5):
"""Wait until name reaches given status.
:param name: node or cluster name
:param status: expected status of node or cluster
:param timeout: timeout in seconds
:param poll_interval: poll interval in seconds
"""
if check_type == 'node':
cmd = ('cluster node show %s' % name)
elif check_type == 'cluster':
cmd = ('cluster show %s' % name)
time.sleep(poll_interval)
start_time = time.time()
while time.time() - start_time < timeout:
check_status = self.openstack(cmd)
result = self.show_to_dict(check_status)
if result['status'] == status:
break
time.sleep(poll_interval)
else:
message = ("%s %s did not reach status %s after %d s"
% (check_type, name, status, timeout))
raise tempest_lib_exc.TimeoutException(message)
def wait_for_delete(self, name, check_type, timeout=60,
poll_interval=5):
"""Wait until delete finish"""
if check_type == 'node':
cmd = ('cluster node show %s' % name)
if check_type == 'cluster':
cmd = ('cluster show %s' % name)
time.sleep(poll_interval)
start_time = time.time()
while time.time() - start_time < timeout:
try:
self.openstack(cmd)
except tempest_lib_exc.CommandFailed as ex:
if "No Node found" or "No Cluster found" in ex.stderr:
break
time.sleep(poll_interval)
else:
message = ("failed in deleting %s %s after %d seconds"
% (check_type, name, timeout))
raise tempest_lib_exc.TimeoutException(message)
def policy_create(self, name, policy='deletion_policy.yaml'):
pf = self._get_policy_path(policy)
cmd = ('cluster policy create --spec-file %s %s'
% (pf, name))
policy_raw = self.openstack(cmd)
result = self.show_to_dict(policy_raw)
return result
def policy_delete(self, name_or_id):
cmd = ('cluster policy delete %s --force' % name_or_id)
self.openstack(cmd)
def profile_create(self, name, profile='cirros_basic.yaml'):
pf = self._get_profile_path(profile)
cmd = ('cluster profile create --spec-file %s %s'
% (pf, name))
profile_raw = self.openstack(cmd)
result = self.show_to_dict(profile_raw)
return result
def profile_delete(self, name_or_id):
cmd = ('cluster profile delete %s --force' % name_or_id)
self.openstack(cmd)
def node_create(self, profile, name):
cmd = ('cluster node create --profile %s %s'
% (profile, name))
node_raw = self.openstack(cmd)
result = self.show_to_dict(node_raw)
self.wait_for_status(name, 'ACTIVE', 'node', 120)
return result
def node_delete(self, name_or_id):
cmd = ('cluster node delete %s --force' % name_or_id)
self.openstack(cmd)
self.wait_for_delete(name_or_id, 'node', 120)
def cluster_create(self, profile, name, desired_capacity=0):
cmd = ('cluster create --profile %s --desired-capacity %d %s'
% (profile, desired_capacity, name))
cluster_raw = self.openstack(cmd)
result = self.show_to_dict(cluster_raw)
self.wait_for_status(name, 'ACTIVE', 'cluster', 120)
return result
def cluster_delete(self, name_or_id):
cmd = ('cluster delete %s --force' % name_or_id)
self.openstack(cmd)
self.wait_for_delete(name_or_id, 'cluster', 120)
def receiver_create(self, name, cluster, action='CLUSTER_SCALE_OUT',
rt='webhook'):
cmd = ('cluster receiver create --cluster %s --action %s --type %s '
'%s' % (cluster, action, rt, name))
receiver_raw = self.openstack(cmd)
result = self.show_to_dict(receiver_raw)
return result
def receiver_delete(self, name_or_id):
cmd = ('cluster receiver delete %s --force' % name_or_id)
self.openstack(cmd)

View File

@ -1,19 +0,0 @@
# Sample deletion policy that can be attached to a cluster.
type: senlin.policy.deletion
version: 1.0
description: A policy for choosing victim node(s) from a cluster for deletion.
properties:
# The valid values include:
# OLDEST_FIRST, OLDEST_PROFILE_FIRST, YOUNGEST_FIRST, RANDOM
criteria: OLDEST_FIRST
# Whether deleted node should be destroyed
destroy_after_deletion: True
# Length in number of seconds before the actual deletion happens
# This param buys an instance some time before deletion
grace_period: 60
# Whether the deletion will reduce the desired capacity of
# the cluster as well
reduce_desired_capacity: False

View File

@ -1,12 +0,0 @@
type: os.nova.server
version: 1.0
properties:
flavor: 1
image: "cirros-0.4.0-x86_64-disk"
networks:
- network: private
metadata:
test_key: test_value
user_data: |
#!/bin/sh
echo 'hello, world' > /tmp/test_file

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 senlinclient.tests.functional import base
class ActionTest(base.OpenStackClientTestBase):
"""Test for actions"""
def test_action_list(self):
result = self.openstack('cluster action list')
action_list = self.parser.listing(result)
self.assertTableStruct(action_list,
['id', 'name', 'action', 'status',
'target_id', 'depends_on', 'cluster_id',
'depended_by', 'created_at'])

View File

@ -1,109 +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 senlinclient.tests.functional import base
class ClusterPolicyTest(base.OpenStackClientTestBase):
"""Test cluster for policy"""
def test_cluster_policy_attach_and_detach(self):
name = self.name_generate()
po = self.policy_create(name)
self.addCleanup(self.policy_delete, po['id'])
pf = self.profile_create(name)
self.addCleanup(self.profile_delete, pf['id'])
cluster = self.cluster_create(pf['id'], name)
self.addCleanup(self.cluster_delete, cluster['id'])
cp_raw = self.openstack('cluster policy binding list %s'
% cluster['id'])
cp_data = self.show_to_dict(cp_raw)
self.assertEqual({}, cp_data)
# Attach policy to cluster
cmd = ('cluster policy attach --policy %s %s' % (po['id'],
cluster['id']))
self.openstack(cmd)
self.wait_for_status(cluster['id'], 'ACTIVE', 'cluster', 120)
cmd = ('cluster policy binding show --policy %s %s' %
(po['id'], cluster['id']))
policy_raw = self.openstack(cmd)
policy_data = self.show_to_dict(policy_raw)
self.assertEqual(po['name'], policy_data['policy_name'])
self.assertEqual(cluster['name'], policy_data['cluster_name'])
self.assertTrue(policy_data['is_enabled'])
# Detach policy from cluster
cmd = ('cluster policy detach --policy %s %s' % (po['id'],
cluster['id']))
self.openstack(cmd)
self.wait_for_status(cluster['id'], 'ACTIVE', 'cluster', 120)
cp_raw = self.openstack('cluster policy binding list %s'
% cluster['id'])
cp_data = self.show_to_dict(cp_raw)
self.assertEqual({}, cp_data)
def test_cluster_policy_list(self):
name = self.name_generate()
po = self.policy_create(name)
self.addCleanup(self.policy_delete, po['id'])
pf = self.profile_create(name)
self.addCleanup(self.profile_delete, pf['id'])
cluster = self.cluster_create(pf['id'], name)
self.addCleanup(self.cluster_delete, cluster['id'])
cmd = ('cluster policy attach --policy %s %s' % (po['id'],
cluster['id']))
self.openstack(cmd)
self.wait_for_status(cluster['id'], 'ACTIVE', 'cluster', 120)
# List cluster policy binding
cmd = ('cluster policy binding list --filters policy_name=%s %s'
% (po['name'], cluster['id']))
result = self.openstack(cmd)
binding_list = self.parser.listing(result)
self.assertTableStruct(binding_list, ['policy_id', 'policy_name',
'policy_type', 'is_enabled'])
cmd = ('cluster policy detach --policy %s %s' % (po['id'],
cluster['id']))
self.openstack(cmd)
self.wait_for_status(cluster['id'], 'ACTIVE', 'cluster', 120)
def test_cluster_policy_update(self):
name = self.name_generate()
po = self.policy_create(name)
self.addCleanup(self.policy_delete, po['id'])
pf = self.profile_create(name)
self.addCleanup(self.profile_delete, pf['id'])
cluster = self.cluster_create(pf['id'], name)
self.addCleanup(self.cluster_delete, cluster['id'])
cmd = ('cluster policy attach --policy %s %s' % (po['id'],
cluster['id']))
self.openstack(cmd)
self.wait_for_status(cluster['id'], 'ACTIVE', 'cluster', 120)
# Update cluster policy binding
cmd = ('cluster policy binding update --policy %s --enabled false %s'
% (po['id'], cluster['id']))
self.openstack(cmd)
self.wait_for_status(cluster['id'], 'ACTIVE', 'cluster', 120)
cp_update = self.openstack('cluster policy binding show --policy %s %s'
% (po['id'], cluster['id']))
cp_update_data = self.show_to_dict(cp_update)
self.assertFalse(cp_update_data['is_enabled'].isupper())
cmd = ('cluster policy detach --policy %s %s' % (po['id'],
cluster['id']))
self.openstack(cmd)
self.wait_for_status(cluster['id'], 'ACTIVE', 'cluster', 120)

View File

@ -1,279 +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 senlinclient.tests.functional import base
class ClusterTest(base.OpenStackClientTestBase):
"""Test for clusters"""
def test_cluster_list(self):
result = self.openstack('cluster list')
cluster_list = self.parser.listing(result)
self.assertTableStruct(cluster_list, ['id', 'name', 'status',
'created_at', 'updated_at'])
def test_cluster_list_filters(self):
params = {'name': 'test1', 'status': 'ACTIVE'}
for k, v in params.items():
cmd = ('cluster list --filters %s=%s' % (k, v))
self.openstack(cmd)
def test_cluster_list_sort(self):
params = ['name', 'status', 'init_at', 'created_at', 'updated_at']
for param in params:
cmd = 'cluster list --sort %s' % param
self.openstack(cmd)
def test_cluster_full_id(self):
self.openstack('cluster list --full-id')
def test_cluster_limit(self):
self.openstack('cluster list --limit 1')
def test_cluster_create(self):
name = self.name_generate()
pf = self.profile_create(name)
self.addCleanup(self.profile_delete, pf['id'])
cluster = self.cluster_create(pf['id'], name, 1)
self.addCleanup(self.cluster_delete, cluster['id'])
cluster_raw = self.openstack('cluster show %s' % name)
cluster_data = self.show_to_dict(cluster_raw)
self.assertEqual(cluster_data['name'], name)
self.assertEqual(cluster_data['status'], 'ACTIVE')
self.assertEqual(cluster_data['desired_capacity'], '1')
def test_cluster_update(self):
old_name = self.name_generate()
old_pf = self.profile_create(old_name)
self.addCleanup(self.profile_delete, old_pf['id'])
new_name = self.name_generate()
new_pf = self.profile_create(new_name)
self.addCleanup(self.profile_delete, new_pf['id'])
cluster = self.cluster_create(old_pf['id'], old_name, 1)
self.addCleanup(self.cluster_delete, cluster['id'])
self.assertEqual(cluster['name'], old_name)
# cluster update
cmd = ('cluster update --name %s --profile %s --timeout 300 %s'
% (old_name, new_pf['id'], cluster['id']))
self.openstack(cmd)
self.wait_for_status(cluster['id'], 'ACTIVE', 'cluster', 120)
cluster_raw = self.openstack('cluster show %s' % cluster['id'])
cluster_data = self.show_to_dict(cluster_raw)
node_raw = self.openstack('cluster node show %s' %
cluster_data['node_ids'])
node_data = self.show_to_dict(node_raw)
# if not profile-only, change all profile
self.assertEqual(cluster['name'], cluster_data['name'])
self.assertEqual(cluster_data['profile_id'], new_pf['id'])
self.assertEqual(cluster_data['timeout'], '300')
self.assertEqual(new_pf['name'], node_data['profile_name'])
def test_cluster_update_profile_only(self):
old_name = self.name_generate()
old_pf = self.profile_create(old_name)
self.addCleanup(self.profile_delete, old_pf['id'])
new_name = self.name_generate()
new_pf = self.profile_create(new_name)
self.addCleanup(self.profile_delete, new_pf['id'])
cluster = self.cluster_create(old_pf['id'], old_name, 1)
self.addCleanup(self.cluster_delete, cluster['id'])
self.assertEqual(cluster['name'], old_name)
cmd = ('cluster update --name %s --profile %s --profile-only true'
' --timeout 300 %s' % (new_name, new_pf['id'], cluster['id']))
self.openstack(cmd)
self.wait_for_status(cluster['id'], 'ACTIVE', 'cluster', 120)
cluster_raw = self.openstack('cluster show %s' % cluster['id'])
cluster_data = self.show_to_dict(cluster_raw)
node_raw = self.openstack('cluster node show %s' %
cluster_data['node_ids'])
node_data = self.show_to_dict(node_raw)
# if profile-only true, not change exist node profile
self.assertNotEqual(cluster['name'], cluster_data['name'])
self.assertNotEqual(cluster_data['profile_id'], cluster['profile_id'])
self.assertEqual(cluster_data['profile_id'], new_pf['id'])
self.assertEqual(cluster_data['timeout'], '300')
self.assertNotEqual(new_name, node_data['profile_name'])
def test_cluster_show(self):
name = self.name_generate()
pf = self.profile_create(name)
self.addCleanup(self.profile_delete, pf['id'])
cluster = self.cluster_create(pf['id'], name)
self.addCleanup(self.cluster_delete, cluster['id'])
cluster_raw = self.openstack('cluster show %s' % name)
cluster_data = self.show_to_dict(cluster_raw)
self.assertIn('node_ids', cluster_data)
self.assertIn('timeout', cluster_data)
def test_cluster_expand_and_shrink(self):
name = self.name_generate()
pf = self.profile_create(name)
self.addCleanup(self.profile_delete, pf['id'])
cluster = self.cluster_create(pf['id'], name)
self.addCleanup(self.cluster_delete, cluster['id'])
cluster_raw = self.openstack('cluster show %s' % name)
cluster_data = self.show_to_dict(cluster_raw)
# cluster expand
self.openstack('cluster expand --count 1 %s' % name)
self.wait_for_status(cluster['id'], 'ACTIVE', 'cluster', 120)
expand_raw = self.openstack('cluster show %s' % name)
expand_data = self.show_to_dict(expand_raw)
self.assertNotEqual(cluster_data['desired_capacity'],
expand_data['desired_capacity'])
self.assertEqual(expand_data['desired_capacity'], '1')
# cluster shrink
self.openstack('cluster shrink --count 1 %s' % name)
self.wait_for_status(cluster['id'], 'ACTIVE', 'cluster', 120)
shrink_raw = self.openstack('cluster show %s' % name)
shrink_data = self.show_to_dict(shrink_raw)
self.assertNotEqual(shrink_data['desired_capacity'],
expand_data['desired_capacity'])
self.assertEqual(cluster_data['desired_capacity'],
shrink_data['desired_capacity'])
# NOTE(chenyb4): Since functional tests only focus on the client/server
# interaction without invovling other OpenStack services, it is not
# possible to mock a cluster failure and then test if the check logic
# works. Such tests would be left to integration tests instead.
def test_cluster_check(self):
name = self.name_generate()
pf = self.profile_create(name)
self.addCleanup(self.profile_delete, pf['id'])
cluster = self.cluster_create(pf['id'], name, 1)
self.addCleanup(self.cluster_delete, cluster['id'])
self.openstack('cluster check %s' % cluster['id'])
self.wait_for_status(cluster['id'], 'ACTIVE', 'cluster', 120)
check_raw = self.openstack('cluster show %s' % name)
check_data = self.show_to_dict(check_raw)
self.assertIn('CLUSTER_CHECK', check_data['status_reason'])
cluster_status = ['ACTIVE', 'WARNING']
self.assertIn(check_data['status'], cluster_status)
# NOTE(chenyb4): A end-to-end test of the cluster recover operation needs
# to be done with other OpenStack services involved, thus out of scope
# for functional tests. Such tests would be left to integration tests
# instead.
def test_cluster_recover(self):
name = self.name_generate()
pf = self.profile_create(name)
self.addCleanup(self.profile_delete, pf['id'])
cluster = self.cluster_create(pf['id'], name, 1)
self.addCleanup(self.cluster_delete, cluster['id'])
cmd = ('cluster recover --check true %s' % cluster['id'])
self.openstack(cmd)
self.wait_for_status(cluster['id'], 'ACTIVE', 'cluster', 120)
recover_raw = self.openstack('cluster show %s' % name)
recover_data = self.show_to_dict(recover_raw)
self.assertIn('CLUSTER_RECOVER', recover_data['status_reason'])
self.assertEqual('ACTIVE', recover_data['status'])
def test_cluster_resize(self):
name = self.name_generate()
pf = self.profile_create(name)
self.addCleanup(self.profile_delete, pf['id'])
cluster = self.cluster_create(pf['id'], name)
self.addCleanup(self.cluster_delete, cluster['id'])
cluster_raw = self.openstack('cluster show %s' % name)
cluster_data = self.show_to_dict(cluster_raw)
self.assertEqual(cluster_data['desired_capacity'], '0')
self.assertEqual(cluster_data['max_size'], '-1')
self.assertEqual(cluster_data['min_size'], '0')
cmd = ('cluster resize --max-size 5 --min-size 1 --adjustment 2 %s'
% cluster['id'])
self.openstack(cmd)
self.wait_for_status(cluster['id'], 'ACTIVE', 'cluster', 120)
resize_raw = self.openstack('cluster show %s' % name)
resize_data = self.show_to_dict(resize_raw)
self.assertEqual(resize_data['desired_capacity'], '2')
self.assertEqual(resize_data['max_size'], '5')
self.assertEqual(resize_data['min_size'], '1')
def test_cluster_members_list(self):
name = self.name_generate()
pf = self.profile_create(name)
self.addCleanup(self.profile_delete, pf['id'])
cluster = self.cluster_create(pf['id'], name)
self.addCleanup(self.cluster_delete, cluster['id'])
result = self.openstack('cluster members list --full-id %s'
% cluster['id'])
members_list = self.parser.listing(result)
self.assertTableStruct(members_list, ['id', 'name', 'index',
'status', 'physical_id',
'created_at'])
def test_cluster_members_add_and_del(self):
name = self.name_generate()
pf = self.profile_create(name)
self.addCleanup(self.profile_delete, pf['id'])
cluster = self.cluster_create(pf['id'], name)
self.addCleanup(self.cluster_delete, cluster['name'])
node = self.node_create(pf['id'], name)
self.addCleanup(self.node_delete, node['id'])
cluster_raw = self.openstack('cluster show %s' % name)
cluster_data = self.show_to_dict(cluster_raw)
self.assertEqual('', cluster_data['node_ids'])
# Add exist node to cluster
cmd = ('cluster members add --nodes %s %s' % (node['name'],
cluster['id']))
self.openstack(cmd)
self.wait_for_status(cluster['id'], 'ACTIVE', 'cluster', 120)
mem_ad_raw = self.openstack('cluster show %s' % name)
mem_ad_data = self.show_to_dict(mem_ad_raw)
self.assertNotEqual('', mem_ad_data['node_ids'])
self.assertIn(node['id'], mem_ad_data['node_ids'])
# Delete node from cluster
cmd = ('cluster members del --nodes %s %s' % (node['name'],
cluster['id']))
self.openstack(cmd)
self.wait_for_status(cluster['id'], 'ACTIVE', 'cluster', 120)
mem_del_raw = self.openstack('cluster show %s' % name)
mem_del_data = self.show_to_dict(mem_del_raw)
self.assertEqual('', mem_del_data['node_ids'])
self.assertNotIn(node['id'], mem_del_data['node_ids'])
def test_cluster_members_replace(self):
name = self.name_generate()
pf = self.profile_create(name)
self.addCleanup(self.profile_delete, pf['id'])
cluster = self.cluster_create(pf['id'], name, 1)
self.addCleanup(self.cluster_delete, cluster['id'])
cluster_raw = self.openstack('cluster show %s' % name)
cluster_data = self.show_to_dict(cluster_raw)
# Create replace node
new_node = self.node_create(pf['id'], name)
self.addCleanup(self.node_delete, new_node['id'])
self.assertNotIn(new_node['id'], cluster_data['node_ids'])
# Cluster node replace
old_node = cluster_data['node_ids']
self.addCleanup(self.node_delete, old_node)
cmd = ('cluster members replace --nodes %s=%s %s'
% (old_node, new_node['id'], cluster['id']))
self.openstack(cmd, flags='--debug')
self.wait_for_status(cluster['id'], 'ACTIVE', 'cluster', 120)
replace_raw = self.openstack('cluster show %s' % name)
replace_data = self.show_to_dict(replace_raw)
self.assertIn(new_node['id'], replace_data['node_ids'])
self.assertNotIn(old_node, replace_data['node_ids'])

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 senlinclient.tests.functional import base
class EventTest(base.OpenStackClientTestBase):
"""Test for events"""
def test_event_list(self):
result = self.openstack('cluster event list')
event_list = self.parser.listing(result)
self.assertTableStruct(event_list, ['id', 'generated_at', 'obj_type',
'obj_id', 'obj_name', 'action',
'status', 'level', 'cluster_id',
'meta_data'])

View File

@ -1,22 +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 senlinclient.tests.functional import base
class HelpTest(base.OpenStackClientTestBase):
"""Test for help commands"""
def test_help_cmd(self):
help_text = self.openstack('help cluster list')
lines = help_text.split('\n')
self.assertFirstLineStartsWith(lines, 'usage: openstack cluster list')

View File

@ -1,104 +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 senlinclient.tests.functional import base
class NodeTest(base.OpenStackClientTestBase):
"""Test for nodes"""
def test_node_list(self):
result = self.openstack('cluster node list')
node_list = self.parser.listing(result)
self.assertTableStruct(node_list, ['id', 'name', 'index', 'status',
'cluster_id', 'physical_id',
'profile_name', 'created_at',
'updated_at', 'tainted'])
def test_node_create(self):
name = self.name_generate()
pf = self.profile_create(name)
node = self.node_create(pf['id'], name)
self.assertEqual(node['name'], name)
self.node_delete(node['id'])
self.addCleanup(self.profile_delete, pf['id'])
def test_node_update(self):
old_name = self.name_generate()
pf = self.profile_create(old_name)
n1 = self.node_create(pf['id'], old_name)
new_name = self.name_generate()
pf_new = self.profile_create(new_name)
role = 'master'
tainted = 'True'
cmd = ('cluster node update --name %s --role %s --profile %s '
'--tainted %s %s'
% (new_name, role, pf_new['id'], tainted, n1['id']))
self.openstack(cmd)
self.wait_for_status(n1['id'], 'ACTIVE', 'node', 120)
raw_node = self.openstack('cluster node show %s' % n1['id'])
node_data = self.show_to_dict(raw_node)
self.assertEqual(node_data['name'], new_name)
self.assertNotEqual(node_data['name'], old_name)
self.assertEqual(node_data['role'], role)
self.assertEqual(node_data['tainted'], tainted)
self.assertEqual(node_data['profile_id'], pf_new['id'])
self.node_delete(new_name)
self.addCleanup(self.profile_delete, pf['id'])
self.addCleanup(self.profile_delete, pf_new['id'])
def test_node_detail(self):
name = self.name_generate()
pf = self.profile_create(name)
node = self.node_create(pf['id'], name)
cmd = ('cluster node show --details %s' % name)
raw_node = self.openstack(cmd)
node_data = self.show_to_dict(raw_node)
self.assertIn('details', node_data)
self.assertIsNotNone(node_data['details'])
self.node_delete(node['id'])
self.addCleanup(self.profile_delete, pf['id'])
# NOTE(Qiming): Since functional tests only focus on the client/server
# interaction without invovling other OpenStack services, it is not
# possible to mock a node failure and then test if the check logic works.
# Such tests would be left to integration tests instead.
def test_node_check(self):
name = self.name_generate()
pf = self.profile_create(name)
node = self.node_create(pf['id'], name)
cmd = ('cluster node check %s' % node['id'])
self.openstack(cmd)
check_raw = self.openstack('cluster node show %s' % name)
check_data = self.show_to_dict(check_raw)
self.assertIn('Check', check_data['status_reason'])
node_status = ['ACTIVE', 'ERROR']
self.assertIn(check_data['status'], node_status)
self.node_delete(node['id'])
self.addCleanup(self.profile_delete, pf['id'])
# NOTE(Qiming): A end-to-end test of the node recover operation needs to
# be done with other OpenStack services involved, thus out of scope for
# functional tests. Such tests would be left to integration tests instead.
def test_node_recover(self):
name = self.name_generate()
pf = self.profile_create(name)
node = self.node_create(pf['id'], name)
cmd = ('cluster node recover --check true %s' % node['id'])
self.openstack(cmd)
self.wait_for_status(name, 'ACTIVE', 'node', 120)
recover_raw = self.openstack('cluster node show %s' % name)
recover_data = self.show_to_dict(recover_raw)
self.assertIn('Recover', recover_data['status_reason'])
self.assertEqual('ACTIVE', recover_data['status'])
self.node_delete(node['id'])
self.addCleanup(self.profile_delete, pf['id'])

View File

@ -1,40 +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 senlinclient.tests.functional import base
class PolicyTest(base.OpenStackClientTestBase):
"""Test for policies"""
def test_policy_list(self):
result = self.openstack('cluster policy list')
policy_list = self.parser.listing(result)
self.assertTableStruct(policy_list, ['id', 'name', 'type',
'created_at'])
def test_policy_create(self):
name = self.name_generate()
result = self.policy_create(name, 'deletion_policy.yaml')
self.assertEqual(result['name'], name)
self.addCleanup(self.policy_delete, result['id'])
def test_policy_update(self):
old_name = self.name_generate()
pc1 = self.policy_create(old_name, 'deletion_policy.yaml')
new_name = self.name_generate()
cmd = ('cluster policy update --name %s %s' % (new_name, pc1['id']))
result = self.openstack(cmd)
pc2 = self.show_to_dict(result)
self.assertEqual(pc2['name'], new_name)
self.assertNotEqual(pc1['name'], pc2['name'])
self.addCleanup(self.policy_delete, pc2['id'])

View File

@ -1,37 +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 senlinclient.tests.functional import base
class PolicyTypeTest(base.OpenStackClientTestBase):
"""Test for policy types"""
def test_policy_type_list(self):
result = self.openstack('cluster policy type list')
policy_type = self.parser.listing(result)
columns = ['name', 'version']
if any('support_status' in i for i in policy_type):
columns.append('support_status')
self.assertTableStruct(policy_type, columns)
def test_policy_type_show(self):
params = ['senlin.policy.affinity-1.0',
'senlin.policy.batch-1.0',
'senlin.policy.deletion-1.0',
'senlin.policy.health-1.0',
'senlin.policy.loadbalance-1.1',
'senlin.policy.region_placement-1.0',
'senlin.policy.zone_placement-1.0']
for param in params:
cmd = 'cluster policy type show %s' % param
self.openstack(cmd)

View File

@ -1,42 +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 senlinclient.tests.functional import base
class ProfileTypeTest(base.OpenStackClientTestBase):
"""Test for profile types
Basic smoke test for the Openstack CLI commands which do not require
creating or modifying.
"""
def test_profile_type_list(self):
result = self.openstack('cluster profile type list')
profile_type = self.parser.listing(result)
columns = ['name', 'version']
if any('support_status' in i for i in profile_type):
columns.append('support_status')
self.assertTableStruct(profile_type, columns)
def test_profile_list_debug(self):
self.openstack('cluster profile type list', flags='--debug')
def test_profile_type_show(self):
params = ['container.dockerinc.docker-1.0',
'os.heat.stack-1.0',
'os.nova.server-1.0']
for param in params:
cmd = 'cluster profile type show %s' % param
self.openstack(cmd)
def test_profile_type_show_json(self):
self.openstack('cluster profile type show os.nova.server-1.0 -f json')

View File

@ -1,40 +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 senlinclient.tests.functional import base
class ProfileTest(base.OpenStackClientTestBase):
"""Test for profiles"""
def test_profile_list(self):
result = self.openstack('cluster profile list')
profile_list = self.parser.listing(result)
self.assertTableStruct(profile_list, ['id', 'name', 'type',
'created_at'])
def test_pofile_create(self):
name = self.name_generate()
result = self.profile_create(name, 'cirros_basic.yaml')
self.assertEqual(result['name'], name)
self.addCleanup(self.profile_delete, result['id'])
def test_profile_update(self):
old_name = self.name_generate()
pf1 = self.profile_create(old_name, 'cirros_basic.yaml')
new_name = self.name_generate()
cmd = ('cluster profile update --name %s %s' % (new_name, pf1['id']))
result = self.openstack(cmd)
pf2 = self.show_to_dict(result)
self.assertEqual(pf2['name'], new_name)
self.assertNotEqual(pf1['name'], pf2['name'])
self.addCleanup(self.profile_delete, pf2['id'])

View File

@ -1,24 +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 tempest.lib import exceptions
from senlinclient.tests.functional import base
class FakeTest(base.OpenStackClientTestBase):
"""Test if fake actions can be detected"""
def test_fake_action(self):
self.assertRaises(exceptions.CommandFailed,
self.openstack,
'this-does-not-exist')

View File

@ -1,57 +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 senlinclient.tests.functional import base
class ReceiverTest(base.OpenStackClientTestBase):
"""Test for receivers"""
def test_receiver_list(self):
result = self.openstack('cluster receiver list')
receiver_list = self.parser.listing(result)
self.assertTableStruct(receiver_list, ['id', 'name', 'type',
'cluster_id', 'action',
'created_at'])
def test_receiver_create(self):
name = self.name_generate()
pf = self.profile_create(name)
self.addCleanup(self.profile_delete, pf['id'])
cluster = self.cluster_create(pf['id'], name)
self.addCleanup(self.cluster_delete, cluster['id'])
receiver = self.receiver_create(name, cluster['id'])
self.addCleanup(self.receiver_delete, receiver['id'])
self.assertEqual(receiver['name'], name)
self.assertEqual(receiver['type'], 'webhook')
self.assertEqual(receiver['action'], 'CLUSTER_SCALE_OUT')
def test_receiver_update(self):
old_name = self.name_generate()
pf = self.profile_create(old_name)
self.addCleanup(self.profile_delete, pf['id'])
cluster = self.cluster_create(pf['id'], old_name)
self.addCleanup(self.cluster_delete, cluster['id'])
receiver = self.receiver_create(old_name, cluster['id'])
self.addCleanup(self.receiver_delete, receiver['id'])
new_name = self.name_generate()
cmd = ('cluster receiver update --name %s --params count=2 '
'--action CLUSTER_SCALE_IN %s' % (new_name, receiver['id']))
self.openstack(cmd)
receiver_raw = self.openstack('cluster receiver show %s'
% receiver['id'])
receiver_data = self.show_to_dict(receiver_raw)
self.assertNotEqual(receiver['name'], receiver_data['name'])
self.assertEqual(receiver_data['name'], new_name)
self.assertNotEqual(receiver['action'], receiver_data['action'])
self.assertEqual(receiver_data['action'], 'CLUSTER_SCALE_IN')

View File

@ -1,20 +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 senlinclient.tests.functional import base
class VersionTest(base.OpenStackClientTestBase):
"""Test for versions"""
def test_openstack_version(self):
self.openstack('', flags='--version')

View File

@ -1,8 +0,0 @@
type: senlin.policy.deletion
version: 1.0
description: A policy for choosing victim node(s) from a cluster for deletion.
properties:
criteria: OLDEST_FIRST
destroy_after_deletion: True
grace_period: 60
reduce_desired_capacity: False

View File

@ -1,6 +0,0 @@
type: os.nova.server
version: 1.0
properties:
name: cirros_server
flavor: 1
image: cirros-0.3.4-x86_64-uec

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 senlinclient.common import utils
def do_command_foo(sc, args):
"""Pydoc for command foo."""
return
@utils.arg('-F', '--flag', metavar='<FLAG>', help='Flag desc.')
@utils.arg('arg1', metavar='<ARG1>', help='Arg1 desc')
def do_command_bar(sc, args):
"""This is the command doc."""
return

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 yaml
from oslo_serialization import jsonutils
from osc_lib.tests import utils
from senlinclient.common import format_utils
columns = ['col1', 'col2', 'col3']
data = ['abcde', ['fg', 'hi', 'jk'], {'lmnop': 'qrstu'}]
class ShowJson(format_utils.JsonFormat):
def take_action(self, parsed_args):
return columns, data
class ShowYaml(format_utils.YamlFormat):
def take_action(self, parsed_args):
return columns, data
class ShowShell(format_utils.ShellFormat):
def take_action(self, parsed_args):
return columns, data
class ShowValue(format_utils.ValueFormat):
def take_action(self, parsed_args):
return columns, data
class TestFormats(utils.TestCommand):
def test_json_format(self):
self.cmd = ShowJson(self.app, None)
parsed_args = self.check_parser(self.cmd, [], [])
expected = jsonutils.dumps(dict(zip(columns, data)), indent=2)
self.cmd.run(parsed_args)
self.assertEqual(jsonutils.loads(expected),
jsonutils.loads(self.app.stdout.make_string()))
def test_yaml_format(self):
self.cmd = ShowYaml(self.app, None)
parsed_args = self.check_parser(self.cmd, [], [])
expected = yaml.safe_dump(dict(zip(columns, data)),
default_flow_style=False)
self.cmd.run(parsed_args)
self.assertEqual(expected, self.app.stdout.make_string())
def test_shell_format(self):
self.cmd = ShowShell(self.app, None)
parsed_args = self.check_parser(self.cmd, [], [])
expected = '''\
col1="abcde"
col2="['fg', 'hi', 'jk']"
col3="{'lmnop': 'qrstu'}"
'''
self.cmd.run(parsed_args)
self.assertEqual(expected, self.app.stdout.make_string())
def test_value_format(self):
self.cmd = ShowValue(self.app, None)
parsed_args = self.check_parser(self.cmd, [], [])
expected = '''\
abcde
['fg', 'hi', 'jk']
{'lmnop': 'qrstu'}
'''
self.cmd.run(parsed_args)
self.assertEqual(expected, self.app.stdout.make_string())

View File

@ -1,72 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from openstack import connection as sdk_connection
import testtools
from unittest import mock
from senlinclient import plugin
class TestPlugin(testtools.TestCase):
@mock.patch.object(sdk_connection, 'Connection')
def test_create_connection_with_profile(self, mock_connection):
class FakeService(object):
interface = 'public'
region = 'a_region'
version = '1'
api_version = None
service_type = 'clustering'
mock_prof = mock.Mock()
mock_prof.get_services.return_value = [FakeService()]
mock_conn = mock.Mock()
mock_connection.return_value = mock_conn
kwargs = {
'user_id': '123',
'password': 'abc',
'auth_url': 'test_url'
}
res = plugin.create_connection(mock_prof, **kwargs)
mock_connection.assert_called_once_with(
app_name=None, app_version=None,
config=mock.ANY,
clustering_api_version=plugin.CURRENT_API_VERSION,
**kwargs
)
self.assertEqual(mock_conn, res)
@mock.patch.object(sdk_connection, 'Connection')
def test_create_connection_without_profile(self, mock_connection):
mock_conn = mock.Mock()
mock_connection.return_value = mock_conn
kwargs = {
'interface': 'public',
'region_name': 'RegionOne',
'user_id': '123',
'password': 'abc',
'auth_url': 'test_url'
}
res = plugin.create_connection(**kwargs)
mock_connection.assert_called_once_with(
app_name=None, app_version=None,
auth_url='test_url',
clustering_api_version=plugin.CURRENT_API_VERSION,
config=None,
interface='public',
password='abc',
region_name='RegionOne',
user_id='123'
)
self.assertEqual(mock_conn, res)

View File

@ -1,147 +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 heatclient.common import template_utils
from oslo_utils import importutils
from unittest import mock
import testtools
import time
from senlinclient.common import exc
from senlinclient.common.i18n import _
from senlinclient.common import utils
class UtilTest(testtools.TestCase):
def test_format_parameter(self):
params = ['status=ACTIVE;name=cluster1']
format_params = {'status': 'ACTIVE', 'name': 'cluster1'}
self.assertEqual(format_params,
utils.format_parameters(params))
def test_import_versioned_module(self):
module = 'senlinclient'
version = 'v1'
submodule = '__init__'
module_name = '.'.join((module, version, submodule))
self.assertIsNone(utils.import_versioned_module(version[-1]))
self.assertEqual(utils.import_versioned_module(version[-1], submodule),
importutils.import_module(module_name))
def test_format_parameter_split(self):
params = ['status=ACTIVE', 'name=cluster1']
format_params = {'status': 'ACTIVE', 'name': 'cluster1'}
self.assertEqual(format_params,
utils.format_parameters(params))
def test_format_parameter_none_dict(self):
params = ['{}']
self.assertEqual({}, utils.format_parameters(params))
def test_format_parameter_none(self):
self.assertEqual({}, utils.format_parameters(None))
def test_format_parameter_bad_format(self):
params = ['status:ACTIVE;name:cluster1']
ex = self.assertRaises(exc.CommandError,
utils.format_parameters,
params)
msg = _('Malformed parameter(status:ACTIVE). '
'Use the key=value format.')
self.assertEqual(msg, str(ex))
@mock.patch.object(template_utils,
'process_multiple_environments_and_files')
@mock.patch.object(template_utils, 'get_template_contents')
def test_process_stack_spec(self, mock_get_temp, mock_process):
spec = {
'template': 'temp.yaml',
'disable_rollback': True,
'context': {
'region_name': 'RegionOne'
},
}
tpl_files = {'fake_key1': 'fake_value1'}
template = mock.Mock()
mock_get_temp.return_value = tpl_files, template
env_files = {'fake_key2': 'fake_value2'}
env = mock.Mock()
mock_process.return_value = env_files, env
new_spec = utils.process_stack_spec(spec)
stack_spec = {
'disable_rollback': True,
'context': {
'region_name': 'RegionOne',
},
'parameters': {},
'timeout': 60,
'template': template,
'files': {
'fake_key1': 'fake_value1',
'fake_key2': 'fake_value2',
},
'environment': env
}
self.assertEqual(stack_spec, new_spec)
mock_get_temp.assert_called_once_with(template_file='temp.yaml')
mock_process.assert_called_once_with(env_paths=None)
def test_json_formatter_with_empty_json(self):
params = {}
self.assertEqual('{}', utils.json_formatter(params))
def test_list_formatter_with_list(self):
params = ['foo', 'bar']
self.assertEqual('foo\nbar', utils.list_formatter(params))
def test_list_formatter_with_empty_list(self):
params = []
self.assertEqual('', utils.list_formatter(params))
@mock.patch.object(utils, '_check')
def test_await_cluster_action(self, mock_check):
utils.await_action('fake-client', 'test-action-id')
mock_check.assert_called_once()
@mock.patch.object(utils, '_check')
def test_await_cluster_status(self, mock_check):
utils.await_cluster_status('fake-client', 'ACTIVE')
mock_check.assert_called_once()
@mock.patch.object(utils, '_check')
def test_await_cluster_delete(self, mock_check):
utils.await_cluster_delete('fake-client', 'test-cluster-id')
mock_check.assert_called_once()
def test_check(self):
check_func = mock.Mock(return_value=True)
try:
utils._check(check_func)
except Exception:
self.fail("_check() unexpectedly raised an exception")
check_func.assert_called()
@mock.patch.object(time, 'sleep')
def test_check_raises(self, mock_sleep):
mock_check_func = mock.Mock(return_value=False)
poll_count = 2
poll_interval = 1
self.assertRaises(exc.PollingExceededError, utils._check,
mock_check_func, poll_count, poll_interval)
mock_check_func.assert_called()
mock_sleep.assert_called()

View File

@ -1,176 +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 sys
from unittest import mock
from osc_lib.tests import utils
import requests
from oslo_serialization import jsonutils
AUTH_TOKEN = "foobar"
AUTH_URL = "http://0.0.0.0"
USERNAME = "itchy"
PASSWORD = "scratchy"
TEST_RESPONSE_DICT_V3 = {
"token": {
"audit_ids": [
"a"
],
"catalog": [
],
"expires_at": "2034-09-29T18:27:15.978064Z",
"extras": {},
"issued_at": "2014-09-29T17:27:15.978097Z",
"methods": [
"password"
],
"project": {
"domain": {
"id": "default",
"name": "Default"
},
"id": "bbb",
"name": "project"
},
"roles": [
],
"user": {
"domain": {
"id": "default",
"name": "Default"
},
"id": "aaa",
"name": USERNAME
}
}
}
TEST_VERSIONS = {
"versions": {
"values": [
{
"id": "v3.0",
"links": [
{
"href": AUTH_URL,
"rel": "self"
}
],
"media-types": [
{
"base": "application/json",
"type": "application/vnd.openstack.identity-v3+json"
},
{
"base": "application/xml",
"type": "application/vnd.openstack.identity-v3+xml"
}
],
"status": "stable",
"updated": "2013-03-06T00:00:00Z"
}
]
}
}
class FakeStdout(object):
def __init__(self):
self.content = []
def write(self, text):
self.content.append(text)
def make_string(self):
result = ''
for line in self.content:
result = result + line
return result
class FakeApp(object):
def __init__(self, _stdout):
self.stdout = _stdout
self.client_manager = None
self.stdin = sys.stdin
self.stdout = _stdout or sys.stdout
self.stderr = sys.stderr
class FakeClient(object):
def __init__(self, **kwargs):
self.auth_url = kwargs['auth_url']
self.token = kwargs['token']
class FakeClientManager(object):
def __init__(self):
self.compute = None
self.identity = None
self.image = None
self.object_store = None
self.volume = None
self.network = None
self.session = None
self.auth_ref = None
class FakeModule(object):
def __init__(self, name, version):
self.name = name
self.__version__ = version
class FakeResource(object):
def __init__(self, manager, info, loaded=False):
self.manager = manager
self._info = info
self._add_details(info)
self._loaded = loaded
def _add_details(self, info):
for (k, v) in info.items():
setattr(self, k, v)
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)
class FakeResponse(requests.Response):
def __init__(self, headers={}, status_code=200, data=None, encoding=None):
super(FakeResponse, self).__init__()
self.status_code = status_code
self.headers.update(headers)
self._content = jsonutils.dump_as_bytes(data)
class FakeClusteringv1Client(object):
def __init__(self, **kwargs):
self.http_client = mock.Mock()
self.http_client.auth_token = kwargs['token']
self.profiles = FakeResource(None, {})
class TestClusteringv1(utils.TestCommand):
def setUp(self):
super(TestClusteringv1, self).setUp()
self.app.client_manager.clustering = FakeClusteringv1Client(
token=AUTH_TOKEN, auth_url=AUTH_URL
)

View File

@ -1,222 +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
from unittest import mock
from openstack import exceptions as sdk_exc
from osc_lib import exceptions as exc
from senlinclient.tests.unit.v1 import fakes
from senlinclient.v1 import action as osc_action
class TestAction(fakes.TestClusteringv1):
def setUp(self):
super(TestAction, self).setUp()
self.mock_client = self.app.client_manager.clustering
class TestActionList(TestAction):
columns = ['id', 'name', 'action', 'status', 'target_id', 'depends_on',
'depended_by', 'created_at', 'cluster_id']
defaults = {
'global_project': False,
'marker': None,
'limit': None,
'sort': None,
}
def setUp(self):
super(TestActionList, self).setUp()
self.cmd = osc_action.ListAction(self.app, None)
fake_action = mock.Mock(
action="NODE_CREATE",
cluster_id="FAKE_CLUSTER_ID",
cause="RPC Request",
created_at="2015-12-04T04:54:41",
depended_by=[],
depends_on=[],
end_time=1425550000.0,
id="2366d440-c73e-4961-9254-6d1c3af7c167",
inputs={},
interval=-1,
name="node_create_0df0931b",
outputs={},
owner=None,
start_time=1425550000.0,
status="SUCCEEDED",
status_reason="Action completed successfully.",
target_id="0df0931b-e251-4f2e-8719-4ebfda3627ba",
timeout=3600,
updated_at=None
)
fake_action.to_dict = mock.Mock(return_value={})
self.mock_client.actions = mock.Mock(return_value=[fake_action])
def test_action_list_defaults(self):
arglist = []
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, data = self.cmd.take_action(parsed_args)
self.mock_client.actions.assert_called_with(**self.defaults)
self.assertEqual(self.columns, columns)
def test_action_list_full_id(self):
arglist = ['--full-id']
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, data = self.cmd.take_action(parsed_args)
self.mock_client.actions.assert_called_with(**self.defaults)
self.assertEqual(self.columns, columns)
def test_action_list_limit(self):
kwargs = copy.deepcopy(self.defaults)
kwargs['limit'] = '3'
arglist = ['--limit', '3']
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, data = self.cmd.take_action(parsed_args)
self.mock_client.actions.assert_called_with(**kwargs)
self.assertEqual(self.columns, columns)
def test_action_list_sort(self):
kwargs = copy.deepcopy(self.defaults)
kwargs['sort'] = 'name:asc'
arglist = ['--sort', 'name:asc']
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, data = self.cmd.take_action(parsed_args)
self.mock_client.actions.assert_called_with(**kwargs)
self.assertEqual(self.columns, columns)
def test_action_list_sort_invalid_key(self):
kwargs = copy.deepcopy(self.defaults)
kwargs['sort'] = 'bad_key'
arglist = ['--sort', 'bad_key']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.mock_client.actions.side_effect = sdk_exc.HttpException()
self.assertRaises(sdk_exc.HttpException,
self.cmd.take_action, parsed_args)
def test_action_list_sort_invalid_direction(self):
kwargs = copy.deepcopy(self.defaults)
kwargs['sort'] = 'name:bad_direction'
arglist = ['--sort', 'name:bad_direction']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.mock_client.actions.side_effect = sdk_exc.HttpException()
self.assertRaises(sdk_exc.HttpException,
self.cmd.take_action, parsed_args)
def test_action_list_filter(self):
kwargs = copy.deepcopy(self.defaults)
kwargs['name'] = 'my_action'
arglist = ['--filter', 'name=my_action']
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, data = self.cmd.take_action(parsed_args)
self.mock_client.actions.assert_called_with(**kwargs)
self.assertEqual(self.columns, columns)
def test_action_list_marker(self):
kwargs = copy.deepcopy(self.defaults)
kwargs['marker'] = 'a9448bf6'
arglist = ['--marker', 'a9448bf6']
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, data = self.cmd.take_action(parsed_args)
self.mock_client.actions.assert_called_with(**kwargs)
self.assertEqual(self.columns, columns)
class TestActionShow(TestAction):
def setUp(self):
super(TestActionShow, self).setUp()
self.cmd = osc_action.ShowAction(self.app, None)
fake_action = mock.Mock(
action="NODE_CREATE",
cluster_id="FAKE_CLUSTER_ID",
cause="RPC Request",
created_at="2015-12-04T04:54:41",
depended_by=[],
depends_on=[],
end_time=1425550000.0,
id="2366d440-c73e-4961-9254-6d1c3af7c167",
inputs={},
interval=-1,
name="node_create_0df0931b",
outputs={},
owner=None,
start_time=1425550000.0,
status="SUCCEEDED",
status_reason="Action completed successfully.",
target_id="0df0931b-e251-4f2e-8719-4ebfda3627ba",
timeout=3600,
updated_at=None
)
fake_action.to_dict = mock.Mock(return_value={})
self.mock_client.get_action = mock.Mock(return_value=fake_action)
def test_action_show(self):
arglist = ['my_action']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
self.mock_client.get_action.assert_called_with('my_action')
def test_action_show_not_found(self):
arglist = ['my_action']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.mock_client.get_action.side_effect = sdk_exc.ResourceNotFound()
error = self.assertRaises(exc.CommandError, self.cmd.take_action,
parsed_args)
self.assertEqual('Action not found: my_action', str(error))
class TestActionUpdate(TestAction):
def setUp(self):
super(TestActionUpdate, self).setUp()
self.cmd = osc_action.UpdateAction(self.app, None)
fake_action = mock.Mock(
action="NODE_CREATE",
cluster_id="FAKE_CLUSTER_ID",
cause="RPC Request",
created_at="2015-12-04T04:54:41",
depended_by=[],
depends_on=[],
end_time=1425550000.0,
id="2366d440-c73e-4961-9254-6d1c3af7c167",
inputs={},
interval=-1,
name="node_create_0df0931b",
outputs={},
owner=None,
start_time=1425550000.0,
status="INIT",
status_reason="Action completed successfully.",
target_id="0df0931b-e251-4f2e-8719-4ebfda3627ba",
timeout=3600,
updated_at=None
)
fake_action.to_dict = mock.Mock(return_value={})
self.mock_client.get_action = mock.Mock(return_value=fake_action)
self.mock_client.update_action = mock.Mock(return_value=fake_action)
def test_action_update(self):
arglist = ['--status', 'CANCELLED',
'2366d440-c73e-4961-9254-6d1c3af7c167']
parsed_args = self.check_parser(self.cmd, arglist, [])
defaults = {
"status": "CANCELLED"
}
self.cmd.take_action(parsed_args)
self.mock_client.update_action.assert_called_with(
"2366d440-c73e-4961-9254-6d1c3af7c167", **defaults)

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