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:
		@@ -1,8 +0,0 @@
 | 
			
		||||
[run]
 | 
			
		||||
branch = True
 | 
			
		||||
source = heatclient
 | 
			
		||||
omit = heatclient/tests/*
 | 
			
		||||
 | 
			
		||||
[report]
 | 
			
		||||
ignore_errors = True
 | 
			
		||||
precision = 2
 | 
			
		||||
							
								
								
									
										19
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										19
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -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
 | 
			
		||||
@@ -1,4 +0,0 @@
 | 
			
		||||
[gerrit]
 | 
			
		||||
host=review.openstack.org
 | 
			
		||||
port=29418
 | 
			
		||||
project=openstack/python-heatclient.git
 | 
			
		||||
@@ -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
 | 
			
		||||
@@ -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
									
									
									
									
									
								
							
							
						
						
									
										175
									
								
								LICENSE
									
									
									
									
									
								
							@@ -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
									
								
							
							
						
						
									
										14
									
								
								README
									
									
									
									
									
										Normal 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.
 | 
			
		||||
							
								
								
									
										48
									
								
								README.rst
									
									
									
									
									
								
							
							
						
						
									
										48
									
								
								README.rst
									
									
									
									
									
								
							@@ -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/
 | 
			
		||||
							
								
								
									
										2
									
								
								doc/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								doc/.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,2 +0,0 @@
 | 
			
		||||
build/
 | 
			
		||||
source/ref/
 | 
			
		||||
							
								
								
									
										90
									
								
								doc/Makefile
									
									
									
									
									
								
							
							
						
						
									
										90
									
								
								doc/Makefile
									
									
									
									
									
								
							@@ -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."
 | 
			
		||||
@@ -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 = ''
 | 
			
		||||
@@ -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"])
 | 
			
		||||
@@ -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
 | 
			
		||||
@@ -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/.
 | 
			
		||||
@@ -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()
 | 
			
		||||
@@ -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()
 | 
			
		||||
@@ -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)
 | 
			
		||||
@@ -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)
 | 
			
		||||
@@ -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)
 | 
			
		||||
@@ -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] = {}
 | 
			
		||||
@@ -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
 | 
			
		||||
@@ -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)))
 | 
			
		||||
@@ -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
 | 
			
		||||
@@ -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)
 | 
			
		||||
@@ -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')
 | 
			
		||||
@@ -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
 | 
			
		||||
@@ -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
 | 
			
		||||
@@ -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
 | 
			
		||||
@@ -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
 | 
			
		||||
@@ -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)"
 | 
			
		||||
@@ -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 "因为用户没有确认栈删除操作,所以不执行任何动作。"
 | 
			
		||||
@@ -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文件。"
 | 
			
		||||
@@ -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
 | 
			
		||||
@@ -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)
 | 
			
		||||
@@ -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')
 | 
			
		||||
@@ -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})
 | 
			
		||||
@@ -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
 | 
			
		||||
@@ -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)
 | 
			
		||||
        )
 | 
			
		||||
@@ -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})
 | 
			
		||||
@@ -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
 | 
			
		||||
@@ -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
											
										
									
								
							@@ -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)
 | 
			
		||||
@@ -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
 | 
			
		||||
@@ -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()
 | 
			
		||||
@@ -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)
 | 
			
		||||
@@ -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
 | 
			
		||||
@@ -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
 | 
			
		||||
@@ -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)
 | 
			
		||||
@@ -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']
 | 
			
		||||
        )
 | 
			
		||||
@@ -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)
 | 
			
		||||
@@ -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}
 | 
			
		||||
@@ -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 }
 | 
			
		||||
@@ -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'])
 | 
			
		||||
@@ -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
 | 
			
		||||
'''
 | 
			
		||||
@@ -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": {}
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
heat_template_version: 2013-05-23
 | 
			
		||||
@@ -1,7 +0,0 @@
 | 
			
		||||
heat_template_version: 2013-05-23
 | 
			
		||||
 | 
			
		||||
parameters:
 | 
			
		||||
  p1:
 | 
			
		||||
    type: string
 | 
			
		||||
  p2:
 | 
			
		||||
    type: number
 | 
			
		||||
@@ -1,5 +0,0 @@
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
from mox3 import mox
 | 
			
		||||
 | 
			
		||||
sys.modules['mox'] = mox
 | 
			
		||||
@@ -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)
 | 
			
		||||
@@ -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()
 | 
			
		||||
@@ -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
 | 
			
		||||
@@ -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()
 | 
			
		||||
@@ -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))
 | 
			
		||||
@@ -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())
 | 
			
		||||
@@ -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))
 | 
			
		||||
@@ -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)
 | 
			
		||||
@@ -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)
 | 
			
		||||
@@ -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()
 | 
			
		||||
@@ -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)
 | 
			
		||||
@@ -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
											
										
									
								
							@@ -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
 | 
			
		||||
''')
 | 
			
		||||
@@ -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)
 | 
			
		||||
@@ -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)
 | 
			
		||||
@@ -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)
 | 
			
		||||
@@ -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())
 | 
			
		||||
@@ -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)
 | 
			
		||||
@@ -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))
 | 
			
		||||
@@ -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)
 | 
			
		||||
@@ -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')
 | 
			
		||||
@@ -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())
 | 
			
		||||
@@ -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))
 | 
			
		||||
@@ -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())
 | 
			
		||||
@@ -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)
 | 
			
		||||
@@ -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)
 | 
			
		||||
@@ -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
											
										
									
								
							@@ -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)
 | 
			
		||||
@@ -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
		Reference in New Issue
	
	Block a user