Lots of cleanup
* deletion of unneeded heat code * lots of pep8 cleanup * renaming and shifting some stuff * adding/updating some licenses Change-Id: Idd28a0669d1833d4975f877b601c4b43f76eb5e1
This commit is contained in:
parent
1db6fa3074
commit
c85ace9302
|
@ -0,0 +1,2 @@
|
|||
Dale Smith <dale@catalyst-eu.net>
|
||||
adriant <adriant@catalyst.net.nz>
|
|
@ -1,11 +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
|
||||
|
|
@ -1,10 +1,5 @@
|
|||
include AUTHORS
|
||||
include babel.cfg
|
||||
include LICENSE
|
||||
include README.rst
|
||||
include README.md
|
||||
include ChangeLog
|
||||
include tox.ini
|
||||
include .testr.conf
|
||||
recursive-include doc *
|
||||
recursive-include tools *
|
||||
recursive-include python-stacktaskclient *.po *.pot
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
include AUTHORS
|
||||
include babel.cfg
|
||||
include LICENSE
|
||||
include README.rst
|
||||
include ChangeLog
|
||||
include tox.ini
|
||||
include .testr.conf
|
||||
recursive-include doc *
|
||||
recursive-include tools *
|
||||
recursive-include python-stacktaskclient *.po *.pot
|
86
README.rst
86
README.rst
|
@ -1,86 +0,0 @@
|
|||
=================
|
||||
python-stacktaskclient
|
||||
=================
|
||||
|
||||
OpenStack Orchestration API Client Library
|
||||
|
||||
This is a client library for Stacktask built on the Catalyst Stacktask API. It
|
||||
provides a Python API (the ``stacktaskclient`` module) and a command-line tool
|
||||
(``stacktask``).
|
||||
|
||||
* Free software: Apache license
|
||||
* Documentation: http://docs.openstack.org/developer/python-heatclient
|
||||
* Source: http://git.openstack.org/cgit/openstack/python-heatclient
|
||||
* Bugs: http://bugs.launchpad.net/python-heatclient
|
||||
|
||||
===================
|
||||
Basic setup from source
|
||||
===================
|
||||
python tools/install_venv.py
|
||||
source .tox/venv/bin/activate
|
||||
source openrc.sh
|
||||
|
||||
Verify:
|
||||
'pip list' should give: python-stacktaskclient (0.6.1.dev63, /home/dale/dale/dev/openstack/python-heatclient)
|
||||
'which stacktask' should give: /home/dale/dale/dev/openstack/python-heatclient/.tox/venv/bin/stacktask
|
||||
'stacktask user-tenant-list' should give list of users.
|
||||
|
||||
===
|
||||
Add the registration endpoint
|
||||
===
|
||||
Either:
|
||||
keystone endpoint-create --service registration --publicurl http://192.168.122.160:8040/v1 --internalurl http://192.168.122.160:8040/v1 --region RegionOne
|
||||
Or:
|
||||
Add '--bypass-url http://192.168.122.160:8040/v1' to all stacktask calls
|
||||
|
||||
|
||||
===================
|
||||
Usage examples
|
||||
===
|
||||
(NYI) = Not Yet Implemented
|
||||
(NYI cli) = Not Yet Implemented, client implementation only.
|
||||
(NYI be) = Not Yet Implemented, backend service feature required.
|
||||
===================
|
||||
|
||||
List all current users in your tenant:
|
||||
stacktask user-list
|
||||
|
||||
Send/Resend invite for a user to use your tenant:
|
||||
(NYI cli) stacktask user-invite-send --user-email dale@catalyst-eu.net
|
||||
|
||||
List pending invites for members to your tenant:
|
||||
(NYI be) stacktask user-invite-list
|
||||
|
||||
Cancel invite to a user:
|
||||
(NYI be) stacktask user-invite-cancel --user-email dale@catalyst-eu.net
|
||||
|
||||
List pending actions(admin only)
|
||||
(NYI be) stacktask task-list
|
||||
|
||||
Cancel a token(admin only?):
|
||||
(NYI be) stacktask task-cancel --token-id <token>
|
||||
|
||||
Get information about an email token(returns params required, action type):
|
||||
(NYI cli) stacktask task-get --token-id <token>
|
||||
|
||||
Submit an action token(include any parameters):
|
||||
(NYI cli) stacktask task-submit --token-id <token> --parameters password=123456
|
||||
|
||||
Approve a pending action(admin only):
|
||||
(NYI be) stacktask task-approve --token-id <token>
|
||||
|
||||
Allow a user to invite others:
|
||||
stacktask managed-role-list
|
||||
(NYI be) stacktask user-role-add --role project_moderator --user dale@catalyst-eu.net
|
||||
|
||||
Allow a user to administrate your tenant:
|
||||
stacktask managed-role-list
|
||||
(NYI be) stacktask user-role-add --role project_moderator --user dale@catalyst-eu.net
|
||||
(NYI be) stacktask user-role-add --role project_owner --user dale@catalyst-eu.net
|
||||
|
||||
Remove user from your tenant:
|
||||
stacktask managed-role-list
|
||||
(NYI be) stacktask user-role-remove --role project_owner --user dale@catalyst-eu.net
|
||||
(NYI be) stacktask user-role-remove --role project_moderator --user dale@catalyst-eu.net
|
||||
(NYI be) stacktask user-role-remove --role _member_ --user dale@catalyst-eu.net
|
||||
(NYI be) stacktask user-role-remove --role Member --user dale@catalyst-eu.net
|
|
@ -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,266 +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', 'oslosphinx']
|
||||
|
||||
# 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 = '_theme'
|
||||
|
||||
# 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 = '%b %d, %Y'
|
||||
git_cmd = "git log --pretty=format:'%ad, commit %h' --date=local -n1"
|
||||
html_last_updated_fmt = os.popen(git_cmd).read()
|
||||
|
||||
# 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'
|
|
@ -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,75 +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)
|
||||
|
||||
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,8 +0,0 @@
|
|||
[DEFAULT]
|
||||
|
||||
# The list of modules to copy from openstack-common
|
||||
modules=apiclient
|
||||
module=cliutils
|
||||
|
||||
# The base module to hold the copy of openstack.common
|
||||
base=stacktaskclient
|
121
run_tests.sh
121
run_tests.sh
|
@ -1,121 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
BASE_DIR=`dirname $0`
|
||||
|
||||
function usage {
|
||||
echo "Usage: $0 [OPTION]..."
|
||||
echo "Run stacktaskclient test suite(s)"
|
||||
echo ""
|
||||
echo " -V, --virtual-env Use virtualenv. Install automatically if not present."
|
||||
echo " (Default is to run tests in local environment)"
|
||||
echo " -F, --force Force a clean re-build of the virtual environment. Useful when dependencies have been added."
|
||||
echo " -f, --func Functional tests have been removed."
|
||||
echo " -u, --unit Run unit tests (default when nothing specified)"
|
||||
echo " -p, --pep8 Run pep8 tests"
|
||||
echo " --all Run pep8 and unit tests"
|
||||
echo " -c, --coverage Generate coverage report"
|
||||
echo " -d, --debug Run tests with testtools instead of testr. This allows you to use the debugger."
|
||||
echo " -h, --help Print this usage message"
|
||||
exit
|
||||
}
|
||||
|
||||
# must not assign -a as an option, needed for selecting custom attributes
|
||||
no_venv=1
|
||||
function process_option {
|
||||
case "$1" in
|
||||
-V|--virtual-env) no_venv=0;;
|
||||
-F|--force) force=1;;
|
||||
-f|--func) test_func=1;;
|
||||
-u|--unit) test_unit=1;;
|
||||
-p|--pep8) test_pep8=1;;
|
||||
--all) test_unit=1; test_pep8=1;;
|
||||
-c|--coverage) coverage=1;;
|
||||
-d|--debug) debug=1;;
|
||||
-h|--help) usage;;
|
||||
*) args="$args $1"; test_unit=1;;
|
||||
esac
|
||||
}
|
||||
|
||||
venv=.venv
|
||||
with_venv=tools/with_venv.sh
|
||||
wrapper=""
|
||||
debug=0
|
||||
|
||||
function run_tests {
|
||||
echo 'Running tests'
|
||||
|
||||
if [ $debug -eq 1 ]; then
|
||||
echo "Debugging..."
|
||||
if [ "$args" = "" ]; then
|
||||
# Default to running all tests if specific test is not
|
||||
# provided.
|
||||
testrargs="discover ./stacktaskclient/tests"
|
||||
fi
|
||||
${wrapper} python -m testtools.run $args $testrargs
|
||||
|
||||
# Short circuit because all of the testr and coverage stuff
|
||||
# below does not make sense when running testtools.run for
|
||||
# debugging purposes.
|
||||
return $?
|
||||
fi
|
||||
|
||||
# Just run the test suites in current environment
|
||||
if [ -n "$args" ] ; then
|
||||
args="-t $args"
|
||||
fi
|
||||
${wrapper} python setup.py testr --slowest $args
|
||||
}
|
||||
|
||||
function run_pep8 {
|
||||
echo "Running flake8..."
|
||||
bash -c "${wrapper} flake8"
|
||||
}
|
||||
|
||||
# run unit tests with pep8 when no arguments are specified
|
||||
# otherwise process CLI options
|
||||
if [[ $# == 0 ]]; then
|
||||
test_pep8=1
|
||||
test_unit=1
|
||||
else
|
||||
for arg in "$@"; do
|
||||
process_option $arg
|
||||
done
|
||||
fi
|
||||
|
||||
if [ "$no_venv" == 0 ]
|
||||
then
|
||||
# Remove the virtual environment if --force used
|
||||
if [ "$force" == 1 ]; then
|
||||
echo "Cleaning virtualenv..."
|
||||
rm -rf ${venv}
|
||||
fi
|
||||
if [ -e ${venv} ]; then
|
||||
wrapper="${with_venv}"
|
||||
else
|
||||
# Automatically install the virtualenv
|
||||
python tools/install_venv.py
|
||||
wrapper="${with_venv}"
|
||||
fi
|
||||
fi
|
||||
|
||||
result=0
|
||||
|
||||
# If functional or unit tests have been selected, run them
|
||||
if [ "$test_unit" == 1 ] || [ "$debug" == 1 ] ; then
|
||||
run_tests
|
||||
result=$?
|
||||
fi
|
||||
|
||||
# Run pep8 if it was selected
|
||||
if [ "$test_pep8" == 1 ]; then
|
||||
run_pep8
|
||||
fi
|
||||
|
||||
# Generate coverage report
|
||||
if [ "$coverage" == 1 ]; then
|
||||
echo "Generating coverage report in ./cover"
|
||||
${wrapper} python setup.py testr --coverage --slowest
|
||||
${wrapper} python -m coverage report --show-missing
|
||||
fi
|
||||
|
||||
exit $result
|
32
setup.cfg
32
setup.cfg
|
@ -1,11 +1,11 @@
|
|||
[metadata]
|
||||
name = python-stacktaskclient
|
||||
summary = OpenStack Orchestration API Client Library
|
||||
summary = StackTask API Client Library
|
||||
description-file =
|
||||
README.rst
|
||||
author = OpenStack
|
||||
author-email = openstack-dev@lists.openstack.org
|
||||
home-page = http://www.openstack.org/
|
||||
README.md
|
||||
author = Dale Smith
|
||||
author-email = dale@catalyst-eu.net
|
||||
home-page = https://github.com/catalyst/stacktask-client
|
||||
classifier =
|
||||
Environment :: OpenStack
|
||||
Intended Audience :: Information Technology
|
||||
|
@ -16,8 +16,6 @@ classifier =
|
|||
Programming Language :: Python :: 2
|
||||
Programming Language :: Python :: 2.7
|
||||
Programming Language :: Python :: 2.6
|
||||
Programming Language :: Python :: 3
|
||||
Programming Language :: Python :: 3.4
|
||||
|
||||
[files]
|
||||
packages =
|
||||
|
@ -31,27 +29,7 @@ console_scripts =
|
|||
setup-hooks =
|
||||
pbr.hooks.setup_hook
|
||||
|
||||
[build_sphinx]
|
||||
source-dir = doc/source
|
||||
build-dir = doc/build
|
||||
all_files = 1
|
||||
|
||||
[upload_sphinx]
|
||||
upload-dir = doc/build/html
|
||||
|
||||
[wheel]
|
||||
universal = 1
|
||||
|
||||
[extract_messages]
|
||||
keywords = _ gettext ngettext l_ lazy_gettext
|
||||
mapping_file = babel.cfg
|
||||
output_file = python-stacktaskclient/locale/python-stacktaskclient.pot
|
||||
|
||||
[compile_catalog]
|
||||
directory = python-stacktaskclient/locale
|
||||
domain = python-stacktaskclient
|
||||
|
||||
[update_catalog]
|
||||
domain = python-stacktaskclient
|
||||
output_dir = python-stacktaskclient/locale
|
||||
input_file = python-stacktaskclient/locale/python-stacktaskclient.pot
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
[metadata]
|
||||
name = python-stacktaskclient
|
||||
summary = StackTask API Client Library
|
||||
description-file =
|
||||
README.md
|
||||
author = Dale Smith
|
||||
author-email = dale@catalyst-eu.net
|
||||
home-page = https://github.com/catalyst/stacktask-client
|
||||
classifier =
|
||||
Environment :: OpenStack
|
||||
Intended Audience :: Information Technology
|
||||
Intended Audience :: System Administrators
|
||||
License :: OSI Approved :: Apache Software License
|
||||
Operating System :: POSIX :: Linux
|
||||
Programming Language :: Python
|
||||
Programming Language :: Python :: 2
|
||||
Programming Language :: Python :: 2.7
|
||||
Programming Language :: Python :: 2.6
|
||||
|
||||
[files]
|
||||
packages =
|
||||
stacktaskclient
|
||||
|
||||
[entry_points]
|
||||
console_scripts =
|
||||
stacktask = stacktaskclient.shell:main
|
||||
|
||||
[global]
|
||||
setup-hooks =
|
||||
pbr.hooks.setup_hook
|
||||
|
||||
[wheel]
|
||||
universal = 1
|
||||
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import copy
|
||||
import six
|
||||
from six.moves.urllib import parse as urlparse
|
||||
from swiftclient import client as sc
|
||||
from swiftclient import utils as swiftclient_utils
|
||||
import uuid
|
||||
|
||||
from stacktaskclient import exc
|
||||
from stacktaskclient.openstack.common._i18n import _
|
||||
|
||||
|
||||
def build_derived_config_params(action, source, name, input_values,
|
||||
server_id, signal_transport, signal_id=None):
|
||||
|
||||
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 stacktask 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,52 +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 stacktaskclient.common import template_format
|
||||
from stacktaskclient.openstack.common._i18n import _
|
||||
|
||||
import yaml
|
||||
|
||||
|
||||
SECTIONS = (PARAMETER_DEFAULTS, PARAMETERS, RESOURCE_REGISTRY) = \
|
||||
('parameter_defaults', 'parameters', 'resource_registry')
|
||||
|
||||
|
||||
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 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:
|
||||
env[param] = {}
|
|
@ -1,128 +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.
|
||||
|
||||
from stacktaskclient.common import utils
|
||||
import stacktaskclient.exc as exc
|
||||
|
||||
from stacktaskclient.openstack.common._i18n import _
|
||||
|
||||
|
||||
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'
|
||||
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):
|
||||
events = _get_stack_events(hc, stack_id, event_args)
|
||||
if nested_depth > 0:
|
||||
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:
|
||||
marker_index = [e.id for e in events].index(marker)
|
||||
events = events[marker_index:]
|
||||
|
||||
# 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 stacktask version, but this will be required
|
||||
# until kilo stacktask 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_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:
|
||||
# Show which stack the event comes from (for nested events)
|
||||
for e in events:
|
||||
e.stack_name = stack_id.split("/")[0]
|
||||
return events
|
|
@ -1,63 +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 stacktaskclient.openstack.common._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.
|
||||
'''
|
||||
if tmpl_str.startswith('{'):
|
||||
tpl = json.loads(tmpl_str)
|
||||
else:
|
||||
try:
|
||||
tpl = yaml.load(tmpl_str, Loader=yaml_loader)
|
||||
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,224 +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 parse
|
||||
from six.moves.urllib import request
|
||||
|
||||
from stacktaskclient.common import environment_format
|
||||
from stacktaskclient.common import template_format
|
||||
from stacktaskclient.common import utils
|
||||
from stacktaskclient import exc
|
||||
from stacktaskclient.openstack.common._i18n import _
|
||||
|
||||
|
||||
def get_template_contents(template_file=None, template_url=None,
|
||||
template_object=None, object_request=None,
|
||||
files=None, existing=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:
|
||||
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)
|
||||
return files, template
|
||||
|
||||
|
||||
def resolve_template_get_files(template, files, template_base_url):
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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):
|
||||
|
||||
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)
|
||||
|
||||
if isinstance(from_data, dict):
|
||||
for key, value in iter(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:
|
||||
file_content = utils.read_url_content(str_url)
|
||||
if is_template(file_content):
|
||||
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.'''
|
||||
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):
|
||||
merged_files = {}
|
||||
merged_env = {}
|
||||
|
||||
if env_paths:
|
||||
for env_path in env_paths:
|
||||
files, env = process_environment_and_files(env_path, template,
|
||||
template_url)
|
||||
|
||||
# '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)
|
||||
|
||||
return merged_files, merged_env
|
||||
|
||||
|
||||
def process_environment_and_files(env_path=None, template=None,
|
||||
template_url=None):
|
||||
files = {}
|
||||
env = {}
|
||||
|
||||
if 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)
|
||||
|
||||
return files, env
|
||||
|
||||
|
||||
def resolve_environment_urls(resource_registry, files, env_base_url):
|
||||
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 == 'hooks':
|
||||
return True
|
||||
|
||||
get_file_contents(rr, files, base_url, ignore_if)
|
||||
|
||||
for res_name, res_dict in iter(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)
|
|
@ -81,7 +81,7 @@ class HTTPMultipleChoices(HTTPException):
|
|||
'name': self.__class__.__name__,
|
||||
'code': self.code,
|
||||
'details': self.details
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
class BadRequest(HTTPException):
|
||||
|
@ -167,7 +167,7 @@ class HTTPServiceUnavailable(ServiceUnavailable):
|
|||
pass
|
||||
|
||||
|
||||
#NOTE(bcwaldon): Build a mapping of HTTP codes to corresponding exception
|
||||
# NOTE(bcwaldon): Build a mapping of HTTP codes to corresponding exception
|
||||
# classes
|
||||
_code_map = {}
|
||||
for obj_name in dir(sys.modules[__name__]):
|
||||
|
|
|
@ -44,192 +44,191 @@ osprofiler_profiler = importutils.try_import("osprofiler.profiler")
|
|||
class StacktaskShell(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 avaible in
|
||||
# python-keystoneclient.
|
||||
parser.add_argument('-k', '--insecure',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help=_('Explicitly allow this client 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(
|
||||
'-k', '--insecure',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help=_(
|
||||
'Explicitly allow this client 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',
|
||||
help=_('Path of certificate file to use in SSL '
|
||||
'connection. This file can optionally be '
|
||||
'prepended with the private key.'))
|
||||
parser.add_argument(
|
||||
'--os-cert',
|
||||
help=_(
|
||||
'Path of certificate file to use in SSL '
|
||||
'connection. This file can optionally be '
|
||||
'prepended with the private key.'))
|
||||
|
||||
parser.add_argument('--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(
|
||||
'--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('--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(
|
||||
'--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('--os-username',
|
||||
default=utils.env('OS_USERNAME'),
|
||||
help=_('Defaults to %(value)s.') % {
|
||||
'value': 'env[OS_USERNAME]'
|
||||
})
|
||||
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_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',
|
||||
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_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',
|
||||
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_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',
|
||||
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_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',
|
||||
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_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',
|
||||
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_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',
|
||||
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_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',
|
||||
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_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',
|
||||
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_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=_('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_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=_('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_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',
|
||||
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_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',
|
||||
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_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',
|
||||
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_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',
|
||||
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_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',
|
||||
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)
|
||||
parser.add_argument('--os_endpoint_type', help=argparse.SUPPRESS)
|
||||
|
||||
def get_base_parser(self):
|
||||
parser = argparse.ArgumentParser(
|
||||
|
@ -243,70 +242,74 @@ class StacktaskShell(object):
|
|||
)
|
||||
|
||||
# Global arguments
|
||||
parser.add_argument('-h', '--help',
|
||||
action='store_true',
|
||||
help=argparse.SUPPRESS)
|
||||
parser.add_argument(
|
||||
'-h', '--help', action='store_true', help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--version',
|
||||
action='version',
|
||||
version=stacktaskclient.__version__,
|
||||
help=_("Shows the client version and exits."))
|
||||
parser.add_argument(
|
||||
'--version', action='version',
|
||||
version=stacktaskclient.__version__,
|
||||
help=_("Shows the client version and exits."))
|
||||
|
||||
parser.add_argument('-d', '--debug',
|
||||
default=bool(utils.env('STACKTASKCLIENT_DEBUG')),
|
||||
action='store_true',
|
||||
help=_('Defaults to %(value)s.') % {
|
||||
'value': 'env[STACKTASKCLIENT_DEBUG]'
|
||||
})
|
||||
parser.add_argument(
|
||||
'-d', '--debug',
|
||||
default=bool(utils.env('STACKTASKCLIENT_DEBUG')),
|
||||
action='store_true',
|
||||
help=_('Defaults to %(value)s.') % {
|
||||
'value': 'env[STACKTASKCLIENT_DEBUG]'
|
||||
})
|
||||
|
||||
parser.add_argument('-v', '--verbose',
|
||||
default=False, action="store_true",
|
||||
help=_("Print more verbose output."))
|
||||
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'))
|
||||
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 stacktaskclient 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(
|
||||
'--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('--bypass-url',
|
||||
default=utils.env('STACKTASK_BYPASS_URL'),
|
||||
help=_('Defaults to %(value)s.') % {
|
||||
'value': 'env[STACKTASK_BYPASS_URL]'
|
||||
})
|
||||
parser.add_argument(
|
||||
'--bypass-url',
|
||||
default=utils.env('STACKTASK_BYPASS_URL'),
|
||||
help=_('Defaults to %(value)s.') % {
|
||||
'value': 'env[STACKTASK_BYPASS_URL]'
|
||||
})
|
||||
|
||||
parser.add_argument('--api-version',
|
||||
default=utils.env('STACKTASK_API_VERSION',
|
||||
default='1'),
|
||||
help=_('Defaults to %(value)s or 1.') % {
|
||||
'value': 'env[STACKTASK_API_VERSION]'
|
||||
})
|
||||
parser.add_argument(
|
||||
'--api-version',
|
||||
default=utils.env('STACKTASK_API_VERSION',
|
||||
default='1'),
|
||||
help=_('Defaults to %(value)s or 1.') % {
|
||||
'value': 'env[STACKTASK_API_VERSION]'
|
||||
})
|
||||
|
||||
# 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.'))
|
||||
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):
|
||||
|
@ -398,18 +401,14 @@ class StacktaskShell(object):
|
|||
# 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)
|
||||
|
||||
# FIXME(gyee): this code should come from keystoneclient
|
||||
if verify is None:
|
||||
if insecure:
|
||||
verify = False
|
||||
else:
|
||||
# TODO(gyee): should we do
|
||||
# stacktaskclient.common.http.get_system_ca_fle()?
|
||||
verify = cacert or True
|
||||
|
||||
return kssession.Session(verify=verify, cert=cert, timeout=timeout)
|
||||
|
@ -504,23 +503,26 @@ class StacktaskShell(object):
|
|||
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]"))
|
||||
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]"))
|
||||
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.bypass_url:
|
||||
raise exc.CommandError(_("If you specify --os-no-client-auth"
|
||||
" you must also specify a Stacktask API"
|
||||
" URL via either --bypass-url or"
|
||||
" env[STACKTASK_BYPASS_URL]"))
|
||||
raise exc.CommandError(
|
||||
_("If you specify --os-no-client-auth"
|
||||
" you must also specify a Stacktask API"
|
||||
" URL via either --bypass-url or"
|
||||
" env[STACKTASK_BYPASS_URL]"))
|
||||
else:
|
||||
# Tenant/project name or ID is needed to make keystoneclient
|
||||
# retrieve a service catalog, it's not required if
|
||||
|
@ -528,20 +530,22 @@ class StacktaskShell(object):
|
|||
|
||||
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]"))
|
||||
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]"))
|
||||
raise exc.CommandError(
|
||||
_("You must provide an auth url via"
|
||||
" either --os-auth-url or via"
|
||||
" env[OS_AUTH_URL]"))
|
||||
|
||||
kwargs = {
|
||||
'insecure': args.insecure,
|
||||
|
|
|
@ -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,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 .tox/functional/bin/python /usr/local/jenkins/slave_scripts/subunit2html.py $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,18 +0,0 @@
|
|||
HeatTemplateFormatVersion: '2012-12-12'
|
||||
Description: Minimal template to test validation
|
||||
Parameters:
|
||||
InstanceImage:
|
||||
Description: Glance image name
|
||||
Type: String
|
||||
InstanceType:
|
||||
Description: Nova instance type
|
||||
Type: String
|
||||
Default: m1.small
|
||||
AllowedValues: [m1.tiny, m1.small, m1.medium, m1.large, m1.nano, m1.xlarge, m1.micro, m1.heat]
|
||||
ConstraintDescription: must be a valid nova instance type.
|
||||
Resources:
|
||||
InstanceResource:
|
||||
Type: OS::Nova::Server
|
||||
Properties:
|
||||
flavor: {Ref: InstanceType}
|
||||
image: {Ref: InstanceImage}
|
|
@ -1,19 +0,0 @@
|
|||
heat_template_version: 2015-04-30
|
||||
description: A minimal HOT test template
|
||||
parameters:
|
||||
instance_image:
|
||||
description: Glance image name
|
||||
type: string
|
||||
instance_type:
|
||||
description: Nova instance type
|
||||
type: string
|
||||
default: m1.small
|
||||
constraints:
|
||||
- allowed_values: [m1.tiny, m1.small, m1.medium, m1.large, m1.nano, m1.xlarge, m1.micro, m1.heat]
|
||||
description: must be a valid nova instance type.
|
||||
resources:
|
||||
instance:
|
||||
type: OS::Nova::Server
|
||||
properties:
|
||||
image: { get_param: instance_image }
|
||||
flavor: { get_param: instance_type }
|
|
@ -1,102 +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,256 +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
|
||||
|
||||
from heatclient.common import http
|
||||
from heatclient import exc
|
||||
|
||||
|
||||
def script_heat_list(url=None, show_nested=False, client=http.HTTPClient):
|
||||
if url is None:
|
||||
url = '/stacks?'
|
||||
|
||||
resp_dict = {"stacks": [
|
||||
{
|
||||
"id": "1",
|
||||
"stack_name": "teststack",
|
||||
"stack_owner": "testowner",
|
||||
"project": "testproject",
|
||||
"stack_status": 'CREATE_COMPLETE',
|
||||
"creation_time": "2012-10-25T01:58:47Z"
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"stack_name": "teststack2",
|
||||
"stack_owner": "testowner",
|
||||
"project": "testproject",
|
||||
"stack_status": 'IN_PROGRESS',
|
||||
"creation_time": "2012-10-25T01:58:47Z"
|
||||
}]
|
||||
}
|
||||
if show_nested:
|
||||
nested = {
|
||||
"id": "3",
|
||||
"stack_name": "teststack_nested",
|
||||
"stack_status": 'IN_PROGRESS',
|
||||
"creation_time": "2012-10-25T01:58:47Z",
|
||||
"parent": "theparentof3"
|
||||
}
|
||||
resp_dict["stacks"].append(nested)
|
||||
resp = FakeHTTPResponse(200,
|
||||
'success, you',
|
||||
{'content-type': 'application/json'},
|
||||
jsonutils.dumps(resp_dict))
|
||||
if client == http.SessionClient:
|
||||
client.request(url, 'GET').AndReturn(resp)
|
||||
else:
|
||||
client.json_request('GET', url).AndReturn((resp, resp_dict))
|
||||
|
||||
|
||||
def mock_script_heat_list(show_nested=False):
|
||||
resp_dict = {"stacks": [
|
||||
{
|
||||
"id": "1",
|
||||
"stack_name": "teststack",
|
||||
"stack_owner": "testowner",
|
||||
"project": "testproject",
|
||||
"stack_status": 'CREATE_COMPLETE',
|
||||
"creation_time": "2012-10-25T01:58:47Z"
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"stack_name": "teststack2",
|
||||
"stack_owner": "testowner",
|
||||
"project": "testproject",
|
||||
"stack_status": 'IN_PROGRESS',
|
||||
"creation_time": "2012-10-25T01:58:47Z"
|
||||
}]
|
||||
}
|
||||
if show_nested:
|
||||
nested = {
|
||||
"id": "3",
|
||||
"stack_name": "teststack_nested",
|
||||
"stack_status": 'IN_PROGRESS',
|
||||
"creation_time": "2012-10-25T01:58:47Z",
|
||||
"parent": "theparentof3"
|
||||
}
|
||||
resp_dict["stacks"].append(nested)
|
||||
resp = FakeHTTPResponse(200,
|
||||
'success, you',
|
||||
{'content-type': 'application/json'},
|
||||
jsonutils.dumps(resp_dict))
|
||||
return resp, resp_dict
|
||||
|
||||
|
||||
def mock_script_event_list(
|
||||
stack_name="teststack", resource_name=None,
|
||||
rsrc_eventid1="7fecaeed-d237-4559-93a5-92d5d9111205",
|
||||
rsrc_eventid2="e953547a-18f8-40a7-8e63-4ec4f509648b",
|
||||
action="CREATE", final_state="COMPLETE", fakehttp=True):
|
||||
|
||||
resp_dict = {"events": [
|
||||
{"event_time": "2013-12-05T14:14:31Z",
|
||||
"id": rsrc_eventid1,
|
||||
"links": [{"href": "http://heat.example.com:8004/foo",
|
||||
"rel": "self"},
|
||||
{"href": "http://heat.example.com:8004/foo2",
|
||||
"rel": "resource"},
|
||||
{"href": "http://heat.example.com:8004/foo3",
|
||||
"rel": "stack"}],
|
||||
"logical_resource_id": "myDeployment",
|
||||
"physical_resource_id": None,
|
||||
"resource_name": resource_name if resource_name else "testresource",
|
||||
"resource_status": "%s_IN_PROGRESS" % action,
|
||||
"resource_status_reason": "state changed"},
|
||||
{"event_time": "2013-12-05T14:14:32Z",
|
||||
"id": rsrc_eventid2,
|
||||
"links": [{"href": "http://heat.example.com:8004/foo",
|
||||
"rel": "self"},
|
||||
{"href": "http://heat.example.com:8004/foo2",
|
||||
"rel": "resource"},
|
||||
{"href": "http://heat.example.com:8004/foo3",
|
||||
"rel": "stack"}],
|
||||
"logical_resource_id": "myDeployment",
|
||||
"physical_resource_id": "bce15ec4-8919-4a02-8a90-680960fb3731",
|
||||
"resource_name": resource_name if resource_name else "testresource",
|
||||
"resource_status": "%s_%s" % (action, final_state),
|
||||
"resource_status_reason": "state changed"}]}
|
||||
|
||||
if resource_name is None:
|
||||
# if resource_name is not specified,
|
||||
# then request is made for stack events. Hence include the stack event
|
||||
stack_event1 = "0159dccd-65e1-46e8-a094-697d20b009e5"
|
||||
stack_event2 = "8f591a36-7190-4adb-80da-00191fe22388"
|
||||
resp_dict["events"].insert(
|
||||
0, {"event_time": "2013-12-05T14:14:30Z",
|
||||
"id": stack_event1,
|
||||
"links": [{"href": "http://heat.example.com:8004/foo",
|
||||
"rel": "self"},
|
||||
{"href": "http://heat.example.com:8004/foo2",
|
||||
"rel": "resource"},
|
||||
{"href": "http://heat.example.com:8004/foo3",
|
||||
"rel": "stack"}],
|
||||
"logical_resource_id": "aResource",
|
||||
"physical_resource_id": None,
|
||||
"resource_name": stack_name,
|
||||
"resource_status": "%s_IN_PROGRESS" % action,
|
||||
"resource_status_reason": "state changed"})
|
||||
resp_dict["events"].append(
|
||||
{"event_time": "2013-12-05T14:14:33Z",
|
||||
"id": stack_event2,
|
||||
"links": [{"href": "http://heat.example.com:8004/foo",
|
||||
"rel": "self"},
|
||||
{"href": "http://heat.example.com:8004/foo2",
|
||||
"rel": "resource"},
|
||||
{"href": "http://heat.example.com:8004/foo3",
|
||||
"rel": "stack"}],
|
||||
"logical_resource_id": "aResource",
|
||||
"physical_resource_id": None,
|
||||
"resource_name": stack_name,
|
||||
"resource_status": "%s_%s" % (action, final_state),
|
||||
"resource_status_reason": "state changed"})
|
||||
|
||||
resp = FakeHTTPResponse(
|
||||
200,
|
||||
'OK',
|
||||
{'content-type': 'application/json'},
|
||||
jsonutils.dumps(resp_dict)) if fakehttp else None
|
||||
|
||||
return resp, resp_dict
|
||||
|
||||
|
||||
def script_heat_normal_error(client=http.HTTPClient):
|
||||
resp_dict = {
|
||||
"explanation": "The resource could not be found.",
|
||||
"code": 404,
|
||||
"error": {
|
||||
"message": "The Stack (bad) could not be found.",
|
||||
"type": "StackNotFound",
|
||||
"traceback": "",
|
||||
},
|
||||
"title": "Not Found"
|
||||
}
|
||||
resp = FakeHTTPResponse(400,
|
||||
'The resource could not be found',
|
||||
{'content-type': 'application/json'},
|
||||
jsonutils.dumps(resp_dict))
|
||||
if client == http.SessionClient:
|
||||
client.request('/stacks/bad', 'GET').AndRaise(exc.from_response(resp))
|
||||
else:
|
||||
client.json_request('GET',
|
||||
'/stacks/bad').AndRaise(exc.from_response(resp))
|
||||
|
||||
|
||||
def script_heat_error(resp_string, client=http.HTTPClient):
|
||||
resp = FakeHTTPResponse(400,
|
||||
'The resource could not be found',
|
||||
{'content-type': 'application/json'},
|
||||
resp_string)
|
||||
if client == http.SessionClient:
|
||||
client.request('/stacks/bad', 'GET').AndRaise(exc.from_response(resp))
|
||||
else:
|
||||
client.json_request('GET',
|
||||
'/stacks/bad').AndRaise(exc.from_response(resp))
|
||||
|
||||
|
||||
def fake_headers():
|
||||
return {'X-Auth-Token': 'abcd1234',
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'User-Agent': 'python-heatclient'}
|
||||
|
||||
|
||||
class FakeServiceCatalog():
|
||||
def url_for(self, endpoint_type, service_type):
|
||||
return 'http://192.168.1.5:8004/v1/f14b41234'
|
||||
|
||||
|
||||
class FakeKeystone():
|
||||
service_catalog = FakeServiceCatalog()
|
||||
|
||||
def __init__(self, auth_token):
|
||||
self.auth_token = auth_token
|
||||
|
||||
|
||||
class FakeRaw():
|
||||
version = 110
|
||||
|
||||
|
||||
class FakeHTTPResponse():
|
||||
|
||||
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,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.
|
||||
|
||||
from heatclient.tests.unit import fakes
|
||||
from heatclient.v1 import actions
|
||||
|
||||
import testtools
|
||||
|
||||
|
||||
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,41 +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,861 +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 logging
|
||||
import mock
|
||||
import os
|
||||
import socket
|
||||
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
import requests
|
||||
import six
|
||||
import testtools
|
||||
|
||||
from heatclient.common import http
|
||||
from heatclient.common import utils
|
||||
from heatclient import exc
|
||||
from heatclient.tests.unit import fakes
|
||||
from keystoneclient import adapter
|
||||
from mox3 import mox
|
||||
|
||||
|
||||
class HttpClientTest(testtools.TestCase):
|
||||
|
||||
# Patch os.environ to avoid required auth info.
|
||||
def setUp(self):
|
||||
super(HttpClientTest, self).setUp()
|
||||
self.m = mox.Mox()
|
||||
self.m.StubOutWithMock(requests, 'request')
|
||||
self.addCleanup(self.m.VerifyAll)
|
||||
self.addCleanup(self.m.UnsetStubs)
|
||||
|
||||
def test_http_raw_request(self):
|
||||
headers = {'Content-Type': 'application/octet-stream',
|
||||
'User-Agent': 'python-heatclient'}
|
||||
|
||||
# Record a 200
|
||||
mock_conn = http.requests.request('GET', 'http://example.com:8004',
|
||||
allow_redirects=False,
|
||||
headers=headers)
|
||||
mock_conn.AndReturn(
|
||||
fakes.FakeHTTPResponse(
|
||||
200, 'OK',
|
||||
{'content-type': 'application/octet-stream'},
|
||||
''))
|
||||
# Replay, create client, assert
|
||||
self.m.ReplayAll()
|
||||
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]))
|
||||
|
||||
def test_token_or_credentials(self):
|
||||
# Record a 200
|
||||
fake200 = fakes.FakeHTTPResponse(
|
||||
200, 'OK',
|
||||
{'content-type': 'application/octet-stream'},
|
||||
'')
|
||||
|
||||
# no token or credentials
|
||||
mock_conn = http.requests.request(
|
||||
'GET', 'http://example.com:8004',
|
||||
allow_redirects=False,
|
||||
headers={'Content-Type': 'application/octet-stream',
|
||||
'User-Agent': 'python-heatclient'})
|
||||
mock_conn.AndReturn(fake200)
|
||||
|
||||
# credentials
|
||||
mock_conn = http.requests.request(
|
||||
'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_conn.AndReturn(fake200)
|
||||
|
||||
# token suppresses credentials
|
||||
mock_conn = http.requests.request(
|
||||
'GET', 'http://example.com:8004',
|
||||
allow_redirects=False,
|
||||
headers={'Content-Type': 'application/octet-stream',
|
||||
'User-Agent': 'python-heatclient',
|
||||
'X-Auth-Token': 'abcd1234'})
|
||||
mock_conn.AndReturn(fake200)
|
||||
|
||||
# Replay, create client, assert
|
||||
self.m.ReplayAll()
|
||||
client = http.HTTPClient('http://example.com:8004')
|
||||
resp = client.raw_request('GET', '')
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
client.username = 'user'
|
||||
client.password = 'pass'
|
||||
resp = client.raw_request('GET', '')
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
client.auth_token = 'abcd1234'
|
||||
resp = client.raw_request('GET', '')
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
def test_include_pass(self):
|
||||
# Record a 200
|
||||
fake200 = fakes.FakeHTTPResponse(
|
||||
200, 'OK',
|
||||
{'content-type': 'application/octet-stream'},
|
||||
'')
|
||||
|
||||
# no token or credentials
|
||||
mock_conn = http.requests.request(
|
||||
'GET', 'http://example.com:8004',
|
||||
allow_redirects=False,
|
||||
headers={'Content-Type': 'application/octet-stream',
|
||||
'User-Agent': 'python-heatclient'})
|
||||
mock_conn.AndReturn(fake200)
|
||||
|
||||
# credentials
|
||||
mock_conn = http.requests.request(
|
||||
'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_conn.AndReturn(fake200)
|
||||
|
||||
# token suppresses credentials
|
||||
mock_conn = http.requests.request(
|
||||
'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'})
|
||||
mock_conn.AndReturn(fake200)
|
||||
|
||||
# Replay, create client, assert
|
||||
self.m.ReplayAll()
|
||||
client = http.HTTPClient('http://example.com:8004')
|
||||
resp = client.raw_request('GET', '')
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
client.username = 'user'
|
||||
client.password = 'pass'
|
||||
client.include_pass = True
|
||||
resp = client.raw_request('GET', '')
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
client.auth_token = 'abcd1234'
|
||||
resp = client.raw_request('GET', '')
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
def test_not_include_pass(self):
|
||||
# Record a 200
|
||||
fake500 = fakes.FakeHTTPResponse(
|
||||
500, 'ERROR',
|
||||
{'content-type': 'application/octet-stream'},
|
||||
'(HTTP 401)')
|
||||
|
||||
# no token or credentials
|
||||
mock_conn = http.requests.request(
|
||||
'GET', 'http://example.com:8004',
|
||||
allow_redirects=False,
|
||||
headers={'Content-Type': 'application/octet-stream',
|
||||
'User-Agent': 'python-heatclient'})
|
||||
mock_conn.AndReturn(fake500)
|
||||
|
||||
# Replay, create client, assert
|
||||
self.m.ReplayAll()
|
||||
client = http.HTTPClient('http://example.com:8004')
|
||||
e = self.assertRaises(exc.HTTPUnauthorized,
|
||||
client.raw_request, 'GET', '')
|
||||
self.assertIn('include-password', str(e))
|
||||
|
||||
def test_region_name(self):
|
||||
# Record a 200
|
||||
fake200 = fakes.FakeHTTPResponse(
|
||||
200, 'OK',
|
||||
{'content-type': 'application/octet-stream'},
|
||||
'')
|
||||
|
||||
# Specify region name
|
||||
mock_conn = http.requests.request(
|
||||
'GET', 'http://example.com:8004',
|
||||
allow_redirects=False,
|
||||
headers={'Content-Type': 'application/octet-stream',
|
||||
'X-Region-Name': 'RegionOne',
|
||||
'User-Agent': 'python-heatclient'})
|
||||
mock_conn.AndReturn(fake200)
|
||||
|
||||
# Replay, create client, assert
|
||||
self.m.ReplayAll()
|
||||
client = http.HTTPClient('http://example.com:8004')
|
||||
client.region_name = 'RegionOne'
|
||||
resp = client.raw_request('GET', '')
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
def test_http_json_request(self):
|
||||
# Record a 200
|
||||
mock_conn = http.requests.request(
|
||||
'GET', 'http://example.com:8004',
|
||||
allow_redirects=False,
|
||||
headers={'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'User-Agent': 'python-heatclient'})
|
||||
mock_conn.AndReturn(
|
||||
fakes.FakeHTTPResponse(
|
||||
200, 'OK',
|
||||
{'content-type': 'application/json'},
|
||||
'{}'))
|
||||
# Replay, create client, assert
|
||||
self.m.ReplayAll()
|
||||
client = http.HTTPClient('http://example.com:8004')
|
||||
resp, body = client.json_request('GET', '')
|
||||
self.assertEqual(200, resp.status_code)
|
||||
self.assertEqual({}, body)
|
||||
|
||||
def test_http_json_request_argument_passed_to_requests(self):
|
||||
"""Check that we have sent the proper arguments to requests."""
|
||||
# Record a 200
|
||||
mock_conn = http.requests.request(
|
||||
'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'})
|
||||
mock_conn.AndReturn(
|
||||
fakes.FakeHTTPResponse(
|
||||
200, 'OK',
|
||||
{'content-type': 'application/json'},
|
||||
'{}'))
|
||||
# Replay, create client, assert
|
||||
self.m.ReplayAll()
|
||||
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)
|
||||
|
||||
def test_http_json_request_w_req_body(self):
|
||||
# Record a 200
|
||||
mock_conn = http.requests.request(
|
||||
'GET', 'http://example.com:8004',
|
||||
body='test-body',
|
||||
allow_redirects=False,
|
||||
headers={'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'User-Agent': 'python-heatclient'})
|
||||
mock_conn.AndReturn(
|
||||
fakes.FakeHTTPResponse(
|
||||
200, 'OK',
|
||||
{'content-type': 'application/json'},
|
||||
'{}'))
|
||||
# Replay, create client, assert
|
||||
self.m.ReplayAll()
|
||||
client = http.HTTPClient('http://example.com:8004')
|
||||
resp, body = client.json_request('GET', '', body='test-body')
|
||||
self.assertEqual(200, resp.status_code)
|
||||
self.assertEqual({}, body)
|
||||
|
||||
def test_http_json_request_non_json_resp_cont_type(self):
|
||||
# Record a 200
|
||||
mock_conn = http.requests.request(
|
||||
'GET', 'http://example.com:8004', body='test-body',
|
||||
allow_redirects=False,
|
||||
headers={'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'User-Agent': 'python-heatclient'})
|
||||
mock_conn.AndReturn(
|
||||
fakes.FakeHTTPResponse(
|
||||
200, 'OK',
|
||||
{'content-type': 'not/json'},
|
||||
{}))
|
||||
# Replay, create client, assert
|
||||
self.m.ReplayAll()
|
||||
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)
|
||||
|
||||
def test_http_json_request_invalid_json(self):
|
||||
# Record a 200
|
||||
mock_conn = http.requests.request(
|
||||
'GET', 'http://example.com:8004',
|
||||
allow_redirects=False,
|
||||
headers={'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'User-Agent': 'python-heatclient'})
|
||||
mock_conn.AndReturn(
|
||||
fakes.FakeHTTPResponse(
|
||||
200, 'OK',
|
||||
{'content-type': 'application/json'},
|
||||
'invalid-json'))
|
||||
# Replay, create client, assert
|
||||
self.m.ReplayAll()
|
||||
client = http.HTTPClient('http://example.com:8004')
|
||||
resp, body = client.json_request('GET', '')
|
||||
self.assertEqual(200, resp.status_code)
|
||||
self.assertEqual('invalid-json', body)
|
||||
|
||||
def test_http_manual_redirect_delete(self):
|
||||
mock_conn = http.requests.request(
|
||||
'DELETE', 'http://example.com:8004/foo',
|
||||
allow_redirects=False,
|
||||
headers={'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'User-Agent': 'python-heatclient'})
|
||||
mock_conn.AndReturn(
|
||||
fakes.FakeHTTPResponse(
|
||||
302, 'Found',
|
||||
{'location': 'http://example.com:8004/foo/bar'},
|
||||
''))
|
||||
mock_conn = http.requests.request(
|
||||
'DELETE', 'http://example.com:8004/foo/bar',
|
||||
allow_redirects=False,
|
||||
headers={'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'User-Agent': 'python-heatclient'})
|
||||
mock_conn.AndReturn(
|
||||
fakes.FakeHTTPResponse(
|
||||
200, 'OK',
|
||||
{'content-type': 'application/json'},
|
||||
'{}'))
|
||||
|
||||
self.m.ReplayAll()
|
||||
|
||||
client = http.HTTPClient('http://example.com:8004/foo')
|
||||
resp, body = client.json_request('DELETE', '')
|
||||
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
def test_http_manual_redirect_post(self):
|
||||
mock_conn = http.requests.request(
|
||||
'POST', 'http://example.com:8004/foo',
|
||||
allow_redirects=False,
|
||||
headers={'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'User-Agent': 'python-heatclient'})
|
||||
mock_conn.AndReturn(
|
||||
fakes.FakeHTTPResponse(
|
||||
302, 'Found',
|
||||
{'location': 'http://example.com:8004/foo/bar'},
|
||||
''))
|
||||
mock_conn = http.requests.request(
|
||||
'POST', 'http://example.com:8004/foo/bar',
|
||||
allow_redirects=False,
|
||||
headers={'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'User-Agent': 'python-heatclient'})
|
||||
mock_conn.AndReturn(
|
||||
fakes.FakeHTTPResponse(
|
||||
200, 'OK',
|
||||
{'content-type': 'application/json'},
|
||||
'{}'))
|
||||
|
||||
self.m.ReplayAll()
|
||||
|
||||
client = http.HTTPClient('http://example.com:8004/foo')
|
||||
resp, body = client.json_request('POST', '')
|
||||
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
def test_http_manual_redirect_put(self):
|
||||
mock_conn = http.requests.request(
|
||||
'PUT', 'http://example.com:8004/foo',
|
||||
allow_redirects=False,
|
||||
headers={'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'User-Agent': 'python-heatclient'})
|
||||
mock_conn.AndReturn(
|
||||
fakes.FakeHTTPResponse(
|
||||
302, 'Found',
|
||||
{'location': 'http://example.com:8004/foo/bar'},
|
||||
''))
|
||||
mock_conn = http.requests.request(
|
||||
'PUT', 'http://example.com:8004/foo/bar',
|
||||
allow_redirects=False,
|
||||
headers={'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'User-Agent': 'python-heatclient'})
|
||||
mock_conn.AndReturn(
|
||||
fakes.FakeHTTPResponse(
|
||||
200, 'OK',
|
||||
{'content-type': 'application/json'},
|
||||
'{}'))
|
||||
|
||||
self.m.ReplayAll()
|
||||
|
||||
client = http.HTTPClient('http://example.com:8004/foo')
|
||||
resp, body = client.json_request('PUT', '')
|
||||
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
def test_http_manual_redirect_put_uppercase(self):
|
||||
mock_conn = http.requests.request(
|
||||
'PUT', 'http://EXAMPLE.com:8004/foo',
|
||||
allow_redirects=False,
|
||||
headers={'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'User-Agent': 'python-heatclient'})
|
||||
mock_conn.AndReturn(
|
||||
fakes.FakeHTTPResponse(
|
||||
302, 'Found',
|
||||
{'location': 'http://example.com:8004/foo/bar'},
|
||||
''))
|
||||
mock_conn = http.requests.request(
|
||||
'PUT', 'http://EXAMPLE.com:8004/foo/bar',
|
||||
allow_redirects=False,
|
||||
headers={'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'User-Agent': 'python-heatclient'})
|
||||
mock_conn.AndReturn(
|
||||
fakes.FakeHTTPResponse(
|
||||
200, 'OK',
|
||||
{'content-type': 'application/json'},
|
||||
'{}'))
|
||||
|
||||
self.m.ReplayAll()
|
||||
|
||||
client = http.HTTPClient('http://EXAMPLE.com:8004/foo')
|
||||
resp, body = client.json_request('PUT', '')
|
||||
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
def test_http_manual_redirect_prohibited(self):
|
||||
mock_conn = http.requests.request(
|
||||
'DELETE', 'http://example.com:8004/foo',
|
||||
allow_redirects=False,
|
||||
headers={'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'User-Agent': 'python-heatclient'})
|
||||
mock_conn.AndReturn(
|
||||
fakes.FakeHTTPResponse(
|
||||
302, 'Found',
|
||||
{'location': 'http://example.com:8004/'},
|
||||
''))
|
||||
self.m.ReplayAll()
|
||||
client = http.HTTPClient('http://example.com:8004/foo')
|
||||
self.assertRaises(exc.InvalidEndpoint,
|
||||
client.json_request, 'DELETE', '')
|
||||
|
||||
def test_http_manual_redirect_error_without_location(self):
|
||||
mock_conn = http.requests.request(
|
||||
'DELETE', 'http://example.com:8004/foo',
|
||||
allow_redirects=False,
|
||||
headers={'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'User-Agent': 'python-heatclient'})
|
||||
mock_conn.AndReturn(
|
||||
fakes.FakeHTTPResponse(
|
||||
302, 'Found',
|
||||
{},
|
||||
''))
|
||||
self.m.ReplayAll()
|
||||
client = http.HTTPClient('http://example.com:8004/foo')
|
||||
self.assertRaises(exc.InvalidEndpoint,
|
||||
client.json_request, 'DELETE', '')
|
||||
|
||||
def test_http_json_request_redirect(self):
|
||||
# Record the 302
|
||||
mock_conn = http.requests.request(
|
||||
'GET', 'http://example.com:8004',
|
||||
allow_redirects=False,
|
||||
headers={'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'User-Agent': 'python-heatclient'})
|
||||
mock_conn.AndReturn(
|
||||
fakes.FakeHTTPResponse(
|
||||
302, 'Found',
|
||||
{'location': 'http://example.com:8004'},
|
||||
''))
|
||||
# Record the following 200
|
||||
mock_conn = http.requests.request(
|
||||
'GET', 'http://example.com:8004',
|
||||
allow_redirects=False,
|
||||
headers={'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'User-Agent': 'python-heatclient'})
|
||||
mock_conn.AndReturn(
|
||||
fakes.FakeHTTPResponse(
|
||||
200, 'OK',
|
||||
{'content-type': 'application/json'},
|
||||
'{}'))
|
||||
# Replay, create client, assert
|
||||
self.m.ReplayAll()
|
||||
client = http.HTTPClient('http://example.com:8004')
|
||||
resp, body = client.json_request('GET', '')
|
||||
self.assertEqual(200, resp.status_code)
|
||||
self.assertEqual({}, body)
|
||||
|
||||
def test_http_404_json_request(self):
|
||||
# Record a 404
|
||||
mock_conn = http.requests.request(
|
||||
'GET', 'http://example.com:8004',
|
||||
allow_redirects=False,
|
||||
headers={'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'User-Agent': 'python-heatclient'})
|
||||
mock_conn.AndReturn(
|
||||
fakes.FakeHTTPResponse(
|
||||
404, 'OK', {'content-type': 'application/json'},
|
||||
'{}'))
|
||||
# Replay, create client, assert
|
||||
self.m.ReplayAll()
|
||||
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))
|
||||
|
||||
def test_http_300_json_request(self):
|
||||
# Record a 300
|
||||
mock_conn = http.requests.request(
|
||||
'GET', 'http://example.com:8004',
|
||||
allow_redirects=False,
|
||||
headers={'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'User-Agent': 'python-heatclient'})
|
||||
mock_conn.AndReturn(
|
||||
fakes.FakeHTTPResponse(
|
||||
300, 'OK', {'content-type': 'application/json'},
|
||||
'{}'))
|
||||
# Replay, create client, assert
|
||||
self.m.ReplayAll()
|
||||
client = http.HTTPClient('http://example.com:8004')
|
||||
e = self.assertRaises(
|
||||
exc.HTTPMultipleChoices, client.json_request, 'GET', '')
|
||||
# Assert that the raised exception can be converted to string
|
||||
self.assertIsNotNone(str(e))
|
||||
|
||||
def test_fake_json_request(self):
|
||||
headers = {'User-Agent': 'python-heatclient'}
|
||||
mock_conn = http.requests.request('GET', 'fake://example.com:8004/',
|
||||
allow_redirects=False,
|
||||
headers=headers)
|
||||
mock_conn.AndRaise(socket.gaierror)
|
||||
self.m.ReplayAll()
|
||||
|
||||
client = http.HTTPClient('fake://example.com:8004')
|
||||
self.assertRaises(exc.InvalidEndpoint,
|
||||
client._http_request, "/", "GET")
|
||||
|
||||
def test_debug_curl_command(self):
|
||||
self.m.StubOutWithMock(logging.Logger, '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 = logging.Logger.debug(
|
||||
"curl -g -i -X GET -H 'key: value' --key TEST_KEY "
|
||||
"--cert TEST_CERT --cacert TEST_CA "
|
||||
"-k -d 'text' http://foo/bar"
|
||||
)
|
||||
mock_logging_debug.AndReturn(None)
|
||||
self.m.ReplayAll()
|
||||
|
||||
client = http.HTTPClient('http://foo')
|
||||
client.ssl_connection_params = ssl_connection_params
|
||||
client.log_curl_request('GET', '/bar', {'headers': headers,
|
||||
'data': 'text'})
|
||||
|
||||
def test_http_request_socket_error(self):
|
||||
headers = {'User-Agent': 'python-heatclient'}
|
||||
mock_conn = http.requests.request('GET', 'http://example.com:8004/',
|
||||
allow_redirects=False,
|
||||
headers=headers)
|
||||
mock_conn.AndRaise(socket.error)
|
||||
self.m.ReplayAll()
|
||||
|
||||
client = http.HTTPClient('http://example.com:8004')
|
||||
self.assertRaises(exc.CommunicationError,
|
||||
client._http_request, "/", "GET")
|
||||
|
||||
def test_http_request_socket_timeout(self):
|
||||
headers = {'User-Agent': 'python-heatclient'}
|
||||
mock_conn = http.requests.request('GET', 'http://example.com:8004/',
|
||||
allow_redirects=False,
|
||||
headers=headers)
|
||||
mock_conn.AndRaise(socket.timeout)
|
||||
self.m.ReplayAll()
|
||||
|
||||
client = http.HTTPClient('http://example.com:8004')
|
||||
self.assertRaises(exc.CommunicationError,
|
||||
client._http_request, "/", "GET")
|
||||
|
||||
def test_http_request_specify_timeout(self):
|
||||
mock_conn = http.requests.request(
|
||||
'GET', 'http://example.com:8004',
|
||||
allow_redirects=False,
|
||||
headers={'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'User-Agent': 'python-heatclient'},
|
||||
timeout=float(123))
|
||||
mock_conn.AndReturn(
|
||||
fakes.FakeHTTPResponse(
|
||||
200, 'OK',
|
||||
{'content-type': 'application/json'},
|
||||
'{}'))
|
||||
# Replay, create client, assert
|
||||
self.m.ReplayAll()
|
||||
client = http.HTTPClient('http://example.com:8004', timeout='123')
|
||||
resp, body = client.json_request('GET', '')
|
||||
self.assertEqual(200, resp.status_code)
|
||||
self.assertEqual({}, body)
|
||||
|
||||
def test_get_system_ca_file(self):
|
||||
chosen = '/etc/ssl/certs/ca-certificates.crt'
|
||||
self.m.StubOutWithMock(os.path, 'exists')
|
||||
os.path.exists(chosen).AndReturn(chosen)
|
||||
self.m.ReplayAll()
|
||||
|
||||
ca = http.get_system_ca_file()
|
||||
self.assertEqual(chosen, ca)
|
||||
|
||||
def test_insecure_verify_cert_None(self):
|
||||
client = http.HTTPClient('https://foo', insecure=True)
|
||||
self.assertFalse(client.verify_cert)
|
||||
|
||||
def test_passed_cert_to_verify_cert(self):
|
||||
client = http.HTTPClient('https://foo', ca_file="NOWHERE")
|
||||
self.assertEqual("NOWHERE", client.verify_cert)
|
||||
|
||||
self.m.StubOutWithMock(http, 'get_system_ca_file')
|
||||
http.get_system_ca_file().AndReturn("SOMEWHERE")
|
||||
self.m.ReplayAll()
|
||||
client = http.HTTPClient('https://foo')
|
||||
self.assertEqual("SOMEWHERE", client.verify_cert)
|
||||
|
||||
def test_curl_log_i18n_headers(self):
|
||||
self.m.StubOutWithMock(logging.Logger, 'debug')
|
||||
kwargs = {'headers': {'Key': b'foo\xe3\x8a\x8e'}}
|
||||
|
||||
mock_logging_debug = logging.Logger.debug(
|
||||
u"curl -g -i -X GET -H 'Key: foo㊎' http://somewhere"
|
||||
)
|
||||
mock_logging_debug.AndReturn(None)
|
||||
|
||||
self.m.ReplayAll()
|
||||
|
||||
client = http.HTTPClient('http://somewhere')
|
||||
client.log_curl_request("GET", '', kwargs=kwargs)
|
||||
|
||||
|
||||
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_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/',
|
||||
'json': 'some_data',
|
||||
'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,326 +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 heatclient.common import deployment_utils
|
||||
from heatclient import exc
|
||||
from testtools import matchers
|
||||
|
||||
|
||||
load_tests = testscenarios.load_tests_apply_scenarios
|
||||
|
||||
|
||||
def mock_sc(group=None, config=None, options=None,
|
||||
inputs=None, outputs=None):
|
||||
return {
|
||||
'group': group,
|
||||
'config': config,
|
||||
'options': options or {},
|
||||
'inputs': inputs or [],
|
||||
'outputs': outputs or [],
|
||||
}
|
||||
|
||||
|
||||
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': []})),
|
||||
('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,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.
|
||||
|
||||
from heatclient.common import environment_format
|
||||
|
||||
import mock
|
||||
import testscenarios
|
||||
import testtools
|
||||
import yaml
|
||||
|
||||
|
||||
load_tests = testscenarios.load_tests_apply_scenarios
|
||||
|
||||
|
||||
class YamlEnvironmentTest(testtools.TestCase):
|
||||
|
||||
def test_minimal_yaml(self):
|
||||
yaml1 = ''
|
||||
yaml2 = '''
|
||||
parameter_defaults: {}
|
||||
parameters: {}
|
||||
resource_registry: {}
|
||||
'''
|
||||
tpl1 = environment_format.parse(yaml1)
|
||||
environment_format.default_for_missing(tpl1)
|
||||
tpl2 = environment_format.parse(yaml2)
|
||||
self.assertEqual(tpl2, tpl1)
|
||||
|
||||
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)
|
|
@ -1,130 +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 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):
|
||||
ev_info = {"links": [{"href": "http://heat/foo", "rel": "self"}],
|
||||
"logical_resource_id": resource_id,
|
||||
"physical_resource_id": resource_id,
|
||||
"resource_status": "CREATE_COMPLETE",
|
||||
"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)
|
|
@ -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.
|
||||
|
||||
from heatclient.common import utils
|
||||
from heatclient.v1 import events
|
||||
|
||||
import mock
|
||||
from mox3 import mox
|
||||
import testtools
|
||||
|
||||
|
||||
class EventManagerTest(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(EventManagerTest, self).setUp()
|
||||
self.m = mox.Mox()
|
||||
self.addCleanup(self.m.VerifyAll)
|
||||
self.addCleanup(self.m.UnsetStubs)
|
||||
|
||||
def test_list_event(self):
|
||||
stack_id = 'teststack',
|
||||
resource_name = 'testresource'
|
||||
manager = events.EventManager(None)
|
||||
self.m.StubOutWithMock(manager, '_resolve_stack_id')
|
||||
manager._resolve_stack_id(stack_id).AndReturn('teststack/abcd1234')
|
||||
self.m.ReplayAll()
|
||||
manager._list = mock.MagicMock()
|
||||
manager.list(stack_id, resource_name)
|
||||
# Make sure url is correct.
|
||||
manager._list.assert_called_once_with('/stacks/teststack%2Fabcd1234/'
|
||||
'resources/testresource/events',
|
||||
"events")
|
||||
|
||||
def test_list_event_with_unicode_resource_name(self):
|
||||
stack_id = 'teststack',
|
||||
resource_name = u'\u5de5\u4f5c'
|
||||
manager = events.EventManager(None)
|
||||
self.m.StubOutWithMock(manager, '_resolve_stack_id')
|
||||
manager._resolve_stack_id(stack_id).AndReturn('teststack/abcd1234')
|
||||
self.m.ReplayAll()
|
||||
manager._list = mock.MagicMock()
|
||||
manager.list(stack_id, resource_name)
|
||||
# Make sure url is correct.
|
||||
manager._list.assert_called_once_with('/stacks/teststack%2Fabcd1234/'
|
||||
'resources/%E5%B7%A5%E4%BD%9C/'
|
||||
'events', "events")
|
||||
|
||||
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)
|
||||
self.m.StubOutWithMock(manager, '_resolve_stack_id')
|
||||
manager._resolve_stack_id(stack_id).AndReturn('teststack/abcd1234')
|
||||
self.m.ReplayAll()
|
||||
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%2Fabcd1234/'
|
||||
'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)
|
||||
|
||||
def test_get_event(self):
|
||||
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%2Fabcd1234/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'):
|
||||
self.m.StubOutWithMock(manager, '_resolve_stack_id')
|
||||
self.m.StubOutWithMock(utils, 'get_response_body')
|
||||
utils.get_response_body(mox.IgnoreArg()).AndReturn({'event': []})
|
||||
manager._resolve_stack_id('teststack').AndReturn(
|
||||
'teststack/abcd1234')
|
||||
self.m.ReplayAll()
|
||||
manager.get(**fields)
|
||||
|
||||
def test_get_event_with_unicode_resource_name(self):
|
||||
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%2Fabcd1234/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'):
|
||||
self.m.StubOutWithMock(manager, '_resolve_stack_id')
|
||||
self.m.StubOutWithMock(utils, 'get_response_body')
|
||||
utils.get_response_body(mox.IgnoreArg()).AndReturn({'event': []})
|
||||
manager._resolve_stack_id('teststack').AndReturn(
|
||||
'teststack/abcd1234')
|
||||
self.m.ReplayAll()
|
||||
manager.get(**fields)
|
|
@ -1,91 +0,0 @@
|
|||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
import 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()
|
||||
|
||||
@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,210 +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.
|
||||
from heatclient.common import utils
|
||||
|
||||
from heatclient.v1 import resources
|
||||
from six.moves.urllib import parse
|
||||
|
||||
from mox3 import mox
|
||||
import testtools
|
||||
|
||||
|
||||
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%2Fabcd1234/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%2Fabcd1234/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%2Fabcd1234/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_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%2Fabcd1234/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%2Fabcd1234/resources'
|
||||
'/testresource/signal')
|
||||
key = 'signal'
|
||||
|
||||
manager = self._base_test(expect, key)
|
||||
manager.signal(**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,59 +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.
|
||||
|
||||
from heatclient import exc
|
||||
import testtools
|
||||
|
||||
|
||||
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,99 +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())
|
||||
|
||||
@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)
|
|
@ -1,316 +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.v1 import stacks
|
||||
|
||||
import mock
|
||||
import testscenarios
|
||||
from testscenarios import scenarios as scnrs
|
||||
import testtools
|
||||
|
||||
load_tests = testscenarios.load_tests_apply_scenarios
|
||||
|
||||
|
||||
def mock_stack(manager, stack_name, stack_id):
|
||||
return stacks.Stack(manager, {
|
||||
"id": stack_id,
|
||||
"stack_name": stack_name,
|
||||
"links": [{
|
||||
"href": "http://192.0.2.1:8004/v1/1234/stacks/%s/%s" % (
|
||||
stack_name, stack_id),
|
||||
"rel": "self"}],
|
||||
"description": "No description",
|
||||
"stack_status_reason": "Stack create completed successfully",
|
||||
"creation_time": "2013-08-04T20:57:55Z",
|
||||
"updated_time": "2013-08-04T20:57:55Z",
|
||||
"stack_status": "CREATE_COMPLETE"
|
||||
})
|
||||
|
||||
|
||||
class StackStatusActionTest(testtools.TestCase):
|
||||
|
||||
scenarios = scnrs.multiply_scenarios([
|
||||
('CREATE', dict(action='CREATE')),
|
||||
('DELETE', dict(action='DELETE')),
|
||||
('UPDATE', dict(action='UPDATE')),
|
||||
('ROLLBACK', dict(action='ROLLBACK')),
|
||||
('SUSPEND', dict(action='SUSPEND')),
|
||||
('RESUME', dict(action='RESUME')),
|
||||
('CHECK', dict(action='CHECK'))
|
||||
], [
|
||||
('IN_PROGRESS', dict(status='IN_PROGRESS')),
|
||||
('FAILED', dict(status='FAILED')),
|
||||
('COMPLETE', dict(status='COMPLETE'))
|
||||
])
|
||||
|
||||
def test_status_action(self):
|
||||
stack_status = '%s_%s' % (self.action, self.status)
|
||||
stack = mock_stack(None, 'stack_1', 'abcd1234')
|
||||
stack.stack_status = stack_status
|
||||
self.assertEqual(self.action, stack.action)
|
||||
self.assertEqual(self.status, stack.status)
|
||||
|
||||
|
||||
class StackIdentifierTest(testtools.TestCase):
|
||||
|
||||
def test_stack_identifier(self):
|
||||
stack = mock_stack(None, 'the_stack', 'abcd1234')
|
||||
self.assertEqual('the_stack/abcd1234', stack.identifier)
|
||||
|
||||
|
||||
class StackOperationsTest(testtools.TestCase):
|
||||
|
||||
def test_delete_stack(self):
|
||||
manager = mock.MagicMock()
|
||||
stack = mock_stack(manager, 'the_stack', 'abcd1234')
|
||||
stack.delete()
|
||||
manager.delete.assert_called_once_with('the_stack/abcd1234')
|
||||
|
||||
def test_abandon_stack(self):
|
||||
manager = mock.MagicMock()
|
||||
stack = mock_stack(manager, 'the_stack', 'abcd1234')
|
||||
stack.abandon()
|
||||
manager.abandon.assert_called_once_with('the_stack/abcd1234')
|
||||
|
||||
def test_get_stack(self):
|
||||
manager = mock.MagicMock()
|
||||
stack = mock_stack(manager, 'the_stack', 'abcd1234')
|
||||
stack.get()
|
||||
manager.get.assert_called_once_with('the_stack/abcd1234')
|
||||
|
||||
def test_update_stack(self):
|
||||
manager = mock.MagicMock()
|
||||
stack = mock_stack(manager, 'the_stack', 'abcd1234')
|
||||
stack.update()
|
||||
manager.update.assert_called_once_with('the_stack/abcd1234')
|
||||
|
||||
def test_create_stack(self):
|
||||
manager = mock.MagicMock()
|
||||
stack = mock_stack(manager, 'the_stack', 'abcd1234')
|
||||
stack = stack.create()
|
||||
manager.create.assert_called_once_with('the_stack/abcd1234')
|
||||
|
||||
def test_preview_stack(self):
|
||||
manager = mock.MagicMock()
|
||||
stack = mock_stack(manager, 'the_stack', 'abcd1234')
|
||||
stack = stack.preview()
|
||||
manager.preview.assert_called_once_with()
|
||||
|
||||
def test_snapshot(self):
|
||||
manager = mock.MagicMock()
|
||||
stack = mock_stack(manager, 'the_stack', 'abcd1234')
|
||||
stack.snapshot('foo')
|
||||
manager.snapshot.assert_called_once_with('the_stack/abcd1234', 'foo')
|
||||
|
||||
def test_snapshot_show(self):
|
||||
manager = mock.MagicMock()
|
||||
stack = mock_stack(manager, 'the_stack', 'abcd1234')
|
||||
stack.snapshot_show('snap1234')
|
||||
manager.snapshot_show.assert_called_once_with(
|
||||
'the_stack/abcd1234', 'snap1234')
|
||||
|
||||
def test_snapshot_delete(self):
|
||||
manager = mock.MagicMock()
|
||||
stack = mock_stack(manager, 'the_stack', 'abcd1234')
|
||||
stack.snapshot_delete('snap1234')
|
||||
manager.snapshot_delete.assert_called_once_with(
|
||||
'the_stack/abcd1234', 'snap1234')
|
||||
|
||||
def test_restore(self):
|
||||
manager = mock.MagicMock()
|
||||
stack = mock_stack(manager, 'the_stack', 'abcd1234')
|
||||
stack.restore('snap1234')
|
||||
manager.restore.assert_called_once_with(
|
||||
'the_stack/abcd1234', 'snap1234')
|
||||
|
||||
def test_snapshot_list(self):
|
||||
manager = mock.MagicMock()
|
||||
stack = mock_stack(manager, 'the_stack', 'abcd1234')
|
||||
stack.snapshot_list()
|
||||
manager.snapshot_list.assert_called_once_with('the_stack/abcd1234')
|
||||
|
||||
|
||||
class StackManagerNoPaginationTest(testtools.TestCase):
|
||||
|
||||
scenarios = [
|
||||
('total_0', dict(total=0)),
|
||||
('total_1', dict(total=1)),
|
||||
('total_9', dict(total=9)),
|
||||
('total_10', dict(total=10)),
|
||||
('total_11', dict(total=11)),
|
||||
('total_19', dict(total=19)),
|
||||
('total_20', dict(total=20)),
|
||||
('total_21', dict(total=21)),
|
||||
('total_49', dict(total=49)),
|
||||
('total_50', dict(total=50)),
|
||||
('total_51', dict(total=51)),
|
||||
('total_95', dict(total=95)),
|
||||
]
|
||||
|
||||
# absolute limit for results returned
|
||||
limit = 50
|
||||
|
||||
def mock_manager(self):
|
||||
manager = stacks.StackManager(None)
|
||||
manager._list = mock.MagicMock()
|
||||
|
||||
def mock_list(*args, **kwargs):
|
||||
def results():
|
||||
for i in range(0, self.total):
|
||||
stack_name = 'stack_%s' % (i + 1)
|
||||
stack_id = 'abcd1234-%s' % (i + 1)
|
||||
yield mock_stack(manager, stack_name, stack_id)
|
||||
|
||||
return list(results())
|
||||
|
||||
manager._list.side_effect = mock_list
|
||||
return manager
|
||||
|
||||
def test_stack_list_no_pagination(self):
|
||||
manager = self.mock_manager()
|
||||
|
||||
results = list(manager.list())
|
||||
manager._list.assert_called_once_with(
|
||||
'/stacks?', 'stacks')
|
||||
|
||||
# paginate is not specified, so the total
|
||||
# results is always returned
|
||||
self.assertEqual(self.total, len(results))
|
||||
|
||||
if self.total > 0:
|
||||
self.assertEqual('stack_1', results[0].stack_name)
|
||||
self.assertEqual('stack_%s' % self.total, results[-1].stack_name)
|
||||
|
||||
|
||||
class StackManagerPaginationTest(testtools.TestCase):
|
||||
|
||||
scenarios = [
|
||||
('0_offset_0', dict(
|
||||
offset=0,
|
||||
total=0,
|
||||
results=((0, 0),)
|
||||
)),
|
||||
('1_offset_0', dict(
|
||||
offset=0,
|
||||
total=1,
|
||||
results=((0, 1),)
|
||||
)),
|
||||
('9_offset_0', dict(
|
||||
offset=0,
|
||||
total=9,
|
||||
results=((0, 9),)
|
||||
)),
|
||||
('10_offset_0', dict(
|
||||
offset=0,
|
||||
total=10,
|
||||
results=((0, 10), (10, 10))
|
||||
)),
|
||||
('11_offset_0', dict(
|
||||
offset=0,
|
||||
total=11,
|
||||
results=((0, 10), (10, 11))
|
||||
)),
|
||||
('11_offset_10', dict(
|
||||
offset=10,
|
||||
total=11,
|
||||
results=((10, 11),)
|
||||
)),
|
||||
('19_offset_10', dict(
|
||||
offset=10,
|
||||
total=19,
|
||||
results=((10, 19),)
|
||||
)),
|
||||
('20_offset_10', dict(
|
||||
offset=10,
|
||||
total=20,
|
||||
results=((10, 20), (20, 20))
|
||||
)),
|
||||
('21_offset_10', dict(
|
||||
offset=10,
|
||||
total=21,
|
||||
results=((10, 20), (20, 21))
|
||||
)),
|
||||
('21_offset_0', dict(
|
||||
offset=0,
|
||||
total=21,
|
||||
results=((0, 10), (10, 20), (20, 21))
|
||||
)),
|
||||
('21_offset_20', dict(
|
||||
offset=20,
|
||||
total=21,
|
||||
results=((20, 21),)
|
||||
)),
|
||||
('95_offset_90', dict(
|
||||
offset=90,
|
||||
total=95,
|
||||
results=((90, 95),)
|
||||
)),
|
||||
]
|
||||
|
||||
# absolute limit for results returned
|
||||
limit = 50
|
||||
|
||||
def mock_manager(self):
|
||||
manager = stacks.StackManager(None)
|
||||
manager._list = mock.MagicMock()
|
||||
|
||||
def mock_list(arg_url, arg_response_key):
|
||||
try:
|
||||
result = self.results[self.result_index]
|
||||
except IndexError:
|
||||
return []
|
||||
self.result_index = self.result_index + 1
|
||||
|
||||
limit_string = 'limit=%s' % self.limit
|
||||
self.assertIn(limit_string, arg_url)
|
||||
|
||||
offset = result[0]
|
||||
if offset > 0:
|
||||
offset_string = 'marker=abcd1234-%s' % offset
|
||||
self.assertIn(offset_string, arg_url)
|
||||
|
||||
def results():
|
||||
|
||||
for i in range(*result):
|
||||
self.limit -= 1
|
||||
stack_name = 'stack_%s' % (i + 1)
|
||||
stack_id = 'abcd1234-%s' % (i + 1)
|
||||
yield mock_stack(manager, stack_name, stack_id)
|
||||
|
||||
return list(results())
|
||||
|
||||
manager._list.side_effect = mock_list
|
||||
return manager
|
||||
|
||||
def test_stack_list_pagination(self):
|
||||
manager = self.mock_manager()
|
||||
|
||||
list_params = {'limit': self.limit}
|
||||
|
||||
if self.offset > 0:
|
||||
marker = 'abcd1234-%s' % self.offset
|
||||
list_params['marker'] = marker
|
||||
|
||||
self.result_index = 0
|
||||
results = list(manager.list(**list_params))
|
||||
|
||||
# assert that the list method has been called enough times
|
||||
self.assertEqual(len(self.results), self.result_index)
|
||||
|
||||
last_result = min(self.limit, self.total - self.offset)
|
||||
# one or more list calls have been recomposed into a single list
|
||||
self.assertEqual(last_result, len(results))
|
||||
|
||||
if last_result > 0:
|
||||
self.assertEqual('stack_%s' % (self.offset + 1),
|
||||
results[0].stack_name)
|
||||
self.assertEqual('stack_%s' % (self.offset + last_result),
|
||||
results[-1].stack_name)
|
|
@ -1,50 +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 testscenarios
|
||||
import testtools
|
||||
import yaml
|
||||
|
||||
from heatclient.common import template_format
|
||||
|
||||
|
||||
load_tests = testscenarios.load_tests_apply_scenarios
|
||||
|
||||
|
||||
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,
|
||||
template_format.parse, text)
|
||||
|
||||
def test_parse_no_version_format(self):
|
||||
yaml = ''
|
||||
self.assertRaises(ValueError, template_format.parse, yaml)
|
||||
yaml2 = '''Parameters: {}
|
||||
Mappings: {}
|
||||
Resources: {}
|
||||
Outputs: {}
|
||||
'''
|
||||
self.assertRaises(ValueError, template_format.parse, yaml2)
|
File diff suppressed because it is too large
Load Diff
|
@ -1,57 +0,0 @@
|
|||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import testtools
|
||||
|
||||
from heatclient.v1 import template_versions
|
||||
|
||||
|
||||
class TemplateVersionManagerTest(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TemplateVersionManagerTest, self).setUp()
|
||||
|
||||
def test_list_versions(self):
|
||||
expect = ('GET', '/template_versions')
|
||||
|
||||
class FakeResponse(object):
|
||||
def json(self):
|
||||
return {'template_versions': [{'version': '2013-05-23',
|
||||
'type': 'hot'}]}
|
||||
|
||||
class FakeClient(object):
|
||||
def get(self, *args, **kwargs):
|
||||
assert ('GET', args[0]) == expect
|
||||
return FakeResponse()
|
||||
|
||||
manager = template_versions.TemplateVersionManager(FakeClient())
|
||||
versions = manager.list()
|
||||
self.assertEqual('2013-05-23', getattr(versions[0], 'version'))
|
||||
self.assertEqual('hot', getattr(versions[0], 'type'))
|
||||
|
||||
def test_get(self):
|
||||
expect = ('GET', '/template_versions/heat_template_version.2015-04-30'
|
||||
'/functions')
|
||||
|
||||
class FakeResponse(object):
|
||||
def json(self):
|
||||
return {'template_functions': [{'function': 'get_attr'}]}
|
||||
|
||||
class FakeClient(object):
|
||||
def get(self, *args, **kwargs):
|
||||
assert ('GET', args[0]) == expect
|
||||
return FakeResponse()
|
||||
|
||||
manager = template_versions.TemplateVersionManager(FakeClient())
|
||||
functions = manager.get('heat_template_version.2015-04-30')
|
||||
self.assertEqual('get_attr', getattr(functions[0], 'function'))
|
|
@ -1,278 +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.
|
||||
from heatclient.common import utils
|
||||
from heatclient import exc
|
||||
from heatclient.v1 import resources as hc_res
|
||||
import mock
|
||||
import os
|
||||
import testtools
|
||||
|
||||
|
||||
class ShellTest(testtools.TestCase):
|
||||
|
||||
def test_format_parameter_none(self):
|
||||
self.assertEqual({}, utils.format_parameters(None))
|
||||
|
||||
def test_format_parameters(self):
|
||||
p = utils.format_parameters([
|
||||
'InstanceType=m1.large;DBUsername=wp;'
|
||||
'DBPassword=verybadpassword;KeyName=heat_key;'
|
||||
'LinuxDistribution=F17'])
|
||||
self.assertEqual({'InstanceType': 'm1.large',
|
||||
'DBUsername': 'wp',
|
||||
'DBPassword': 'verybadpassword',
|
||||
'KeyName': 'heat_key',
|
||||
'LinuxDistribution': 'F17'
|
||||
}, p)
|
||||
|
||||
def test_format_parameters_split(self):
|
||||
p = utils.format_parameters([
|
||||
'KeyName=heat_key;'
|
||||
'DnsSecKey=hsgx1m31PbamNF4WEcHlwjIlCGgifOdoB58/wwC7a4oAONQ/fDV5ct'
|
||||
'qrYBoLlKHhTfkyQEw9iVScKYZbbMtMNg==;'
|
||||
'UpstreamDNS=8.8.8.8'])
|
||||
self.assertEqual({'KeyName': 'heat_key',
|
||||
'DnsSecKey': 'hsgx1m31PbamNF4WEcHlwjIlCGgifOdoB58/ww'
|
||||
'C7a4oAONQ/fDV5ctqrYBoLlKHhTfkyQEw9iVScKYZbbMtMNg==',
|
||||
'UpstreamDNS': '8.8.8.8'}, p)
|
||||
|
||||
def test_format_parameters_multiple(self):
|
||||
p = utils.format_parameters([
|
||||
'KeyName=heat_key',
|
||||
'DnsSecKey=hsgx1m31PbamNF4WEcHlwjIlCGgifOdoB58/wwC7a4oAONQ/fDV5ct'
|
||||
'qrYBoLlKHhTfkyQEw9iVScKYZbbMtMNg==',
|
||||
'UpstreamDNS=8.8.8.8'])
|
||||
self.assertEqual({'KeyName': 'heat_key',
|
||||
'DnsSecKey': 'hsgx1m31PbamNF4WEcHlwjIlCGgifOdoB58/ww'
|
||||
'C7a4oAONQ/fDV5ctqrYBoLlKHhTfkyQEw9iVScKYZbbMtMNg==',
|
||||
'UpstreamDNS': '8.8.8.8'}, p)
|
||||
|
||||
def test_format_parameters_multiple_semicolon_values(self):
|
||||
p = utils.format_parameters([
|
||||
'KeyName=heat_key',
|
||||
'DnsSecKey=hsgx1m31;PbaNF4WEcHlwj;IlCGgfOdoB;58/ww7a4oAO;NQ/fD==',
|
||||
'UpstreamDNS=8.8.8.8'])
|
||||
self.assertEqual({'KeyName': 'heat_key',
|
||||
'DnsSecKey': 'hsgx1m31;PbaNF4WEcHlwj;IlCGgfOdoB;58/'
|
||||
'ww7a4oAO;NQ/fD==',
|
||||
'UpstreamDNS': '8.8.8.8'}, p)
|
||||
|
||||
def test_format_parameters_parse_semicolon_false(self):
|
||||
p = utils.format_parameters(
|
||||
['KeyName=heat_key;UpstreamDNS=8.8.8.8;a=b'],
|
||||
parse_semicolon=False)
|
||||
self.assertEqual({'KeyName': 'heat_key;UpstreamDNS=8.8.8.8;a=b'}, p)
|
||||
|
||||
def test_format_parameters_multiple_values_per_pamaters(self):
|
||||
p = utils.format_parameters([
|
||||
'status=COMPLETE',
|
||||
'status=FAILED'])
|
||||
self.assertIn('status', p)
|
||||
self.assertIn('COMPLETE', p['status'])
|
||||
self.assertIn('FAILED', p['status'])
|
||||
|
||||
def test_format_parameter_bad_parameter(self):
|
||||
params = ['KeyName=heat_key;UpstreamDNS8.8.8.8']
|
||||
ex = self.assertRaises(exc.CommandError,
|
||||
utils.format_parameters, params)
|
||||
self.assertEqual('Malformed parameter(UpstreamDNS8.8.8.8). '
|
||||
'Use the key=value format.', str(ex))
|
||||
|
||||
def test_format_multiple_bad_parameter(self):
|
||||
params = ['KeyName=heat_key', 'UpstreamDNS8.8.8.8']
|
||||
ex = self.assertRaises(exc.CommandError,
|
||||
utils.format_parameters, params)
|
||||
self.assertEqual('Malformed parameter(UpstreamDNS8.8.8.8). '
|
||||
'Use the key=value format.', str(ex))
|
||||
|
||||
def test_link_formatter(self):
|
||||
self.assertEqual('', utils.link_formatter(None))
|
||||
self.assertEqual('', utils.link_formatter([]))
|
||||
self.assertEqual(
|
||||
'http://foo.example.com\nhttp://bar.example.com',
|
||||
utils.link_formatter([
|
||||
{'href': 'http://foo.example.com'},
|
||||
{'href': 'http://bar.example.com'}]))
|
||||
self.assertEqual(
|
||||
'http://foo.example.com (a)\nhttp://bar.example.com (b)',
|
||||
utils.link_formatter([
|
||||
{'href': 'http://foo.example.com', 'rel': 'a'},
|
||||
{'href': 'http://bar.example.com', 'rel': 'b'}]))
|
||||
self.assertEqual(
|
||||
'\n',
|
||||
utils.link_formatter([
|
||||
{'hrf': 'http://foo.example.com'},
|
||||
{}]))
|
||||
|
||||
def test_resource_nested_identifier(self):
|
||||
rsrc_info = {'resource_name': 'aresource',
|
||||
'links': [{'href': u'http://foo/name/id/resources/0',
|
||||
'rel': u'self'},
|
||||
{'href': u'http://foo/name/id',
|
||||
'rel': u'stack'},
|
||||
{'href': u'http://foo/n_name/n_id',
|
||||
'rel': u'nested'}]}
|
||||
rsrc = hc_res.Resource(manager=None, info=rsrc_info)
|
||||
self.assertEqual('n_name/n_id', utils.resource_nested_identifier(rsrc))
|
||||
|
||||
def test_resource_nested_identifier_none(self):
|
||||
rsrc_info = {'resource_name': 'aresource',
|
||||
'links': [{'href': u'http://foo/name/id/resources/0',
|
||||
'rel': u'self'},
|
||||
{'href': u'http://foo/name/id',
|
||||
'rel': u'stack'}]}
|
||||
rsrc = hc_res.Resource(manager=None, info=rsrc_info)
|
||||
self.assertIsNone(utils.resource_nested_identifier(rsrc))
|
||||
|
||||
def test_json_formatter(self):
|
||||
self.assertEqual('null', utils.json_formatter(None))
|
||||
self.assertEqual('{}', utils.json_formatter({}))
|
||||
self.assertEqual('{\n "foo": "bar"\n}',
|
||||
utils.json_formatter({"foo": "bar"}))
|
||||
self.assertEqual(u'{\n "Uni": "test\u2665"\n}',
|
||||
utils.json_formatter({"Uni": u"test\u2665"}))
|
||||
|
||||
def test_text_wrap_formatter(self):
|
||||
self.assertEqual('', utils.text_wrap_formatter(None))
|
||||
self.assertEqual('', utils.text_wrap_formatter(''))
|
||||
self.assertEqual('one two three',
|
||||
utils.text_wrap_formatter('one two three'))
|
||||
self.assertEqual(
|
||||
'one two three four five six seven eight nine ten eleven\ntwelve',
|
||||
utils.text_wrap_formatter(
|
||||
('one two three four five six seven '
|
||||
'eight nine ten eleven twelve')))
|
||||
|
||||
def test_newline_list_formatter(self):
|
||||
self.assertEqual('', utils.newline_list_formatter(None))
|
||||
self.assertEqual('', utils.newline_list_formatter([]))
|
||||
self.assertEqual('one\ntwo',
|
||||
utils.newline_list_formatter(['one', 'two']))
|
||||
|
||||
|
||||
class ShellTestParameterFiles(testtools.TestCase):
|
||||
|
||||
def test_format_parameter_file_none(self):
|
||||
self.assertEqual({}, utils.format_parameter_file(None))
|
||||
|
||||
def test_format_parameter_file(self):
|
||||
tmpl_file = '/opt/stack/template.yaml'
|
||||
contents = 'DBUsername=wp\nDBPassword=verybadpassword'
|
||||
utils.read_url_content = mock.MagicMock()
|
||||
utils.read_url_content.return_value = 'DBUsername=wp\n' \
|
||||
'DBPassword=verybadpassword'
|
||||
|
||||
p = utils.format_parameter_file([
|
||||
'env_file1=test_file1'], tmpl_file)
|
||||
self.assertEqual({'env_file1': contents
|
||||
}, p)
|
||||
|
||||
def test_format_parameter_file_no_template(self):
|
||||
tmpl_file = None
|
||||
contents = 'DBUsername=wp\nDBPassword=verybadpassword'
|
||||
utils.read_url_content = mock.MagicMock()
|
||||
utils.read_url_content.return_value = 'DBUsername=wp\n' \
|
||||
'DBPassword=verybadpassword'
|
||||
p = utils.format_parameter_file([
|
||||
'env_file1=test_file1'], tmpl_file)
|
||||
self.assertEqual({'env_file1': contents
|
||||
}, p)
|
||||
|
||||
def test_format_all_parameters(self):
|
||||
tmpl_file = '/opt/stack/template.yaml'
|
||||
contents = 'DBUsername=wp\nDBPassword=verybadpassword'
|
||||
params = ['KeyName=heat_key;UpstreamDNS=8.8.8.8']
|
||||
utils.read_url_content = mock.MagicMock()
|
||||
utils.read_url_content.return_value = 'DBUsername=wp\n' \
|
||||
'DBPassword=verybadpassword'
|
||||
p = utils.format_all_parameters(params, [
|
||||
'env_file1=test_file1'], template_file=tmpl_file)
|
||||
self.assertEqual({'KeyName': 'heat_key',
|
||||
'UpstreamDNS': '8.8.8.8',
|
||||
'env_file1': contents}, p)
|
||||
|
||||
|
||||
class TestURLFunctions(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestURLFunctions, self).setUp()
|
||||
self.m = mock.MagicMock()
|
||||
|
||||
self.addCleanup(self.m.UnsetStubs)
|
||||
|
||||
def test_normalise_file_path_to_url_relative(self):
|
||||
self.assertEqual(
|
||||
'file://%s/foo' % os.getcwd(),
|
||||
utils.normalise_file_path_to_url(
|
||||
'foo'))
|
||||
|
||||
def test_normalise_file_path_to_url_absolute(self):
|
||||
self.assertEqual(
|
||||
'file:///tmp/foo',
|
||||
utils.normalise_file_path_to_url(
|
||||
'/tmp/foo'))
|
||||
|
||||
def test_normalise_file_path_to_url_file(self):
|
||||
self.assertEqual(
|
||||
'file:///tmp/foo',
|
||||
utils.normalise_file_path_to_url(
|
||||
'file:///tmp/foo'))
|
||||
|
||||
def test_normalise_file_path_to_url_http(self):
|
||||
self.assertEqual(
|
||||
'http://localhost/foo',
|
||||
utils.normalise_file_path_to_url(
|
||||
'http://localhost/foo'))
|
||||
|
||||
def test_get_template_url(self):
|
||||
tmpl_file = '/opt/stack/template.yaml'
|
||||
tmpl_url = 'file:///opt/stack/template.yaml'
|
||||
self.assertEqual(utils.get_template_url(tmpl_file, None),
|
||||
tmpl_url)
|
||||
self.assertEqual(utils.get_template_url(None, tmpl_url),
|
||||
tmpl_url)
|
||||
self.assertEqual(utils.get_template_url(None, None),
|
||||
None)
|
||||
|
||||
def test_base_url_for_url(self):
|
||||
self.assertEqual(
|
||||
'file:///foo/bar',
|
||||
utils.base_url_for_url(
|
||||
'file:///foo/bar/baz'))
|
||||
self.assertEqual(
|
||||
'file:///foo/bar',
|
||||
utils.base_url_for_url(
|
||||
'file:///foo/bar/baz.txt'))
|
||||
self.assertEqual(
|
||||
'file:///foo/bar',
|
||||
utils.base_url_for_url(
|
||||
'file:///foo/bar/'))
|
||||
self.assertEqual(
|
||||
'file:///',
|
||||
utils.base_url_for_url(
|
||||
'file:///'))
|
||||
self.assertEqual(
|
||||
'file:///',
|
||||
utils.base_url_for_url(
|
||||
'file:///foo'))
|
||||
|
||||
self.assertEqual(
|
||||
'http://foo/bar',
|
||||
utils.base_url_for_url(
|
||||
'http://foo/bar/'))
|
||||
self.assertEqual(
|
||||
'http://foo/bar',
|
||||
utils.base_url_for_url(
|
||||
'http://foo/bar/baz.template'))
|
|
@ -1,318 +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
|
||||
|
||||
import heatclient.v1.shell as shell
|
||||
|
||||
|
||||
class TestHooks(testtools.TestCase):
|
||||
def setUp(self):
|
||||
super(TestHooks, self).setUp()
|
||||
self.client = mock.Mock()
|
||||
nested_stack = mock.Mock()
|
||||
self.client.resources.get = mock.Mock(name='thingy',
|
||||
return_value=nested_stack)
|
||||
type(nested_stack).physical_resource_id = mock.PropertyMock(
|
||||
return_value='nested_id')
|
||||
self.args = mock.Mock()
|
||||
stack_name_p = mock.PropertyMock(return_value="mystack")
|
||||
type(self.args).name = stack_name_p
|
||||
type(self.args).id = stack_name_p
|
||||
shell.template_utils.get_template_contents = mock.Mock(
|
||||
return_value=({}, ""))
|
||||
shell.template_utils.process_multiple_environments_and_files = \
|
||||
mock.Mock(return_value=({}, {}))
|
||||
shell.utils.format_all_parameters = mock.Mock(return_value=[])
|
||||
shell.do_stack_list = mock.Mock()
|
||||
shell.logger = mock.Mock()
|
||||
type(self.args).clear_parameter = mock.PropertyMock(return_value=[])
|
||||
type(self.args).rollback = mock.PropertyMock(return_value=None)
|
||||
type(self.args).pre_create = mock.PropertyMock(return_value=False)
|
||||
type(self.args).pre_update = mock.PropertyMock(return_value=False)
|
||||
type(self.args).poll = mock.PropertyMock(return_value=None)
|
||||
|
||||
def test_create_hooks_in_args(self):
|
||||
type(self.args).pre_create = mock.PropertyMock(
|
||||
return_value=['bp', 'another_bp'])
|
||||
|
||||
shell.do_stack_create(self.client, self.args)
|
||||
self.assertEqual(1, self.client.stacks.create.call_count)
|
||||
expected_hooks = {
|
||||
'bp': {'hooks': 'pre-create'},
|
||||
'another_bp': {'hooks': 'pre-create'}
|
||||
}
|
||||
actual_hooks = self.client.stacks.create.call_args[1][
|
||||
'environment']['resource_registry']['resources']
|
||||
self.assertEqual(expected_hooks, actual_hooks)
|
||||
|
||||
def test_create_nested_hooks_in_args(self):
|
||||
type(self.args).pre_create = mock.PropertyMock(
|
||||
return_value=['nested/bp', 'super/nested/bp'])
|
||||
|
||||
shell.do_stack_create(self.client, self.args)
|
||||
self.assertEqual(1, self.client.stacks.create.call_count)
|
||||
expected_hooks = {
|
||||
'nested': {
|
||||
'bp': {'hooks': 'pre-create'},
|
||||
},
|
||||
'super': {
|
||||
'nested': {
|
||||
'bp': {'hooks': 'pre-create'},
|
||||
}
|
||||
}
|
||||
}
|
||||
actual_hooks = self.client.stacks.create.call_args[1][
|
||||
'environment']['resource_registry']['resources']
|
||||
self.assertEqual(expected_hooks, actual_hooks)
|
||||
|
||||
def test_create_hooks_in_env_and_args(self):
|
||||
type(self.args).pre_create = mock.PropertyMock(return_value=[
|
||||
'nested_a/bp',
|
||||
'bp_a',
|
||||
'another_bp_a',
|
||||
'super_a/nested/bp',
|
||||
])
|
||||
env = {
|
||||
'resource_registry': {
|
||||
'resources': {
|
||||
'bp_e': {'hooks': 'pre-create'},
|
||||
'another_bp_e': {'hooks': 'pre-create'},
|
||||
'nested_e': {
|
||||
'bp': {'hooks': 'pre-create'}
|
||||
},
|
||||
'super_e': {
|
||||
'nested': {
|
||||
'bp': {'hooks': 'pre-create'}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
shell.template_utils.process_multiple_environments_and_files = \
|
||||
mock.Mock(return_value=({}, env))
|
||||
|
||||
shell.do_stack_create(self.client, self.args)
|
||||
self.assertEqual(1, self.client.stacks.create.call_count)
|
||||
actual_hooks = self.client.stacks.create.call_args[1][
|
||||
'environment']['resource_registry']['resources']
|
||||
expected_hooks = {
|
||||
'bp_e': {'hooks': 'pre-create'},
|
||||
'another_bp_e': {'hooks': 'pre-create'},
|
||||
'nested_e': {
|
||||
'bp': {'hooks': 'pre-create'}
|
||||
},
|
||||
'super_e': {
|
||||
'nested': {
|
||||
'bp': {'hooks': 'pre-create'}
|
||||
}
|
||||
},
|
||||
'bp_a': {'hooks': 'pre-create'},
|
||||
'another_bp_a': {'hooks': 'pre-create'},
|
||||
'nested_a': {
|
||||
'bp': {'hooks': 'pre-create'}
|
||||
},
|
||||
'super_a': {
|
||||
'nested': {
|
||||
'bp': {'hooks': 'pre-create'}
|
||||
}
|
||||
},
|
||||
}
|
||||
self.assertEqual(expected_hooks, actual_hooks)
|
||||
|
||||
def test_update_hooks_in_args(self):
|
||||
type(self.args).pre_update = mock.PropertyMock(
|
||||
return_value=['bp', 'another_bp'])
|
||||
|
||||
shell.do_stack_update(self.client, self.args)
|
||||
self.assertEqual(1, self.client.stacks.update.call_count)
|
||||
expected_hooks = {
|
||||
'bp': {'hooks': 'pre-update'},
|
||||
'another_bp': {'hooks': 'pre-update'},
|
||||
}
|
||||
actual_hooks = self.client.stacks.update.call_args[1][
|
||||
'environment']['resource_registry']['resources']
|
||||
self.assertEqual(expected_hooks, actual_hooks)
|
||||
|
||||
def test_update_nested_hooks_in_args(self):
|
||||
type(self.args).pre_update = mock.PropertyMock(
|
||||
return_value=['nested/bp', 'super/nested/bp'])
|
||||
|
||||
shell.do_stack_update(self.client, self.args)
|
||||
self.assertEqual(1, self.client.stacks.update.call_count)
|
||||
expected_hooks = {
|
||||
'nested': {
|
||||
'bp': {'hooks': 'pre-update'}
|
||||
},
|
||||
'super': {
|
||||
'nested': {
|
||||
'bp': {'hooks': 'pre-update'}
|
||||
}
|
||||
}
|
||||
}
|
||||
actual_hooks = self.client.stacks.update.call_args[1][
|
||||
'environment']['resource_registry']['resources']
|
||||
self.assertEqual(expected_hooks, actual_hooks)
|
||||
|
||||
def test_update_hooks_in_env_and_args(self):
|
||||
type(self.args).pre_update = mock.PropertyMock(return_value=[
|
||||
'nested_a/bp',
|
||||
'bp_a',
|
||||
'another_bp_a',
|
||||
'super_a/nested/bp',
|
||||
])
|
||||
env = {
|
||||
'resource_registry': {
|
||||
'resources': {
|
||||
'bp_e': {'hooks': 'pre-update'},
|
||||
'another_bp_e': {'hooks': 'pre-update'},
|
||||
'nested_e': {
|
||||
'bp': {'hooks': 'pre-update'}
|
||||
},
|
||||
'super_e': {
|
||||
'nested': {
|
||||
'bp': {'hooks': 'pre-update'}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
shell.template_utils.process_multiple_environments_and_files = \
|
||||
mock.Mock(return_value=({}, env))
|
||||
|
||||
shell.do_stack_update(self.client, self.args)
|
||||
self.assertEqual(1, self.client.stacks.update.call_count)
|
||||
actual_hooks = self.client.stacks.update.call_args[1][
|
||||
'environment']['resource_registry']['resources']
|
||||
expected_hooks = {
|
||||
'bp_e': {'hooks': 'pre-update'},
|
||||
'another_bp_e': {'hooks': 'pre-update'},
|
||||
'nested_e': {
|
||||
'bp': {'hooks': 'pre-update'}
|
||||
},
|
||||
'super_e': {
|
||||
'nested': {
|
||||
'bp': {'hooks': 'pre-update'}
|
||||
}
|
||||
},
|
||||
'bp_a': {'hooks': 'pre-update'},
|
||||
'another_bp_a': {'hooks': 'pre-update'},
|
||||
'nested_a': {
|
||||
'bp': {'hooks': 'pre-update'}
|
||||
},
|
||||
'super_a': {
|
||||
'nested': {
|
||||
'bp': {'hooks': 'pre-update'}
|
||||
}
|
||||
},
|
||||
}
|
||||
self.assertEqual(expected_hooks, actual_hooks)
|
||||
|
||||
def test_clear_all_hooks(self):
|
||||
shell._get_hook_type_via_status =\
|
||||
mock.Mock(return_value='pre-create')
|
||||
type(self.args).hook = mock.PropertyMock(
|
||||
return_value=['bp'])
|
||||
type(self.args).pre_create = mock.PropertyMock(return_value=True)
|
||||
bp = mock.Mock()
|
||||
type(bp).resource_name = 'bp'
|
||||
self.client.resources.list = mock.Mock(return_value=[bp])
|
||||
|
||||
shell.do_hook_clear(self.client, self.args)
|
||||
self.assertEqual(1, self.client.resources.signal.call_count)
|
||||
payload_pre_create = self.client.resources.signal.call_args_list[0][1]
|
||||
self.assertEqual({'unset_hook': 'pre-create'},
|
||||
payload_pre_create['data'])
|
||||
self.assertEqual('bp', payload_pre_create['resource_name'])
|
||||
self.assertEqual('mystack', payload_pre_create['stack_id'])
|
||||
|
||||
def test_clear_pre_create_hooks(self):
|
||||
type(self.args).hook = mock.PropertyMock(
|
||||
return_value=['bp'])
|
||||
type(self.args).pre_create = mock.PropertyMock(return_value=True)
|
||||
bp = mock.Mock()
|
||||
type(bp).resource_name = 'bp'
|
||||
self.client.resources.list = mock.Mock(return_value=[bp])
|
||||
|
||||
shell.do_hook_clear(self.client, self.args)
|
||||
self.assertEqual(1, self.client.resources.signal.call_count)
|
||||
payload = self.client.resources.signal.call_args_list[0][1]
|
||||
self.assertEqual({'unset_hook': 'pre-create'}, payload['data'])
|
||||
self.assertEqual('bp', payload['resource_name'])
|
||||
self.assertEqual('mystack', payload['stack_id'])
|
||||
|
||||
def test_clear_pre_update_hooks(self):
|
||||
type(self.args).hook = mock.PropertyMock(
|
||||
return_value=['bp'])
|
||||
type(self.args).pre_update = mock.PropertyMock(return_value=True)
|
||||
bp = mock.Mock()
|
||||
type(bp).resource_name = 'bp'
|
||||
self.client.resources.list = mock.Mock(return_value=[bp])
|
||||
|
||||
shell.do_hook_clear(self.client, self.args)
|
||||
self.assertEqual(1, self.client.resources.signal.call_count)
|
||||
payload = self.client.resources.signal.call_args_list[0][1]
|
||||
self.assertEqual({'unset_hook': 'pre-update'}, payload['data'])
|
||||
self.assertEqual('bp', payload['resource_name'])
|
||||
self.assertEqual('mystack', payload['stack_id'])
|
||||
|
||||
def test_clear_nested_hook(self):
|
||||
type(self.args).hook = mock.PropertyMock(
|
||||
return_value=['a/b/bp'])
|
||||
type(self.args).pre_create = mock.PropertyMock(return_value=True)
|
||||
|
||||
a = mock.Mock()
|
||||
type(a).resource_name = 'a'
|
||||
b = mock.Mock()
|
||||
type(b).resource_name = 'b'
|
||||
bp = mock.Mock()
|
||||
type(bp).resource_name = 'bp'
|
||||
self.client.resources.list = mock.Mock(
|
||||
side_effect=[[a], [b], [bp]])
|
||||
m1 = mock.Mock()
|
||||
m2 = mock.Mock()
|
||||
type(m2).physical_resource_id = 'nested_id'
|
||||
self.client.resources.get = mock.Mock(
|
||||
side_effect=[m1, m2])
|
||||
|
||||
shell.do_hook_clear(self.client, self.args)
|
||||
payload = self.client.resources.signal.call_args_list[0][1]
|
||||
self.assertEqual({'unset_hook': 'pre-create'}, payload['data'])
|
||||
self.assertEqual('bp', payload['resource_name'])
|
||||
self.assertEqual('nested_id', payload['stack_id'])
|
||||
|
||||
def test_clear_wildcard_hooks(self):
|
||||
type(self.args).hook = mock.PropertyMock(
|
||||
return_value=['a/*b/bp*'])
|
||||
type(self.args).pre_create = mock.PropertyMock(return_value=True)
|
||||
a = mock.Mock()
|
||||
type(a).resource_name = 'a'
|
||||
b = mock.Mock()
|
||||
type(b).resource_name = 'matcthis_b'
|
||||
bp = mock.Mock()
|
||||
type(bp).resource_name = 'bp_matchthis'
|
||||
self.client.resources.list = mock.Mock(
|
||||
side_effect=[[a], [b], [bp]])
|
||||
m1 = mock.Mock()
|
||||
m2 = mock.Mock()
|
||||
type(m2).physical_resource_id = 'nested_id'
|
||||
self.client.resources.get = mock.Mock(
|
||||
side_effect=[m1, m2])
|
||||
|
||||
shell.do_hook_clear(self.client, self.args)
|
||||
payload = self.client.resources.signal.call_args_list[0][1]
|
||||
self.assertEqual({'unset_hook': 'pre-create'},
|
||||
payload['data'])
|
||||
self.assertEqual('bp_matchthis', payload['resource_name'])
|
||||
self.assertEqual('nested_id', payload['stack_id'])
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"action": "CREATE",
|
||||
"status": "COMPLETE",
|
||||
"name": "teststack",
|
||||
"resources": {}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"AWSTemplateFormatVersion" : "2010-09-09",
|
||||
"Parameters" : {
|
||||
},
|
||||
"Resources" : {
|
||||
},
|
||||
"Outputs" : {
|
||||
}
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
# Copyright 2012 OpenStack Foundation
|
||||
# All Rights Reserved.
|
||||
# Copyright (C) 2016 Catalyst IT Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
# Copyright 2012 OpenStack Foundation
|
||||
# All Rights Reserved.
|
||||
# Copyright (C) 2016 Catalyst IT Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
|
@ -12,6 +11,7 @@
|
|||
# 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 six.moves.urllib import parse
|
||||
|
||||
from stacktaskclient.openstack.common.apiclient import base
|
||||
|
@ -65,9 +65,9 @@ class UserRolesManager(base.BaseManager):
|
|||
def add(self, user, role, tenant=None):
|
||||
"""Add a role to a user"""
|
||||
# TODO: resolve the roles and users into id's
|
||||
#user_id = base.getid(user)
|
||||
# user_id = base.getid(user)
|
||||
user_id = user
|
||||
#role_id = role
|
||||
# role_id = role
|
||||
params = {
|
||||
'roles': [role]
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
# Copyright 2012 OpenStack Foundation
|
||||
# All Rights Reserved.
|
||||
# Copyright (C) 2016 Catalyst IT Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
|
@ -17,6 +16,7 @@ import logging
|
|||
|
||||
import json
|
||||
from stacktaskclient.common import utils
|
||||
from stacktaskclient.common import http
|
||||
|
||||
from stacktaskclient.openstack.common._i18n import _
|
||||
|
||||
|
@ -38,8 +38,8 @@ def _authenticated_fetcher(hc):
|
|||
return _do
|
||||
|
||||
|
||||
#@utils.arg('--tenant-id', metavar='<tenant>',
|
||||
# help=_('Specify a particular tenant'))
|
||||
# @utils.arg('--tenant-id', metavar='<tenant>',
|
||||
# help=_('Specify a particular tenant'))
|
||||
def do_user_list(hc, args):
|
||||
"""List all users in tenant"""
|
||||
kwargs = {}
|
||||
|
@ -212,17 +212,17 @@ def do_user_password_reset(rc, args):
|
|||
print "NOT YET IMPLEMENTED."
|
||||
pass
|
||||
|
||||
#
|
||||
#@utils.arg('--user', '--user-id', metavar='<user>', required=True,
|
||||
# help=_('Name or ID of user.'))
|
||||
#@utils.arg('--roles', '--role-id', metavar='<roles>', required=True,
|
||||
# help=_('List of role ids'))
|
||||
#@utils.arg('--tenant', '--tenant-id', metavar='<tenant>',
|
||||
# help=_('Name or ID of tenant.'))
|
||||
#def do_user_role_set(hc, args):
|
||||
# """Set the roles of a user. May be empty"""
|
||||
# print("do_user_role_set")
|
||||
# pass
|
||||
|
||||
# @utils.arg('--user', '--user-id', metavar='<user>', required=True,
|
||||
# help=_('Name or ID of user.'))
|
||||
# @utils.arg('--roles', '--role-id', metavar='<roles>', required=True,
|
||||
# help=_('List of role ids'))
|
||||
# @utils.arg('--tenant', '--tenant-id', metavar='<tenant>',
|
||||
# help=_('Name or ID of tenant.'))
|
||||
# def do_user_role_set(hc, args):
|
||||
# """Set the roles of a user. May be empty"""
|
||||
# print("do_user_role_set")
|
||||
# pass
|
||||
|
||||
|
||||
@utils.arg('--tenant', metavar='<tenant>',
|
||||
|
@ -241,4 +241,6 @@ def do_status(rc, args):
|
|||
if status.status_code != 200:
|
||||
print "Failed: %s" % status.reason
|
||||
return
|
||||
print json.dumps(json.loads(status.content), sort_keys=True, indent=4, separators=(',', ': '))
|
||||
print json.dumps(
|
||||
json.loads(status.content), sort_keys=True,
|
||||
indent=4, separators=(',', ': '))
|
||||
|
|
|
@ -1,3 +1,17 @@
|
|||
# Copyright (C) 2016 Catalyst IT Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# 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 stacktaskclient.openstack.common.apiclient import base
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
# Copyright 2012 OpenStack Foundation
|
||||
# All Rights Reserved.
|
||||
# Copyright (C) 2016 Catalyst IT Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
#tokens class
|
||||
|
||||
from stacktaskclient.common import utils
|
||||
|
||||
import six
|
||||
from six.moves.urllib import parse
|
||||
# Copyright (C) 2016 Catalyst IT Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# 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 stacktaskclient.openstack.common.apiclient import base
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
# Copyright 2012 OpenStack Foundation
|
||||
# All Rights Reserved.
|
||||
# Copyright (C) 2016 Catalyst IT Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
|
@ -12,7 +11,6 @@
|
|||
# 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 stacktaskclient.common import utils
|
||||
|
||||
import six
|
||||
from six.moves.urllib import parse
|
||||
|
@ -73,9 +71,9 @@ class UsersManager(base.ManagerWithFind):
|
|||
yield stack
|
||||
|
||||
params = {}
|
||||
#if 'filters' in kwargs:
|
||||
# filters = kwargs.pop('filters')
|
||||
# params.update(filters)
|
||||
# if 'filters' in kwargs:
|
||||
# filters = kwargs.pop('filters')
|
||||
# params.update(filters)
|
||||
|
||||
for key, value in six.iteritems(kwargs):
|
||||
if value:
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
# The order of packages is significant, because pip processes them in the order
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
|
||||
# Hacking already pins down pep8, pyflakes and flake8
|
||||
hacking<0.11,>=0.10.0
|
||||
coverage>=3.6
|
||||
discover
|
||||
fixtures>=1.3.1
|
||||
requests-mock>=0.6.0 # Apache-2.0
|
||||
mock>=1.2
|
||||
mox3>=0.7.0
|
||||
oslosphinx>=2.5.0 # Apache-2.0
|
||||
oslotest>=1.10.0 # Apache-2.0
|
||||
sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2
|
||||
tempest-lib>=0.6.1
|
||||
testrepository>=0.0.18
|
||||
testscenarios>=0.4
|
||||
testtools>=1.4.0
|
|
@ -1,27 +0,0 @@
|
|||
# bash completion for openstack heat
|
||||
|
||||
_heat_opts="" # lazy init
|
||||
_heat_flags="" # lazy init
|
||||
_heat_opts_exp="" # lazy init
|
||||
_heat()
|
||||
{
|
||||
local cur prev kbc
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||
|
||||
if [ "x$_heat_opts" == "x" ] ; then
|
||||
kbc="`heat bash-completion | sed -e "s/ -h / /"`"
|
||||
_heat_opts="`echo "$kbc" | sed -e "s/--[a-z0-9_-]*//g" -e "s/[ ][ ]*/ /g"`"
|
||||
_heat_flags="`echo " $kbc" | sed -e "s/ [^-][^-][a-z0-9_-]*//g" -e "s/[ ][ ]*/ /g"`"
|
||||
_heat_opts_exp="`echo $_heat_opts | sed -e "s/[ ]/|/g"`"
|
||||
fi
|
||||
|
||||
if [[ " ${COMP_WORDS[@]} " =~ " "($_heat_opts_exp)" " && "$prev" != "help" ]] ; then
|
||||
COMPREPLY=($(compgen -W "${_heat_flags}" -- ${cur}))
|
||||
else
|
||||
COMPREPLY=($(compgen -W "${_heat_opts}" -- ${cur}))
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
complete -o default -F _heat heat
|
|
@ -1,74 +0,0 @@
|
|||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Copyright 2010 OpenStack Foundation
|
||||
# Copyright 2013 IBM Corp.
|
||||
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import ConfigParser
|
||||
import os
|
||||
import sys
|
||||
|
||||
import install_venv_common as install_venv # flake8: noqa
|
||||
|
||||
|
||||
def print_help(project, venv, root):
|
||||
help = """
|
||||
%(project)s development environment setup is complete.
|
||||
|
||||
%(project)s development uses virtualenv to track and manage Python
|
||||
dependencies while in development and testing.
|
||||
|
||||
To activate the %(project)s virtualenv for the extent of your current
|
||||
shell session you can run:
|
||||
|
||||
$ source %(venv)s/bin/activate
|
||||
|
||||
Or, if you prefer, you can run commands in the virtualenv on a case by
|
||||
case basis by running:
|
||||
|
||||
$ %(root)s/tools/with_venv.sh <your command>
|
||||
"""
|
||||
print help % dict(project=project, venv=venv, root=root)
|
||||
|
||||
|
||||
def main(argv):
|
||||
root = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
||||
|
||||
if os.environ.get('tools_path'):
|
||||
root = os.environ['tools_path']
|
||||
venv = os.path.join(root, '.venv')
|
||||
if os.environ.get('venv'):
|
||||
venv = os.environ['venv']
|
||||
|
||||
pip_requires = os.path.join(root, 'requirements.txt')
|
||||
test_requires = os.path.join(root, 'test-requirements.txt')
|
||||
py_version = "python%s.%s" % (sys.version_info[0], sys.version_info[1])
|
||||
setup_cfg = ConfigParser.ConfigParser()
|
||||
setup_cfg.read('setup.cfg')
|
||||
project = setup_cfg.get('metadata', 'name')
|
||||
|
||||
install = install_venv.InstallVenv(
|
||||
root, venv, pip_requires, test_requires, py_version, project)
|
||||
options = install.parse_args(argv)
|
||||
install.check_python_version()
|
||||
install.check_dependencies()
|
||||
install.create_virtualenv(no_site_packages=options.no_site_packages)
|
||||
install.install_dependencies()
|
||||
print_help(project, venv, root)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv)
|
|
@ -1,172 +0,0 @@
|
|||
# Copyright 2013 OpenStack Foundation
|
||||
# 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.
|
||||
|
||||
"""Provides methods needed by installation script for OpenStack development
|
||||
virtual environments.
|
||||
|
||||
Since this script is used to bootstrap a virtualenv from the system's Python
|
||||
environment, it should be kept strictly compatible with Python 2.6.
|
||||
|
||||
Synced in from openstack-common
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
class InstallVenv(object):
|
||||
|
||||
def __init__(self, root, venv, requirements,
|
||||
test_requirements, py_version,
|
||||
project):
|
||||
self.root = root
|
||||
self.venv = venv
|
||||
self.requirements = requirements
|
||||
self.test_requirements = test_requirements
|
||||
self.py_version = py_version
|
||||
self.project = project
|
||||
|
||||
def die(self, message, *args):
|
||||
print(message % args, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
def check_python_version(self):
|
||||
if sys.version_info < (2, 6):
|
||||
self.die("Need Python Version >= 2.6")
|
||||
|
||||
def run_command_with_code(self, cmd, redirect_output=True,
|
||||
check_exit_code=True):
|
||||
"""Runs a command in an out-of-process shell.
|
||||
|
||||
Returns the output of that command. Working directory is self.root.
|
||||
"""
|
||||
if redirect_output:
|
||||
stdout = subprocess.PIPE
|
||||
else:
|
||||
stdout = None
|
||||
|
||||
proc = subprocess.Popen(cmd, cwd=self.root, stdout=stdout)
|
||||
output = proc.communicate()[0]
|
||||
if check_exit_code and proc.returncode != 0:
|
||||
self.die('Command "%s" failed.\n%s', ' '.join(cmd), output)
|
||||
return (output, proc.returncode)
|
||||
|
||||
def run_command(self, cmd, redirect_output=True, check_exit_code=True):
|
||||
return self.run_command_with_code(cmd, redirect_output,
|
||||
check_exit_code)[0]
|
||||
|
||||
def get_distro(self):
|
||||
if (os.path.exists('/etc/fedora-release') or
|
||||
os.path.exists('/etc/redhat-release')):
|
||||
return Fedora(
|
||||
self.root, self.venv, self.requirements,
|
||||
self.test_requirements, self.py_version, self.project)
|
||||
else:
|
||||
return Distro(
|
||||
self.root, self.venv, self.requirements,
|
||||
self.test_requirements, self.py_version, self.project)
|
||||
|
||||
def check_dependencies(self):
|
||||
self.get_distro().install_virtualenv()
|
||||
|
||||
def create_virtualenv(self, no_site_packages=True):
|
||||
"""Creates the virtual environment and installs PIP.
|
||||
|
||||
Creates the virtual environment and installs PIP only into the
|
||||
virtual environment.
|
||||
"""
|
||||
if not os.path.isdir(self.venv):
|
||||
print('Creating venv...', end=' ')
|
||||
if no_site_packages:
|
||||
self.run_command(['virtualenv', '-q', '--no-site-packages',
|
||||
self.venv])
|
||||
else:
|
||||
self.run_command(['virtualenv', '-q', self.venv])
|
||||
print('done.')
|
||||
else:
|
||||
print("venv already exists...")
|
||||
pass
|
||||
|
||||
def pip_install(self, *args):
|
||||
self.run_command(['tools/with_venv.sh',
|
||||
'pip', 'install', '--upgrade'] + list(args),
|
||||
redirect_output=False)
|
||||
|
||||
def install_dependencies(self):
|
||||
print('Installing dependencies with pip (this can take a while)...')
|
||||
|
||||
# First things first, make sure our venv has the latest pip and
|
||||
# setuptools and pbr
|
||||
self.pip_install('pip>=1.4')
|
||||
self.pip_install('setuptools')
|
||||
self.pip_install('pbr')
|
||||
|
||||
self.pip_install('-r', self.requirements, '-r', self.test_requirements)
|
||||
|
||||
def parse_args(self, argv):
|
||||
"""Parses command-line arguments."""
|
||||
parser = optparse.OptionParser()
|
||||
parser.add_option('-n', '--no-site-packages',
|
||||
action='store_true',
|
||||
help="Do not inherit packages from global Python "
|
||||
"install")
|
||||
return parser.parse_args(argv[1:])[0]
|
||||
|
||||
|
||||
class Distro(InstallVenv):
|
||||
|
||||
def check_cmd(self, cmd):
|
||||
return bool(self.run_command(['which', cmd],
|
||||
check_exit_code=False).strip())
|
||||
|
||||
def install_virtualenv(self):
|
||||
if self.check_cmd('virtualenv'):
|
||||
return
|
||||
|
||||
if self.check_cmd('easy_install'):
|
||||
print('Installing virtualenv via easy_install...', end=' ')
|
||||
if self.run_command(['easy_install', 'virtualenv']):
|
||||
print('Succeeded')
|
||||
return
|
||||
else:
|
||||
print('Failed')
|
||||
|
||||
self.die('ERROR: virtualenv not found.\n\n%s development'
|
||||
' requires virtualenv, please install it using your'
|
||||
' favorite package management tool' % self.project)
|
||||
|
||||
|
||||
class Fedora(Distro):
|
||||
"""This covers all Fedora-based distributions.
|
||||
|
||||
Includes: Fedora, RHEL, CentOS, Scientific Linux
|
||||
"""
|
||||
|
||||
def check_pkg(self, pkg):
|
||||
return self.run_command_with_code(['rpm', '-q', pkg],
|
||||
check_exit_code=False)[1] == 0
|
||||
|
||||
def install_virtualenv(self):
|
||||
if self.check_cmd('virtualenv'):
|
||||
return
|
||||
|
||||
if not self.check_pkg('python-virtualenv'):
|
||||
self.die("Please install 'python-virtualenv'.")
|
||||
|
||||
super(Fedora, self).install_virtualenv()
|
|
@ -1,10 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
command -v tox > /dev/null 2>&1
|
||||
if [ $? -ne 0 ]; then
|
||||
echo 'This script requires "tox" to run.'
|
||||
echo 'You can install it with "pip install tox".'
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
tox -evenv -- $@
|
51
tox.ini
51
tox.ini
|
@ -1,51 +0,0 @@
|
|||
[tox]
|
||||
envlist = py26,py27,pypy,pep8
|
||||
minversion = 1.6
|
||||
skipsdist = True
|
||||
|
||||
[testenv]
|
||||
setenv = VIRTUAL_ENV={envdir}
|
||||
usedevelop = True
|
||||
install_command = pip install -U {opts} {packages}
|
||||
deps = -r{toxinidir}/requirements.txt
|
||||
-r{toxinidir}/test-requirements.txt
|
||||
commands = python setup.py testr --slowest --testr-args='{posargs}'
|
||||
|
||||
[testenv:pypy]
|
||||
deps = setuptools<3.2
|
||||
-r{toxinidir}/requirements.txt
|
||||
-r{toxinidir}/test-requirements.txt
|
||||
|
||||
[testenv:pep8]
|
||||
commands =
|
||||
flake8
|
||||
# Check that .po and .pot files are valid:
|
||||
bash -c "find python-registrationclient -type f -regex '.*\.pot?' -print0|xargs -0 -n 1 msgfmt --check-format -o /dev/null"
|
||||
whitelist_externals = bash
|
||||
|
||||
[testenv:venv]
|
||||
commands = {posargs}
|
||||
|
||||
[testenv:functional]
|
||||
setenv =
|
||||
OS_TEST_PATH = ./registrationclient/tests/functional
|
||||
passenv = OS_*
|
||||
|
||||
[testenv:cover]
|
||||
commands = python setup.py testr --coverage --testr-args='{posargs}'
|
||||
|
||||
[testenv:docs]
|
||||
commands=
|
||||
python setup.py build_sphinx
|
||||
|
||||
[tox:jenkins]
|
||||
downloadcache = ~/cache/pip
|
||||
|
||||
[flake8]
|
||||
ignore = E123,E126,E128,E241,E265,E713,H202,H405,H238
|
||||
show-source = True
|
||||
exclude=.venv,.git,.tox,dist,*openstack/common*,*lib/python*,*egg,build
|
||||
max-complexity=20
|
||||
|
||||
[hacking]
|
||||
import_exceptions = registrationclient.openstack.common._i18n
|
Loading…
Reference in New Issue