Retire Packaging Deb project repos

This commit is part of a series to retire the Packaging Deb
project. Step 2 is to remove all content from the project
repos, replacing it with a README notification where to find
ongoing work, and how to recover the repo if needed at some
future point (as in
https://docs.openstack.org/infra/manual/drivers.html#retiring-a-project).

Change-Id: Ic4a7ec1aeaa1e10887fd1ce5808a7dcaac3357d6
This commit is contained in:
Tony Breeds 2017-09-12 15:59:49 -06:00
parent 6462e5183f
commit 13e7c438bb
132 changed files with 14 additions and 25627 deletions

View File

@ -1,8 +0,0 @@
[run]
branch = True
source = heatclient
omit = heatclient/tests/*
[report]
ignore_errors = True
precision = 2

19
.gitignore vendored
View File

@ -1,19 +0,0 @@
.coverage*
.venv
*,cover
cover
*.pyc
AUTHORS
build
dist
ChangeLog
run_tests.err.log
.tox
doc/source/api
doc/build
*.egg
*.eggs
heatclient/versioninfo
*.egg-info
*.log
.testrepository

View File

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

View File

@ -1,4 +0,0 @@
[DEFAULT]
test_command=${PYTHON:-python} -m subunit.run discover -t ./ ${OS_TEST_PATH:-./heatclient/tests/unit} $LISTOPT $IDOPTION
test_id_option=--load-list $IDFILE
test_list_option=--list

View File

@ -1,16 +0,0 @@
If you would like to contribute to the development of OpenStack,
you must follow the steps documented at:
http://docs.openstack.org/infra/manual/developers.html#development-workflow
Once those steps have been completed, changes to OpenStack
should be submitted for review via the Gerrit tool, following
the workflow documented at:
http://docs.openstack.org/infra/manual/developers.html#development-workflow
Pull requests submitted through GitHub will be ignored.
Bugs should be filed on Launchpad, not GitHub:
https://bugs.launchpad.net/python-heatclient

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.

14
README Normal file
View File

@ -0,0 +1,14 @@
This project is no longer maintained.
The contents of this repository are still available in the Git
source code management system. To see the contents of this
repository before it reached its end of life, please check out the
previous commit with "git checkout HEAD^1".
For ongoing work on maintaining OpenStack packages in the Debian
distribution, please see the Debian OpenStack packaging team at
https://wiki.debian.org/OpenStack/.
For any further questions, please email
openstack-dev@lists.openstack.org or join #openstack-dev on
Freenode.

View File

@ -1,48 +0,0 @@
========================
Team and repository tags
========================
.. image:: http://governance.openstack.org/badges/python-heatclient.svg
:target: http://governance.openstack.org/reference/tags/index.html
.. Change things from this point on
=================
python-heatclient
=================
.. image:: https://img.shields.io/pypi/v/python-heatclient.svg
:target: https://pypi.python.org/pypi/python-heatclient/
:alt: Latest Version
.. image:: https://img.shields.io/pypi/dm/python-heatclient.svg
:target: https://pypi.python.org/pypi/python-heatclient/
:alt: Downloads
OpenStack Orchestration API Client Library
This is a client library for Heat built on the Heat orchestration API. It
provides a Python API (the ``heatclient`` module) and a command-line tool
(``heat``).
* Free software: Apache license
* `PyPi`_ - package installation
* `Online Documentation`_
* `Launchpad project`_ - release management
* `Blueprints`_ - feature specifications
* `Bugs`_ - issue tracking
* `Source`_
* `Specs`_
* `Template`_
* `How to Contribute`_
.. _PyPi: https://pypi.python.org/pypi/python-heatclient
.. _Online Documentation: http://docs.openstack.org/developer/python-heatclient
.. _Launchpad project: https://launchpad.net/python-heatclient
.. _Blueprints: https://blueprints.launchpad.net/python-heatclient
.. _Bugs: https://bugs.launchpad.net/python-heatclient
.. _Source: https://git.openstack.org/cgit/openstack/python-heatclient
.. _How to Contribute: http://docs.openstack.org/infra/manual/developers.html
.. _Specs: http://specs.openstack.org/openstack/heat-specs/
.. _Template: https://git.openstack.org/cgit/openstack/heat-templates/

View File

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

2
doc/.gitignore vendored
View File

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

View File

@ -1,90 +0,0 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SPHINXSOURCE = source
PAPER =
BUILDDIR = build
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) $(SPHINXSOURCE)
.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest
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 " 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 " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " changes to make an overview of all changed/added/deprecated items"
@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."
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-heatclient.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/python-heatclient.qhc"
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
"run these through (pdf)latex."
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."

View File

@ -1,269 +0,0 @@
# -*- coding: utf-8 -*-
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# python-heatclient documentation build configuration file, created by
# sphinx-quickstart on Sun Dec 6 14:19:25 2009.
#
# 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.
import os
# 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.append(os.path.abspath('.'))
exec(open(os.path.join("ext", "gen_ref.py")).read())
# -- General configuration ----------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc', 'openstackdocstheme']
# Add any paths that contain templates here, relative to this directory.
if os.getenv('HUDSON_PUBLISH_DOCS'):
templates_path = ['_ga', '_templates']
else:
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
# source_encoding = 'utf-8'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = 'python-heatclient'
copyright = 'OpenStack Contributors'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '2.13'
# The full version, including alpha/beta/rc tags.
release = '2.13.0'
# 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 = 'sphinx'
# A list of ignored prefixes for module index sorting.
# modindex_common_prefix = []
primary_domain = 'py'
nitpicky = 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_path = ['.']
html_theme = 'openstackdocs'
# 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 = {
"nosidebar": "false"
}
# 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']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
html_last_updated_fmt = '%Y-%m-%d %H:%M'
# 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 = 'python-heatclientdoc'
# -- 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])
latex_documents = [
('index', 'python-heatclient.tex', 'python-heatclient Documentation',
u'OpenStack Foundation', '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 = [
('man/heat', 'heat',
u'Command line access to the heat project.',
[u'Heat 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', 'Heat', u'Heat Documentation',
u'Heat Developers', 'Heat', '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'
# -- Options for openstackdocstheme -------------------------------------------
repository_name = 'openstack/python-heatclient'
bug_project = 'python-heatclient'
bug_tag = ''

View File

@ -1,59 +0,0 @@
# -*- coding: utf-8 -*-
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
import sys
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
ROOT = os.path.abspath(os.path.join(BASE_DIR, "..", "..", ".."))
sys.path.insert(0, ROOT)
sys.path.insert(0, BASE_DIR)
def gen_ref(ver, title, names):
refdir = os.path.join(BASE_DIR, "ref")
pkg = "heatclient"
if ver:
pkg = "%s.%s" % (pkg, ver)
refdir = os.path.join(refdir, ver)
if not os.path.exists(refdir):
os.makedirs(refdir)
idxpath = os.path.join(refdir, "index.rst")
with open(idxpath, "w") as idx:
idx.write(("%(title)s\n"
"%(signs)s\n"
"\n"
".. toctree::\n"
" :maxdepth: 1\n"
"\n") % {"title": title, "signs": "=" * len(title)})
for name in names:
idx.write(" %s\n" % name)
rstpath = os.path.join(refdir, "%s.rst" % name)
with open(rstpath, "w") as rst:
rst.write(("%(title)s\n"
"%(signs)s\n"
"\n"
".. automodule:: %(pkg)s.%(name)s\n"
" :members:\n"
" :undoc-members:\n"
" :show-inheritance:\n"
" :noindex:\n")
% {"title": name.capitalize(),
"signs": "=" * len(name),
"pkg": pkg, "name": name})
gen_ref("", "Client Reference", ["client", "exc"])
gen_ref("v1", "Version 1 API Reference",
["stacks", "resources", "events", "actions",
"software_configs", "software_deployments"])

View File

@ -1,98 +0,0 @@
Python bindings to the OpenStack Heat API
=========================================
This is a client for OpenStack Heat API. There's a Python API
(the :mod:`heatclient` module), and a command-line script
(installed as :program:`heat`).
Python API
==========
In order to use the python api directly, you must first obtain an auth
token and identify which endpoint you wish to speak to::
>>> tenant_id = 'b363706f891f48019483f8bd6503c54b'
>>> heat_url = 'http://heat.example.org:8004/v1/%s' % tenant_id
>>> auth_token = '3bcc3d3a03f44e3d8377f9247b0ad155'
Once you have done so, you can use the API like so::
>>> from heatclient.client import Client
>>> heat = Client('1', endpoint=heat_url, token=auth_token)
Alternatively, you can create a client instance using the keystoneauth session API::
>>> from keystoneauth1 import loading
>>> from keystoneauth1 import session
>>> from heatclient import client
>>> loader = loading.get_plugin_loader('password')
>>> auth = loader.load_from_options(auth_url=AUTH_URL,
... username=USERNAME,
... password=PASSWORD,
... project_id=PROJECT_ID)
>>> sess = session.Session(auth=auth)
>>> heat = client.Client('1', session=sess)
>>> heat.stacks.list()
If you have PROJECT_NAME instead of a PROJECT_ID, use the project_name
parameter. Similarly, if your cloud uses keystone v3 and you have a DOMAIN_NAME
or DOMAIN_ID, provide it as `user_domain_(name|id)` and if you are using a
PROJECT_NAME also provide the domain information as `project_domain_(name|id)`.
For more information on keystoneauth API, see `Using Sessions`_.
.. _Using Sessions: http://docs.openstack.org/developer/keystoneauth/using-sessions.html
Reference
---------
.. toctree::
:maxdepth: 1
ref/index
ref/v1/index
Command-line Tool
=================
In order to use the CLI, you must provide your OpenStack username,
password, tenant, and auth endpoint. Use the corresponding
configuration options (``--os-username``, ``--os-password``,
``--os-tenant-id``, and ``--os-auth-url``) or set them in environment
variables::
export OS_USERNAME=user
export OS_PASSWORD=pass
export OS_TENANT_ID=b363706f891f48019483f8bd6503c54b
export OS_AUTH_URL=http://auth.example.com:5000/v2.0
The command line tool will attempt to reauthenticate using your
provided credentials for every request. You can override this behavior
by manually supplying an auth token using ``--heat-url`` and
``--os-auth-token``. You can alternatively set these environment
variables::
export HEAT_URL=http://heat.example.org:8004/v1/b363706f891f48019483f8bd6503c54b
export OS_AUTH_TOKEN=3bcc3d3a03f44e3d8377f9247b0ad155
Once you've configured your authentication parameters, you can run
``heat help`` to see a complete listing of available commands.
Man Pages
=========
.. toctree::
:maxdepth: 1
man/heat
Contributing
============
Code is hosted `on GitHub`_. Submit bugs to the Heat project on
`Launchpad`_. Submit code to the openstack/python-heatclient project
using `Gerrit`_.
.. _on GitHub: https://github.com/openstack/python-heatclient
.. _Launchpad: https://launchpad.net/python-heatclient
.. _Gerrit: http://docs.openstack.org/infra/manual/developers.html#development-workflow

View File

@ -1,98 +0,0 @@
====
heat
====
.. program:: heat
SYNOPSIS
========
`heat` [options] <command> [command-options]
`heat help`
`heat help` <command>
DESCRIPTION
===========
`heat` is a command line client for controlling OpenStack Heat.
Before the `heat` command is issued, ensure the environment contains
the necessary variables so that the CLI can pass user credentials to
the server.
See `Getting Credentials for a CLI` section of `OpenStack CLI Guide`
for more info.
OPTIONS
=======
To get a list of available commands and options run::
heat help
To get usage and options of a command run::
heat help <command>
EXAMPLES
========
Get information about stack-create command::
heat help stack-create
List available stacks::
heat stack-list
List available resources in a stack::
heat resource-list <stack name>
Create a stack::
heat stack-create mystack -f some-template.yaml -P "KeyName=mine"
View stack information::
heat stack-show mystack
List stack outputs::
heat output-list <stack name>
Show the value of a single output::
heat output-show <stack name> <output key>
List events::
heat event-list mystack
Delete a stack::
heat stack-delete mystack
Abandon a stack::
heat stack-abandon mystack
Adopt a stack ::
heat stack-adopt -a <adopt_file> mystack
List heat-engines running status ::
heat service-list
Note: stack-adopt and stack-abandon commands are not available by default.
Please ask your OpenStack operator to enable this feature.
BUGS
====
Heat client is hosted in Launchpad so you can view current bugs at https://bugs.launchpad.net/python-heatclient/.

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-heatclient').version_string()

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.
"""oslo.i18n integration module.
See http://docs.openstack.org/developer/oslo.i18n/usage.html
"""
import oslo_i18n
# NOTE(dhellmann): This reference to o-s-l-o will be replaced by the
# application name when this module is synced into the separate
# repository. It is OK to have more than one translation function
# using the same domain, since there will still only be one message
# catalog.
_translators = oslo_i18n.TranslatorFactory(domain='heatclient')
# The primary translation function using the well-known name "_"
_ = _translators.primary
def get_available_languages():
return oslo_i18n.get_available_languages('heatclient')
def enable_lazy():
return oslo_i18n.enable_lazy()

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 oslo_utils import importutils
def Client(version, *args, **kwargs):
module = importutils.import_versioned_module('heatclient',
version, 'client')
client_class = getattr(module, 'Client')
return client_class(*args, **kwargs)

View File

@ -1,515 +0,0 @@
# Copyright 2010 Jacob Kaplan-Moss
# Copyright 2011 OpenStack Foundation
# Copyright 2012 Grid Dynamics
# Copyright 2013 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Base utilities to build API operation managers and objects on top of.
"""
import abc
import copy
from oslo_utils import reflection
from oslo_utils import strutils
import six
from six.moves.urllib import parse
from heatclient._i18n import _
from heatclient import exc as exceptions
def getid(obj):
"""Return id if argument is a Resource.
Abstracts the common pattern of allowing both an object or an object's ID
(UUID) as a parameter when dealing with relationships.
"""
try:
if obj.uuid:
return obj.uuid
except AttributeError:
pass
try:
return obj.id
except AttributeError:
return obj
# TODO(aababilov): call run_hooks() in HookableMixin's child classes
class HookableMixin(object):
"""Mixin so classes can register and run hooks."""
_hooks_map = {}
@classmethod
def add_hook(cls, hook_type, hook_func):
"""Add a new hook of specified type.
:param cls: class that registers hooks
:param hook_type: hook type, e.g., '__pre_parse_args__'
:param hook_func: hook function
"""
if hook_type not in cls._hooks_map:
cls._hooks_map[hook_type] = []
cls._hooks_map[hook_type].append(hook_func)
@classmethod
def run_hooks(cls, hook_type, *args, **kwargs):
"""Run all hooks of specified type.
:param cls: class that registers hooks
:param hook_type: hook type, e.g., '__pre_parse_args__'
:param args: args to be passed to every hook function
:param kwargs: kwargs to be passed to every hook function
"""
hook_funcs = cls._hooks_map.get(hook_type) or []
for hook_func in hook_funcs:
hook_func(*args, **kwargs)
class BaseManager(HookableMixin):
"""Basic manager type providing common operations.
Managers interact with a particular type of API (servers, flavors, images,
etc.) and provide CRUD operations for them.
"""
resource_class = None
def __init__(self, client):
"""Initializes BaseManager with `client`.
:param client: instance of BaseClient descendant for HTTP requests
"""
super(BaseManager, self).__init__()
self.client = client
def _list(self, url, response_key=None, obj_class=None, json=None):
"""List the collection.
:param url: a partial URL, e.g., '/servers'
:param response_key: the key to be looked up in response dictionary,
e.g., 'servers'. If response_key is None - all response body
will be used.
:param obj_class: class for constructing the returned objects
(self.resource_class will be used by default)
:param json: data that will be encoded as JSON and passed in POST
request (GET will be sent by default)
"""
if json:
body = self.client.post(url, json=json).json()
else:
body = self.client.get(url).json()
if obj_class is None:
obj_class = self.resource_class
data = body[response_key] if response_key is not None else body
# NOTE(ja): keystone returns values as list as {'values': [ ... ]}
# unlike other services which just return the list...
try:
data = data['values']
except (KeyError, TypeError):
pass
return [obj_class(self, res, loaded=True) for res in data if res]
def _get(self, url, response_key=None):
"""Get an object from collection.
:param url: a partial URL, e.g., '/servers'
:param response_key: the key to be looked up in response dictionary,
e.g., 'server'. If response_key is None - all response body
will be used.
"""
body = self.client.get(url).json()
data = body[response_key] if response_key is not None else body
return self.resource_class(self, data, loaded=True)
def _head(self, url):
"""Retrieve request headers for an object.
:param url: a partial URL, e.g., '/servers'
"""
resp = self.client.head(url)
return resp.status_code == 204
def _post(self, url, json, response_key=None, return_raw=False):
"""Create an object.
:param url: a partial URL, e.g., '/servers'
:param json: data that will be encoded as JSON and passed in POST
request (GET will be sent by default)
:param response_key: the key to be looked up in response dictionary,
e.g., 'server'. If response_key is None - all response body
will be used.
:param return_raw: flag to force returning raw JSON instead of
Python object of self.resource_class
"""
body = self.client.post(url, json=json).json()
data = body[response_key] if response_key is not None else body
if return_raw:
return data
return self.resource_class(self, data)
def _put(self, url, json=None, response_key=None):
"""Update an object with PUT method.
:param url: a partial URL, e.g., '/servers'
:param json: data that will be encoded as JSON and passed in POST
request (GET will be sent by default)
:param response_key: the key to be looked up in response dictionary,
e.g., 'servers'. If response_key is None - all response body
will be used.
"""
resp = self.client.put(url, json=json)
# PUT requests may not return a body
if resp.content:
body = resp.json()
if response_key is not None:
return self.resource_class(self, body[response_key])
else:
return self.resource_class(self, body)
def _patch(self, url, json=None, response_key=None):
"""Update an object with PATCH method.
:param url: a partial URL, e.g., '/servers'
:param json: data that will be encoded as JSON and passed in POST
request (GET will be sent by default)
:param response_key: the key to be looked up in response dictionary,
e.g., 'servers'. If response_key is None - all response body
will be used.
"""
body = self.client.patch(url, json=json).json()
if response_key is not None:
return self.resource_class(self, body[response_key])
else:
return self.resource_class(self, body)
def _delete(self, url):
"""Delete an object.
:param url: a partial URL, e.g., '/servers/my-server'
"""
return self.client.delete(url)
@six.add_metaclass(abc.ABCMeta)
class ManagerWithFind(BaseManager):
"""Manager with additional `find()`/`findall()` methods."""
@abc.abstractmethod
def list(self):
pass
def find(self, **kwargs):
"""Find a single item with attributes matching ``**kwargs``.
This isn't very efficient: it loads the entire list then filters on
the Python side.
"""
matches = self.findall(**kwargs)
num_matches = len(matches)
if num_matches == 0:
msg = _("No %(name)s matching %(args)s.") % {
'name': self.resource_class.__name__,
'args': kwargs
}
raise exceptions.NotFound(msg)
elif num_matches > 1:
raise exceptions.NoUniqueMatch()
else:
return matches[0]
def findall(self, **kwargs):
"""Find all items with attributes matching ``**kwargs``.
This isn't very efficient: it loads the entire list then filters on
the Python side.
"""
found = []
searches = kwargs.items()
for obj in self.list():
try:
if all(getattr(obj, attr) == value
for (attr, value) in searches):
found.append(obj)
except AttributeError:
continue
return found
class CrudManager(BaseManager):
"""Base manager class for manipulating entities.
Children of this class are expected to define a `collection_key` and `key`.
- `collection_key`: Usually a plural noun by convention (e.g. `entities`);
used to refer collections in both URL's (e.g. `/v3/entities`) and JSON
objects containing a list of member resources (e.g. `{'entities': [{},
{}, {}]}`).
- `key`: Usually a singular noun by convention (e.g. `entity`); used to
refer to an individual member of the collection.
"""
collection_key = None
key = None
def build_url(self, base_url=None, **kwargs):
"""Builds a resource URL for the given kwargs.
Given an example collection where `collection_key = 'entities'` and
`key = 'entity'`, the following URL's could be generated.
By default, the URL will represent a collection of entities, e.g.::
/entities
If kwargs contains an `entity_id`, then the URL will represent a
specific member, e.g.::
/entities/{entity_id}
:param base_url: if provided, the generated URL will be appended to it
"""
url = base_url if base_url is not None else ''
url += '/%s' % self.collection_key
# do we have a specific entity?
entity_id = kwargs.get('%s_id' % self.key)
if entity_id is not None:
url += '/%s' % entity_id
return url
def _filter_kwargs(self, kwargs):
"""Drop null values and handle ids."""
for key, ref in kwargs.copy().items():
if ref is None:
kwargs.pop(key)
else:
if isinstance(ref, Resource):
kwargs.pop(key)
kwargs['%s_id' % key] = getid(ref)
return kwargs
def create(self, **kwargs):
kwargs = self._filter_kwargs(kwargs)
return self._post(
self.build_url(**kwargs),
{self.key: kwargs},
self.key)
def get(self, **kwargs):
kwargs = self._filter_kwargs(kwargs)
return self._get(
self.build_url(**kwargs),
self.key)
def head(self, **kwargs):
kwargs = self._filter_kwargs(kwargs)
return self._head(self.build_url(**kwargs))
def list(self, base_url=None, **kwargs):
"""List the collection.
:param base_url: if provided, the generated URL will be appended to it
"""
kwargs = self._filter_kwargs(kwargs)
return self._list(
'%(base_url)s%(query)s' % {
'base_url': self.build_url(base_url=base_url, **kwargs),
'query': '?%s' % parse.urlencode(kwargs) if kwargs else '',
},
self.collection_key)
def put(self, base_url=None, **kwargs):
"""Update an element.
:param base_url: if provided, the generated URL will be appended to it
"""
kwargs = self._filter_kwargs(kwargs)
return self._put(self.build_url(base_url=base_url, **kwargs))
def update(self, **kwargs):
kwargs = self._filter_kwargs(kwargs)
params = kwargs.copy()
params.pop('%s_id' % self.key)
return self._patch(
self.build_url(**kwargs),
{self.key: params},
self.key)
def delete(self, **kwargs):
kwargs = self._filter_kwargs(kwargs)
return self._delete(
self.build_url(**kwargs))
def find(self, base_url=None, **kwargs):
"""Find a single item with attributes matching ``**kwargs``.
:param base_url: if provided, the generated URL will be appended to it
"""
kwargs = self._filter_kwargs(kwargs)
rl = self._list(
'%(base_url)s%(query)s' % {
'base_url': self.build_url(base_url=base_url, **kwargs),
'query': '?%s' % parse.urlencode(kwargs) if kwargs else '',
},
self.collection_key)
num = len(rl)
if num == 0:
msg = _("No %(name)s matching %(args)s.") % {
'name': self.resource_class.__name__,
'args': kwargs
}
raise exceptions.NotFound(msg)
elif num > 1:
raise exceptions.NoUniqueMatch
else:
return rl[0]
class Extension(HookableMixin):
"""Extension descriptor."""
SUPPORTED_HOOKS = ('__pre_parse_args__', '__post_parse_args__')
manager_class = None
def __init__(self, name, module):
super(Extension, self).__init__()
self.name = name
self.module = module
self._parse_extension_module()
def _parse_extension_module(self):
self.manager_class = None
for attr_name, attr_value in self.module.__dict__.items():
if attr_name in self.SUPPORTED_HOOKS:
self.add_hook(attr_name, attr_value)
else:
try:
if issubclass(attr_value, BaseManager):
self.manager_class = attr_value
except TypeError:
pass
def __repr__(self):
return "<Extension '%s'>" % self.name
class Resource(object):
"""Base class for OpenStack resources (tenant, user, etc.).
This is pretty much just a bag for attributes.
"""
HUMAN_ID = False
NAME_ATTR = 'name'
def __init__(self, manager, info, loaded=False):
"""Populate and bind to a manager.
:param manager: BaseManager object
:param info: dictionary representing resource attributes
:param loaded: prevent lazy-loading if set to True
"""
self.manager = manager
self._info = info
self._add_details(info)
self._loaded = loaded
def __repr__(self):
reprkeys = sorted(k
for k in self.__dict__.keys()
if k[0] != '_' and k != 'manager')
info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys)
class_name = reflection.get_class_name(self, fully_qualified=False)
return "<%s %s>" % (class_name, info)
@property
def human_id(self):
"""Human-readable ID which can be used for bash completion. """
if self.HUMAN_ID:
name = getattr(self, self.NAME_ATTR, None)
if name is not None:
return strutils.to_slug(name)
return None
def _add_details(self, info):
for (k, v) in info.items():
try:
setattr(self, k, v)
self._info[k] = v
except AttributeError:
# In this case we already defined the attribute on the class
pass
def __getattr__(self, k):
if k not in self.__dict__:
# NOTE(bcwaldon): disallow lazy-loading if already loaded once
if not self.is_loaded():
self.get()
return self.__getattr__(k)
raise AttributeError(k)
else:
return self.__dict__[k]
def get(self):
"""Support for lazy loading details.
Some clients, such as novaclient have the option to lazy load the
details, details which can be loaded with this function.
"""
# set_loaded() first ... so if we have to bail, we know we tried.
self.set_loaded(True)
if not hasattr(self.manager, 'get'):
return
new = self.manager.get(self.id)
if new:
self._add_details(new._info)
self._add_details(
{'x_request_id': self.manager.client.last_request_id})
def __eq__(self, other):
if not isinstance(other, Resource):
return NotImplemented
# two resources of different types are not equal
if not isinstance(other, self.__class__):
return False
return self._info == other._info
def __ne__(self, other):
return not self.__eq__(other)
def is_same_obj(self, other):
"""Identify the two objects are same one with same id."""
if isinstance(other, self.__class__):
if hasattr(self, 'id') and hasattr(other, 'id'):
return self.id == other.id
return False
def is_loaded(self):
return self._loaded
def set_loaded(self, val):
self._loaded = val
def to_dict(self):
return copy.deepcopy(self._info)

View File

@ -1,151 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import copy
import uuid
import six
from six.moves.urllib import parse as urlparse
from swiftclient import client as sc
from swiftclient import utils as swiftclient_utils
from heatclient._i18n import _
from heatclient import exc
from heatclient.v1 import software_configs
def build_derived_config_params(action, source, name, input_values,
server_id, signal_transport, signal_id=None):
if isinstance(source, software_configs.SoftwareConfig):
source = source.to_dict()
input_values = input_values or {}
inputs = copy.deepcopy(source.get('inputs')) or []
for inp in inputs:
input_key = inp['name']
inp['value'] = input_values.pop(input_key, inp.get('default'))
# for any input values that do not have a declared input, add
# a derived declared input so that they can be used as config
# inputs
for inpk, inpv in input_values.items():
inputs.append({
'name': inpk,
'type': 'String',
'value': inpv
})
inputs.extend([{
'name': 'deploy_server_id',
'description': _('ID of the server being deployed to'),
'type': 'String',
'value': server_id
}, {
'name': 'deploy_action',
'description': _('Name of the current action being deployed'),
'type': 'String',
'value': action
}, {
'name': 'deploy_signal_transport',
'description': _('How the server should signal to heat with '
'the deployment output values.'),
'type': 'String',
'value': signal_transport
}])
if signal_transport == 'TEMP_URL_SIGNAL':
inputs.append({
'name': 'deploy_signal_id',
'description': _('ID of signal to use for signaling '
'output values'),
'type': 'String',
'value': signal_id
})
inputs.append({
'name': 'deploy_signal_verb',
'description': _('HTTP verb to use for signaling '
'output values'),
'type': 'String',
'value': 'PUT'
})
elif signal_transport != 'NO_SIGNAL':
raise exc.CommandError(
_('Unsupported signal transport %s') % signal_transport)
return {
'group': source.get('group') or 'Heat::Ungrouped',
'config': source.get('config') or '',
'options': source.get('options') or {},
'inputs': inputs,
'outputs': source.get('outputs') or [],
'name': name
}
def create_temp_url(swift_client, name, timeout, container=None):
container = container or '%(name)s-%(uuid)s' % {
'name': name, 'uuid': uuid.uuid4()}
object_name = str(uuid.uuid4())
swift_client.put_container(container)
key_header = 'x-account-meta-temp-url-key'
if key_header not in swift_client.head_account():
swift_client.post_account({
key_header: six.text_type(uuid.uuid4())[:32]})
key = swift_client.head_account()[key_header]
project_path = swift_client.url.split('/')[-1]
path = '/v1/%s/%s/%s' % (project_path, container, object_name)
timeout_secs = timeout * 60
tempurl = swiftclient_utils.generate_temp_url(path, timeout_secs, key,
'PUT')
sw_url = urlparse.urlparse(swift_client.url)
put_url = '%s://%s%s' % (sw_url.scheme, sw_url.netloc, tempurl)
swift_client.put_object(container, object_name, '')
return put_url
def build_signal_id(hc, args):
if args.signal_transport != 'TEMP_URL_SIGNAL':
return
if args.os_no_client_auth:
raise exc.CommandError(_(
'Cannot use --os-no-client-auth, auth required to create '
'a Swift TempURL.'))
swift_client = create_swift_client(
hc.http_client.auth, hc.http_client.session, args)
return create_temp_url(swift_client, args.name, args.timeout)
def create_swift_client(auth, session, args):
auth_token = auth.get_token(session)
endpoint = auth.get_endpoint(session,
service_type='object-store',
region_name=args.os_region_name)
project_name = args.os_project_name or args.os_tenant_name
swift_args = {
'auth_version': '2.0',
'tenant_name': project_name,
'user': args.os_username,
'key': None,
'authurl': None,
'preauthtoken': auth_token,
'preauthurl': endpoint,
'cacert': args.os_cacert,
'insecure': args.insecure
}
return sc.Connection(**swift_args)

View File

@ -1,67 +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 heatclient._i18n import _
from heatclient.common import template_format
SECTIONS = (
PARAMETER_DEFAULTS, PARAMETERS, RESOURCE_REGISTRY,
ENCRYPTED_PARAM_NAMES, EVENT_SINKS,
PARAMETER_MERGE_STRATEGIES
) = (
'parameter_defaults', 'parameters', 'resource_registry',
'encrypted_param_names', 'event_sinks',
'parameter_merge_strategies'
)
def parse(env_str):
"""Takes a string and returns a dict containing the parsed structure.
This includes determination of whether the string is using the
YAML format.
"""
try:
env = yaml.load(env_str, Loader=template_format.yaml_loader)
except yaml.YAMLError:
# NOTE(prazumovsky): we need to return more informative error for
# user, so use SafeLoader, which return error message with template
# snippet where error has been occurred.
try:
env = yaml.load(env_str, Loader=yaml.SafeLoader)
except yaml.YAMLError as yea:
raise ValueError(yea)
else:
if env is None:
env = {}
elif not isinstance(env, dict):
raise ValueError(_('The environment is not a valid '
'YAML mapping data type.'))
for param in env:
if param not in SECTIONS:
raise ValueError(_('environment has wrong section "%s"') % param)
return env
def default_for_missing(env):
"""Checks a parsed environment for missing sections."""
for param in SECTIONS:
if param not in env and param != PARAMETER_MERGE_STRATEGIES:
if param in (ENCRYPTED_PARAM_NAMES, EVENT_SINKS):
env[param] = []
else:
env[param] = {}

View File

@ -1,264 +0,0 @@
# Copyright 2015 Red Hat Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import sys
import time
from heatclient._i18n import _
from heatclient.common import utils
import heatclient.exc as exc
from heatclient.v1 import events as events_mod
def get_hook_events(hc, stack_id, event_args, nested_depth=0,
hook_type='pre-create'):
if hook_type == 'pre-create':
stack_action_reason = 'Stack CREATE started'
hook_event_reason = 'CREATE paused until Hook pre-create is cleared'
hook_clear_event_reason = 'Hook pre-create is cleared'
elif hook_type == 'pre-update':
stack_action_reason = 'Stack UPDATE started'
hook_event_reason = 'UPDATE paused until Hook pre-update is cleared'
hook_clear_event_reason = 'Hook pre-update is cleared'
elif hook_type == 'pre-delete':
stack_action_reason = 'Stack DELETE started'
hook_event_reason = 'DELETE paused until Hook pre-delete is cleared'
hook_clear_event_reason = 'Hook pre-delete is cleared'
else:
raise exc.CommandError(_('Unexpected hook type %s') % hook_type)
events = get_events(hc, stack_id=stack_id, event_args=event_args,
nested_depth=nested_depth)
# Get the most recent event associated with this action, which gives us the
# event when we moved into IN_PROGRESS for the hooks we're interested in.
stack_name = stack_id.split("/")[0]
action_start_event = [e for e in enumerate(events)
if e[1].resource_status_reason == stack_action_reason
and e[1].stack_name == stack_name][-1]
# Slice the events with the index from the enumerate
action_start_index = action_start_event[0]
events = events[action_start_index:]
# Get hook events still pending by some list filtering/comparison
# We build a map hook events per-resource, and remove any event
# for which there is a corresponding hook-clear event.
resource_event_map = {}
for e in events:
stack_resource = (e.stack_name, e.resource_name)
if e.resource_status_reason == hook_event_reason:
resource_event_map[(e.stack_name, e.resource_name)] = e
elif e.resource_status_reason == hook_clear_event_reason:
if resource_event_map.get(stack_resource):
del(resource_event_map[(e.stack_name, e.resource_name)])
return list(resource_event_map.values())
def get_events(hc, stack_id, event_args, nested_depth=0,
marker=None, limit=None):
event_args = dict(event_args)
if marker:
event_args['marker'] = marker
if limit:
event_args['limit'] = limit
if not nested_depth:
# simple call with no nested_depth
return _get_stack_events(hc, stack_id, event_args)
# assume an API which supports nested_depth
event_args['nested_depth'] = nested_depth
events = _get_stack_events(hc, stack_id, event_args)
if not events:
return events
first_links = getattr(events[0], 'links', [])
root_stack_link = [l for l in first_links
if l.get('rel') == 'root_stack']
if root_stack_link:
# response has a root_stack link, indicating this is an API which
# supports nested_depth
return events
# API doesn't support nested_depth, do client-side paging and recursive
# event fetch
marker = event_args.pop('marker', None)
limit = event_args.pop('limit', None)
event_args.pop('nested_depth', None)
events = _get_stack_events(hc, stack_id, event_args)
events.extend(_get_nested_events(hc, nested_depth,
stack_id, event_args))
# Because there have been multiple stacks events mangled into
# one list, we need to sort before passing to print_list
# Note we can't use the prettytable sortby_index here, because
# the "start" option doesn't allow post-sort slicing, which
# will be needed to make "--marker" work for nested_depth lists
events.sort(key=lambda x: x.event_time)
# Slice the list if marker is specified
if marker:
try:
marker_index = [e.id for e in events].index(marker)
events = events[marker_index:]
except ValueError:
pass
# Slice the list if limit is specified
if limit:
limit_index = min(int(limit), len(events))
events = events[:limit_index]
return events
def _get_nested_ids(hc, stack_id):
nested_ids = []
try:
resources = hc.resources.list(stack_id=stack_id)
except exc.HTTPNotFound:
raise exc.CommandError(_('Stack not found: %s') % stack_id)
for r in resources:
nested_id = utils.resource_nested_identifier(r)
if nested_id:
nested_ids.append(nested_id)
return nested_ids
def _get_nested_events(hc, nested_depth, stack_id, event_args):
# FIXME(shardy): this is very inefficient, we should add nested_depth to
# the event_list API in a future heat version, but this will be required
# until kilo heat is EOL.
nested_ids = _get_nested_ids(hc, stack_id)
nested_events = []
for n_id in nested_ids:
stack_events = _get_stack_events(hc, n_id, event_args)
if stack_events:
nested_events.extend(stack_events)
if nested_depth > 1:
next_depth = nested_depth - 1
nested_events.extend(_get_nested_events(
hc, next_depth, n_id, event_args))
return nested_events
def _get_stack_name_from_links(event):
links = dict((l.get('rel'),
l.get('href')) for l in getattr(event, 'links', []))
href = links.get('stack')
if not href:
return
return href.split('/stacks/', 1)[-1].split('/')[0]
def _get_stack_events(hc, stack_id, event_args):
event_args['stack_id'] = stack_id
try:
events = hc.events.list(**event_args)
except exc.HTTPNotFound as ex:
# it could be the stack or resource that is not found
# just use the message that the server sent us.
raise exc.CommandError(str(ex))
else:
stack_name = stack_id.split("/")[0]
# Show which stack the event comes from (for nested events)
for e in events:
e.stack_name = _get_stack_name_from_links(e) or stack_name
return events
def poll_for_events(hc, stack_name, action=None, poll_period=5, marker=None,
out=None, nested_depth=0):
"""Continuously poll events and logs for performed action on stack."""
if action:
stop_status = ('%s_FAILED' % action, '%s_COMPLETE' % action)
stop_check = lambda a: a in stop_status
else:
stop_check = lambda a: a.endswith('_COMPLETE') or a.endswith('_FAILED')
no_event_polls = 0
msg_template = _("\n Stack %(name)s %(status)s \n")
if not out:
out = sys.stdout
event_log_context = utils.EventLogContext()
def is_stack_event(event):
if getattr(event, 'resource_name', '') != stack_name:
return False
phys_id = getattr(event, 'physical_resource_id', '')
links = dict((l.get('rel'),
l.get('href')) for l in getattr(event, 'links', []))
stack_id = links.get('stack', phys_id).rsplit('/', 1)[-1]
return stack_id == phys_id
while True:
events = get_events(hc, stack_id=stack_name, nested_depth=nested_depth,
event_args={'sort_dir': 'asc',
'marker': marker})
if len(events) == 0:
no_event_polls += 1
else:
no_event_polls = 0
# set marker to last event that was received.
marker = getattr(events[-1], 'id', None)
events_log = utils.event_log_formatter(events, event_log_context)
out.write(events_log)
out.write('\n')
for event in events:
# check if stack event was also received
if is_stack_event(event):
stack_status = getattr(event, 'resource_status', '')
msg = msg_template % dict(
name=stack_name, status=stack_status)
if stop_check(stack_status):
return stack_status, msg
if no_event_polls >= 2:
# after 2 polls with no events, fall back to a stack get
stack = hc.stacks.get(stack_name, resolve_outputs=False)
stack_status = stack.stack_status
msg = msg_template % dict(
name=stack_name, status=stack_status)
if stop_check(stack_status):
return stack_status, msg
# go back to event polling again
no_event_polls = 0
time.sleep(poll_period)
def wait_for_events(ws, stack_name, out=None):
"""Receive events over the passed websocket and wait for final status."""
msg_template = _("\n Stack %(name)s %(status)s \n")
if not out:
out = sys.stdout
event_log_context = utils.EventLogContext()
while True:
data = ws.recv()['body']
event = events_mod.Event(None, data['payload'], True)
# Keep compatibility with the HTTP API
event.event_time = data['timestamp']
event.resource_status = '%s_%s' % (event.resource_action,
event.resource_status)
events_log = utils.event_log_formatter([event], event_log_context)
out.write(events_log)
out.write('\n')
if data['payload']['resource_name'] == stack_name:
stack_status = data['payload']['resource_status']
if stack_status in ('COMPLETE', 'FAILED'):
msg = msg_template % dict(
name=stack_name, status=event.resource_status)
return '%s_%s' % (event.resource_action, stack_status), msg

View File

@ -1,96 +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.
#
# Copyright 2015 IBM Corp.
import sys
from osc_lib.command import command
import six
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'
def indent_and_truncate(txt, spaces=0, truncate=False, truncate_limit=10,
truncate_prefix=None, truncate_postfix=None):
"""Indents supplied multiline text by the specified number of spaces
"""
if txt is None:
return
lines = six.text_type(txt).splitlines()
if truncate and len(lines) > truncate_limit:
lines = lines[-truncate_limit:]
if truncate_prefix is not None:
lines.insert(0, truncate_prefix)
if truncate_postfix is not None:
lines.append(truncate_postfix)
if spaces > 0:
lines = [" " * spaces + line for line in lines]
return '\n'.join(lines)
def print_software_deployment_output(data, name, out=sys.stdout, long=False):
"""Prints details of the software deployment for user consumption
The format attempts to be valid yaml, but is primarily aimed at showing
useful information to the user in a helpful layout.
"""
if data is None:
data = {}
if name in ('deploy_stdout', 'deploy_stderr'):
output = indent_and_truncate(
data.get(name),
spaces=4,
truncate=not long,
truncate_prefix='...',
truncate_postfix='(truncated, view all with --long)')
out.write(' %s: |\n%s\n' % (name, output))
else:
out.write(' %s: %s\n' % (name, data.get(name)))

View File

@ -1,80 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
from oslo_utils import fnmatch
from heatclient._i18n import _
from heatclient import exc
logger = logging.getLogger(__name__)
def clear_hook(hc, stack_id, resource_name, hook_type):
try:
hc.resources.signal(
stack_id=stack_id,
resource_name=resource_name,
data={'unset_hook': hook_type})
except exc.HTTPNotFound:
logger.error(
"Stack %(stack)s or resource %(resource)s "
"not found for hook %(hook_type)",
{'resource': resource_name, 'stack': stack_id,
'hook_type': hook_type})
def clear_wildcard_hooks(hc, stack_id, stack_patterns, hook_type,
resource_pattern):
if stack_patterns:
for resource in hc.resources.list(stack_id):
res_name = resource.resource_name
if fnmatch.fnmatchcase(res_name, stack_patterns[0]):
nested_stack = hc.resources.get(
stack_id=stack_id,
resource_name=res_name)
clear_wildcard_hooks(
hc,
nested_stack.physical_resource_id,
stack_patterns[1:], hook_type, resource_pattern)
else:
for resource in hc.resources.list(stack_id):
res_name = resource.resource_name
if fnmatch.fnmatchcase(res_name, resource_pattern):
clear_hook(hc, stack_id, res_name, hook_type)
def get_hook_type_via_status(hc, stack_id):
# Figure out if the hook should be pre-create, pre-update or
# pre-delete based on the stack status, also sanity assertions
# that we're in-progress.
try:
stack = hc.stacks.get(stack_id=stack_id)
except exc.HTTPNotFound:
raise exc.CommandError(_('Stack not found: %s') % stack_id)
else:
if 'IN_PROGRESS' not in stack.stack_status:
raise exc.CommandError(_('Stack status %s not IN_PROGRESS') %
stack.stack_status)
if 'CREATE' in stack.stack_status:
hook_type = 'pre-create'
elif 'UPDATE' in stack.stack_status:
hook_type = 'pre-update'
elif 'DELETE' in stack.stack_status:
hook_type = 'pre-delete'
else:
raise exc.CommandError(_('Unexpected stack status %s, '
'only create, update and delete supported')
% stack.stack_status)
return hook_type

View File

@ -1,363 +0,0 @@
# Copyright 2012 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import copy
import hashlib
import logging
import os
import socket
from keystoneauth1 import adapter
from oslo_serialization import jsonutils
from oslo_utils import encodeutils
from oslo_utils import importutils
import requests
import six
from six.moves.urllib import parse
from heatclient._i18n import _
from heatclient.common import utils
from heatclient import exc
LOG = logging.getLogger(__name__)
USER_AGENT = 'python-heatclient'
CHUNKSIZE = 1024 * 64 # 64kB
SENSITIVE_HEADERS = ('X-Auth-Token',)
osprofiler_web = importutils.try_import("osprofiler.web")
def authenticated_fetcher(hc):
"""A wrapper around the heat client object to fetch a template."""
def _do(*args, **kwargs):
if isinstance(hc.http_client, SessionClient):
method, url = args
return hc.http_client.request(url, method, **kwargs).content
else:
return hc.http_client.raw_request(*args, **kwargs).content
return _do
def get_system_ca_file():
"""Return path to system default CA file."""
# Standard CA file locations for Debian/Ubuntu, RedHat/Fedora,
# Suse, FreeBSD/OpenBSD, MacOSX, and the bundled ca
ca_path = ['/etc/ssl/certs/ca-certificates.crt',
'/etc/pki/tls/certs/ca-bundle.crt',
'/etc/ssl/ca-bundle.pem',
'/etc/ssl/cert.pem',
'/System/Library/OpenSSL/certs/cacert.pem',
requests.certs.where()]
for ca in ca_path:
LOG.debug("Looking for ca file %s", ca)
if os.path.exists(ca):
LOG.debug("Using ca file %s", ca)
return ca
LOG.warning("System ca file could not be found.")
class HTTPClient(object):
def __init__(self, endpoint, **kwargs):
self.endpoint = endpoint
self.auth_url = kwargs.get('auth_url')
self.auth_token = kwargs.get('token')
self.username = kwargs.get('username')
self.password = kwargs.get('password')
self.region_name = kwargs.get('region_name')
self.include_pass = kwargs.get('include_pass')
self.endpoint_url = endpoint
self.cert_file = kwargs.get('cert_file')
self.key_file = kwargs.get('key_file')
self.timeout = kwargs.get('timeout')
self.ssl_connection_params = {
'ca_file': kwargs.get('ca_file'),
'cert_file': kwargs.get('cert_file'),
'key_file': kwargs.get('key_file'),
'insecure': kwargs.get('insecure'),
}
self.verify_cert = None
if parse.urlparse(endpoint).scheme == "https":
if kwargs.get('insecure'):
self.verify_cert = False
else:
self.verify_cert = kwargs.get('ca_file', get_system_ca_file())
# FIXME(shardy): We need this for compatibility with the oslo apiclient
# we should move to inheriting this class from the oslo HTTPClient
self.last_request_id = None
def safe_header(self, name, value):
if name in SENSITIVE_HEADERS:
# because in python3 byte string handling is ... ug
v = value.encode('utf-8')
h = hashlib.sha1(v)
d = h.hexdigest()
return encodeutils.safe_decode(name), "{SHA1}%s" % d
else:
return (encodeutils.safe_decode(name),
encodeutils.safe_decode(value))
def log_curl_request(self, method, url, kwargs):
curl = ['curl -g -i -X %s' % method]
for (key, value) in kwargs['headers'].items():
header = '-H \'%s: %s\'' % self.safe_header(key, value)
curl.append(header)
conn_params_fmt = [
('key_file', '--key %s'),
('cert_file', '--cert %s'),
('ca_file', '--cacert %s'),
]
for (key, fmt) in conn_params_fmt:
value = self.ssl_connection_params.get(key)
if value:
curl.append(fmt % value)
if self.ssl_connection_params.get('insecure'):
curl.append('-k')
if 'data' in kwargs:
curl.append('-d \'%s\'' % kwargs['data'])
curl.append('%s%s' % (self.endpoint, url))
LOG.debug(' '.join(curl))
@staticmethod
def log_http_response(resp):
status = (resp.raw.version / 10.0, resp.status_code, resp.reason)
dump = ['\nHTTP/%.1f %s %s' % status]
dump.extend(['%s: %s' % (k, v) for k, v in resp.headers.items()])
dump.append('')
if resp.content:
content = resp.content
if isinstance(content, six.binary_type):
content = content.decode()
dump.extend([content, ''])
LOG.debug('\n'.join(dump))
def _http_request(self, url, method, **kwargs):
"""Send an http request with the specified characteristics.
Wrapper around requests.request to handle tasks such as
setting headers and error handling.
"""
# Copy the kwargs so we can reuse the original in case of redirects
kwargs['headers'] = copy.deepcopy(kwargs.get('headers', {}))
kwargs['headers'].setdefault('User-Agent', USER_AGENT)
if self.auth_token:
kwargs['headers'].setdefault('X-Auth-Token', self.auth_token)
else:
kwargs['headers'].update(self.credentials_headers())
if self.auth_url:
kwargs['headers'].setdefault('X-Auth-Url', self.auth_url)
if self.region_name:
kwargs['headers'].setdefault('X-Region-Name', self.region_name)
if self.include_pass and 'X-Auth-Key' not in kwargs['headers']:
kwargs['headers'].update(self.credentials_headers())
if osprofiler_web:
kwargs['headers'].update(osprofiler_web.get_trace_id_headers())
self.log_curl_request(method, url, kwargs)
if self.cert_file and self.key_file:
kwargs['cert'] = (self.cert_file, self.key_file)
if self.verify_cert is not None:
kwargs['verify'] = self.verify_cert
if self.timeout is not None:
kwargs['timeout'] = float(self.timeout)
# Allow caller to specify not to follow redirects, in which case we
# just return the redirect response. Useful for using stacks:lookup.
redirect = kwargs.pop('redirect', True)
# Since requests does not follow the RFC when doing redirection to sent
# back the same method on a redirect we are simply bypassing it. For
# example if we do a DELETE/POST/PUT on a URL and we get a 302 RFC says
# that we should follow that URL with the same method as before,
# requests doesn't follow that and send a GET instead for the method.
# Hopefully this could be fixed as they say in a comment in a future
# point version i.e.: 3.x
# See issue: https://github.com/kennethreitz/requests/issues/1704
allow_redirects = False
# Use fully qualified URL from response header for redirects
if not parse.urlparse(url).netloc:
url = self.endpoint_url + url
try:
resp = requests.request(
method,
url,
allow_redirects=allow_redirects,
**kwargs)
except socket.gaierror as e:
message = (_("Error finding address for %(url)s: %(e)s") %
{'url': self.endpoint_url + url, 'e': e})
raise exc.InvalidEndpoint(message=message)
except (socket.error, socket.timeout) as e:
endpoint = self.endpoint
message = (_("Error communicating with %(endpoint)s %(e)s") %
{'endpoint': endpoint, 'e': e})
raise exc.CommunicationError(message=message)
self.log_http_response(resp)
if not ('X-Auth-Key' in kwargs['headers']) and (
resp.status_code == 401 or
(resp.status_code == 500 and "(HTTP 401)" in resp.content)):
raise exc.HTTPUnauthorized(_("Authentication failed: %s")
% resp.content)
elif 400 <= resp.status_code < 600:
raise exc.from_response(resp)
elif resp.status_code in (301, 302, 305):
# Redirected. Reissue the request to the new location,
# unless caller specified redirect=False
if redirect:
location = resp.headers.get('location')
if not location:
message = _("Location not returned with redirect")
raise exc.InvalidEndpoint(message=message)
resp = self._http_request(location, method, **kwargs)
elif resp.status_code == 300:
raise exc.from_response(resp)
return resp
def credentials_headers(self):
creds = {}
# NOTE(dhu): (shardy) When deferred_auth_method=password, Heat
# encrypts and stores username/password. For Keystone v3, the
# intent is to use trusts since SHARDY is working towards
# deferred_auth_method=trusts as the default.
# TODO(dhu): Make Keystone v3 work in Heat standalone mode. Maye
# require X-Auth-User-Domain.
if self.username:
creds['X-Auth-User'] = self.username
if self.password:
creds['X-Auth-Key'] = self.password
return creds
def json_request(self, method, url, **kwargs):
kwargs.setdefault('headers', {})
kwargs['headers'].setdefault('Content-Type', 'application/json')
kwargs['headers'].setdefault('Accept', 'application/json')
if 'data' in kwargs:
kwargs['data'] = jsonutils.dumps(kwargs['data'])
resp = self._http_request(url, method, **kwargs)
body = utils.get_response_body(resp)
return resp, body
def raw_request(self, method, url, **kwargs):
kwargs.setdefault('headers', {})
kwargs['headers'].setdefault('Content-Type',
'application/octet-stream')
return self._http_request(url, method, **kwargs)
def client_request(self, method, url, **kwargs):
resp, body = self.json_request(method, url, **kwargs)
return resp
def head(self, url, **kwargs):
return self.client_request("HEAD", url, **kwargs)
def get(self, url, **kwargs):
return self.client_request("GET", url, **kwargs)
def post(self, url, **kwargs):
return self.client_request("POST", url, **kwargs)
def put(self, url, **kwargs):
return self.client_request("PUT", url, **kwargs)
def delete(self, url, **kwargs):
return self.raw_request("DELETE", url, **kwargs)
def patch(self, url, **kwargs):
return self.client_request("PATCH", url, **kwargs)
class SessionClient(adapter.LegacyJsonAdapter):
"""HTTP client based on Keystone client session."""
def request(self, url, method, **kwargs):
redirect = kwargs.get('redirect')
kwargs.setdefault('user_agent', USER_AGENT)
if 'data' in kwargs:
kwargs['data'] = jsonutils.dumps(kwargs['data'])
resp, body = super(SessionClient, self).request(
url, method,
raise_exc=False,
**kwargs)
if 400 <= resp.status_code < 600:
raise exc.from_response(resp)
elif resp.status_code in (301, 302, 305):
if redirect:
location = resp.headers.get('location')
path = self.strip_endpoint(location)
resp = self.request(path, method, **kwargs)
elif resp.status_code == 300:
raise exc.from_response(resp)
return resp
def credentials_headers(self):
return {}
def strip_endpoint(self, location):
if location is None:
message = _("Location not returned with 302")
raise exc.InvalidEndpoint(message=message)
if (self.endpoint_override is not None and
location.lower().startswith(self.endpoint_override.lower())):
return location[len(self.endpoint_override):]
else:
return location
def _construct_http_client(endpoint=None, username=None, password=None,
include_pass=None, endpoint_type=None,
auth_url=None, **kwargs):
session = kwargs.pop('session', None)
auth = kwargs.pop('auth', None)
if session:
if 'endpoint_override' not in kwargs and endpoint:
kwargs['endpoint_override'] = endpoint
if 'service_type' not in kwargs:
kwargs['service_type'] = 'orchestration'
if 'interface' not in kwargs and endpoint_type:
kwargs['interface'] = endpoint_type
return SessionClient(session, auth=auth, **kwargs)
else:
return HTTPClient(endpoint=endpoint, username=username,
password=password, include_pass=include_pass,
endpoint_type=endpoint_type, auth_url=auth_url,
**kwargs)

View File

@ -1,150 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
import collections
import hashlib
from cliff.formatters import base
class ResourceDotInfo(object):
def __init__(self, res):
self.resource = res
links = {l['rel']: l['href'] for l in res.links}
self.nested_dot_id = self.dot_id(links.get('nested'), 'stack')
self.stack_dot_id = self.dot_id(links.get('stack'), 'stack')
self.res_dot_id = self.dot_id(links.get('self'))
@staticmethod
def dot_id(url, prefix=None):
"""Build an id with a prefix and a truncated hash of the URL"""
if not url:
return None
if not prefix:
prefix = 'r'
hash_object = hashlib.sha256(url.encode('utf-8'))
return '%s_%s' % (prefix, hash_object.hexdigest()[:20])
class ResourceDotFormatter(base.ListFormatter):
def add_argument_group(self, parser):
pass
def emit_list(self, column_names, data, stdout, parsed_args):
writer = ResourceDotWriter(data, stdout)
writer.write()
class ResourceDotWriter(object):
def __init__(self, data, stdout):
self.resources_by_stack = collections.defaultdict(
collections.OrderedDict)
self.resources_by_dot_id = collections.OrderedDict()
self.nested_stack_ids = []
self.stdout = stdout
for r in data:
rinfo = ResourceDotInfo(r)
if rinfo.stack_dot_id:
self.resources_by_stack[
rinfo.stack_dot_id][r.resource_name] = rinfo
if rinfo.res_dot_id:
self.resources_by_dot_id[rinfo.res_dot_id] = rinfo
if rinfo.nested_dot_id:
self.nested_stack_ids.append(rinfo.nested_dot_id)
def write(self):
stdout = self.stdout
stdout.write('digraph G {\n')
stdout.write(' graph [\n'
' fontsize=10 fontname="Verdana" '
'compound=true rankdir=LR\n'
' ]\n')
self.write_root_nodes()
self.write_subgraphs()
self.write_nested_stack_edges()
self.write_required_by_edges()
stdout.write('}\n')
def write_root_nodes(self):
for stack_dot_id in set(self.resources_by_stack.keys()).difference(
self.nested_stack_ids):
resources = self.resources_by_stack[stack_dot_id]
self.write_nodes(resources, 2)
def write_subgraphs(self):
for dot_id, rinfo in self.resources_by_dot_id.items():
if rinfo.nested_dot_id:
resources = self.resources_by_stack[rinfo.nested_dot_id]
if resources:
self.write_subgraph(resources, rinfo)
def write_nodes(self, resources, indent):
stdout = self.stdout
spaces = ' ' * indent
for rinfo in resources.values():
r = rinfo.resource
dot_id = rinfo.res_dot_id
if r.resource_status.endswith('FAILED'):
style = 'style=filled color=red'
else:
style = ''
stdout.write('%s%s [label="%s\n%s" %s];\n'
% (spaces, dot_id, r.resource_name,
r.resource_type, style))
stdout.write('\n')
def write_subgraph(self, resources, nested_resource):
stdout = self.stdout
stack_dot_id = nested_resource.nested_dot_id
nested_name = nested_resource.resource.resource_name
stdout.write(' subgraph cluster_%s {\n' % stack_dot_id)
stdout.write(' label="%s";\n' % nested_name)
self.write_nodes(resources, 4)
stdout.write(' }\n\n')
def write_required_by_edges(self):
stdout = self.stdout
for dot_id, rinfo in self.resources_by_dot_id.items():
r = rinfo.resource
required_by = r.required_by
stack_dot_id = rinfo.stack_dot_id
if not required_by or not stack_dot_id:
continue
stack_resources = self.resources_by_stack.get(stack_dot_id, {})
for req in required_by:
other_rinfo = stack_resources.get(req)
if other_rinfo:
stdout.write(' %s -> %s;\n'
% (rinfo.res_dot_id, other_rinfo.res_dot_id))
stdout.write('\n')
def write_nested_stack_edges(self):
stdout = self.stdout
for dot_id, rinfo in self.resources_by_dot_id.items():
if rinfo.nested_dot_id:
nested_resources = self.resources_by_stack[rinfo.nested_dot_id]
if nested_resources:
first_resource = list(nested_resources.values())[0]
stdout.write(
' %s -> %s [\n color=dimgray lhead=cluster_%s '
'arrowhead=none\n ];\n'
% (dot_id, first_resource.res_dot_id,
rinfo.nested_dot_id))
stdout.write('\n')

View File

@ -1,71 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import json
import yaml
from heatclient._i18n import _
if hasattr(yaml, 'CSafeLoader'):
yaml_loader = yaml.CSafeLoader
else:
yaml_loader = yaml.SafeLoader
if hasattr(yaml, 'CSafeDumper'):
yaml_dumper = yaml.CSafeDumper
else:
yaml_dumper = yaml.SafeDumper
def _construct_yaml_str(self, node):
# Override the default string handling function
# to always return unicode objects
return self.construct_scalar(node)
yaml_loader.add_constructor(u'tag:yaml.org,2002:str', _construct_yaml_str)
# Unquoted dates like 2013-05-23 in yaml files get loaded as objects of type
# datetime.data which causes problems in API layer when being processed by
# openstack.common.jsonutils. Therefore, make unicode string out of timestamps
# until jsonutils can handle dates.
yaml_loader.add_constructor(u'tag:yaml.org,2002:timestamp',
_construct_yaml_str)
def parse(tmpl_str):
"""Takes a string and returns a dict containing the parsed structure.
This includes determination of whether the string is using the
JSON or YAML format.
"""
# strip any whitespace before the check
tmpl_str = tmpl_str.strip()
if tmpl_str.startswith('{'):
tpl = json.loads(tmpl_str)
else:
try:
tpl = yaml.load(tmpl_str, Loader=yaml_loader)
except yaml.YAMLError:
# NOTE(prazumovsky): we need to return more informative error for
# user, so use SafeLoader, which return error message with template
# snippet where error has been occurred.
try:
tpl = yaml.load(tmpl_str, Loader=yaml.SafeLoader)
except yaml.YAMLError as yea:
raise ValueError(yea)
else:
if tpl is None:
tpl = {}
# Looking for supported version keys in the loaded template
if not ('HeatTemplateFormatVersion' in tpl
or 'heat_template_version' in tpl
or 'AWSTemplateFormatVersion' in tpl):
raise ValueError(_("Template format version not found."))
return tpl

View File

@ -1,382 +0,0 @@
# Copyright 2012 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import collections
from oslo_serialization import jsonutils
import six
from six.moves.urllib import error
from six.moves.urllib import parse
from six.moves.urllib import request
from heatclient._i18n import _
from heatclient.common import environment_format
from heatclient.common import template_format
from heatclient.common import utils
from heatclient import exc
def process_template_path(template_path, object_request=None, existing=False):
"""Read template from template path.
Attempt to read template first as a file or url. If that is unsuccessful,
try again assuming path is to a template object.
:param template_path: local or uri path to template
:param object_request: custom object request function used to get template
if local or uri path fails
:param existing: if the current stack's template should be used
:returns: get_file dict and template contents
:raises: error.URLError
"""
try:
return get_template_contents(template_file=template_path,
existing=existing)
except error.URLError as template_file_exc:
try:
return get_template_contents(template_object=template_path,
object_request=object_request,
existing=existing)
except exc.HTTPNotFound:
# The initial exception gives the user better failure context.
raise template_file_exc
def get_template_contents(template_file=None, template_url=None,
template_object=None, object_request=None,
files=None, existing=False):
is_object = False
# Transform a bare file path to a file:// URL.
if template_file:
template_url = utils.normalise_file_path_to_url(template_file)
if template_url:
tpl = request.urlopen(template_url).read()
elif template_object:
is_object = True
template_url = template_object
tpl = object_request and object_request('GET',
template_object)
elif existing:
return {}, None
else:
raise exc.CommandError(_('Need to specify exactly one of '
'%(arg1)s, %(arg2)s or %(arg3)s') %
{
'arg1': '--template-file',
'arg2': '--template-url',
'arg3': '--template-object'})
if not tpl:
raise exc.CommandError(_('Could not fetch template from %s')
% template_url)
try:
if isinstance(tpl, six.binary_type):
tpl = tpl.decode('utf-8')
template = template_format.parse(tpl)
except ValueError as e:
raise exc.CommandError(_('Error parsing template %(url)s %(error)s') %
{'url': template_url, 'error': e})
tmpl_base_url = utils.base_url_for_url(template_url)
if files is None:
files = {}
resolve_template_get_files(template, files, tmpl_base_url, is_object,
object_request)
return files, template
def resolve_template_get_files(template, files, template_base_url,
is_object=False, object_request=None):
def ignore_if(key, value):
if key != 'get_file' and key != 'type':
return True
if not isinstance(value, six.string_types):
return True
if (key == 'type' and
not value.endswith(('.yaml', '.template'))):
return True
return False
def recurse_if(value):
return isinstance(value, (dict, list))
get_file_contents(template, files, template_base_url,
ignore_if, recurse_if, is_object, object_request)
def is_template(file_content):
try:
if isinstance(file_content, six.binary_type):
file_content = file_content.decode('utf-8')
template_format.parse(file_content)
except (ValueError, TypeError):
return False
return True
def get_file_contents(from_data, files, base_url=None,
ignore_if=None, recurse_if=None,
is_object=False, object_request=None):
if recurse_if and recurse_if(from_data):
if isinstance(from_data, dict):
recurse_data = six.itervalues(from_data)
else:
recurse_data = from_data
for value in recurse_data:
get_file_contents(value, files, base_url, ignore_if, recurse_if,
is_object, object_request)
if isinstance(from_data, dict):
for key, value in from_data.items():
if ignore_if and ignore_if(key, value):
continue
if base_url and not base_url.endswith('/'):
base_url = base_url + '/'
str_url = parse.urljoin(base_url, value)
if str_url not in files:
if is_object and object_request:
file_content = object_request('GET', str_url)
else:
file_content = utils.read_url_content(str_url)
if is_template(file_content):
if is_object:
template = get_template_contents(
template_object=str_url, files=files,
object_request=object_request)[1]
else:
template = get_template_contents(
template_url=str_url, files=files)[1]
file_content = jsonutils.dumps(template)
files[str_url] = file_content
# replace the data value with the normalised absolute URL
from_data[key] = str_url
def read_url_content(url):
'''DEPRECATED! Use 'utils.read_url_content' instead.'''
return utils.read_url_content(url)
def base_url_for_url(url):
'''DEPRECATED! Use 'utils.base_url_for_url' instead.'''
return utils.base_url_for_url(url)
def normalise_file_path_to_url(path):
'''DEPRECATED! Use 'utils.normalise_file_path_to_url' instead.'''
return utils.normalise_file_path_to_url(path)
def deep_update(old, new):
'''Merge nested dictionaries.'''
# Prevents an error if in a previous iteration
# old[k] = None but v[k] = {...},
if old is None:
old = {}
for k, v in new.items():
if isinstance(v, collections.Mapping):
r = deep_update(old.get(k, {}), v)
old[k] = r
else:
old[k] = new[k]
return old
def process_multiple_environments_and_files(env_paths=None, template=None,
template_url=None,
env_path_is_object=None,
object_request=None,
env_list_tracker=None):
"""Reads one or more environment files.
Reads in each specified environment file and returns a dictionary
of the filenames->contents (suitable for the files dict)
and the consolidated environment (after having applied the correct
overrides based on order).
If a list is provided in the env_list_tracker parameter, the behavior
is altered to take advantage of server-side environment resolution.
Specifically, this means:
* Populating env_list_tracker with an ordered list of environment file
URLs to be passed to the server
* Including the contents of each environment file in the returned
files dict, keyed by one of the URLs in env_list_tracker
:param env_paths: list of paths to the environment files to load; if
None, empty results will be returned
:type env_paths: list or None
:param template: unused; only included for API compatibility
:param template_url: unused; only included for API compatibility
:param env_list_tracker: if specified, environment filenames will be
stored within
:type env_list_tracker: list or None
:return: tuple of files dict and a dict of the consolidated environment
:rtype: tuple
"""
merged_files = {}
merged_env = {}
# If we're keeping a list of environment files separately, include the
# contents of the files in the files dict
include_env_in_files = env_list_tracker is not None
if env_paths:
for env_path in env_paths:
files, env = process_environment_and_files(
env_path=env_path,
template=template,
template_url=template_url,
env_path_is_object=env_path_is_object,
object_request=object_request,
include_env_in_files=include_env_in_files)
# 'files' looks like {"filename1": contents, "filename2": contents}
# so a simple update is enough for merging
merged_files.update(files)
# 'env' can be a deeply nested dictionary, so a simple update is
# not enough
merged_env = deep_update(merged_env, env)
if env_list_tracker is not None:
env_url = utils.normalise_file_path_to_url(env_path)
env_list_tracker.append(env_url)
return merged_files, merged_env
def process_environment_and_files(env_path=None,
template=None,
template_url=None,
env_path_is_object=None,
object_request=None,
include_env_in_files=False):
"""Loads a single environment file.
Returns an entry suitable for the files dict which maps the environment
filename to its contents.
:param env_path: full path to the file to load
:type env_path: str or None
:param include_env_in_files: if specified, the raw environment file itself
will be included in the returned files dict
:type include_env_in_files: bool
:return: tuple of files dict and the loaded environment as a dict
:rtype: (dict, dict)
"""
files = {}
env = {}
is_object = env_path_is_object and env_path_is_object(env_path)
if is_object:
raw_env = object_request and object_request('GET', env_path)
env = environment_format.parse(raw_env)
env_base_url = utils.base_url_for_url(env_path)
resolve_environment_urls(
env.get('resource_registry'),
files,
env_base_url, is_object=True, object_request=object_request)
elif env_path:
env_url = utils.normalise_file_path_to_url(env_path)
env_base_url = utils.base_url_for_url(env_url)
raw_env = request.urlopen(env_url).read()
env = environment_format.parse(raw_env)
resolve_environment_urls(
env.get('resource_registry'),
files,
env_base_url)
if include_env_in_files:
files[env_url] = jsonutils.dumps(env)
return files, env
def resolve_environment_urls(resource_registry, files, env_base_url,
is_object=False, object_request=None):
"""Handles any resource URLs specified in an environment.
:param resource_registry: mapping of type name to template filename
:type resource_registry: dict
:param files: dict to store loaded file contents into
:type files: dict
:param env_base_url: base URL to look in when loading files
:type env_base_url: str or None
"""
if resource_registry is None:
return
rr = resource_registry
base_url = rr.get('base_url', env_base_url)
def ignore_if(key, value):
if key == 'base_url':
return True
if isinstance(value, dict):
return True
if '::' in value:
# Built in providers like: "X::Compute::Server"
# don't need downloading.
return True
if key in ['hooks', 'restricted_actions']:
return True
get_file_contents(rr, files, base_url, ignore_if,
is_object=is_object, object_request=object_request)
for res_name, res_dict in rr.get('resources', {}).items():
res_base_url = res_dict.get('base_url', base_url)
get_file_contents(
res_dict, files, res_base_url, ignore_if,
is_object=is_object, object_request=object_request)
def hooks_to_env(env, arg_hooks, hook):
"""Add hooks from args to environment's resource_registry section.
Hooks are either "resource_name" (if it's a top-level resource) or
"nested_stack/resource_name" (if the resource is in a nested stack).
The environment expects each hook to be associated with the resource
within `resource_registry/resources` using the `hooks: pre-create` format.
"""
if 'resource_registry' not in env:
env['resource_registry'] = {}
if 'resources' not in env['resource_registry']:
env['resource_registry']['resources'] = {}
for hook_declaration in arg_hooks:
hook_path = [r for r in hook_declaration.split('/') if r]
resources = env['resource_registry']['resources']
for nested_stack in hook_path:
if nested_stack not in resources:
resources[nested_stack] = {}
resources = resources[nested_stack]
else:
resources['hooks'] = hook

View File

@ -1,449 +0,0 @@
# Copyright 2012 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import base64
import logging
import os
import textwrap
import uuid
from oslo_serialization import jsonutils
from oslo_utils import encodeutils
import prettytable
import six
from six.moves.urllib import error
from six.moves.urllib import parse
from six.moves.urllib import request
import yaml
from heatclient._i18n import _
from heatclient import exc
LOG = logging.getLogger(__name__)
supported_formats = {
"json": lambda x: jsonutils.dumps(x, indent=2),
"yaml": yaml.safe_dump
}
def arg(*args, **kwargs):
"""Decorator for CLI args.
Example:
>>> @arg("name", help="Name of the new entity")
... def entity_create(args):
... pass
"""
def _decorator(func):
add_arg(func, *args, **kwargs)
return func
return _decorator
def env(*args, **kwargs):
"""Returns the first environment variable set.
If all are empty, defaults to '' or keyword arg `default`.
"""
for arg in args:
value = os.environ.get(arg)
if value:
return value
return kwargs.get('default', '')
def add_arg(func, *args, **kwargs):
"""Bind CLI arguments to a shell.py `do_foo` function."""
if not hasattr(func, 'arguments'):
func.arguments = []
# NOTE(sirp): avoid dups that can occur when the module is shared across
# tests.
if (args, kwargs) not in func.arguments:
# Because of the semantics of decorator composition if we just append
# to the options list positional options will appear to be backwards.
func.arguments.insert(0, (args, kwargs))
def print_list(objs, fields, formatters=None, sortby_index=0,
mixed_case_fields=None, field_labels=None):
"""Print a list of objects as a table, one row per object.
:param objs: iterable of :class:`Resource`
:param fields: attributes that correspond to columns, in order
:param formatters: `dict` of callables for field formatting
:param sortby_index: index of the field for sorting table rows
:param mixed_case_fields: fields corresponding to object attributes that
have mixed case names (e.g., 'serverId')
:param field_labels: Labels to use in the heading of the table, default to
fields.
"""
formatters = formatters or {}
mixed_case_fields = mixed_case_fields or []
field_labels = field_labels or fields
if len(field_labels) != len(fields):
raise ValueError(_("Field labels list %(labels)s has different number "
"of elements than fields list %(fields)s"),
{'labels': field_labels, 'fields': fields})
if sortby_index is None:
kwargs = {}
else:
kwargs = {'sortby': field_labels[sortby_index]}
pt = prettytable.PrettyTable(field_labels)
pt.align = 'l'
for o in objs:
row = []
for field in fields:
if field in formatters:
row.append(formatters[field](o))
else:
if field in mixed_case_fields:
field_name = field.replace(' ', '_')
else:
field_name = field.lower().replace(' ', '_')
data = getattr(o, field_name, '')
row.append(data)
pt.add_row(row)
if six.PY3:
print(encodeutils.safe_encode(pt.get_string(**kwargs)).decode())
else:
print(encodeutils.safe_encode(pt.get_string(**kwargs)))
def link_formatter(links):
def format_link(l):
if 'rel' in l:
return "%s (%s)" % (l.get('href', ''), l.get('rel', ''))
else:
return "%s" % (l.get('href', ''))
return '\n'.join(format_link(l) for l in links or [])
def resource_nested_identifier(rsrc):
nested_link = [l for l in rsrc.links or []
if l.get('rel') == 'nested']
if nested_link:
nested_href = nested_link[0].get('href')
nested_identifier = nested_href.split("/")[-2:]
return "/".join(nested_identifier)
def json_formatter(js):
return jsonutils.dumps(js, indent=2, ensure_ascii=False,
separators=(', ', ': '))
def yaml_formatter(js):
return yaml.safe_dump(js, default_flow_style=False)
def text_wrap_formatter(d):
return '\n'.join(textwrap.wrap(d or '', 55))
def newline_list_formatter(r):
return '\n'.join(r or [])
def print_dict(d, formatters=None):
formatters = formatters or {}
pt = prettytable.PrettyTable(['Property', 'Value'],
caching=False, print_empty=False)
pt.align = 'l'
for field in d.keys():
if field in formatters:
pt.add_row([field, formatters[field](d[field])])
else:
pt.add_row([field, d[field]])
print(pt.get_string(sortby='Property'))
class EventLogContext(object):
def __init__(self):
# key is a stack id or the name of the nested stack, value is a tuple
# of the parent stack id, and the name of the resource in the parent
# stack
self.id_to_res_info = {}
def prepend_paths(self, resource_path, stack_id):
if stack_id not in self.id_to_res_info:
return
stack_id, res_name = self.id_to_res_info.get(stack_id)
if res_name in self.id_to_res_info:
# do a double lookup to skip the ugly stack name that doesn't
# correspond to an actual resource name
n_stack_id, res_name = self.id_to_res_info.get(res_name)
resource_path.insert(0, res_name)
self.prepend_paths(resource_path, n_stack_id)
elif res_name:
resource_path.insert(0, res_name)
def build_resource_name(self, event):
res_name = getattr(event, 'resource_name')
# Contribute this event to self.id_to_res_info to assist with
# future calls to build_resource_name
def get_stack_id():
if getattr(event, 'stack_id', None) is not None:
return event.stack_id
for l in getattr(event, 'links', []):
if l.get('rel') == 'stack':
if 'href' not in l:
return None
stack_link = l['href']
return stack_link.split('/')[-1]
stack_id = get_stack_id()
if not stack_id:
return res_name
phys_id = getattr(event, 'physical_resource_id', None)
status = getattr(event, 'resource_status', None)
is_stack_event = stack_id == phys_id
if is_stack_event:
# this is an event for a stack
self.id_to_res_info[stack_id] = (stack_id, res_name)
elif phys_id and status == 'CREATE_IN_PROGRESS':
# this might be an event for a resource which creates a stack
self.id_to_res_info[phys_id] = (stack_id, res_name)
# Now build this resource path based on previous calls to
# build_resource_name
resource_path = []
if res_name and not is_stack_event:
resource_path.append(res_name)
self.prepend_paths(resource_path, stack_id)
return '.'.join(resource_path)
def event_log_formatter(events, event_log_context=None):
"""Return the events in log format."""
event_log = []
log_format = ("%(event_time)s "
"[%(rsrc_name)s]: %(rsrc_status)s %(rsrc_status_reason)s")
# It is preferable for a context to be passed in, but there might be enough
# events in this call to build a better resource name, so create a context
# anyway
if event_log_context is None:
event_log_context = EventLogContext()
for event in events:
rsrc_name = event_log_context.build_resource_name(event)
event_time = getattr(event, 'event_time', '')
log = log_format % {
'event_time': event_time.replace('T', ' '),
'rsrc_name': rsrc_name,
'rsrc_status': getattr(event, 'resource_status', ''),
'rsrc_status_reason': getattr(event, 'resource_status_reason', '')
}
event_log.append(log)
return "\n".join(event_log)
def print_update_list(lst, fields, formatters=None):
"""Print the stack-update --dry-run output as a table.
This function is necessary to print the stack-update --dry-run
output, which contains additional information about the update.
"""
formatters = formatters or {}
pt = prettytable.PrettyTable(fields, caching=False, print_empty=False)
pt.align = 'l'
for change in lst:
row = []
for field in fields:
if field in formatters:
row.append(formatters[field](change.get(field, None)))
else:
row.append(change.get(field, None))
pt.add_row(row)
if six.PY3:
print(encodeutils.safe_encode(pt.get_string()).decode())
else:
print(encodeutils.safe_encode(pt.get_string()))
def find_resource(manager, name_or_id):
"""Helper for the _find_* methods."""
# first try to get entity as integer id
try:
if isinstance(name_or_id, int) or name_or_id.isdigit():
return manager.get(int(name_or_id))
except exc.NotFound:
pass
# now try to get entity as uuid
try:
uuid.UUID(str(name_or_id))
return manager.get(name_or_id)
except (ValueError, exc.NotFound):
pass
# finally try to find entity by name
try:
return manager.find(name=name_or_id)
except exc.NotFound:
msg = (
_("No %(name)s with a name or ID of "
"'%(name_or_id)s' exists.")
% {
'name': manager.resource_class.__name__.lower(),
'name_or_id': name_or_id
})
raise exc.CommandError(msg)
def format_parameters(params, parse_semicolon=True):
'''Reformat parameters into dict of format expected by the API.'''
if not 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_all_parameters(params, param_files,
template_file=None, template_url=None):
parameters = {}
parameters.update(format_parameters(params))
parameters.update(format_parameter_file(
param_files,
template_file,
template_url))
return parameters
def format_parameter_file(param_files, template_file=None,
template_url=None):
'''Reformat file parameters into dict of format expected by the API.'''
if not param_files:
return {}
params = format_parameters(param_files, False)
template_base_url = None
if template_file or template_url:
template_base_url = base_url_for_url(get_template_url(
template_file, template_url))
param_file = {}
for key, value in params.items():
param_file[key] = resolve_param_get_file(value,
template_base_url)
return param_file
def resolve_param_get_file(file, base_url):
if base_url and not base_url.endswith('/'):
base_url = base_url + '/'
str_url = parse.urljoin(base_url, file)
return read_url_content(str_url)
def format_output(output, format='yaml'):
"""Format the supplied dict as specified."""
output_format = format.lower()
try:
return supported_formats[output_format](output)
except KeyError:
raise exc.HTTPUnsupported(_("The format(%s) is unsupported.")
% output_format)
def parse_query_url(url):
base_url, query_params = url.split('?')
return base_url, parse.parse_qs(query_params)
def get_template_url(template_file=None, template_url=None):
if template_file:
template_url = normalise_file_path_to_url(template_file)
return template_url
def read_url_content(url):
try:
content = request.urlopen(url).read()
except error.URLError:
raise exc.CommandError(_('Could not fetch contents for %s') % url)
if content:
try:
content.decode('utf-8')
except ValueError:
content = base64.encodestring(content)
return content
def base_url_for_url(url):
parsed = parse.urlparse(url)
parsed_dir = os.path.dirname(parsed.path)
return parse.urljoin(url, parsed_dir)
def normalise_file_path_to_url(path):
if parse.urlparse(path).scheme:
return path
path = os.path.abspath(path)
return parse.urljoin('file:', request.pathname2url(path))
def get_response_body(resp):
body = resp.content
if 'application/json' in resp.headers.get('content-type', ''):
try:
body = resp.json()
except ValueError:
LOG.error('Could not decode response body as JSON')
else:
body = None
return body

View File

@ -1,202 +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 oslo_serialization import jsonutils
from oslo_utils import reflection
from heatclient._i18n import _
verbose = 0
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 InvalidEndpoint(BaseException):
"""The provided endpoint is invalid."""
class CommunicationError(BaseException):
"""Unable to communicate with server."""
class HTTPException(BaseException):
"""Base exception for all HTTP-derived exceptions."""
code = 'N/A'
def __init__(self, message=None, code=None):
super(HTTPException, self).__init__(message)
try:
self.error = jsonutils.loads(message)
if 'error' not in self.error:
raise KeyError(_('Key "error" not exists'))
except KeyError:
# NOTE(jianingy): If key 'error' happens 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__}}
if self.code == "N/A" and code is not None:
self.code = code
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:
return _('ERROR: %s') % message
class HTTPMultipleChoices(HTTPException):
code = 300
def __str__(self):
self.details = _("Requested version of Heat API is not"
"available.")
return (_("%(name)s (HTTP %(code)s) %(details)s") %
{
'name': reflection.get_class_name(self, fully_qualified=False),
'code': self.code,
'details': self.details})
class BadRequest(HTTPException):
"""DEPRECATED."""
code = 400
class HTTPBadRequest(BadRequest):
pass
class Unauthorized(HTTPException):
"""DEPRECATED."""
code = 401
class HTTPUnauthorized(Unauthorized):
pass
class Forbidden(HTTPException):
"""DEPRECATED."""
code = 403
class HTTPForbidden(Forbidden):
pass
class NotFound(HTTPException):
"""DEPRECATED."""
code = 404
class HTTPNotFound(NotFound):
pass
class NoUniqueMatch(HTTPException):
pass
class HTTPMethodNotAllowed(HTTPException):
code = 405
class Conflict(HTTPException):
"""DEPRECATED."""
code = 409
class HTTPConflict(Conflict):
pass
class OverLimit(HTTPException):
"""DEPRECATED."""
code = 413
class HTTPOverLimit(OverLimit):
pass
class HTTPUnsupported(HTTPException):
code = 415
class HTTPInternalServerError(HTTPException):
code = 500
class HTTPNotImplemented(HTTPException):
code = 501
class HTTPBadGateway(HTTPException):
code = 502
class ServiceUnavailable(HTTPException):
"""DEPRECATED."""
code = 503
class HTTPServiceUnavailable(ServiceUnavailable):
pass
# NOTE(bcwaldon): Build a mapping of HTTP codes to corresponding exception
# classes
_code_map = {}
for obj_name in dir(sys.modules[__name__]):
if obj_name.startswith('HTTP'):
obj = getattr(sys.modules[__name__], obj_name)
_code_map[obj.code] = obj
def from_response(response):
"""Return an instance of an HTTPException based on requests response."""
cls = _code_map.get(response.status_code, HTTPException)
return cls(response.content, response.status_code)
class NoTokenLookupException(Exception):
"""DEPRECATED."""
pass
class EndpointNotFound(Exception):
"""DEPRECATED."""
pass
class StackFailure(Exception):
pass

View File

@ -1,25 +0,0 @@
# zzxwill <zzxwill@gmail.com>, 2016. #zanata
msgid ""
msgstr ""
"Project-Id-Version: python-heatclient 1.2.1.dev53\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2016-06-27 08:52+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-27 11:57+0000\n"
"Last-Translator: zzxwill <zzxwill@gmail.com>\n"
"Language-Team: Chinese (China)\n"
"Language: zh-CN\n"
"X-Generator: Zanata 3.7.3\n"
"Plural-Forms: nplurals=1; plural=0\n"
msgid "Could not decode response body as JSON"
msgstr "无法将响应主题编码为JSON"
#, python-format
msgid "Stack %(stack)s or resource %(resource)snot found for hook %(hook_type)"
msgstr ""
"Postpone to translate this message as we Need to add a space between "
"'%(resource)s' and 'not'. Submitted a fix for the original string. (https://"
"review.openstack.org/334428)"

View File

@ -1,24 +0,0 @@
# zzxwill <zzxwill@gmail.com>, 2016. #zanata
msgid ""
msgstr ""
"Project-Id-Version: python-heatclient 1.2.1.dev53\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2016-06-27 08:52+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-27 11:59+0000\n"
"Last-Translator: zzxwill <zzxwill@gmail.com>\n"
"Language-Team: Chinese (China)\n"
"Language: zh-CN\n"
"X-Generator: Zanata 3.7.3\n"
"Plural-Forms: nplurals=1; plural=0\n"
msgid "User did not confirm stack delete (ctrl-c) so taking no action."
msgstr "因为用户没有确认栈删除操作ctrl-c所以不执行任何动作。"
msgid "User did not confirm stack delete (ctrl-d) so taking no action."
msgstr "因为用户没有确认栈删除操作ctrl-d所以不执行任何动作。"
msgid "User did not confirm stack delete so taking no action."
msgstr "因为用户没有确认栈删除操作,所以不执行任何动作。"

View File

@ -1,26 +0,0 @@
# zzxwill <zzxwill@gmail.com>, 2016. #zanata
msgid ""
msgstr ""
"Project-Id-Version: python-heatclient 1.2.1.dev53\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2016-06-27 08:52+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-27 12:01+0000\n"
"Last-Translator: zzxwill <zzxwill@gmail.com>\n"
"Language-Team: Chinese (China)\n"
"Language: zh-CN\n"
"X-Generator: Zanata 3.7.3\n"
"Plural-Forms: nplurals=1; plural=0\n"
#, python-format
msgid "\"%(old)s\" is deprecated, please use \"%(new)s\" instead"
msgstr "\"%(old)s\"已弃用,请使用\"%(new)s\"。"
#, python-format
msgid "%(arg1)s is deprecated, please use %(arg2)s instead"
msgstr "\"%(arg1)s\"已弃用,请使用\"%(arg2)s\"。"
msgid "System ca file could not be found."
msgstr "找不到系统CA文件。"

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.
#
import logging
from osc_lib import utils
LOG = logging.getLogger(__name__)
DEFAULT_ORCHESTRATION_API_VERSION = '1'
API_VERSION_OPTION = 'os_orchestration_api_version'
API_NAME = 'orchestration'
API_VERSIONS = {
'1': 'heatclient.v1.client.Client',
}
def make_client(instance):
"""Returns an orchestration service client"""
heat_client = utils.get_client_class(
API_NAME,
instance._api_version[API_NAME],
API_VERSIONS)
LOG.debug('Instantiating orchestration client: %s', heat_client)
kwargs = {'region_name': instance.region_name,
'interface': instance.interface}
if instance.session:
kwargs.update({'session': instance.session,
'service_type': API_NAME})
else:
endpoint = instance.get_endpoint_for_service_type(
API_NAME,
region_name=instance.region_name,
interface=instance.interface,
)
kwargs.update({'endpoint': endpoint,
'auth_url': instance.auth.auth_url,
'username': instance.auth_ref.username,
'token': instance.auth_ref.auth_token})
client = heat_client(**kwargs)
return client
def build_option_parser(parser):
"""Hook to add global options"""
parser.add_argument(
'--os-orchestration-api-version',
metavar='<orchestration-api-version>',
default=utils.env(
'OS_ORCHESTRATION_API_VERSION',
default=DEFAULT_ORCHESTRATION_API_VERSION),
help='Orchestration API version, default=' +
DEFAULT_ORCHESTRATION_API_VERSION +
' (Env: OS_ORCHESTRATION_API_VERSION)')
return parser

View File

@ -1,46 +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.
#
"""Orchestration v1 build info action implementations"""
import logging
from osc_lib.command import command
from osc_lib import utils
import six
from heatclient.common import utils as heat_utils
class BuildInfo(command.ShowOne):
"""Retrieve build information."""
log = logging.getLogger(__name__ + ".BuildInfo")
def get_parser(self, prog_name):
parser = super(BuildInfo, self).get_parser(prog_name)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)", parsed_args)
heat_client = self.app.client_manager.orchestration
result = heat_client.build_info.build_info()
formatters = {
'api': heat_utils.json_formatter,
'engine': heat_utils.json_formatter,
}
columns = sorted(list(six.iterkeys(result)))
return columns, utils.get_dict_properties(result, columns,
formatters=formatters)

View File

@ -1,250 +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.
#
# Copyright 2015 IBM Corp.
import logging
import time
from cliff.formatters import base
from osc_lib.command import command
from osc_lib import utils
from heatclient._i18n import _
from heatclient.common import event_utils
from heatclient.common import utils as heat_utils
from heatclient import exc
class ShowEvent(command.ShowOne):
"""Show event details."""
log = logging.getLogger(__name__ + '.ShowEvent')
def get_parser(self, prog_name):
parser = super(ShowEvent, self).get_parser(prog_name)
parser.add_argument(
'stack',
metavar='<stack>',
help=_('Name or ID of stack to show events for')
)
parser.add_argument(
'resource',
metavar='<resource>',
help=_('Name of the resource event belongs to')
)
parser.add_argument(
'event',
metavar='<event>',
help=_('ID of event to display details for')
)
return parser
def take_action(self, parsed_args):
self.log.debug('take_action(%s)', parsed_args)
client = self.app.client_manager.orchestration
fields = {
'stack_id': parsed_args.stack,
'resource_name': parsed_args.resource,
'event_id': parsed_args.event
}
try:
client.stacks.get(parsed_args.stack)
client.resources.get(parsed_args.stack, parsed_args.resource)
event = client.events.get(**fields)
except exc.HTTPNotFound as ex:
raise exc.CommandError(str(ex))
formatters = {
'links': heat_utils.link_formatter,
'resource_properties': heat_utils.json_formatter
}
columns = []
for key in event.to_dict():
columns.append(key)
return columns, utils.get_item_properties(event, columns,
formatters=formatters)
class ListEvent(command.Lister):
"""List events."""
log = logging.getLogger(__name__ + '.ListEvent')
@property
def formatter_default(self):
return 'log'
@property
def formatter_namespace(self):
return 'heatclient.event.formatter.list'
def get_parser(self, prog_name):
parser = super(ListEvent, self).get_parser(prog_name)
parser.add_argument(
'stack',
metavar='<stack>',
help=_('Name or ID of stack to show events for')
)
parser.add_argument(
'--resource',
metavar='<resource>',
help=_('Name of resource to show events for. Note: this cannot '
'be specified with --nested-depth')
)
parser.add_argument(
'--filter',
metavar='<key=value>',
action='append',
help=_('Filter parameters to apply on returned events')
)
parser.add_argument(
'--limit',
metavar='<limit>',
type=int,
help=_('Limit the number of events returned')
)
parser.add_argument(
'--marker',
metavar='<id>',
help=_('Only return events that appear after the given ID')
)
parser.add_argument(
'--nested-depth',
metavar='<depth>',
type=int,
help=_('Depth of nested stacks from which to display events. '
'Note: this cannot be specified with --resource')
)
parser.add_argument(
'--sort',
metavar='<key>[:<direction>]',
action='append',
help=_('Sort output by selected keys and directions (asc or desc) '
'(default: asc). Specify multiple times to sort on '
'multiple keys. Sort key can be: '
'"event_time" (default), "resource_name", "links", '
'"logical_resource_id", "resource_status", '
'"resource_status_reason", "physical_resource_id", or '
'"id". You can leave the key empty and specify ":desc" '
'for sorting by reverse time.')
)
parser.add_argument(
'--follow',
action='store_true',
help=_('Print events until process is halted')
)
return parser
def take_action(self, parsed_args):
self.log.debug('take_action(%s)', parsed_args)
client = self.app.client_manager.orchestration
columns = ['id', 'resource_status', 'resource_status_reason',
'event_time', 'physical_resource_id']
kwargs = {
'resource_name': parsed_args.resource,
'filters': heat_utils.format_parameters(parsed_args.filter),
'sort_dir': 'asc'
}
if parsed_args.resource and parsed_args.nested_depth:
msg = _('--nested-depth cannot be specified with --resource')
raise exc.CommandError(msg)
if parsed_args.nested_depth:
columns.append('stack_name')
nested_depth = parsed_args.nested_depth
else:
nested_depth = 0
if parsed_args.sort:
sorts = []
sort_keys = []
for sort in parsed_args.sort:
if sort.startswith(":"):
sorts.append(":".join(["event_time", sort.lstrip(":")]))
else:
sorts.append(sort)
sort_keys.append(sort.split(":")[0])
kwargs['sort_keys'] = sort_keys
if ":" in parsed_args.sort[0]:
kwargs['sort_dir'] = parsed_args.sort[0].split(":")[1]
if parsed_args.follow:
if parsed_args.formatter != 'log':
msg = _('--follow can only be specified with --format log')
raise exc.CommandError(msg)
marker = parsed_args.marker
try:
event_log_context = heat_utils.EventLogContext()
while True:
events = event_utils.get_events(
client,
stack_id=parsed_args.stack,
event_args=kwargs,
nested_depth=nested_depth,
marker=marker)
if events:
marker = getattr(events[-1], 'id', None)
events_log = heat_utils.event_log_formatter(
events, event_log_context)
self.app.stdout.write(events_log)
self.app.stdout.write('\n')
time.sleep(5)
# this loop never exits
except (KeyboardInterrupt, EOFError): # ctrl-c, ctrl-d
return [], []
events = event_utils.get_events(
client, stack_id=parsed_args.stack, event_args=kwargs,
nested_depth=nested_depth, marker=parsed_args.marker,
limit=parsed_args.limit)
if parsed_args.sort:
events = utils.sort_items(events, ','.join(sorts))
if parsed_args.formatter == 'log':
return [], events
if len(events):
if hasattr(events[0], 'resource_name'):
columns.insert(0, 'resource_name')
columns.append('logical_resource_id')
else:
columns.insert(0, 'logical_resource_id')
return (
columns,
(utils.get_item_properties(s, columns) for s in events)
)
class LogFormatter(base.ListFormatter):
"""A formatter which prints event objects in a log style"""
def add_argument_group(self, parser):
pass
def emit_list(self, column_names, data, stdout, parsed_args):
stdout.write(heat_utils.event_log_formatter(data))
stdout.write('\n')

View File

@ -1,304 +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.
#
"""Orchestration v1 Stack action implementations"""
import logging
from osc_lib.command import command
from osc_lib import exceptions as exc
from osc_lib.i18n import _
from osc_lib import utils
from oslo_serialization import jsonutils
import six
from six.moves.urllib import request
from heatclient.common import format_utils
from heatclient.common import utils as heat_utils
from heatclient import exc as heat_exc
class ResourceShow(command.ShowOne):
"""Display stack resource."""
log = logging.getLogger(__name__ + '.ResourceShowStack')
def get_parser(self, prog_name):
parser = super(ResourceShow, self).get_parser(prog_name)
parser.add_argument(
'stack',
metavar='<stack>',
help=_('Name or ID of stack to query')
)
parser.add_argument(
'resource',
metavar='<resource>',
help=_('Name of resource')
)
parser.add_argument(
'--with-attr',
metavar='<attribute>',
action='append',
help=_('Attribute to show, can be specified multiple times')
)
return parser
def take_action(self, parsed_args):
self.log.debug('take_action(%s)', parsed_args)
client = self.app.client_manager.orchestration
try:
resource = client.resources.get(parsed_args.stack,
parsed_args.resource,
with_attr=parsed_args.with_attr)
except heat_exc.HTTPNotFound:
msg = (_('Stack or resource not found: %(stack)s %(resource)s') %
{'stack': parsed_args.stack,
'resource': parsed_args.resource})
raise exc.CommandError(msg)
return self.dict2columns(resource.to_dict())
class ResourceList(command.Lister):
"""List stack resources."""
log = logging.getLogger(__name__ + '.ResourceListStack')
@property
def formatter_namespace(self):
return 'heatclient.resource.formatter.list'
def get_parser(self, prog_name):
parser = super(ResourceList, self).get_parser(prog_name)
parser.add_argument(
'stack',
metavar='<stack>',
help=_('Name or ID of stack to query')
)
parser.add_argument(
'--long',
action='store_true',
help=_('Enable detailed information presented for each resource '
'in resource list')
)
parser.add_argument(
'-n', '--nested-depth',
metavar='<nested-depth>',
type=int,
help=_('Depth of nested stacks from which to display resources')
)
parser.add_argument(
'--filter',
metavar='<key=value>',
action='append',
help=_('Filter parameters to apply on returned resources based on '
'their name, status, type, action, id and '
'physical_resource_id')
)
return parser
def take_action(self, parsed_args):
self.log.debug('take_action(%s)', parsed_args)
client = self.app.client_manager.orchestration
fields = {
'nested_depth': parsed_args.nested_depth,
'with_detail': parsed_args.long,
'filters': heat_utils.format_parameters(parsed_args.filter),
}
try:
resources = client.resources.list(parsed_args.stack, **fields)
except heat_exc.HTTPNotFound:
msg = _('Stack not found: %s') % parsed_args.stack
raise exc.CommandError(msg)
if parsed_args.formatter == 'dot':
return [], resources
columns = ['physical_resource_id', 'resource_type', 'resource_status',
'updated_time']
if len(resources) >= 1 and not hasattr(resources[0], 'resource_name'):
columns.insert(0, 'logical_resource_id')
else:
columns.insert(0, 'resource_name')
if parsed_args.nested_depth or parsed_args.long:
columns.append('stack_name')
return (
columns,
(utils.get_item_properties(r, columns) for r in resources)
)
class ResourceMetadata(format_utils.JsonFormat):
"""Show resource metadata"""
log = logging.getLogger(__name__ + ".ResourceMetadata")
def get_parser(self, prog_name):
parser = super(ResourceMetadata, self).get_parser(prog_name)
parser.add_argument(
'stack',
metavar='<stack>',
help=_('Stack to display (name or ID)'),
)
parser.add_argument(
'resource',
metavar='<resource>',
help=_('Name of the resource to show the metadata for'))
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)", parsed_args)
heat_client = self.app.client_manager.orchestration
return _resource_metadata(heat_client, parsed_args)
def _resource_metadata(heat_client, args):
fields = {'stack_id': args.stack,
'resource_name': args.resource}
try:
metadata = heat_client.resources.metadata(**fields)
except heat_exc.HTTPNotFound:
raise exc.CommandError(_('Stack %(stack)s or resource %(resource)s '
'not found.') %
{'stack': args.stack,
'resource': args.resource})
data = list(six.itervalues(metadata))
columns = list(six.iterkeys(metadata))
return columns, data
class ResourceSignal(command.Command):
"""Signal a resource with optional data."""
log = logging.getLogger(__name__ + ".ResourceSignal")
def get_parser(self, prog_name):
parser = super(ResourceSignal, self).get_parser(prog_name)
parser.add_argument(
'stack',
metavar='<stack>',
help=_('Name or ID of stack the resource belongs to'),
)
parser.add_argument(
'resource',
metavar='<resource>',
help=_('Name of the resoure to signal'),
)
parser.add_argument(
'--data',
metavar='<data>',
help=_('JSON Data to send to the signal handler')
)
parser.add_argument(
'--data-file',
metavar='<data-file>',
help=_('File containing JSON data to send to the signal handler')
)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)", parsed_args)
heat_client = self.app.client_manager.orchestration
return _resource_signal(heat_client, parsed_args)
def _resource_signal(heat_client, args):
fields = {'stack_id': args.stack,
'resource_name': args.resource}
data = args.data
data_file = args.data_file
if data and data_file:
raise exc.CommandError(_('Should only specify one of data or '
'data-file'))
if data_file:
data_url = heat_utils.normalise_file_path_to_url(data_file)
data = request.urlopen(data_url).read()
if data:
try:
data = jsonutils.loads(data)
except ValueError as ex:
raise exc.CommandError(_('Data should be in JSON format: %s') % ex)
if not isinstance(data, dict):
raise exc.CommandError(_('Data should be a JSON dict'))
fields['data'] = data
try:
heat_client.resources.signal(**fields)
except heat_exc.HTTPNotFound:
raise exc.CommandError(_('Stack %(stack)s or resource %(resource)s '
'not found.') %
{'stack': args.stack,
'resource': args.resource})
class ResourceMarkUnhealthy(command.Command):
"""Set resource's health."""
log = logging.getLogger(__name__ + ".ResourceMarkUnhealthy")
def get_parser(self, prog_name):
parser = super(ResourceMarkUnhealthy, self).get_parser(prog_name)
parser.add_argument(
'stack',
metavar='<stack>',
help=_('Name or ID of stack the resource belongs to')
)
parser.add_argument(
'resource',
metavar='<resource>',
help=_('Name of the resource')
)
parser.add_argument(
'reason',
default="",
nargs='?',
help=_('Reason for state change')
)
parser.add_argument(
'--reset',
default=False,
action="store_true",
help=_('Set the resource as healthy')
)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)", parsed_args)
heat_client = self.app.client_manager.orchestration
fields = {'stack_id': parsed_args.stack,
'resource_name': parsed_args.resource,
'mark_unhealthy': not parsed_args.reset,
'resource_status_reason': parsed_args.reason}
try:
heat_client.resources.mark_unhealthy(**fields)
except heat_exc.HTTPNotFound:
raise exc.CommandError(_('Stack or resource not found: '
'%(id)s %(resource)s') %
{'id': parsed_args.stack,
'resource': parsed_args.resource})

View File

@ -1,132 +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.
#
"""Orchestration v1 resource type implementations"""
import logging
from osc_lib.command import command
from osc_lib import exceptions as exc
from osc_lib.i18n import _
import six
from heatclient.common import format_utils
from heatclient.common import utils as heat_utils
from heatclient import exc as heat_exc
class ResourceTypeShow(format_utils.YamlFormat):
"""Show details and optionally generate a template for a resource type."""
log = logging.getLogger(__name__ + ".ResourceTypeShow")
def get_parser(self, prog_name):
parser = super(ResourceTypeShow,
self).get_parser(prog_name)
parser.add_argument(
'resource_type',
metavar='<resource-type>',
help=_('Resource type to show details for'),
)
parser.add_argument(
'--template-type',
metavar='<template-type>',
help=_('Optional template type to generate, hot or cfn')
)
parser.add_argument(
'--long',
default=False,
action='store_true',
help=_('Show resource type with corresponding description.')
)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)", parsed_args)
if parsed_args.template_type is not None and parsed_args.long:
msg = _('Cannot use --template-type and --long in one time.')
raise exc.CommandError(msg)
heat_client = self.app.client_manager.orchestration
return _show_resourcetype(heat_client, parsed_args)
def _show_resourcetype(heat_client, parsed_args):
try:
if parsed_args.template_type:
template_type = parsed_args.template_type.lower()
if template_type not in ('hot', 'cfn'):
raise exc.CommandError(
_('Template type invalid: %s') % parsed_args.template_type)
fields = {'resource_type': parsed_args.resource_type,
'template_type': template_type}
data = heat_client.resource_types.generate_template(**fields)
else:
data = heat_client.resource_types.get(parsed_args.resource_type,
parsed_args.long)
except heat_exc.HTTPNotFound:
raise exc.CommandError(
_('Resource type not found: %s') % parsed_args.resource_type)
rows = list(six.itervalues(data))
columns = list(six.iterkeys(data))
return columns, rows
class ResourceTypeList(command.Lister):
"""List resource types."""
log = logging.getLogger(__name__ + '.ResourceTypeList')
def get_parser(self, prog_name):
parser = super(ResourceTypeList,
self).get_parser(prog_name)
parser.add_argument(
'--filter',
dest='filter',
metavar='<key=value>',
help=_('Filter parameters to apply on returned resource types. '
'This can be specified multiple times. It can be any of '
'name, version or support_status'),
action='append'
)
parser.add_argument(
'--long',
default=False,
action='store_true',
help=_('Show resource types with corresponding description of '
'each resource type.')
)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)", parsed_args)
heat_client = self.app.client_manager.orchestration
return _list_resourcetypes(heat_client, parsed_args)
def _list_resourcetypes(heat_client, parsed_args):
resource_types = heat_client.resource_types.list(
filters=heat_utils.format_parameters(parsed_args.filter),
with_description=parsed_args.long
)
if parsed_args.long:
columns = ['Resource Type', 'Description']
rows = sorted([r.resource_type, r.description] for r in resource_types)
else:
columns = ['Resource Type']
rows = sorted([r.resource_type] for r in resource_types)
return columns, rows

View File

@ -1,41 +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.
#
"""Orchestration v1 Service action implementations"""
import logging
from osc_lib.command import command
from osc_lib import utils
class ListService(command.Lister):
"""List the Heat engines."""
log = logging.getLogger(__name__ + ".ListService")
def get_parser(self, prog_name):
parser = super(ListService, self).get_parser(prog_name)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)", parsed_args)
heat_client = self.app.client_manager.orchestration
columns = ['Hostname', 'Binary', 'Engine ID', 'Host',
'Topic', 'Updated At', 'Status']
services = heat_client.services.list()
return (
columns,
(utils.get_item_properties(s, columns) for s in services)
)

View File

@ -1,231 +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.
#
"""Orchestration v1 Stack Snapshot implementations."""
import logging
import sys
from osc_lib.command import command
from osc_lib import exceptions as exc
from osc_lib.i18n import _
from osc_lib import utils
import six
from heatclient.common import format_utils
from heatclient import exc as heat_exc
class ListSnapshot(command.Lister):
"""List stack snapshots."""
log = logging.getLogger(__name__ + ".ListSnapshot")
def get_parser(self, prog_name):
parser = super(ListSnapshot, self).get_parser(prog_name)
parser.add_argument(
'stack',
metavar='<stack>',
help=_('Name or ID of stack containing the snapshots')
)
return parser
def take_action(self, parsed_args):
self.log.debug('take_action(%s)', parsed_args)
heat_client = self.app.client_manager.orchestration
return self._list_snapshot(heat_client, parsed_args)
def _list_snapshot(self, heat_client, parsed_args):
fields = {'stack_id': parsed_args.stack}
try:
snapshots = heat_client.stacks.snapshot_list(**fields)
except heat_exc.HTTPNotFound:
raise exc.CommandError(_('Stack not found: %s') %
parsed_args.stack)
columns = ['id', 'name', 'status', 'status_reason', 'creation_time']
return (
columns,
(utils.get_dict_properties(s, columns)
for s in snapshots['snapshots'])
)
class ShowSnapshot(format_utils.YamlFormat):
"""Show stack snapshot."""
log = logging.getLogger(__name__ + ".ShowSnapshot")
def get_parser(self, prog_name):
parser = super(ShowSnapshot, self).get_parser(prog_name)
parser.add_argument(
'stack',
metavar='<stack>',
help=_('Name or ID of stack containing the snapshot')
)
parser.add_argument(
'snapshot',
metavar='<snapshot>',
help=_('ID of the snapshot to show')
)
return parser
def take_action(self, parsed_args):
self.log.debug('take_action(%s)', parsed_args)
heat_client = self.app.client_manager.orchestration
return self._show_snapshot(heat_client, parsed_args.stack,
parsed_args.snapshot)
def _show_snapshot(self, heat_client, stack_id, snapshot_id):
try:
data = heat_client.stacks.snapshot_show(stack_id, snapshot_id)
except heat_exc.HTTPNotFound:
raise exc.CommandError(_('Snapshot ID <%(snapshot_id)s> not found '
'for stack <%(stack_id)s>')
% {'snapshot_id': snapshot_id,
'stack_id': stack_id})
rows = list(six.itervalues(data))
columns = list(six.iterkeys(data))
return columns, rows
class RestoreSnapshot(command.Command):
"""Restore stack snapshot"""
log = logging.getLogger(__name__ + ".RestoreSnapshot")
def get_parser(self, prog_name):
parser = super(RestoreSnapshot, self).get_parser(prog_name)
parser.add_argument(
'stack',
metavar='<stack>',
help=_('Name or ID of stack containing the snapshot')
)
parser.add_argument(
'snapshot',
metavar='<snapshot>',
help=_('ID of the snapshot to restore')
)
return parser
def take_action(self, parsed_args):
self.log.debug('take_action(%s)', parsed_args)
heat_client = self.app.client_manager.orchestration
return self._restore_snapshot(heat_client, parsed_args)
def _restore_snapshot(self, heat_client, parsed_args):
fields = {'stack_id': parsed_args.stack,
'snapshot_id': parsed_args.snapshot}
try:
heat_client.stacks.restore(**fields)
except heat_exc.HTTPNotFound:
raise exc.CommandError(_('Stack %(stack)s or '
'snapshot %(snapshot)s not found.') %
{'stack': parsed_args.stack,
'snapshot': parsed_args.snapshot})
class CreateSnapshot(command.ShowOne):
"""Create stack snapshot."""
log = logging.getLogger(__name__ + ".CreateSnapshot")
def get_parser(self, prog_name):
parser = super(CreateSnapshot, self).get_parser(prog_name)
parser.add_argument(
'stack',
metavar='<stack>',
help=_('Name or ID of stack')
)
parser.add_argument(
'--name',
metavar='<name>',
help=_('Name of snapshot')
)
return parser
def take_action(self, parsed_args):
self.log.debug('take_action(%s)', parsed_args)
heat_client = self.app.client_manager.orchestration
try:
data = heat_client.stacks.snapshot(parsed_args.stack,
parsed_args.name)
except heat_exc.HTTPNotFound:
raise exc.CommandError(_('Stack not found: %s')
% parsed_args.stack)
columns = [
'ID',
'name',
'status',
'status_reason',
'data',
'creation_time'
]
return (columns, utils.get_dict_properties(data, columns))
class DeleteSnapshot(command.Command):
"""Delete stack snapshot."""
log = logging.getLogger(__name__ + ".DeleteSnapshot")
def get_parser(self, prog_name):
parser = super(DeleteSnapshot, self).get_parser(prog_name)
parser.add_argument(
'stack',
metavar='<stack>',
help=_('Name or ID of stack')
)
parser.add_argument(
'snapshot',
metavar='<snapshot>',
help=_('ID of stack snapshot')
)
parser.add_argument(
'-y', '--yes',
action='store_true',
help=_('Skip yes/no prompt (assume yes)')
)
return parser
def take_action(self, parsed_args):
self.log.debug('take_action(%s)', parsed_args)
heat_client = self.app.client_manager.orchestration
msg = ('User did not confirm snapshot delete '
'%sso taking no action.')
try:
if not parsed_args.yes and sys.stdin.isatty():
sys.stdout.write(
_('Are you sure you want to delete the snapshot of this '
'stack [Y/N]?'))
prompt_response = sys.stdin.readline().lower()
if not prompt_response.startswith('y'):
self.log.info(msg, '')
return
except KeyboardInterrupt: # ctrl-c
self.log.info(msg, '(ctrl-c) ')
return
except EOFError: # ctrl-d
self.log.info(msg, '(ctrl-d) ')
return
try:
heat_client.stacks.snapshot_delete(parsed_args.stack,
parsed_args.snapshot)
except heat_exc.HTTPNotFound:
raise exc.CommandError(_('Snapshot ID <%(snapshot_id)s> not found '
'for stack <%(stack_id)s>')
% {'snapshot_id': parsed_args.snapshot,
'stack_id': parsed_args.stack})

View File

@ -1,240 +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.
#
"""Orchestration v1 software config action implementations"""
import logging
from osc_lib.command import command
from osc_lib import exceptions as exc
from osc_lib import utils
import six
from six.moves.urllib import request
import yaml
from heatclient._i18n import _
from heatclient.common import format_utils
from heatclient.common import template_format
from heatclient.common import utils as heat_utils
from heatclient import exc as heat_exc
class DeleteConfig(command.Command):
"""Delete software configs"""
log = logging.getLogger(__name__ + ".DeleteConfig")
def get_parser(self, prog_name):
parser = super(DeleteConfig, self).get_parser(prog_name)
parser.add_argument(
'config',
metavar='<config>',
nargs='+',
help=_('IDs of the software configs to delete')
)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)", parsed_args)
heat_client = self.app.client_manager.orchestration
return _delete_config(heat_client, parsed_args)
def _delete_config(heat_client, args):
failure_count = 0
for config_id in args.config:
try:
heat_client.software_configs.delete(
config_id=config_id)
except Exception as e:
if isinstance(e, heat_exc.HTTPNotFound):
print(_('Software config with ID %s not found') % config_id)
failure_count += 1
continue
if failure_count:
raise exc.CommandError(_('Unable to delete %(count)s of the '
'%(total)s software configs.') %
{'count': failure_count,
'total': len(args.config)})
class ListConfig(command.Lister):
"""List software configs"""
log = logging.getLogger(__name__ + ".ListConfig")
def get_parser(self, prog_name):
parser = super(ListConfig, self).get_parser(prog_name)
parser.add_argument(
'--limit',
metavar='<limit>',
help=_('Limit the number of configs returned')
)
parser.add_argument(
'--marker',
metavar='<id>',
help=_('Return configs that appear after the given config ID')
)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)", parsed_args)
heat_client = self.app.client_manager.orchestration
return _list_config(heat_client, parsed_args)
def _list_config(heat_client, args):
kwargs = {}
if args.limit:
kwargs['limit'] = args.limit
if args.marker:
kwargs['marker'] = args.marker
scs = heat_client.software_configs.list(**kwargs)
columns = ['id', 'name', 'group', 'creation_time']
return (columns, (utils.get_item_properties(s, columns) for s in scs))
class CreateConfig(format_utils.JsonFormat):
"""Create software config"""
log = logging.getLogger(__name__ + ".CreateConfig")
def get_parser(self, prog_name):
parser = super(CreateConfig, self).get_parser(prog_name)
parser.add_argument(
'name',
metavar='<config-name>',
help=_('Name of the software config to create')
)
parser.add_argument(
'--config-file',
metavar='<config-file>',
help=_('Path to JSON/YAML containing map defining '
'<inputs>, <outputs>, and <options>')
)
parser.add_argument(
'--definition-file',
metavar='<destination-file>',
help=_('Path to software config script/data')
)
parser.add_argument(
'--group',
metavar='<group>',
default='Heat::Ungrouped',
help=_('Group name of tool expected by the software config')
)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)", parsed_args)
heat_client = self.app.client_manager.orchestration
return _create_config(heat_client, parsed_args)
def _create_config(heat_client, args):
config = {
'group': args.group,
'config': ''
}
defn = {}
if args.definition_file:
defn_url = heat_utils.normalise_file_path_to_url(
args.definition_file)
defn_raw = request.urlopen(defn_url).read() or '{}'
defn = yaml.load(defn_raw, Loader=template_format.yaml_loader)
config['inputs'] = defn.get('inputs', [])
config['outputs'] = defn.get('outputs', [])
config['options'] = defn.get('options', {})
if args.config_file:
config_url = heat_utils.normalise_file_path_to_url(
args.config_file)
config['config'] = request.urlopen(config_url).read()
# build a mini-template with a config resource and validate it
validate_template = {
'heat_template_version': '2013-05-23',
'resources': {
args.name: {
'type': 'OS::Heat::SoftwareConfig',
'properties': config
}
}
}
heat_client.stacks.validate(template=validate_template)
config['name'] = args.name
sc = heat_client.software_configs.create(**config).to_dict()
rows = list(six.itervalues(sc))
columns = list(six.iterkeys(sc))
return columns, rows
class ShowConfig(format_utils.YamlFormat):
"""Show software config details"""
log = logging.getLogger(__name__ + ".ShowConfig")
def get_parser(self, prog_name):
parser = super(ShowConfig, self).get_parser(prog_name)
parser.add_argument(
'config',
metavar='<config>',
help=_('ID of the config')
)
parser.add_argument(
'--config-only',
default=False,
action="store_true",
help=_('Only display the value of the <config> property.')
)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)", parsed_args)
heat_client = self.app.client_manager.orchestration
return _show_config(heat_client, config_id=parsed_args.config,
config_only=parsed_args.config_only)
def _show_config(heat_client, config_id, config_only):
try:
sc = heat_client.software_configs.get(config_id=config_id)
except heat_exc.HTTPNotFound:
raise exc.CommandError(_('Configuration not found: %s') % config_id)
columns = None
rows = None
if config_only:
print(sc.config)
else:
columns = (
'id',
'name',
'group',
'config',
'inputs',
'outputs',
'options',
'creation_time',
)
rows = utils.get_dict_properties(sc.to_dict(), columns)
return columns, rows

View File

@ -1,356 +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.
#
"""Orchestration v1 Software Deployment action implementations"""
import logging
from osc_lib.command import command
from osc_lib import exceptions as exc
from osc_lib import utils
from oslo_serialization import jsonutils
from heatclient._i18n import _
from heatclient.common import deployment_utils
from heatclient.common import format_utils
from heatclient.common import utils as heat_utils
from heatclient import exc as heat_exc
class CreateDeployment(format_utils.YamlFormat):
"""Create a software deployment."""
log = logging.getLogger(__name__ + '.CreateDeployment')
def get_parser(self, prog_name):
parser = super(CreateDeployment, self).get_parser(prog_name)
parser.add_argument(
'name',
metavar='<deployment-name>',
help=_('Name of the derived config associated with this '
'deployment. This is used to apply a sort order to the '
'list of configurations currently deployed to the server.')
)
parser.add_argument(
'--input-value',
metavar='<key=value>',
action='append',
help=_('Input value to set on the deployment. This can be '
'specified multiple times.')
)
parser.add_argument(
'--action',
metavar='<action>',
default='UPDATE',
help=_('Name of an action for this deployment. This can be a '
'custom action, or one of CREATE, UPDATE, DELETE, SUSPEND, '
'RESUME. Default is UPDATE')
)
parser.add_argument(
'--config',
metavar='<config>',
help=_('ID of the configuration to deploy')
)
parser.add_argument(
'--signal-transport',
metavar='<signal-transport>',
default='TEMP_URL_SIGNAL',
help=_('How the server should signal to heat with the deployment '
'output values. TEMP_URL_SIGNAL will create a Swift '
'TempURL to be signaled via HTTP PUT. ZAQAR_SIGNAL will '
'create a dedicated zaqar queue to be signaled using the '
'provided keystone credentials.NO_SIGNAL will result in '
'the resource going to the COMPLETE state without waiting '
'for any signal')
)
parser.add_argument(
'--container',
metavar='<container>',
help=_('Optional name of container to store TEMP_URL_SIGNAL '
'objects in. If not specified a container will be created '
'with a name derived from the DEPLOY_NAME')
)
parser.add_argument(
'--timeout',
metavar='<timeout>',
type=int,
default=60,
help=_('Deployment timeout in minutes')
)
parser.add_argument(
'--server',
metavar='<server>',
required=True,
help=_('ID of the server being deployed to')
)
return parser
def take_action(self, parsed_args):
self.log.debug('take_action(%s)', parsed_args)
client = self.app.client_manager.orchestration
config = {}
if parsed_args.config:
try:
config = client.software_configs.get(parsed_args.config)
except heat_exc.HTTPNotFound:
msg = (_('Software configuration not found: %s') %
parsed_args.config)
raise exc.CommandError(msg)
derived_params = deployment_utils.build_derived_config_params(
parsed_args.action,
config,
parsed_args.name,
heat_utils.format_parameters(parsed_args.input_value, False),
parsed_args.server,
parsed_args.signal_transport,
signal_id=deployment_utils.build_signal_id(client, parsed_args)
)
derived_config = client.software_configs.create(**derived_params)
sd = client.software_deployments.create(
config_id=derived_config.id,
server_id=parsed_args.server,
action=parsed_args.action,
status='IN_PROGRESS'
)
return zip(*sorted(sd.to_dict().items()))
class DeleteDeployment(command.Command):
"""Delete software deployment(s) and correlative config(s)."""
log = logging.getLogger(__name__ + '.DeleteDeployment')
def get_parser(self, prog_name):
parser = super(DeleteDeployment, self).get_parser(prog_name)
parser.add_argument(
'deployment',
metavar='<deployment>',
nargs='+',
help=_('ID of the deployment(s) to delete.')
)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)", parsed_args)
hc = self.app.client_manager.orchestration
failure_count = 0
for deploy_id in parsed_args.deployment:
try:
sd = hc.software_deployments.get(deployment_id=deploy_id)
hc.software_deployments.delete(
deployment_id=deploy_id)
except Exception as e:
if isinstance(e, heat_exc.HTTPNotFound):
print(_('Deployment with ID %s not found') % deploy_id)
else:
print(_('Deployment with ID %s failed to delete')
% deploy_id)
failure_count += 1
continue
# just try best to delete the corresponding config
try:
config_id = getattr(sd, 'config_id')
hc.software_configs.delete(config_id=config_id)
except Exception:
print(_('Failed to delete the correlative config'
' %(config_id)s of deployment %(deploy_id)s') %
{'config_id': config_id, 'deploy_id': deploy_id})
if failure_count:
raise exc.CommandError(_('Unable to delete %(count)s of the '
'%(total)s deployments.') %
{'count': failure_count,
'total': len(parsed_args.deployment)})
class ListDeployment(command.Lister):
"""List software deployments."""
log = logging.getLogger(__name__ + '.ListDeployment')
def get_parser(self, prog_name):
parser = super(ListDeployment, self).get_parser(prog_name)
parser.add_argument(
'--server',
metavar='<server>',
help=_('ID of the server to fetch deployments for')
)
parser.add_argument(
'--long',
action='store_true',
help=_('List more fields in output')
)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)", parsed_args)
heat_client = self.app.client_manager.orchestration
return _list_deployment(heat_client, args=parsed_args)
def _list_deployment(heat_client, args=None):
kwargs = {'server_id': args.server} if args.server else {}
columns = ['id', 'config_id', 'server_id', 'action', 'status']
if args.long:
columns.append('creation_time')
columns.append('status_reason')
deployments = heat_client.software_deployments.list(**kwargs)
return (
columns,
(utils.get_item_properties(s, columns) for s in deployments)
)
class ShowDeployment(command.ShowOne):
"""Show SoftwareDeployment Details."""
log = logging.getLogger(__name__ + ".ShowSoftwareDeployment")
def get_parser(self, prog_name):
parser = super(ShowDeployment, self).get_parser(prog_name)
parser.add_argument(
'deployment',
metavar='<deployment>',
help=_('ID of the deployment')
)
parser.add_argument(
'--long',
action='store_true',
help=_('Show more fields in output')
)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)", parsed_args)
heat_client = self.app.client_manager.orchestration
try:
data = heat_client.software_deployments.get(
deployment_id=parsed_args.deployment)
except heat_exc.HTTPNotFound:
raise exc.CommandError(
_('Software Deployment not found: %s')
% parsed_args.deployment)
else:
columns = [
'id',
'server_id',
'config_id',
'creation_time',
'updated_time',
'status',
'status_reason',
'input_values',
'action',
]
if parsed_args.long:
columns.append('output_values')
return columns, utils.get_item_properties(data, columns)
class ShowMetadataDeployment(command.Command):
"""Get deployment configuration metadata for the specified server."""
log = logging.getLogger(__name__ + '.ShowMetadataDeployment')
def get_parser(self, prog_name):
parser = super(ShowMetadataDeployment, self).get_parser(prog_name)
parser.add_argument(
'server',
metavar='<server>',
help=_('ID of the server to fetch deployments for')
)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)", parsed_args)
heat_client = self.app.client_manager.orchestration
md = heat_client.software_deployments.metadata(
server_id=parsed_args.server)
print(jsonutils.dumps(md, indent=2))
class ShowOutputDeployment(command.Command):
"""Show a specific deployment output."""
log = logging.getLogger(__name__ + '.ShowOutputDeployment')
def get_parser(self, prog_name):
parser = super(ShowOutputDeployment, self).get_parser(prog_name)
parser.add_argument(
'deployment',
metavar='<deployment>',
help=_('ID of deployment to show the output for')
)
parser.add_argument(
'output',
metavar='<output-name>',
nargs='?',
default=None,
help=_('Name of an output to display')
)
parser.add_argument(
'--all',
default=False,
action='store_true',
help=_('Display all deployment outputs')
)
parser.add_argument(
'--long',
action='store_true',
default=False,
help='Show full deployment logs in output',
)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)", parsed_args)
heat_client = self.app.client_manager.orchestration
if (not parsed_args.all and parsed_args.output is None or
parsed_args.all and parsed_args.output is not None):
raise exc.CommandError(
_('Error: either %(output)s or %(all)s argument is needed.')
% {'output': '<output-name>', 'all': '--all'})
try:
sd = heat_client.software_deployments.get(
deployment_id=parsed_args.deployment)
except heat_exc.HTTPNotFound:
raise exc.CommandError(_('Deployment not found: %s')
% parsed_args.deployment)
outputs = sd.output_values
if outputs:
if parsed_args.all:
print('output_values:\n')
for k in outputs.keys():
format_utils.print_software_deployment_output(
data=outputs, name=k, long=parsed_args.long)
else:
if parsed_args.output not in outputs:
msg = (_('Output %(output)s does not exist in deployment'
' %(deployment)s')
% {'output': parsed_args.output,
'deployment': parsed_args.deployment})
raise exc.CommandError(msg)
else:
print('output_value:\n')
format_utils.print_software_deployment_output(
data=outputs, name=parsed_args.output)

File diff suppressed because it is too large Load Diff

View File

@ -1,136 +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 collections
from osc_lib.command import command
from heatclient._i18n import _
from heatclient.common import format_utils
from heatclient import exc
class ListStackFailures(command.Command):
"""Show information about failed stack resources."""
def take_action(self, parsed_args):
self.heat_client = self.app.client_manager.orchestration
failures = self._build_failed_resources(parsed_args.stack)
deployment_failures = self._build_software_deployments(failures)
self._print_failures(failures, deployment_failures,
long=parsed_args.long)
def get_parser(self, prog_name):
parser = super(ListStackFailures, self).get_parser(prog_name)
parser.add_argument(
'stack',
metavar='<stack>',
help=_('Stack to display (name or ID)'),
)
parser.add_argument(
'--long',
action='store_true',
default=False,
help=_('Show full deployment logs in output'),
)
return parser
def _build_failed_resources(self, stack):
"""List information about FAILED stack resources.
Failed resources are added by recursing from the top level stack into
failed nested stack resources. A failed nested stack resource is only
added to the failed list if it contains no failed resources.
"""
s = self.heat_client.stacks.get(stack)
if s.status != 'FAILED':
return []
resources = self.heat_client.resources.list(s.id)
failures = collections.OrderedDict()
self._append_failed_resources(failures, resources, [s.stack_name])
return failures
def _append_failed_resources(self, failures, resources, resource_path):
"""Recursively build list of failed resources."""
appended = False
for r in resources:
if not r.resource_status.endswith('FAILED'):
continue
# determine if this resources is a nested stack
links_rel = list([l['rel'] for l in r.links])
is_nested = 'nested' in links_rel
nested_appended = False
next_resource_path = list(resource_path)
next_resource_path.append(r.resource_name)
if is_nested:
try:
nested_resources = self.heat_client.resources.list(
r.physical_resource_id)
nested_appended = self._append_failed_resources(
failures, nested_resources, next_resource_path)
except exc.HTTPNotFound:
# there is a failed resource but no stack
pass
if not nested_appended:
failures['.'.join(next_resource_path)] = r
appended = True
return appended
def _build_software_deployments(self, resources):
"""Build a dict of software deployments from the supplied resources.
The key is the deployment ID.
"""
df = {}
if not resources:
return df
for r in resources.values():
if r.resource_type not in ('OS::Heat::StructuredDeployment',
'OS::Heat::SoftwareDeployment'):
continue
try:
sd = self.heat_client.software_deployments.get(
deployment_id=r.physical_resource_id)
df[r.physical_resource_id] = sd
except exc.HTTPNotFound:
pass
return df
def _print_failures(self, failures, deployment_failures, long=False):
"""Print failed resources.
If the resource is a deployment resource, look up the deployment and
print deploy_stdout and deploy_stderr.
"""
out = self.app.stdout
if not failures:
return
for k, f in failures.items():
out.write('%s:\n' % k)
out.write(' resource_type: %s\n' % f.resource_type)
out.write(' physical_resource_id: %s\n' %
f.physical_resource_id)
out.write(' status: %s\n' % f.resource_status)
reason = format_utils.indent_and_truncate(
f.resource_status_reason,
spaces=4,
truncate=not long,
truncate_prefix='...\n')
out.write(' status_reason: |\n%s\n' % reason)
df = deployment_failures.get(f.physical_resource_id)
if df:
for output in ('deploy_stdout', 'deploy_stderr'):
format_utils.print_software_deployment_output(
data=df.output_values, name=output, long=long, out=out)

View File

@ -1,174 +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.
#
# Copyright 2015 IBM Corp.
import logging
from osc_lib.command import command
from osc_lib import utils
import six
from heatclient._i18n import _
from heatclient.common import format_utils
from heatclient.common import http
from heatclient.common import template_utils
from heatclient.common import utils as heat_utils
from heatclient import exc
class VersionList(command.Lister):
"""List the available template versions."""
log = logging.getLogger(__name__ + '.VersionList')
def take_action(self, parsed_args):
self.log.debug('take_action(%s)', parsed_args)
client = self.app.client_manager.orchestration
versions = client.template_versions.list()
try:
versions[1].aliases
def format_alias(aliases):
return ','.join(aliases)
fields = ['Version', 'Type', 'Aliases']
formatters = {'Aliases': format_alias}
except AttributeError:
fields = ['Version', 'Type']
formatters = None
items = (utils.get_item_properties(s, fields,
formatters=formatters)
for s in versions)
return (fields, items)
class FunctionList(command.Lister):
"""List the available functions."""
log = logging.getLogger(__name__ + '.FunctionList')
def get_parser(self, prog_name):
parser = super(FunctionList, self).get_parser(prog_name)
parser.add_argument(
'template_version',
metavar='<template-version>',
help=_('Template version to get the functions for')
)
parser.add_argument(
'--with_conditions',
default=False,
action='store_true',
help=_('Show condition functions for template.')
)
return parser
def take_action(self, parsed_args):
self.log.debug('take_action(%s)', parsed_args)
client = self.app.client_manager.orchestration
version = parsed_args.template_version
try:
functions = client.template_versions.get(
version, with_condition_func=parsed_args.with_conditions)
except exc.HTTPNotFound:
msg = _('Template version not found: %s') % version
raise exc.CommandError(msg)
fields = ['Functions', 'Description']
return (
fields,
(utils.get_item_properties(s, fields) for s in functions)
)
class Validate(format_utils.YamlFormat):
"""Validate a template"""
log = logging.getLogger(__name__ + ".Validate")
def get_parser(self, prog_name):
parser = super(Validate, self).get_parser(prog_name)
parser.add_argument(
'-e', '--environment',
metavar='<environment>',
action='append',
help=_('Path to the environment. Can be specified multiple times')
)
parser.add_argument(
'--show-nested',
action='store_true',
help=_('Resolve parameters from nested templates as well')
)
parser.add_argument(
'--parameter',
metavar='<key=value>',
action='append',
help=_('Parameter values used to create the stack. This can be '
'specified multiple times')
)
parser.add_argument(
'--ignore-errors',
metavar='<error1,error2,...>',
help=_('List of heat errors to ignore')
)
parser.add_argument(
'-t', '--template',
metavar='<template>',
required=True,
help=_('Path to the template')
)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)", parsed_args)
heat_client = self.app.client_manager.orchestration
return _validate(heat_client, parsed_args)
def _validate(heat_client, args):
tpl_files, template = template_utils.process_template_path(
args.template,
object_request=http.authenticated_fetcher(heat_client))
env_files_list = []
env_files, env = template_utils.process_multiple_environments_and_files(
env_paths=args.environment, env_list_tracker=env_files_list)
fields = {
'template': template,
'parameters': heat_utils.format_parameters(args.parameter),
'files': dict(list(tpl_files.items()) + list(env_files.items())),
'environment': env,
}
if args.ignore_errors:
fields['ignore_errors'] = args.ignore_errors
# If one or more environments is found, pass the listing to the server
if env_files_list:
fields['environment_files'] = env_files_list
if args.show_nested:
fields['show_nested'] = args.show_nested
validation = heat_client.stacks.validate(**fields)
data = list(six.itervalues(validation))
columns = list(six.iterkeys(validation))
return columns, data

View File

@ -1,619 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Command-line interface to the Heat API.
"""
from __future__ import print_function
import argparse
import logging
import sys
from keystoneauth1.identity import generic
from keystoneauth1 import session as kssession
from oslo_utils import encodeutils
from oslo_utils import importutils
import six
import heatclient
from heatclient._i18n import _
from heatclient import client as heat_client
from heatclient.common import utils
from heatclient import exc
osprofiler_profiler = importutils.try_import("osprofiler.profiler")
class HeatShell(object):
def _append_global_identity_args(self, parser):
# FIXME(gyee): these are global identity (Keystone) arguments which
# should be consistent and shared by all service clients. Therefore,
# they should be provided by python-keystoneclient. We will need to
# refactor this code once this functionality is available in
# python-keystoneclient.
parser.add_argument(
'-k', '--insecure', default=False, action='store_true',
help=_('Explicitly allow heatclient to perform '
'\"insecure SSL\" (https) requests. '
'The server\'s certificate will not be verified '
'against any certificate authorities. '
'This option should be used with caution.'))
parser.add_argument(
'--os-cert',
default=utils.env('OS_CERT'),
help=_('Path of certificate file to use in SSL connection. '
'This file can optionally be prepended with '
'the private key.'))
# for backward compatibility only
parser.add_argument('--cert-file',
dest='os_cert',
help=_('DEPRECATED! Use %(arg)s.') %
{'arg': '--os-cert'})
parser.add_argument('--os-key',
default=utils.env('OS_KEY'),
help=_('Path of client key to use in SSL '
'connection. This option is not necessary '
'if your key is prepended to your cert '
'file.'))
parser.add_argument('--key-file',
dest='os_key',
help=_('DEPRECATED! Use %(arg)s.') %
{'arg': '--os-key'})
parser.add_argument('--os-cacert',
metavar='<ca-certificate-file>',
dest='os_cacert',
default=utils.env('OS_CACERT'),
help=_('Path of CA TLS certificate(s) used to '
'verify the remote server\'s certificate. '
'Without this option glance looks for the '
'default system CA certificates.'))
parser.add_argument('--ca-file',
dest='os_cacert',
help=_('DEPRECATED! Use %(arg)s.') %
{'arg': '--os-cacert'})
parser.add_argument('--os-username',
default=utils.env('OS_USERNAME'),
help=_('Defaults to %(value)s.') % {
'value': 'env[OS_USERNAME]'
})
parser.add_argument('--os_username',
help=argparse.SUPPRESS)
parser.add_argument('--os-user-id',
default=utils.env('OS_USER_ID'),
help=_('Defaults to %(value)s.') % {
'value': 'env[OS_USER_ID]'
})
parser.add_argument('--os_user_id',
help=argparse.SUPPRESS)
parser.add_argument('--os-user-domain-id',
default=utils.env('OS_USER_DOMAIN_ID'),
help=_('Defaults to %(value)s.') % {
'value': 'env[OS_USER_DOMAIN_ID]'
})
parser.add_argument('--os_user_domain_id',
help=argparse.SUPPRESS)
parser.add_argument('--os-user-domain-name',
default=utils.env('OS_USER_DOMAIN_NAME'),
help=_('Defaults to %(value)s.') % {
'value': 'env[OS_USER_DOMAIN_NAME]'
})
parser.add_argument('--os_user_domain_name',
help=argparse.SUPPRESS)
parser.add_argument('--os-project-id',
default=utils.env('OS_PROJECT_ID'),
help=(_('Another way to specify tenant ID. '
'This option is mutually exclusive with '
'%(arg)s. Defaults to %(value)s.') %
{
'arg': '--os-tenant-id',
'value': 'env[OS_PROJECT_ID]'}))
parser.add_argument('--os_project_id',
help=argparse.SUPPRESS)
parser.add_argument('--os-project-name',
default=utils.env('OS_PROJECT_NAME'),
help=(_('Another way to specify tenant name. '
'This option is mutually exclusive with '
'%(arg)s. Defaults to %(value)s.') %
{
'arg': '--os-tenant-name',
'value': 'env[OS_PROJECT_NAME]'}))
parser.add_argument('--os_project_name',
help=argparse.SUPPRESS)
parser.add_argument('--os-project-domain-id',
default=utils.env('OS_PROJECT_DOMAIN_ID'),
help=_('Defaults to %(value)s.') % {
'value': 'env[OS_PROJECT_DOMAIN_ID]'
})
parser.add_argument('--os_project_domain_id',
help=argparse.SUPPRESS)
parser.add_argument('--os-project-domain-name',
default=utils.env('OS_PROJECT_DOMAIN_NAME'),
help=_('Defaults to %(value)s.') % {
'value': 'env[OS_PROJECT_DOMAIN_NAME]'
})
parser.add_argument('--os_project_domain_name',
help=argparse.SUPPRESS)
parser.add_argument('--os-password',
default=utils.env('OS_PASSWORD'),
help=_('Defaults to %(value)s.') % {
'value': 'env[OS_PASSWORD]'
})
parser.add_argument('--os_password',
help=argparse.SUPPRESS)
parser.add_argument('--os-tenant-id',
default=utils.env('OS_TENANT_ID'),
help=_('Defaults to %(value)s.') % {
'value': 'env[OS_TENANT_ID]'
})
parser.add_argument('--os_tenant_id',
default=utils.env('OS_TENANT_ID'),
help=argparse.SUPPRESS)
parser.add_argument('--os-tenant-name',
default=utils.env('OS_TENANT_NAME'),
help=_('Defaults to %(value)s.') % {
'value': 'env[OS_TENANT_NAME]'
})
parser.add_argument('--os_tenant_name',
default=utils.env('OS_TENANT_NAME'),
help=argparse.SUPPRESS)
parser.add_argument('--os-auth-url',
default=utils.env('OS_AUTH_URL'),
help=_('Defaults to %(value)s.') % {
'value': 'env[OS_AUTH_URL]'
})
parser.add_argument('--os_auth_url',
help=argparse.SUPPRESS)
parser.add_argument('--os-region-name',
default=utils.env('OS_REGION_NAME'),
help=_('Defaults to %(value)s.') % {
'value': 'env[OS_REGION_NAME]'
})
parser.add_argument('--os_region_name',
help=argparse.SUPPRESS)
parser.add_argument('--os-auth-token',
default=utils.env('OS_AUTH_TOKEN'),
help=_('Defaults to %(value)s.') % {
'value': 'env[OS_AUTH_TOKEN]'
})
parser.add_argument('--os_auth_token',
help=argparse.SUPPRESS)
parser.add_argument('--os-service-type',
default=utils.env('OS_SERVICE_TYPE'),
help=_('Defaults to %(value)s.') % {
'value': 'env[OS_SERVICE_TYPE]'
})
parser.add_argument('--os_service_type',
help=argparse.SUPPRESS)
parser.add_argument('--os-endpoint-type',
default=utils.env('OS_ENDPOINT_TYPE'),
help=_('Defaults to %(value)s.') % {
'value': 'env[OS_ENDPOINT_TYPE]'
})
parser.add_argument('--os_endpoint_type',
help=argparse.SUPPRESS)
def get_base_parser(self):
parser = argparse.ArgumentParser(
prog='heat',
description=__doc__.strip(),
epilog=_('See "%(arg)s" for help on a specific command.') % {
'arg': 'heat help COMMAND'
},
add_help=False,
formatter_class=HelpFormatter,
)
# Global arguments
parser.add_argument('-h', '--help',
action='store_true',
help=argparse.SUPPRESS)
parser.add_argument('--version',
action='version',
version=heatclient.__version__,
help=_("Shows the client version and exits."))
parser.add_argument('-d', '--debug',
default=bool(utils.env('HEATCLIENT_DEBUG')),
action='store_true',
help=_('Defaults to %(value)s.') % {
'value': 'env[HEATCLIENT_DEBUG]'
})
parser.add_argument('-v', '--verbose',
default=False, action="store_true",
help=_("Print more verbose output."))
parser.add_argument('--api-timeout',
help=_('Number of seconds to wait for an '
'API response, '
'defaults to system socket timeout'))
# os-no-client-auth tells heatclient to use token, instead of
# env[OS_AUTH_URL]
parser.add_argument('--os-no-client-auth',
default=utils.env('OS_NO_CLIENT_AUTH'),
action='store_true',
help=(_("Do not contact keystone for a token. "
"Defaults to %(value)s.") %
{'value': 'env[OS_NO_CLIENT_AUTH]'}))
parser.add_argument('--heat-url',
default=utils.env('HEAT_URL'),
help=_('Defaults to %(value)s.') % {
'value': 'env[HEAT_URL]'
})
parser.add_argument('--heat_url',
help=argparse.SUPPRESS)
parser.add_argument('--heat-api-version',
default=utils.env('HEAT_API_VERSION', default='1'),
help=_('Defaults to %(value)s or 1.') % {
'value': 'env[HEAT_API_VERSION]'
})
parser.add_argument('--heat_api_version',
help=argparse.SUPPRESS)
# This unused option should remain so that scripts that
# use it do not break. It is suppressed so it will not
# appear in the help.
parser.add_argument('-t', '--token-only',
default=bool(False),
action='store_true',
help=argparse.SUPPRESS)
parser.add_argument('--include-password',
default=bool(utils.env('HEAT_INCLUDE_PASSWORD')),
action='store_true',
help=_('Send %(arg1)s and %(arg2)s to heat.') % {
'arg1': 'os-username',
'arg2': 'os-password'
})
# FIXME(gyee): this method should come from python-keystoneclient.
# Will refactor this code once it is available.
# https://bugs.launchpad.net/python-keystoneclient/+bug/1332337
self._append_global_identity_args(parser)
if osprofiler_profiler:
parser.add_argument(
'--profile',
metavar='HMAC_KEY',
help=_('HMAC key to use for encrypting context data '
'for performance profiling of operation. '
'This key should be the value of HMAC key '
'configured in osprofiler middleware in heat, '
'it is specified in the paste configuration '
'(/etc/heat/api-paste.ini). Without the key, '
'profiling will not be triggered '
'even if osprofiler is enabled on server side.'))
return parser
def get_subcommand_parser(self, version):
parser = self.get_base_parser()
self.subcommands = {}
subparsers = parser.add_subparsers(metavar='<subcommand>')
submodule = importutils.import_versioned_module('heatclient',
version, 'shell')
self._find_actions(subparsers, submodule)
self._find_actions(subparsers, self)
self._add_bash_completion_subparser(subparsers)
return parser
def _add_bash_completion_subparser(self, subparsers):
subparser = subparsers.add_parser(
'bash_completion',
add_help=False,
formatter_class=HelpFormatter
)
self.subcommands['bash_completion'] = subparser
subparser.set_defaults(func=self.do_bash_completion)
def _find_actions(self, subparsers, actions_module):
for attr in (a for a in dir(actions_module) if a.startswith('do_')):
# I prefer to be hyphen-separated instead of underscores.
command = attr[3:].replace('_', '-')
callback = getattr(actions_module, attr)
desc = callback.__doc__ or ''
help = desc.strip().split('\n')[0]
arguments = getattr(callback, 'arguments', [])
subparser = subparsers.add_parser(command,
help=help,
description=desc,
add_help=False,
formatter_class=HelpFormatter)
subparser.add_argument('-h', '--help',
action='help',
help=argparse.SUPPRESS)
self.subcommands[command] = subparser
for (args, kwargs) in arguments:
subparser.add_argument(*args, **kwargs)
subparser.set_defaults(func=callback)
def _setup_logging(self, debug):
log_lvl = logging.DEBUG if debug else logging.WARNING
logging.basicConfig(
format="%(levelname)s (%(module)s) %(message)s",
level=log_lvl)
logging.getLogger('iso8601').setLevel(logging.WARNING)
logging.getLogger('urllib3.connectionpool').setLevel(logging.WARNING)
def _setup_verbose(self, verbose):
if verbose:
exc.verbose = 1
def _get_keystone_session(self, **kwargs):
# first create a Keystone session
cacert = kwargs.pop('cacert', None)
cert = kwargs.pop('cert', None)
key = kwargs.pop('key', None)
insecure = kwargs.pop('insecure', False)
timeout = kwargs.pop('timeout', None)
verify = kwargs.pop('verify', None)
if verify is None:
if insecure:
verify = False
else:
# TODO(gyee): should we do
# heatclient.common.http.get_system_ca_fle()?
verify = cacert or True
if cert and key:
# passing cert and key together is deprecated in favour of the
# requests lib form of having the cert and key as a tuple
cert = (cert, key)
return kssession.Session(verify=verify, cert=cert, timeout=timeout)
def main(self, argv):
# Parse args once to find version
parser = self.get_base_parser()
(options, args) = parser.parse_known_args(argv)
self._setup_logging(options.debug)
self._setup_verbose(options.verbose)
# build available subcommands based on version
api_version = options.heat_api_version
subcommand_parser = self.get_subcommand_parser(api_version)
self.parser = subcommand_parser
# Handle top-level --help/-h before attempting to parse
# a command off the command line
if not args and options.help or not argv:
self.do_help(options)
return 0
# Parse args again and call whatever callback was selected
args = subcommand_parser.parse_args(argv)
# Short-circuit and deal with help command right away.
if args.func == self.do_help:
self.do_help(args)
return 0
elif args.func == self.do_bash_completion:
self.do_bash_completion(args)
return 0
if not args.os_username and not args.os_auth_token:
raise exc.CommandError(_("You must provide a username via either "
"--os-username or env[OS_USERNAME] "
"or a token via --os-auth-token or "
"env[OS_AUTH_TOKEN]"))
if not args.os_password and not args.os_auth_token:
raise exc.CommandError(_("You must provide a password via either "
"--os-password or env[OS_PASSWORD] "
"or a token via --os-auth-token or "
"env[OS_AUTH_TOKEN]"))
if args.os_no_client_auth:
if not args.heat_url:
raise exc.CommandError(_("If you specify --os-no-client-auth "
"you must also specify a Heat API "
"URL via either --heat-url or "
"env[HEAT_URL]"))
else:
# Tenant/project name or ID is needed to make keystoneclient
# retrieve a service catalog, it's not required if
# os_no_client_auth is specified, neither is the auth URL
if not (args.os_tenant_id or args.os_tenant_name or
args.os_project_id or args.os_project_name):
raise exc.CommandError(
_("You must provide a tenant id via either "
"--os-tenant-id or env[OS_TENANT_ID] or a tenant name "
"via either --os-tenant-name or env[OS_TENANT_NAME] "
"or a project id via either --os-project-id or "
"env[OS_PROJECT_ID] or a project name via "
"either --os-project-name or env[OS_PROJECT_NAME]"))
if not args.os_auth_url:
raise exc.CommandError(_("You must provide an auth url via "
"either --os-auth-url or via "
"env[OS_AUTH_URL]"))
kwargs = {
'insecure': args.insecure,
'cacert': args.os_cacert,
'cert': args.os_cert,
'key': args.os_key,
'timeout': args.api_timeout
}
service_type = args.os_service_type or 'orchestration'
if args.os_no_client_auth:
# Do not use session since no_client_auth means using heat to
# to authenticate
kwargs = {
'username': args.os_username,
'password': args.os_password,
'auth_url': args.os_auth_url,
'token': args.os_auth_token,
'include_pass': args.include_password,
'insecure': args.insecure,
'timeout': args.api_timeout,
'endpoint': args.heat_url
}
else:
keystone_session = self._get_keystone_session(**kwargs)
endpoint_type = args.os_endpoint_type or 'publicURL'
if args.os_auth_token:
kwargs = {
'token': args.os_auth_token,
'auth_url': args.os_auth_url
}
keystone_auth = generic.Token(**kwargs)
else:
project_id = args.os_project_id or args.os_tenant_id
project_name = args.os_project_name or args.os_tenant_name
kwargs = {
'username': args.os_username,
'user_id': args.os_user_id,
'user_domain_id': args.os_user_domain_id,
'user_domain_name': args.os_user_domain_name,
'password': args.os_password,
'auth_url': args.os_auth_url,
'project_id': project_id,
'project_name': project_name,
'project_domain_id': args.os_project_domain_id,
'project_domain_name': args.os_project_domain_name,
}
keystone_auth = generic.Password(**kwargs)
kwargs = {
'auth_url': args.os_auth_url,
'session': keystone_session,
'auth': keystone_auth,
'service_type': service_type,
'endpoint_type': endpoint_type,
'region_name': args.os_region_name,
'username': args.os_username,
'password': args.os_password,
'include_pass': args.include_password,
'endpoint_override': args.heat_url,
}
client = heat_client.Client(api_version, **kwargs)
profile = osprofiler_profiler and options.profile
if profile:
osprofiler_profiler.init(options.profile)
args.func(client, args)
if profile:
trace_id = osprofiler_profiler.get().get_base_id()
print(_("Trace ID: %s") % trace_id)
print(_("To display trace use next command:\n"
"osprofiler trace show --html %s ") % trace_id)
def do_bash_completion(self, args):
"""Prints all of the commands and options to stdout.
The heat.bash_completion script doesn't have to hard code them.
"""
commands = set()
options = set()
for sc_str, sc in self.subcommands.items():
commands.add(sc_str)
for option in list(sc._optionals._option_string_actions):
options.add(option)
commands.remove('bash-completion')
commands.remove('bash_completion')
print(' '.join(commands | options))
@utils.arg('command', metavar='<subcommand>', nargs='?',
help=_('Display help for <subcommand>.'))
def do_help(self, args):
"""Display help about this program or one of its subcommands."""
if getattr(args, 'command', None):
if args.command in self.subcommands:
self.subcommands[args.command].print_help()
else:
raise exc.CommandError("'%s' is not a valid subcommand" %
args.command)
else:
self.parser.print_help()
class HelpFormatter(argparse.HelpFormatter):
def start_section(self, heading):
# Title-case the headings
heading = '%s%s' % (heading[0].upper(), heading[1:])
super(HelpFormatter, self).start_section(heading)
def main(args=None):
try:
if args is None:
args = sys.argv[1:]
HeatShell().main(args)
except KeyboardInterrupt:
print(_("... terminating heat client"), file=sys.stderr)
sys.exit(130)
except Exception as e:
if '--debug' in args or '-d' in args:
raise
else:
print(encodeutils.safe_encode(six.text_type(e)), file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()

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.
import os
from tempest.lib.cli import base
class ClientTestBase(base.ClientTestBase):
"""This is a first pass at a simple read only python-heatclient test.
This only exercises client commands that are read only.
This should test commands:
* as a regular user
* as a admin user
* with and without optional parameters
* initially just check return codes, and later test command outputs
"""
def _get_clients(self):
cli_dir = os.environ.get(
'OS_HEATCLIENT_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_TENANT_NAME'),
uri=os.environ.get('OS_AUTH_URL'),
cli_dir=cli_dir)
def heat(self, *args, **kwargs):
return self.clients.heat(*args, **kwargs)

View File

@ -1,14 +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.
HEAT_MINIMAL_HOT_TEMPLATE_URL = 'https://raw.githubusercontent.com/openstack/python-heatclient/acb378364c96f9c5d46d8d4095d69a81fe91727a/heatclient/tests/functional/templates/heat_minimal_hot.yaml' # noqa

View File

@ -1,50 +0,0 @@
#!/bin/bash -xe
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
# This script is executed inside post_test_hook function in devstack gate.
function generate_testr_results {
if [ -f .testrepository/0 ]; then
sudo .tox/functional/bin/testr last --subunit > $WORKSPACE/testrepository.subunit
sudo mv $WORKSPACE/testrepository.subunit $BASE/logs/testrepository.subunit
sudo /usr/os-testr-env/bin/subunit2html $BASE/logs/testrepository.subunit $BASE/logs/testr_results.html
sudo gzip -9 $BASE/logs/testrepository.subunit
sudo gzip -9 $BASE/logs/testr_results.html
sudo chown jenkins:jenkins $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz
sudo chmod a+r $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz
fi
}
export HEATCLIENT_DIR="$BASE/new/python-heatclient"
# Get admin credentials
cd $BASE/new/devstack
source openrc admin admin
# Go to the heatclient dir
cd $HEATCLIENT_DIR
sudo chown -R jenkins:stack $HEATCLIENT_DIR
# Run tests
echo "Running heatclient functional test suite"
set +e
# Preserve env for OS_ credentials
sudo -E -H -u jenkins tox -efunctional
EXIT_CODE=$?
set -e
# Collect and parse result
generate_testr_results
exit $EXIT_CODE

View File

@ -1,116 +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 six
from tempest.lib.cli import base
from tempest.lib.cli import output_parser
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_HEATCLIENT_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_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 get_template_path(self, templ_name):
return os.path.join(os.path.dirname(os.path.realpath(__file__)),
'../../templates/%s' % templ_name)
def show_to_dict(self, output):
obj = {}
items = self.parser.listing(output)
for item in items:
obj[item['Field']] = six.text_type(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 list_to_dict(self, output, id):
obj = {}
items = self.parser.listing(output)
for item in items:
if item['ID'] == id:
obj = item
break
return dict((self._key_name(k), v) for k, v in obj.items())
def _stack_create(self, name, template, parameters=[], wait=True):
cmd = 'stack create ' + name
if template:
cmd += ' -t ' + template
if wait:
cmd += ' --wait'
for parameter in parameters:
cmd += ' --parameter ' + parameter
stack_raw = self.openstack(cmd)
stack = self.show_to_dict(stack_raw)
self.addCleanup(self._stack_delete, stack['id'])
return stack
def _stack_delete(self, id, wait=False):
cmd = 'stack delete ' + id + ' --yes'
if wait:
cmd += ' --wait'
if id in self.openstack('stack list --short'):
self.openstack(cmd)
def _stack_suspend(self, id, wait=True):
cmd = 'stack suspend ' + id
if wait:
cmd += ' --wait'
stack_raw = self.openstack(cmd)
stack = self.list_to_dict(stack_raw, id)
return stack
def _stack_resume(self, id, wait=True):
cmd = 'stack resume ' + id
if wait:
cmd += ' --wait'
stack_raw = self.openstack(cmd)
stack = self.list_to_dict(stack_raw, id)
return stack
def _stack_snapshot_create(self, id, name):
cmd = 'stack snapshot create ' + id + ' --name ' + name
snapshot_raw = self.openstack(cmd)
snapshot = self.show_to_dict(snapshot_raw)
self.addCleanup(self._stack_snapshot_delete, id, snapshot['id'])
return snapshot
def _stack_snapshot_delete(self, id, snapshot_id):
cmd = 'stack snapshot delete ' + id + ' ' + snapshot_id
if snapshot_id in self.openstack('stack snapshot list ' + id):
self.openstack(cmd)
def _stack_snapshot_restore(self, id, snapshot_id):
cmd = 'stack snapshot restore ' + id + ' ' + snapshot_id
self.openstack(cmd)

View File

@ -1,135 +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 tempest.lib import exceptions
from heatclient.tests.functional.osc.v1 import base
class SimpleReadOnlyOpenStackClientTest(base.OpenStackClientTestBase):
"""Basic, read-only tests for Openstack CLI client heat plugin.
Basic smoke test for the openstack CLI commands which do not require
creating or modifying stacks.
"""
def test_openstack_fake_action(self):
self.assertRaises(exceptions.CommandFailed,
self.openstack,
'this-does-not-exist')
# Empty list commands
def test_openstack_empty_lists(self):
cmds = [
'software config',
'software deployment',
'stack',
]
for cmd in cmds:
self.openstack(cmd + ' list')
# Stack not found commands
def test_openstack_stack_not_found(self):
cmds = [
'stack abandon',
'stack check',
'stack output list',
'stack resume',
'stack show',
'stack snapshot list',
'stack suspend',
'stack template show',
'stack cancel'
]
for cmd in cmds:
err = self.assertRaises(exceptions.CommandFailed,
self.openstack,
cmd + ' I-AM-NOT-FOUND')
self.assertIn('Stack not found: I-AM-NOT-FOUND', str(err))
def test_openstack_stack_list_debug(self):
self.openstack('stack list', flags='--debug')
def test_openstack_stack_list_property(self):
self.openstack('stack list --property id=123')
def test_openstack_help_cmd(self):
help_text = self.openstack('help stack list')
lines = help_text.split('\n')
self.assertFirstLineStartsWith(lines, 'usage: openstack stack list')
def test_openstack_version(self):
self.openstack('', flags='--version')
def test_openstack_template_version_list(self):
ret = self.openstack('orchestration template version list')
tmpl_types = self.parser.listing(ret)
self.assertTableStruct(tmpl_types, ['Version', 'Type'])
def test_openstack_template_function_list(self):
ret = self.openstack('orchestration template function list '
'heat_template_version.2015-10-15')
tmpl_functions = self.parser.listing(ret)
self.assertTableStruct(tmpl_functions, ['Functions', 'Description'])
def test_openstack_resource_type_list(self):
ret = self.openstack('orchestration resource type list')
rsrc_types = self.parser.listing(ret)
self.assertTableStruct(rsrc_types, ['Resource Type'])
def test_openstack_resource_type_show(self):
rsrc_schema = self.openstack('orchestration resource type show '
'OS::Heat::RandomString')
self.assertIsInstance(yaml.load(rsrc_schema), dict)
def _template_validate(self, templ_name, parms):
heat_template_path = self.get_template_path(templ_name)
cmd = 'stack create test-stack --dry-run --template %s'\
% heat_template_path
for parm in parms:
cmd += ' --parameter ' + parm
ret = self.openstack(cmd)
self.assertRegex(ret, r'stack_name.*|.*test_stack')
def test_heat_template_validate_yaml(self):
self._template_validate(
'heat_minimal.yaml',
['ClientName=ClientName', 'WaitSecs=123']
)
def test_heat_template_validate_hot(self):
self._template_validate(
'heat_minimal_hot.yaml',
['test_client_name=test_client_name', 'test_wait_secs=123']
)
def _orchestration_template_validate(self, templ_name, parms):
template_path = self.get_template_path(templ_name)
cmd = 'orchestration template validate --template %s' % template_path
for parm in parms:
cmd += ' --parameter ' + parm
ret = self.openstack(cmd)
self.assertRegex(ret, r'Value:.*123')
def test_orchestration_template_validate_yaml(self):
self._orchestration_template_validate(
'heat_minimal.yaml',
['ClientName=ClientName', 'WaitSecs=123']
)
def test_orchestration_template_validate_hot(self):
self._orchestration_template_validate(
'heat_minimal_hot.yaml',
['test_client_name=test_client_name', 'test_wait_secs=123']
)

View File

@ -1,77 +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.common.utils import data_utils as utils
from heatclient.tests.functional import config
from heatclient.tests.functional.osc.v1 import base
class OpenStackClientStackTest(base.OpenStackClientTestBase):
"""Basic stack operation tests for Openstack CLI client heat plugin.
Basic smoke test for the openstack CLI stack commands.
"""
def setUp(self):
super(OpenStackClientStackTest, self).setUp()
self.stack_name = utils.rand_name(name='test-stack')
def _stack_create_minimal(self, from_url=False):
if from_url:
template = config.HEAT_MINIMAL_HOT_TEMPLATE_URL
else:
template = self.get_template_path('heat_minimal_hot.yaml')
parameters = ['test_client_name=test_client_name']
return self._stack_create(self.stack_name, template=template,
parameters=parameters)
def test_stack_create_minimal_from_file(self):
stack = self._stack_create_minimal()
self.assertEqual(self.stack_name, stack['stack_name'])
self.assertEqual("CREATE_COMPLETE", stack['stack_status'])
def test_stack_create_minimal_from_url(self):
stack = self._stack_create_minimal(from_url=True)
self.assertEqual(self.stack_name, stack['stack_name'])
self.assertEqual("CREATE_COMPLETE", stack['stack_status'])
def test_stack_suspend_resume(self):
stack = self._stack_create_minimal()
stack = self._stack_suspend(stack['id'])
self.assertEqual(self.stack_name, stack['stack_name'])
self.assertEqual("SUSPEND_COMPLETE", stack['stack_status'])
stack = self._stack_resume(stack['id'])
self.assertEqual(self.stack_name, stack['stack_name'])
self.assertEqual("RESUME_COMPLETE", stack['stack_status'])
def test_stack_snapshot_create_restore(self):
snapshot_name = utils.rand_name(name='test-stack-snapshot')
stack = self._stack_create_minimal()
snapshot = self._stack_snapshot_create(stack['id'], snapshot_name)
self.assertEqual(snapshot_name, snapshot['name'])
self._stack_snapshot_restore(stack['id'], snapshot['id'])
def test_stack_delete(self):
stack = self._stack_create_minimal()
self._stack_delete(stack['id'])
stacks_raw = self.openstack('stack list')
self.assertNotIn(stack['id'], stacks_raw)
def test_stack_snapshot_delete(self):
snapshot_name = utils.rand_name(name='test-stack-snapshot')
stack = self._stack_create_minimal()
snapshot = self._stack_snapshot_create(stack['id'], snapshot_name)
self._stack_snapshot_delete(stack['id'], snapshot['id'])
stacks_raw = self.openstack(
'stack snapshot list' + ' ' + self.stack_name)
self.assertNotIn(snapshot['id'], stacks_raw)

View File

@ -1,16 +0,0 @@
HeatTemplateFormatVersion: '2012-12-12'
Description: Minimal template to test validation
Parameters:
ClientName:
Description: Client to poll
Type: String
WaitSecs:
Description: Seconds to wait after an action (-1 is infinite)
Type: Number
Default: 0
Resources:
TestResource:
Type: OS::Heat::TestResource
Properties:
client_name: {Ref: ClientName}
wait_secs: {Ref: WaitSecs}

View File

@ -1,16 +0,0 @@
heat_template_version: 2015-04-30
description: A minimal HOT test template
parameters:
test_client_name:
description: Client to poll
type: string
test_wait_secs:
description: Seconds to wait after an action (-1 is infinite)
type: number
default: 0
resources:
test_resource:
type: OS::Heat::TestResource
properties:
client_name: { get_param: test_client_name }
wait_secs: { get_param: test_wait_secs }

View File

@ -1,103 +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 json
import os
from tempest.lib import exceptions
import yaml
from heatclient.tests.functional import base
class SimpleReadOnlyHeatClientTest(base.ClientTestBase):
"""Basic, read-only tests for Heat CLI client.
Basic smoke test for the heat CLI commands which do not require
creating or modifying stacks.
"""
def test_heat_fake_action(self):
self.assertRaises(exceptions.CommandFailed,
self.heat,
'this-does-not-exist')
def test_heat_stack_list(self):
self.heat('stack-list')
def test_heat_stack_list_debug(self):
self.heat('stack-list', flags='--debug')
def test_heat_resource_template_fmt_default(self):
ret = self.heat('resource-template OS::Nova::Server')
self.assertIn('Type: OS::Nova::Server', ret)
def test_heat_resource_template_fmt_arg_short_yaml(self):
ret = self.heat('resource-template -F yaml OS::Nova::Server')
self.assertIn('Type: OS::Nova::Server', ret)
self.assertIsInstance(yaml.safe_load(ret), dict)
def test_heat_resource_template_fmt_arg_long_json(self):
ret = self.heat('resource-template --format json OS::Nova::Server')
self.assertIn('"Type": "OS::Nova::Server"', ret)
self.assertIsInstance(json.loads(ret), dict)
def test_heat_resource_type_list(self):
ret = self.heat('resource-type-list')
rsrc_types = self.parser.listing(ret)
self.assertTableStruct(rsrc_types, ['resource_type'])
def test_heat_resource_type_show(self):
rsrc_schema = self.heat('resource-type-show OS::Heat::RandomString')
# resource-type-show returns a json resource schema
self.assertIsInstance(json.loads(rsrc_schema), dict)
def _template_validate(self, templ_name):
heat_template_path = os.path.join(
os.path.dirname(os.path.realpath(__file__)),
'templates/%s' % templ_name)
ret = self.heat('template-validate -f %s' % heat_template_path)
# On success template-validate returns a json representation
# of the template parameters
self.assertIsInstance(json.loads(ret), dict)
def test_heat_template_validate_yaml(self):
self._template_validate('heat_minimal.yaml')
def test_heat_template_validate_hot(self):
self._template_validate('heat_minimal_hot.yaml')
def test_heat_help(self):
self.heat('help')
def test_heat_bash_completion(self):
self.heat('bash-completion')
def test_heat_help_cmd(self):
# Check requesting help for a specific command works
help_text = self.heat('help resource-template')
lines = help_text.split('\n')
self.assertFirstLineStartsWith(lines, 'usage: heat resource-template')
def test_heat_version(self):
self.heat('', flags='--version')
def test_heat_template_version_list(self):
ret = self.heat('template-version-list')
tmpl_types = self.parser.listing(ret)
self.assertTableStruct(tmpl_types, ['version', 'type'])
def test_heat_template_function_list(self):
ret = self.heat('template-function-list '
'heat_template_version.2013-05-23')
tmpl_functions = self.parser.listing(ret)
self.assertTableStruct(tmpl_functions, ['functions', 'description'])

View File

@ -1,78 +0,0 @@
#
# Copyright 2016 IBM Corp.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
FULL_TEMPLATE = '''
heat_template_version: 2016-04-08
description: a template
parameter_groups:
- label: param_group_1
description: parameter group 1
parameters:
- param1
- param2
- label: param_group_2
description: parameter group 2
parameters:
- param3
parameters:
param1:
type: string
label: parameter 5
description: parameter 5
default: foo
hidden: false
constraints:
- allowed_values: ['foo', 'bar', 'bax']
param2:
type: number
default: 0
constraints:
- range: {min: 0, max: 10}
description: must be between 0 and 10
param3:
type: boolean
resources:
resource1:
type: OS::Heat::None
properties:
prop1: { get_param: param1 }
prop2: { get_param: param2 }
prop3: value
resource2:
type: OS::Heat::None
properties:
prop1: { get_param: param3 }
depends_on: resource1
outputs:
output1:
description: resource 1 prop 3
value: { get_attr: [resource1, prop3] }
output2:
description: resource 2 prop 1
value: { get_attr: [resource2, prop1] }
'''
SHORT_TEMPLATE = '''
heat_template_version: 2016-04-08
resources:
res1:
type: OS::Heat::None
'''

View File

@ -1,32 +0,0 @@
{
"files": {},
"status": "COMPLETE",
"name": "my_stack",
"tags": null,
"stack_user_project_id": "123456",
"environment": {},
"template": {
"heat_template_version": "2016-04-08",
"resources": {
"thing": {
"type": "OS::Heat::TestResource"
}
}
},
"action": "CREATE",
"project_id": "56789",
"id": "2468",
"resources": {
"thing": {
"status": "COMPLETE",
"name": "thing",
"resource_data": {
"value": "test_string"
},
"resource_id": "my_stack-thing-1234",
"action": "CREATE",
"type": "OS::Heat::TestResource",
"metadata": {}
}
}
}

View File

@ -1 +0,0 @@
heat_template_version: 2013-05-23

View File

@ -1,7 +0,0 @@
heat_template_version: 2013-05-23
parameters:
p1:
type: string
p2:
type: number

View File

@ -1,5 +0,0 @@
import sys
from mox3 import mox
sys.modules['mox'] = mox

View File

@ -1,47 +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 oslo_serialization import jsonutils
class FakeRaw(object):
version = 110
class FakeHTTPResponse(object):
version = 1.1
def __init__(self, status_code, reason, headers, content):
self.headers = headers
self.content = content
self.status_code = status_code
self.reason = reason
self.raw = FakeRaw()
def getheader(self, name, default=None):
return self.headers.get(name, default)
def getheaders(self):
return self.headers.items()
def read(self, amt=None):
b = self.content
self.content = None
return b
def iter_content(self, chunksize):
return self.content
def json(self):
return jsonutils.loads(self.content)

View File

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

View File

@ -1,83 +0,0 @@
# Copyright 2012-2013 OpenStack Foundation
# Copyright 2013 Nebula Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
import os
import fixtures
import mock
import sys
import testtools
from heatclient.tests.unit.osc import fakes
class TestCase(testtools.TestCase):
def setUp(self):
testtools.TestCase.setUp(self)
if (os.environ.get("OS_STDOUT_CAPTURE") == "True" or
os.environ.get("OS_STDOUT_CAPTURE") == "1"):
stdout = self.useFixture(fixtures.StringStream("stdout")).stream
self.useFixture(fixtures.MonkeyPatch("sys.stdout", stdout))
if (os.environ.get("OS_STDERR_CAPTURE") == "True" or
os.environ.get("OS_STDERR_CAPTURE") == "1"):
stderr = self.useFixture(fixtures.StringStream("stderr")).stream
self.useFixture(fixtures.MonkeyPatch("sys.stderr", stderr))
def assertNotCalled(self, m, msg=None):
"""Assert a function was not called"""
if m.called:
if not msg:
msg = 'method %s should not have been called' % m
self.fail(msg)
# 2.6 doesn't have the assert dict equals so make sure that it exists
if tuple(sys.version_info)[0:2] < (2, 7):
def assertIsInstance(self, obj, cls, msg=None):
"""self.assertTrue(isinstance(obj, cls)), with a nicer message"""
if not isinstance(obj, cls):
standardMsg = '%s is not an instance of %r' % (obj, cls)
self.fail(self._formatMessage(msg, standardMsg))
class TestCommand(TestCase):
"""Test cliff command classes"""
def setUp(self):
super(TestCommand, self).setUp()
# Build up a fake app
self.fake_stdout = fakes.FakeStdout()
self.app = mock.MagicMock()
self.app.stdout = self.fake_stdout
self.app.stdin = sys.stdin
self.app.stderr = sys.stderr
def check_parser(self, cmd, args, verify_args):
cmd_parser = cmd.get_parser('check_parser')
try:
parsed_args = cmd_parser.parse_args(args)
except SystemExit:
raise Exception("Argument parse failed")
for av in verify_args:
attr, value = av
if attr:
self.assertIn(attr, parsed_args)
self.assertEqual(getattr(parsed_args, attr), value)
return parsed_args

View File

@ -1,25 +0,0 @@
# Copyright 2014 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
import mock
from heatclient.tests.unit.osc import utils
class TestOrchestrationv1(utils.TestCommand):
def setUp(self):
super(TestOrchestrationv1, self).setUp()
self.app.client_manager.orchestration = mock.MagicMock()

View File

@ -1,44 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
import mock
from heatclient.osc.v1 import build_info as osc_build_info
from heatclient.tests.unit.osc.v1 import fakes as orchestration_fakes
class TestBuildInfo(orchestration_fakes.TestOrchestrationv1):
response = {"api": {
"revision": "{api_build_revision}"
},
"engine": {
"revision": "{engine_build_revision}"
}
}
def setUp(self):
super(TestBuildInfo, self).setUp()
self.cmd = osc_build_info.BuildInfo(self.app, None)
self.mock_client = self.app.client_manager.orchestration
self.mock_client.build_info.build_info = mock.Mock(
return_value=self.response)
def test_build_info(self):
arglist = []
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, data = self.cmd.take_action(parsed_args)
self.mock_client.build_info.build_info.assert_called_with()
self.assertEqual(['api', 'engine'], columns)
self.assertEqual(['{\n "revision": "{api_build_revision}"\n}',
'{\n "revision": "{engine_build_revision}"\n}'],
list(data))

View File

@ -1,269 +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.
#
# Copyright 2015 IBM Corp.
import copy
import mock
import testscenarios
from heatclient import exc
from heatclient.osc.v1 import event
from heatclient.tests.unit.osc.v1 import fakes
from heatclient.v1 import events
load_tests = testscenarios.load_tests_apply_scenarios
class TestEvent(fakes.TestOrchestrationv1):
def setUp(self):
super(TestEvent, self).setUp()
self.mock_client = self.app.client_manager.orchestration
self.event_client = self.app.client_manager.orchestration.events
self.stack_client = self.app.client_manager.orchestration.stacks
self.resource_client = self.app.client_manager.orchestration.resources
class TestEventShow(TestEvent):
scenarios = [
('table', dict(format='table')),
('shell', dict(format='shell')),
('value', dict(format='value')),
]
response = {
'event': {
"resource_name": "my_resource",
"event_time": "2015-11-11T15:23:47Z",
"links": [],
"logical_resource_id": "my_resource",
"resource_status": "CREATE_FAILED",
"resource_status_reason": "NotFound",
"physical_resource_id": "null",
"id": "474bfdf0-a450-46ec-a78a-0c7faa404073"
}
}
def setUp(self):
super(TestEventShow, self).setUp()
self.cmd = event.ShowEvent(self.app, None)
def test_event_show(self):
arglist = ['--format', self.format, 'my_stack', 'my_resource', '1234']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.event_client.get.return_value = events.Event(None, self.response)
self.cmd.take_action(parsed_args)
self.event_client.get.assert_called_with(**{
'stack_id': 'my_stack',
'resource_name': 'my_resource',
'event_id': '1234'
})
def _test_not_found(self, error):
arglist = ['my_stack', 'my_resource', '1234']
parsed_args = self.check_parser(self.cmd, arglist, [])
ex = self.assertRaises(exc.CommandError, self.cmd.take_action,
parsed_args)
self.assertIn(error, str(ex))
def test_event_show_stack_not_found(self):
error = 'Stack not found'
self.stack_client.get.side_effect = exc.HTTPNotFound(error)
self._test_not_found(error)
def test_event_show_resource_not_found(self):
error = 'Resource not found'
self.stack_client.get.side_effect = exc.HTTPNotFound(error)
self._test_not_found(error)
def test_event_show_event_not_found(self):
error = 'Event not found'
self.stack_client.get.side_effect = exc.HTTPNotFound(error)
self._test_not_found(error)
class TestEventList(TestEvent):
defaults = {
'stack_id': 'my_stack',
'resource_name': None,
'filters': {},
'sort_dir': 'asc'
}
fields = ['resource_name', 'id', 'resource_status',
'resource_status_reason', 'event_time', 'physical_resource_id',
'logical_resource_id']
class MockEvent(object):
data = {
'event_time': '2015-11-13T10:02:17',
'id': '1234',
'logical_resource_id': 'resource1',
'physical_resource_id': '',
'resource_name': 'resource1',
'resource_status': 'CREATE_COMPLETE',
'resource_status_reason': 'state changed',
'stack_name': 'my_stack',
}
def __getattr__(self, key):
try:
return self.data[key]
except KeyError:
# hasattr() in python 3 expects an AttributeError to be raised
raise AttributeError
def setUp(self):
super(TestEventList, self).setUp()
self.cmd = event.ListEvent(self.app, None)
self.event = self.MockEvent()
self.event_client.list.return_value = [self.event]
self.resource_client.list.return_value = {}
def test_event_list_defaults(self):
arglist = ['my_stack', '--format', 'table']
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, data = self.cmd.take_action(parsed_args)
self.event_client.list.assert_called_with(**self.defaults)
self.assertEqual(self.fields, columns)
def test_event_list_resource_nested_depth(self):
arglist = ['my_stack', '--resource', 'my_resource',
'--nested-depth', '3', '--format', 'table']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args)
def test_event_list_logical_resource_id(self):
arglist = ['my_stack', '--format', 'table']
del self.event.data['resource_name']
cols = copy.deepcopy(self.fields)
cols.pop()
cols[0] = 'logical_resource_id'
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, data = self.cmd.take_action(parsed_args)
self.event_client.list.assert_called_with(**self.defaults)
self.assertEqual(cols, columns)
self.event.data['resource_name'] = 'resource1'
def test_event_list_nested_depth(self):
arglist = ['my_stack', '--nested-depth', '3', '--format', 'table']
kwargs = copy.deepcopy(self.defaults)
kwargs['nested_depth'] = 3
cols = copy.deepcopy(self.fields)
cols[-1] = 'stack_name'
cols.append('logical_resource_id')
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, data = self.cmd.take_action(parsed_args)
self.event_client.list.assert_has_calls([
mock.call(**kwargs),
mock.call(**self.defaults)
])
self.assertEqual(cols, columns)
@mock.patch('osc_lib.utils.sort_items')
def test_event_list_sort(self, mock_sort_items):
arglist = ['my_stack', '--sort', 'resource_name:desc',
'--format', 'table']
parsed_args = self.check_parser(self.cmd, arglist, [])
mock_event = self.MockEvent()
mock_sort_items.return_value = [mock_event]
columns, data = self.cmd.take_action(parsed_args)
mock_sort_items.assert_called_with(mock.ANY,
"resource_name:desc")
self.event_client.list.assert_called_with(
filters={}, resource_name=None, sort_dir='desc',
sort_keys=['resource_name'], stack_id='my_stack')
self.assertEqual(self.fields, columns)
@mock.patch('osc_lib.utils.sort_items')
def test_event_list_sort_multiple(self, mock_sort_items):
arglist = ['my_stack', '--sort', 'resource_name:desc',
'--sort', 'id:asc', '--format', 'table']
parsed_args = self.check_parser(self.cmd, arglist, [])
mock_event = self.MockEvent()
mock_sort_items.return_value = [mock_event]
columns, data = self.cmd.take_action(parsed_args)
mock_sort_items.assert_called_with(mock.ANY,
"resource_name:desc,id:asc")
self.event_client.list.assert_called_with(
filters={}, resource_name=None, sort_dir='desc',
sort_keys=['resource_name', 'id'], stack_id='my_stack')
self.assertEqual(self.fields, columns)
@mock.patch('osc_lib.utils.sort_items')
def test_event_list_sort_default_key(self, mock_sort_items):
arglist = ['my_stack', '--sort', ':desc',
'--format', 'table']
parsed_args = self.check_parser(self.cmd, arglist, [])
mock_event = self.MockEvent()
mock_sort_items.return_value = [mock_event]
columns, data = self.cmd.take_action(parsed_args)
mock_sort_items.assert_called_with(mock.ANY, "event_time:desc")
self.event_client.list.assert_called_with(
filters={}, resource_name=None, sort_dir='desc', sort_keys=[],
stack_id='my_stack')
self.assertEqual(self.fields, columns)
@mock.patch('time.sleep')
def test_event_list_follow(self, sleep):
sleep.side_effect = [None, KeyboardInterrupt()]
arglist = ['--follow', 'my_stack']
expected = (
'2015-11-13 10:02:17 [resource1]: '
'CREATE_COMPLETE state changed\n'
'2015-11-13 10:02:17 [resource1]: '
'CREATE_COMPLETE state changed\n'
)
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, data = self.cmd.take_action(parsed_args)
defaults_with_marker = dict(self.defaults)
defaults_with_marker['marker'] = '1234'
self.event_client.list.assert_has_calls([
mock.call(**self.defaults),
mock.call(**defaults_with_marker)
])
self.assertEqual([], columns)
self.assertEqual([], data)
self.assertEqual(expected, self.fake_stdout.make_string())
def test_event_list_log_format(self):
arglist = ['my_stack']
expected = ('2015-11-13 10:02:17 [resource1]: CREATE_COMPLETE '
'state changed\n')
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.run(parsed_args)
self.event_client.list.assert_called_with(**self.defaults)
self.assertEqual(expected, self.fake_stdout.make_string())

View File

@ -1,361 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
import copy
import mock
from osc_lib import exceptions as exc
from heatclient import exc as heat_exc
from heatclient.osc.v1 import resource
from heatclient.tests.unit.osc.v1 import fakes as orchestration_fakes
from heatclient.v1 import resources as v1_resources
class TestResource(orchestration_fakes.TestOrchestrationv1):
def setUp(self):
super(TestResource, self).setUp()
self.resource_client = self.app.client_manager.orchestration.resources
class TestStackResourceShow(TestResource):
response = {
'attributes': {},
'creation_time': '2016-02-01T20:20:53',
'description': 'a resource',
'links': [
{'rel': 'stack',
"href": "http://heat.example.com:8004/my_stack/12"}
],
'logical_resource_id': 'my_resource',
'physical_resource_id': '1234',
'required_by': [],
'resource_name': 'my_resource',
'resource_status': 'CREATE_COMPLETE',
'resource_status_reason': 'state changed',
'resource_type': 'OS::Heat::None',
'updated_time': '2016-02-01T20:20:53',
}
def setUp(self):
super(TestStackResourceShow, self).setUp()
self.cmd = resource.ResourceShow(self.app, None)
self.resource_client.get.return_value = v1_resources.Resource(
None, self.response)
def test_resource_show(self):
arglist = ['my_stack', 'my_resource']
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, data = self.cmd.take_action(parsed_args)
self.resource_client.get.assert_called_with('my_stack', 'my_resource',
with_attr=None)
for key in self.response:
self.assertIn(key, columns)
self.assertIn(self.response[key], data)
def test_resource_show_with_attr(self):
arglist = ['my_stack', 'my_resource',
'--with-attr', 'foo', '--with-attr', 'bar']
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, data = self.cmd.take_action(parsed_args)
self.resource_client.get.assert_called_with('my_stack', 'my_resource',
with_attr=['foo', 'bar'])
for key in self.response:
self.assertIn(key, columns)
self.assertIn(self.response[key], data)
def test_resource_show_not_found(self):
arglist = ['my_stack', 'bad_resource']
self.resource_client.get.side_effect = heat_exc.HTTPNotFound
parsed_args = self.check_parser(self.cmd, arglist, [])
error = self.assertRaises(exc.CommandError,
self.cmd.take_action, parsed_args)
self.assertEqual('Stack or resource not found: my_stack bad_resource',
str(error))
class TestStackResourceList(TestResource):
response = {
'attributes': {},
'creation_time': '2016-02-01T20:20:53',
'description': 'a resource',
'links': [
{'rel': 'stack',
"href": "http://heat.example.com:8004/my_stack/12"}
],
'logical_resource_id': '1234',
'physical_resource_id': '1234',
'required_by': [],
'resource_name': 'my_resource',
'resource_status': 'CREATE_COMPLETE',
'resource_status_reason': 'state changed',
'resource_type': 'OS::Heat::None',
'updated_time': '2016-02-01T20:20:53',
}
columns = ['resource_name', 'physical_resource_id', 'resource_type',
'resource_status', 'updated_time']
data = ['my_resource', '1234', 'OS::Heat::None',
'CREATE_COMPLETE', '2016-02-01T20:20:53']
def setUp(self):
super(TestStackResourceList, self).setUp()
self.cmd = resource.ResourceList(self.app, None)
self.resource_client.list.return_value = [
v1_resources.Resource(None, self.response)]
def test_resource_list(self):
arglist = ['my_stack']
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, data = self.cmd.take_action(parsed_args)
self.resource_client.list.assert_called_with(
'my_stack',
filters={},
with_detail=False,
nested_depth=None)
self.assertEqual(self.columns, columns)
self.assertEqual(tuple(self.data), list(data)[0])
def test_resource_list_not_found(self):
arglist = ['bad_stack']
self.resource_client.list.side_effect = heat_exc.HTTPNotFound
parsed_args = self.check_parser(self.cmd, arglist, [])
self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args)
def test_resource_list_with_detail(self):
arglist = ['my_stack', '--long']
cols = copy.deepcopy(self.columns)
cols.append('stack_name')
out = copy.deepcopy(self.data)
out.append('my_stack')
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, data = self.cmd.take_action(parsed_args)
self.resource_client.list.assert_called_with(
'my_stack',
filters={},
with_detail=True,
nested_depth=None)
self.assertEqual(cols, columns)
self.assertEqual(tuple(out), list(data)[0])
def test_resource_list_nested_depth(self):
arglist = ['my_stack', '--nested-depth', '3']
cols = copy.deepcopy(self.columns)
cols.append('stack_name')
out = copy.deepcopy(self.data)
out.append('my_stack')
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, data = self.cmd.take_action(parsed_args)
self.resource_client.list.assert_called_with(
'my_stack',
filters={},
with_detail=False,
nested_depth=3)
self.assertEqual(cols, columns)
self.assertEqual(tuple(out), list(data)[0])
def test_resource_list_no_resource_name(self):
arglist = ['my_stack']
resp = copy.deepcopy(self.response)
del resp['resource_name']
cols = copy.deepcopy(self.columns)
cols[0] = 'logical_resource_id'
out = copy.deepcopy(self.data)
out[1] = '1234'
self.resource_client.list.return_value = [
v1_resources.Resource(None, resp)]
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, data = self.cmd.take_action(parsed_args)
self.resource_client.list.assert_called_with(
'my_stack',
filters={},
with_detail=False,
nested_depth=None)
self.assertEqual(cols, columns)
def test_resource_list_filter(self):
arglist = ['my_stack', '--filter', 'name=my_resource']
out = copy.deepcopy(self.data)
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, data = self.cmd.take_action(parsed_args)
self.resource_client.list.assert_called_with(
'my_stack',
filters=dict(name='my_resource'),
with_detail=False,
nested_depth=None)
self.assertEqual(tuple(out), list(data)[0])
class TestResourceMetadata(TestResource):
def setUp(self):
super(TestResourceMetadata, self).setUp()
self.cmd = resource.ResourceMetadata(self.app, None)
self.resource_client.metadata.return_value = {}
def test_resource_metadata(self):
arglist = ['my_stack', 'my_resource']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
self.resource_client.metadata.assert_called_with(**{
'stack_id': 'my_stack',
'resource_name': 'my_resource'
})
def test_resource_metadata_yaml(self):
arglist = ['my_stack', 'my_resource', '--format', 'yaml']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
self.resource_client.metadata.assert_called_with(**{
'stack_id': 'my_stack',
'resource_name': 'my_resource'
})
def test_resource_metadata_error(self):
arglist = ['my_stack', 'my_resource']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.resource_client.metadata.side_effect = heat_exc.HTTPNotFound
error = self.assertRaises(exc.CommandError,
self.cmd.take_action,
parsed_args)
self.assertEqual('Stack my_stack or resource my_resource not found.',
str(error))
class TestResourceSignal(TestResource):
def setUp(self):
super(TestResourceSignal, self).setUp()
self.cmd = resource.ResourceSignal(self.app, None)
def test_resource_signal(self):
arglist = ['my_stack', 'my_resource']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
self.resource_client.signal.assert_called_with(**{
'stack_id': 'my_stack',
'resource_name': 'my_resource'
})
def test_resource_signal_error(self):
arglist = ['my_stack', 'my_resource']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.resource_client.signal.side_effect = heat_exc.HTTPNotFound
error = self.assertRaises(exc.CommandError,
self.cmd.take_action,
parsed_args)
self.assertEqual('Stack my_stack or resource my_resource not found.',
str(error))
def test_resource_signal_data(self):
arglist = ['my_stack', 'my_resource',
'--data', '{"message":"Content"}']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
self.resource_client.signal.assert_called_with(**{
'data': {u'message': u'Content'},
'stack_id': 'my_stack',
'resource_name': 'my_resource'
})
def test_resource_signal_data_not_json(self):
arglist = ['my_stack', 'my_resource', '--data', '{']
parsed_args = self.check_parser(self.cmd, arglist, [])
error = self.assertRaises(exc.CommandError,
self.cmd.take_action,
parsed_args)
self.assertIn('Data should be in JSON format', str(error))
def test_resource_signal_data_and_file_error(self):
arglist = ['my_stack', 'my_resource',
'--data', '{}', '--data-file', 'file']
parsed_args = self.check_parser(self.cmd, arglist, [])
error = self.assertRaises(exc.CommandError,
self.cmd.take_action,
parsed_args)
self.assertEqual('Should only specify one of data or data-file',
str(error))
@mock.patch('six.moves.urllib.request.urlopen')
def test_resource_signal_file(self, urlopen):
data = mock.Mock()
data.read.side_effect = ['{"message":"Content"}']
urlopen.return_value = data
arglist = ['my_stack', 'my_resource', '--data-file', 'test_file']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
self.resource_client.signal.assert_called_with(**{
'data': {u'message': u'Content'},
'stack_id': 'my_stack',
'resource_name': 'my_resource'
})
class TestResourceMarkUnhealthy(TestResource):
def setUp(self):
super(TestResourceMarkUnhealthy, self).setUp()
self.cmd = resource.ResourceMarkUnhealthy(self.app, None)
self.resource_client.mark_unhealthy = mock.Mock()
def test_resource_mark_unhealthy(self):
arglist = ['my_stack', 'my_resource', 'reason']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
self.resource_client.mark_unhealthy.assert_called_with(**{
"stack_id": "my_stack",
"resource_name": "my_resource",
"mark_unhealthy": True,
"resource_status_reason": "reason"
})
def test_resource_mark_unhealthy_reset(self):
arglist = ['my_stack', 'my_resource', '--reset']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
self.resource_client.mark_unhealthy.assert_called_with(**{
"stack_id": "my_stack",
"resource_name": "my_resource",
"mark_unhealthy": False,
"resource_status_reason": ""
})
def test_resource_mark_unhealthy_not_found(self):
arglist = ['my_stack', 'my_resource', '--reset']
self.resource_client.mark_unhealthy.side_effect = (
heat_exc.HTTPNotFound)
parsed_args = self.check_parser(self.cmd, arglist, [])
error = self.assertRaises(exc.CommandError,
self.cmd.take_action, parsed_args)
self.assertEqual('Stack or resource not found: my_stack my_resource',
str(error))

View File

@ -1,178 +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 import exceptions as exc
from heatclient import exc as heat_exc
from heatclient.osc.v1 import resource_type
from heatclient.tests.unit.osc.v1 import fakes as orchestration_fakes
from heatclient.v1 import resource_types
class TestResourceType(orchestration_fakes.TestOrchestrationv1):
def setUp(self):
super(TestResourceType, self).setUp()
self.mock_client = self.app.client_manager.orchestration
class TestResourceTypeShow(TestResourceType):
def setUp(self):
super(TestResourceTypeShow, self).setUp()
self.cmd = resource_type.ResourceTypeShow(self.app, None)
self.mock_client.resource_types.get.return_value = {}
self.mock_client.resource_types.generate_template.return_value = {}
def test_resourcetype_show(self):
arglist = ['OS::Heat::None']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
self.mock_client.resource_types.get.assert_called_once_with(
'OS::Heat::None', False)
def test_resourcetype_show_json(self):
arglist = ['OS::Heat::None',
'--format', 'json']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
self.mock_client.resource_types.get.assert_called_once_with(
'OS::Heat::None', False)
def test_resourcetype_show_error_get(self):
arglist = ['OS::Heat::None']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.mock_client.resource_types.get.side_effect = heat_exc.HTTPNotFound
self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args)
def test_resourcetype_show_error_template(self):
arglist = ['OS::Heat::None',
'--template-type', 'hot']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.mock_client.resource_types.generate_template.side_effect = \
heat_exc.HTTPNotFound
self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args)
def test_resourcetype_show_template_hot(self):
arglist = ['OS::Heat::None',
'--template-type', 'Hot']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
self.mock_client.resource_types.generate_template.assert_called_with(
**{'resource_type': 'OS::Heat::None',
'template_type': 'hot'})
def test_resourcetype_show_template_cfn(self):
arglist = ['OS::Heat::None',
'--template-type', 'cfn']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
self.mock_client.resource_types.generate_template.assert_called_with(
**{'resource_type': 'OS::Heat::None',
'template_type': 'cfn'})
def test_resourcetype_show_template_cfn_yaml(self):
arglist = ['OS::Heat::None',
'--template-type', 'Cfn',
'--format', 'yaml']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
self.mock_client.resource_types.generate_template.assert_called_with(
**{'resource_type': 'OS::Heat::None',
'template_type': 'cfn'})
def test_resourcetype_show_invalid_template_type(self):
arglist = ['OS::Heat::None',
'--template-type', 'abc']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args)
def test_resourcetype_show_with_description(self):
arglist = ['OS::Heat::None', '--long']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
self.mock_client.resource_types.get.assert_called_with(
'OS::Heat::None', True)
def test_resourcetype_show_long_and_template_type_error(self):
arglist = ['OS::Heat::None',
'--template-type', 'cfn',
'--long']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args)
class TestTypeList(TestResourceType):
expected_columns = ['Resource Type']
list_response = [
resource_types.ResourceType(None, {'resource_type': 'BBB',
'description': 'This is BBB'}),
resource_types.ResourceType(None, {'resource_type': 'AAA',
'description': 'Well done'}),
resource_types.ResourceType(None,
{'resource_type': 'CCC',
'description': 'No description given'})
]
expected_rows = [
['AAA'],
['BBB'],
['CCC']
]
def setUp(self):
super(TestTypeList, self).setUp()
self.cmd = resource_type.ResourceTypeList(self.app, None)
self.mock_client.resource_types.list.return_value = self.list_response
def test_resourcetype_list(self):
arglist = []
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, rows = self.cmd.take_action(parsed_args)
self.mock_client.resource_types.list.assert_called_with(
filters={}, with_description=False)
self.assertEqual(self.expected_columns, columns)
self.assertEqual(self.expected_rows, rows)
def test_resourcetype_list_filter(self):
arglist = ['--filter', 'name=B']
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, rows = self.cmd.take_action(parsed_args)
self.mock_client.resource_types.list.assert_called_once_with(
filters={'name': 'B'}, with_description=False)
self.assertEqual(self.expected_columns, columns)
self.assertEqual(self.expected_rows, rows)
def test_resourcetype_list_filters(self):
arglist = ['--filter', 'name=B', '--filter', 'version=123']
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, rows = self.cmd.take_action(parsed_args)
self.mock_client.resource_types.list.assert_called_once_with(
filters={'name': 'B', 'version': '123'}, with_description=False)
self.assertEqual(self.expected_columns, columns)
self.assertEqual(self.expected_rows, rows)
def test_resourcetype_list_with_description(self):
arglist = ['--long']
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, rows = self.cmd.take_action(parsed_args)
self.mock_client.resource_types.list.assert_called_once_with(
filters={}, with_description=True)
self.assertEqual(['Resource Type', 'Description'], columns)
self.assertEqual([['AAA', 'Well done'],
['BBB', 'This is BBB'],
['CCC', 'No description given']],
rows)

View File

@ -1,62 +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.osc.v1 import service as osc_service
from heatclient.tests.unit.osc.v1 import fakes as orchestration_fakes
class TestServiceList(orchestration_fakes.TestOrchestrationv1):
response = {"services": [
{
"status": "up",
"binary": "heat-engine",
"report_interval": 60,
"engine_id": "9d9242c3-4b9e-45e1-9e74-7615fbf20e5d",
"created_at": "2015-02-03T05:55:59.000000",
"hostname": "mrkanag",
"updated_at": "2015-02-03T05:57:59.000000",
"topic": "engine",
"host": "engine-1",
"deleted_at": 'null',
"id": "e1908f44-42f9-483f-b778-bc814072c33d"
},
{
"status": "down",
"binary": "heat-engine",
"report_interval": 60,
"engine_id": "2d2434bf-adb6-4453-9c6b-b22fb8bd2306",
"created_at": "2015-02-03T06:03:14.000000",
"hostname": "mrkanag",
"updated_at": "2015-02-03T06:09:55.000000",
"topic": "engine",
"host": "engine",
"deleted_at": 'null',
"id": "582b5657-6db7-48ad-8483-0096350faa21"
}
]}
columns = ['Hostname', 'Binary', 'Engine ID', 'Host',
'Topic', 'Updated At', 'Status']
def setUp(self):
super(TestServiceList, self).setUp()
self.cmd = osc_service.ListService(self.app, None)
self.mock_client = self.app.client_manager.orchestration
self.mock_client.services.list.return_value = self.response
def test_service_list(self):
arglist = []
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, data = self.cmd.take_action(parsed_args)
self.mock_client.services.list.assert_called_with()
self.assertEqual(self.columns, columns)

View File

@ -1,185 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
import mock
from osc_lib import exceptions as exc
import six
from heatclient import exc as heat_exc
from heatclient.osc.v1 import snapshot
from heatclient.tests.unit.osc.v1 import fakes as orchestration_fakes
class TestStack(orchestration_fakes.TestOrchestrationv1):
def setUp(self):
super(TestStack, self).setUp()
self.mock_client = self.app.client_manager.orchestration
self.stack_client = self.app.client_manager.orchestration.stacks
class TestListSnapshot(TestStack):
def setUp(self):
super(TestListSnapshot, self).setUp()
self.cmd = snapshot.ListSnapshot(self.app, None)
self.stack_client.snapshot_list.return_value = {'snapshots': []}
def test_snapshot_list(self):
arglist = ['my_stack']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
self.stack_client.snapshot_list.assert_called_with(
stack_id='my_stack')
def test_snapshot_list_error(self):
self.stack_client.snapshot_list.side_effect = heat_exc.HTTPNotFound()
arglist = ['my_stack']
parsed_args = self.check_parser(self.cmd, arglist, [])
error = self.assertRaises(
exc.CommandError,
self.cmd.take_action,
parsed_args)
self.assertEqual('Stack not found: my_stack',
str(error))
class TestSnapshotShow(TestStack):
def setUp(self):
super(TestSnapshotShow, self).setUp()
self.cmd = snapshot.ShowSnapshot(self.app, None)
def test_snapshot_show(self):
arglist = ['my_stack', 'snapshot_id']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.stack_client.snapshot_show.return_value = {}
self.cmd.take_action(parsed_args)
self.stack_client.snapshot_show.assert_called_with(
'my_stack', 'snapshot_id')
def test_snapshot_not_found(self):
arglist = ['my_stack', 'snapshot_id']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.stack_client.snapshot_show.side_effect = heat_exc.HTTPNotFound()
self.assertRaises(
exc.CommandError,
self.cmd.take_action,
parsed_args)
class TestRestoreSnapshot(TestStack):
def setUp(self):
super(TestRestoreSnapshot, self).setUp()
self.cmd = snapshot.RestoreSnapshot(self.app, None)
def test_snapshot_restore(self):
arglist = ['my_stack', 'my_snapshot']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
self.stack_client.restore.assert_called_with(
snapshot_id='my_snapshot', stack_id='my_stack')
def test_snapshot_restore_error(self):
self.stack_client.restore.side_effect = heat_exc.HTTPNotFound()
arglist = ['my_stack', 'my_snapshot']
parsed_args = self.check_parser(self.cmd, arglist, [])
error = self.assertRaises(
exc.CommandError,
self.cmd.take_action,
parsed_args)
self.assertEqual('Stack my_stack or snapshot my_snapshot not found.',
str(error))
class TestSnapshotCreate(TestStack):
get_response = {
"status": "IN_PROGRESS",
"name": "test_snapshot",
"status_reason": None,
"creation_time": "2015-11-09T04:35:38.534130",
"data": None,
"id": "108604fe-6d13-41b7-aa3a-79b6cf60c4ff"
}
def setUp(self):
super(TestSnapshotCreate, self).setUp()
self.cmd = snapshot.CreateSnapshot(self.app, None)
def test_snapshot_create(self):
arglist = ['my_stack', '--name', 'test_snapshot']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.stack_client.snapshot.return_value = self.get_response
self.cmd.take_action(parsed_args)
self.stack_client.snapshot.assert_called_with(
'my_stack', 'test_snapshot')
def test_snapshot_create_no_name(self):
arglist = ['my_stack']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.stack_client.snapshot.return_value = self.get_response
self.cmd.take_action(parsed_args)
self.stack_client.snapshot.assert_called_with(
'my_stack', None)
def test_snapshot_create_error(self):
arglist = ['my_stack', '--name', 'test_snapshot']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.stack_client.snapshot.side_effect = heat_exc.HTTPNotFound
self.assertRaises(
exc.CommandError,
self.cmd.take_action,
parsed_args)
class TestSnapshotDelete(TestStack):
def setUp(self):
super(TestSnapshotDelete, self).setUp()
self.cmd = snapshot.DeleteSnapshot(self.app, None)
def test_snapshot_delete(self):
arglist = ['my_stack', 'snapshot_id']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
self.stack_client.snapshot_delete.assert_called_with(
'my_stack', 'snapshot_id')
def test_snapshot_delete_not_found(self):
arglist = ['my_stack', 'snapshot_id']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.stack_client.snapshot_delete.side_effect = heat_exc.HTTPNotFound()
self.assertRaises(
exc.CommandError,
self.cmd.take_action,
parsed_args)
@mock.patch('sys.stdin', spec=six.StringIO)
def test_snapshot_delete_prompt(self, mock_stdin):
arglist = ['my_stack', 'snapshot_id']
mock_stdin.isatty.return_value = True
mock_stdin.readline.return_value = 'y'
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
mock_stdin.readline.assert_called_with()
self.stack_client.snapshot_delete.assert_called_with('my_stack',
'snapshot_id')
@mock.patch('sys.stdin', spec=six.StringIO)
def test_snapshot_delete_prompt_no(self, mock_stdin):
arglist = ['my_stack', 'snapshot_id']
mock_stdin.isatty.return_value = True
mock_stdin.readline.return_value = 'n'
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
mock_stdin.readline.assert_called_with()
self.stack_client.snapshot_delete.assert_not_called()

View File

@ -1,270 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
import mock
from osc_lib import exceptions as exc
import yaml
from heatclient import exc as heat_exc
from heatclient.osc.v1 import software_config
from heatclient.tests.unit.osc.v1 import fakes as orchestration_fakes
from heatclient.v1 import software_configs
class TestConfig(orchestration_fakes.TestOrchestrationv1):
def setUp(self):
super(TestConfig, self).setUp()
self.mock_client = self.app.client_manager.orchestration
class TestDeleteConfig(TestConfig):
def setUp(self):
super(TestDeleteConfig, self).setUp()
self.cmd = software_config.DeleteConfig(self.app, None)
self.mock_delete = self.mock_client.software_configs.delete
def test_config_delete(self):
arglist = ['id_123']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
self.mock_delete.assert_called_with(
config_id='id_123')
def test_config_delete_multi(self):
arglist = ['id_123', 'id_456']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
self.mock_delete.assert_has_calls(
[mock.call(config_id='id_123'),
mock.call(config_id='id_456')])
def test_config_delete_not_found(self):
arglist = ['id_123', 'id_456', 'id_789']
self.mock_client.software_configs.delete.side_effect = [
None, heat_exc.HTTPNotFound, None]
parsed_args = self.check_parser(self.cmd, arglist, [])
error = self.assertRaises(exc.CommandError, self.cmd.take_action,
parsed_args)
self.mock_delete.assert_has_calls(
[mock.call(config_id='id_123'),
mock.call(config_id='id_456'),
mock.call(config_id='id_789')])
self.assertEqual('Unable to delete 1 of the 3 software configs.',
str(error))
class TestListConfig(TestConfig):
def setUp(self):
super(TestListConfig, self).setUp()
self.cmd = software_config.ListConfig(self.app, None)
self.mock_client.software_configs.list.return_value = [
software_configs.SoftwareConfig(None, {})]
def test_config_list(self):
arglist = []
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
self.mock_client.software_configs.list.assert_called_once_with()
def test_config_list_limit(self):
arglist = ['--limit', '3']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
self.mock_client.software_configs.list.assert_called_with(limit='3')
def test_config_list_marker(self):
arglist = ['--marker', 'id123']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
self.mock_client.software_configs.list.assert_called_with(
marker='id123')
class TestCreateConfig(TestConfig):
def setUp(self):
super(TestCreateConfig, self).setUp()
self.cmd = software_config.CreateConfig(self.app, None)
self.mock_client.software_configs.create.return_value = \
software_configs.SoftwareConfig(None, {})
def test_config_create(self):
properties = {
'config': '',
'group': 'Heat::Ungrouped',
'name': 'test',
'options': {},
'inputs': [],
'outputs': []
}
arglist = ['test']
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, rows = self.cmd.take_action(parsed_args)
self.mock_client.stacks.validate.assert_called_with(**{
'template': {
'heat_template_version': '2013-05-23',
'resources': {
'test': {
'type': 'OS::Heat::SoftwareConfig',
'properties': properties}}}})
self.mock_client.software_configs.create.assert_called_with(
**properties)
def test_config_create_group(self):
properties = {
'config': '',
'group': 'group',
'name': 'test',
'options': {},
'inputs': [],
'outputs': []
}
arglist = ['test', '--group', 'group']
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, rows = self.cmd.take_action(parsed_args)
self.mock_client.stacks.validate.assert_called_with(**{
'template': {
'heat_template_version': '2013-05-23',
'resources': {
'test': {
'type': 'OS::Heat::SoftwareConfig',
'properties': properties}}}})
self.mock_client.software_configs.create.assert_called_with(
**properties)
@mock.patch('six.moves.urllib.request.urlopen')
def test_config_create_config_file(self, urlopen):
properties = {
'config': 'config',
'group': 'Heat::Ungrouped',
'name': 'test',
'options': {},
'inputs': [],
'outputs': []
}
data = mock.Mock()
data.read.side_effect = ['config']
urlopen.return_value = data
arglist = ['test', '--config-file', 'config_file']
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, rows = self.cmd.take_action(parsed_args)
self.mock_client.stacks.validate.assert_called_with(**{
'template': {
'heat_template_version': '2013-05-23',
'resources': {
'test': {
'type': 'OS::Heat::SoftwareConfig',
'properties': properties}}}})
self.mock_client.software_configs.create.assert_called_with(
**properties)
@mock.patch('six.moves.urllib.request.urlopen')
def test_config_create_definition_file(self, urlopen):
definition = {
'inputs': [
{'name': 'input'},
],
'outputs': [
{'name': 'output'}
],
'options': {'option': 'value'}
}
properties = {
'config': '',
'group': 'Heat::Ungrouped',
'name': 'test'
}
properties.update(definition)
data = mock.Mock()
data.read.side_effect = [yaml.safe_dump(definition)]
urlopen.return_value = data
arglist = ['test', '--definition-file', 'definition-file']
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, rows = self.cmd.take_action(parsed_args)
self.mock_client.stacks.validate.assert_called_with(**{
'template': {
'heat_template_version': '2013-05-23',
'resources': {
'test': {
'type': 'OS::Heat::SoftwareConfig',
'properties': properties}}}})
self.mock_client.software_configs.create.assert_called_with(
**properties)
class TestConfigShow(TestConfig):
columns = (
'id',
'name',
'group',
'config',
'inputs',
'outputs',
'options',
'creation_time',
)
data = (
'96dfee3f-27b7-42ae-a03e-966226871ae6',
'test',
'Heat::Ungrouped',
'',
[],
[],
{},
'2015-12-09T11:55:06',
)
response = dict(zip(columns, data))
def setUp(self):
super(TestConfigShow, self).setUp()
self.cmd = software_config.ShowConfig(self.app, None)
self.mock_client.software_configs.get.return_value = \
software_configs.SoftwareConfig(None, self.response)
def test_config_show(self):
arglist = ['96dfee3f-27b7-42ae-a03e-966226871ae6']
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, data = self.cmd.take_action(parsed_args)
self.mock_client.software_configs.get.assert_called_with(**{
'config_id': '96dfee3f-27b7-42ae-a03e-966226871ae6',
})
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
def test_config_show_config_only(self):
arglist = ['--config-only', '96dfee3f-27b7-42ae-a03e-966226871ae6']
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, data = self.cmd.take_action(parsed_args)
self.mock_client.software_configs.get.assert_called_with(**{
'config_id': '96dfee3f-27b7-42ae-a03e-966226871ae6',
})
self.assertIsNone(columns)
self.assertIsNone(data)
def test_config_show_not_found(self):
arglist = ['96dfee3f-27b7-42ae-a03e-966226871ae6']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.mock_client.software_configs.get.side_effect = \
heat_exc.HTTPNotFound()
self.assertRaises(
exc.CommandError,
self.cmd.take_action,
parsed_args)

View File

@ -1,440 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
import copy
import mock
from osc_lib import exceptions as exc
from heatclient import exc as heat_exc
from heatclient.osc.v1 import software_deployment
from heatclient.tests.unit.osc.v1 import fakes as orchestration_fakes
from heatclient.v1 import software_configs
from heatclient.v1 import software_deployments
class TestDeployment(orchestration_fakes.TestOrchestrationv1):
def setUp(self):
super(TestDeployment, self).setUp()
self.mock_client = self.app.client_manager.orchestration
self.config_client = self.mock_client.software_configs
self.sd_client = self.mock_client.software_deployments
class TestDeploymentCreate(TestDeployment):
server_id = '1234'
config_id = '5678'
deploy_id = '910'
config = {
'name': 'my_deploy',
'group': 'strict',
'config': '#!/bin/bash',
'inputs': [],
'outputs': [],
'options': [],
'id': config_id,
}
deployment = {
'server_id': server_id,
'input_values': {},
'action': 'UPDATE',
'status': 'IN_PROGRESS',
'status_reason': None,
'signal_id': 'signal_id',
'config_id': config_id,
'id': deploy_id,
}
config_defaults = {
'group': 'Heat::Ungrouped',
'config': '',
'options': {},
'inputs': [
{
'name': 'deploy_server_id',
'description': 'ID of the server being deployed to',
'type': 'String',
'value': server_id,
},
{
'name': 'deploy_action',
'description': 'Name of the current action being deployed',
'type': 'String',
'value': 'UPDATE',
},
{
'name': 'deploy_signal_transport',
'description': 'How the server should signal to heat with the '
'deployment output values.',
'type': 'String',
'value': 'TEMP_URL_SIGNAL',
},
{
'name': 'deploy_signal_id',
'description': 'ID of signal to use for signaling output '
'values',
'type': 'String',
'value': 'signal_id',
},
{
'name': 'deploy_signal_verb',
'description': 'HTTP verb to use for signaling output values',
'type': 'String',
'value': 'PUT',
},
],
'outputs': [],
'name': 'my_deploy',
}
deploy_defaults = {
'config_id': config_id,
'server_id': server_id,
'action': 'UPDATE',
'status': 'IN_PROGRESS',
}
def setUp(self):
super(TestDeploymentCreate, self).setUp()
self.cmd = software_deployment.CreateDeployment(self.app, None)
self.config_client.create.return_value = \
software_configs.SoftwareConfig(None, self.config)
self.config_client.get.return_value = \
software_configs.SoftwareConfig(None, self.config)
self.sd_client.create.return_value = \
software_deployments.SoftwareDeployment(None, self.deployment)
@mock.patch('heatclient.common.deployment_utils.build_signal_id',
return_value='signal_id')
def test_deployment_create(self, mock_build):
arglist = ['my_deploy', '--server', self.server_id]
expected_cols = ('action', 'config_id', 'id', 'input_values',
'server_id', 'signal_id', 'status', 'status_reason')
expected_data = ('UPDATE', self.config_id, self.deploy_id, {},
self.server_id, 'signal_id', 'IN_PROGRESS', None)
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, data = self.cmd.take_action(parsed_args)
self.config_client.create.assert_called_with(**self.config_defaults)
self.sd_client.create.assert_called_with(
**self.deploy_defaults)
self.assertEqual(expected_cols, columns)
self.assertEqual(expected_data, data)
@mock.patch('heatclient.common.deployment_utils.build_signal_id',
return_value='signal_id')
def test_deployment_create_with_config(self, mock_build):
arglist = ['my_deploy', '--server', self.server_id,
'--config', self.config_id]
config = copy.deepcopy(self.config_defaults)
config['config'] = '#!/bin/bash'
config['group'] = 'strict'
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, data = self.cmd.take_action(parsed_args)
self.config_client.get.assert_called_with(self.config_id)
self.config_client.create.assert_called_with(**config)
self.sd_client.create.assert_called_with(
**self.deploy_defaults)
def test_deployment_create_config_not_found(self):
arglist = ['my_deploy', '--server', self.server_id,
'--config', 'bad_id']
self.config_client.get.side_effect = heat_exc.HTTPNotFound
parsed_args = self.check_parser(self.cmd, arglist, [])
self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args)
def test_deployment_create_no_signal(self):
arglist = ['my_deploy', '--server', self.server_id,
'--signal-transport', 'NO_SIGNAL']
config = copy.deepcopy(self.config_defaults)
config['inputs'] = config['inputs'][:-2]
config['inputs'][2]['value'] = 'NO_SIGNAL'
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, data = self.cmd.take_action(parsed_args)
self.config_client.create.assert_called_with(**config)
self.sd_client.create.assert_called_with(
**self.deploy_defaults)
@mock.patch('heatclient.common.deployment_utils.build_signal_id',
return_value='signal_id')
def test_deployment_create_invalid_signal_transport(self, mock_build):
arglist = ['my_deploy', '--server', self.server_id,
'--signal-transport', 'A']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.assertRaises(heat_exc.CommandError,
self.cmd.take_action, parsed_args)
@mock.patch('heatclient.common.deployment_utils.build_signal_id',
return_value='signal_id')
def test_deployment_create_input_value(self, mock_build):
arglist = ['my_deploy', '--server', self.server_id,
'--input-value', 'foo=bar']
config = copy.deepcopy(self.config_defaults)
config['inputs'].insert(
0, {'name': 'foo', 'type': 'String', 'value': 'bar'})
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, data = self.cmd.take_action(parsed_args)
self.config_client.create.assert_called_with(**config)
self.sd_client.create.assert_called_with(
**self.deploy_defaults)
@mock.patch('heatclient.common.deployment_utils.build_signal_id',
return_value='signal_id')
def test_deployment_create_action(self, mock_build):
arglist = ['my_deploy', '--server', self.server_id,
'--action', 'DELETE']
config = copy.deepcopy(self.config_defaults)
config['inputs'][1]['value'] = 'DELETE'
deploy = copy.deepcopy(self.deploy_defaults)
deploy['action'] = 'DELETE'
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, data = self.cmd.take_action(parsed_args)
self.config_client.create.assert_called_with(**config)
self.sd_client.create.assert_called_with(**deploy)
class TestDeploymentDelete(TestDeployment):
def setUp(self):
super(TestDeploymentDelete, self).setUp()
self.cmd = software_deployment.DeleteDeployment(self.app, None)
def test_deployment_delete_success(self):
arglist = ['test_deployment']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
self.sd_client.delete.assert_called_with(
deployment_id='test_deployment')
def test_deployment_delete_multiple(self):
arglist = ['test_deployment', 'test_deployment2']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
self.sd_client.delete.assert_has_calls(
[mock.call(deployment_id='test_deployment'),
mock.call(deployment_id='test_deployment2')])
def test_deployment_delete_not_found(self):
arglist = ['test_deployment', 'test_deployment2']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.sd_client.delete.side_effect = heat_exc.HTTPNotFound()
error = self.assertRaises(
exc.CommandError, self.cmd.take_action, parsed_args)
self.assertIn("Unable to delete 2 of the 2 deployments.", str(error))
def test_deployment_config_delete_failed(self):
arglist = ['test_deployment']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.config_client.delete.side_effect = heat_exc.HTTPNotFound()
self.assertIsNone(self.cmd.take_action(parsed_args))
class TestDeploymentList(TestDeployment):
columns = ['id', 'config_id', 'server_id', 'action', 'status']
data = {"software_deployments": [
{
"status": "COMPLETE",
"server_id": "ec14c864-096e-4e27-bb8a-2c2b4dc6f3f5",
"config_id": "8da95794-2ad9-4979-8ae5-739ce314c5cd",
"output_values": {
"deploy_stdout": "Writing to /tmp/barmy Written to /tmp/barmy",
"deploy_stderr": "+ echo Writing to /tmp/barmy\n+ echo fu\n+ c"
"at /tmp/barmy\n+ echo -n The file /tmp/barmy"
"contains for server ec14c864-096e-4e27-bb8a-"
"2c2b4dc6f3f5 during CREATE\n+"
"echo Output to stderr\nOutput to stderr\n",
"deploy_status_code": 0,
"result": "The file /tmp/barmy contains fu for server "
"ec14c864-096e-4e27-bb8a-2c2b4dc6f3f5 during CREATE"
},
"input_values": None,
"action": "CREATE",
"status_reason": "Outputs received",
"id": "ef422fa5-719a-419e-a10c-72e3a367b0b8",
"creation_time": "2015-01-31T15:12:36Z",
"updated_time": "2015-01-31T15:18:21Z"
}
]
}
def setUp(self):
super(TestDeploymentList, self).setUp()
self.cmd = software_deployment.ListDeployment(self.app, None)
self.sd_client.list = mock.MagicMock(return_value=[self.data])
def test_deployment_list(self):
arglist = []
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, data = self.cmd.take_action(parsed_args)
self.sd_client.list.assert_called_with()
self.assertEqual(self.columns, columns)
def test_deployment_list_server(self):
kwargs = {}
kwargs['server_id'] = 'ec14c864-096e-4e27-bb8a-2c2b4dc6f3f5'
arglist = ['--server', 'ec14c864-096e-4e27-bb8a-2c2b4dc6f3f5']
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, data = self.cmd.take_action(parsed_args)
self.sd_client.list.assert_called_with(**kwargs)
self.assertEqual(self.columns, columns)
def test_deployment_list_long(self):
kwargs = {}
cols = ['id', 'config_id', 'server_id', 'action', 'status',
'creation_time', 'status_reason']
arglist = ['--long']
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, data = self.cmd.take_action(parsed_args)
self.sd_client.list.assert_called_with(**kwargs)
self.assertEqual(cols, columns)
class TestDeploymentShow(TestDeployment):
get_response = {"software_deployment": {
"status": "IN_PROGRESS",
"server_id": "ec14c864-096e-4e27-bb8a-2c2b4dc6f3f5",
"config_id": "3d5ec2a8-7004-43b6-a7f6-542bdbe9d434",
"output_values": 'null',
"input_values": 'null',
"action": "CREATE",
"status_reason": "Deploy data available",
"id": "06e87bcc-33a2-4bce-aebd-533e698282d3",
"creation_time": "2015-01-31T15:12:36Z",
"updated_time": "2015-01-31T15:18:21Z"
}}
def setUp(self):
super(TestDeploymentShow, self).setUp()
self.cmd = software_deployment.ShowDeployment(self.app, None)
def test_deployment_show(self):
arglist = ['my_deployment']
cols = ['id', 'server_id', 'config_id', 'creation_time',
'updated_time', 'status', 'status_reason',
'input_values', 'action']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.sd_client.get.return_value = \
software_deployments.SoftwareDeployment(
None, self.get_response)
columns, data = self.cmd.take_action(parsed_args)
self.sd_client.get.assert_called_with(**{
'deployment_id': 'my_deployment',
})
self.assertEqual(cols, columns)
def test_deployment_show_long(self):
arglist = ['my_deployment', '--long']
cols = ['id', 'server_id', 'config_id', 'creation_time',
'updated_time', 'status', 'status_reason',
'input_values', 'action', 'output_values']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.sd_client.get.return_value = \
software_deployments.SoftwareDeployment(
None, self.get_response)
columns, data = self.cmd.take_action(parsed_args)
self.sd_client.get.assert_called_once_with(**{
'deployment_id': 'my_deployment',
})
self.assertEqual(cols, columns)
def test_deployment_not_found(self):
arglist = ['my_deployment']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.sd_client.get.side_effect = heat_exc.HTTPNotFound()
self.assertRaises(
exc.CommandError,
self.cmd.take_action,
parsed_args)
class TestDeploymentMetadataShow(TestDeployment):
def setUp(self):
super(TestDeploymentMetadataShow, self).setUp()
self.cmd = software_deployment.ShowMetadataDeployment(self.app, None)
self.sd_client.metadata.return_value = {}
def test_deployment_show_metadata(self):
arglist = ['ec14c864-096e-4e27-bb8a-2c2b4dc6f3f5']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
self.sd_client.metadata.assert_called_with(
server_id='ec14c864-096e-4e27-bb8a-2c2b4dc6f3f5')
class TestDeploymentOutputShow(TestDeployment):
get_response = {
"status": "IN_PROGRESS",
"server_id": "ec14c864-096e-4e27-bb8a-2c2b4dc6f3f5",
"config_id": "3d5ec2a8-7004-43b6-a7f6-542bdbe9d434",
"output_values": None,
"input_values": None,
"action": "CREATE",
"status_reason": "Deploy data available",
"id": "06e87bcc-33a2-4bce-aebd-533e698282d3",
"creation_time": "2015-01-31T15:12:36Z",
"updated_time": "2015-01-31T15:18:21Z"
}
def setUp(self):
super(TestDeploymentOutputShow, self).setUp()
self.cmd = software_deployment.ShowOutputDeployment(self.app, None)
def test_deployment_output_show(self):
arglist = ['85c3a507-351b-4b28-a7d8-531c8d53f4e6', '--all', '--long']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.sd_client.get.return_value = \
software_deployments.SoftwareDeployment(
None, self.get_response)
self.cmd.take_action(parsed_args)
self.sd_client.get.assert_called_with(**{
'deployment_id': '85c3a507-351b-4b28-a7d8-531c8d53f4e6'
})
def test_deployment_output_show_invalid(self):
arglist = ['85c3a507-351b-4b28-a7d8-531c8d53f4e6']
parsed_args = self.check_parser(self.cmd, arglist, [])
error = self.assertRaises(
exc.CommandError,
self.cmd.take_action,
parsed_args)
self.assertIn('either <output-name> or --all argument is needed',
str(error))
def test_deployment_output_show_not_found(self):
arglist = ['85c3a507-351b-4b28-a7d8-531c8d53f4e6', '--all']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.sd_client.get.side_effect = heat_exc.HTTPNotFound()
self.assertRaises(
exc.CommandError,
self.cmd.take_action,
parsed_args)

File diff suppressed because it is too large Load Diff

View File

@ -1,261 +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 collections
import mock
from heatclient import exc
from heatclient.osc.v1 import stack_failures
from heatclient.tests.unit.osc.v1 import fakes as orchestration_fakes
class ListStackFailuresTest(orchestration_fakes.TestOrchestrationv1):
def setUp(self):
super(ListStackFailuresTest, self).setUp()
self.cmd = stack_failures.ListStackFailures(self.app, None)
self.cmd.heat_client = self.app.client_manager.orchestration
self.stack_client = self.app.client_manager.orchestration.stacks
self.resource_client = self.app.client_manager.orchestration.resources
self.software_deployments_client = \
self.app.client_manager.orchestration.software_deployments
self.stack = mock.MagicMock(id='123', status='FAILED',
stack_name='stack')
self.stack_client.get.return_value = self.stack
self.failed_template_resource = mock.MagicMock(
physical_resource_id='aaaa',
resource_type='My::TemplateResource',
resource_status='CREATE_FAILED',
links=[{'rel': 'nested'}],
resource_name='my_templateresource',
resource_status_reason='All gone Pete Tong',
logical_resource_id='my_templateresource',
)
self.failed_resource = mock.MagicMock(
physical_resource_id='cccc',
resource_type='OS::Nova::Server',
resource_status='CREATE_FAILED',
links=[],
resource_name='my_server',
resource_status_reason='All gone Pete Tong',
logical_resource_id='my_server',
)
self.other_failed_template_resource = mock.MagicMock(
physical_resource_id='dddd',
resource_type='My::OtherTemplateResource',
resource_status='CREATE_FAILED',
links=[{'rel': 'nested'}],
resource_name='my_othertemplateresource',
resource_status_reason='RPC timeout',
logical_resource_id='my_othertemplateresource',
)
self.working_resource = mock.MagicMock(
physical_resource_id='bbbb',
resource_type='OS::Nova::Server',
resource_status='CREATE_COMPLETE',
resource_name='my_server',
)
self.failed_deployment_resource = mock.MagicMock(
physical_resource_id='eeee',
resource_type='OS::Heat::SoftwareDeployment',
resource_status='CREATE_FAILED',
links=[],
resource_name='my_deployment',
resource_status_reason='Returned deploy_statuscode 1',
logical_resource_id='my_deployment',
)
self.failed_deployment = mock.MagicMock(
id='eeee',
output_values={
'deploy_statuscode': '1',
'deploy_stderr': 'It broke',
'deploy_stdout': ('1\n2\n3\n4\n5\n6\n7\n8\n9\n10'
'\n11\n12')
},
)
self.software_deployments_client.get.return_value = (
self.failed_deployment)
def test_build_failed_none(self):
self.stack = mock.MagicMock(id='123', status='COMPLETE',
stack_name='stack')
failures = self.cmd._build_failed_resources('stack')
expected = collections.OrderedDict()
self.assertEqual(expected, failures)
def test_build_failed_resources(self):
self.resource_client.list.side_effect = [[
# resource-list stack
self.failed_template_resource,
self.other_failed_template_resource,
self.working_resource,
], [ # resource-list aaaa
self.failed_resource
], [ # resource-list dddd
]]
failures = self.cmd._build_failed_resources('stack')
expected = collections.OrderedDict()
expected['stack.my_templateresource.my_server'] = self.failed_resource
expected['stack.my_othertemplateresource'] = (
self.other_failed_template_resource)
self.assertEqual(expected, failures)
def test_build_failed_resources_not_found(self):
self.resource_client.list.side_effect = [[
# resource-list stack
self.failed_template_resource,
self.other_failed_template_resource,
self.working_resource,
], exc.HTTPNotFound(), [ # resource-list dddd
]]
failures = self.cmd._build_failed_resources('stack')
expected = collections.OrderedDict()
expected['stack.my_templateresource'] = self.failed_template_resource
expected['stack.my_othertemplateresource'] = (
self.other_failed_template_resource)
self.assertEqual(expected, failures)
def test_build_software_deployments(self):
resources = {
'stack.my_server': self.working_resource,
'stack.my_deployment': self.failed_deployment_resource
}
deployments = self.cmd._build_software_deployments(resources)
self.assertEqual({
'eeee': self.failed_deployment
}, deployments)
def test_build_software_deployments_not_found(self):
resources = {
'stack.my_server': self.working_resource,
'stack.my_deployment': self.failed_deployment_resource
}
self.software_deployments_client.get.side_effect = exc.HTTPNotFound()
deployments = self.cmd._build_software_deployments(resources)
self.assertEqual({}, deployments)
def test_build_software_deployments_no_resources(self):
resources = {}
self.software_deployments_client.get.side_effect = exc.HTTPNotFound()
deployments = self.cmd._build_software_deployments(resources)
self.assertEqual({}, deployments)
def test_list_stack_failures(self):
self.resource_client.list.side_effect = [[
# resource-list stack
self.failed_template_resource,
self.other_failed_template_resource,
self.working_resource,
self.failed_deployment_resource
], [ # resource-list aaaa
self.failed_resource
], [ # resource-list dddd
]]
arglist = ['stack']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
self.assertEqual(
self.app.stdout.make_string(),
'''stack.my_templateresource.my_server:
resource_type: OS::Nova::Server
physical_resource_id: cccc
status: CREATE_FAILED
status_reason: |
All gone Pete Tong
stack.my_othertemplateresource:
resource_type: My::OtherTemplateResource
physical_resource_id: dddd
status: CREATE_FAILED
status_reason: |
RPC timeout
stack.my_deployment:
resource_type: OS::Heat::SoftwareDeployment
physical_resource_id: eeee
status: CREATE_FAILED
status_reason: |
Returned deploy_statuscode 1
deploy_stdout: |
...
3
4
5
6
7
8
9
10
11
12
(truncated, view all with --long)
deploy_stderr: |
It broke
''')
def test_list_stack_failures_long(self):
self.resource_client.list.side_effect = [[
# resource-list stack
self.failed_template_resource,
self.other_failed_template_resource,
self.working_resource,
self.failed_deployment_resource
], [ # resource-list aaaa
self.failed_resource
], [ # resource-list dddd
]]
arglist = ['--long', 'stack']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
self.assertEqual(
self.app.stdout.make_string(),
'''stack.my_templateresource.my_server:
resource_type: OS::Nova::Server
physical_resource_id: cccc
status: CREATE_FAILED
status_reason: |
All gone Pete Tong
stack.my_othertemplateresource:
resource_type: My::OtherTemplateResource
physical_resource_id: dddd
status: CREATE_FAILED
status_reason: |
RPC timeout
stack.my_deployment:
resource_type: OS::Heat::SoftwareDeployment
physical_resource_id: eeee
status: CREATE_FAILED
status_reason: |
Returned deploy_statuscode 1
deploy_stdout: |
1
2
3
4
5
6
7
8
9
10
11
12
deploy_stderr: |
It broke
''')

View File

@ -1,182 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# Copyright 2015 IBM Corp.
import mock
from heatclient import exc
from heatclient.osc.v1 import template
from heatclient.tests.unit.osc.v1 import fakes
from heatclient.v1 import template_versions
class TestTemplate(fakes.TestOrchestrationv1):
def setUp(self):
super(TestTemplate, self).setUp()
self.mock_client = self.app.client_manager.orchestration
self.template_versions = self.mock_client.template_versions
class TestTemplateVersionList(TestTemplate):
def _stub_versions_list(self, ret_data):
tv1 = template_versions.TemplateVersion(None, ret_data[0])
tv2 = template_versions.TemplateVersion(None, ret_data[1])
self.template_versions.list.return_value = [tv1, tv2]
self.cmd = template.VersionList(self.app, None)
def test_version_list(self):
ret_data = [
{'version': 'HOT123', 'type': 'hot'},
{'version': 'CFN456', 'type': 'cfn'}]
self._stub_versions_list(ret_data)
parsed_args = self.check_parser(self.cmd, [], [])
columns, data = self.cmd.take_action(parsed_args)
self.assertEqual(['Version', 'Type'], columns)
self.assertEqual([('HOT123', 'hot'), ('CFN456', 'cfn')], list(data))
def test_version_list_with_aliases(self):
ret_data = [
{'version': 'HOT123', 'type': 'hot', 'aliases': ['releasex']},
{'version': 'CFN456', 'type': 'cfn', 'aliases': ['releasey']}]
self._stub_versions_list(ret_data)
parsed_args = self.check_parser(self.cmd, [], [])
columns, data = self.cmd.take_action(parsed_args)
self.assertEqual(['Version', 'Type', 'Aliases'], columns)
self.assertEqual([('HOT123', 'hot', 'releasex'),
('CFN456', 'cfn', 'releasey')], list(data))
class TestTemplateFunctionList(TestTemplate):
defaults = [
{'functions': 'func1', 'description': 'Function 1'},
{'functions': 'func2', 'description': 'Function 2'},
{'functions': 'condition func', 'description': 'Condition Function'}
]
def setUp(self):
super(TestTemplateFunctionList, self).setUp()
self.tv1 = template_versions.TemplateVersion(None, self.defaults[0])
self.tv2 = template_versions.TemplateVersion(None, self.defaults[1])
self.tv_with_cf = template_versions.TemplateVersion(
None, self.defaults[2])
self.cmd = template.FunctionList(self.app, None)
def test_function_list(self):
arglist = ['version1']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.template_versions.get.return_value = [self.tv1, self.tv2]
columns, data = self.cmd.take_action(parsed_args)
self.assertEqual(['Functions', 'Description'], columns)
self.assertEqual([('func1', 'Function 1'), ('func2', 'Function 2')],
list(data))
def test_function_list_with_condition_func(self):
arglist = ['version1', '--with_conditions']
parsed_args = self.check_parser(self.cmd, arglist, [])
self.template_versions.get.return_value = [self.tv1, self.tv2,
self.tv_with_cf]
columns, data = self.cmd.take_action(parsed_args)
self.assertEqual(['Functions', 'Description'], columns)
self.assertEqual([('func1', 'Function 1'),
('func2', 'Function 2'),
('condition func', 'Condition Function')],
list(data))
def test_function_list_not_found(self):
arglist = ['bad_version']
self.template_versions.get.side_effect = exc.HTTPNotFound
parsed_args = self.check_parser(self.cmd, arglist, [])
self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args)
class TestTemplateValidate(TestTemplate):
template_path = 'heatclient/tests/test_templates/empty.yaml'
env_path = 'heatclient/tests/unit/var/environment.json'
defaults = {
'environment': {},
'files': {},
'parameters': {},
'template': {'heat_template_version': '2013-05-23'}
}
def setUp(self):
super(TestTemplateValidate, self).setUp()
self.stack_client = self.app.client_manager.orchestration.stacks
self.stack_client.validate = mock.MagicMock(return_value={})
self.cmd = template.Validate(self.app, None)
def test_validate(self):
arglist = ['-t', self.template_path]
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, data = self.cmd.take_action(parsed_args)
self.stack_client.validate.assert_called_once_with(**self.defaults)
self.assertEqual([], columns)
self.assertEqual([], data)
def test_validate_env(self):
arglist = ['-t', self.template_path, '-e', self.env_path]
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, data = self.cmd.take_action(parsed_args)
self.assertEqual(1, self.stack_client.validate.call_count)
args = self.stack_client.validate.call_args[1]
self.assertEqual(args.get('environment'), {'parameters': {}})
self.assertIn(self.env_path, args.get('environment_files')[0])
self.assertEqual([], columns)
self.assertEqual([], data)
def test_validate_nested(self):
arglist = ['-t', self.template_path, '--show-nested']
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, data = self.cmd.take_action(parsed_args)
args = dict(self.defaults)
args['show_nested'] = True
self.stack_client.validate.assert_called_once_with(**args)
self.assertEqual([], columns)
self.assertEqual([], data)
def test_validate_parameter(self):
arglist = ['-t', self.template_path,
'--parameter', 'key1=value1',
'--parameter', 'key2=value2']
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, data = self.cmd.take_action(parsed_args)
args = dict(self.defaults)
args['parameters'] = {'key1': 'value1', 'key2': 'value2'}
self.stack_client.validate.assert_called_once_with(**args)
self.assertEqual([], columns)
self.assertEqual([], data)
def test_validate_ignore_errors(self):
arglist = ['-t', self.template_path,
'--ignore-errors', 'err1,err2']
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, data = self.cmd.take_action(parsed_args)
args = dict(self.defaults)
args['ignore_errors'] = 'err1,err2'
self.stack_client.validate.assert_called_once_with(**args)
self.assertEqual([], columns)
self.assertEqual([], data)

View File

@ -1,108 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import testtools
from heatclient.tests.unit import fakes
from heatclient.v1 import actions
class ActionManagerTest(testtools.TestCase):
def setUp(self):
super(ActionManagerTest, self).setUp()
def _base_test(self, expect_args, expect_kwargs):
class FakeAPI(object):
"""Fake API and ensure request url is correct."""
def json_request(self, *args, **kwargs):
assert expect_args == args
assert expect_kwargs['data'] == kwargs['data']
return fakes.FakeHTTPResponse(
'200',
'',
{'content-type': 'application/json'},
{}), {}
def raw_request(self, *args, **kwargs):
assert expect_args == args
return fakes.FakeHTTPResponse(
'200',
'',
{},
{})
def head(self, url, **kwargs):
resp, body = self.json_request("HEAD", url, **kwargs)
return resp
def get(self, url, **kwargs):
resp, body = self.json_request("GET", url, **kwargs)
return resp
def post(self, url, **kwargs):
resp, body = self.json_request("POST", url, **kwargs)
return resp
def put(self, url, **kwargs):
resp, body = self.json_request("PUT", url, **kwargs)
return resp
def delete(self, url, **kwargs):
resp, body = self.raw_request("DELETE", url, **kwargs)
return resp
def patch(self, url, **kwargs):
resp, body = self.json_request("PATCH", url, **kwargs)
return resp
manager = actions.ActionManager(FakeAPI())
return manager
def test_suspend(self):
fields = {'stack_id': 'teststack%2Fabcd1234'}
expect_args = ('POST',
'/stacks/teststack%2Fabcd1234/actions')
expect_kwargs = {'data': {'suspend': None}}
manager = self._base_test(expect_args, expect_kwargs)
manager.suspend(**fields)
def test_resume(self):
fields = {'stack_id': 'teststack%2Fabcd1234'}
expect_args = ('POST',
'/stacks/teststack%2Fabcd1234/actions')
expect_kwargs = {'data': {'resume': None}}
manager = self._base_test(expect_args, expect_kwargs)
manager.resume(**fields)
def test_cancel_update(self):
fields = {'stack_id': 'teststack%2Fabcd1234'}
expect_args = ('POST',
'/stacks/teststack%2Fabcd1234/actions')
expect_kwargs = {'data': {'cancel_update': None}}
manager = self._base_test(expect_args, expect_kwargs)
manager.cancel_update(**fields)
def test_check(self):
fields = {'stack_id': 'teststack%2Fabcd1234'}
expect_args = ('POST',
'/stacks/teststack%2Fabcd1234/actions')
expect_kwargs = {'data': {'check': None}}
manager = self._base_test(expect_args, expect_kwargs)
manager.check(**fields)

View File

@ -1,42 +0,0 @@
# Copyright 2012 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from oslo_serialization import jsonutils
import testtools
from heatclient.tests.unit import fakes
from heatclient.v1 import build_info
class BuildInfoManagerTest(testtools.TestCase):
def setUp(self):
super(BuildInfoManagerTest, self).setUp()
self.client = mock.Mock()
self.client.get.return_value = fakes.FakeHTTPResponse(
200,
None,
{'content-type': 'application/json'},
jsonutils.dumps('body')
)
self.manager = build_info.BuildInfoManager(self.client)
def test_build_info_makes_a_call_to_the_api(self):
self.manager.build_info()
self.client.get.assert_called_once_with('/build_info')
def test_build_info_returns_the_response_body(self):
response = self.manager.build_info()
self.assertEqual('body', response)

View File

@ -1,799 +0,0 @@
# -*- coding:utf-8 -*-
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import socket
from keystoneauth1 import adapter
import mock
from oslo_serialization import jsonutils
import six
import testtools
from heatclient.common import http
from heatclient.common import utils
from heatclient import exc
from heatclient.tests.unit import fakes
@mock.patch('heatclient.common.http.requests.request')
class HttpClientTest(testtools.TestCase):
def test_http_raw_request(self, mock_request):
headers = {'Content-Type': 'application/octet-stream',
'User-Agent': 'python-heatclient'}
# Record a 200
mock_request.return_value = fakes.FakeHTTPResponse(
200, 'OK',
{'content-type': 'application/octet-stream'},
'')
# Replay, create client, assert
client = http.HTTPClient('http://example.com:8004')
resp = client.raw_request('GET', '')
self.assertEqual(200, resp.status_code)
self.assertEqual('', ''.join([x for x in resp.content]))
mock_request.assert_called_with('GET', 'http://example.com:8004',
allow_redirects=False,
headers=headers)
def test_token_or_credentials(self, mock_request):
# Record a 200
fake200 = fakes.FakeHTTPResponse(
200, 'OK',
{'content-type': 'application/octet-stream'},
'')
mock_request.return_value = fake200
# no token or credentials
client = http.HTTPClient('http://example.com:8004')
resp = client.raw_request('GET', '')
self.assertEqual(200, resp.status_code)
# credentials
client.username = 'user'
client.password = 'pass'
resp = client.raw_request('GET', '')
self.assertEqual(200, resp.status_code)
# token suppresses credentials
client.auth_token = 'abcd1234'
resp = client.raw_request('GET', '')
self.assertEqual(200, resp.status_code)
mock_request.assert_has_calls([
mock.call('GET', 'http://example.com:8004',
allow_redirects=False,
headers={'Content-Type': 'application/octet-stream',
'User-Agent': 'python-heatclient'}),
mock.call('GET', 'http://example.com:8004',
allow_redirects=False,
headers={'Content-Type': 'application/octet-stream',
'User-Agent': 'python-heatclient',
'X-Auth-Key': 'pass',
'X-Auth-User': 'user'}),
mock.call('GET', 'http://example.com:8004',
allow_redirects=False,
headers={'Content-Type': 'application/octet-stream',
'User-Agent': 'python-heatclient',
'X-Auth-Token': 'abcd1234'})
])
def test_include_pass(self, mock_request):
# Record a 200
fake200 = fakes.FakeHTTPResponse(
200, 'OK',
{'content-type': 'application/octet-stream'},
'')
mock_request.return_value = fake200
# no token or credentials
client = http.HTTPClient('http://example.com:8004')
resp = client.raw_request('GET', '')
self.assertEqual(200, resp.status_code)
# credentials
client.username = 'user'
client.password = 'pass'
client.include_pass = True
resp = client.raw_request('GET', '')
self.assertEqual(200, resp.status_code)
# token suppresses credentials
client.auth_token = 'abcd1234'
resp = client.raw_request('GET', '')
self.assertEqual(200, resp.status_code)
mock_request.assert_has_calls([
mock.call('GET', 'http://example.com:8004',
allow_redirects=False,
headers={'Content-Type': 'application/octet-stream',
'User-Agent': 'python-heatclient'}),
mock.call('GET', 'http://example.com:8004',
allow_redirects=False,
headers={'Content-Type': 'application/octet-stream',
'User-Agent': 'python-heatclient',
'X-Auth-Key': 'pass',
'X-Auth-User': 'user'}),
mock.call('GET', 'http://example.com:8004',
allow_redirects=False,
headers={'Content-Type': 'application/octet-stream',
'User-Agent': 'python-heatclient',
'X-Auth-Token': 'abcd1234',
'X-Auth-Key': 'pass',
'X-Auth-User': 'user'})
])
def test_not_include_pass(self, mock_request):
# Record a 200
fake500 = fakes.FakeHTTPResponse(
500, 'ERROR',
{'content-type': 'application/octet-stream'},
'(HTTP 401)')
# no token or credentials
mock_request.return_value = fake500
# Replay, create client, assert
client = http.HTTPClient('http://example.com:8004')
e = self.assertRaises(exc.HTTPUnauthorized,
client.raw_request, 'GET', '')
self.assertIn('Authentication failed', str(e))
mock_request.assert_called_with(
'GET', 'http://example.com:8004',
allow_redirects=False,
headers={'Content-Type': 'application/octet-stream',
'User-Agent': 'python-heatclient'})
def test_region_name(self, mock_request):
# Record a 200
fake200 = fakes.FakeHTTPResponse(
200, 'OK',
{'content-type': 'application/octet-stream'},
'')
# Specify region name
mock_request.return_value = fake200
# Replay, create client, assert
client = http.HTTPClient('http://example.com:8004')
client.region_name = 'RegionOne'
resp = client.raw_request('GET', '')
self.assertEqual(200, resp.status_code)
mock_request.assert_called_with(
'GET', 'http://example.com:8004',
allow_redirects=False,
headers={'Content-Type': 'application/octet-stream',
'X-Region-Name': 'RegionOne',
'User-Agent': 'python-heatclient'})
def test_http_json_request(self, mock_request):
# Record a 200
mock_request.return_value = fakes.FakeHTTPResponse(
200, 'OK',
{'content-type': 'application/json'},
'{}')
# Replay, create client, assert
client = http.HTTPClient('http://example.com:8004')
resp, body = client.json_request('GET', '')
self.assertEqual(200, resp.status_code)
self.assertEqual({}, body)
mock_request.assert_called_with(
'GET', 'http://example.com:8004',
allow_redirects=False,
headers={'Content-Type': 'application/json',
'Accept': 'application/json',
'User-Agent': 'python-heatclient'})
def test_http_json_request_argument_passed_to_requests(self, mock_request):
"""Check that we have sent the proper arguments to requests."""
# Record a 200
mock_request.return_value = fakes.FakeHTTPResponse(
200, 'OK',
{'content-type': 'application/json'},
'{}')
client = http.HTTPClient('http://example.com:8004')
client.verify_cert = True
client.cert_file = 'RANDOM_CERT_FILE'
client.key_file = 'RANDOM_KEY_FILE'
client.auth_url = 'http://AUTH_URL'
resp, body = client.json_request('GET', '', data='text')
self.assertEqual(200, resp.status_code)
self.assertEqual({}, body)
mock_request.assert_called_with(
'GET', 'http://example.com:8004',
allow_redirects=False,
cert=('RANDOM_CERT_FILE', 'RANDOM_KEY_FILE'),
verify=True,
data='"text"',
headers={'Content-Type': 'application/json',
'Accept': 'application/json',
'X-Auth-Url': 'http://AUTH_URL',
'User-Agent': 'python-heatclient'})
def test_http_json_request_w_req_body(self, mock_request):
# Record a 200
mock_request.return_value = fakes.FakeHTTPResponse(
200, 'OK',
{'content-type': 'application/json'},
'{}')
# Replay, create client, assert
client = http.HTTPClient('http://example.com:8004')
resp, body = client.json_request('GET', '', body='test-body')
self.assertEqual(200, resp.status_code)
mock_request.assert_called_with(
'GET', 'http://example.com:8004',
body='test-body',
allow_redirects=False,
headers={'Content-Type': 'application/json',
'Accept': 'application/json',
'User-Agent': 'python-heatclient'})
def test_http_json_request_non_json_resp_cont_type(self, mock_request):
# Record a 200i
mock_request.return_value = fakes.FakeHTTPResponse(
200, 'OK',
{'content-type': 'not/json'},
'{}')
# Replay, create client, assert
client = http.HTTPClient('http://example.com:8004')
resp, body = client.json_request('GET', '', body='test-body')
self.assertEqual(200, resp.status_code)
self.assertIsNone(body)
mock_request.assert_called_with(
'GET', 'http://example.com:8004',
body='test-body',
allow_redirects=False,
headers={'Content-Type': 'application/json',
'Accept': 'application/json',
'User-Agent': 'python-heatclient'})
def test_http_json_request_invalid_json(self, mock_request):
# Record a 200
mock_request.return_value = fakes.FakeHTTPResponse(
200, 'OK',
{'content-type': 'application/json'},
'invalid-json')
# Replay, create client, assert
client = http.HTTPClient('http://example.com:8004')
resp, body = client.json_request('GET', '')
self.assertEqual(200, resp.status_code)
self.assertEqual('invalid-json', body)
mock_request.assert_called_with(
'GET', 'http://example.com:8004',
allow_redirects=False,
headers={'Content-Type': 'application/json',
'Accept': 'application/json',
'User-Agent': 'python-heatclient'})
def test_http_manual_redirect_delete(self, mock_request):
mock_request.side_effect = [
fakes.FakeHTTPResponse(
302, 'Found',
{'location': 'http://example.com:8004/foo/bar'},
''),
fakes.FakeHTTPResponse(
200, 'OK',
{'content-type': 'application/json'},
'invalid-json')
]
client = http.HTTPClient('http://example.com:8004/foo')
resp, body = client.json_request('DELETE', '')
mock_request.assert_has_calls([
mock.call('DELETE', 'http://example.com:8004/foo',
allow_redirects=False,
headers={'Content-Type': 'application/json',
'Accept': 'application/json',
'User-Agent': 'python-heatclient'}),
mock.call('DELETE', 'http://example.com:8004/foo/bar',
allow_redirects=False,
headers={'Content-Type': 'application/json',
'Accept': 'application/json',
'User-Agent': 'python-heatclient'})
])
def test_http_manual_redirect_post(self, mock_request):
mock_request.side_effect = [
fakes.FakeHTTPResponse(
302, 'Found',
{'location': 'http://example.com:8004/foo/bar'},
''),
fakes.FakeHTTPResponse(
200, 'OK',
{'content-type': 'application/json'},
'invalid-json')
]
client = http.HTTPClient('http://example.com:8004/foo')
resp, body = client.json_request('POST', '')
mock_request.assert_has_calls([
mock.call('POST', 'http://example.com:8004/foo',
allow_redirects=False,
headers={'Content-Type': 'application/json',
'Accept': 'application/json',
'User-Agent': 'python-heatclient'}),
mock.call('POST', 'http://example.com:8004/foo/bar',
allow_redirects=False,
headers={'Content-Type': 'application/json',
'Accept': 'application/json',
'User-Agent': 'python-heatclient'})
])
def test_http_manual_redirect_put(self, mock_request):
mock_request.side_effect = [
fakes.FakeHTTPResponse(
302, 'Found',
{'location': 'http://example.com:8004/foo/bar'},
''),
fakes.FakeHTTPResponse(
200, 'OK',
{'content-type': 'application/json'},
'invalid-json')
]
client = http.HTTPClient('http://example.com:8004/foo')
resp, body = client.json_request('PUT', '')
mock_request.assert_has_calls([
mock.call('PUT', 'http://example.com:8004/foo',
allow_redirects=False,
headers={'Content-Type': 'application/json',
'Accept': 'application/json',
'User-Agent': 'python-heatclient'}),
mock.call('PUT', 'http://example.com:8004/foo/bar',
allow_redirects=False,
headers={'Content-Type': 'application/json',
'Accept': 'application/json',
'User-Agent': 'python-heatclient'})
])
def test_http_manual_redirect_put_uppercase(self, mock_request):
mock_request.side_effect = [
fakes.FakeHTTPResponse(
302, 'Found',
{'location': 'http://example.com:8004/foo/bar'},
''),
fakes.FakeHTTPResponse(
200, 'OK',
{'content-type': 'application/json'},
'invalid-json')
]
client = http.HTTPClient('http://EXAMPLE.com:8004/foo')
resp, body = client.json_request('PUT', '')
self.assertEqual(200, resp.status_code)
mock_request.assert_has_calls([
mock.call('PUT', 'http://EXAMPLE.com:8004/foo',
allow_redirects=False,
headers={'Content-Type': 'application/json',
'Accept': 'application/json',
'User-Agent': 'python-heatclient'}),
mock.call('PUT', 'http://example.com:8004/foo/bar',
allow_redirects=False,
headers={'Content-Type': 'application/json',
'Accept': 'application/json',
'User-Agent': 'python-heatclient'})
])
def test_http_manual_redirect_error_without_location(self, mock_request):
mock_request.return_value = fakes.FakeHTTPResponse(
302, 'Found',
{},
'')
client = http.HTTPClient('http://example.com:8004/foo')
self.assertRaises(exc.InvalidEndpoint,
client.json_request, 'DELETE', '')
mock_request.assert_called_once_with(
'DELETE', 'http://example.com:8004/foo',
allow_redirects=False,
headers={'Content-Type': 'application/json',
'Accept': 'application/json',
'User-Agent': 'python-heatclient'})
def test_http_json_request_redirect(self, mock_request):
# Record the 302
mock_request.side_effect = [
fakes.FakeHTTPResponse(
302, 'Found',
{'location': 'http://example.com:8004'},
''),
fakes.FakeHTTPResponse(
200, 'OK',
{'content-type': 'application/json'},
'{}')
]
client = http.HTTPClient('http://example.com:8004')
resp, body = client.json_request('GET', '')
self.assertEqual(200, resp.status_code)
self.assertEqual({}, body)
mock_request.assert_has_calls([
mock.call('GET', 'http://example.com:8004',
allow_redirects=False,
headers={'Content-Type': 'application/json',
'Accept': 'application/json',
'User-Agent': 'python-heatclient'}),
mock.call('GET', 'http://example.com:8004',
allow_redirects=False,
headers={'Content-Type': 'application/json',
'Accept': 'application/json',
'User-Agent': 'python-heatclient'})
])
def test_http_404_json_request(self, mock_request):
# Record a 404
mock_request.return_value = fakes.FakeHTTPResponse(
404, 'OK', {'content-type': 'application/json'},
'{}')
client = http.HTTPClient('http://example.com:8004')
e = self.assertRaises(exc.HTTPNotFound, client.json_request, 'GET', '')
# Assert that the raised exception can be converted to string
self.assertIsNotNone(str(e))
mock_request.assert_called_with(
'GET', 'http://example.com:8004',
allow_redirects=False,
headers={'Content-Type': 'application/json',
'Accept': 'application/json',
'User-Agent': 'python-heatclient'})
def test_http_300_json_request(self, mock_request):
# Record a 300
mock_request.return_value = fakes.FakeHTTPResponse(
300, 'OK', {'content-type': 'application/json'},
'{}')
# Assert that the raised exception can be converted to string
client = http.HTTPClient('http://example.com:8004')
e = self.assertRaises(
exc.HTTPMultipleChoices, client.json_request, 'GET', '')
self.assertIsNotNone(str(e))
mock_request.assert_called_with(
'GET', 'http://example.com:8004',
allow_redirects=False,
headers={'Content-Type': 'application/json',
'Accept': 'application/json',
'User-Agent': 'python-heatclient'})
def test_fake_json_request(self, mock_request):
headers = {'User-Agent': 'python-heatclient'}
mock_request.side_effect = [socket.gaierror]
client = http.HTTPClient('fake://example.com:8004')
self.assertRaises(exc.InvalidEndpoint,
client._http_request, "/", "GET")
mock_request.assert_called_with('GET', 'fake://example.com:8004/',
allow_redirects=False,
headers=headers)
def test_debug_curl_command(self, mock_request):
with mock.patch('logging.Logger.debug') as mock_logging_debug:
ssl_connection_params = {'ca_file': 'TEST_CA',
'cert_file': 'TEST_CERT',
'key_file': 'TEST_KEY',
'insecure': 'TEST_NSA'}
headers = {'key': 'value'}
mock_logging_debug.return_value = None
client = http.HTTPClient('http://foo')
client.ssl_connection_params = ssl_connection_params
client.log_curl_request('GET', '/bar', {'headers': headers,
'data': 'text'})
mock_logging_debug.assert_called_with(
"curl -g -i -X GET -H 'key: value' --key TEST_KEY "
"--cert TEST_CERT --cacert TEST_CA "
"-k -d 'text' http://foo/bar"
)
def test_http_request_socket_error(self, mock_request):
headers = {'User-Agent': 'python-heatclient'}
mock_request.side_effect = [socket.error]
client = http.HTTPClient('http://example.com:8004')
self.assertRaises(exc.CommunicationError,
client._http_request, "/", "GET")
mock_request.assert_called_with('GET', 'http://example.com:8004/',
allow_redirects=False,
headers=headers)
def test_http_request_socket_timeout(self, mock_request):
headers = {'User-Agent': 'python-heatclient'}
mock_request.side_effect = [socket.timeout]
client = http.HTTPClient('http://example.com:8004')
self.assertRaises(exc.CommunicationError,
client._http_request, "/", "GET")
mock_request.assert_called_with('GET', 'http://example.com:8004/',
allow_redirects=False,
headers=headers)
def test_http_request_specify_timeout(self, mock_request):
mock_request.return_value = fakes.FakeHTTPResponse(
200, 'OK',
{'content-type': 'application/json'},
'{}')
client = http.HTTPClient('http://example.com:8004', timeout='123')
resp, body = client.json_request('GET', '')
self.assertEqual(200, resp.status_code)
mock_request.assert_called_with(
'GET', 'http://example.com:8004',
allow_redirects=False,
headers={'Content-Type': 'application/json',
'Accept': 'application/json',
'User-Agent': 'python-heatclient'},
timeout=float(123))
def test_get_system_ca_file(self, mock_request):
chosen = '/etc/ssl/certs/ca-certificates.crt'
with mock.patch('os.path.exists') as mock_os:
mock_os.return_value = chosen
ca = http.get_system_ca_file()
self.assertEqual(chosen, ca)
mock_os.assert_called_once_with(chosen)
def test_insecure_verify_cert_None(self, mock_request):
client = http.HTTPClient('https://foo', insecure=True)
self.assertFalse(client.verify_cert)
def test_passed_cert_to_verify_cert(self, mock_request):
client = http.HTTPClient('https://foo', ca_file="NOWHERE")
self.assertEqual("NOWHERE", client.verify_cert)
with mock.patch('heatclient.common.http.get_system_ca_file') as gsf:
gsf.return_value = "SOMEWHERE"
client = http.HTTPClient('https://foo')
self.assertEqual("SOMEWHERE", client.verify_cert)
@mock.patch('logging.Logger.debug', return_value=None)
def test_curl_log_i18n_headers(self, mock_log, mock_request):
kwargs = {'headers': {'Key': b'foo\xe3\x8a\x8e'}}
client = http.HTTPClient('http://somewhere')
client.log_curl_request("GET", '', kwargs=kwargs)
mock_log.assert_called_once_with(
u"curl -g -i -X GET -H 'Key: foo㊎' http://somewhere")
class SessionClientTest(testtools.TestCase):
def setUp(self):
super(SessionClientTest, self).setUp()
self.request = mock.patch.object(adapter.LegacyJsonAdapter,
'request').start()
def test_session_simple_request(self):
resp = fakes.FakeHTTPResponse(
200,
'OK',
{'content-type': 'application/octet-stream'},
'')
self.request.return_value = (resp, '')
client = http.SessionClient(session=mock.ANY,
auth=mock.ANY)
response = client.request(method='GET', url='')
self.assertEqual(200, response.status_code)
self.assertEqual('', ''.join([x for x in response.content]))
def test_session_json_request(self):
fake = fakes.FakeHTTPResponse(
200,
'OK',
{'content-type': 'application/json'},
jsonutils.dumps({'some': 'body'}))
self.request.return_value = (fake, {})
client = http.SessionClient(session=mock.ANY,
auth=mock.ANY)
resp = client.request('', 'GET')
self.assertEqual(200, resp.status_code)
self.assertEqual({'some': 'body'}, resp.json())
def test_404_error_response(self):
fake = fakes.FakeHTTPResponse(
404,
'FAIL',
{'content-type': 'application/octet-stream'},
'')
self.request.return_value = (fake, '')
client = http.SessionClient(session=mock.ANY,
auth=mock.ANY)
e = self.assertRaises(exc.HTTPNotFound,
client.request, '', 'GET')
# Assert that the raised exception can be converted to string
self.assertIsNotNone(six.text_type(e))
def test_redirect_302_location(self):
fake1 = fakes.FakeHTTPResponse(
302,
'OK',
{'location': 'http://no.where/ishere'},
''
)
fake2 = fakes.FakeHTTPResponse(
200,
'OK',
{'content-type': 'application/json'},
jsonutils.dumps({'Mount': 'Fuji'})
)
self.request.side_effect = [
(fake1, ''), (fake2, jsonutils.dumps({'Mount': 'Fuji'}))]
client = http.SessionClient(session=mock.ANY,
auth=mock.ANY,
endpoint_override='http://no.where/')
resp = client.request('', 'GET', redirect=True)
self.assertEqual(200, resp.status_code)
self.assertEqual({'Mount': 'Fuji'}, utils.get_response_body(resp))
self.assertEqual(('', 'GET'), self.request.call_args_list[0][0])
self.assertEqual(('ishere', 'GET'), self.request.call_args_list[1][0])
for call in self.request.call_args_list:
self.assertEqual({'user_agent': 'python-heatclient',
'raise_exc': False,
'redirect': True}, call[1])
def test_302_location_no_endpoint(self):
fake1 = fakes.FakeHTTPResponse(
302,
'OK',
{'location': 'http://no.where/ishere'},
''
)
fake2 = fakes.FakeHTTPResponse(
200,
'OK',
{'content-type': 'application/json'},
jsonutils.dumps({'Mount': 'Fuji'})
)
self.request.side_effect = [
(fake1, ''), (fake2, jsonutils.dumps({'Mount': 'Fuji'}))]
client = http.SessionClient(session=mock.ANY,
auth=mock.ANY)
resp = client.request('', 'GET', redirect=True)
self.assertEqual(200, resp.status_code)
self.assertEqual({'Mount': 'Fuji'}, utils.get_response_body(resp))
self.assertEqual(('', 'GET'), self.request.call_args_list[0][0])
self.assertEqual(('http://no.where/ishere',
'GET'), self.request.call_args_list[1][0])
for call in self.request.call_args_list:
self.assertEqual({'user_agent': 'python-heatclient',
'raise_exc': False,
'redirect': True}, call[1])
def test_redirect_302_no_location(self):
fake = fakes.FakeHTTPResponse(
302,
'OK',
{},
''
)
self.request.side_effect = [(fake, '')]
client = http.SessionClient(session=mock.ANY,
auth=mock.ANY)
e = self.assertRaises(exc.InvalidEndpoint,
client.request, '', 'GET', redirect=True)
self.assertEqual("Location not returned with 302", six.text_type(e))
def test_no_redirect_302_no_location(self):
fake = fakes.FakeHTTPResponse(
302,
'OK',
{'location': 'http://no.where/ishere'},
''
)
self.request.side_effect = [(fake, '')]
client = http.SessionClient(session=mock.ANY,
auth=mock.ANY)
self.assertEqual(fake, client.request('', 'GET'))
def test_300_error_response(self):
fake = fakes.FakeHTTPResponse(
300,
'FAIL',
{'content-type': 'application/octet-stream'},
'')
self.request.return_value = (fake, '')
client = http.SessionClient(session=mock.ANY,
auth=mock.ANY)
e = self.assertRaises(exc.HTTPMultipleChoices,
client.request, '', 'GET')
# Assert that the raised exception can be converted to string
self.assertIsNotNone(six.text_type(e))
def test_504_error_response(self):
# for 504 we don't have specific exception type
fake = fakes.FakeHTTPResponse(
504,
'FAIL',
{'content-type': 'application/octet-stream'},
'')
self.request.return_value = (fake, '')
client = http.SessionClient(session=mock.ANY,
auth=mock.ANY)
e = self.assertRaises(exc.HTTPException,
client.request, '', 'GET')
self.assertEqual(504, e.code)
def test_kwargs(self):
fake = fakes.FakeHTTPResponse(
200,
'OK',
{'content-type': 'application/json'},
{}
)
kwargs = dict(endpoint_override='http://no.where/',
data='some_data')
client = http.SessionClient(mock.ANY)
self.request.return_value = (fake, {})
resp = client.request('', 'GET', **kwargs)
self.assertEqual({'endpoint_override': 'http://no.where/',
'data': '"some_data"',
'user_agent': 'python-heatclient',
'raise_exc': False}, self.request.call_args[1])
self.assertEqual(200, resp.status_code)
@mock.patch.object(jsonutils, 'dumps')
def test_kwargs_with_files(self, mock_dumps):
fake = fakes.FakeHTTPResponse(
200,
'OK',
{'content-type': 'application/json'},
{}
)
mock_dumps.return_value = "{'files': test}}"
data = six.BytesIO(b'test')
kwargs = {'endpoint_override': 'http://no.where/',
'data': {'files': data}}
client = http.SessionClient(mock.ANY)
self.request.return_value = (fake, {})
resp = client.request('', 'GET', **kwargs)
self.assertEqual({'endpoint_override': 'http://no.where/',
'data': "{'files': test}}",
'user_agent': 'python-heatclient',
'raise_exc': False}, self.request.call_args[1])
self.assertEqual(200, resp.status_code)
def test_methods(self):
fake = fakes.FakeHTTPResponse(
200,
'OK',
{'content-type': 'application/json'},
{}
)
self.request.return_value = (fake, {})
client = http.SessionClient(mock.ANY)
methods = [client.get, client.put, client.post, client.patch,
client.delete, client.head]
for method in methods:
resp = method('')
self.assertEqual(200, resp.status_code)
def test_credentials_headers(self):
client = http.SessionClient(mock.ANY)
self.assertEqual({}, client.credentials_headers())

View File

@ -1,359 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import mock
import six
import swiftclient.client
import testscenarios
import testtools
from testtools import matchers
from heatclient.common import deployment_utils
from heatclient import exc
from heatclient.v1 import software_configs
load_tests = testscenarios.load_tests_apply_scenarios
def mock_sc(group=None, config=None, options=None,
inputs=None, outputs=None):
return software_configs.SoftwareConfig(None, {
'group': group,
'config': config,
'options': options or {},
'inputs': inputs or [],
'outputs': outputs or [],
}, True)
class DerivedConfigTest(testtools.TestCase):
scenarios = [
('defaults', dict(
action='UPDATE',
source=mock_sc(),
name='s1',
input_values=None,
server_id='1234',
signal_transport='NO_SIGNAL',
signal_id=None,
result={
'config': '',
'group': 'Heat::Ungrouped',
'inputs': [{
'description': 'ID of the server being deployed to',
'name': 'deploy_server_id',
'type': 'String',
'value': '1234'
}, {
'description': 'Name of the current action '
'being deployed',
'name': 'deploy_action',
'type': 'String',
'value': 'UPDATE'
}, {
'description': 'How the server should signal to '
'heat with the deployment output values.',
'name': 'deploy_signal_transport',
'type': 'String',
'value': 'NO_SIGNAL'}],
'name': 's1',
'options': {},
'outputs': []})),
('defaults_empty', dict(
action='UPDATE',
source={},
name='s1',
input_values=None,
server_id='1234',
signal_transport='NO_SIGNAL',
signal_id=None,
result={
'config': '',
'group': 'Heat::Ungrouped',
'inputs': [{
'description': 'ID of the server being deployed to',
'name': 'deploy_server_id',
'type': 'String',
'value': '1234'
}, {
'description': 'Name of the current action '
'being deployed',
'name': 'deploy_action',
'type': 'String',
'value': 'UPDATE'
}, {
'description': 'How the server should signal to '
'heat with the deployment output values.',
'name': 'deploy_signal_transport',
'type': 'String',
'value': 'NO_SIGNAL'}],
'name': 's1',
'options': {},
'outputs': []})),
('config_values', dict(
action='UPDATE',
source=mock_sc(
group='puppet',
config='do the foo',
inputs=[
{'name': 'one', 'default': '1'},
{'name': 'two'}],
options={'option1': 'value'},
outputs=[
{'name': 'output1'},
{'name': 'output2'}],
),
name='s2',
input_values={'one': 'foo', 'two': 'bar', 'three': 'baz'},
server_id='1234',
signal_transport='NO_SIGNAL',
signal_id=None,
result={
'config': 'do the foo',
'group': 'puppet',
'inputs': [{
'name': 'one',
'default': '1',
'value': 'foo'
}, {
'name': 'two',
'value': 'bar'
}, {
'name': 'three',
'type': 'String',
'value': 'baz'
}, {
'description': 'ID of the server being deployed to',
'name': 'deploy_server_id',
'type': 'String',
'value': '1234'
}, {
'description': 'Name of the current action '
'being deployed',
'name': 'deploy_action',
'type': 'String',
'value': 'UPDATE'
}, {
'description': 'How the server should signal to '
'heat with the deployment output values.',
'name': 'deploy_signal_transport',
'type': 'String',
'value': 'NO_SIGNAL'
}],
'name': 's2',
'options': {'option1': 'value'},
'outputs': [
{'name': 'output1'},
{'name': 'output2'}]})),
('temp_url', dict(
action='UPDATE',
source=mock_sc(),
name='s1',
input_values=None,
server_id='1234',
signal_transport='TEMP_URL_SIGNAL',
signal_id='http://192.0.2.1:8080/foo',
result={
'config': '',
'group': 'Heat::Ungrouped',
'inputs': [{
'description': 'ID of the server being deployed to',
'name': 'deploy_server_id',
'type': 'String',
'value': '1234'
}, {
'description': 'Name of the current action '
'being deployed',
'name': 'deploy_action',
'type': 'String',
'value': 'UPDATE'
}, {
'description': 'How the server should signal to '
'heat with the deployment output values.',
'name': 'deploy_signal_transport',
'type': 'String',
'value': 'TEMP_URL_SIGNAL'
}, {
'description': 'ID of signal to use for signaling '
'output values',
'name': 'deploy_signal_id',
'type': 'String',
'value': 'http://192.0.2.1:8080/foo'
}, {
'description': 'HTTP verb to use for signaling '
'output values',
'name': 'deploy_signal_verb',
'type': 'String',
'value': 'PUT'}],
'name': 's1',
'options': {},
'outputs': []})),
('unsupported', dict(
action='UPDATE',
source=mock_sc(),
name='s1',
input_values=None,
server_id='1234',
signal_transport='ASDF',
signal_id=None,
result_error=exc.CommandError,
result_error_msg='Unsupported signal transport ASDF',
result=None)),
]
def test_build_derived_config_params(self):
try:
self.assertEqual(
self.result,
deployment_utils.build_derived_config_params(
action=self.action,
source=self.source,
name=self.name,
input_values=self.input_values,
server_id=self.server_id,
signal_transport=self.signal_transport,
signal_id=self.signal_id))
except Exception as e:
if not self.result_error:
raise e
self.assertIsInstance(e, self.result_error)
self.assertEqual(self.result_error_msg, six.text_type(e))
class TempURLSignalTest(testtools.TestCase):
@mock.patch.object(swiftclient.client, 'Connection')
def test_create_swift_client(self, sc_conn):
auth = mock.MagicMock()
auth.get_token.return_value = '1234'
auth.get_endpoint.return_value = 'http://192.0.2.1:8080'
session = mock.MagicMock()
args = mock.MagicMock()
args.os_region_name = 'Region1'
args.os_project_name = 'project'
args.os_username = 'user'
args.os_cacert = None
args.insecure = True
sc_conn.return_value = mock.MagicMock()
sc = deployment_utils.create_swift_client(auth, session, args)
self.assertEqual(sc_conn.return_value, sc)
self.assertEqual(
mock.call(session),
auth.get_token.call_args)
self.assertEqual(
mock.call(
session,
service_type='object-store',
region_name='Region1'),
auth.get_endpoint.call_args)
self.assertEqual(
mock.call(
cacert=None,
insecure=True,
key=None,
tenant_name='project',
preauthtoken='1234',
authurl=None,
user='user',
preauthurl='http://192.0.2.1:8080',
auth_version='2.0'),
sc_conn.call_args)
def test_create_temp_url(self):
swift_client = mock.MagicMock()
swift_client.url = ("http://fake-host.com:8080/v1/AUTH_demo")
swift_client.head_account = mock.Mock(return_value={
'x-account-meta-temp-url-key': '123456'})
swift_client.post_account = mock.Mock()
uuid_pattern = ('[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB]'
'[a-f0-9]{3}-[a-f0-9]{12}')
url = deployment_utils.create_temp_url(swift_client, 'bar', 60)
self.assertFalse(swift_client.post_account.called)
regexp = ("http://fake-host.com:8080/v1/AUTH_demo/bar-%s"
"/%s\?temp_url_sig=[0-9a-f]{40}&"
"temp_url_expires=[0-9]{10}" % (uuid_pattern, uuid_pattern))
self.assertThat(url, matchers.MatchesRegex(regexp))
timeout = int(url.split('=')[-1])
self.assertTrue(timeout < 2147483647)
def test_get_temp_url_no_account_key(self):
swift_client = mock.MagicMock()
swift_client.url = ("http://fake-host.com:8080/v1/AUTH_demo")
head_account = {}
def post_account(data):
head_account.update(data)
swift_client.head_account = mock.Mock(return_value=head_account)
swift_client.post_account = post_account
self.assertNotIn('x-account-meta-temp-url-key', head_account)
deployment_utils.create_temp_url(swift_client, 'bar', 60, 'foo')
self.assertIn('x-account-meta-temp-url-key', head_account)
def test_build_signal_id_no_signal(self):
hc = mock.MagicMock()
args = mock.MagicMock()
args.signal_transport = 'NO_SIGNAL'
self.assertIsNone(deployment_utils.build_signal_id(hc, args))
def test_build_signal_id_no_client_auth(self):
hc = mock.MagicMock()
args = mock.MagicMock()
args.os_no_client_auth = True
args.signal_transport = 'TEMP_URL_SIGNAL'
e = self.assertRaises(exc.CommandError,
deployment_utils.build_signal_id, hc, args)
self.assertEqual((
'Cannot use --os-no-client-auth, auth required to create '
'a Swift TempURL.'),
six.text_type(e))
@mock.patch.object(deployment_utils, 'create_temp_url')
@mock.patch.object(deployment_utils, 'create_swift_client')
def test_build_signal_id(self, csc, ctu):
hc = mock.MagicMock()
args = mock.MagicMock()
args.name = 'foo'
args.timeout = 60
args.os_no_client_auth = False
args.signal_transport = 'TEMP_URL_SIGNAL'
csc.return_value = mock.MagicMock()
temp_url = (
'http://fake-host.com:8080/v1/AUTH_demo/foo/'
'a81a74d5-c395-4269-9670-ddd0824fd696'
'?temp_url_sig=6a68371d602c7a14aaaa9e3b3a63b8b85bd9a503'
'&temp_url_expires=1425270977')
ctu.return_value = temp_url
self.assertEqual(
temp_url, deployment_utils.build_signal_id(hc, args))
self.assertEqual(
mock.call(hc.http_client.auth, hc.http_client.session, args),
csc.call_args)
self.assertEqual(
mock.call(csc.return_value, 'foo', 60),
ctu.call_args)

View File

@ -1,108 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
import six
import testscenarios
import testtools
import yaml
from heatclient.common import environment_format
load_tests = testscenarios.load_tests_apply_scenarios
class YamlEnvironmentTest(testtools.TestCase):
def test_minimal_yaml(self):
yaml1 = ''
yaml2 = '''
parameter_defaults: {}
encrypted_param_names: []
parameters: {}
resource_registry: {}
event_sinks: []
'''
tpl1 = environment_format.parse(yaml1)
environment_format.default_for_missing(tpl1)
tpl2 = environment_format.parse(yaml2)
self.assertEqual(tpl2, tpl1)
def test_param_valid_strategy_section(self):
yaml1 = ''
yaml2 = '''
parameters: {}
encrypted_param_names: []
parameter_defaults: {}
parameter_merge_strategies: {}
event_sinks: []
resource_registry: {}
'''
tpl1 = environment_format.parse(yaml1)
environment_format.default_for_missing(tpl1)
tpl2 = environment_format.parse(yaml2)
self.assertNotEqual(tpl1, tpl2)
def test_wrong_sections(self):
env = '''
parameters: {}
resource_regis: {}
'''
self.assertRaises(ValueError, environment_format.parse, env)
def test_bad_yaml(self):
env = '''
parameters: }
'''
self.assertRaises(ValueError, environment_format.parse, env)
def test_parse_string_environment(self):
env = 'just string'
expect = 'The environment is not a valid YAML mapping data type.'
e = self.assertRaises(ValueError, environment_format.parse, env)
self.assertIn(expect, str(e))
def test_parse_document(self):
env = '["foo", "bar"]'
expect = 'The environment is not a valid YAML mapping data type.'
e = self.assertRaises(ValueError, environment_format.parse, env)
self.assertIn(expect, str(e))
class YamlParseExceptions(testtools.TestCase):
scenarios = [
('scanner', dict(raised_exception=yaml.scanner.ScannerError())),
('parser', dict(raised_exception=yaml.parser.ParserError())),
('reader',
dict(raised_exception=yaml.reader.ReaderError('', '', '', '', ''))),
]
def test_parse_to_value_exception(self):
text = 'not important'
with mock.patch.object(yaml, 'load') as yaml_loader:
yaml_loader.side_effect = self.raised_exception
self.assertRaises(ValueError,
environment_format.parse, text)
class DetailedYAMLParseExceptions(testtools.TestCase):
def test_parse_to_value_exception(self):
yaml = """not important
but very:
- incorrect
"""
ex = self.assertRaises(ValueError, environment_format.parse, yaml)
self.assertIn('but very:\n ^', six.text_type(ex))

View File

@ -1,337 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
import testtools
from heatclient.common import event_utils
from heatclient.v1 import events as hc_ev
from heatclient.v1 import resources as hc_res
class FakeWebSocket(object):
def __init__(self, events):
self.events = events
def recv(self):
return self.events.pop(0)
class ShellTestEventUtils(testtools.TestCase):
@staticmethod
def _mock_resource(resource_id, nested_id=None):
res_info = {"links": [{"href": "http://heat/foo", "rel": "self"},
{"href": "http://heat/foo2", "rel": "resource"}],
"logical_resource_id": resource_id,
"physical_resource_id": resource_id,
"resource_status": "CREATE_COMPLETE",
"resource_status_reason": "state changed",
"resource_type": "OS::Nested::Server",
"updated_time": "2014-01-06T16:14:26Z"}
if nested_id:
nested_link = {"href": "http://heat/%s" % nested_id,
"rel": "nested"}
res_info["links"].append(nested_link)
return hc_res.Resource(manager=None, info=res_info)
@staticmethod
def _mock_event(event_id, resource_id,
resource_status='CREATE_COMPLETE'):
ev_info = {"links": [
{"href": "http://heat/foo", "rel": "self"},
{"href": "http://heat/stacks/astack", "rel": "stack"}],
"logical_resource_id": resource_id,
"physical_resource_id": resource_id,
"resource_name": resource_id,
"resource_status": resource_status,
"resource_status_reason": "state changed",
"event_time": "2014-12-05T14:14:30Z",
"id": event_id}
return hc_ev.Event(manager=None, info=ev_info)
@staticmethod
def _mock_stack_event(event_id, stack_name,
stack_status='CREATE_COMPLETE'):
stack_id = 'abcdef'
ev_info = {"links": [{"href": "http://heat/foo", "rel": "self"},
{"href": "http://heat/stacks/%s/%s" % (stack_name,
stack_id),
"rel": "stack"}],
"logical_resource_id": stack_name,
"physical_resource_id": stack_id,
"resource_name": stack_name,
"resource_status": stack_status,
"resource_status_reason": "state changed",
"event_time": "2014-12-05T14:14:30Z",
"id": event_id}
return hc_ev.Event(manager=None, info=ev_info)
def test_get_nested_ids(self):
def list_stub(stack_id):
return [self._mock_resource('aresource', 'foo3/3id')]
mock_client = mock.MagicMock()
mock_client.resources.list.side_effect = list_stub
ids = event_utils._get_nested_ids(hc=mock_client,
stack_id='astack/123')
mock_client.resources.list.assert_called_once_with(
stack_id='astack/123')
self.assertEqual(['foo3/3id'], ids)
def test_get_stack_events(self):
def event_stub(stack_id, argfoo):
return [self._mock_event('event1', 'aresource')]
mock_client = mock.MagicMock()
mock_client.events.list.side_effect = event_stub
ev_args = {'argfoo': 123}
evs = event_utils._get_stack_events(hc=mock_client,
stack_id='astack/123',
event_args=ev_args)
mock_client.events.list.assert_called_once_with(
stack_id='astack/123', argfoo=123)
self.assertEqual(1, len(evs))
self.assertEqual('event1', evs[0].id)
self.assertEqual('astack', evs[0].stack_name)
def test_get_nested_events(self):
resources = {'parent': self._mock_resource('resource1', 'foo/child1'),
'foo/child1': self._mock_resource('res_child1',
'foo/child2'),
'foo/child2': self._mock_resource('res_child2',
'foo/child3'),
'foo/child3': self._mock_resource('res_child3',
'foo/END')}
def resource_list_stub(stack_id):
return [resources[stack_id]]
mock_client = mock.MagicMock()
mock_client.resources.list.side_effect = resource_list_stub
events = {'foo/child1': self._mock_event('event1', 'res_child1'),
'foo/child2': self._mock_event('event2', 'res_child2'),
'foo/child3': self._mock_event('event3', 'res_child3')}
def event_list_stub(stack_id, argfoo):
return [events[stack_id]]
mock_client.events.list.side_effect = event_list_stub
ev_args = {'argfoo': 123}
# Check nested_depth=1 (non recursive)..
evs = event_utils._get_nested_events(hc=mock_client,
nested_depth=1,
stack_id='parent',
event_args=ev_args)
rsrc_calls = [mock.call(stack_id='parent')]
mock_client.resources.list.assert_has_calls(rsrc_calls)
ev_calls = [mock.call(stack_id='foo/child1', argfoo=123)]
mock_client.events.list.assert_has_calls(ev_calls)
self.assertEqual(1, len(evs))
self.assertEqual('event1', evs[0].id)
# ..and the recursive case via nested_depth=3
mock_client.resources.list.reset_mock()
mock_client.events.list.reset_mock()
evs = event_utils._get_nested_events(hc=mock_client,
nested_depth=3,
stack_id='parent',
event_args=ev_args)
rsrc_calls = [mock.call(stack_id='parent'),
mock.call(stack_id='foo/child1'),
mock.call(stack_id='foo/child2')]
mock_client.resources.list.assert_has_calls(rsrc_calls)
ev_calls = [mock.call(stack_id='foo/child1', argfoo=123),
mock.call(stack_id='foo/child2', argfoo=123),
mock.call(stack_id='foo/child3', argfoo=123)]
mock_client.events.list.assert_has_calls(ev_calls)
self.assertEqual(3, len(evs))
self.assertEqual('event1', evs[0].id)
self.assertEqual('event2', evs[1].id)
self.assertEqual('event3', evs[2].id)
@mock.patch('heatclient.common.event_utils.get_events')
def test_poll_for_events(self, ge):
ge.side_effect = [[
self._mock_stack_event('1', 'astack', 'CREATE_IN_PROGRESS'),
self._mock_event('2', 'res_child1', 'CREATE_IN_PROGRESS'),
self._mock_event('3', 'res_child2', 'CREATE_IN_PROGRESS'),
self._mock_event('4', 'res_child3', 'CREATE_IN_PROGRESS')
], [
self._mock_event('5', 'res_child1', 'CREATE_COMPLETE'),
self._mock_event('6', 'res_child2', 'CREATE_COMPLETE'),
self._mock_event('7', 'res_child3', 'CREATE_COMPLETE'),
self._mock_stack_event('8', 'astack', 'CREATE_COMPLETE')
]]
stack_status, msg = event_utils.poll_for_events(
None, 'astack', action='CREATE', poll_period=0)
self.assertEqual('CREATE_COMPLETE', stack_status)
self.assertEqual('\n Stack astack CREATE_COMPLETE \n', msg)
ge.assert_has_calls([
mock.call(None, stack_id='astack', nested_depth=0, event_args={
'sort_dir': 'asc', 'marker': None
}),
mock.call(None, stack_id='astack', nested_depth=0, event_args={
'sort_dir': 'asc', 'marker': '4'
})
])
@mock.patch('heatclient.common.event_utils.get_events')
def test_poll_for_events_same_name(self, ge):
ge.side_effect = [[
self._mock_stack_event('1', 'mything', 'CREATE_IN_PROGRESS'),
self._mock_event('2', 'res_child1', 'CREATE_IN_PROGRESS'),
self._mock_event('3', 'mything', 'CREATE_IN_PROGRESS'),
], [
self._mock_event('4', 'mything', 'CREATE_COMPLETE'),
], [
self._mock_event('5', 'res_child1', 'CREATE_COMPLETE'),
self._mock_stack_event('6', 'mything', 'CREATE_COMPLETE'),
]]
stack_status, msg = event_utils.poll_for_events(
None, 'mything', action='CREATE', poll_period=0)
self.assertEqual('CREATE_COMPLETE', stack_status)
self.assertEqual('\n Stack mything CREATE_COMPLETE \n', msg)
ge.assert_has_calls([
mock.call(None, stack_id='mything', nested_depth=0, event_args={
'sort_dir': 'asc', 'marker': None
}),
mock.call(None, stack_id='mything', nested_depth=0, event_args={
'sort_dir': 'asc', 'marker': '3'
}),
mock.call(None, stack_id='mything', nested_depth=0, event_args={
'sort_dir': 'asc', 'marker': '4'
})
])
@mock.patch('heatclient.common.event_utils.get_events')
def test_poll_for_events_with_marker(self, ge):
ge.side_effect = [[
self._mock_event('5', 'res_child1', 'CREATE_COMPLETE'),
self._mock_event('6', 'res_child2', 'CREATE_COMPLETE'),
self._mock_event('7', 'res_child3', 'CREATE_COMPLETE'),
self._mock_stack_event('8', 'astack', 'CREATE_COMPLETE')
]]
stack_status, msg = event_utils.poll_for_events(
None, 'astack', action='CREATE', poll_period=0, marker='4',
nested_depth=0)
self.assertEqual('CREATE_COMPLETE', stack_status)
self.assertEqual('\n Stack astack CREATE_COMPLETE \n', msg)
ge.assert_has_calls([
mock.call(None, stack_id='astack', nested_depth=0, event_args={
'sort_dir': 'asc', 'marker': '4'
})
])
@mock.patch('heatclient.common.event_utils.get_events')
def test_poll_for_events_in_progress_resource(self, ge):
ge.side_effect = [[
self._mock_stack_event('1', 'astack', 'CREATE_IN_PROGRESS'),
self._mock_event('2', 'res_child1', 'CREATE_IN_PROGRESS'),
self._mock_stack_event('3', 'astack', 'CREATE_COMPLETE')
]]
stack_status, msg = event_utils.poll_for_events(
None, 'astack', action='CREATE', poll_period=0)
self.assertEqual('CREATE_COMPLETE', stack_status)
self.assertEqual('\n Stack astack CREATE_COMPLETE \n', msg)
@mock.patch('heatclient.common.event_utils.get_events')
def test_poll_for_events_failed(self, ge):
ge.side_effect = [[
self._mock_stack_event('1', 'astack', 'CREATE_IN_PROGRESS'),
self._mock_event('2', 'res_child1', 'CREATE_IN_PROGRESS'),
self._mock_event('3', 'res_child2', 'CREATE_IN_PROGRESS'),
self._mock_event('4', 'res_child3', 'CREATE_IN_PROGRESS')
], [
self._mock_event('5', 'res_child1', 'CREATE_COMPLETE'),
self._mock_event('6', 'res_child2', 'CREATE_FAILED'),
self._mock_event('7', 'res_child3', 'CREATE_COMPLETE'),
self._mock_stack_event('8', 'astack', 'CREATE_FAILED')
]]
stack_status, msg = event_utils.poll_for_events(
None, 'astack', action='CREATE', poll_period=0)
self.assertEqual('CREATE_FAILED', stack_status)
self.assertEqual('\n Stack astack CREATE_FAILED \n', msg)
@mock.patch('heatclient.common.event_utils.get_events')
def test_poll_for_events_no_action(self, ge):
ge.side_effect = [[
self._mock_stack_event('1', 'astack', 'CREATE_IN_PROGRESS'),
self._mock_event('2', 'res_child1', 'CREATE_IN_PROGRESS'),
self._mock_event('3', 'res_child2', 'CREATE_IN_PROGRESS'),
self._mock_event('4', 'res_child3', 'CREATE_IN_PROGRESS')
], [
self._mock_event('5', 'res_child1', 'CREATE_COMPLETE'),
self._mock_event('6', 'res_child2', 'CREATE_FAILED'),
self._mock_event('7', 'res_child3', 'CREATE_COMPLETE'),
self._mock_stack_event('8', 'astack', 'FOO_FAILED')
]]
stack_status, msg = event_utils.poll_for_events(
None, 'astack', action=None, poll_period=0)
self.assertEqual('FOO_FAILED', stack_status)
self.assertEqual('\n Stack astack FOO_FAILED \n', msg)
@mock.patch('heatclient.common.event_utils.get_events')
def test_poll_for_events_stack_get(self, ge):
mock_client = mock.MagicMock()
mock_client.stacks.get.return_value.stack_status = 'CREATE_FAILED'
ge.side_effect = [[
self._mock_stack_event('1', 'astack', 'CREATE_IN_PROGRESS'),
self._mock_event('2', 'res_child1', 'CREATE_IN_PROGRESS'),
self._mock_event('3', 'res_child2', 'CREATE_IN_PROGRESS'),
self._mock_event('4', 'res_child3', 'CREATE_IN_PROGRESS')
], [], []]
stack_status, msg = event_utils.poll_for_events(
mock_client, 'astack', action='CREATE', poll_period=0)
self.assertEqual('CREATE_FAILED', stack_status)
self.assertEqual('\n Stack astack CREATE_FAILED \n', msg)
def test_wait_for_events(self):
ws = FakeWebSocket([
{'body': {
'timestamp': '2014-01-06T16:14:26Z',
'payload': {'resource_action': 'CREATE',
'resource_status': 'COMPLETE',
'resource_name': 'mystack',
'physical_resource_id': 'stackid1',
'stack_id': 'stackid1'}}}])
stack_status, msg = event_utils.wait_for_events(ws, 'mystack')
self.assertEqual('CREATE_COMPLETE', stack_status)
self.assertEqual('\n Stack mystack CREATE_COMPLETE \n', msg)
def test_wait_for_events_failed(self):
ws = FakeWebSocket([
{'body': {
'timestamp': '2014-01-06T16:14:23Z',
'payload': {'resource_action': 'CREATE',
'resource_status': 'IN_PROGRESS',
'resource_name': 'mystack',
'physical_resource_id': 'stackid1',
'stack_id': 'stackid1'}}},
{'body': {
'timestamp': '2014-01-06T16:14:26Z',
'payload': {'resource_action': 'CREATE',
'resource_status': 'FAILED',
'resource_name': 'mystack',
'physical_resource_id': 'stackid1',
'stack_id': 'stackid1'}}}])
stack_status, msg = event_utils.wait_for_events(ws, 'mystack')
self.assertEqual('CREATE_FAILED', stack_status)
self.assertEqual('\n Stack mystack CREATE_FAILED \n', msg)

View File

@ -1,153 +0,0 @@
# Copyright 2013 IBM Corp.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
import testtools
from heatclient.common import utils
from heatclient.v1 import events
class EventManagerTest(testtools.TestCase):
def test_list_event(self):
stack_id = 'teststack',
resource_name = 'testresource'
manager = events.EventManager(None)
with mock.patch('heatclient.v1.events.EventManager._resolve_stack_id')\
as mock_re:
mock_re.return_value = 'teststack/abcd1234'
manager._list = mock.MagicMock()
manager.list(stack_id, resource_name)
# Make sure url is correct.
manager._list.assert_called_once_with(
'/stacks/teststack/abcd1234/'
'resources/testresource/events',
"events")
mock_re.assert_called_once_with(stack_id)
def test_list_event_with_unicode_resource_name(self):
stack_id = 'teststack',
resource_name = u'\u5de5\u4f5c'
manager = events.EventManager(None)
with mock.patch('heatclient.v1.events.EventManager._resolve_stack_id')\
as mock_re:
mock_re.return_value = 'teststack/abcd1234'
manager._list = mock.MagicMock()
manager.list(stack_id, resource_name)
# Make sure url is correct.
manager._list.assert_called_once_with(
'/stacks/teststack/abcd1234/'
'resources/%E5%B7%A5%E4%BD%9C/'
'events', "events")
mock_re.assert_called_once_with(stack_id)
def test_list_event_with_none_resource_name(self):
stack_id = 'teststack',
manager = events.EventManager(None)
manager._list = mock.MagicMock()
manager.list(stack_id)
# Make sure url is correct.
manager._list.assert_called_once_with('/stacks/teststack/'
'events', "events")
def test_list_event_with_kwargs(self):
stack_id = 'teststack',
resource_name = 'testresource'
kwargs = {'limit': 2,
'marker': '6d6935f4-0ae5',
'filters': {
'resource_action': 'CREATE',
'resource_status': 'COMPLETE'
}}
manager = events.EventManager(None)
manager = events.EventManager(None)
with mock.patch('heatclient.v1.events.EventManager._resolve_stack_id')\
as mock_re:
mock_re.return_value = 'teststack/abcd1234'
manager._list = mock.MagicMock()
manager.list(stack_id, resource_name, **kwargs)
# Make sure url is correct.
self.assertEqual(1, manager._list.call_count)
args = manager._list.call_args
self.assertEqual(2, len(args[0]))
url, param = args[0]
self.assertEqual("events", param)
base_url, query_params = utils.parse_query_url(url)
expected_base_url = ('/stacks/teststack/abcd1234/'
'resources/testresource/events')
self.assertEqual(expected_base_url, base_url)
expected_query_dict = {'marker': ['6d6935f4-0ae5'],
'limit': ['2'],
'resource_action': ['CREATE'],
'resource_status': ['COMPLETE']}
self.assertEqual(expected_query_dict, query_params)
mock_re.assert_called_once_with(stack_id)
@mock.patch('heatclient.v1.events.EventManager._resolve_stack_id')
@mock.patch('heatclient.common.utils.get_response_body')
def test_get_event(self, mock_utils, mock_re):
fields = {'stack_id': 'teststack',
'resource_name': 'testresource',
'event_id': '1'}
class FakeAPI(object):
"""Fake API and ensure request url is correct."""
def json_request(self, *args, **kwargs):
expect = ('GET',
'/stacks/teststack/abcd1234/resources'
'/testresource/events/1')
assert args == expect
return {}, {'event': []}
def get(self, *args, **kwargs):
pass
manager = events.EventManager(FakeAPI())
with mock.patch('heatclient.v1.events.Event'):
mock_utils.return_value = {'event': []}
mock_re.return_value = 'teststack/abcd1234'
manager.get(**fields)
mock_re.assert_called_once_with('teststack')
@mock.patch('heatclient.v1.events.EventManager._resolve_stack_id')
@mock.patch('heatclient.common.utils.get_response_body')
def test_get_event_with_unicode_resource_name(self, mock_utils, mock_re):
fields = {'stack_id': 'teststack',
'resource_name': u'\u5de5\u4f5c',
'event_id': '1'}
class FakeAPI(object):
"""Fake API and ensure request url is correct."""
def json_request(self, *args, **kwargs):
expect = ('GET',
'/stacks/teststack/abcd1234/resources'
'/%E5%B7%A5%E4%BD%9C/events/1')
assert args == expect
return {}, {'event': []}
def get(self, *args, **kwargs):
pass
manager = events.EventManager(FakeAPI())
with mock.patch('heatclient.v1.events.Event'):
mock_utils.return_value = {'event': []}
mock_re.return_value = 'teststack/abcd1234'
manager.get(**fields)
mock_re.assert_called_once_with('teststack')

View File

@ -1,177 +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.
#
# Copyright 2015 IBM Corp.
import json
import six
import yaml
from heatclient.common import format_utils
from heatclient.tests.unit.osc import 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 = json.dumps(dict(zip(columns, data)), indent=2)
self.cmd.run(parsed_args)
self.assertEqual(expected, 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())
def test_indent_and_truncate(self):
self.assertIsNone(format_utils.indent_and_truncate(None))
self.assertIsNone(format_utils.indent_and_truncate(None,
truncate=True))
self.assertEqual(
'',
format_utils.indent_and_truncate(''))
self.assertEqual(
'one',
format_utils.indent_and_truncate('one'))
self.assertIsNone(format_utils.indent_and_truncate(None, spaces=2))
self.assertEqual(
'',
format_utils.indent_and_truncate('', spaces=2))
self.assertEqual(
' one',
format_utils.indent_and_truncate('one', spaces=2))
self.assertEqual(
'one\ntwo\nthree\nfour\nfive',
format_utils.indent_and_truncate('one\ntwo\nthree\nfour\nfive'))
self.assertEqual(
'three\nfour\nfive',
format_utils.indent_and_truncate(
'one\ntwo\nthree\nfour\nfive',
truncate=True,
truncate_limit=3))
self.assertEqual(
' and so on\n three\n four\n five\n truncated',
format_utils.indent_and_truncate(
'one\ntwo\nthree\nfour\nfive',
spaces=2,
truncate=True,
truncate_limit=3,
truncate_prefix='and so on',
truncate_postfix='truncated'))
def test_print_software_deployment_output(self):
out = six.StringIO()
format_utils.print_software_deployment_output(
{'deploy_stdout': ''}, out=out, name='deploy_stdout')
self.assertEqual(
' deploy_stdout: |\n\n',
out.getvalue())
ov = {'deploy_stdout': '', 'deploy_stderr': '1\n2\n3\n4\n5\n6\n7\n8\n9'
'\n10\n11',
'deploy_status_code': 0}
out = six.StringIO()
format_utils.print_software_deployment_output(ov, out=out,
name='deploy_stderr')
self.assertEqual(
u'''\
deploy_stderr: |
...
2
3
4
5
6
7
8
9
10
11
(truncated, view all with --long)
''', out.getvalue())
out = six.StringIO()
format_utils.print_software_deployment_output(ov, out=out,
name='deploy_stderr',
long=True)
self.assertEqual(
u'''\
deploy_stderr: |
1
2
3
4
5
6
7
8
9
10
11
''', out.getvalue())

View File

@ -1,75 +0,0 @@
# Copyright 2015 Huawei.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import testtools
from heatclient.common import base
from heatclient.v1 import events
from heatclient.v1 import stacks
class BaseTest(testtools.TestCase):
def test_two_resources_with_same_id_are_not_equal(self):
# Two resources with same ID: never equal if their info is not equal
r1 = base.Resource(None, {'id': 1, 'name': 'hi'})
r2 = base.Resource(None, {'id': 1, 'name': 'hello'})
self.assertNotEqual(r1, r2)
def test_two_resources_with_same_id_and_info_are_equal(self):
# Two resources with same ID: equal if their info is equal
r1 = base.Resource(None, {'id': 1, 'name': 'hello'})
r2 = base.Resource(None, {'id': 1, 'name': 'hello'})
self.assertEqual(r1, r2)
def test_two_resources_with_diff_type_are_not_equal(self):
# Two resoruces of different types: never equal
r1 = base.Resource(None, {'id': 1})
r2 = events.Event(None, {'id': 1})
self.assertNotEqual(r1, r2)
def test_two_resources_with_no_id_are_equal(self):
# Two resources with no ID: equal if their info is equal
r1 = base.Resource(None, {'name': 'joe', 'age': 12})
r2 = base.Resource(None, {'name': 'joe', 'age': 12})
self.assertEqual(r1, r2)
def test_is_same_object(self):
# Two resources with same type and same ID: is same object
r1 = base.Resource(None, {'id': 1, 'name': 'hi'})
r2 = base.Resource(None, {'id': 1, 'name': 'hello'})
self.assertTrue(r1.is_same_obj(r2))
self.assertTrue(r2.is_same_obj(r1))
def test_is_diff_object_with_diff_id(self):
# Two resources with same type and different ID: is different object
r1 = base.Resource(None, {'id': 1, 'name': 'hello'})
r2 = base.Resource(None, {'id': 2, 'name': 'hello'})
self.assertFalse(r1.is_same_obj(r2))
self.assertFalse(r2.is_same_obj(r1))
def test_is_diff_object_with_diff_type(self):
# Two resources with different types: is different object
r1 = events.Event(None, {'id': 1})
r2 = stacks.Stack(None, {'id': 1})
self.assertFalse(r1.is_same_obj(r2))
self.assertFalse(r2.is_same_obj(r1))
def test_is_diff_object_with_no_id(self):
# Two resources with no ID: is different object
r1 = base.Resource(None, {'name': 'joe', 'age': 12})
r2 = base.Resource(None, {'name': 'joe', 'age': 12})
self.assertFalse(r1.is_same_obj(r2))
self.assertFalse(r2.is_same_obj(r1))

View File

@ -1,101 +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 json
import os
import six
from heatclient.common import resource_formatter
from heatclient.osc.v1 import resource
from heatclient.tests.unit.osc.v1 import fakes as orchestration_fakes
from heatclient.v1 import resources as v1_resources
TEST_VAR_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__),
'var'))
class TestStackResourceListDotFormat(orchestration_fakes.TestOrchestrationv1):
response_path = os.path.join(TEST_VAR_DIR, 'dot_test.json')
data = '''digraph G {
graph [
fontsize=10 fontname="Verdana" compound=true rankdir=LR
]
r_f34a35baf594b319a741 [label="rg1
OS::Heat::ResourceGroup" ];
r_121e343b017a6d246f36 [label="random2
OS::Heat::RandomString" ];
r_dbcae38ad41dc991751d [label="random1
OS::Heat::RandomString" style=filled color=red];
subgraph cluster_stack_16437984473ec64a8e6c {
label="rg1";
r_30e9aa76bc0d53310cde [label="1
OS::Heat::ResourceGroup" ];
r_63c05d424cb708f1599f [label="0
OS::Heat::ResourceGroup" ];
}
subgraph cluster_stack_fbfb461c8cc84b686c08 {
label="1";
r_e2e5c36ae18e29d9c299 [label="1
OS::Heat::RandomString" ];
r_56c62630a0d655bce234 [label="0
OS::Heat::RandomString" ];
}
subgraph cluster_stack_d427657dfccc28a131a7 {
label="0";
r_240756913e2e940387ff [label="1
OS::Heat::RandomString" ];
r_81c64c43d9131aceedbb [label="0
OS::Heat::RandomString" ];
}
r_f34a35baf594b319a741 -> r_30e9aa76bc0d53310cde [
color=dimgray lhead=cluster_stack_16437984473ec64a8e6c arrowhead=none
];
r_30e9aa76bc0d53310cde -> r_e2e5c36ae18e29d9c299 [
color=dimgray lhead=cluster_stack_fbfb461c8cc84b686c08 arrowhead=none
];
r_63c05d424cb708f1599f -> r_240756913e2e940387ff [
color=dimgray lhead=cluster_stack_d427657dfccc28a131a7 arrowhead=none
];
r_dbcae38ad41dc991751d -> r_121e343b017a6d246f36;
}
'''
def setUp(self):
super(TestStackResourceListDotFormat, self).setUp()
self.resource_client = self.app.client_manager.orchestration.resources
self.cmd = resource.ResourceList(self.app, None)
with open(self.response_path) as f:
response = json.load(f)
self.resources = []
for r in response['resources']:
self.resources.append(v1_resources.Resource(None, r))
def test_resource_list(self):
out = six.StringIO()
formatter = resource_formatter.ResourceDotFormatter()
formatter.emit_list(None, self.resources, out, None)
self.assertEqual(self.data, out.getvalue())

View File

@ -1,114 +0,0 @@
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
import testtools
from heatclient.common import utils
from heatclient.v1 import resource_types
class ResourceTypeManagerTest(testtools.TestCase):
def _base_test(self, expect, key):
class FakeAPI(object):
"""Fake API and ensure request url is correct."""
def get(self, *args, **kwargs):
assert ('GET', args[0]) == expect
def json_request(self, *args, **kwargs):
assert args == expect
ret = key and {key: []} or {}
return {}, {key: ret}
def raw_request(self, *args, **kwargs):
assert args == expect
return {}
def head(self, url, **kwargs):
return self.json_request("HEAD", url, **kwargs)
def post(self, url, **kwargs):
return self.json_request("POST", url, **kwargs)
def put(self, url, **kwargs):
return self.json_request("PUT", url, **kwargs)
def delete(self, url, **kwargs):
return self.raw_request("DELETE", url, **kwargs)
def patch(self, url, **kwargs):
return self.json_request("PATCH", url, **kwargs)
manager = resource_types.ResourceTypeManager(FakeAPI())
return manager
def test_list_types(self):
key = 'resource_types'
expect = ('GET', '/resource_types')
class FakeResponse(object):
def json(self):
return {key: {}}
class FakeClient(object):
def get(self, *args, **kwargs):
assert ('GET', args[0]) == expect
return FakeResponse()
manager = resource_types.ResourceTypeManager(FakeClient())
manager.list()
def test_list_types_with_filters(self):
filters = {'name': 'OS::Keystone::*',
'version': '5.0.0',
'support_status': 'SUPPORTED'}
manager = resource_types.ResourceTypeManager(None)
with mock.patch.object(manager, '_list') as mock_list:
mock_list.return_value = None
manager.list(filters=filters)
self.assertEqual(1, mock_list.call_count)
url, param = mock_list.call_args[0]
self.assertEqual("resource_types", param)
base_url, query_params = utils.parse_query_url(url)
self.assertEqual('/%s' % manager.KEY, base_url)
# parameters in query_params is in list format, so filter params
# are made to be inline with it
filters_params = {}
for item in filters:
filters_params[item] = [filters[item]]
self.assertEqual(filters_params, query_params)
@mock.patch.object(utils, 'get_response_body')
def test_get(self, mock_utils):
key = 'resource_types'
resource_type = 'OS::Nova::KeyPair'
expect = ('GET', '/resource_types/OS%3A%3ANova%3A%3AKeyPair')
manager = self._base_test(expect, key)
mock_utils.return_value = None
manager.get(resource_type)
@mock.patch.object(utils, 'get_response_body')
def test_generate_template(self, mock_utils):
key = 'resource_types'
resource_type = 'OS::Nova::KeyPair'
template_type = 'cfn'
expect = ('GET', '/resource_types/OS%3A%3ANova%3A%3AKeyPair/template'
'?template_type=cfn')
manager = self._base_test(expect, key)
mock_utils.return_value = None
manager.generate_template(resource_type, template_type)

View File

@ -1,232 +0,0 @@
# Copyright 2013 IBM Corp.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mox
from six.moves.urllib import parse
import testtools
from heatclient.common import utils
from heatclient.v1 import resources
class ResourceManagerTest(testtools.TestCase):
def setUp(self):
super(ResourceManagerTest, self).setUp()
self.m = mox.Mox()
self.addCleanup(self.m.UnsetStubs)
def _base_test(self, expect, key):
class FakeAPI(object):
"""Fake API and ensure request url is correct."""
def get(self, *args, **kwargs):
assert ('GET', args[0]) == expect
def json_request(self, *args, **kwargs):
assert args == expect
ret = key and {key: []} or {}
return {}, {key: ret}
def raw_request(self, *args, **kwargs):
assert args == expect
return {}
def head(self, url, **kwargs):
return self.json_request("HEAD", url, **kwargs)
def post(self, url, **kwargs):
return self.json_request("POST", url, **kwargs)
def put(self, url, **kwargs):
return self.json_request("PUT", url, **kwargs)
def delete(self, url, **kwargs):
return self.raw_request("DELETE", url, **kwargs)
def patch(self, url, **kwargs):
return self.json_request("PATCH", url, **kwargs)
manager = resources.ResourceManager(FakeAPI())
self.m.StubOutWithMock(manager, '_resolve_stack_id')
self.m.StubOutWithMock(utils, 'get_response_body')
utils.get_response_body(mox.IgnoreArg()).AndReturn(
{key: key and {key: []} or {}})
manager._resolve_stack_id('teststack').AndReturn('teststack/abcd1234')
self.m.ReplayAll()
return manager
def test_get(self):
fields = {'stack_id': 'teststack',
'resource_name': 'testresource'}
expect = ('GET',
'/stacks/teststack/abcd1234/resources'
'/testresource')
key = 'resource'
manager = self._base_test(expect, key)
manager.get(**fields)
self.m.VerifyAll()
def test_get_with_attr(self):
fields = {'stack_id': 'teststack',
'resource_name': 'testresource',
'with_attr': ['attr_a', 'attr_b']}
expect = ('GET',
'/stacks/teststack/abcd1234/resources'
'/testresource?with_attr=attr_a&with_attr=attr_b')
key = 'resource'
manager = self._base_test(expect, key)
manager.get(**fields)
self.m.VerifyAll()
def test_get_with_unicode_resource_name(self):
fields = {'stack_id': 'teststack',
'resource_name': u'\u5de5\u4f5c'}
expect = ('GET',
'/stacks/teststack/abcd1234/resources'
'/%E5%B7%A5%E4%BD%9C')
key = 'resource'
manager = self._base_test(expect, key)
manager.get(**fields)
self.m.VerifyAll()
def test_list(self):
self._test_list(
fields={'stack_id': 'teststack'},
expect='/stacks/teststack/resources')
def test_list_nested(self):
self._test_list(
fields={'stack_id': 'teststack', 'nested_depth': '99'},
expect='/stacks/teststack/resources?%s' % parse.urlencode({
'nested_depth': 99
}, True)
)
def test_list_filtering(self):
self._test_list(
fields={'stack_id': 'teststack', 'filters': {'name': 'rsc_1'}},
expect='/stacks/teststack/resources?%s' % parse.urlencode({
'name': 'rsc_1'
}, True)
)
def test_list_detail(self):
self._test_list(
fields={'stack_id': 'teststack', 'with_detail': 'True'},
expect='/stacks/teststack/resources?%s' % parse.urlencode({
'with_detail': True,
}, True)
)
def _test_list(self, fields, expect):
key = 'resources'
class FakeResponse(object):
def json(self):
return {key: {}}
class FakeClient(object):
def get(self, *args, **kwargs):
assert args[0] == expect
return FakeResponse()
manager = resources.ResourceManager(FakeClient())
manager.list(**fields)
def test_metadata(self):
fields = {'stack_id': 'teststack',
'resource_name': 'testresource'}
expect = ('GET',
'/stacks/teststack/abcd1234/resources'
'/testresource/metadata')
key = 'metadata'
manager = self._base_test(expect, key)
manager.metadata(**fields)
self.m.VerifyAll()
def test_generate_template(self):
fields = {'resource_name': 'testresource'}
expect = ('GET', '/resource_types/testresource/template')
key = None
class FakeAPI(object):
"""Fake API and ensure request url is correct."""
def get(self, *args, **kwargs):
assert ('GET', args[0]) == expect
def json_request(self, *args, **kwargs):
assert args == expect
ret = key and {key: []} or {}
return {}, {key: ret}
manager = resources.ResourceManager(FakeAPI())
self.m.StubOutWithMock(utils, 'get_response_body')
utils.get_response_body(mox.IgnoreArg()).AndReturn(
{key: key and {key: []} or {}})
self.m.ReplayAll()
manager.generate_template(**fields)
self.m.VerifyAll()
def test_signal(self):
fields = {'stack_id': 'teststack',
'resource_name': 'testresource',
'data': 'Some content'}
expect = ('POST',
'/stacks/teststack/abcd1234/resources'
'/testresource/signal')
key = 'signal'
manager = self._base_test(expect, key)
manager.signal(**fields)
self.m.VerifyAll()
def test_mark_unhealthy(self):
fields = {'stack_id': 'teststack',
'resource_name': 'testresource',
'mark_unhealthy': 'True',
'resource_status_reason': 'Anything'}
expect = ('PATCH',
'/stacks/teststack/abcd1234/resources'
'/testresource')
key = 'mark_unhealthy'
manager = self._base_test(expect, key)
manager.mark_unhealthy(**fields)
self.m.VerifyAll()
class ResourceStackNameTest(testtools.TestCase):
def test_stack_name(self):
resource = resources.Resource(None, {"links": [{
"href": "http://heat.example.com:8004/foo/12/resources/foobar",
"rel": "self"
}, {
"href": "http://heat.example.com:8004/foo/12",
"rel": "stack"
}]})
self.assertEqual('foo', resource.stack_name)
def test_stack_name_no_links(self):
resource = resources.Resource(None, {})
self.assertIsNone(resource.stack_name)

View File

@ -1,58 +0,0 @@
# Copyright 2012 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import testtools
from heatclient import exc
from heatclient.v1 import services
class ManageServiceTest(testtools.TestCase):
def setUp(self):
super(ManageServiceTest, self).setUp()
def test_service_list(self):
class FakeResponse(object):
def json(self):
return {'services': []}
class FakeClient(object):
def get(self, *args, **kwargs):
assert args[0] == ('/services')
return FakeResponse()
manager = services.ServiceManager(FakeClient())
self.assertEqual([], manager.list())
def test_service_list_403(self):
class FakeClient403(object):
def get(self, *args, **kwargs):
assert args[0] == ('/services')
raise exc.HTTPForbidden()
manager = services.ServiceManager(FakeClient403())
self.assertRaises(exc.HTTPForbidden,
manager.list)
def test_service_list_503(self):
class FakeClient503(object):
def get(self, *args, **kwargs):
assert args[0] == ('/services')
raise exc.HTTPServiceUnavailable()
manager = services.ServiceManager(FakeClient503())
self.assertRaises(exc.HTTPServiceUnavailable,
manager.list)

File diff suppressed because it is too large Load Diff

View File

@ -1,110 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
import testtools
from heatclient.common import utils
from heatclient.v1 import software_configs
class SoftwareConfigTest(testtools.TestCase):
def setUp(self):
super(SoftwareConfigTest, self).setUp()
config_id = 'bca6871d-86c0-4aff-b792-58a1f6947b57'
self.config = software_configs.SoftwareConfig(mock.MagicMock(),
info={'id': config_id})
self.config_id = config_id
def test_delete(self):
self.config.manager.delete.return_value = None
self.assertIsNone(self.config.delete())
kwargs = self.config.manager.delete.call_args[1]
self.assertEqual(self.config_id, kwargs['config_id'])
def test_data(self):
self.assertEqual(
"<SoftwareConfig {'id': '%s'}>" % self.config_id, str(self.config))
self.config.manager.data.return_value = None
self.config.data(name='config_mysql')
kwargs = self.config.manager.data.call_args[1]
self.assertEqual('config_mysql', kwargs['name'])
class SoftwareConfigManagerTest(testtools.TestCase):
def setUp(self):
super(SoftwareConfigManagerTest, self).setUp()
self.manager = software_configs.SoftwareConfigManager(mock.MagicMock())
def test_list(self):
config_id = 'bca6871d-86c0-4aff-b792-58a1f6947b57'
self.manager.client.json_request.return_value = (
{},
{'software_configs': []})
result = self.manager.list(limit=1, marker=config_id)
self.assertEqual([], result)
call_args = self.manager.client.get.call_args
self.assertEqual(
('/software_configs?limit=1&marker=%s' % config_id,), *call_args)
@mock.patch.object(utils, 'get_response_body')
def test_get(self, mock_body):
config_id = 'bca6871d-86c0-4aff-b792-58a1f6947b57'
data = {
'id': config_id,
'name': 'config_mysql',
'group': 'Heat::Shell',
'config': '#!/bin/bash',
'inputs': [],
'ouputs': [],
'options': []}
self.manager.client.json_request.return_value = (
{}, {'software_config': data})
mock_body.return_value = {'software_config': data}
result = self.manager.get(config_id=config_id)
self.assertEqual(software_configs.SoftwareConfig(self.manager, data),
result)
call_args = self.manager.client.get.call_args
self.assertEqual(
('/software_configs/%s' % config_id,), *call_args)
@mock.patch.object(utils, 'get_response_body')
def test_create(self, mock_body):
config_id = 'bca6871d-86c0-4aff-b792-58a1f6947b57'
body = {
'name': 'config_mysql',
'group': 'Heat::Shell',
'config': '#!/bin/bash',
'inputs': [],
'ouputs': [],
'options': []}
data = body.copy()
data['id'] = config_id
self.manager.client.json_request.return_value = (
{}, {'software_config': data})
mock_body.return_value = {'software_config': data}
result = self.manager.create(**body)
self.assertEqual(software_configs.SoftwareConfig(self.manager, data),
result)
args, kargs = self.manager.client.post.call_args
self.assertEqual('/software_configs', args[0])
self.assertEqual({'data': body}, kargs)
def test_delete(self):
config_id = 'bca6871d-86c0-4aff-b792-58a1f6947b57'
self.manager.delete(config_id)
call_args = self.manager.client.delete.call_args
self.assertEqual(
('/software_configs/%s' % config_id,), *call_args)

View File

@ -1,166 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
import testtools
from heatclient.common import utils
from heatclient.v1 import software_deployments
class SoftwareDeploymentTest(testtools.TestCase):
def setUp(self):
super(SoftwareDeploymentTest, self).setUp()
deployment_id = 'bca6871d-86c0-4aff-b792-58a1f6947b57'
self.deployment = software_deployments.SoftwareDeployment(
mock.MagicMock(), info={'id': deployment_id})
self.deployment_id = deployment_id
def test_delete(self):
self.deployment.manager.delete.return_value = None
self.assertIsNone(self.deployment.delete())
kwargs = self.deployment.manager.delete.call_args[1]
self.assertEqual(self.deployment_id, kwargs['deployment_id'])
def test_update(self):
self.assertEqual(
"<SoftwareDeployment {'id': '%s'}>" % self.deployment_id,
str(self.deployment))
self.deployment.manager.update.return_value = None
config_id = 'd00ba4aa-db33-42e1-92f4-2a6469260107'
self.assertIsNone(self.deployment.update(config_id=config_id))
kwargs = self.deployment.manager.update.call_args[1]
self.assertEqual(self.deployment_id, kwargs['deployment_id'])
self.assertEqual(config_id, kwargs['config_id'])
class SoftwareDeploymentManagerTest(testtools.TestCase):
def setUp(self):
super(SoftwareDeploymentManagerTest, self).setUp()
self.manager = software_deployments.SoftwareDeploymentManager(
mock.MagicMock())
def test_list(self):
server_id = 'fc01f89f-e151-4dc5-9c28-543c0d20ed6a'
self.manager.client.json_request.return_value = (
{},
{'software_deployments': []})
result = self.manager.list(server_id=server_id)
self.assertEqual([], result)
call_args = self.manager.client.get.call_args
self.assertEqual(
('/software_deployments?server_id=%s' % server_id,),
*call_args)
@mock.patch.object(utils, 'get_response_body')
def test_metadata(self, mock_utils):
server_id = 'fc01f89f-e151-4dc5-9c28-543c0d20ed6a'
metadata = {
'group1': [{'foo': 'bar'}],
'group2': [{'foo': 'bar'}, {'bar': 'baz'}],
}
self.manager.client.get.return_value = {}
mock_utils.return_value = {'metadata': metadata}
result = self.manager.metadata(server_id=server_id)
self.assertEqual(metadata, result)
call_args = self.manager.client.get.call_args
self.assertEqual(
'/software_deployments/metadata/%s' % server_id,
call_args[0][0])
@mock.patch.object(utils, 'get_response_body')
def test_get(self, mock_utils):
deployment_id = 'bca6871d-86c0-4aff-b792-58a1f6947b57'
config_id = 'd00ba4aa-db33-42e1-92f4-2a6469260107'
server_id = 'fb322564-7927-473d-8aad-68ae7fbf2abf'
data = {
'id': deployment_id,
'server_id': server_id,
'input_values': {},
'output_values': {},
'action': 'INIT',
'status': 'COMPLETE',
'status_reason': None,
'signal_id': None,
'config_id': config_id,
'config': '#!/bin/bash',
'name': 'config_mysql',
'group': 'Heat::Shell',
'inputs': [],
'outputs': [],
'options': []}
self.manager.client.get.return_value = {}
mock_utils.return_value = {'software_deployment': data}
result = self.manager.get(deployment_id=deployment_id)
self.assertEqual(software_deployments.SoftwareDeployment(
self.manager, data), result)
call_args = self.manager.client.get.call_args
self.assertEqual(
('/software_deployments/%s' % deployment_id,), *call_args)
@mock.patch.object(utils, 'get_response_body')
def test_create(self, mock_utils):
deployment_id = 'bca6871d-86c0-4aff-b792-58a1f6947b57'
config_id = 'd00ba4aa-db33-42e1-92f4-2a6469260107'
server_id = 'fb322564-7927-473d-8aad-68ae7fbf2abf'
body = {
'server_id': server_id,
'input_values': {},
'action': 'INIT',
'status': 'COMPLETE',
'status_reason': None,
'signal_id': None,
'config_id': config_id}
data = body.copy()
data['id'] = deployment_id
self.manager.client.post.return_value = {}
mock_utils.return_value = {'software_deployment': data}
result = self.manager.create(**body)
self.assertEqual(software_deployments.SoftwareDeployment(
self.manager, data), result)
args, kwargs = self.manager.client.post.call_args
self.assertEqual('/software_deployments', args[0])
self.assertEqual({'data': body}, kwargs)
def test_delete(self):
deployment_id = 'bca6871d-86c0-4aff-b792-58a1f6947b57'
self.manager.delete(deployment_id)
call_args = self.manager.client.delete.call_args
self.assertEqual(
('/software_deployments/%s' % deployment_id,), *call_args)
@mock.patch.object(utils, 'get_response_body')
def test_update(self, mock_utils):
deployment_id = 'bca6871d-86c0-4aff-b792-58a1f6947b57'
config_id = 'd00ba4aa-db33-42e1-92f4-2a6469260107'
server_id = 'fb322564-7927-473d-8aad-68ae7fbf2abf'
body = {
'server_id': server_id,
'input_values': {},
'action': 'DEPLOYED',
'status': 'COMPLETE',
'status_reason': None,
'signal_id': None,
'config_id': config_id}
data = body.copy()
data['id'] = deployment_id
self.manager.client.put.return_value = {}
mock_utils.return_value = {'software_deployment': data}
result = self.manager.update(deployment_id, **body)
self.assertEqual(software_deployments.SoftwareDeployment(
self.manager, data), result)
args, kwargs = self.manager.client.put.call_args
self.assertEqual('/software_deployments/%s' % deployment_id, args[0])
self.assertEqual({'data': body}, kwargs)

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