push initial version
Change-Id: Ifecc2c7dd6bd859ba6ef327fddd891982382df3b
This commit is contained in:
		
							
								
								
									
										56
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
			
		||||
*.py[cod]
 | 
			
		||||
 | 
			
		||||
# C extensions
 | 
			
		||||
*.so
 | 
			
		||||
 | 
			
		||||
# Packages
 | 
			
		||||
*.egg
 | 
			
		||||
*.egg-info
 | 
			
		||||
dist
 | 
			
		||||
build
 | 
			
		||||
eggs
 | 
			
		||||
parts
 | 
			
		||||
bin
 | 
			
		||||
var
 | 
			
		||||
sdist
 | 
			
		||||
develop-eggs
 | 
			
		||||
.installed.cfg
 | 
			
		||||
lib
 | 
			
		||||
lib64
 | 
			
		||||
 | 
			
		||||
# Installer logs
 | 
			
		||||
pip-log.txt
 | 
			
		||||
 | 
			
		||||
# Unit test / coverage reports
 | 
			
		||||
.coverage
 | 
			
		||||
.tox
 | 
			
		||||
nosetests.xml
 | 
			
		||||
.testrepository
 | 
			
		||||
.venv
 | 
			
		||||
 | 
			
		||||
# Translations
 | 
			
		||||
*.mo
 | 
			
		||||
 | 
			
		||||
# Mr Developer
 | 
			
		||||
.mr.developer.cfg
 | 
			
		||||
.project
 | 
			
		||||
.pydevproject
 | 
			
		||||
 | 
			
		||||
# Complexity
 | 
			
		||||
output/*.html
 | 
			
		||||
output/*/index.html
 | 
			
		||||
 | 
			
		||||
# Sphinx
 | 
			
		||||
doc/build
 | 
			
		||||
 | 
			
		||||
# pbr generates these
 | 
			
		||||
AUTHORS
 | 
			
		||||
ChangeLog
 | 
			
		||||
 | 
			
		||||
# Editors
 | 
			
		||||
*~
 | 
			
		||||
.*.swp
 | 
			
		||||
.*sw?
 | 
			
		||||
 | 
			
		||||
sftp-config.json
 | 
			
		||||
/.idea/
 | 
			
		||||
							
								
								
									
										7
									
								
								.testr.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								.testr.conf
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
[DEFAULT]
 | 
			
		||||
test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \
 | 
			
		||||
             OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \
 | 
			
		||||
             OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-10} \
 | 
			
		||||
             ${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION
 | 
			
		||||
test_id_option=--load-list $IDFILE
 | 
			
		||||
test_list_option=--list
 | 
			
		||||
							
								
								
									
										16
									
								
								CONTRIBUTING.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								CONTRIBUTING.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
If you would like to contribute to the development of OpenStack,
 | 
			
		||||
you must follow the steps in this page:
 | 
			
		||||
 | 
			
		||||
   http://docs.openstack.org/infra/manual/developers.html
 | 
			
		||||
 | 
			
		||||
Once those steps have been completed, changes to OpenStack
 | 
			
		||||
should be submitted for review via the Gerrit tool, following
 | 
			
		||||
the workflow documented at:
 | 
			
		||||
 | 
			
		||||
   http://docs.openstack.org/infra/manual/developers.html#development-workflow
 | 
			
		||||
 | 
			
		||||
Pull requests submitted through GitHub will be ignored.
 | 
			
		||||
 | 
			
		||||
Bugs should be filed on Launchpad, not GitHub:
 | 
			
		||||
 | 
			
		||||
   https://bugs.launchpad.net/python-watcherclient
 | 
			
		||||
							
								
								
									
										4
									
								
								HACKING.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								HACKING.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
python-watcherclient Style Commandments
 | 
			
		||||
===============================================
 | 
			
		||||
 | 
			
		||||
Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/
 | 
			
		||||
							
								
								
									
										6
									
								
								MANIFEST.in
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								MANIFEST.in
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
include AUTHORS
 | 
			
		||||
include ChangeLog
 | 
			
		||||
exclude .gitignore
 | 
			
		||||
exclude .gitreview
 | 
			
		||||
 | 
			
		||||
global-exclude *.pyc
 | 
			
		||||
							
								
								
									
										13
									
								
								README.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								README.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
===============================
 | 
			
		||||
python-watcherclient
 | 
			
		||||
===============================
 | 
			
		||||
 | 
			
		||||
Python client library for Watcher API
 | 
			
		||||
 | 
			
		||||
* Free software: Apache license
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Features
 | 
			
		||||
--------
 | 
			
		||||
 | 
			
		||||
* TODO
 | 
			
		||||
							
								
								
									
										94
									
								
								doc/source/api_v1.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								doc/source/api_v1.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,94 @@
 | 
			
		||||
.. _api_v1:
 | 
			
		||||
 | 
			
		||||
========================
 | 
			
		||||
watcherclient Python API
 | 
			
		||||
========================
 | 
			
		||||
 | 
			
		||||
The watcherclient python API lets you access watcher, the OpenStack
 | 
			
		||||
TODEFINE Service.
 | 
			
		||||
 | 
			
		||||
For example, to manipulate audits, you interact with an `watcherclient.v1.audit`_ object.
 | 
			
		||||
You obtain access to audits via attributes of the `watcherclient.v1.client.Client`_ object.
 | 
			
		||||
 | 
			
		||||
Usage
 | 
			
		||||
=====
 | 
			
		||||
 | 
			
		||||
Get a Client object
 | 
			
		||||
-------------------
 | 
			
		||||
First, create an `watcherclient.v1.client.Client`_ instance by passing your
 | 
			
		||||
credentials to `watcherclient.client.get_client()`_. By default, the
 | 
			
		||||
Watcher system is configured so that only administrators (users with 
 | 
			
		||||
'admin' role) have access.
 | 
			
		||||
 | 
			
		||||
There are two different sets of credentials that can be used::
 | 
			
		||||
 | 
			
		||||
   * watcher endpoint and auth token
 | 
			
		||||
   * Identity Service (keystone) credentials
 | 
			
		||||
 | 
			
		||||
Using watcher endpoint and auth token
 | 
			
		||||
.....................................
 | 
			
		||||
 | 
			
		||||
An auth token and the watcher endpoint can be used to authenticate::
 | 
			
		||||
 | 
			
		||||
      * os_auth_token: authentication token (from Identity Service)
 | 
			
		||||
      * watcher_url: watcher API endpoint, eg http://watcher.example.org:9322/v1
 | 
			
		||||
 | 
			
		||||
To create the client, you can use the API like so::
 | 
			
		||||
 | 
			
		||||
   >>> from watcherclient import client
 | 
			
		||||
   >>>
 | 
			
		||||
   >>> kwargs = {'os_auth_token': '3bcc3d3a03f44e3d8377f9247b0ad155'
 | 
			
		||||
   >>>           'watcher_url': 'http://watcher.example.org:9322/'}
 | 
			
		||||
   >>> watcher = client.get_client(1, **kwargs)
 | 
			
		||||
 | 
			
		||||
Using Identity Service (keystone) credentials
 | 
			
		||||
.............................................
 | 
			
		||||
 | 
			
		||||
These Identity Service credentials can be used to authenticate::
 | 
			
		||||
 | 
			
		||||
   * os_username: name of user
 | 
			
		||||
   * os_password: user's password
 | 
			
		||||
   * os_auth_url: Identity Service endpoint for authorization
 | 
			
		||||
   * os_tenant_{name|id}: name or ID of tenant
 | 
			
		||||
 | 
			
		||||
To create a client, you can use the API like so::
 | 
			
		||||
 | 
			
		||||
   >>> from watcherclient import client
 | 
			
		||||
   >>>
 | 
			
		||||
   >>> kwargs = {'os_username': 'name',
 | 
			
		||||
   >>>           'os_password': 'password',
 | 
			
		||||
   >>>           'os_auth_url': 'http://keystone.example.org:5000/',
 | 
			
		||||
   >>>           'os_tenant_name': 'tenant'}
 | 
			
		||||
   >>> watcher = client.get_client(1, **kwargs)
 | 
			
		||||
 | 
			
		||||
Perform watcher operations
 | 
			
		||||
--------------------------
 | 
			
		||||
 | 
			
		||||
Once you have an watcher `Client`_, you can perform various tasks::
 | 
			
		||||
 | 
			
		||||
   >>> watcher.action.list()  # list of actions
 | 
			
		||||
   >>> watcher.action_plan.list()  # list of action_plan
 | 
			
		||||
   >>> watcher.audit.get(audit_uuid)  # information about a particular audit
 | 
			
		||||
 | 
			
		||||
When the `Client`_ needs to propagate an exception, it will usually
 | 
			
		||||
raise an instance subclassed from
 | 
			
		||||
`watcherclient.exc.BaseException`_ or `watcherclient.exc.ClientException`_.
 | 
			
		||||
 | 
			
		||||
Refer to the modules themselves, for more details.
 | 
			
		||||
 | 
			
		||||
=====================
 | 
			
		||||
watcherclient Modules
 | 
			
		||||
=====================
 | 
			
		||||
 | 
			
		||||
.. toctree::
 | 
			
		||||
    :maxdepth: 1
 | 
			
		||||
 | 
			
		||||
    modules <api/autoindex>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.. _watcherclient.v1.audit: api/watcherclient.v1.audit.html#watcherclient.v1.audit.Audit
 | 
			
		||||
.. _watcherclient.v1.client.Client: api/watcherclient.v1.client.html#watcherclient.v1.client.Client
 | 
			
		||||
.. _Client: api/watcherclient.v1.client.html#watcherclient.v1.client.Client
 | 
			
		||||
.. _watcherclient.client.get_client(): api/watcherclient.client.html#watcherclient.client.get_client
 | 
			
		||||
.. _watcherclient.exc.BaseException: api/watcherclient.exc.html#watcherclient.exc.BaseException
 | 
			
		||||
.. _watcherclient.exc.ClientException: api/watcherclient.exc.html#watcherclient.exc.ClientException
 | 
			
		||||
							
								
								
									
										89
									
								
								doc/source/cli.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								doc/source/cli.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,89 @@
 | 
			
		||||
==============================================
 | 
			
		||||
:program:`watcher` Command-Line Interface (CLI)
 | 
			
		||||
==============================================
 | 
			
		||||
 | 
			
		||||
.. program:: watcher
 | 
			
		||||
.. highlight:: bash
 | 
			
		||||
 | 
			
		||||
SYNOPSIS
 | 
			
		||||
========
 | 
			
		||||
 | 
			
		||||
:program:`watcher` [options] <command> [command-options]
 | 
			
		||||
 | 
			
		||||
:program:`watcher help`
 | 
			
		||||
 | 
			
		||||
:program:`watcher help` <command>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
DESCRIPTION
 | 
			
		||||
===========
 | 
			
		||||
 | 
			
		||||
The :program:`watcher` command-line interface (CLI) interacts with the
 | 
			
		||||
OpenStack TODEFINE Service (Watcher).
 | 
			
		||||
 | 
			
		||||
In order to use the CLI, you must provide your OpenStack username, password,
 | 
			
		||||
project (historically called tenant), and auth endpoint. You can use
 | 
			
		||||
configuration options :option:`--os-username`, :option:`--os-password`,
 | 
			
		||||
:option:`--os-tenant-id` (or :option:`--os-tenant-name`),
 | 
			
		||||
and :option:`--os-auth-url`, or set the corresponding
 | 
			
		||||
environment variables::
 | 
			
		||||
 | 
			
		||||
    export OS_USERNAME=user
 | 
			
		||||
    export OS_PASSWORD=password
 | 
			
		||||
    export OS_TENANT_ID=b363706f891f48019483f8bd6503c54b   # or OS_TENANT_NAME
 | 
			
		||||
    export OS_TENANT_NAME=project                          # or OS_TENANT_ID
 | 
			
		||||
    export OS_AUTH_URL=http://auth.example.com:5000/v2.0
 | 
			
		||||
 | 
			
		||||
The command-line tool will attempt to reauthenticate using the provided
 | 
			
		||||
credentials for every request. You can override this behavior by manually
 | 
			
		||||
supplying an auth token using :option:`--watcher-url` and
 | 
			
		||||
:option:`--os-auth-token`, or by setting the corresponding environment variables::
 | 
			
		||||
 | 
			
		||||
    export WATCHER_URL=http://watcher.example.org:9322/
 | 
			
		||||
    export OS_AUTH_TOKEN=3bcc3d3a03f44e3d8377f9247b0ad155
 | 
			
		||||
 | 
			
		||||
Since Keystone can return multiple regions in the Service Catalog, you can
 | 
			
		||||
specify the one you want with :option:`--os-region-name` or set the following
 | 
			
		||||
environment variable. (It defaults to the first in the list returned.)
 | 
			
		||||
::
 | 
			
		||||
 | 
			
		||||
    export OS_REGION_NAME=region
 | 
			
		||||
 | 
			
		||||
Watcher CLI supports bash completion. The command-line tool can automatically
 | 
			
		||||
fill partially typed commands. To use this feature, source the below file
 | 
			
		||||
(available at
 | 
			
		||||
https://git.openstack.org/cgit/openstack/python-watcherclient/tree/tools/watcher.bash_completion)
 | 
			
		||||
to your terminal and then bash completion should work::
 | 
			
		||||
 | 
			
		||||
    source watcher.bash_completion
 | 
			
		||||
 | 
			
		||||
To avoid doing this every time, add this to your ``.bashrc`` or copy the
 | 
			
		||||
watcher.bash_completion file to the default bash completion scripts directory
 | 
			
		||||
on your linux distribution.
 | 
			
		||||
 | 
			
		||||
OPTIONS
 | 
			
		||||
=======
 | 
			
		||||
 | 
			
		||||
To get a list of available (sub)commands and options, run::
 | 
			
		||||
 | 
			
		||||
    watcher help
 | 
			
		||||
 | 
			
		||||
To get usage and options of a command, run::
 | 
			
		||||
 | 
			
		||||
    watcher help <command>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
EXAMPLES
 | 
			
		||||
========
 | 
			
		||||
 | 
			
		||||
Get information about the audit-create command::
 | 
			
		||||
 | 
			
		||||
    watcher help audit-create
 | 
			
		||||
 | 
			
		||||
Get a list of available goal::
 | 
			
		||||
 | 
			
		||||
    watcher goal-list
 | 
			
		||||
 | 
			
		||||
Get a list of audits::
 | 
			
		||||
 | 
			
		||||
    watcher audit-list
 | 
			
		||||
							
								
								
									
										89
									
								
								doc/source/conf.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								doc/source/conf.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,89 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
# Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
# you may not use this file except in compliance with the License.
 | 
			
		||||
# You may obtain a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
# Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
# distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 | 
			
		||||
# implied.
 | 
			
		||||
# See the License for the specific language governing permissions and
 | 
			
		||||
# limitations under the License.
 | 
			
		||||
 | 
			
		||||
from watcherclient import version as watcherclient_version
 | 
			
		||||
 | 
			
		||||
# -- General configuration ----------------------------------------------------
 | 
			
		||||
 | 
			
		||||
# Add any Sphinx extension module names here, as strings. They can be
 | 
			
		||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
 | 
			
		||||
extensions = ['sphinx.ext.autodoc',
 | 
			
		||||
              'sphinx.ext.viewcode',
 | 
			
		||||
              'oslosphinx',
 | 
			
		||||
              ]
 | 
			
		||||
 | 
			
		||||
# autodoc generation is a bit aggressive and a nuisance when doing heavy
 | 
			
		||||
# text edit cycles.
 | 
			
		||||
# execute "export SPHINX_DEBUG=1" in your terminal to disable
 | 
			
		||||
 | 
			
		||||
# Add any paths that contain templates here, relative to this directory.
 | 
			
		||||
templates_path = ['_templates']
 | 
			
		||||
 | 
			
		||||
# The suffix of source filenames.
 | 
			
		||||
source_suffix = '.rst'
 | 
			
		||||
 | 
			
		||||
# The master toctree document.
 | 
			
		||||
master_doc = 'index'
 | 
			
		||||
 | 
			
		||||
# General information about the project.
 | 
			
		||||
project = u'python-watcherclient'
 | 
			
		||||
copyright = u'OpenStack Foundation'
 | 
			
		||||
 | 
			
		||||
# 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.
 | 
			
		||||
# The full version, including alpha/beta/rc tags.
 | 
			
		||||
release = watcherclient_version.version_info.release_string()
 | 
			
		||||
# The short X.Y version.
 | 
			
		||||
version = watcherclient_version.version_info.version_string()
 | 
			
		||||
 | 
			
		||||
# A list of ignored prefixes for module index sorting.
 | 
			
		||||
modindex_common_prefix = ['watcherclient.']
 | 
			
		||||
 | 
			
		||||
# If true, '()' will be appended to :func: etc. cross-reference text.
 | 
			
		||||
add_function_parentheses = True
 | 
			
		||||
 | 
			
		||||
# If true, the current module name will be prepended to all description
 | 
			
		||||
# unit titles (such as .. function::).
 | 
			
		||||
add_module_names = True
 | 
			
		||||
 | 
			
		||||
# The name of the Pygments (syntax highlighting) style to use.
 | 
			
		||||
pygments_style = 'sphinx'
 | 
			
		||||
 | 
			
		||||
# -- Options for HTML output --------------------------------------------------
 | 
			
		||||
 | 
			
		||||
# The theme to use for HTML and HTML Help pages. Major themes that come with
 | 
			
		||||
# Sphinx are currently 'default' and 'sphinxdoc'.
 | 
			
		||||
# html_theme_path = ["."]
 | 
			
		||||
# html_theme = '_theme'
 | 
			
		||||
# html_static_path = ['_static']
 | 
			
		||||
html_theme_options = {'incubating': True}
 | 
			
		||||
 | 
			
		||||
# Output file base name for HTML help builder.
 | 
			
		||||
htmlhelp_basename = '%sdoc' % project
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Grouping the document tree into LaTeX files. List of tuples
 | 
			
		||||
# (source start file, target name, title, author, documentclass
 | 
			
		||||
# [howto/manual]).
 | 
			
		||||
latex_documents = [
 | 
			
		||||
    (
 | 
			
		||||
        'index',
 | 
			
		||||
        '%s.tex' % project,
 | 
			
		||||
        u'%s Documentation' % project,
 | 
			
		||||
        u'OpenStack Foundation', 'manual'
 | 
			
		||||
    ),
 | 
			
		||||
]
 | 
			
		||||
							
								
								
									
										54
									
								
								doc/source/contributing.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								doc/source/contributing.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,54 @@
 | 
			
		||||
.. _contributing:
 | 
			
		||||
 | 
			
		||||
===================================
 | 
			
		||||
Contributing to python-watcherclient
 | 
			
		||||
===================================
 | 
			
		||||
 | 
			
		||||
If you're interested in contributing to the python-watcherclient project,
 | 
			
		||||
the following will help get you started.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Contributor License Agreement
 | 
			
		||||
-----------------------------
 | 
			
		||||
 | 
			
		||||
.. index::
 | 
			
		||||
   single: license; agreement
 | 
			
		||||
 | 
			
		||||
In order to contribute to the python-watcherclient project, you need to have
 | 
			
		||||
signed OpenStack's contributor's agreement.
 | 
			
		||||
 | 
			
		||||
.. seealso::
 | 
			
		||||
 | 
			
		||||
   * http://docs.openstack.org/infra/manual/developers.html
 | 
			
		||||
   * http://wiki.openstack.org/CLA
 | 
			
		||||
 | 
			
		||||
LaunchPad Project
 | 
			
		||||
-----------------
 | 
			
		||||
 | 
			
		||||
Most of the tools used for OpenStack depend on a launchpad.net ID for
 | 
			
		||||
authentication. After signing up for a launchpad account, join the
 | 
			
		||||
"openstack" team to have access to the mailing list and receive
 | 
			
		||||
notifications of important events.
 | 
			
		||||
 | 
			
		||||
.. seealso::
 | 
			
		||||
 | 
			
		||||
   * http://launchpad.net
 | 
			
		||||
   * http://launchpad.net/python-watcherclient
 | 
			
		||||
   * http://launchpad.net/~openstack
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Project Hosting Details
 | 
			
		||||
-------------------------
 | 
			
		||||
 | 
			
		||||
Bug tracker
 | 
			
		||||
    http://launchpad.net/python-watcherclient
 | 
			
		||||
 | 
			
		||||
Mailing list (prefix subjects with ``[watcher]`` for faster responses)
 | 
			
		||||
    http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev
 | 
			
		||||
 | 
			
		||||
Code Hosting
 | 
			
		||||
    https://git.openstack.org/cgit/openstack/python-watcherclient
 | 
			
		||||
 | 
			
		||||
Code Review
 | 
			
		||||
    https://review.openstack.org/#/q/status:open+project:openstack/python-watcherclient,n,z
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										50
									
								
								doc/source/index.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								doc/source/index.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,50 @@
 | 
			
		||||
Python bindings to the OpenStack Watcher API
 | 
			
		||||
============================================
 | 
			
		||||
 | 
			
		||||
This is a client for OpenStack Watcher API. There's :doc:`a Python API
 | 
			
		||||
<api_v1>` (the :mod:`watcherclient` modules), and a :doc:`command-line script
 | 
			
		||||
<cli>` (installed as :program:`watcher`). Each implements the entire
 | 
			
		||||
OpenStack Watcher API.
 | 
			
		||||
 | 
			
		||||
You'll need credentials for an OpenStack cloud in order to use the watcher client.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Contents:
 | 
			
		||||
 | 
			
		||||
.. toctree::
 | 
			
		||||
   :maxdepth: 1
 | 
			
		||||
 | 
			
		||||
   readme
 | 
			
		||||
   installation
 | 
			
		||||
   api_v1
 | 
			
		||||
   cli
 | 
			
		||||
   contributing
 | 
			
		||||
 | 
			
		||||
Contributing
 | 
			
		||||
============
 | 
			
		||||
 | 
			
		||||
Code is hosted at `git.openstack.org`_. Submit bugs to the Watcher project on
 | 
			
		||||
`Launchpad`_. Submit code to the openstack/python-watcherclient project using
 | 
			
		||||
`Gerrit`_.
 | 
			
		||||
 | 
			
		||||
.. _git.openstack.org: https://git.openstack.org/cgit/openstack/python-watcherclient
 | 
			
		||||
.. _Launchpad: https://launchpad.net/watcher
 | 
			
		||||
.. _Gerrit: http://docs.openstack.org/infra/manual/developers.html#development-workflow
 | 
			
		||||
 | 
			
		||||
Testing
 | 
			
		||||
-------
 | 
			
		||||
 | 
			
		||||
The preferred way to run the unit tests is using ``tox``.
 | 
			
		||||
 | 
			
		||||
See `Consistent Testing Interface`_ for more details.
 | 
			
		||||
 | 
			
		||||
.. _Consistent Testing Interface: http://git.openstack.org/cgit/openstack/governance/tree/reference/project-testing-interface.rst
 | 
			
		||||
 | 
			
		||||
Indices and tables
 | 
			
		||||
==================
 | 
			
		||||
 | 
			
		||||
* :ref:`genindex`
 | 
			
		||||
* :ref:`modindex`
 | 
			
		||||
* :ref:`search`
 | 
			
		||||
 | 
			
		||||
.. _Watcher: https://wiki.openstack.org/wiki/Watcher
 | 
			
		||||
							
								
								
									
										10
									
								
								doc/source/installation.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								doc/source/installation.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
============
 | 
			
		||||
Installation
 | 
			
		||||
============
 | 
			
		||||
 | 
			
		||||
Or, if you have `virtualenvwrapper <https://virtualenvwrapper.readthedocs.org/en/latest/install.html>`_   installed::
 | 
			
		||||
 | 
			
		||||
    $ mkvirtualenv python-watcherclient
 | 
			
		||||
    $ git clone https://git.openstack.org/openstack/stackforge/python-watcherclient
 | 
			
		||||
    $ cd python-watcherclient && python setup.py install
 | 
			
		||||
    $ pip install -r ./requirements.txt
 | 
			
		||||
							
								
								
									
										1
									
								
								doc/source/readme.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								doc/source/readme.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
.. include:: ../../README.rst
 | 
			
		||||
							
								
								
									
										10
									
								
								openstack-common.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								openstack-common.conf
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
[DEFAULT]
 | 
			
		||||
 | 
			
		||||
# The list of modules to copy from oslo-incubator.git
 | 
			
		||||
module=apiclient
 | 
			
		||||
module=cliutils
 | 
			
		||||
module=_i18n
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# The base module to hold the copy of openstack.common
 | 
			
		||||
base=watcherclient
 | 
			
		||||
							
								
								
									
										10
									
								
								requirements.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								requirements.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
# 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.
 | 
			
		||||
 | 
			
		||||
argparse
 | 
			
		||||
pbr>=0.6,!=0.7,<1.0
 | 
			
		||||
Babel>=1.3
 | 
			
		||||
oslo.i18n
 | 
			
		||||
python-keystoneclient>=0.11.1
 | 
			
		||||
six>=1.7.0
 | 
			
		||||
							
								
								
									
										37
									
								
								setup.cfg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								setup.cfg
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
[metadata]
 | 
			
		||||
name = python-watcherclient
 | 
			
		||||
summary = Python client library for Watcher API
 | 
			
		||||
description-file =
 | 
			
		||||
    README.rst
 | 
			
		||||
author = OpenStack
 | 
			
		||||
author-email = openstack-dev@lists.openstack.org
 | 
			
		||||
home-page = http://www.openstack.org/
 | 
			
		||||
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
 | 
			
		||||
    Programming Language :: Python :: 3
 | 
			
		||||
    Programming Language :: Python :: 3.3
 | 
			
		||||
    Programming Language :: Python :: 3.4
 | 
			
		||||
 | 
			
		||||
[files]
 | 
			
		||||
packages =
 | 
			
		||||
    watcherclient
 | 
			
		||||
 | 
			
		||||
[entry_points]
 | 
			
		||||
console_scripts =
 | 
			
		||||
    watcher = watcherclient.shell:main
 | 
			
		||||
 | 
			
		||||
[pbr]
 | 
			
		||||
autodoc_index_modules = True
 | 
			
		||||
 | 
			
		||||
[build_sphinx]
 | 
			
		||||
source-dir = doc/source
 | 
			
		||||
build-dir = doc/build
 | 
			
		||||
all_files = 1
 | 
			
		||||
							
								
								
									
										30
									
								
								setup.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										30
									
								
								setup.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
 | 
			
		||||
#
 | 
			
		||||
# Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
# you may not use this file except in compliance with the License.
 | 
			
		||||
# You may obtain a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
# Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
# distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 | 
			
		||||
# implied.
 | 
			
		||||
# See the License for the specific language governing permissions and
 | 
			
		||||
# limitations under the License.
 | 
			
		||||
 | 
			
		||||
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
 | 
			
		||||
import setuptools
 | 
			
		||||
 | 
			
		||||
# In python < 2.7.4, a lazy loading of package `pbr` will break
 | 
			
		||||
# setuptools if some other modules registered functions in `atexit`.
 | 
			
		||||
# solution from: http://bugs.python.org/issue15881#msg170215
 | 
			
		||||
try:
 | 
			
		||||
    import multiprocessing  # noqa
 | 
			
		||||
except ImportError:
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
setuptools.setup(
 | 
			
		||||
    setup_requires=['pbr'],
 | 
			
		||||
    pbr=True)
 | 
			
		||||
							
								
								
									
										18
									
								
								test-requirements.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								test-requirements.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
# The order of packages is significant, because pip processes them in the order
 | 
			
		||||
# of appearance. Changing the order has an impact on the overall integration
 | 
			
		||||
# process, which may cause wedges in the gate later.
 | 
			
		||||
 | 
			
		||||
hacking<0.11,>=0.10.0
 | 
			
		||||
 | 
			
		||||
coverage>=3.6
 | 
			
		||||
discover
 | 
			
		||||
python-subunit>=0.0.18
 | 
			
		||||
sphinx>=1.1.2,!=1.2.0,!=1.3b1,<1.3
 | 
			
		||||
oslosphinx>=2.2.0  # Apache-2.0
 | 
			
		||||
oslotest>=1.2.0  # Apache-2.0
 | 
			
		||||
testrepository>=0.0.18
 | 
			
		||||
testscenarios>=0.4
 | 
			
		||||
testtools>=0.9.36,!=1.2.0
 | 
			
		||||
mock>=1.0
 | 
			
		||||
# httpretty>=0.8.4,!=0.8.7
 | 
			
		||||
httpretty>=0.8.8
 | 
			
		||||
							
								
								
									
										27
									
								
								tools/watcher.bash_completion
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								tools/watcher.bash_completion
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
_watcher_opts="" # lazy init
 | 
			
		||||
_watcher_flags="" # lazy init
 | 
			
		||||
_watcher_opts_exp="" # lazy init
 | 
			
		||||
_watcher()
 | 
			
		||||
{
 | 
			
		||||
    local cur prev nbc cflags
 | 
			
		||||
    COMPREPLY=()
 | 
			
		||||
    cur="${COMP_WORDS[COMP_CWORD]}"
 | 
			
		||||
    prev="${COMP_WORDS[COMP_CWORD-1]}"
 | 
			
		||||
 | 
			
		||||
    if [ "x$_watcher_opts" == "x" ] ; then
 | 
			
		||||
        nbc="`watcher bash-completion | sed -e "s/  *-h  */ /" -e "s/  *-i  */ /"`"
 | 
			
		||||
        _watcher_opts="`echo "$nbc" | sed -e "s/--[a-z0-9_-]*//g" -e "s/  */ /g"`"
 | 
			
		||||
        _watcher_flags="`echo " $nbc" | sed -e "s/ [^-][^-][a-z0-9_-]*//g" -e "s/  */ /g"`"
 | 
			
		||||
        _watcher_opts_exp="`echo "$_watcher_opts" | tr ' ' '|'`"
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    if [[ " ${COMP_WORDS[@]} " =~ " "($_watcher_opts_exp)" " && "$prev" != "help" ]] ; then
 | 
			
		||||
        COMPLETION_CACHE=$HOME/.cache/python-watcherclient/*/*-cache
 | 
			
		||||
        cflags="$_watcher_flags "$(cat $COMPLETION_CACHE 2> /dev/null | tr '\n' ' ')
 | 
			
		||||
        COMPREPLY=($(compgen -W "${cflags}" -- ${cur}))
 | 
			
		||||
    else
 | 
			
		||||
        COMPREPLY=($(compgen -W "${_watcher_opts}" -- ${cur}))
 | 
			
		||||
    fi
 | 
			
		||||
    return 0
 | 
			
		||||
}
 | 
			
		||||
complete -F _watcher watcher
 | 
			
		||||
							
								
								
									
										35
									
								
								tox.ini
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								tox.ini
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
[tox]
 | 
			
		||||
minversion = 1.6
 | 
			
		||||
envlist = py33,py34,py26,py27,pep8
 | 
			
		||||
skipsdist = True
 | 
			
		||||
 | 
			
		||||
[testenv]
 | 
			
		||||
usedevelop = True
 | 
			
		||||
install_command = pip install -U {opts} {packages}
 | 
			
		||||
setenv =
 | 
			
		||||
   VIRTUAL_ENV={envdir}
 | 
			
		||||
deps = -r{toxinidir}/requirements.txt
 | 
			
		||||
       -r{toxinidir}/test-requirements.txt
 | 
			
		||||
commands = python setup.py testr --slowest --testr-args='{posargs}'
 | 
			
		||||
 | 
			
		||||
[testenv:pep8]
 | 
			
		||||
commands = flake8
 | 
			
		||||
 | 
			
		||||
[testenv:venv]
 | 
			
		||||
commands = {posargs}
 | 
			
		||||
 | 
			
		||||
[testenv:cover]
 | 
			
		||||
commands = python setup.py testr --coverage --testr-args='{posargs}'
 | 
			
		||||
 | 
			
		||||
[testenv:docs]
 | 
			
		||||
commands = python setup.py build_sphinx
 | 
			
		||||
 | 
			
		||||
[testenv:debug]
 | 
			
		||||
commands = oslo_debug_helper {posargs}
 | 
			
		||||
 | 
			
		||||
[flake8]
 | 
			
		||||
# E123, E125 skipped as they are invalid PEP-8.
 | 
			
		||||
show-source = True
 | 
			
		||||
ignore = E123,E125
 | 
			
		||||
builtins = _
 | 
			
		||||
exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build
 | 
			
		||||
							
								
								
									
										26
									
								
								watcherclient/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								watcherclient/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
# Copyright 2013 Hewlett-Packard Development Company, L.P.
 | 
			
		||||
# 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 pbr.version
 | 
			
		||||
from watcherclient import client
 | 
			
		||||
from watcherclient import exceptions
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
__version__ = pbr.version.VersionInfo(
 | 
			
		||||
    'python-watcherclient').version_string()
 | 
			
		||||
 | 
			
		||||
__all__ = ['client', 'exceptions', ]
 | 
			
		||||
							
								
								
									
										123
									
								
								watcherclient/client.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								watcherclient/client.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,123 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
			
		||||
#    not use this file except in compliance with the License. You may obtain
 | 
			
		||||
#    a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#         http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
#    Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
#    License for the specific language governing permissions and limitations
 | 
			
		||||
#    under the License.
 | 
			
		||||
 | 
			
		||||
from keystoneclient.v2_0 import client as ksclient
 | 
			
		||||
 | 
			
		||||
from watcherclient.common import utils
 | 
			
		||||
from watcherclient import exceptions as exc
 | 
			
		||||
from watcherclient.openstack.common._i18n import _
 | 
			
		||||
from watcherclient.openstack.common import gettextutils
 | 
			
		||||
 | 
			
		||||
gettextutils.install('watcherclient')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _get_ksclient(**kwargs):
 | 
			
		||||
    """Get an endpoint and auth token from Keystone.
 | 
			
		||||
 | 
			
		||||
    :param kwargs: keyword args containing credentials:
 | 
			
		||||
            * username: name of user
 | 
			
		||||
            * password: user's password
 | 
			
		||||
            * auth_url: endpoint to authenticate against
 | 
			
		||||
            * insecure: allow insecure SSL (no cert verification)
 | 
			
		||||
            * tenant_{name|id}: name or ID of tenant
 | 
			
		||||
    """
 | 
			
		||||
    return ksclient.Client(username=kwargs.get('username'),
 | 
			
		||||
                           password=kwargs.get('password'),
 | 
			
		||||
                           tenant_id=kwargs.get('tenant_id'),
 | 
			
		||||
                           tenant_name=kwargs.get('tenant_name'),
 | 
			
		||||
                           auth_url=kwargs.get('auth_url'),
 | 
			
		||||
                           insecure=kwargs.get('insecure'))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _get_endpoint(client, **kwargs):
 | 
			
		||||
    """Get an endpoint using the provided keystone client."""
 | 
			
		||||
    attr = None
 | 
			
		||||
    filter_value = None
 | 
			
		||||
    if kwargs.get('region_name'):
 | 
			
		||||
        attr = 'region'
 | 
			
		||||
        filter_value = kwargs.get('region_name')
 | 
			
		||||
    return client.service_catalog.url_for(
 | 
			
		||||
        service_type=kwargs.get('service_type') or 'infra-optim',
 | 
			
		||||
        attr=attr,
 | 
			
		||||
        filter_value=filter_value,
 | 
			
		||||
        endpoint_type=kwargs.get('endpoint_type') or 'publicURL')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_client(api_version, **kwargs):
 | 
			
		||||
    """Get an authenticated client, based on the credentials in args.
 | 
			
		||||
 | 
			
		||||
    :param api_version: the API version to use. Valid value: '1'.
 | 
			
		||||
    :param kwargs: keyword args containing credentials, either:
 | 
			
		||||
            * os_auth_token: pre-existing token to re-use
 | 
			
		||||
            * watcher_url: watcher API endpoint
 | 
			
		||||
            or:
 | 
			
		||||
            * os_username: name of user
 | 
			
		||||
            * os_password: user's password
 | 
			
		||||
            * os_auth_url: endpoint to authenticate against
 | 
			
		||||
            * insecure: allow insecure SSL (no cert verification)
 | 
			
		||||
            * os_tenant_{name|id}: name or ID of tenant
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    if kwargs.get('os_auth_token') and kwargs.get('watcher_url'):
 | 
			
		||||
        token = kwargs.get('os_auth_token')
 | 
			
		||||
        endpoint = kwargs.get('watcher_url')
 | 
			
		||||
        auth_ref = None
 | 
			
		||||
    elif (kwargs.get('os_username') and
 | 
			
		||||
          kwargs.get('os_password') and
 | 
			
		||||
          kwargs.get('os_auth_url') and
 | 
			
		||||
          (kwargs.get('os_tenant_id') or kwargs.get('os_tenant_name'))):
 | 
			
		||||
 | 
			
		||||
        ks_kwargs = {
 | 
			
		||||
            'username': kwargs.get('os_username'),
 | 
			
		||||
            'password': kwargs.get('os_password'),
 | 
			
		||||
            'tenant_id': kwargs.get('os_tenant_id'),
 | 
			
		||||
            'tenant_name': kwargs.get('os_tenant_name'),
 | 
			
		||||
            'auth_url': kwargs.get('os_auth_url'),
 | 
			
		||||
            'service_type': kwargs.get('os_service_type'),
 | 
			
		||||
            'endpoint_type': kwargs.get('os_endpoint_type'),
 | 
			
		||||
            'insecure': kwargs.get('insecure'),
 | 
			
		||||
        }
 | 
			
		||||
        _ksclient = _get_ksclient(**ks_kwargs)
 | 
			
		||||
        token = (kwargs.get('os_auth_token')
 | 
			
		||||
                 if kwargs.get('os_auth_token')
 | 
			
		||||
                 else _ksclient.auth_token)
 | 
			
		||||
 | 
			
		||||
        ks_kwargs['region_name'] = kwargs.get('os_region_name')
 | 
			
		||||
        endpoint = (kwargs.get('watcher_url') or
 | 
			
		||||
                    _get_endpoint(_ksclient, **ks_kwargs))
 | 
			
		||||
 | 
			
		||||
        auth_ref = _ksclient.auth_ref
 | 
			
		||||
 | 
			
		||||
    else:
 | 
			
		||||
        e = (_('Must provide Keystone credentials or user-defined endpoint '
 | 
			
		||||
               'and token'))
 | 
			
		||||
        raise exc.AmbiguousAuthSystem(e)
 | 
			
		||||
 | 
			
		||||
    cli_kwargs = {
 | 
			
		||||
        'token': token,
 | 
			
		||||
        'insecure': kwargs.get('insecure'),
 | 
			
		||||
        'timeout': kwargs.get('timeout'),
 | 
			
		||||
        'ca_file': kwargs.get('ca_file'),
 | 
			
		||||
        'cert_file': kwargs.get('cert_file'),
 | 
			
		||||
        'key_file': kwargs.get('key_file'),
 | 
			
		||||
        'auth_ref': auth_ref,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return Client(api_version, endpoint, **cli_kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def Client(version, *args, **kwargs):
 | 
			
		||||
    module = utils.import_versioned_module(version, 'client')
 | 
			
		||||
    client_class = getattr(module, 'Client')
 | 
			
		||||
    return client_class(*args, **kwargs)
 | 
			
		||||
							
								
								
									
										0
									
								
								watcherclient/common/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								watcherclient/common/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										146
									
								
								watcherclient/common/base.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								watcherclient/common/base.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,146 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
# Copyright 2012 OpenStack LLC.
 | 
			
		||||
# All Rights Reserved.
 | 
			
		||||
#
 | 
			
		||||
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
			
		||||
#    not use this file except in compliance with the License. You may obtain
 | 
			
		||||
#    a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#         http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
#    Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
#    License for the specific language governing permissions and limitations
 | 
			
		||||
#    under the License.
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
Base utilities to build API operation managers and objects on top of.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import copy
 | 
			
		||||
 | 
			
		||||
import six.moves.urllib.parse as urlparse
 | 
			
		||||
 | 
			
		||||
from watcherclient.openstack.common.apiclient import base
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def getid(obj):
 | 
			
		||||
    """Wrapper to get  object's ID.
 | 
			
		||||
 | 
			
		||||
    Abstracts the common pattern of allowing both an object or an
 | 
			
		||||
    object's ID (UUID) as a parameter when dealing with relationships.
 | 
			
		||||
    """
 | 
			
		||||
    try:
 | 
			
		||||
        return obj.id
 | 
			
		||||
    except AttributeError:
 | 
			
		||||
        return obj
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Manager(object):
 | 
			
		||||
    """Provides  CRUD operations with a particular API."""
 | 
			
		||||
    resource_class = None
 | 
			
		||||
 | 
			
		||||
    def __init__(self, api):
 | 
			
		||||
        self.api = api
 | 
			
		||||
 | 
			
		||||
    def _create(self, url, body):
 | 
			
		||||
        resp, body = self.api.json_request('POST', url, body=body)
 | 
			
		||||
        if body:
 | 
			
		||||
            return self.resource_class(self, body)
 | 
			
		||||
 | 
			
		||||
    def _format_body_data(self, body, response_key):
 | 
			
		||||
        if response_key:
 | 
			
		||||
            try:
 | 
			
		||||
                data = body[response_key]
 | 
			
		||||
            except KeyError:
 | 
			
		||||
                return []
 | 
			
		||||
        else:
 | 
			
		||||
            data = body
 | 
			
		||||
 | 
			
		||||
        if not isinstance(data, list):
 | 
			
		||||
            data = [data]
 | 
			
		||||
 | 
			
		||||
        return data
 | 
			
		||||
 | 
			
		||||
    def _list_pagination(self, url, response_key=None, obj_class=None,
 | 
			
		||||
                         limit=None):
 | 
			
		||||
        """Retrieve a list of items.
 | 
			
		||||
 | 
			
		||||
        The Watcher API is configured to return a maximum number of
 | 
			
		||||
        items per request, (see Watcher's api.max_limit option). This
 | 
			
		||||
        iterates over the 'next' link (pagination) in the responses,
 | 
			
		||||
        to get the number of items specified by 'limit'. If 'limit'
 | 
			
		||||
        is None this function will continue pagination until there are
 | 
			
		||||
        no more values to be returned.
 | 
			
		||||
 | 
			
		||||
        :param url: a partial URL, e.g. '/nodes'
 | 
			
		||||
        :param response_key: the key to be looked up in response
 | 
			
		||||
            dictionary, e.g. 'nodes'
 | 
			
		||||
        :param obj_class: class for constructing the returned objects.
 | 
			
		||||
        :param limit: maximum number of items to return. If None returns
 | 
			
		||||
            everything.
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        if obj_class is None:
 | 
			
		||||
            obj_class = self.resource_class
 | 
			
		||||
 | 
			
		||||
        if limit is not None:
 | 
			
		||||
            limit = int(limit)
 | 
			
		||||
 | 
			
		||||
        object_list = []
 | 
			
		||||
        object_count = 0
 | 
			
		||||
        limit_reached = False
 | 
			
		||||
        while url:
 | 
			
		||||
            resp, body = self.api.json_request('GET', url)
 | 
			
		||||
            data = self._format_body_data(body, response_key)
 | 
			
		||||
            for obj in data:
 | 
			
		||||
                object_list.append(obj_class(self, obj, loaded=True))
 | 
			
		||||
                object_count += 1
 | 
			
		||||
                if limit and object_count >= limit:
 | 
			
		||||
                    # break the for loop
 | 
			
		||||
                    limit_reached = True
 | 
			
		||||
                    break
 | 
			
		||||
 | 
			
		||||
            # break the while loop and return
 | 
			
		||||
            if limit_reached:
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
            url = body.get('next')
 | 
			
		||||
            if url:
 | 
			
		||||
                # NOTE(lucasagomes): We need to edit the URL to remove
 | 
			
		||||
                # the scheme and netloc
 | 
			
		||||
                url_parts = list(urlparse.urlparse(url))
 | 
			
		||||
                url_parts[0] = url_parts[1] = ''
 | 
			
		||||
                url = urlparse.urlunparse(url_parts)
 | 
			
		||||
 | 
			
		||||
        return object_list
 | 
			
		||||
 | 
			
		||||
    def _list(self, url, response_key=None, obj_class=None, body=None):
 | 
			
		||||
        resp, body = self.api.json_request('GET', url)
 | 
			
		||||
 | 
			
		||||
        if obj_class is None:
 | 
			
		||||
            obj_class = self.resource_class
 | 
			
		||||
 | 
			
		||||
        data = self._format_body_data(body, response_key)
 | 
			
		||||
        return [obj_class(self, res, loaded=True) for res in data if res]
 | 
			
		||||
 | 
			
		||||
    def _update(self, url, body, method='PATCH', response_key=None):
 | 
			
		||||
        resp, body = self.api.json_request(method, url, body=body)
 | 
			
		||||
        # PATCH/PUT requests may not return a body
 | 
			
		||||
        if body:
 | 
			
		||||
            return self.resource_class(self, body)
 | 
			
		||||
 | 
			
		||||
    def _delete(self, url):
 | 
			
		||||
        self.api.raw_request('DELETE', url)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Resource(base.Resource):
 | 
			
		||||
    """Represents a particular instance of an object (tenant, user, etc).
 | 
			
		||||
 | 
			
		||||
    This is pretty much just a bag for attributes.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def to_dict(self):
 | 
			
		||||
        return copy.deepcopy(self._info)
 | 
			
		||||
							
								
								
									
										384
									
								
								watcherclient/common/http.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										384
									
								
								watcherclient/common/http.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,384 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
# Copyright 2012 OpenStack LLC.
 | 
			
		||||
# All Rights Reserved.
 | 
			
		||||
#
 | 
			
		||||
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
			
		||||
#    not use this file except in compliance with the License. You may obtain
 | 
			
		||||
#    a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#         http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
#    Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
#    License for the specific language governing permissions and limitations
 | 
			
		||||
#    under the License.
 | 
			
		||||
 | 
			
		||||
import copy
 | 
			
		||||
import json
 | 
			
		||||
import logging
 | 
			
		||||
import os
 | 
			
		||||
import socket
 | 
			
		||||
import ssl
 | 
			
		||||
 | 
			
		||||
from keystoneclient import adapter
 | 
			
		||||
import six
 | 
			
		||||
import six.moves.urllib.parse as urlparse
 | 
			
		||||
 | 
			
		||||
from watcherclient import exceptions as exc
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
LOG = logging.getLogger(__name__)
 | 
			
		||||
USER_AGENT = 'python-watcherclient'
 | 
			
		||||
CHUNKSIZE = 1024 * 64  # 64kB
 | 
			
		||||
 | 
			
		||||
API_VERSION = '/v1'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _trim_endpoint_api_version(url):
 | 
			
		||||
    """Trim API version and trailing slash from endpoint."""
 | 
			
		||||
    return url.rstrip('/').rstrip(API_VERSION)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _extract_error_json(body):
 | 
			
		||||
    """Return  error_message from the HTTP response body."""
 | 
			
		||||
    error_json = {}
 | 
			
		||||
    try:
 | 
			
		||||
        body_json = json.loads(body)
 | 
			
		||||
        if 'error_message' in body_json:
 | 
			
		||||
            raw_msg = body_json['error_message']
 | 
			
		||||
            error_json = json.loads(raw_msg)
 | 
			
		||||
    except ValueError:
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    return error_json
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HTTPClient(object):
 | 
			
		||||
 | 
			
		||||
    def __init__(self, endpoint, **kwargs):
 | 
			
		||||
        self.endpoint = endpoint
 | 
			
		||||
        self.endpoint_trimmed = _trim_endpoint_api_version(endpoint)
 | 
			
		||||
        self.auth_token = kwargs.get('token')
 | 
			
		||||
        self.auth_ref = kwargs.get('auth_ref')
 | 
			
		||||
        self.connection_params = self.get_connection_params(endpoint, **kwargs)
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_connection_params(endpoint, **kwargs):
 | 
			
		||||
        parts = urlparse.urlparse(endpoint)
 | 
			
		||||
 | 
			
		||||
        path = _trim_endpoint_api_version(parts.path)
 | 
			
		||||
 | 
			
		||||
        _args = (parts.hostname, parts.port, path)
 | 
			
		||||
        _kwargs = {'timeout': (float(kwargs.get('timeout'))
 | 
			
		||||
                               if kwargs.get('timeout') else 600)}
 | 
			
		||||
 | 
			
		||||
        if parts.scheme == 'https':
 | 
			
		||||
            _class = VerifiedHTTPSConnection
 | 
			
		||||
            _kwargs['ca_file'] = kwargs.get('ca_file', None)
 | 
			
		||||
            _kwargs['cert_file'] = kwargs.get('cert_file', None)
 | 
			
		||||
            _kwargs['key_file'] = kwargs.get('key_file', None)
 | 
			
		||||
            _kwargs['insecure'] = kwargs.get('insecure', False)
 | 
			
		||||
        elif parts.scheme == 'http':
 | 
			
		||||
            _class = six.moves.http_client.HTTPConnection
 | 
			
		||||
        else:
 | 
			
		||||
            msg = 'Unsupported scheme: %s' % parts.scheme
 | 
			
		||||
            raise exc.EndpointException(msg)
 | 
			
		||||
 | 
			
		||||
        return (_class, _args, _kwargs)
 | 
			
		||||
 | 
			
		||||
    def get_connection(self):
 | 
			
		||||
        _class = self.connection_params[0]
 | 
			
		||||
        try:
 | 
			
		||||
            return _class(*self.connection_params[1][0:2],
 | 
			
		||||
                          **self.connection_params[2])
 | 
			
		||||
        except six.moves.http_client.InvalidURL:
 | 
			
		||||
            raise exc.EndpointException()
 | 
			
		||||
 | 
			
		||||
    def log_curl_request(self, method, url, kwargs):
 | 
			
		||||
        curl = ['curl -i -X %s' % method]
 | 
			
		||||
 | 
			
		||||
        for (key, value) in kwargs['headers'].items():
 | 
			
		||||
            header = '-H \'%s: %s\'' % (key, value)
 | 
			
		||||
            curl.append(header)
 | 
			
		||||
 | 
			
		||||
        conn_params_fmt = [
 | 
			
		||||
            ('key_file', '--key %s'),
 | 
			
		||||
            ('cert_file', '--cert %s'),
 | 
			
		||||
            ('ca_file', '--cacert %s'),
 | 
			
		||||
        ]
 | 
			
		||||
        for (key, fmt) in conn_params_fmt:
 | 
			
		||||
            value = self.connection_params[2].get(key)
 | 
			
		||||
            if value:
 | 
			
		||||
                curl.append(fmt % value)
 | 
			
		||||
 | 
			
		||||
        if self.connection_params[2].get('insecure'):
 | 
			
		||||
            curl.append('-k')
 | 
			
		||||
 | 
			
		||||
        if 'body' in kwargs:
 | 
			
		||||
            curl.append('-d \'%s\'' % kwargs['body'])
 | 
			
		||||
 | 
			
		||||
        curl.append(urlparse.urljoin(self.endpoint_trimmed, url))
 | 
			
		||||
        LOG.debug(' '.join(curl))
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def log_http_response(resp, body=None):
 | 
			
		||||
        status = (resp.version / 10.0, resp.status, resp.reason)
 | 
			
		||||
        dump = ['\nHTTP/%.1f %s %s' % status]
 | 
			
		||||
        dump.extend(['%s: %s' % (k, v) for k, v in resp.getheaders()])
 | 
			
		||||
        dump.append('')
 | 
			
		||||
        if body:
 | 
			
		||||
            dump.extend([body, ''])
 | 
			
		||||
        LOG.debug('\n'.join(dump))
 | 
			
		||||
 | 
			
		||||
    def _make_connection_url(self, url):
 | 
			
		||||
        (_class, _args, _kwargs) = self.connection_params
 | 
			
		||||
        base_url = _args[2]
 | 
			
		||||
        return '%s/%s' % (base_url, url.lstrip('/'))
 | 
			
		||||
 | 
			
		||||
    def _http_request(self, url, method, **kwargs):
 | 
			
		||||
        """Send an http request with the specified characteristics.
 | 
			
		||||
 | 
			
		||||
        Wrapper around httplib.HTTP(S)Connection.request to handle tasks such
 | 
			
		||||
        as setting headers and error handling.
 | 
			
		||||
        """
 | 
			
		||||
        # Copy the kwargs so we can reuse the original in case of redirects
 | 
			
		||||
        kwargs['headers'] = copy.deepcopy(kwargs.get('headers', {}))
 | 
			
		||||
        kwargs['headers'].setdefault('User-Agent', USER_AGENT)
 | 
			
		||||
        if self.auth_token:
 | 
			
		||||
            kwargs['headers'].setdefault('X-Auth-Token', self.auth_token)
 | 
			
		||||
 | 
			
		||||
        self.log_curl_request(method, url, kwargs)
 | 
			
		||||
        conn = self.get_connection()
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            conn_url = self._make_connection_url(url)
 | 
			
		||||
            conn.request(method, conn_url, **kwargs)
 | 
			
		||||
            resp = conn.getresponse()
 | 
			
		||||
        except socket.gaierror as e:
 | 
			
		||||
            message = ("Error finding address for %(url)s: %(e)s"
 | 
			
		||||
                       % dict(url=url, e=e))
 | 
			
		||||
            raise exc.EndpointNotFound(message)
 | 
			
		||||
        except (socket.error, socket.timeout) as e:
 | 
			
		||||
            endpoint = self.endpoint
 | 
			
		||||
            message = ("Error communicating with %(endpoint)s %(e)s"
 | 
			
		||||
                       % dict(endpoint=endpoint, e=e))
 | 
			
		||||
            raise exc.ConnectionRefused(message)
 | 
			
		||||
 | 
			
		||||
        body_iter = ResponseBodyIterator(resp)
 | 
			
		||||
 | 
			
		||||
        # Read body into string if it isn't obviously image data
 | 
			
		||||
        body_str = None
 | 
			
		||||
        if resp.getheader('content-type', None) != 'application/octet-stream':
 | 
			
		||||
            body_str = ''.join([chunk for chunk in body_iter])
 | 
			
		||||
            self.log_http_response(resp, body_str)
 | 
			
		||||
            body_iter = six.StringIO(body_str)
 | 
			
		||||
        else:
 | 
			
		||||
            self.log_http_response(resp)
 | 
			
		||||
 | 
			
		||||
        if 400 <= resp.status < 600:
 | 
			
		||||
            LOG.warn("Request returned failure status.")
 | 
			
		||||
            error_json = _extract_error_json(body_str)
 | 
			
		||||
            raise exc.from_response(
 | 
			
		||||
                resp, error_json.get('faultstring'),
 | 
			
		||||
                error_json.get('debuginfo'), method, url)
 | 
			
		||||
        elif resp.status in (301, 302, 305):
 | 
			
		||||
            # Redirected. Reissue the request to the new location.
 | 
			
		||||
            return self._http_request(resp['location'], method, **kwargs)
 | 
			
		||||
        elif resp.status == 300:
 | 
			
		||||
            raise exc.from_response(resp, method=method, url=url)
 | 
			
		||||
 | 
			
		||||
        return resp, body_iter
 | 
			
		||||
 | 
			
		||||
    def json_request(self, method, url, **kwargs):
 | 
			
		||||
        kwargs.setdefault('headers', {})
 | 
			
		||||
        kwargs['headers'].setdefault('Content-Type', 'application/json')
 | 
			
		||||
        kwargs['headers'].setdefault('Accept', 'application/json')
 | 
			
		||||
 | 
			
		||||
        if 'body' in kwargs:
 | 
			
		||||
            kwargs['body'] = json.dumps(kwargs['body'])
 | 
			
		||||
 | 
			
		||||
        resp, body_iter = self._http_request(url, method, **kwargs)
 | 
			
		||||
        content_type = resp.getheader('content-type', None)
 | 
			
		||||
 | 
			
		||||
        if resp.status == 204 or resp.status == 205 or content_type is None:
 | 
			
		||||
            return resp, list()
 | 
			
		||||
 | 
			
		||||
        if 'application/json' in content_type:
 | 
			
		||||
            body = ''.join([chunk for chunk in body_iter])
 | 
			
		||||
            try:
 | 
			
		||||
                body = json.loads(body)
 | 
			
		||||
            except ValueError:
 | 
			
		||||
                LOG.error('Could not decode response body as JSON')
 | 
			
		||||
        else:
 | 
			
		||||
            body = None
 | 
			
		||||
 | 
			
		||||
        return resp, body
 | 
			
		||||
 | 
			
		||||
    def raw_request(self, method, url, **kwargs):
 | 
			
		||||
        kwargs.setdefault('headers', {})
 | 
			
		||||
        kwargs['headers'].setdefault('Content-Type',
 | 
			
		||||
                                     'application/octet-stream')
 | 
			
		||||
        return self._http_request(url, method, **kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class VerifiedHTTPSConnection(six.moves.http_client.HTTPSConnection):
 | 
			
		||||
    """httplib-compatibile connection using client-side SSL authentication
 | 
			
		||||
 | 
			
		||||
    :see http://code.activestate.com/recipes/
 | 
			
		||||
            577548-https-httplib-client-connection-with-certificate-v/
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, host, port, key_file=None, cert_file=None,
 | 
			
		||||
                 ca_file=None, timeout=None, insecure=False):
 | 
			
		||||
        six.moves.http_client.HTTPSConnection.__init__(
 | 
			
		||||
            self, host, port,
 | 
			
		||||
            key_file=key_file,
 | 
			
		||||
            cert_file=cert_file)
 | 
			
		||||
        self.key_file = key_file
 | 
			
		||||
        self.cert_file = cert_file
 | 
			
		||||
        if ca_file is not None:
 | 
			
		||||
            self.ca_file = ca_file
 | 
			
		||||
        else:
 | 
			
		||||
            self.ca_file = self.get_system_ca_file()
 | 
			
		||||
        self.timeout = timeout
 | 
			
		||||
        self.insecure = insecure
 | 
			
		||||
 | 
			
		||||
    def connect(self):
 | 
			
		||||
        """Connect to a host on a given (SSL) port.
 | 
			
		||||
 | 
			
		||||
        If ca_file is pointing somewhere, use it to check Server Certificate.
 | 
			
		||||
 | 
			
		||||
        Redefined/copied and extended from httplib.py:1105 (Python 2.6.x).
 | 
			
		||||
        This is needed to pass cert_reqs=ssl.CERT_REQUIRED as parameter to
 | 
			
		||||
        ssl.wrap_socket(), which forces SSL to check server certificate against
 | 
			
		||||
        our client certificate.
 | 
			
		||||
        """
 | 
			
		||||
        sock = socket.create_connection((self.host, self.port), self.timeout)
 | 
			
		||||
 | 
			
		||||
        if self._tunnel_host:
 | 
			
		||||
            self.sock = sock
 | 
			
		||||
            self._tunnel()
 | 
			
		||||
 | 
			
		||||
        if self.insecure is True:
 | 
			
		||||
            kwargs = {'cert_reqs': ssl.CERT_NONE}
 | 
			
		||||
        else:
 | 
			
		||||
            kwargs = {'cert_reqs': ssl.CERT_REQUIRED, 'ca_certs': self.ca_file}
 | 
			
		||||
 | 
			
		||||
        if self.cert_file:
 | 
			
		||||
            kwargs['certfile'] = self.cert_file
 | 
			
		||||
            if self.key_file:
 | 
			
		||||
                kwargs['keyfile'] = self.key_file
 | 
			
		||||
 | 
			
		||||
        self.sock = ssl.wrap_socket(sock, **kwargs)
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_system_ca_file():
 | 
			
		||||
        """Return path to system default CA file."""
 | 
			
		||||
        # Standard CA file locations for Debian/Ubuntu, RedHat/Fedora,
 | 
			
		||||
        # Suse, FreeBSD/OpenBSD
 | 
			
		||||
        ca_path = ['/etc/ssl/certs/ca-certificates.crt',
 | 
			
		||||
                   '/etc/pki/tls/certs/ca-bundle.crt',
 | 
			
		||||
                   '/etc/ssl/ca-bundle.pem',
 | 
			
		||||
                   '/etc/ssl/cert.pem']
 | 
			
		||||
        for ca in ca_path:
 | 
			
		||||
            if os.path.exists(ca):
 | 
			
		||||
                return ca
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SessionClient(adapter.LegacyJsonAdapter):
 | 
			
		||||
    """HTTP client based on Keystone client session."""
 | 
			
		||||
 | 
			
		||||
    def _http_request(self, url, method, **kwargs):
 | 
			
		||||
        kwargs.setdefault('user_agent', USER_AGENT)
 | 
			
		||||
        kwargs.setdefault('auth', self.auth)
 | 
			
		||||
 | 
			
		||||
        endpoint_filter = kwargs.setdefault('endpoint_filter', {})
 | 
			
		||||
        endpoint_filter.setdefault('interface', self.interface)
 | 
			
		||||
        endpoint_filter.setdefault('service_type', self.service_type)
 | 
			
		||||
        endpoint_filter.setdefault('region_name', self.region_name)
 | 
			
		||||
 | 
			
		||||
        resp = self.session.request(url, method,
 | 
			
		||||
                                    raise_exc=False, **kwargs)
 | 
			
		||||
 | 
			
		||||
        if 400 <= resp.status_code < 600:
 | 
			
		||||
            error_json = _extract_error_json(resp.content)
 | 
			
		||||
            raise exc.from_response(resp, error_json.get(
 | 
			
		||||
                'faultstring'),
 | 
			
		||||
                error_json.get('debuginfo'), method, url)
 | 
			
		||||
        elif resp.status_code in (301, 302, 305):
 | 
			
		||||
            # Redirected. Reissue the request to the new location.
 | 
			
		||||
            location = resp.headers.get('location')
 | 
			
		||||
            resp = self._http_request(location, method, **kwargs)
 | 
			
		||||
        elif resp.status_code == 300:
 | 
			
		||||
            raise exc.from_response(resp, method=method, url=url)
 | 
			
		||||
        return resp
 | 
			
		||||
 | 
			
		||||
    def json_request(self, method, url, **kwargs):
 | 
			
		||||
        kwargs.setdefault('headers', {})
 | 
			
		||||
        kwargs['headers'].setdefault('Content-Type', 'application/json')
 | 
			
		||||
        kwargs['headers'].setdefault('Accept', 'application/json')
 | 
			
		||||
 | 
			
		||||
        if 'body' in kwargs:
 | 
			
		||||
            kwargs['data'] = json.dumps(kwargs.pop('body'))
 | 
			
		||||
 | 
			
		||||
        resp = self._http_request(url, method, **kwargs)
 | 
			
		||||
        body = resp.content
 | 
			
		||||
        content_type = resp.headers.get('content-type', None)
 | 
			
		||||
        status = resp.status_code
 | 
			
		||||
        if status == 204 or status == 205 or content_type is None:
 | 
			
		||||
            return resp, list()
 | 
			
		||||
        if 'application/json' in content_type:
 | 
			
		||||
            try:
 | 
			
		||||
                body = resp.json()
 | 
			
		||||
            except ValueError:
 | 
			
		||||
                LOG.error('Could not decode response body as JSON')
 | 
			
		||||
        else:
 | 
			
		||||
            body = None
 | 
			
		||||
 | 
			
		||||
        return resp, body
 | 
			
		||||
 | 
			
		||||
    def raw_request(self, method, url, **kwargs):
 | 
			
		||||
        kwargs.setdefault('headers', {})
 | 
			
		||||
        kwargs['headers'].setdefault('Content-Type',
 | 
			
		||||
                                     'application/octet-stream')
 | 
			
		||||
        return self._http_request(url, method, **kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ResponseBodyIterator(object):
 | 
			
		||||
    """A class that acts as an iterator over an HTTP response."""
 | 
			
		||||
 | 
			
		||||
    def __init__(self, resp):
 | 
			
		||||
        self.resp = resp
 | 
			
		||||
 | 
			
		||||
    def __iter__(self):
 | 
			
		||||
        while True:
 | 
			
		||||
            yield self.next()
 | 
			
		||||
 | 
			
		||||
    def next(self):
 | 
			
		||||
        chunk = self.resp.read(CHUNKSIZE)
 | 
			
		||||
        if chunk:
 | 
			
		||||
            return chunk
 | 
			
		||||
        else:
 | 
			
		||||
            raise StopIteration()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _construct_http_client(*args, **kwargs):
 | 
			
		||||
    session = kwargs.pop('session', None)
 | 
			
		||||
    auth = kwargs.pop('auth', None)
 | 
			
		||||
 | 
			
		||||
    if session:
 | 
			
		||||
        service_type = kwargs.pop('service_type', 'infra-optim')
 | 
			
		||||
        interface = kwargs.pop('endpoint_type', None)
 | 
			
		||||
        region_name = kwargs.pop('region_name', None)
 | 
			
		||||
        return SessionClient(session=session,
 | 
			
		||||
                             auth=auth,
 | 
			
		||||
                             interface=interface,
 | 
			
		||||
                             service_type=service_type,
 | 
			
		||||
                             region_name=region_name,
 | 
			
		||||
                             service_name=None,
 | 
			
		||||
                             user_agent='python-watcherclient')
 | 
			
		||||
    else:
 | 
			
		||||
        return HTTPClient(*args, **kwargs)
 | 
			
		||||
							
								
								
									
										179
									
								
								watcherclient/common/utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										179
									
								
								watcherclient/common/utils.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,179 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
# Copyright 2012 OpenStack LLC.
 | 
			
		||||
# All Rights Reserved.
 | 
			
		||||
#
 | 
			
		||||
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
			
		||||
#    not use this file except in compliance with the License. You may obtain
 | 
			
		||||
#    a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#         http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
#    Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
#    License for the specific language governing permissions and limitations
 | 
			
		||||
#    under the License.
 | 
			
		||||
 | 
			
		||||
from __future__ import print_function
 | 
			
		||||
 | 
			
		||||
import argparse
 | 
			
		||||
import json
 | 
			
		||||
 | 
			
		||||
from watcherclient import exceptions as exc
 | 
			
		||||
from watcherclient.openstack.common._i18n import _
 | 
			
		||||
from watcherclient.openstack.common import importutils
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HelpFormatter(argparse.HelpFormatter):
 | 
			
		||||
    def start_section(self, heading):
 | 
			
		||||
        # Title-case the headings
 | 
			
		||||
        heading = '%s%s' % (heading[0].upper(), heading[1:])
 | 
			
		||||
        super(HelpFormatter, self).start_section(heading)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def define_command(subparsers, command, callback, cmd_mapper):
 | 
			
		||||
    '''Define a command in the subparsers collection.
 | 
			
		||||
 | 
			
		||||
    :param subparsers: subparsers collection where the command will go
 | 
			
		||||
    :param command: command name
 | 
			
		||||
    :param callback: function that will be used to process the command
 | 
			
		||||
    '''
 | 
			
		||||
    desc = callback.__doc__ or ''
 | 
			
		||||
    help = desc.strip().split('\n')[0]
 | 
			
		||||
    arguments = getattr(callback, 'arguments', [])
 | 
			
		||||
 | 
			
		||||
    subparser = subparsers.add_parser(command, help=help,
 | 
			
		||||
                                      description=desc,
 | 
			
		||||
                                      add_help=False,
 | 
			
		||||
                                      formatter_class=HelpFormatter)
 | 
			
		||||
    subparser.add_argument('-h', '--help', action='help',
 | 
			
		||||
                           help=argparse.SUPPRESS)
 | 
			
		||||
    cmd_mapper[command] = subparser
 | 
			
		||||
    for (args, kwargs) in arguments:
 | 
			
		||||
        subparser.add_argument(*args, **kwargs)
 | 
			
		||||
    subparser.set_defaults(func=callback)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def define_commands_from_module(subparsers, command_module, cmd_mapper):
 | 
			
		||||
    """Add *do_* methods in a module and add as commands into a subparsers."""
 | 
			
		||||
 | 
			
		||||
    for method_name in (a for a in dir(command_module) if a.startswith('do_')):
 | 
			
		||||
        # Commands should be hypen-separated instead of underscores.
 | 
			
		||||
        command = method_name[3:].replace('_', '-')
 | 
			
		||||
        callback = getattr(command_module, method_name)
 | 
			
		||||
        define_command(subparsers, command, callback, cmd_mapper)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def import_versioned_module(version, submodule=None):
 | 
			
		||||
    module = 'watcherclient.v%s' % version
 | 
			
		||||
    if submodule:
 | 
			
		||||
        module = '.'.join((module, submodule))
 | 
			
		||||
    return importutils.import_module(module)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def split_and_deserialize(string):
 | 
			
		||||
    """Split and try to JSON deserialize a string.
 | 
			
		||||
 | 
			
		||||
    Gets a string with the KEY=VALUE format, split it (using '=' as the
 | 
			
		||||
    separator) and try to JSON deserialize the VALUE.
 | 
			
		||||
 | 
			
		||||
    :returns: A tuple of (key, value).
 | 
			
		||||
    """
 | 
			
		||||
    try:
 | 
			
		||||
        key, value = string.split("=", 1)
 | 
			
		||||
    except ValueError:
 | 
			
		||||
        raise exc.CommandError(_('Attributes must be a list of '
 | 
			
		||||
                                 'PATH=VALUE not "%s"') % string)
 | 
			
		||||
    try:
 | 
			
		||||
        value = json.loads(value)
 | 
			
		||||
    except ValueError:
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    return (key, value)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def args_array_to_dict(kwargs, key_to_convert):
 | 
			
		||||
    values_to_convert = kwargs.get(key_to_convert)
 | 
			
		||||
    if values_to_convert:
 | 
			
		||||
        kwargs[key_to_convert] = dict(split_and_deserialize(v)
 | 
			
		||||
                                      for v in values_to_convert)
 | 
			
		||||
    return kwargs
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def args_array_to_patch(op, attributes):
 | 
			
		||||
    patch = []
 | 
			
		||||
    for attr in attributes:
 | 
			
		||||
        # Sanitize
 | 
			
		||||
        if not attr.startswith('/'):
 | 
			
		||||
            attr = '/' + attr
 | 
			
		||||
 | 
			
		||||
        if op in ['add', 'replace']:
 | 
			
		||||
            path, value = split_and_deserialize(attr)
 | 
			
		||||
            patch.append({'op': op, 'path': path, 'value': value})
 | 
			
		||||
 | 
			
		||||
        elif op == "remove":
 | 
			
		||||
            # For remove only the key is needed
 | 
			
		||||
            patch.append({'op': op, 'path': attr})
 | 
			
		||||
        else:
 | 
			
		||||
            raise exc.CommandError(_('Unknown PATCH operation: %s') % op)
 | 
			
		||||
    return patch
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def common_params_for_list(args, fields, field_labels):
 | 
			
		||||
    """Generate 'params' dict that is common for every 'list' command.
 | 
			
		||||
 | 
			
		||||
    :param args: arguments from command line.
 | 
			
		||||
    :param fields: possible fields for sorting.
 | 
			
		||||
    :param field_labels: possible field labels for sorting.
 | 
			
		||||
    :returns: a dict with params to pass to the client method.
 | 
			
		||||
    """
 | 
			
		||||
    params = {}
 | 
			
		||||
    if args.limit is not None:
 | 
			
		||||
        if args.limit < 0:
 | 
			
		||||
            raise exc.CommandError(
 | 
			
		||||
                _('Expected non-negative --limit, got %s') % args.limit)
 | 
			
		||||
        params['limit'] = args.limit
 | 
			
		||||
 | 
			
		||||
    if args.sort_key is not None:
 | 
			
		||||
        # Support using both heading and field name for sort_key
 | 
			
		||||
        fields_map = dict(zip(field_labels, fields))
 | 
			
		||||
        fields_map.update(zip(fields, fields))
 | 
			
		||||
        try:
 | 
			
		||||
            sort_key = fields_map[args.sort_key]
 | 
			
		||||
        except KeyError:
 | 
			
		||||
            raise exc.CommandError(
 | 
			
		||||
                _("%(sort_key)s is an invalid field for sorting, "
 | 
			
		||||
                  "valid values for --sort-key are: %(valid)s") %
 | 
			
		||||
                {'sort_key': args.sort_key,
 | 
			
		||||
                 'valid': list(fields_map)})
 | 
			
		||||
        params['sort_key'] = sort_key
 | 
			
		||||
    if args.sort_dir is not None:
 | 
			
		||||
        if args.sort_dir not in ('asc', 'desc'):
 | 
			
		||||
            raise exc.CommandError(
 | 
			
		||||
                _("%s is an invalid value for sort direction, "
 | 
			
		||||
                  "valid values for --sort-dir are: 'asc', 'desc'") %
 | 
			
		||||
                args.sort_dir)
 | 
			
		||||
        params['sort_dir'] = args.sort_dir
 | 
			
		||||
 | 
			
		||||
    params['detail'] = args.detail
 | 
			
		||||
 | 
			
		||||
    return params
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def common_filters(limit=None, sort_key=None, sort_dir=None):
 | 
			
		||||
    """Generate common filters for any list request.
 | 
			
		||||
 | 
			
		||||
    :param limit: maximum number of entities to return.
 | 
			
		||||
    :param sort_key: field to use for sorting.
 | 
			
		||||
    :param sort_dir: direction of sorting: 'asc' or 'desc'.
 | 
			
		||||
    :returns: list of string filters.
 | 
			
		||||
    """
 | 
			
		||||
    filters = []
 | 
			
		||||
    if isinstance(limit, int) and limit > 0:
 | 
			
		||||
        filters.append('limit=%s' % limit)
 | 
			
		||||
    if sort_key is not None:
 | 
			
		||||
        filters.append('sort_key=%s' % sort_key)
 | 
			
		||||
    if sort_dir is not None:
 | 
			
		||||
        filters.append('sort_dir=%s' % sort_dir)
 | 
			
		||||
    return filters
 | 
			
		||||
							
								
								
									
										75
									
								
								watcherclient/exceptions.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								watcherclient/exceptions.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,75 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
			
		||||
#    not use this file except in compliance with the License. You may obtain
 | 
			
		||||
#    a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#         http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
#    Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
#    License for the specific language governing permissions and limitations
 | 
			
		||||
#    under the License.
 | 
			
		||||
 | 
			
		||||
from watcherclient.openstack.common.apiclient import exceptions
 | 
			
		||||
from watcherclient.openstack.common.apiclient.exceptions import *  # noqa
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# NOTE(akurilin): This alias is left here since v.0.1.3 to support backwards
 | 
			
		||||
# compatibility.
 | 
			
		||||
InvalidEndpoint = EndpointException
 | 
			
		||||
CommunicationError = ConnectionRefused
 | 
			
		||||
HTTPBadRequest = BadRequest
 | 
			
		||||
HTTPInternalServerError = InternalServerError
 | 
			
		||||
HTTPNotFound = NotFound
 | 
			
		||||
HTTPServiceUnavailable = ServiceUnavailable
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AmbiguousAuthSystem(ClientException):
 | 
			
		||||
    """Could not obtain token and endpoint using provided credentials."""
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
# Alias for backwards compatibility
 | 
			
		||||
AmbigiousAuthSystem = AmbiguousAuthSystem
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class InvalidAttribute(ClientException):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def from_response(response, message=None, traceback=None, method=None,
 | 
			
		||||
                  url=None):
 | 
			
		||||
    """Return an HttpError instance based on response from httplib/requests."""
 | 
			
		||||
 | 
			
		||||
    error_body = {}
 | 
			
		||||
    if message:
 | 
			
		||||
        error_body['message'] = message
 | 
			
		||||
    if traceback:
 | 
			
		||||
        error_body['details'] = traceback
 | 
			
		||||
 | 
			
		||||
    if hasattr(response, 'status') and not hasattr(response, 'status_code'):
 | 
			
		||||
        # NOTE(akurilin): These modifications around response object give
 | 
			
		||||
        # ability to get all necessary information in method `from_response`
 | 
			
		||||
        # from common code, which expecting response object from `requests`
 | 
			
		||||
        # library instead of object from `httplib/httplib2` library.
 | 
			
		||||
        response.status_code = response.status
 | 
			
		||||
        response.headers = {
 | 
			
		||||
            'Content-Type': response.getheader('content-type', "")}
 | 
			
		||||
 | 
			
		||||
    if hasattr(response, 'status_code'):
 | 
			
		||||
        # NOTE(jiangfei): These modifications allow SessionClient
 | 
			
		||||
        # to handle faultstring.
 | 
			
		||||
        response.json = lambda: {'error': error_body}
 | 
			
		||||
 | 
			
		||||
    if (response.headers['Content-Type'].startswith('text/') and
 | 
			
		||||
            not hasattr(response, 'text')):
 | 
			
		||||
        # NOTE(clif_h): There seems to be a case in the
 | 
			
		||||
        # openstack.common.apiclient.exceptions module where if the
 | 
			
		||||
        # content-type of the response is text/* then it expects
 | 
			
		||||
        # the response to have a 'text' attribute, but that
 | 
			
		||||
        # doesn't always seem to necessarily be the case.
 | 
			
		||||
        # This is to work around that problem.
 | 
			
		||||
        response.text = ''
 | 
			
		||||
 | 
			
		||||
    return exceptions.from_response(response, message, url)
 | 
			
		||||
							
								
								
									
										0
									
								
								watcherclient/openstack/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								watcherclient/openstack/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										0
									
								
								watcherclient/openstack/common/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								watcherclient/openstack/common/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										45
									
								
								watcherclient/openstack/common/_i18n.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								watcherclient/openstack/common/_i18n.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
			
		||||
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
			
		||||
#    not use this file except in compliance with the License. You may obtain
 | 
			
		||||
#    a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#         http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
#    Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
#    License for the specific language governing permissions and limitations
 | 
			
		||||
#    under the License.
 | 
			
		||||
 | 
			
		||||
"""oslo.i18n integration module.
 | 
			
		||||
 | 
			
		||||
See http://docs.openstack.org/developer/oslo.i18n/usage.html
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
try:
 | 
			
		||||
    import oslo_i18n
 | 
			
		||||
 | 
			
		||||
    # NOTE(dhellmann): This reference to o-s-l-o will be replaced by the
 | 
			
		||||
    # application name when this module is synced into the separate
 | 
			
		||||
    # repository. It is OK to have more than one translation function
 | 
			
		||||
    # using the same domain, since there will still only be one message
 | 
			
		||||
    # catalog.
 | 
			
		||||
    _translators = oslo_i18n.TranslatorFactory(domain='watcherclient')
 | 
			
		||||
 | 
			
		||||
    # The primary translation function using the well-known name "_"
 | 
			
		||||
    _ = _translators.primary
 | 
			
		||||
 | 
			
		||||
    # Translators for log levels.
 | 
			
		||||
    #
 | 
			
		||||
    # The abbreviated names are meant to reflect the usual use of a short
 | 
			
		||||
    # name like '_'. The "L" is for "log" and the other letter comes from
 | 
			
		||||
    # the level.
 | 
			
		||||
    _LI = _translators.log_info
 | 
			
		||||
    _LW = _translators.log_warning
 | 
			
		||||
    _LE = _translators.log_error
 | 
			
		||||
    _LC = _translators.log_critical
 | 
			
		||||
except ImportError:
 | 
			
		||||
    # NOTE(dims): Support for cases where a project wants to use
 | 
			
		||||
    # code from oslo-incubator, but is not ready to be internationalized
 | 
			
		||||
    # (like tempest)
 | 
			
		||||
    _ = _LI = _LW = _LE = _LC = lambda x: x
 | 
			
		||||
							
								
								
									
										234
									
								
								watcherclient/openstack/common/apiclient/auth.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										234
									
								
								watcherclient/openstack/common/apiclient/auth.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,234 @@
 | 
			
		||||
# Copyright 2013 OpenStack Foundation
 | 
			
		||||
# Copyright 2013 Spanish National Research Council.
 | 
			
		||||
# 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.
 | 
			
		||||
 | 
			
		||||
# E0202: An attribute inherited from %s hide this method
 | 
			
		||||
# pylint: disable=E0202
 | 
			
		||||
 | 
			
		||||
########################################################################
 | 
			
		||||
#
 | 
			
		||||
# THIS MODULE IS DEPRECATED
 | 
			
		||||
#
 | 
			
		||||
# Please refer to
 | 
			
		||||
# https://etherpad.openstack.org/p/kilo-watcherclient-library-proposals for
 | 
			
		||||
# the discussion leading to this deprecation.
 | 
			
		||||
#
 | 
			
		||||
# We recommend checking out the python-openstacksdk project
 | 
			
		||||
# (https://launchpad.net/python-openstacksdk) instead.
 | 
			
		||||
#
 | 
			
		||||
########################################################################
 | 
			
		||||
 | 
			
		||||
import abc
 | 
			
		||||
import argparse
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
import six
 | 
			
		||||
from stevedore import extension
 | 
			
		||||
 | 
			
		||||
from watcherclient.openstack.common.apiclient import exceptions
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
_discovered_plugins = {}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def discover_auth_systems():
 | 
			
		||||
    """Discover the available auth-systems.
 | 
			
		||||
 | 
			
		||||
    This won't take into account the old style auth-systems.
 | 
			
		||||
    """
 | 
			
		||||
    global _discovered_plugins
 | 
			
		||||
    _discovered_plugins = {}
 | 
			
		||||
 | 
			
		||||
    def add_plugin(ext):
 | 
			
		||||
        _discovered_plugins[ext.name] = ext.plugin
 | 
			
		||||
 | 
			
		||||
    ep_namespace = "watcherclient.openstack.common.apiclient.auth"
 | 
			
		||||
    mgr = extension.ExtensionManager(ep_namespace)
 | 
			
		||||
    mgr.map(add_plugin)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def load_auth_system_opts(parser):
 | 
			
		||||
    """Load options needed by the available auth-systems into a parser.
 | 
			
		||||
 | 
			
		||||
    This function will try to populate the parser with options from the
 | 
			
		||||
    available plugins.
 | 
			
		||||
    """
 | 
			
		||||
    group = parser.add_argument_group("Common auth options")
 | 
			
		||||
    BaseAuthPlugin.add_common_opts(group)
 | 
			
		||||
    for name, auth_plugin in six.iteritems(_discovered_plugins):
 | 
			
		||||
        group = parser.add_argument_group(
 | 
			
		||||
            "Auth-system '%s' options" % name,
 | 
			
		||||
            conflict_handler="resolve")
 | 
			
		||||
        auth_plugin.add_opts(group)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def load_plugin(auth_system):
 | 
			
		||||
    try:
 | 
			
		||||
        plugin_class = _discovered_plugins[auth_system]
 | 
			
		||||
    except KeyError:
 | 
			
		||||
        raise exceptions.AuthSystemNotFound(auth_system)
 | 
			
		||||
    return plugin_class(auth_system=auth_system)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def load_plugin_from_args(args):
 | 
			
		||||
    """Load required plugin and populate it with options.
 | 
			
		||||
 | 
			
		||||
    Try to guess auth system if it is not specified. Systems are tried in
 | 
			
		||||
    alphabetical order.
 | 
			
		||||
 | 
			
		||||
    :type args: argparse.Namespace
 | 
			
		||||
    :raises: AuthPluginOptionsMissing
 | 
			
		||||
    """
 | 
			
		||||
    auth_system = args.os_auth_system
 | 
			
		||||
    if auth_system:
 | 
			
		||||
        plugin = load_plugin(auth_system)
 | 
			
		||||
        plugin.parse_opts(args)
 | 
			
		||||
        plugin.sufficient_options()
 | 
			
		||||
        return plugin
 | 
			
		||||
 | 
			
		||||
    for plugin_auth_system in sorted(six.iterkeys(_discovered_plugins)):
 | 
			
		||||
        plugin_class = _discovered_plugins[plugin_auth_system]
 | 
			
		||||
        plugin = plugin_class()
 | 
			
		||||
        plugin.parse_opts(args)
 | 
			
		||||
        try:
 | 
			
		||||
            plugin.sufficient_options()
 | 
			
		||||
        except exceptions.AuthPluginOptionsMissing:
 | 
			
		||||
            continue
 | 
			
		||||
        return plugin
 | 
			
		||||
    raise exceptions.AuthPluginOptionsMissing(["auth_system"])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@six.add_metaclass(abc.ABCMeta)
 | 
			
		||||
class BaseAuthPlugin(object):
 | 
			
		||||
    """Base class for authentication plugins.
 | 
			
		||||
 | 
			
		||||
    An authentication plugin needs to override at least the authenticate
 | 
			
		||||
    method to be a valid plugin.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    auth_system = None
 | 
			
		||||
    opt_names = []
 | 
			
		||||
    common_opt_names = [
 | 
			
		||||
        "auth_system",
 | 
			
		||||
        "username",
 | 
			
		||||
        "password",
 | 
			
		||||
        "tenant_name",
 | 
			
		||||
        "token",
 | 
			
		||||
        "auth_url",
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    def __init__(self, auth_system=None, **kwargs):
 | 
			
		||||
        self.auth_system = auth_system or self.auth_system
 | 
			
		||||
        self.opts = dict((name, kwargs.get(name))
 | 
			
		||||
                         for name in self.opt_names)
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def _parser_add_opt(parser, opt):
 | 
			
		||||
        """Add an option to parser in two variants.
 | 
			
		||||
 | 
			
		||||
        :param opt: option name (with underscores)
 | 
			
		||||
        """
 | 
			
		||||
        dashed_opt = opt.replace("_", "-")
 | 
			
		||||
        env_var = "OS_%s" % opt.upper()
 | 
			
		||||
        arg_default = os.environ.get(env_var, "")
 | 
			
		||||
        arg_help = "Defaults to env[%s]." % env_var
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            "--os-%s" % dashed_opt,
 | 
			
		||||
            metavar="<%s>" % dashed_opt,
 | 
			
		||||
            default=arg_default,
 | 
			
		||||
            help=arg_help)
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            "--os_%s" % opt,
 | 
			
		||||
            metavar="<%s>" % dashed_opt,
 | 
			
		||||
            help=argparse.SUPPRESS)
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def add_opts(cls, parser):
 | 
			
		||||
        """Populate the parser with the options for this plugin.
 | 
			
		||||
        """
 | 
			
		||||
        for opt in cls.opt_names:
 | 
			
		||||
            # use `BaseAuthPlugin.common_opt_names` since it is never
 | 
			
		||||
            # changed in child classes
 | 
			
		||||
            if opt not in BaseAuthPlugin.common_opt_names:
 | 
			
		||||
                cls._parser_add_opt(parser, opt)
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def add_common_opts(cls, parser):
 | 
			
		||||
        """Add options that are common for several plugins.
 | 
			
		||||
        """
 | 
			
		||||
        for opt in cls.common_opt_names:
 | 
			
		||||
            cls._parser_add_opt(parser, opt)
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_opt(opt_name, args):
 | 
			
		||||
        """Return option name and value.
 | 
			
		||||
 | 
			
		||||
        :param opt_name: name of the option, e.g., "username"
 | 
			
		||||
        :param args: parsed arguments
 | 
			
		||||
        """
 | 
			
		||||
        return (opt_name, getattr(args, "os_%s" % opt_name, None))
 | 
			
		||||
 | 
			
		||||
    def parse_opts(self, args):
 | 
			
		||||
        """Parse the actual auth-system options if any.
 | 
			
		||||
 | 
			
		||||
        This method is expected to populate the attribute `self.opts` with a
 | 
			
		||||
        dict containing the options and values needed to make authentication.
 | 
			
		||||
        """
 | 
			
		||||
        self.opts.update(dict(self.get_opt(opt_name, args)
 | 
			
		||||
                              for opt_name in self.opt_names))
 | 
			
		||||
 | 
			
		||||
    def authenticate(self, http_client):
 | 
			
		||||
        """Authenticate using plugin defined method.
 | 
			
		||||
 | 
			
		||||
        The method usually analyses `self.opts` and performs
 | 
			
		||||
        a request to authentication server.
 | 
			
		||||
 | 
			
		||||
        :param http_client: client object that needs authentication
 | 
			
		||||
        :type http_client: HTTPClient
 | 
			
		||||
        :raises: AuthorizationFailure
 | 
			
		||||
        """
 | 
			
		||||
        self.sufficient_options()
 | 
			
		||||
        self._do_authenticate(http_client)
 | 
			
		||||
 | 
			
		||||
    @abc.abstractmethod
 | 
			
		||||
    def _do_authenticate(self, http_client):
 | 
			
		||||
        """Protected method for authentication.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
    def sufficient_options(self):
 | 
			
		||||
        """Check if all required options are present.
 | 
			
		||||
 | 
			
		||||
        :raises: AuthPluginOptionsMissing
 | 
			
		||||
        """
 | 
			
		||||
        missing = [opt
 | 
			
		||||
                   for opt in self.opt_names
 | 
			
		||||
                   if not self.opts.get(opt)]
 | 
			
		||||
        if missing:
 | 
			
		||||
            raise exceptions.AuthPluginOptionsMissing(missing)
 | 
			
		||||
 | 
			
		||||
    @abc.abstractmethod
 | 
			
		||||
    def token_and_endpoint(self, endpoint_type, service_type):
 | 
			
		||||
        """Return token and endpoint.
 | 
			
		||||
 | 
			
		||||
        :param service_type: Service type of the endpoint
 | 
			
		||||
        :type service_type: string
 | 
			
		||||
        :param endpoint_type: Type of endpoint.
 | 
			
		||||
                              Possible values: public or publicURL,
 | 
			
		||||
                              internal or internalURL,
 | 
			
		||||
                              admin or adminURL
 | 
			
		||||
        :type endpoint_type: string
 | 
			
		||||
        :returns: tuple of token and endpoint strings
 | 
			
		||||
        :raises: EndpointException
 | 
			
		||||
        """
 | 
			
		||||
							
								
								
									
										532
									
								
								watcherclient/openstack/common/apiclient/base.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										532
									
								
								watcherclient/openstack/common/apiclient/base.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,532 @@
 | 
			
		||||
# Copyright 2010 Jacob Kaplan-Moss
 | 
			
		||||
# Copyright 2011 OpenStack Foundation
 | 
			
		||||
# Copyright 2012 Grid Dynamics
 | 
			
		||||
# Copyright 2013 OpenStack Foundation
 | 
			
		||||
# All Rights Reserved.
 | 
			
		||||
#
 | 
			
		||||
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
			
		||||
#    not use this file except in compliance with the License. You may obtain
 | 
			
		||||
#    a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#         http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
#    Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
#    License for the specific language governing permissions and limitations
 | 
			
		||||
#    under the License.
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
Base utilities to build API operation managers and objects on top of.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
########################################################################
 | 
			
		||||
#
 | 
			
		||||
# THIS MODULE IS DEPRECATED
 | 
			
		||||
#
 | 
			
		||||
# Please refer to
 | 
			
		||||
# https://etherpad.openstack.org/p/kilo-watcherclient-library-proposals for
 | 
			
		||||
# the discussion leading to this deprecation.
 | 
			
		||||
#
 | 
			
		||||
# We recommend checking out the python-openstacksdk project
 | 
			
		||||
# (https://launchpad.net/python-openstacksdk) instead.
 | 
			
		||||
#
 | 
			
		||||
########################################################################
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# E1102: %s is not callable
 | 
			
		||||
# pylint: disable=E1102
 | 
			
		||||
 | 
			
		||||
import abc
 | 
			
		||||
import copy
 | 
			
		||||
 | 
			
		||||
from oslo_utils import strutils
 | 
			
		||||
import six
 | 
			
		||||
from six.moves.urllib import parse
 | 
			
		||||
 | 
			
		||||
from watcherclient.openstack.common._i18n import _
 | 
			
		||||
from watcherclient.openstack.common.apiclient import exceptions
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def getid(obj):
 | 
			
		||||
    """Return id if argument is a Resource.
 | 
			
		||||
 | 
			
		||||
    Abstracts the common pattern of allowing both an object or an object's ID
 | 
			
		||||
    (UUID) as a parameter when dealing with relationships.
 | 
			
		||||
    """
 | 
			
		||||
    try:
 | 
			
		||||
        if obj.uuid:
 | 
			
		||||
            return obj.uuid
 | 
			
		||||
    except AttributeError:
 | 
			
		||||
        pass
 | 
			
		||||
    try:
 | 
			
		||||
        return obj.id
 | 
			
		||||
    except AttributeError:
 | 
			
		||||
        return obj
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# TODO(aababilov): call run_hooks() in HookableMixin's child classes
 | 
			
		||||
class HookableMixin(object):
 | 
			
		||||
    """Mixin so classes can register and run hooks."""
 | 
			
		||||
    _hooks_map = {}
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def add_hook(cls, hook_type, hook_func):
 | 
			
		||||
        """Add a new hook of specified type.
 | 
			
		||||
 | 
			
		||||
        :param cls: class that registers hooks
 | 
			
		||||
        :param hook_type: hook type, e.g., '__pre_parse_args__'
 | 
			
		||||
        :param hook_func: hook function
 | 
			
		||||
        """
 | 
			
		||||
        if hook_type not in cls._hooks_map:
 | 
			
		||||
            cls._hooks_map[hook_type] = []
 | 
			
		||||
 | 
			
		||||
        cls._hooks_map[hook_type].append(hook_func)
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def run_hooks(cls, hook_type, *args, **kwargs):
 | 
			
		||||
        """Run all hooks of specified type.
 | 
			
		||||
 | 
			
		||||
        :param cls: class that registers hooks
 | 
			
		||||
        :param hook_type: hook type, e.g., '__pre_parse_args__'
 | 
			
		||||
        :param args: args to be passed to every hook function
 | 
			
		||||
        :param kwargs: kwargs to be passed to every hook function
 | 
			
		||||
        """
 | 
			
		||||
        hook_funcs = cls._hooks_map.get(hook_type) or []
 | 
			
		||||
        for hook_func in hook_funcs:
 | 
			
		||||
            hook_func(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BaseManager(HookableMixin):
 | 
			
		||||
    """Basic manager type providing common operations.
 | 
			
		||||
 | 
			
		||||
    Managers interact with a particular type of API (servers, flavors, images,
 | 
			
		||||
    etc.) and provide CRUD operations for them.
 | 
			
		||||
    """
 | 
			
		||||
    resource_class = None
 | 
			
		||||
 | 
			
		||||
    def __init__(self, client):
 | 
			
		||||
        """Initializes BaseManager with `client`.
 | 
			
		||||
 | 
			
		||||
        :param client: instance of BaseClient descendant for HTTP requests
 | 
			
		||||
        """
 | 
			
		||||
        super(BaseManager, self).__init__()
 | 
			
		||||
        self.client = client
 | 
			
		||||
 | 
			
		||||
    def _list(self, url, response_key=None, obj_class=None, json=None):
 | 
			
		||||
        """List the collection.
 | 
			
		||||
 | 
			
		||||
        :param url: a partial URL, e.g., '/servers'
 | 
			
		||||
        :param response_key: the key to be looked up in response dictionary,
 | 
			
		||||
            e.g., 'servers'. If response_key is None - all response body
 | 
			
		||||
            will be used.
 | 
			
		||||
        :param obj_class: class for constructing the returned objects
 | 
			
		||||
            (self.resource_class will be used by default)
 | 
			
		||||
        :param json: data that will be encoded as JSON and passed in POST
 | 
			
		||||
            request (GET will be sent by default)
 | 
			
		||||
        """
 | 
			
		||||
        if json:
 | 
			
		||||
            body = self.client.post(url, json=json).json()
 | 
			
		||||
        else:
 | 
			
		||||
            body = self.client.get(url).json()
 | 
			
		||||
 | 
			
		||||
        if obj_class is None:
 | 
			
		||||
            obj_class = self.resource_class
 | 
			
		||||
 | 
			
		||||
        data = body[response_key] if response_key is not None else body
 | 
			
		||||
        # NOTE(ja): keystone returns values as list as {'values': [ ... ]}
 | 
			
		||||
        #           unlike other services which just return the list...
 | 
			
		||||
        try:
 | 
			
		||||
            data = data['values']
 | 
			
		||||
        except (KeyError, TypeError):
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        return [obj_class(self, res, loaded=True) for res in data if res]
 | 
			
		||||
 | 
			
		||||
    def _get(self, url, response_key=None):
 | 
			
		||||
        """Get an object from collection.
 | 
			
		||||
 | 
			
		||||
        :param url: a partial URL, e.g., '/servers'
 | 
			
		||||
        :param response_key: the key to be looked up in response dictionary,
 | 
			
		||||
            e.g., 'server'. If response_key is None - all response body
 | 
			
		||||
            will be used.
 | 
			
		||||
        """
 | 
			
		||||
        body = self.client.get(url).json()
 | 
			
		||||
        data = body[response_key] if response_key is not None else body
 | 
			
		||||
        return self.resource_class(self, data, loaded=True)
 | 
			
		||||
 | 
			
		||||
    def _head(self, url):
 | 
			
		||||
        """Retrieve request headers for an object.
 | 
			
		||||
 | 
			
		||||
        :param url: a partial URL, e.g., '/servers'
 | 
			
		||||
        """
 | 
			
		||||
        resp = self.client.head(url)
 | 
			
		||||
        return resp.status_code == 204
 | 
			
		||||
 | 
			
		||||
    def _post(self, url, json, response_key=None, return_raw=False):
 | 
			
		||||
        """Create an object.
 | 
			
		||||
 | 
			
		||||
        :param url: a partial URL, e.g., '/servers'
 | 
			
		||||
        :param json: data that will be encoded as JSON and passed in POST
 | 
			
		||||
            request (GET will be sent by default)
 | 
			
		||||
        :param response_key: the key to be looked up in response dictionary,
 | 
			
		||||
            e.g., 'server'. If response_key is None - all response body
 | 
			
		||||
            will be used.
 | 
			
		||||
        :param return_raw: flag to force returning raw JSON instead of
 | 
			
		||||
            Python object of self.resource_class
 | 
			
		||||
        """
 | 
			
		||||
        body = self.client.post(url, json=json).json()
 | 
			
		||||
        data = body[response_key] if response_key is not None else body
 | 
			
		||||
        if return_raw:
 | 
			
		||||
            return data
 | 
			
		||||
        return self.resource_class(self, data)
 | 
			
		||||
 | 
			
		||||
    def _put(self, url, json=None, response_key=None):
 | 
			
		||||
        """Update an object with PUT method.
 | 
			
		||||
 | 
			
		||||
        :param url: a partial URL, e.g., '/servers'
 | 
			
		||||
        :param json: data that will be encoded as JSON and passed in POST
 | 
			
		||||
            request (GET will be sent by default)
 | 
			
		||||
        :param response_key: the key to be looked up in response dictionary,
 | 
			
		||||
            e.g., 'servers'. If response_key is None - all response body
 | 
			
		||||
            will be used.
 | 
			
		||||
        """
 | 
			
		||||
        resp = self.client.put(url, json=json)
 | 
			
		||||
        # PUT requests may not return a body
 | 
			
		||||
        if resp.content:
 | 
			
		||||
            body = resp.json()
 | 
			
		||||
            if response_key is not None:
 | 
			
		||||
                return self.resource_class(self, body[response_key])
 | 
			
		||||
            else:
 | 
			
		||||
                return self.resource_class(self, body)
 | 
			
		||||
 | 
			
		||||
    def _patch(self, url, json=None, response_key=None):
 | 
			
		||||
        """Update an object with PATCH method.
 | 
			
		||||
 | 
			
		||||
        :param url: a partial URL, e.g., '/servers'
 | 
			
		||||
        :param json: data that will be encoded as JSON and passed in POST
 | 
			
		||||
            request (GET will be sent by default)
 | 
			
		||||
        :param response_key: the key to be looked up in response dictionary,
 | 
			
		||||
            e.g., 'servers'. If response_key is None - all response body
 | 
			
		||||
            will be used.
 | 
			
		||||
        """
 | 
			
		||||
        body = self.client.patch(url, json=json).json()
 | 
			
		||||
        if response_key is not None:
 | 
			
		||||
            return self.resource_class(self, body[response_key])
 | 
			
		||||
        else:
 | 
			
		||||
            return self.resource_class(self, body)
 | 
			
		||||
 | 
			
		||||
    def _delete(self, url):
 | 
			
		||||
        """Delete an object.
 | 
			
		||||
 | 
			
		||||
        :param url: a partial URL, e.g., '/servers/my-server'
 | 
			
		||||
        """
 | 
			
		||||
        return self.client.delete(url)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@six.add_metaclass(abc.ABCMeta)
 | 
			
		||||
class ManagerWithFind(BaseManager):
 | 
			
		||||
    """Manager with additional `find()`/`findall()` methods."""
 | 
			
		||||
 | 
			
		||||
    @abc.abstractmethod
 | 
			
		||||
    def list(self):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def find(self, **kwargs):
 | 
			
		||||
        """Find a single item with attributes matching ``**kwargs``.
 | 
			
		||||
 | 
			
		||||
        This isn't very efficient: it loads the entire list then filters on
 | 
			
		||||
        the Python side.
 | 
			
		||||
        """
 | 
			
		||||
        matches = self.findall(**kwargs)
 | 
			
		||||
        num_matches = len(matches)
 | 
			
		||||
        if num_matches == 0:
 | 
			
		||||
            msg = _("No %(name)s matching %(args)s.") % {
 | 
			
		||||
                'name': self.resource_class.__name__,
 | 
			
		||||
                'args': kwargs
 | 
			
		||||
            }
 | 
			
		||||
            raise exceptions.NotFound(msg)
 | 
			
		||||
        elif num_matches > 1:
 | 
			
		||||
            raise exceptions.NoUniqueMatch()
 | 
			
		||||
        else:
 | 
			
		||||
            return matches[0]
 | 
			
		||||
 | 
			
		||||
    def findall(self, **kwargs):
 | 
			
		||||
        """Find all items with attributes matching ``**kwargs``.
 | 
			
		||||
 | 
			
		||||
        This isn't very efficient: it loads the entire list then filters on
 | 
			
		||||
        the Python side.
 | 
			
		||||
        """
 | 
			
		||||
        found = []
 | 
			
		||||
        searches = kwargs.items()
 | 
			
		||||
 | 
			
		||||
        for obj in self.list():
 | 
			
		||||
            try:
 | 
			
		||||
                if all(getattr(obj, attr) == value
 | 
			
		||||
                       for (attr, value) in searches):
 | 
			
		||||
                    found.append(obj)
 | 
			
		||||
            except AttributeError:
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
        return found
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CrudManager(BaseManager):
 | 
			
		||||
    """Base manager class for manipulating entities.
 | 
			
		||||
 | 
			
		||||
    Children of this class are expected to define a `collection_key` and `key`.
 | 
			
		||||
 | 
			
		||||
    - `collection_key`: Usually a plural noun by convention (e.g. `entities`);
 | 
			
		||||
      used to refer collections in both URL's (e.g.  `/v3/entities`) and JSON
 | 
			
		||||
      objects containing a list of member resources (e.g. `{'entities': [{},
 | 
			
		||||
      {}, {}]}`).
 | 
			
		||||
    - `key`: Usually a singular noun by convention (e.g. `entity`); used to
 | 
			
		||||
      refer to an individual member of the collection.
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    collection_key = None
 | 
			
		||||
    key = None
 | 
			
		||||
 | 
			
		||||
    def build_url(self, base_url=None, **kwargs):
 | 
			
		||||
        """Builds a resource URL for the given kwargs.
 | 
			
		||||
 | 
			
		||||
        Given an example collection where `collection_key = 'entities'` and
 | 
			
		||||
        `key = 'entity'`, the following URL's could be generated.
 | 
			
		||||
 | 
			
		||||
        By default, the URL will represent a collection of entities, e.g.::
 | 
			
		||||
 | 
			
		||||
            /entities
 | 
			
		||||
 | 
			
		||||
        If kwargs contains an `entity_id`, then the URL will represent a
 | 
			
		||||
        specific member, e.g.::
 | 
			
		||||
 | 
			
		||||
            /entities/{entity_id}
 | 
			
		||||
 | 
			
		||||
        :param base_url: if provided, the generated URL will be appended to it
 | 
			
		||||
        """
 | 
			
		||||
        url = base_url if base_url is not None else ''
 | 
			
		||||
 | 
			
		||||
        url += '/%s' % self.collection_key
 | 
			
		||||
 | 
			
		||||
        # do we have a specific entity?
 | 
			
		||||
        entity_id = kwargs.get('%s_id' % self.key)
 | 
			
		||||
        if entity_id is not None:
 | 
			
		||||
            url += '/%s' % entity_id
 | 
			
		||||
 | 
			
		||||
        return url
 | 
			
		||||
 | 
			
		||||
    def _filter_kwargs(self, kwargs):
 | 
			
		||||
        """Drop null values and handle ids."""
 | 
			
		||||
        for key, ref in six.iteritems(kwargs.copy()):
 | 
			
		||||
            if ref is None:
 | 
			
		||||
                kwargs.pop(key)
 | 
			
		||||
            else:
 | 
			
		||||
                if isinstance(ref, Resource):
 | 
			
		||||
                    kwargs.pop(key)
 | 
			
		||||
                    kwargs['%s_id' % key] = getid(ref)
 | 
			
		||||
        return kwargs
 | 
			
		||||
 | 
			
		||||
    def create(self, **kwargs):
 | 
			
		||||
        kwargs = self._filter_kwargs(kwargs)
 | 
			
		||||
        return self._post(
 | 
			
		||||
            self.build_url(**kwargs),
 | 
			
		||||
            {self.key: kwargs},
 | 
			
		||||
            self.key)
 | 
			
		||||
 | 
			
		||||
    def get(self, **kwargs):
 | 
			
		||||
        kwargs = self._filter_kwargs(kwargs)
 | 
			
		||||
        return self._get(
 | 
			
		||||
            self.build_url(**kwargs),
 | 
			
		||||
            self.key)
 | 
			
		||||
 | 
			
		||||
    def head(self, **kwargs):
 | 
			
		||||
        kwargs = self._filter_kwargs(kwargs)
 | 
			
		||||
        return self._head(self.build_url(**kwargs))
 | 
			
		||||
 | 
			
		||||
    def list(self, base_url=None, **kwargs):
 | 
			
		||||
        """List the collection.
 | 
			
		||||
 | 
			
		||||
        :param base_url: if provided, the generated URL will be appended to it
 | 
			
		||||
        """
 | 
			
		||||
        kwargs = self._filter_kwargs(kwargs)
 | 
			
		||||
 | 
			
		||||
        return self._list(
 | 
			
		||||
            '%(base_url)s%(query)s' % {
 | 
			
		||||
                'base_url': self.build_url(base_url=base_url, **kwargs),
 | 
			
		||||
                'query': '?%s' % parse.urlencode(kwargs) if kwargs else '',
 | 
			
		||||
            },
 | 
			
		||||
            self.collection_key)
 | 
			
		||||
 | 
			
		||||
    def put(self, base_url=None, **kwargs):
 | 
			
		||||
        """Update an element.
 | 
			
		||||
 | 
			
		||||
        :param base_url: if provided, the generated URL will be appended to it
 | 
			
		||||
        """
 | 
			
		||||
        kwargs = self._filter_kwargs(kwargs)
 | 
			
		||||
 | 
			
		||||
        return self._put(self.build_url(base_url=base_url, **kwargs))
 | 
			
		||||
 | 
			
		||||
    def update(self, **kwargs):
 | 
			
		||||
        kwargs = self._filter_kwargs(kwargs)
 | 
			
		||||
        params = kwargs.copy()
 | 
			
		||||
        params.pop('%s_id' % self.key)
 | 
			
		||||
 | 
			
		||||
        return self._patch(
 | 
			
		||||
            self.build_url(**kwargs),
 | 
			
		||||
            {self.key: params},
 | 
			
		||||
            self.key)
 | 
			
		||||
 | 
			
		||||
    def delete(self, **kwargs):
 | 
			
		||||
        kwargs = self._filter_kwargs(kwargs)
 | 
			
		||||
 | 
			
		||||
        return self._delete(
 | 
			
		||||
            self.build_url(**kwargs))
 | 
			
		||||
 | 
			
		||||
    def find(self, base_url=None, **kwargs):
 | 
			
		||||
        """Find a single item with attributes matching ``**kwargs``.
 | 
			
		||||
 | 
			
		||||
        :param base_url: if provided, the generated URL will be appended to it
 | 
			
		||||
        """
 | 
			
		||||
        kwargs = self._filter_kwargs(kwargs)
 | 
			
		||||
 | 
			
		||||
        rl = self._list(
 | 
			
		||||
            '%(base_url)s%(query)s' % {
 | 
			
		||||
                'base_url': self.build_url(base_url=base_url, **kwargs),
 | 
			
		||||
                'query': '?%s' % parse.urlencode(kwargs) if kwargs else '',
 | 
			
		||||
            },
 | 
			
		||||
            self.collection_key)
 | 
			
		||||
        num = len(rl)
 | 
			
		||||
 | 
			
		||||
        if num == 0:
 | 
			
		||||
            msg = _("No %(name)s matching %(args)s.") % {
 | 
			
		||||
                'name': self.resource_class.__name__,
 | 
			
		||||
                'args': kwargs
 | 
			
		||||
            }
 | 
			
		||||
            raise exceptions.NotFound(404, msg)
 | 
			
		||||
        elif num > 1:
 | 
			
		||||
            raise exceptions.NoUniqueMatch
 | 
			
		||||
        else:
 | 
			
		||||
            return rl[0]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Extension(HookableMixin):
 | 
			
		||||
    """Extension descriptor."""
 | 
			
		||||
 | 
			
		||||
    SUPPORTED_HOOKS = ('__pre_parse_args__', '__post_parse_args__')
 | 
			
		||||
    manager_class = None
 | 
			
		||||
 | 
			
		||||
    def __init__(self, name, module):
 | 
			
		||||
        super(Extension, self).__init__()
 | 
			
		||||
        self.name = name
 | 
			
		||||
        self.module = module
 | 
			
		||||
        self._parse_extension_module()
 | 
			
		||||
 | 
			
		||||
    def _parse_extension_module(self):
 | 
			
		||||
        self.manager_class = None
 | 
			
		||||
        for attr_name, attr_value in self.module.__dict__.items():
 | 
			
		||||
            if attr_name in self.SUPPORTED_HOOKS:
 | 
			
		||||
                self.add_hook(attr_name, attr_value)
 | 
			
		||||
            else:
 | 
			
		||||
                try:
 | 
			
		||||
                    if issubclass(attr_value, BaseManager):
 | 
			
		||||
                        self.manager_class = attr_value
 | 
			
		||||
                except TypeError:
 | 
			
		||||
                    pass
 | 
			
		||||
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        return "<Extension '%s'>" % self.name
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Resource(object):
 | 
			
		||||
    """Base class for OpenStack resources (tenant, user, etc.).
 | 
			
		||||
 | 
			
		||||
    This is pretty much just a bag for attributes.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    HUMAN_ID = False
 | 
			
		||||
    NAME_ATTR = 'name'
 | 
			
		||||
 | 
			
		||||
    def __init__(self, manager, info, loaded=False):
 | 
			
		||||
        """Populate and bind to a manager.
 | 
			
		||||
 | 
			
		||||
        :param manager: BaseManager object
 | 
			
		||||
        :param info: dictionary representing resource attributes
 | 
			
		||||
        :param loaded: prevent lazy-loading if set to True
 | 
			
		||||
        """
 | 
			
		||||
        self.manager = manager
 | 
			
		||||
        self._info = info
 | 
			
		||||
        self._add_details(info)
 | 
			
		||||
        self._loaded = loaded
 | 
			
		||||
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        reprkeys = sorted(k
 | 
			
		||||
                          for k in self.__dict__.keys()
 | 
			
		||||
                          if k[0] != '_' and k != 'manager')
 | 
			
		||||
        info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys)
 | 
			
		||||
        return "<%s %s>" % (self.__class__.__name__, info)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def human_id(self):
 | 
			
		||||
        """Human-readable ID which can be used for bash completion.
 | 
			
		||||
        """
 | 
			
		||||
        if self.HUMAN_ID:
 | 
			
		||||
            name = getattr(self, self.NAME_ATTR, None)
 | 
			
		||||
            if name is not None:
 | 
			
		||||
                return strutils.to_slug(name)
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
    def _add_details(self, info):
 | 
			
		||||
        for (k, v) in six.iteritems(info):
 | 
			
		||||
            try:
 | 
			
		||||
                setattr(self, k, v)
 | 
			
		||||
                self._info[k] = v
 | 
			
		||||
            except AttributeError:
 | 
			
		||||
                # In this case we already defined the attribute on the class
 | 
			
		||||
                pass
 | 
			
		||||
 | 
			
		||||
    def __getattr__(self, k):
 | 
			
		||||
        if k not in self.__dict__:
 | 
			
		||||
            # NOTE(bcwaldon): disallow lazy-loading if already loaded once
 | 
			
		||||
            if not self.is_loaded():
 | 
			
		||||
                self.get()
 | 
			
		||||
                return self.__getattr__(k)
 | 
			
		||||
 | 
			
		||||
            raise AttributeError(k)
 | 
			
		||||
        else:
 | 
			
		||||
            return self.__dict__[k]
 | 
			
		||||
 | 
			
		||||
    def get(self):
 | 
			
		||||
        """Support for lazy loading details.
 | 
			
		||||
 | 
			
		||||
        Some clients, such as novaclient have the option to lazy load the
 | 
			
		||||
        details, details which can be loaded with this function.
 | 
			
		||||
        """
 | 
			
		||||
        # set_loaded() first ... so if we have to bail, we know we tried.
 | 
			
		||||
        self.set_loaded(True)
 | 
			
		||||
        if not hasattr(self.manager, 'get'):
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        new = self.manager.get(self.id)
 | 
			
		||||
        if new:
 | 
			
		||||
            self._add_details(new._info)
 | 
			
		||||
            self._add_details(
 | 
			
		||||
                {'x_request_id': self.manager.client.last_request_id})
 | 
			
		||||
 | 
			
		||||
    def __eq__(self, other):
 | 
			
		||||
        if not isinstance(other, Resource):
 | 
			
		||||
            return NotImplemented
 | 
			
		||||
        # two resources of different types are not equal
 | 
			
		||||
        if not isinstance(other, self.__class__):
 | 
			
		||||
            return False
 | 
			
		||||
        if hasattr(self, 'id') and hasattr(other, 'id'):
 | 
			
		||||
            return self.id == other.id
 | 
			
		||||
        return self._info == other._info
 | 
			
		||||
 | 
			
		||||
    def is_loaded(self):
 | 
			
		||||
        return self._loaded
 | 
			
		||||
 | 
			
		||||
    def set_loaded(self, val):
 | 
			
		||||
        self._loaded = val
 | 
			
		||||
 | 
			
		||||
    def to_dict(self):
 | 
			
		||||
        return copy.deepcopy(self._info)
 | 
			
		||||
							
								
								
									
										388
									
								
								watcherclient/openstack/common/apiclient/client.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										388
									
								
								watcherclient/openstack/common/apiclient/client.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,388 @@
 | 
			
		||||
# Copyright 2010 Jacob Kaplan-Moss
 | 
			
		||||
# Copyright 2011 OpenStack Foundation
 | 
			
		||||
# Copyright 2011 Piston Cloud Computing, Inc.
 | 
			
		||||
# Copyright 2013 Alessio Ababilov
 | 
			
		||||
# Copyright 2013 Grid Dynamics
 | 
			
		||||
# Copyright 2013 OpenStack Foundation
 | 
			
		||||
# All Rights Reserved.
 | 
			
		||||
#
 | 
			
		||||
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
			
		||||
#    not use this file except in compliance with the License. You may obtain
 | 
			
		||||
#    a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#         http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
#    Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
#    License for the specific language governing permissions and limitations
 | 
			
		||||
#    under the License.
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
OpenStack Client interface. Handles the REST calls and responses.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
# E0202: An attribute inherited from %s hide this method
 | 
			
		||||
# pylint: disable=E0202
 | 
			
		||||
 | 
			
		||||
import hashlib
 | 
			
		||||
import logging
 | 
			
		||||
import time
 | 
			
		||||
 | 
			
		||||
try:
 | 
			
		||||
    import simplejson as json
 | 
			
		||||
except ImportError:
 | 
			
		||||
    import json
 | 
			
		||||
 | 
			
		||||
from oslo_utils import encodeutils
 | 
			
		||||
from oslo_utils import importutils
 | 
			
		||||
import requests
 | 
			
		||||
 | 
			
		||||
from watcherclient.openstack.common._i18n import _
 | 
			
		||||
from watcherclient.openstack.common.apiclient import exceptions
 | 
			
		||||
 | 
			
		||||
_logger = logging.getLogger(__name__)
 | 
			
		||||
SENSITIVE_HEADERS = ('X-Auth-Token', 'X-Subject-Token',)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HTTPClient(object):
 | 
			
		||||
    """This client handles sending HTTP requests to OpenStack servers.
 | 
			
		||||
 | 
			
		||||
    Features:
 | 
			
		||||
 | 
			
		||||
    - share authentication information between several clients to different
 | 
			
		||||
      services (e.g., for compute and image clients);
 | 
			
		||||
    - reissue authentication request for expired tokens;
 | 
			
		||||
    - encode/decode JSON bodies;
 | 
			
		||||
    - raise exceptions on HTTP errors;
 | 
			
		||||
    - pluggable authentication;
 | 
			
		||||
    - store authentication information in a keyring;
 | 
			
		||||
    - store time spent for requests;
 | 
			
		||||
    - register clients for particular services, so one can use
 | 
			
		||||
      `http_client.identity` or `http_client.compute`;
 | 
			
		||||
    - log requests and responses in a format that is easy to copy-and-paste
 | 
			
		||||
      into terminal and send the same request with curl.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    user_agent = "watcherclient.openstack.common.apiclient"
 | 
			
		||||
 | 
			
		||||
    def __init__(self,
 | 
			
		||||
                 auth_plugin,
 | 
			
		||||
                 region_name=None,
 | 
			
		||||
                 endpoint_type="publicURL",
 | 
			
		||||
                 original_ip=None,
 | 
			
		||||
                 verify=True,
 | 
			
		||||
                 cert=None,
 | 
			
		||||
                 timeout=None,
 | 
			
		||||
                 timings=False,
 | 
			
		||||
                 keyring_saver=None,
 | 
			
		||||
                 debug=False,
 | 
			
		||||
                 user_agent=None,
 | 
			
		||||
                 http=None):
 | 
			
		||||
        self.auth_plugin = auth_plugin
 | 
			
		||||
 | 
			
		||||
        self.endpoint_type = endpoint_type
 | 
			
		||||
        self.region_name = region_name
 | 
			
		||||
 | 
			
		||||
        self.original_ip = original_ip
 | 
			
		||||
        self.timeout = timeout
 | 
			
		||||
        self.verify = verify
 | 
			
		||||
        self.cert = cert
 | 
			
		||||
 | 
			
		||||
        self.keyring_saver = keyring_saver
 | 
			
		||||
        self.debug = debug
 | 
			
		||||
        self.user_agent = user_agent or self.user_agent
 | 
			
		||||
 | 
			
		||||
        self.times = []  # [("item", starttime, endtime), ...]
 | 
			
		||||
        self.timings = timings
 | 
			
		||||
 | 
			
		||||
        # requests within the same session can reuse TCP connections from pool
 | 
			
		||||
        self.http = http or requests.Session()
 | 
			
		||||
 | 
			
		||||
        self.cached_token = None
 | 
			
		||||
        self.last_request_id = None
 | 
			
		||||
 | 
			
		||||
    def _safe_header(self, name, value):
 | 
			
		||||
        if name in SENSITIVE_HEADERS:
 | 
			
		||||
            # because in python3 byte string handling is ... ug
 | 
			
		||||
            v = value.encode('utf-8')
 | 
			
		||||
            h = hashlib.sha1(v)
 | 
			
		||||
            d = h.hexdigest()
 | 
			
		||||
            return encodeutils.safe_decode(name), "{SHA1}%s" % d
 | 
			
		||||
        else:
 | 
			
		||||
            return (encodeutils.safe_decode(name),
 | 
			
		||||
                    encodeutils.safe_decode(value))
 | 
			
		||||
 | 
			
		||||
    def _http_log_req(self, method, url, kwargs):
 | 
			
		||||
        if not self.debug:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        string_parts = [
 | 
			
		||||
            "curl -g -i",
 | 
			
		||||
            "-X '%s'" % method,
 | 
			
		||||
            "'%s'" % url,
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        for element in kwargs['headers']:
 | 
			
		||||
            header = ("-H '%s: %s'" %
 | 
			
		||||
                      self._safe_header(element, kwargs['headers'][element]))
 | 
			
		||||
            string_parts.append(header)
 | 
			
		||||
 | 
			
		||||
        _logger.debug("REQ: %s" % " ".join(string_parts))
 | 
			
		||||
        if 'data' in kwargs:
 | 
			
		||||
            _logger.debug("REQ BODY: %s\n" % (kwargs['data']))
 | 
			
		||||
 | 
			
		||||
    def _http_log_resp(self, resp):
 | 
			
		||||
        if not self.debug:
 | 
			
		||||
            return
 | 
			
		||||
        _logger.debug(
 | 
			
		||||
            "RESP: [%s] %s\n",
 | 
			
		||||
            resp.status_code,
 | 
			
		||||
            resp.headers)
 | 
			
		||||
        if resp._content_consumed:
 | 
			
		||||
            _logger.debug(
 | 
			
		||||
                "RESP BODY: %s\n",
 | 
			
		||||
                resp.text)
 | 
			
		||||
 | 
			
		||||
    def serialize(self, kwargs):
 | 
			
		||||
        if kwargs.get('json') is not None:
 | 
			
		||||
            kwargs['headers']['Content-Type'] = 'application/json'
 | 
			
		||||
            kwargs['data'] = json.dumps(kwargs['json'])
 | 
			
		||||
        try:
 | 
			
		||||
            del kwargs['json']
 | 
			
		||||
        except KeyError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
    def get_timings(self):
 | 
			
		||||
        return self.times
 | 
			
		||||
 | 
			
		||||
    def reset_timings(self):
 | 
			
		||||
        self.times = []
 | 
			
		||||
 | 
			
		||||
    def request(self, method, url, **kwargs):
 | 
			
		||||
        """Send an http request with the specified characteristics.
 | 
			
		||||
 | 
			
		||||
        Wrapper around `requests.Session.request` to handle tasks such as
 | 
			
		||||
        setting headers, JSON encoding/decoding, and error handling.
 | 
			
		||||
 | 
			
		||||
        :param method: method of HTTP request
 | 
			
		||||
        :param url: URL of HTTP request
 | 
			
		||||
        :param kwargs: any other parameter that can be passed to
 | 
			
		||||
             requests.Session.request (such as `headers`) or `json`
 | 
			
		||||
             that will be encoded as JSON and used as `data` argument
 | 
			
		||||
        """
 | 
			
		||||
        kwargs.setdefault("headers", {})
 | 
			
		||||
        kwargs["headers"]["User-Agent"] = self.user_agent
 | 
			
		||||
        if self.original_ip:
 | 
			
		||||
            kwargs["headers"]["Forwarded"] = "for=%s;by=%s" % (
 | 
			
		||||
                self.original_ip, self.user_agent)
 | 
			
		||||
        if self.timeout is not None:
 | 
			
		||||
            kwargs.setdefault("timeout", self.timeout)
 | 
			
		||||
        kwargs.setdefault("verify", self.verify)
 | 
			
		||||
        if self.cert is not None:
 | 
			
		||||
            kwargs.setdefault("cert", self.cert)
 | 
			
		||||
        self.serialize(kwargs)
 | 
			
		||||
 | 
			
		||||
        self._http_log_req(method, url, kwargs)
 | 
			
		||||
        if self.timings:
 | 
			
		||||
            start_time = time.time()
 | 
			
		||||
        resp = self.http.request(method, url, **kwargs)
 | 
			
		||||
        if self.timings:
 | 
			
		||||
            self.times.append(("%s %s" % (method, url),
 | 
			
		||||
                               start_time, time.time()))
 | 
			
		||||
        self._http_log_resp(resp)
 | 
			
		||||
 | 
			
		||||
        self.last_request_id = resp.headers.get('x-openstack-request-id')
 | 
			
		||||
 | 
			
		||||
        if resp.status_code >= 400:
 | 
			
		||||
            _logger.debug(
 | 
			
		||||
                "Request returned failure status: %s",
 | 
			
		||||
                resp.status_code)
 | 
			
		||||
            raise exceptions.from_response(resp, method, url)
 | 
			
		||||
 | 
			
		||||
        return resp
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def concat_url(endpoint, url):
 | 
			
		||||
        """Concatenate endpoint and final URL.
 | 
			
		||||
 | 
			
		||||
        E.g., "http://keystone/v2.0/" and "/tokens" are concatenated to
 | 
			
		||||
        "http://keystone/v2.0/tokens".
 | 
			
		||||
 | 
			
		||||
        :param endpoint: the base URL
 | 
			
		||||
        :param url: the final URL
 | 
			
		||||
        """
 | 
			
		||||
        return "%s/%s" % (endpoint.rstrip("/"), url.strip("/"))
 | 
			
		||||
 | 
			
		||||
    def client_request(self, client, method, url, **kwargs):
 | 
			
		||||
        """Send an http request using `client`'s endpoint and specified `url`.
 | 
			
		||||
 | 
			
		||||
        If request was rejected as unauthorized (possibly because the token is
 | 
			
		||||
        expired), issue one authorization attempt and send the request once
 | 
			
		||||
        again.
 | 
			
		||||
 | 
			
		||||
        :param client: instance of BaseClient descendant
 | 
			
		||||
        :param method: method of HTTP request
 | 
			
		||||
        :param url: URL of HTTP request
 | 
			
		||||
        :param kwargs: any other parameter that can be passed to
 | 
			
		||||
            `HTTPClient.request`
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        filter_args = {
 | 
			
		||||
            "endpoint_type": client.endpoint_type or self.endpoint_type,
 | 
			
		||||
            "service_type": client.service_type,
 | 
			
		||||
        }
 | 
			
		||||
        token, endpoint = (self.cached_token, client.cached_endpoint)
 | 
			
		||||
        just_authenticated = False
 | 
			
		||||
        if not (token and endpoint):
 | 
			
		||||
            try:
 | 
			
		||||
                token, endpoint = self.auth_plugin.token_and_endpoint(
 | 
			
		||||
                    **filter_args)
 | 
			
		||||
            except exceptions.EndpointException:
 | 
			
		||||
                pass
 | 
			
		||||
            if not (token and endpoint):
 | 
			
		||||
                self.authenticate()
 | 
			
		||||
                just_authenticated = True
 | 
			
		||||
                token, endpoint = self.auth_plugin.token_and_endpoint(
 | 
			
		||||
                    **filter_args)
 | 
			
		||||
                if not (token and endpoint):
 | 
			
		||||
                    raise exceptions.AuthorizationFailure(
 | 
			
		||||
                        _("Cannot find endpoint or token for request"))
 | 
			
		||||
 | 
			
		||||
        old_token_endpoint = (token, endpoint)
 | 
			
		||||
        kwargs.setdefault("headers", {})["X-Auth-Token"] = token
 | 
			
		||||
        self.cached_token = token
 | 
			
		||||
        client.cached_endpoint = endpoint
 | 
			
		||||
        # Perform the request once. If we get Unauthorized, then it
 | 
			
		||||
        # might be because the auth token expired, so try to
 | 
			
		||||
        # re-authenticate and try again. If it still fails, bail.
 | 
			
		||||
        try:
 | 
			
		||||
            return self.request(
 | 
			
		||||
                method, self.concat_url(endpoint, url), **kwargs)
 | 
			
		||||
        except exceptions.Unauthorized as unauth_ex:
 | 
			
		||||
            if just_authenticated:
 | 
			
		||||
                raise
 | 
			
		||||
            self.cached_token = None
 | 
			
		||||
            client.cached_endpoint = None
 | 
			
		||||
            if self.auth_plugin.opts.get('token'):
 | 
			
		||||
                self.auth_plugin.opts['token'] = None
 | 
			
		||||
            if self.auth_plugin.opts.get('endpoint'):
 | 
			
		||||
                self.auth_plugin.opts['endpoint'] = None
 | 
			
		||||
            self.authenticate()
 | 
			
		||||
            try:
 | 
			
		||||
                token, endpoint = self.auth_plugin.token_and_endpoint(
 | 
			
		||||
                    **filter_args)
 | 
			
		||||
            except exceptions.EndpointException:
 | 
			
		||||
                raise unauth_ex
 | 
			
		||||
            if (not (token and endpoint) or
 | 
			
		||||
                    old_token_endpoint == (token, endpoint)):
 | 
			
		||||
                raise unauth_ex
 | 
			
		||||
            self.cached_token = token
 | 
			
		||||
            client.cached_endpoint = endpoint
 | 
			
		||||
            kwargs["headers"]["X-Auth-Token"] = token
 | 
			
		||||
            return self.request(
 | 
			
		||||
                method, self.concat_url(endpoint, url), **kwargs)
 | 
			
		||||
 | 
			
		||||
    def add_client(self, base_client_instance):
 | 
			
		||||
        """Add a new instance of :class:`BaseClient` descendant.
 | 
			
		||||
 | 
			
		||||
        `self` will store a reference to `base_client_instance`.
 | 
			
		||||
 | 
			
		||||
        Example:
 | 
			
		||||
 | 
			
		||||
        >>> def test_clients():
 | 
			
		||||
        ...     from keystoneclient.auth import keystone
 | 
			
		||||
        ...     from openstack.common.apiclient import client
 | 
			
		||||
        ...     auth = keystone.KeystoneAuthPlugin(
 | 
			
		||||
        ...         username="user", password="pass", tenant_name="tenant",
 | 
			
		||||
        ...         auth_url="http://auth:5000/v2.0")
 | 
			
		||||
        ...     openstack_client = client.HTTPClient(auth)
 | 
			
		||||
        ...     # create nova client
 | 
			
		||||
        ...     from novaclient.v1_1 import client
 | 
			
		||||
        ...     client.Client(openstack_client)
 | 
			
		||||
        ...     # create keystone client
 | 
			
		||||
        ...     from keystoneclient.v2_0 import client
 | 
			
		||||
        ...     client.Client(openstack_client)
 | 
			
		||||
        ...     # use them
 | 
			
		||||
        ...     openstack_client.identity.tenants.list()
 | 
			
		||||
        ...     openstack_client.compute.servers.list()
 | 
			
		||||
        """
 | 
			
		||||
        service_type = base_client_instance.service_type
 | 
			
		||||
        if service_type and not hasattr(self, service_type):
 | 
			
		||||
            setattr(self, service_type, base_client_instance)
 | 
			
		||||
 | 
			
		||||
    def authenticate(self):
 | 
			
		||||
        self.auth_plugin.authenticate(self)
 | 
			
		||||
        # Store the authentication results in the keyring for later requests
 | 
			
		||||
        if self.keyring_saver:
 | 
			
		||||
            self.keyring_saver.save(self)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BaseClient(object):
 | 
			
		||||
    """Top-level object to access the OpenStack API.
 | 
			
		||||
 | 
			
		||||
    This client uses :class:`HTTPClient` to send requests. :class:`HTTPClient`
 | 
			
		||||
    will handle a bunch of issues such as authentication.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    service_type = None
 | 
			
		||||
    endpoint_type = None  # "publicURL" will be used
 | 
			
		||||
    cached_endpoint = None
 | 
			
		||||
 | 
			
		||||
    def __init__(self, http_client, extensions=None):
 | 
			
		||||
        self.http_client = http_client
 | 
			
		||||
        http_client.add_client(self)
 | 
			
		||||
 | 
			
		||||
        # Add in any extensions...
 | 
			
		||||
        if extensions:
 | 
			
		||||
            for extension in extensions:
 | 
			
		||||
                if extension.manager_class:
 | 
			
		||||
                    setattr(self, extension.name,
 | 
			
		||||
                            extension.manager_class(self))
 | 
			
		||||
 | 
			
		||||
    def client_request(self, method, url, **kwargs):
 | 
			
		||||
        return self.http_client.client_request(
 | 
			
		||||
            self, method, url, **kwargs)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def last_request_id(self):
 | 
			
		||||
        return self.http_client.last_request_id
 | 
			
		||||
 | 
			
		||||
    def head(self, url, **kwargs):
 | 
			
		||||
        return self.client_request("HEAD", url, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def get(self, url, **kwargs):
 | 
			
		||||
        return self.client_request("GET", url, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def post(self, url, **kwargs):
 | 
			
		||||
        return self.client_request("POST", url, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def put(self, url, **kwargs):
 | 
			
		||||
        return self.client_request("PUT", url, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def delete(self, url, **kwargs):
 | 
			
		||||
        return self.client_request("DELETE", url, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def patch(self, url, **kwargs):
 | 
			
		||||
        return self.client_request("PATCH", url, **kwargs)
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_class(api_name, version, version_map):
 | 
			
		||||
        """Returns the client class for the requested API version
 | 
			
		||||
 | 
			
		||||
        :param api_name: the name of the API, e.g. 'compute', 'image', etc
 | 
			
		||||
        :param version: the requested API version
 | 
			
		||||
        :param version_map: a dict of client classes keyed by version
 | 
			
		||||
        :rtype: a client class for the requested API version
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            client_path = version_map[str(version)]
 | 
			
		||||
        except (KeyError, ValueError):
 | 
			
		||||
            msg = _("Invalid %(api_name)s client version '%(version)s'. "
 | 
			
		||||
                    "Must be one of: %(version_map)s") % {
 | 
			
		||||
                        'api_name': api_name,
 | 
			
		||||
                        'version': version,
 | 
			
		||||
                        'version_map': ', '.join(version_map.keys())}
 | 
			
		||||
            raise exceptions.UnsupportedVersion(msg)
 | 
			
		||||
 | 
			
		||||
        return importutils.import_class(client_path)
 | 
			
		||||
							
								
								
									
										479
									
								
								watcherclient/openstack/common/apiclient/exceptions.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										479
									
								
								watcherclient/openstack/common/apiclient/exceptions.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,479 @@
 | 
			
		||||
# Copyright 2010 Jacob Kaplan-Moss
 | 
			
		||||
# Copyright 2011 Nebula, Inc.
 | 
			
		||||
# Copyright 2013 Alessio Ababilov
 | 
			
		||||
# Copyright 2013 OpenStack Foundation
 | 
			
		||||
# All Rights Reserved.
 | 
			
		||||
#
 | 
			
		||||
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
			
		||||
#    not use this file except in compliance with the License. You may obtain
 | 
			
		||||
#    a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#         http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
#    Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
#    License for the specific language governing permissions and limitations
 | 
			
		||||
#    under the License.
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
Exception definitions.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
########################################################################
 | 
			
		||||
#
 | 
			
		||||
# THIS MODULE IS DEPRECATED
 | 
			
		||||
#
 | 
			
		||||
# Please refer to
 | 
			
		||||
# https://etherpad.openstack.org/p/kilo-watcherclient-library-proposals for
 | 
			
		||||
# the discussion leading to this deprecation.
 | 
			
		||||
#
 | 
			
		||||
# We recommend checking out the python-openstacksdk project
 | 
			
		||||
# (https://launchpad.net/python-openstacksdk) instead.
 | 
			
		||||
#
 | 
			
		||||
########################################################################
 | 
			
		||||
 | 
			
		||||
import inspect
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
import six
 | 
			
		||||
 | 
			
		||||
from watcherclient.openstack.common._i18n import _
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ClientException(Exception):
 | 
			
		||||
    """The base exception class for all exceptions this library raises.
 | 
			
		||||
    """
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ValidationError(ClientException):
 | 
			
		||||
    """Error in validation on API client side."""
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UnsupportedVersion(ClientException):
 | 
			
		||||
    """User is trying to use an unsupported version of the API."""
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CommandError(ClientException):
 | 
			
		||||
    """Error in CLI tool."""
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AuthorizationFailure(ClientException):
 | 
			
		||||
    """Cannot authorize API client."""
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ConnectionError(ClientException):
 | 
			
		||||
    """Cannot connect to API service."""
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ConnectionRefused(ConnectionError):
 | 
			
		||||
    """Connection refused while trying to connect to API service."""
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AuthPluginOptionsMissing(AuthorizationFailure):
 | 
			
		||||
    """Auth plugin misses some options."""
 | 
			
		||||
    def __init__(self, opt_names):
 | 
			
		||||
        super(AuthPluginOptionsMissing, self).__init__(
 | 
			
		||||
            _("Authentication failed. Missing options: %s") %
 | 
			
		||||
            ", ".join(opt_names))
 | 
			
		||||
        self.opt_names = opt_names
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AuthSystemNotFound(AuthorizationFailure):
 | 
			
		||||
    """User has specified an AuthSystem that is not installed."""
 | 
			
		||||
    def __init__(self, auth_system):
 | 
			
		||||
        super(AuthSystemNotFound, self).__init__(
 | 
			
		||||
            _("AuthSystemNotFound: %r") % auth_system)
 | 
			
		||||
        self.auth_system = auth_system
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NoUniqueMatch(ClientException):
 | 
			
		||||
    """Multiple entities found instead of one."""
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EndpointException(ClientException):
 | 
			
		||||
    """Something is rotten in Service Catalog."""
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EndpointNotFound(EndpointException):
 | 
			
		||||
    """Could not find requested endpoint in Service Catalog."""
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AmbiguousEndpoints(EndpointException):
 | 
			
		||||
    """Found more than one matching endpoint in Service Catalog."""
 | 
			
		||||
    def __init__(self, endpoints=None):
 | 
			
		||||
        super(AmbiguousEndpoints, self).__init__(
 | 
			
		||||
            _("AmbiguousEndpoints: %r") % endpoints)
 | 
			
		||||
        self.endpoints = endpoints
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HttpError(ClientException):
 | 
			
		||||
    """The base exception class for all HTTP exceptions.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 0
 | 
			
		||||
    message = _("HTTP Error")
 | 
			
		||||
 | 
			
		||||
    def __init__(self, message=None, details=None,
 | 
			
		||||
                 response=None, request_id=None,
 | 
			
		||||
                 url=None, method=None, http_status=None):
 | 
			
		||||
        self.http_status = http_status or self.http_status
 | 
			
		||||
        self.message = message or self.message
 | 
			
		||||
        self.details = details
 | 
			
		||||
        self.request_id = request_id
 | 
			
		||||
        self.response = response
 | 
			
		||||
        self.url = url
 | 
			
		||||
        self.method = method
 | 
			
		||||
        formatted_string = "%s (HTTP %s)" % (self.message, self.http_status)
 | 
			
		||||
        if request_id:
 | 
			
		||||
            formatted_string += " (Request-ID: %s)" % request_id
 | 
			
		||||
        super(HttpError, self).__init__(formatted_string)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HTTPRedirection(HttpError):
 | 
			
		||||
    """HTTP Redirection."""
 | 
			
		||||
    message = _("HTTP Redirection")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HTTPClientError(HttpError):
 | 
			
		||||
    """Client-side HTTP error.
 | 
			
		||||
 | 
			
		||||
    Exception for cases in which the client seems to have erred.
 | 
			
		||||
    """
 | 
			
		||||
    message = _("HTTP Client Error")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HttpServerError(HttpError):
 | 
			
		||||
    """Server-side HTTP error.
 | 
			
		||||
 | 
			
		||||
    Exception for cases in which the server is aware that it has
 | 
			
		||||
    erred or is incapable of performing the request.
 | 
			
		||||
    """
 | 
			
		||||
    message = _("HTTP Server Error")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MultipleChoices(HTTPRedirection):
 | 
			
		||||
    """HTTP 300 - Multiple Choices.
 | 
			
		||||
 | 
			
		||||
    Indicates multiple options for the resource that the client may follow.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    http_status = 300
 | 
			
		||||
    message = _("Multiple Choices")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BadRequest(HTTPClientError):
 | 
			
		||||
    """HTTP 400 - Bad Request.
 | 
			
		||||
 | 
			
		||||
    The request cannot be fulfilled due to bad syntax.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 400
 | 
			
		||||
    message = _("Bad Request")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Unauthorized(HTTPClientError):
 | 
			
		||||
    """HTTP 401 - Unauthorized.
 | 
			
		||||
 | 
			
		||||
    Similar to 403 Forbidden, but specifically for use when authentication
 | 
			
		||||
    is required and has failed or has not yet been provided.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 401
 | 
			
		||||
    message = _("Unauthorized")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PaymentRequired(HTTPClientError):
 | 
			
		||||
    """HTTP 402 - Payment Required.
 | 
			
		||||
 | 
			
		||||
    Reserved for future use.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 402
 | 
			
		||||
    message = _("Payment Required")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Forbidden(HTTPClientError):
 | 
			
		||||
    """HTTP 403 - Forbidden.
 | 
			
		||||
 | 
			
		||||
    The request was a valid request, but the server is refusing to respond
 | 
			
		||||
    to it.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 403
 | 
			
		||||
    message = _("Forbidden")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NotFound(HTTPClientError):
 | 
			
		||||
    """HTTP 404 - Not Found.
 | 
			
		||||
 | 
			
		||||
    The requested resource could not be found but may be available again
 | 
			
		||||
    in the future.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 404
 | 
			
		||||
    message = _("Not Found")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MethodNotAllowed(HTTPClientError):
 | 
			
		||||
    """HTTP 405 - Method Not Allowed.
 | 
			
		||||
 | 
			
		||||
    A request was made of a resource using a request method not supported
 | 
			
		||||
    by that resource.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 405
 | 
			
		||||
    message = _("Method Not Allowed")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NotAcceptable(HTTPClientError):
 | 
			
		||||
    """HTTP 406 - Not Acceptable.
 | 
			
		||||
 | 
			
		||||
    The requested resource is only capable of generating content not
 | 
			
		||||
    acceptable according to the Accept headers sent in the request.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 406
 | 
			
		||||
    message = _("Not Acceptable")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ProxyAuthenticationRequired(HTTPClientError):
 | 
			
		||||
    """HTTP 407 - Proxy Authentication Required.
 | 
			
		||||
 | 
			
		||||
    The client must first authenticate itself with the proxy.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 407
 | 
			
		||||
    message = _("Proxy Authentication Required")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RequestTimeout(HTTPClientError):
 | 
			
		||||
    """HTTP 408 - Request Timeout.
 | 
			
		||||
 | 
			
		||||
    The server timed out waiting for the request.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 408
 | 
			
		||||
    message = _("Request Timeout")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Conflict(HTTPClientError):
 | 
			
		||||
    """HTTP 409 - Conflict.
 | 
			
		||||
 | 
			
		||||
    Indicates that the request could not be processed because of conflict
 | 
			
		||||
    in the request, such as an edit conflict.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 409
 | 
			
		||||
    message = _("Conflict")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Gone(HTTPClientError):
 | 
			
		||||
    """HTTP 410 - Gone.
 | 
			
		||||
 | 
			
		||||
    Indicates that the resource requested is no longer available and will
 | 
			
		||||
    not be available again.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 410
 | 
			
		||||
    message = _("Gone")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LengthRequired(HTTPClientError):
 | 
			
		||||
    """HTTP 411 - Length Required.
 | 
			
		||||
 | 
			
		||||
    The request did not specify the length of its content, which is
 | 
			
		||||
    required by the requested resource.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 411
 | 
			
		||||
    message = _("Length Required")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PreconditionFailed(HTTPClientError):
 | 
			
		||||
    """HTTP 412 - Precondition Failed.
 | 
			
		||||
 | 
			
		||||
    The server does not meet one of the preconditions that the requester
 | 
			
		||||
    put on the request.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 412
 | 
			
		||||
    message = _("Precondition Failed")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RequestEntityTooLarge(HTTPClientError):
 | 
			
		||||
    """HTTP 413 - Request Entity Too Large.
 | 
			
		||||
 | 
			
		||||
    The request is larger than the server is willing or able to process.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 413
 | 
			
		||||
    message = _("Request Entity Too Large")
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        try:
 | 
			
		||||
            self.retry_after = int(kwargs.pop('retry_after'))
 | 
			
		||||
        except (KeyError, ValueError):
 | 
			
		||||
            self.retry_after = 0
 | 
			
		||||
 | 
			
		||||
        super(RequestEntityTooLarge, self).__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RequestUriTooLong(HTTPClientError):
 | 
			
		||||
    """HTTP 414 - Request-URI Too Long.
 | 
			
		||||
 | 
			
		||||
    The URI provided was too long for the server to process.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 414
 | 
			
		||||
    message = _("Request-URI Too Long")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UnsupportedMediaType(HTTPClientError):
 | 
			
		||||
    """HTTP 415 - Unsupported Media Type.
 | 
			
		||||
 | 
			
		||||
    The request entity has a media type which the server or resource does
 | 
			
		||||
    not support.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 415
 | 
			
		||||
    message = _("Unsupported Media Type")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RequestedRangeNotSatisfiable(HTTPClientError):
 | 
			
		||||
    """HTTP 416 - Requested Range Not Satisfiable.
 | 
			
		||||
 | 
			
		||||
    The client has asked for a portion of the file, but the server cannot
 | 
			
		||||
    supply that portion.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 416
 | 
			
		||||
    message = _("Requested Range Not Satisfiable")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ExpectationFailed(HTTPClientError):
 | 
			
		||||
    """HTTP 417 - Expectation Failed.
 | 
			
		||||
 | 
			
		||||
    The server cannot meet the requirements of the Expect request-header field.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 417
 | 
			
		||||
    message = _("Expectation Failed")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UnprocessableEntity(HTTPClientError):
 | 
			
		||||
    """HTTP 422 - Unprocessable Entity.
 | 
			
		||||
 | 
			
		||||
    The request was well-formed but was unable to be followed due to semantic
 | 
			
		||||
    errors.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 422
 | 
			
		||||
    message = _("Unprocessable Entity")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class InternalServerError(HttpServerError):
 | 
			
		||||
    """HTTP 500 - Internal Server Error.
 | 
			
		||||
 | 
			
		||||
    A generic error message, given when no more specific message is suitable.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 500
 | 
			
		||||
    message = _("Internal Server Error")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# NotImplemented is a python keyword.
 | 
			
		||||
class HttpNotImplemented(HttpServerError):
 | 
			
		||||
    """HTTP 501 - Not Implemented.
 | 
			
		||||
 | 
			
		||||
    The server either does not recognize the request method, or it lacks
 | 
			
		||||
    the ability to fulfill the request.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 501
 | 
			
		||||
    message = _("Not Implemented")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BadGateway(HttpServerError):
 | 
			
		||||
    """HTTP 502 - Bad Gateway.
 | 
			
		||||
 | 
			
		||||
    The server was acting as a gateway or proxy and received an invalid
 | 
			
		||||
    response from the upstream server.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 502
 | 
			
		||||
    message = _("Bad Gateway")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ServiceUnavailable(HttpServerError):
 | 
			
		||||
    """HTTP 503 - Service Unavailable.
 | 
			
		||||
 | 
			
		||||
    The server is currently unavailable.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 503
 | 
			
		||||
    message = _("Service Unavailable")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class GatewayTimeout(HttpServerError):
 | 
			
		||||
    """HTTP 504 - Gateway Timeout.
 | 
			
		||||
 | 
			
		||||
    The server was acting as a gateway or proxy and did not receive a timely
 | 
			
		||||
    response from the upstream server.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 504
 | 
			
		||||
    message = _("Gateway Timeout")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HttpVersionNotSupported(HttpServerError):
 | 
			
		||||
    """HTTP 505 - HttpVersion Not Supported.
 | 
			
		||||
 | 
			
		||||
    The server does not support the HTTP protocol version used in the request.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 505
 | 
			
		||||
    message = _("HTTP Version Not Supported")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# _code_map contains all the classes that have http_status attribute.
 | 
			
		||||
_code_map = dict(
 | 
			
		||||
    (getattr(obj, 'http_status', None), obj)
 | 
			
		||||
    for name, obj in six.iteritems(vars(sys.modules[__name__]))
 | 
			
		||||
    if inspect.isclass(obj) and getattr(obj, 'http_status', False)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def from_response(response, method, url):
 | 
			
		||||
    """Returns an instance of :class:`HttpError` or subclass based on response.
 | 
			
		||||
 | 
			
		||||
    :param response: instance of `requests.Response` class
 | 
			
		||||
    :param method: HTTP method used for request
 | 
			
		||||
    :param url: URL used for request
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    req_id = response.headers.get("x-openstack-request-id")
 | 
			
		||||
    # NOTE(hdd) true for older versions of nova and cinder
 | 
			
		||||
    if not req_id:
 | 
			
		||||
        req_id = response.headers.get("x-compute-request-id")
 | 
			
		||||
    kwargs = {
 | 
			
		||||
        "http_status": response.status_code,
 | 
			
		||||
        "response": response,
 | 
			
		||||
        "method": method,
 | 
			
		||||
        "url": url,
 | 
			
		||||
        "request_id": req_id,
 | 
			
		||||
    }
 | 
			
		||||
    if "retry-after" in response.headers:
 | 
			
		||||
        kwargs["retry_after"] = response.headers["retry-after"]
 | 
			
		||||
 | 
			
		||||
    content_type = response.headers.get("Content-Type", "")
 | 
			
		||||
    if content_type.startswith("application/json"):
 | 
			
		||||
        try:
 | 
			
		||||
            body = response.json()
 | 
			
		||||
        except ValueError:
 | 
			
		||||
            pass
 | 
			
		||||
        else:
 | 
			
		||||
            if isinstance(body, dict):
 | 
			
		||||
                error = body.get(list(body)[0])
 | 
			
		||||
                if isinstance(error, dict):
 | 
			
		||||
                    kwargs["message"] = (error.get("message") or
 | 
			
		||||
                                         error.get("faultstring"))
 | 
			
		||||
                    kwargs["details"] = (error.get("details") or
 | 
			
		||||
                                         six.text_type(body))
 | 
			
		||||
    elif content_type.startswith("text/"):
 | 
			
		||||
        kwargs["details"] = response.text
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        cls = _code_map[response.status_code]
 | 
			
		||||
    except KeyError:
 | 
			
		||||
        if 500 <= response.status_code < 600:
 | 
			
		||||
            cls = HttpServerError
 | 
			
		||||
        elif 400 <= response.status_code < 500:
 | 
			
		||||
            cls = HTTPClientError
 | 
			
		||||
        else:
 | 
			
		||||
            cls = HttpError
 | 
			
		||||
    return cls(**kwargs)
 | 
			
		||||
							
								
								
									
										190
									
								
								watcherclient/openstack/common/apiclient/fake_client.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										190
									
								
								watcherclient/openstack/common/apiclient/fake_client.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,190 @@
 | 
			
		||||
# Copyright 2013 OpenStack Foundation
 | 
			
		||||
# All Rights Reserved.
 | 
			
		||||
#
 | 
			
		||||
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
			
		||||
#    not use this file except in compliance with the License. You may obtain
 | 
			
		||||
#    a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#         http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
#    Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
#    License for the specific language governing permissions and limitations
 | 
			
		||||
#    under the License.
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
A fake server that "responds" to API methods with pre-canned responses.
 | 
			
		||||
 | 
			
		||||
All of these responses come from the spec, so if for some reason the spec's
 | 
			
		||||
wrong the tests might raise AssertionError. I've indicated in comments the
 | 
			
		||||
places where actual behavior differs from the spec.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
########################################################################
 | 
			
		||||
#
 | 
			
		||||
# THIS MODULE IS DEPRECATED
 | 
			
		||||
#
 | 
			
		||||
# Please refer to
 | 
			
		||||
# https://etherpad.openstack.org/p/kilo-watcherclient-library-proposals for
 | 
			
		||||
# the discussion leading to this deprecation.
 | 
			
		||||
#
 | 
			
		||||
# We recommend checking out the python-openstacksdk project
 | 
			
		||||
# (https://launchpad.net/python-openstacksdk) instead.
 | 
			
		||||
#
 | 
			
		||||
########################################################################
 | 
			
		||||
 | 
			
		||||
# W0102: Dangerous default value %s as argument
 | 
			
		||||
# pylint: disable=W0102
 | 
			
		||||
 | 
			
		||||
import json
 | 
			
		||||
 | 
			
		||||
import requests
 | 
			
		||||
import six
 | 
			
		||||
from six.moves.urllib import parse
 | 
			
		||||
 | 
			
		||||
from watcherclient.openstack.common.apiclient import client
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def assert_has_keys(dct, required=None, optional=None):
 | 
			
		||||
    required = required or []
 | 
			
		||||
    optional = optional or []
 | 
			
		||||
    for k in required:
 | 
			
		||||
        try:
 | 
			
		||||
            assert k in dct
 | 
			
		||||
        except AssertionError:
 | 
			
		||||
            extra_keys = set(dct.keys()).difference(set(required + optional))
 | 
			
		||||
            raise AssertionError("found unexpected keys: %s" %
 | 
			
		||||
                                 list(extra_keys))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestResponse(requests.Response):
 | 
			
		||||
    """Wrap requests.Response and provide a convenient initialization.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, data):
 | 
			
		||||
        super(TestResponse, self).__init__()
 | 
			
		||||
        self._content_consumed = True
 | 
			
		||||
        if isinstance(data, dict):
 | 
			
		||||
            self.status_code = data.get('status_code', 200)
 | 
			
		||||
            # Fake the text attribute to streamline Response creation
 | 
			
		||||
            text = data.get('text', "")
 | 
			
		||||
            if isinstance(text, (dict, list)):
 | 
			
		||||
                self._content = json.dumps(text)
 | 
			
		||||
                default_headers = {
 | 
			
		||||
                    "Content-Type": "application/json",
 | 
			
		||||
                }
 | 
			
		||||
            else:
 | 
			
		||||
                self._content = text
 | 
			
		||||
                default_headers = {}
 | 
			
		||||
            if six.PY3 and isinstance(self._content, six.string_types):
 | 
			
		||||
                self._content = self._content.encode('utf-8', 'strict')
 | 
			
		||||
            self.headers = data.get('headers') or default_headers
 | 
			
		||||
        else:
 | 
			
		||||
            self.status_code = data
 | 
			
		||||
 | 
			
		||||
    def __eq__(self, other):
 | 
			
		||||
        return (self.status_code == other.status_code and
 | 
			
		||||
                self.headers == other.headers and
 | 
			
		||||
                self._content == other._content)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FakeHTTPClient(client.HTTPClient):
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        self.callstack = []
 | 
			
		||||
        self.fixtures = kwargs.pop("fixtures", None) or {}
 | 
			
		||||
        if not args and "auth_plugin" not in kwargs:
 | 
			
		||||
            args = (None, )
 | 
			
		||||
        super(FakeHTTPClient, self).__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def assert_called(self, method, url, body=None, pos=-1):
 | 
			
		||||
        """Assert than an API method was just called.
 | 
			
		||||
        """
 | 
			
		||||
        expected = (method, url)
 | 
			
		||||
        called = self.callstack[pos][0:2]
 | 
			
		||||
        assert self.callstack, \
 | 
			
		||||
            "Expected %s %s but no calls were made." % expected
 | 
			
		||||
 | 
			
		||||
        assert expected == called, 'Expected %s %s; got %s %s' % \
 | 
			
		||||
            (expected + called)
 | 
			
		||||
 | 
			
		||||
        if body is not None:
 | 
			
		||||
            if self.callstack[pos][3] != body:
 | 
			
		||||
                raise AssertionError('%r != %r' %
 | 
			
		||||
                                     (self.callstack[pos][3], body))
 | 
			
		||||
 | 
			
		||||
    def assert_called_anytime(self, method, url, body=None):
 | 
			
		||||
        """Assert than an API method was called anytime in the test.
 | 
			
		||||
        """
 | 
			
		||||
        expected = (method, url)
 | 
			
		||||
 | 
			
		||||
        assert self.callstack, \
 | 
			
		||||
            "Expected %s %s but no calls were made." % expected
 | 
			
		||||
 | 
			
		||||
        found = False
 | 
			
		||||
        entry = None
 | 
			
		||||
        for entry in self.callstack:
 | 
			
		||||
            if expected == entry[0:2]:
 | 
			
		||||
                found = True
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
        assert found, 'Expected %s %s; got %s' % \
 | 
			
		||||
            (method, url, self.callstack)
 | 
			
		||||
        if body is not None:
 | 
			
		||||
            assert entry[3] == body, "%s != %s" % (entry[3], body)
 | 
			
		||||
 | 
			
		||||
        self.callstack = []
 | 
			
		||||
 | 
			
		||||
    def clear_callstack(self):
 | 
			
		||||
        self.callstack = []
 | 
			
		||||
 | 
			
		||||
    def authenticate(self):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def client_request(self, client, method, url, **kwargs):
 | 
			
		||||
        # Check that certain things are called correctly
 | 
			
		||||
        if method in ["GET", "DELETE"]:
 | 
			
		||||
            assert "json" not in kwargs
 | 
			
		||||
 | 
			
		||||
        # Note the call
 | 
			
		||||
        self.callstack.append(
 | 
			
		||||
            (method,
 | 
			
		||||
             url,
 | 
			
		||||
             kwargs.get("headers") or {},
 | 
			
		||||
             kwargs.get("json") or kwargs.get("data")))
 | 
			
		||||
        try:
 | 
			
		||||
            fixture = self.fixtures[url][method]
 | 
			
		||||
        except KeyError:
 | 
			
		||||
            pass
 | 
			
		||||
        else:
 | 
			
		||||
            return TestResponse({"headers": fixture[0],
 | 
			
		||||
                                 "text": fixture[1]})
 | 
			
		||||
 | 
			
		||||
        # Call the method
 | 
			
		||||
        args = parse.parse_qsl(parse.urlparse(url)[4])
 | 
			
		||||
        kwargs.update(args)
 | 
			
		||||
        munged_url = url.rsplit('?', 1)[0]
 | 
			
		||||
        munged_url = munged_url.strip('/').replace('/', '_').replace('.', '_')
 | 
			
		||||
        munged_url = munged_url.replace('-', '_')
 | 
			
		||||
 | 
			
		||||
        callback = "%s_%s" % (method.lower(), munged_url)
 | 
			
		||||
 | 
			
		||||
        if not hasattr(self, callback):
 | 
			
		||||
            raise AssertionError('Called unknown API method: %s %s, '
 | 
			
		||||
                                 'expected fakes method name: %s' %
 | 
			
		||||
                                 (method, url, callback))
 | 
			
		||||
 | 
			
		||||
        resp = getattr(self, callback)(**kwargs)
 | 
			
		||||
        if len(resp) == 3:
 | 
			
		||||
            status, headers, body = resp
 | 
			
		||||
        else:
 | 
			
		||||
            status, body = resp
 | 
			
		||||
            headers = {}
 | 
			
		||||
        self.last_request_id = headers.get('x-openstack-request-id',
 | 
			
		||||
                                           'req-test')
 | 
			
		||||
        return TestResponse({
 | 
			
		||||
            "status_code": status,
 | 
			
		||||
            "text": body,
 | 
			
		||||
            "headers": headers,
 | 
			
		||||
        })
 | 
			
		||||
							
								
								
									
										100
									
								
								watcherclient/openstack/common/apiclient/utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								watcherclient/openstack/common/apiclient/utils.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,100 @@
 | 
			
		||||
#
 | 
			
		||||
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
			
		||||
#    not use this file except in compliance with the License. You may obtain
 | 
			
		||||
#    a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#         http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
#    Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
#    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 MODULE IS DEPRECATED
 | 
			
		||||
#
 | 
			
		||||
# Please refer to
 | 
			
		||||
# https://etherpad.openstack.org/p/kilo-watcherclient-library-proposals for
 | 
			
		||||
# the discussion leading to this deprecation.
 | 
			
		||||
#
 | 
			
		||||
# We recommend checking out the python-openstacksdk project
 | 
			
		||||
# (https://launchpad.net/python-openstacksdk) instead.
 | 
			
		||||
#
 | 
			
		||||
########################################################################
 | 
			
		||||
 | 
			
		||||
from oslo_utils import encodeutils
 | 
			
		||||
from oslo_utils import uuidutils
 | 
			
		||||
import six
 | 
			
		||||
 | 
			
		||||
from watcherclient.openstack.common._i18n import _
 | 
			
		||||
from watcherclient.openstack.common.apiclient import exceptions
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def find_resource(manager, name_or_id, **find_args):
 | 
			
		||||
    """Look for resource in a given manager.
 | 
			
		||||
 | 
			
		||||
    Used as a helper for the _find_* methods.
 | 
			
		||||
    Example:
 | 
			
		||||
 | 
			
		||||
    .. code-block:: python
 | 
			
		||||
 | 
			
		||||
        def _find_hypervisor(cs, hypervisor):
 | 
			
		||||
            #Get a hypervisor by name or ID.
 | 
			
		||||
            return cliutils.find_resource(cs.hypervisors, hypervisor)
 | 
			
		||||
    """
 | 
			
		||||
    # first try to get entity as integer id
 | 
			
		||||
    try:
 | 
			
		||||
        return manager.get(int(name_or_id))
 | 
			
		||||
    except (TypeError, ValueError, exceptions.NotFound):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    # now try to get entity as uuid
 | 
			
		||||
    try:
 | 
			
		||||
        if six.PY2:
 | 
			
		||||
            tmp_id = encodeutils.safe_encode(name_or_id)
 | 
			
		||||
        else:
 | 
			
		||||
            tmp_id = encodeutils.safe_decode(name_or_id)
 | 
			
		||||
 | 
			
		||||
        if uuidutils.is_uuid_like(tmp_id):
 | 
			
		||||
            return manager.get(tmp_id)
 | 
			
		||||
    except (TypeError, ValueError, exceptions.NotFound):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    # for str id which is not uuid
 | 
			
		||||
    if getattr(manager, 'is_alphanum_id_allowed', False):
 | 
			
		||||
        try:
 | 
			
		||||
            return manager.get(name_or_id)
 | 
			
		||||
        except exceptions.NotFound:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        try:
 | 
			
		||||
            return manager.find(human_id=name_or_id, **find_args)
 | 
			
		||||
        except exceptions.NotFound:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        # finally try to find entity by name
 | 
			
		||||
        try:
 | 
			
		||||
            resource = getattr(manager, 'resource_class', None)
 | 
			
		||||
            name_attr = resource.NAME_ATTR if resource else 'name'
 | 
			
		||||
            kwargs = {name_attr: name_or_id}
 | 
			
		||||
            kwargs.update(find_args)
 | 
			
		||||
            return manager.find(**kwargs)
 | 
			
		||||
        except exceptions.NotFound:
 | 
			
		||||
            msg = _("No %(name)s with a name or "
 | 
			
		||||
                    "ID of '%(name_or_id)s' exists.") % \
 | 
			
		||||
                {
 | 
			
		||||
                    "name": manager.resource_class.__name__.lower(),
 | 
			
		||||
                    "name_or_id": name_or_id
 | 
			
		||||
                }
 | 
			
		||||
            raise exceptions.CommandError(msg)
 | 
			
		||||
    except exceptions.NoUniqueMatch:
 | 
			
		||||
        msg = _("Multiple %(name)s matches found for "
 | 
			
		||||
                "'%(name_or_id)s', use an ID to be more specific.") % \
 | 
			
		||||
            {
 | 
			
		||||
                "name": manager.resource_class.__name__.lower(),
 | 
			
		||||
                "name_or_id": name_or_id
 | 
			
		||||
            }
 | 
			
		||||
        raise exceptions.CommandError(msg)
 | 
			
		||||
							
								
								
									
										271
									
								
								watcherclient/openstack/common/cliutils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										271
									
								
								watcherclient/openstack/common/cliutils.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,271 @@
 | 
			
		||||
# Copyright 2012 Red Hat, Inc.
 | 
			
		||||
#
 | 
			
		||||
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
			
		||||
#    not use this file except in compliance with the License. You may obtain
 | 
			
		||||
#    a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#         http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
#    Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
#    License for the specific language governing permissions and limitations
 | 
			
		||||
#    under the License.
 | 
			
		||||
 | 
			
		||||
# W0603: Using the global statement
 | 
			
		||||
# W0621: Redefining name %s from outer scope
 | 
			
		||||
# pylint: disable=W0603,W0621
 | 
			
		||||
 | 
			
		||||
from __future__ import print_function
 | 
			
		||||
 | 
			
		||||
import getpass
 | 
			
		||||
import inspect
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
import textwrap
 | 
			
		||||
 | 
			
		||||
from oslo_utils import encodeutils
 | 
			
		||||
from oslo_utils import strutils
 | 
			
		||||
import prettytable
 | 
			
		||||
import six
 | 
			
		||||
from six import moves
 | 
			
		||||
 | 
			
		||||
from watcherclient.openstack.common._i18n import _
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MissingArgs(Exception):
 | 
			
		||||
    """Supplied arguments are not sufficient for calling a function."""
 | 
			
		||||
    def __init__(self, missing):
 | 
			
		||||
        self.missing = missing
 | 
			
		||||
        msg = _("Missing arguments: %s") % ", ".join(missing)
 | 
			
		||||
        super(MissingArgs, self).__init__(msg)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_args(fn, *args, **kwargs):
 | 
			
		||||
    """Check that the supplied args are sufficient for calling a function.
 | 
			
		||||
 | 
			
		||||
    >>> validate_args(lambda a: None)
 | 
			
		||||
    Traceback (most recent call last):
 | 
			
		||||
        ...
 | 
			
		||||
    MissingArgs: Missing argument(s): a
 | 
			
		||||
    >>> validate_args(lambda a, b, c, d: None, 0, c=1)
 | 
			
		||||
    Traceback (most recent call last):
 | 
			
		||||
        ...
 | 
			
		||||
    MissingArgs: Missing argument(s): b, d
 | 
			
		||||
 | 
			
		||||
    :param fn: the function to check
 | 
			
		||||
    :param arg: the positional arguments supplied
 | 
			
		||||
    :param kwargs: the keyword arguments supplied
 | 
			
		||||
    """
 | 
			
		||||
    argspec = inspect.getargspec(fn)
 | 
			
		||||
 | 
			
		||||
    num_defaults = len(argspec.defaults or [])
 | 
			
		||||
    required_args = argspec.args[:len(argspec.args) - num_defaults]
 | 
			
		||||
 | 
			
		||||
    def isbound(method):
 | 
			
		||||
        return getattr(method, '__self__', None) is not None
 | 
			
		||||
 | 
			
		||||
    if isbound(fn):
 | 
			
		||||
        required_args.pop(0)
 | 
			
		||||
 | 
			
		||||
    missing = [arg for arg in required_args if arg not in kwargs]
 | 
			
		||||
    missing = missing[len(args):]
 | 
			
		||||
    if missing:
 | 
			
		||||
        raise MissingArgs(missing)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def arg(*args, **kwargs):
 | 
			
		||||
    """Decorator for CLI args.
 | 
			
		||||
 | 
			
		||||
    Example:
 | 
			
		||||
 | 
			
		||||
    >>> @arg("name", help="Name of the new entity")
 | 
			
		||||
    ... def entity_create(args):
 | 
			
		||||
    ...     pass
 | 
			
		||||
    """
 | 
			
		||||
    def _decorator(func):
 | 
			
		||||
        add_arg(func, *args, **kwargs)
 | 
			
		||||
        return func
 | 
			
		||||
    return _decorator
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def env(*args, **kwargs):
 | 
			
		||||
    """Returns the first environment variable set.
 | 
			
		||||
 | 
			
		||||
    If all are empty, defaults to '' or keyword arg `default`.
 | 
			
		||||
    """
 | 
			
		||||
    for arg in args:
 | 
			
		||||
        value = os.environ.get(arg)
 | 
			
		||||
        if value:
 | 
			
		||||
            return value
 | 
			
		||||
    return kwargs.get('default', '')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def add_arg(func, *args, **kwargs):
 | 
			
		||||
    """Bind CLI arguments to a shell.py `do_foo` function."""
 | 
			
		||||
 | 
			
		||||
    if not hasattr(func, 'arguments'):
 | 
			
		||||
        func.arguments = []
 | 
			
		||||
 | 
			
		||||
    # NOTE(sirp): avoid dups that can occur when the module is shared across
 | 
			
		||||
    # tests.
 | 
			
		||||
    if (args, kwargs) not in func.arguments:
 | 
			
		||||
        # Because of the semantics of decorator composition if we just append
 | 
			
		||||
        # to the options list positional options will appear to be backwards.
 | 
			
		||||
        func.arguments.insert(0, (args, kwargs))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def unauthenticated(func):
 | 
			
		||||
    """Adds 'unauthenticated' attribute to decorated function.
 | 
			
		||||
 | 
			
		||||
    Usage:
 | 
			
		||||
 | 
			
		||||
    >>> @unauthenticated
 | 
			
		||||
    ... def mymethod(f):
 | 
			
		||||
    ...     pass
 | 
			
		||||
    """
 | 
			
		||||
    func.unauthenticated = True
 | 
			
		||||
    return func
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def isunauthenticated(func):
 | 
			
		||||
    """Checks if the function does not require authentication.
 | 
			
		||||
 | 
			
		||||
    Mark such functions with the `@unauthenticated` decorator.
 | 
			
		||||
 | 
			
		||||
    :returns: bool
 | 
			
		||||
    """
 | 
			
		||||
    return getattr(func, 'unauthenticated', False)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def print_list(objs, fields, formatters=None, sortby_index=0,
 | 
			
		||||
               mixed_case_fields=None, field_labels=None):
 | 
			
		||||
    """Print a list or objects as a table, one row per object.
 | 
			
		||||
 | 
			
		||||
    :param objs: iterable of :class:`Resource`
 | 
			
		||||
    :param fields: attributes that correspond to columns, in order
 | 
			
		||||
    :param formatters: `dict` of callables for field formatting
 | 
			
		||||
    :param sortby_index: index of the field for sorting table rows
 | 
			
		||||
    :param mixed_case_fields: fields corresponding to object attributes that
 | 
			
		||||
        have mixed case names (e.g., 'serverId')
 | 
			
		||||
    :param field_labels: Labels to use in the heading of the table, default to
 | 
			
		||||
        fields.
 | 
			
		||||
    """
 | 
			
		||||
    formatters = formatters or {}
 | 
			
		||||
    mixed_case_fields = mixed_case_fields or []
 | 
			
		||||
    field_labels = field_labels or fields
 | 
			
		||||
    if len(field_labels) != len(fields):
 | 
			
		||||
        raise ValueError(_("Field labels list %(labels)s has different number "
 | 
			
		||||
                           "of elements than fields list %(fields)s"),
 | 
			
		||||
                         {'labels': field_labels, 'fields': fields})
 | 
			
		||||
 | 
			
		||||
    if sortby_index is None:
 | 
			
		||||
        kwargs = {}
 | 
			
		||||
    else:
 | 
			
		||||
        kwargs = {'sortby': field_labels[sortby_index]}
 | 
			
		||||
    pt = prettytable.PrettyTable(field_labels)
 | 
			
		||||
    pt.align = 'l'
 | 
			
		||||
 | 
			
		||||
    for o in objs:
 | 
			
		||||
        row = []
 | 
			
		||||
        for field in fields:
 | 
			
		||||
            if field in formatters:
 | 
			
		||||
                row.append(formatters[field](o))
 | 
			
		||||
            else:
 | 
			
		||||
                if field in mixed_case_fields:
 | 
			
		||||
                    field_name = field.replace(' ', '_')
 | 
			
		||||
                else:
 | 
			
		||||
                    field_name = field.lower().replace(' ', '_')
 | 
			
		||||
                data = getattr(o, field_name, '')
 | 
			
		||||
                row.append(data)
 | 
			
		||||
        pt.add_row(row)
 | 
			
		||||
 | 
			
		||||
    if six.PY3:
 | 
			
		||||
        print(encodeutils.safe_encode(pt.get_string(**kwargs)).decode())
 | 
			
		||||
    else:
 | 
			
		||||
        print(encodeutils.safe_encode(pt.get_string(**kwargs)))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def print_dict(dct, dict_property="Property", wrap=0):
 | 
			
		||||
    """Print a `dict` as a table of two columns.
 | 
			
		||||
 | 
			
		||||
    :param dct: `dict` to print
 | 
			
		||||
    :param dict_property: name of the first column
 | 
			
		||||
    :param wrap: wrapping for the second column
 | 
			
		||||
    """
 | 
			
		||||
    pt = prettytable.PrettyTable([dict_property, 'Value'])
 | 
			
		||||
    pt.align = 'l'
 | 
			
		||||
    for k, v in six.iteritems(dct):
 | 
			
		||||
        # convert dict to str to check length
 | 
			
		||||
        if isinstance(v, dict):
 | 
			
		||||
            v = six.text_type(v)
 | 
			
		||||
        if wrap > 0:
 | 
			
		||||
            v = textwrap.fill(six.text_type(v), wrap)
 | 
			
		||||
        # if value has a newline, add in multiple rows
 | 
			
		||||
        # e.g. fault with stacktrace
 | 
			
		||||
        if v and isinstance(v, six.string_types) and r'\n' in v:
 | 
			
		||||
            lines = v.strip().split(r'\n')
 | 
			
		||||
            col1 = k
 | 
			
		||||
            for line in lines:
 | 
			
		||||
                pt.add_row([col1, line])
 | 
			
		||||
                col1 = ''
 | 
			
		||||
        else:
 | 
			
		||||
            pt.add_row([k, v])
 | 
			
		||||
 | 
			
		||||
    if six.PY3:
 | 
			
		||||
        print(encodeutils.safe_encode(pt.get_string()).decode())
 | 
			
		||||
    else:
 | 
			
		||||
        print(encodeutils.safe_encode(pt.get_string()))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_password(max_password_prompts=3):
 | 
			
		||||
    """Read password from TTY."""
 | 
			
		||||
    verify = strutils.bool_from_string(env("OS_VERIFY_PASSWORD"))
 | 
			
		||||
    pw = None
 | 
			
		||||
    if hasattr(sys.stdin, "isatty") and sys.stdin.isatty():
 | 
			
		||||
        # Check for Ctrl-D
 | 
			
		||||
        try:
 | 
			
		||||
            for __ in moves.range(max_password_prompts):
 | 
			
		||||
                pw1 = getpass.getpass("OS Password: ")
 | 
			
		||||
                if verify:
 | 
			
		||||
                    pw2 = getpass.getpass("Please verify: ")
 | 
			
		||||
                else:
 | 
			
		||||
                    pw2 = pw1
 | 
			
		||||
                if pw1 == pw2 and pw1:
 | 
			
		||||
                    pw = pw1
 | 
			
		||||
                    break
 | 
			
		||||
        except EOFError:
 | 
			
		||||
            pass
 | 
			
		||||
    return pw
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def service_type(stype):
 | 
			
		||||
    """Adds 'service_type' attribute to decorated function.
 | 
			
		||||
 | 
			
		||||
    Usage:
 | 
			
		||||
 | 
			
		||||
    .. code-block:: python
 | 
			
		||||
 | 
			
		||||
       @service_type('volume')
 | 
			
		||||
       def mymethod(f):
 | 
			
		||||
       ...
 | 
			
		||||
    """
 | 
			
		||||
    def inner(f):
 | 
			
		||||
        f.service_type = stype
 | 
			
		||||
        return f
 | 
			
		||||
    return inner
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_service_type(f):
 | 
			
		||||
    """Retrieves service type from function."""
 | 
			
		||||
    return getattr(f, 'service_type', None)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def pretty_choice_list(l):
 | 
			
		||||
    return ', '.join("'%s'" % i for i in l)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def exit(msg=''):
 | 
			
		||||
    if msg:
 | 
			
		||||
        print (msg, file=sys.stderr)
 | 
			
		||||
    sys.exit(1)
 | 
			
		||||
							
								
								
									
										479
									
								
								watcherclient/openstack/common/gettextutils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										479
									
								
								watcherclient/openstack/common/gettextutils.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,479 @@
 | 
			
		||||
# Copyright 2012 Red Hat, Inc.
 | 
			
		||||
# Copyright 2013 IBM Corp.
 | 
			
		||||
# 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.
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
gettext for openstack-common modules.
 | 
			
		||||
 | 
			
		||||
Usual usage in an openstack.common module:
 | 
			
		||||
 | 
			
		||||
    from watcherclient.openstack.common.gettextutils import _
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import copy
 | 
			
		||||
import gettext
 | 
			
		||||
import locale
 | 
			
		||||
from logging import handlers
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
from babel import localedata
 | 
			
		||||
import six
 | 
			
		||||
 | 
			
		||||
_AVAILABLE_LANGUAGES = {}
 | 
			
		||||
 | 
			
		||||
# FIXME(dhellmann): Remove this when moving to oslo.i18n.
 | 
			
		||||
USE_LAZY = False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TranslatorFactory(object):
 | 
			
		||||
    """Create translator functions
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, domain, localedir=None):
 | 
			
		||||
        """Establish a set of translation functions for the domain.
 | 
			
		||||
 | 
			
		||||
        :param domain: Name of translation domain,
 | 
			
		||||
                       specifying a message catalog.
 | 
			
		||||
        :type domain: str
 | 
			
		||||
        :param lazy: Delays translation until a message is emitted.
 | 
			
		||||
                     Defaults to False.
 | 
			
		||||
        :type lazy: Boolean
 | 
			
		||||
        :param localedir: Directory with translation catalogs.
 | 
			
		||||
        :type localedir: str
 | 
			
		||||
        """
 | 
			
		||||
        self.domain = domain
 | 
			
		||||
        if localedir is None:
 | 
			
		||||
            localedir = os.environ.get(domain.upper() + '_LOCALEDIR')
 | 
			
		||||
        self.localedir = localedir
 | 
			
		||||
 | 
			
		||||
    def _make_translation_func(self, domain=None):
 | 
			
		||||
        """Return a new translation function ready for use.
 | 
			
		||||
 | 
			
		||||
        Takes into account whether or not lazy translation is being
 | 
			
		||||
        done.
 | 
			
		||||
 | 
			
		||||
        The domain can be specified to override the default from the
 | 
			
		||||
        factory, but the localedir from the factory is always used
 | 
			
		||||
        because we assume the log-level translation catalogs are
 | 
			
		||||
        installed in the same directory as the main application
 | 
			
		||||
        catalog.
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        if domain is None:
 | 
			
		||||
            domain = self.domain
 | 
			
		||||
        t = gettext.translation(domain,
 | 
			
		||||
                                localedir=self.localedir,
 | 
			
		||||
                                fallback=True)
 | 
			
		||||
        # Use the appropriate method of the translation object based
 | 
			
		||||
        # on the python version.
 | 
			
		||||
        m = t.gettext if six.PY3 else t.ugettext
 | 
			
		||||
 | 
			
		||||
        def f(msg):
 | 
			
		||||
            """oslo.i18n.gettextutils translation function."""
 | 
			
		||||
            if USE_LAZY:
 | 
			
		||||
                return Message(msg, domain=domain)
 | 
			
		||||
            return m(msg)
 | 
			
		||||
        return f
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def primary(self):
 | 
			
		||||
        "The default translation function."
 | 
			
		||||
        return self._make_translation_func()
 | 
			
		||||
 | 
			
		||||
    def _make_log_translation_func(self, level):
 | 
			
		||||
        return self._make_translation_func(self.domain + '-log-' + level)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def log_info(self):
 | 
			
		||||
        "Translate info-level log messages."
 | 
			
		||||
        return self._make_log_translation_func('info')
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def log_warning(self):
 | 
			
		||||
        "Translate warning-level log messages."
 | 
			
		||||
        return self._make_log_translation_func('warning')
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def log_error(self):
 | 
			
		||||
        "Translate error-level log messages."
 | 
			
		||||
        return self._make_log_translation_func('error')
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def log_critical(self):
 | 
			
		||||
        "Translate critical-level log messages."
 | 
			
		||||
        return self._make_log_translation_func('critical')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# NOTE(dhellmann): When this module moves out of the incubator into
 | 
			
		||||
# oslo.i18n, these global variables can be moved to an integration
 | 
			
		||||
# module within each application.
 | 
			
		||||
 | 
			
		||||
# Create the global translation functions.
 | 
			
		||||
_translators = TranslatorFactory('watcherclient')
 | 
			
		||||
 | 
			
		||||
# The primary translation function using the well-known name "_"
 | 
			
		||||
_ = _translators.primary
 | 
			
		||||
 | 
			
		||||
# Translators for log levels.
 | 
			
		||||
#
 | 
			
		||||
# The abbreviated names are meant to reflect the usual use of a short
 | 
			
		||||
# name like '_'. The "L" is for "log" and the other letter comes from
 | 
			
		||||
# the level.
 | 
			
		||||
_LI = _translators.log_info
 | 
			
		||||
_LW = _translators.log_warning
 | 
			
		||||
_LE = _translators.log_error
 | 
			
		||||
_LC = _translators.log_critical
 | 
			
		||||
 | 
			
		||||
# NOTE(dhellmann): End of globals that will move to the application's
 | 
			
		||||
# integration module.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def enable_lazy():
 | 
			
		||||
    """Convenience function for configuring _() to use lazy gettext
 | 
			
		||||
 | 
			
		||||
    Call this at the start of execution to enable the gettextutils._
 | 
			
		||||
    function to use lazy gettext functionality. This is useful if
 | 
			
		||||
    your project is importing _ directly instead of using the
 | 
			
		||||
    gettextutils.install() way of importing the _ function.
 | 
			
		||||
    """
 | 
			
		||||
    global USE_LAZY
 | 
			
		||||
    USE_LAZY = True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def install(domain):
 | 
			
		||||
    """Install a _() function using the given translation domain.
 | 
			
		||||
 | 
			
		||||
    Given a translation domain, install a _() function using gettext's
 | 
			
		||||
    install() function.
 | 
			
		||||
 | 
			
		||||
    The main difference from gettext.install() is that we allow
 | 
			
		||||
    overriding the default localedir (e.g. /usr/share/locale) using
 | 
			
		||||
    a translation-domain-specific environment variable (e.g.
 | 
			
		||||
    NOVA_LOCALEDIR).
 | 
			
		||||
 | 
			
		||||
    Note that to enable lazy translation, enable_lazy must be
 | 
			
		||||
    called.
 | 
			
		||||
 | 
			
		||||
    :param domain: the translation domain
 | 
			
		||||
    """
 | 
			
		||||
    from six import moves
 | 
			
		||||
    tf = TranslatorFactory(domain)
 | 
			
		||||
    moves.builtins.__dict__['_'] = tf.primary
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Message(six.text_type):
 | 
			
		||||
    """A Message object is a unicode object that can be translated.
 | 
			
		||||
 | 
			
		||||
    Translation of Message is done explicitly using the translate() method.
 | 
			
		||||
    For all non-translation intents and purposes, a Message is simply unicode,
 | 
			
		||||
    and can be treated as such.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __new__(cls, msgid, msgtext=None, params=None,
 | 
			
		||||
                domain='watcherclient', *args):
 | 
			
		||||
        """Create a new Message object.
 | 
			
		||||
 | 
			
		||||
        In order for translation to work gettext requires a message ID, this
 | 
			
		||||
        msgid will be used as the base unicode text. It is also possible
 | 
			
		||||
        for the msgid and the base unicode text to be different by passing
 | 
			
		||||
        the msgtext parameter.
 | 
			
		||||
        """
 | 
			
		||||
        # If the base msgtext is not given, we use the default translation
 | 
			
		||||
        # of the msgid (which is in English) just in case the system locale is
 | 
			
		||||
        # not English, so that the base text will be in that locale by default.
 | 
			
		||||
        if not msgtext:
 | 
			
		||||
            msgtext = Message._translate_msgid(msgid, domain)
 | 
			
		||||
        # We want to initialize the parent unicode with the actual object that
 | 
			
		||||
        # would have been plain unicode if 'Message' was not enabled.
 | 
			
		||||
        msg = super(Message, cls).__new__(cls, msgtext)
 | 
			
		||||
        msg.msgid = msgid
 | 
			
		||||
        msg.domain = domain
 | 
			
		||||
        msg.params = params
 | 
			
		||||
        return msg
 | 
			
		||||
 | 
			
		||||
    def translate(self, desired_locale=None):
 | 
			
		||||
        """Translate this message to the desired locale.
 | 
			
		||||
 | 
			
		||||
        :param desired_locale: The desired locale to translate the message to,
 | 
			
		||||
                               if no locale is provided the message will be
 | 
			
		||||
                               translated to the system's default locale.
 | 
			
		||||
 | 
			
		||||
        :returns: the translated message in unicode
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        translated_message = Message._translate_msgid(self.msgid,
 | 
			
		||||
                                                      self.domain,
 | 
			
		||||
                                                      desired_locale)
 | 
			
		||||
        if self.params is None:
 | 
			
		||||
            # No need for more translation
 | 
			
		||||
            return translated_message
 | 
			
		||||
 | 
			
		||||
        # This Message object may have been formatted with one or more
 | 
			
		||||
        # Message objects as substitution arguments, given either as a single
 | 
			
		||||
        # argument, part of a tuple, or as one or more values in a dictionary.
 | 
			
		||||
        # When translating this Message we need to translate those Messages too
 | 
			
		||||
        translated_params = _translate_args(self.params, desired_locale)
 | 
			
		||||
 | 
			
		||||
        translated_message = translated_message % translated_params
 | 
			
		||||
 | 
			
		||||
        return translated_message
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def _translate_msgid(msgid, domain, desired_locale=None):
 | 
			
		||||
        if not desired_locale:
 | 
			
		||||
            system_locale = locale.getdefaultlocale()
 | 
			
		||||
            # If the system locale is not available to the runtime use English
 | 
			
		||||
            if not system_locale[0]:
 | 
			
		||||
                desired_locale = 'en_US'
 | 
			
		||||
            else:
 | 
			
		||||
                desired_locale = system_locale[0]
 | 
			
		||||
 | 
			
		||||
        locale_dir = os.environ.get(domain.upper() + '_LOCALEDIR')
 | 
			
		||||
        lang = gettext.translation(domain,
 | 
			
		||||
                                   localedir=locale_dir,
 | 
			
		||||
                                   languages=[desired_locale],
 | 
			
		||||
                                   fallback=True)
 | 
			
		||||
        if six.PY3:
 | 
			
		||||
            translator = lang.gettext
 | 
			
		||||
        else:
 | 
			
		||||
            translator = lang.ugettext
 | 
			
		||||
 | 
			
		||||
        translated_message = translator(msgid)
 | 
			
		||||
        return translated_message
 | 
			
		||||
 | 
			
		||||
    def __mod__(self, other):
 | 
			
		||||
        # When we mod a Message we want the actual operation to be performed
 | 
			
		||||
        # by the parent class (i.e. unicode()), the only thing  we do here is
 | 
			
		||||
        # save the original msgid and the parameters in case of a translation
 | 
			
		||||
        params = self._sanitize_mod_params(other)
 | 
			
		||||
        unicode_mod = super(Message, self).__mod__(params)
 | 
			
		||||
        modded = Message(self.msgid,
 | 
			
		||||
                         msgtext=unicode_mod,
 | 
			
		||||
                         params=params,
 | 
			
		||||
                         domain=self.domain)
 | 
			
		||||
        return modded
 | 
			
		||||
 | 
			
		||||
    def _sanitize_mod_params(self, other):
 | 
			
		||||
        """Sanitize the object being modded with this Message.
 | 
			
		||||
 | 
			
		||||
        - Add support for modding 'None' so translation supports it
 | 
			
		||||
        - Trim the modded object, which can be a large dictionary, to only
 | 
			
		||||
        those keys that would actually be used in a translation
 | 
			
		||||
        - Snapshot the object being modded, in case the message is
 | 
			
		||||
        translated, it will be used as it was when the Message was created
 | 
			
		||||
        """
 | 
			
		||||
        if other is None:
 | 
			
		||||
            params = (other,)
 | 
			
		||||
        elif isinstance(other, dict):
 | 
			
		||||
            # Merge the dictionaries
 | 
			
		||||
            # Copy each item in case one does not support deep copy.
 | 
			
		||||
            params = {}
 | 
			
		||||
            if isinstance(self.params, dict):
 | 
			
		||||
                for key, val in self.params.items():
 | 
			
		||||
                    params[key] = self._copy_param(val)
 | 
			
		||||
            for key, val in other.items():
 | 
			
		||||
                params[key] = self._copy_param(val)
 | 
			
		||||
        else:
 | 
			
		||||
            params = self._copy_param(other)
 | 
			
		||||
        return params
 | 
			
		||||
 | 
			
		||||
    def _copy_param(self, param):
 | 
			
		||||
        try:
 | 
			
		||||
            return copy.deepcopy(param)
 | 
			
		||||
        except Exception:
 | 
			
		||||
            # Fallback to casting to unicode this will handle the
 | 
			
		||||
            # python code-like objects that can't be deep-copied
 | 
			
		||||
            return six.text_type(param)
 | 
			
		||||
 | 
			
		||||
    def __add__(self, other):
 | 
			
		||||
        msg = _('Message objects do not support addition.')
 | 
			
		||||
        raise TypeError(msg)
 | 
			
		||||
 | 
			
		||||
    def __radd__(self, other):
 | 
			
		||||
        return self.__add__(other)
 | 
			
		||||
 | 
			
		||||
    if six.PY2:
 | 
			
		||||
        def __str__(self):
 | 
			
		||||
            # NOTE(luisg): Logging in python 2.6 tries to str() log records,
 | 
			
		||||
            # and it expects specifically a UnicodeError in order to proceed.
 | 
			
		||||
            msg = _('Message objects do not support str() because they may '
 | 
			
		||||
                    'contain non-ascii characters. '
 | 
			
		||||
                    'Please use unicode() or translate() instead.')
 | 
			
		||||
            raise UnicodeError(msg)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_available_languages(domain):
 | 
			
		||||
    """Lists the available languages for the given translation domain.
 | 
			
		||||
 | 
			
		||||
    :param domain: the domain to get languages for
 | 
			
		||||
    """
 | 
			
		||||
    if domain in _AVAILABLE_LANGUAGES:
 | 
			
		||||
        return copy.copy(_AVAILABLE_LANGUAGES[domain])
 | 
			
		||||
 | 
			
		||||
    localedir = '%s_LOCALEDIR' % domain.upper()
 | 
			
		||||
    find = lambda x: gettext.find(domain,
 | 
			
		||||
                                  localedir=os.environ.get(localedir),
 | 
			
		||||
                                  languages=[x])
 | 
			
		||||
 | 
			
		||||
    # NOTE(mrodden): en_US should always be available (and first in case
 | 
			
		||||
    # order matters) since our in-line message strings are en_US
 | 
			
		||||
    language_list = ['en_US']
 | 
			
		||||
    # NOTE(luisg): Babel <1.0 used a function called list(), which was
 | 
			
		||||
    # renamed to locale_identifiers() in >=1.0, the requirements master list
 | 
			
		||||
    # requires >=0.9.6, uncapped, so defensively work with both. We can remove
 | 
			
		||||
    # this check when the master list updates to >=1.0, and update all projects
 | 
			
		||||
    list_identifiers = (getattr(localedata, 'list', None) or
 | 
			
		||||
                        getattr(localedata, 'locale_identifiers'))
 | 
			
		||||
    locale_identifiers = list_identifiers()
 | 
			
		||||
 | 
			
		||||
    for i in locale_identifiers:
 | 
			
		||||
        if find(i) is not None:
 | 
			
		||||
            language_list.append(i)
 | 
			
		||||
 | 
			
		||||
    # NOTE(luisg): Babel>=1.0,<1.3 has a bug where some OpenStack supported
 | 
			
		||||
    # locales (e.g. 'zh_CN', and 'zh_TW') aren't supported even though they
 | 
			
		||||
    # are perfectly legitimate locales:
 | 
			
		||||
    #     https://github.com/mitsuhiko/babel/issues/37
 | 
			
		||||
    # In Babel 1.3 they fixed the bug and they support these locales, but
 | 
			
		||||
    # they are still not explicitly "listed" by locale_identifiers().
 | 
			
		||||
    # That is  why we add the locales here explicitly if necessary so that
 | 
			
		||||
    # they are listed as supported.
 | 
			
		||||
    aliases = {'zh': 'zh_CN',
 | 
			
		||||
               'zh_Hant_HK': 'zh_HK',
 | 
			
		||||
               'zh_Hant': 'zh_TW',
 | 
			
		||||
               'fil': 'tl_PH'}
 | 
			
		||||
    for (locale_, alias) in six.iteritems(aliases):
 | 
			
		||||
        if locale_ in language_list and alias not in language_list:
 | 
			
		||||
            language_list.append(alias)
 | 
			
		||||
 | 
			
		||||
    _AVAILABLE_LANGUAGES[domain] = language_list
 | 
			
		||||
    return copy.copy(language_list)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def translate(obj, desired_locale=None):
 | 
			
		||||
    """Gets the translated unicode representation of the given object.
 | 
			
		||||
 | 
			
		||||
    If the object is not translatable it is returned as-is.
 | 
			
		||||
    If the locale is None the object is translated to the system locale.
 | 
			
		||||
 | 
			
		||||
    :param obj: the object to translate
 | 
			
		||||
    :param desired_locale: the locale to translate the message to, if None the
 | 
			
		||||
                           default system locale will be used
 | 
			
		||||
    :returns: the translated object in unicode, or the original object if
 | 
			
		||||
              it could not be translated
 | 
			
		||||
    """
 | 
			
		||||
    message = obj
 | 
			
		||||
    if not isinstance(message, Message):
 | 
			
		||||
        # If the object to translate is not already translatable,
 | 
			
		||||
        # let's first get its unicode representation
 | 
			
		||||
        message = six.text_type(obj)
 | 
			
		||||
    if isinstance(message, Message):
 | 
			
		||||
        # Even after unicoding() we still need to check if we are
 | 
			
		||||
        # running with translatable unicode before translating
 | 
			
		||||
        return message.translate(desired_locale)
 | 
			
		||||
    return obj
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _translate_args(args, desired_locale=None):
 | 
			
		||||
    """Translates all the translatable elements of the given arguments object.
 | 
			
		||||
 | 
			
		||||
    This method is used for translating the translatable values in method
 | 
			
		||||
    arguments which include values of tuples or dictionaries.
 | 
			
		||||
    If the object is not a tuple or a dictionary the object itself is
 | 
			
		||||
    translated if it is translatable.
 | 
			
		||||
 | 
			
		||||
    If the locale is None the object is translated to the system locale.
 | 
			
		||||
 | 
			
		||||
    :param args: the args to translate
 | 
			
		||||
    :param desired_locale: the locale to translate the args to, if None the
 | 
			
		||||
                           default system locale will be used
 | 
			
		||||
    :returns: a new args object with the translated contents of the original
 | 
			
		||||
    """
 | 
			
		||||
    if isinstance(args, tuple):
 | 
			
		||||
        return tuple(translate(v, desired_locale) for v in args)
 | 
			
		||||
    if isinstance(args, dict):
 | 
			
		||||
        translated_dict = {}
 | 
			
		||||
        for (k, v) in six.iteritems(args):
 | 
			
		||||
            translated_v = translate(v, desired_locale)
 | 
			
		||||
            translated_dict[k] = translated_v
 | 
			
		||||
        return translated_dict
 | 
			
		||||
    return translate(args, desired_locale)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TranslationHandler(handlers.MemoryHandler):
 | 
			
		||||
    """Handler that translates records before logging them.
 | 
			
		||||
 | 
			
		||||
    The TranslationHandler takes a locale and a target logging.Handler object
 | 
			
		||||
    to forward LogRecord objects to after translating them. This handler
 | 
			
		||||
    depends on Message objects being logged, instead of regular strings.
 | 
			
		||||
 | 
			
		||||
    The handler can be configured declaratively in the logging.conf as follows:
 | 
			
		||||
 | 
			
		||||
        [handlers]
 | 
			
		||||
        keys = translatedlog, translator
 | 
			
		||||
 | 
			
		||||
        [handler_translatedlog]
 | 
			
		||||
        class = handlers.WatchedFileHandler
 | 
			
		||||
        args = ('/var/log/api-localized.log',)
 | 
			
		||||
        formatter = context
 | 
			
		||||
 | 
			
		||||
        [handler_translator]
 | 
			
		||||
        class = openstack.common.log.TranslationHandler
 | 
			
		||||
        target = translatedlog
 | 
			
		||||
        args = ('zh_CN',)
 | 
			
		||||
 | 
			
		||||
    If the specified locale is not available in the system, the handler will
 | 
			
		||||
    log in the default locale.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, locale=None, target=None):
 | 
			
		||||
        """Initialize a TranslationHandler
 | 
			
		||||
 | 
			
		||||
        :param locale: locale to use for translating messages
 | 
			
		||||
        :param target: logging.Handler object to forward
 | 
			
		||||
                       LogRecord objects to after translation
 | 
			
		||||
        """
 | 
			
		||||
        # NOTE(luisg): In order to allow this handler to be a wrapper for
 | 
			
		||||
        # other handlers, such as a FileHandler, and still be able to
 | 
			
		||||
        # configure it using logging.conf, this handler has to extend
 | 
			
		||||
        # MemoryHandler because only the MemoryHandlers' logging.conf
 | 
			
		||||
        # parsing is implemented such that it accepts a target handler.
 | 
			
		||||
        handlers.MemoryHandler.__init__(self, capacity=0, target=target)
 | 
			
		||||
        self.locale = locale
 | 
			
		||||
 | 
			
		||||
    def setFormatter(self, fmt):
 | 
			
		||||
        self.target.setFormatter(fmt)
 | 
			
		||||
 | 
			
		||||
    def emit(self, record):
 | 
			
		||||
        # We save the message from the original record to restore it
 | 
			
		||||
        # after translation, so other handlers are not affected by this
 | 
			
		||||
        original_msg = record.msg
 | 
			
		||||
        original_args = record.args
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self._translate_and_log_record(record)
 | 
			
		||||
        finally:
 | 
			
		||||
            record.msg = original_msg
 | 
			
		||||
            record.args = original_args
 | 
			
		||||
 | 
			
		||||
    def _translate_and_log_record(self, record):
 | 
			
		||||
        record.msg = translate(record.msg, self.locale)
 | 
			
		||||
 | 
			
		||||
        # In addition to translating the message, we also need to translate
 | 
			
		||||
        # arguments that were passed to the log method that were not part
 | 
			
		||||
        # of the main message e.g., log.info(_('Some message %s'), this_one))
 | 
			
		||||
        record.args = _translate_args(record.args, self.locale)
 | 
			
		||||
 | 
			
		||||
        self.target.emit(record)
 | 
			
		||||
							
								
								
									
										73
									
								
								watcherclient/openstack/common/importutils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								watcherclient/openstack/common/importutils.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,73 @@
 | 
			
		||||
# Copyright 2011 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 related utilities and helper functions.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import sys
 | 
			
		||||
import traceback
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def import_class(import_str):
 | 
			
		||||
    """Returns a class from a string including module and class."""
 | 
			
		||||
    mod_str, _sep, class_str = import_str.rpartition('.')
 | 
			
		||||
    __import__(mod_str)
 | 
			
		||||
    try:
 | 
			
		||||
        return getattr(sys.modules[mod_str], class_str)
 | 
			
		||||
    except AttributeError:
 | 
			
		||||
        raise ImportError('Class %s cannot be found (%s)' %
 | 
			
		||||
                          (class_str,
 | 
			
		||||
                           traceback.format_exception(*sys.exc_info())))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def import_object(import_str, *args, **kwargs):
 | 
			
		||||
    """Import a class and return an instance of it."""
 | 
			
		||||
    return import_class(import_str)(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def import_object_ns(name_space, import_str, *args, **kwargs):
 | 
			
		||||
    """Tries to import object from default namespace.
 | 
			
		||||
 | 
			
		||||
    Imports a class and return an instance of it, first by trying
 | 
			
		||||
    to find the class in a default namespace, then failing back to
 | 
			
		||||
    a full path if not found in the default namespace.
 | 
			
		||||
    """
 | 
			
		||||
    import_value = "%s.%s" % (name_space, import_str)
 | 
			
		||||
    try:
 | 
			
		||||
        return import_class(import_value)(*args, **kwargs)
 | 
			
		||||
    except ImportError:
 | 
			
		||||
        return import_class(import_str)(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def import_module(import_str):
 | 
			
		||||
    """Import a module."""
 | 
			
		||||
    __import__(import_str)
 | 
			
		||||
    return sys.modules[import_str]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def import_versioned_module(version, submodule=None):
 | 
			
		||||
    module = 'watcherclient.v%s' % version
 | 
			
		||||
    if submodule:
 | 
			
		||||
        module = '.'.join((module, submodule))
 | 
			
		||||
    return import_module(module)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def try_import(import_str, default=None):
 | 
			
		||||
    """Try to import a module and if it fails return default."""
 | 
			
		||||
    try:
 | 
			
		||||
        return import_module(import_str)
 | 
			
		||||
    except ImportError:
 | 
			
		||||
        return default
 | 
			
		||||
							
								
								
									
										316
									
								
								watcherclient/openstack/common/strutils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										316
									
								
								watcherclient/openstack/common/strutils.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,316 @@
 | 
			
		||||
# Copyright 2011 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.
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
System-level utilities and helper functions.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import math
 | 
			
		||||
import re
 | 
			
		||||
import sys
 | 
			
		||||
import unicodedata
 | 
			
		||||
 | 
			
		||||
import six
 | 
			
		||||
 | 
			
		||||
from watcherclient.openstack.common.gettextutils import _
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
UNIT_PREFIX_EXPONENT = {
 | 
			
		||||
    'k': 1,
 | 
			
		||||
    'K': 1,
 | 
			
		||||
    'Ki': 1,
 | 
			
		||||
    'M': 2,
 | 
			
		||||
    'Mi': 2,
 | 
			
		||||
    'G': 3,
 | 
			
		||||
    'Gi': 3,
 | 
			
		||||
    'T': 4,
 | 
			
		||||
    'Ti': 4,
 | 
			
		||||
}
 | 
			
		||||
UNIT_SYSTEM_INFO = {
 | 
			
		||||
    'IEC': (1024, re.compile(r'(^[-+]?\d*\.?\d+)([KMGT]i?)?(b|bit|B)$')),
 | 
			
		||||
    'SI': (1000, re.compile(r'(^[-+]?\d*\.?\d+)([kMGT])?(b|bit|B)$')),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TRUE_STRINGS = ('1', 't', 'true', 'on', 'y', 'yes')
 | 
			
		||||
FALSE_STRINGS = ('0', 'f', 'false', 'off', 'n', 'no')
 | 
			
		||||
 | 
			
		||||
SLUGIFY_STRIP_RE = re.compile(r"[^\w\s-]")
 | 
			
		||||
SLUGIFY_HYPHENATE_RE = re.compile(r"[-\s]+")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# NOTE(flaper87): The following globals are used by `mask_password`
 | 
			
		||||
_SANITIZE_KEYS = ['adminPass', 'admin_pass', 'password', 'admin_password']
 | 
			
		||||
 | 
			
		||||
# NOTE(ldbragst): Let's build a list of regex objects using the list of
 | 
			
		||||
# _SANITIZE_KEYS we already have. This way, we only have to add the new key
 | 
			
		||||
# to the list of _SANITIZE_KEYS and we can generate regular expressions
 | 
			
		||||
# for XML and JSON automatically.
 | 
			
		||||
_SANITIZE_PATTERNS_2 = []
 | 
			
		||||
_SANITIZE_PATTERNS_1 = []
 | 
			
		||||
 | 
			
		||||
# NOTE(amrith): Some regular expressions have only one parameter, some
 | 
			
		||||
# have two parameters. Use different lists of patterns here.
 | 
			
		||||
_FORMAT_PATTERNS_1 = [r'(%(key)s\s*[=]\s*)[^\s^\'^\"]+']
 | 
			
		||||
_FORMAT_PATTERNS_2 = [r'(%(key)s\s*[=]\s*[\"\']).*?([\"\'])',
 | 
			
		||||
                      r'(%(key)s\s+[\"\']).*?([\"\'])',
 | 
			
		||||
                      r'([-]{2}%(key)s\s+)[^\'^\"^=^\s]+([\s]*)',
 | 
			
		||||
                      r'(<%(key)s>).*?(</%(key)s>)',
 | 
			
		||||
                      r'([\"\']%(key)s[\"\']\s*:\s*[\"\']).*?([\"\'])',
 | 
			
		||||
                      r'([\'"].*?%(key)s[\'"]\s*:\s*u?[\'"]).*?([\'"])',
 | 
			
		||||
                      r'([\'"].*?%(key)s[\'"]\s*,\s*\'--?[A-z]+\'\s*,\s*u?'
 | 
			
		||||
                      '[\'"]).*?([\'"])',
 | 
			
		||||
                      r'(%(key)s\s*--?[A-z]+\s*)\S+(\s*)']
 | 
			
		||||
 | 
			
		||||
for key in _SANITIZE_KEYS:
 | 
			
		||||
    for pattern in _FORMAT_PATTERNS_2:
 | 
			
		||||
        reg_ex = re.compile(pattern % {'key': key}, re.DOTALL)
 | 
			
		||||
        _SANITIZE_PATTERNS_2.append(reg_ex)
 | 
			
		||||
 | 
			
		||||
    for pattern in _FORMAT_PATTERNS_1:
 | 
			
		||||
        reg_ex = re.compile(pattern % {'key': key}, re.DOTALL)
 | 
			
		||||
        _SANITIZE_PATTERNS_1.append(reg_ex)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def int_from_bool_as_string(subject):
 | 
			
		||||
    """Interpret a string as a boolean and return either 1 or 0.
 | 
			
		||||
 | 
			
		||||
    Any string value in:
 | 
			
		||||
 | 
			
		||||
        ('True', 'true', 'On', 'on', '1')
 | 
			
		||||
 | 
			
		||||
    is interpreted as a boolean True.
 | 
			
		||||
 | 
			
		||||
    Useful for JSON-decoded stuff and config file parsing
 | 
			
		||||
    """
 | 
			
		||||
    return bool_from_string(subject) and 1 or 0
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def bool_from_string(subject, strict=False, default=False):
 | 
			
		||||
    """Interpret a string as a boolean.
 | 
			
		||||
 | 
			
		||||
    A case-insensitive match is performed such that strings matching 't',
 | 
			
		||||
    'true', 'on', 'y', 'yes', or '1' are considered True and, when
 | 
			
		||||
    `strict=False`, anything else returns the value specified by 'default'.
 | 
			
		||||
 | 
			
		||||
    Useful for JSON-decoded stuff and config file parsing.
 | 
			
		||||
 | 
			
		||||
    If `strict=True`, unrecognized values, including None, will raise a
 | 
			
		||||
    ValueError which is useful when parsing values passed in from an API call.
 | 
			
		||||
    Strings yielding False are 'f', 'false', 'off', 'n', 'no', or '0'.
 | 
			
		||||
    """
 | 
			
		||||
    if not isinstance(subject, six.string_types):
 | 
			
		||||
        subject = six.text_type(subject)
 | 
			
		||||
 | 
			
		||||
    lowered = subject.strip().lower()
 | 
			
		||||
 | 
			
		||||
    if lowered in TRUE_STRINGS:
 | 
			
		||||
        return True
 | 
			
		||||
    elif lowered in FALSE_STRINGS:
 | 
			
		||||
        return False
 | 
			
		||||
    elif strict:
 | 
			
		||||
        acceptable = ', '.join(
 | 
			
		||||
            "'%s'" % s for s in sorted(TRUE_STRINGS + FALSE_STRINGS))
 | 
			
		||||
        msg = _("Unrecognized value '%(val)s', acceptable values are:"
 | 
			
		||||
                " %(acceptable)s") % {'val': subject,
 | 
			
		||||
                                      'acceptable': acceptable}
 | 
			
		||||
        raise ValueError(msg)
 | 
			
		||||
    else:
 | 
			
		||||
        return default
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def safe_decode(text, incoming=None, errors='strict'):
 | 
			
		||||
    """Decodes incoming text/bytes string using `incoming` if they're not
 | 
			
		||||
       already unicode.
 | 
			
		||||
 | 
			
		||||
    :param incoming: Text's current encoding
 | 
			
		||||
    :param errors: Errors handling policy. See here for valid
 | 
			
		||||
        values http://docs.python.org/2/library/codecs.html
 | 
			
		||||
    :returns: text or a unicode `incoming` encoded
 | 
			
		||||
                representation of it.
 | 
			
		||||
    :raises TypeError: If text is not an instance of str
 | 
			
		||||
    """
 | 
			
		||||
    if not isinstance(text, (six.string_types, six.binary_type)):
 | 
			
		||||
        raise TypeError("%s can't be decoded" % type(text))
 | 
			
		||||
 | 
			
		||||
    if isinstance(text, six.text_type):
 | 
			
		||||
        return text
 | 
			
		||||
 | 
			
		||||
    if not incoming:
 | 
			
		||||
        incoming = (sys.stdin.encoding or
 | 
			
		||||
                    sys.getdefaultencoding())
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        return text.decode(incoming, errors)
 | 
			
		||||
    except UnicodeDecodeError:
 | 
			
		||||
        # Note(flaper87) If we get here, it means that
 | 
			
		||||
        # sys.stdin.encoding / sys.getdefaultencoding
 | 
			
		||||
        # didn't return a suitable encoding to decode
 | 
			
		||||
        # text. This happens mostly when global LANG
 | 
			
		||||
        # var is not set correctly and there's no
 | 
			
		||||
        # default encoding. In this case, most likely
 | 
			
		||||
        # python will use ASCII or ANSI encoders as
 | 
			
		||||
        # default encodings but they won't be capable
 | 
			
		||||
        # of decoding non-ASCII characters.
 | 
			
		||||
        #
 | 
			
		||||
        # Also, UTF-8 is being used since it's an ASCII
 | 
			
		||||
        # extension.
 | 
			
		||||
        return text.decode('utf-8', errors)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def safe_encode(text, incoming=None,
 | 
			
		||||
                encoding='utf-8', errors='strict'):
 | 
			
		||||
    """Encodes incoming text/bytes string using `encoding`.
 | 
			
		||||
 | 
			
		||||
    If incoming is not specified, text is expected to be encoded with
 | 
			
		||||
    current python's default encoding. (`sys.getdefaultencoding`)
 | 
			
		||||
 | 
			
		||||
    :param incoming: Text's current encoding
 | 
			
		||||
    :param encoding: Expected encoding for text (Default UTF-8)
 | 
			
		||||
    :param errors: Errors handling policy. See here for valid
 | 
			
		||||
        values http://docs.python.org/2/library/codecs.html
 | 
			
		||||
    :returns: text or a bytestring `encoding` encoded
 | 
			
		||||
                representation of it.
 | 
			
		||||
    :raises TypeError: If text is not an instance of str
 | 
			
		||||
    """
 | 
			
		||||
    if not isinstance(text, (six.string_types, six.binary_type)):
 | 
			
		||||
        raise TypeError("%s can't be encoded" % type(text))
 | 
			
		||||
 | 
			
		||||
    if not incoming:
 | 
			
		||||
        incoming = (sys.stdin.encoding or
 | 
			
		||||
                    sys.getdefaultencoding())
 | 
			
		||||
 | 
			
		||||
    if isinstance(text, six.text_type):
 | 
			
		||||
        return text.encode(encoding, errors)
 | 
			
		||||
    elif text and encoding != incoming:
 | 
			
		||||
        # Decode text before encoding it with `encoding`
 | 
			
		||||
        text = safe_decode(text, incoming, errors)
 | 
			
		||||
        return text.encode(encoding, errors)
 | 
			
		||||
    else:
 | 
			
		||||
        return text
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def string_to_bytes(text, unit_system='IEC', return_int=False):
 | 
			
		||||
    """Converts a string into an float representation of bytes.
 | 
			
		||||
 | 
			
		||||
    The units supported for IEC ::
 | 
			
		||||
 | 
			
		||||
        Kb(it), Kib(it), Mb(it), Mib(it), Gb(it), Gib(it), Tb(it), Tib(it)
 | 
			
		||||
        KB, KiB, MB, MiB, GB, GiB, TB, TiB
 | 
			
		||||
 | 
			
		||||
    The units supported for SI ::
 | 
			
		||||
 | 
			
		||||
        kb(it), Mb(it), Gb(it), Tb(it)
 | 
			
		||||
        kB, MB, GB, TB
 | 
			
		||||
 | 
			
		||||
    Note that the SI unit system does not support capital letter 'K'
 | 
			
		||||
 | 
			
		||||
    :param text: String input for bytes size conversion.
 | 
			
		||||
    :param unit_system: Unit system for byte size conversion.
 | 
			
		||||
    :param return_int: If True, returns integer representation of text
 | 
			
		||||
                       in bytes. (default: decimal)
 | 
			
		||||
    :returns: Numerical representation of text in bytes.
 | 
			
		||||
    :raises ValueError: If text has an invalid value.
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    try:
 | 
			
		||||
        base, reg_ex = UNIT_SYSTEM_INFO[unit_system]
 | 
			
		||||
    except KeyError:
 | 
			
		||||
        msg = _('Invalid unit system: "%s"') % unit_system
 | 
			
		||||
        raise ValueError(msg)
 | 
			
		||||
    match = reg_ex.match(text)
 | 
			
		||||
    if match:
 | 
			
		||||
        magnitude = float(match.group(1))
 | 
			
		||||
        unit_prefix = match.group(2)
 | 
			
		||||
        if match.group(3) in ['b', 'bit']:
 | 
			
		||||
            magnitude /= 8
 | 
			
		||||
    else:
 | 
			
		||||
        msg = _('Invalid string format: %s') % text
 | 
			
		||||
        raise ValueError(msg)
 | 
			
		||||
    if not unit_prefix:
 | 
			
		||||
        res = magnitude
 | 
			
		||||
    else:
 | 
			
		||||
        res = magnitude * pow(base, UNIT_PREFIX_EXPONENT[unit_prefix])
 | 
			
		||||
    if return_int:
 | 
			
		||||
        return int(math.ceil(res))
 | 
			
		||||
    return res
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_slug(value, incoming=None, errors="strict"):
 | 
			
		||||
    """Normalize string.
 | 
			
		||||
 | 
			
		||||
    Convert to lowercase, remove non-word characters, and convert spaces
 | 
			
		||||
    to hyphens.
 | 
			
		||||
 | 
			
		||||
    Inspired by Django's `slugify` filter.
 | 
			
		||||
 | 
			
		||||
    :param value: Text to slugify
 | 
			
		||||
    :param incoming: Text's current encoding
 | 
			
		||||
    :param errors: Errors handling policy. See here for valid
 | 
			
		||||
        values http://docs.python.org/2/library/codecs.html
 | 
			
		||||
    :returns: slugified unicode representation of `value`
 | 
			
		||||
    :raises TypeError: If text is not an instance of str
 | 
			
		||||
    """
 | 
			
		||||
    value = safe_decode(value, incoming, errors)
 | 
			
		||||
    # NOTE(aababilov): no need to use safe_(encode|decode) here:
 | 
			
		||||
    # encodings are always "ascii", error handling is always "ignore"
 | 
			
		||||
    # and types are always known (first: unicode; second: str)
 | 
			
		||||
    value = unicodedata.normalize("NFKD", value).encode(
 | 
			
		||||
        "ascii", "ignore").decode("ascii")
 | 
			
		||||
    value = SLUGIFY_STRIP_RE.sub("", value).strip().lower()
 | 
			
		||||
    return SLUGIFY_HYPHENATE_RE.sub("-", value)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def mask_password(message, secret="***"):
 | 
			
		||||
    """Replace password with 'secret' in message.
 | 
			
		||||
 | 
			
		||||
    :param message: The string which includes security information.
 | 
			
		||||
    :param secret: value with which to replace passwords.
 | 
			
		||||
    :returns: The unicode value of message with the password fields masked.
 | 
			
		||||
 | 
			
		||||
    For example:
 | 
			
		||||
 | 
			
		||||
    >>> mask_password("'adminPass' : 'aaaaa'")
 | 
			
		||||
    "'adminPass' : '***'"
 | 
			
		||||
    >>> mask_password("'admin_pass' : 'aaaaa'")
 | 
			
		||||
    "'admin_pass' : '***'"
 | 
			
		||||
    >>> mask_password('"password" : "aaaaa"')
 | 
			
		||||
    '"password" : "***"'
 | 
			
		||||
    >>> mask_password("'original_password' : 'aaaaa'")
 | 
			
		||||
    "'original_password' : '***'"
 | 
			
		||||
    >>> mask_password("u'original_password' :   u'aaaaa'")
 | 
			
		||||
    "u'original_password' :   u'***'"
 | 
			
		||||
    """
 | 
			
		||||
    try:
 | 
			
		||||
        message = six.text_type(message)
 | 
			
		||||
    except UnicodeDecodeError:
 | 
			
		||||
        # NOTE(jecarey): Temporary fix to handle cases where message is a
 | 
			
		||||
        # byte string.   A better solution will be provided in Kilo.
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    # NOTE(ldbragst): Check to see if anything in message contains any key
 | 
			
		||||
    # specified in _SANITIZE_KEYS, if not then just return the message since
 | 
			
		||||
    # we don't have to mask any passwords.
 | 
			
		||||
    if not any(key in message for key in _SANITIZE_KEYS):
 | 
			
		||||
        return message
 | 
			
		||||
 | 
			
		||||
    substitute = r'\g<1>' + secret + r'\g<2>'
 | 
			
		||||
    for pattern in _SANITIZE_PATTERNS_2:
 | 
			
		||||
        message = re.sub(pattern, substitute, message)
 | 
			
		||||
 | 
			
		||||
    substitute = r'\g<1>' + secret
 | 
			
		||||
    for pattern in _SANITIZE_PATTERNS_1:
 | 
			
		||||
        message = re.sub(pattern, substitute, message)
 | 
			
		||||
 | 
			
		||||
    return message
 | 
			
		||||
							
								
								
									
										510
									
								
								watcherclient/shell.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										510
									
								
								watcherclient/shell.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,510 @@
 | 
			
		||||
# -*- 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.
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
Command-line interface to the Watcher API.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from __future__ import print_function
 | 
			
		||||
 | 
			
		||||
import argparse
 | 
			
		||||
import getpass
 | 
			
		||||
import logging
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
from keystoneclient.auth.identity import v2 as v2_auth
 | 
			
		||||
from keystoneclient.auth.identity import v3 as v3_auth
 | 
			
		||||
from keystoneclient import discover
 | 
			
		||||
from keystoneclient.openstack.common.apiclient import exceptions as ks_exc
 | 
			
		||||
from keystoneclient import session as kssession
 | 
			
		||||
import six.moves.urllib.parse as urlparse
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import watcherclient
 | 
			
		||||
from watcherclient import client as watcher_client
 | 
			
		||||
from watcherclient.common import utils
 | 
			
		||||
from watcherclient import exceptions as exc
 | 
			
		||||
from watcherclient.openstack.common._i18n import _
 | 
			
		||||
from watcherclient.openstack.common import cliutils
 | 
			
		||||
from watcherclient.openstack.common import gettextutils
 | 
			
		||||
 | 
			
		||||
gettextutils.install('watcherclient')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class WatcherShell(object):
 | 
			
		||||
 | 
			
		||||
    def _append_global_identity_args(self, parser):
 | 
			
		||||
        # FIXME(dhu): 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.
 | 
			
		||||
 | 
			
		||||
        # Register arguments needed for a Session
 | 
			
		||||
        kssession.Session.register_cli_options(parser)
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os-user-domain-id',
 | 
			
		||||
                            default=cliutils.env('OS_USER_DOMAIN_ID'),
 | 
			
		||||
                            help='Defaults to env[OS_USER_DOMAIN_ID].')
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os-user-domain-name',
 | 
			
		||||
                            default=cliutils.env('OS_USER_DOMAIN_NAME'),
 | 
			
		||||
                            help='Defaults to env[OS_USER_DOMAIN_NAME].')
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os-project-id',
 | 
			
		||||
                            default=cliutils.env('OS_PROJECT_ID'),
 | 
			
		||||
                            help='Another way to specify tenant ID. '
 | 
			
		||||
                                 'This option is mutually exclusive with '
 | 
			
		||||
                                 ' --os-tenant-id. '
 | 
			
		||||
                                 'Defaults to env[OS_PROJECT_ID].')
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os-project-name',
 | 
			
		||||
                            default=cliutils.env('OS_PROJECT_NAME'),
 | 
			
		||||
                            help='Another way to specify tenant name. '
 | 
			
		||||
                                 'This option is mutually exclusive with '
 | 
			
		||||
                                 ' --os-tenant-name. '
 | 
			
		||||
                                 'Defaults to env[OS_PROJECT_NAME].')
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os-project-domain-id',
 | 
			
		||||
                            default=cliutils.env('OS_PROJECT_DOMAIN_ID'),
 | 
			
		||||
                            help='Defaults to env[OS_PROJECT_DOMAIN_ID].')
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os-project-domain-name',
 | 
			
		||||
                            default=cliutils.env('OS_PROJECT_DOMAIN_NAME'),
 | 
			
		||||
                            help='Defaults to env[OS_PROJECT_DOMAIN_NAME].')
 | 
			
		||||
 | 
			
		||||
    def get_base_parser(self):
 | 
			
		||||
        parser = argparse.ArgumentParser(
 | 
			
		||||
            prog='watcher',
 | 
			
		||||
            description=__doc__.strip(),
 | 
			
		||||
            epilog='See "watcher help COMMAND" '
 | 
			
		||||
                   'for help on a specific command.',
 | 
			
		||||
            add_help=False,
 | 
			
		||||
            formatter_class=HelpFormatter,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # Global arguments
 | 
			
		||||
        parser.add_argument('-h', '--help',
 | 
			
		||||
                            action='store_true',
 | 
			
		||||
                            help=argparse.SUPPRESS,
 | 
			
		||||
                            )
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--version',
 | 
			
		||||
                            action='version',
 | 
			
		||||
                            version=watcherclient.__version__)
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--debug',
 | 
			
		||||
                            default=bool(cliutils.env('WATCHERCLIENT_DEBUG')),
 | 
			
		||||
                            action='store_true',
 | 
			
		||||
                            help='Defaults to env[WATCHERCLIENT_DEBUG]')
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('-v', '--verbose',
 | 
			
		||||
                            default=False, action="store_true",
 | 
			
		||||
                            help="Print more verbose output")
 | 
			
		||||
 | 
			
		||||
        # for backward compatibility only
 | 
			
		||||
        parser.add_argument('--cert-file',
 | 
			
		||||
                            dest='os_cert',
 | 
			
		||||
                            help='DEPRECATED! Use --os-cert.')
 | 
			
		||||
 | 
			
		||||
        # for backward compatibility only
 | 
			
		||||
        parser.add_argument('--key-file',
 | 
			
		||||
                            dest='os_key',
 | 
			
		||||
                            help='DEPRECATED! Use --os-key.')
 | 
			
		||||
 | 
			
		||||
        # for backward compatibility only
 | 
			
		||||
        parser.add_argument('--ca-file',
 | 
			
		||||
                            dest='os_cacert',
 | 
			
		||||
                            help='DEPRECATED! Use --os-cacert.')
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os-username',
 | 
			
		||||
                            default=cliutils.env('OS_USERNAME'),
 | 
			
		||||
                            help='Defaults to env[OS_USERNAME]')
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os_username',
 | 
			
		||||
                            help=argparse.SUPPRESS)
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os-password',
 | 
			
		||||
                            default=cliutils.env('OS_PASSWORD'),
 | 
			
		||||
                            help='Defaults to env[OS_PASSWORD]')
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os_password',
 | 
			
		||||
                            help=argparse.SUPPRESS)
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os-tenant-id',
 | 
			
		||||
                            default=cliutils.env('OS_TENANT_ID'),
 | 
			
		||||
                            help='Defaults to env[OS_TENANT_ID]')
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os_tenant_id',
 | 
			
		||||
                            help=argparse.SUPPRESS)
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os-tenant-name',
 | 
			
		||||
                            default=cliutils.env('OS_TENANT_NAME'),
 | 
			
		||||
                            help='Defaults to env[OS_TENANT_NAME]')
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os_tenant_name',
 | 
			
		||||
                            help=argparse.SUPPRESS)
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os-auth-url',
 | 
			
		||||
                            default=cliutils.env('OS_AUTH_URL'),
 | 
			
		||||
                            help='Defaults to env[OS_AUTH_URL]')
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os_auth_url',
 | 
			
		||||
                            help=argparse.SUPPRESS)
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os-region-name',
 | 
			
		||||
                            default=cliutils.env('OS_REGION_NAME'),
 | 
			
		||||
                            help='Defaults to env[OS_REGION_NAME]')
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os_region_name',
 | 
			
		||||
                            help=argparse.SUPPRESS)
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os-auth-token',
 | 
			
		||||
                            default=cliutils.env('OS_AUTH_TOKEN'),
 | 
			
		||||
                            help='Defaults to env[OS_AUTH_TOKEN]')
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os_auth_token',
 | 
			
		||||
                            help=argparse.SUPPRESS)
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--watcher-url',
 | 
			
		||||
                            default=cliutils.env('WATCHER_URL'),
 | 
			
		||||
                            help='Defaults to env[WATCHER_URL]')
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--watcher_url',
 | 
			
		||||
                            help=argparse.SUPPRESS)
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--watcher-api-version',
 | 
			
		||||
                            default=cliutils.env(
 | 
			
		||||
                                'WATCHER_API_VERSION', default='1'),
 | 
			
		||||
                            help='Defaults to env[WATCHER_API_VERSION] '
 | 
			
		||||
                                 'or 1')
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--watcher_api_version',
 | 
			
		||||
                            help=argparse.SUPPRESS)
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os-service-type',
 | 
			
		||||
                            default=cliutils.env('OS_SERVICE_TYPE'),
 | 
			
		||||
                            help='Defaults to env[OS_SERVICE_TYPE] or '
 | 
			
		||||
                                 '"watcher"')
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os_service_type',
 | 
			
		||||
                            help=argparse.SUPPRESS)
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os-endpoint',
 | 
			
		||||
                            default=cliutils.env('OS_SERVICE_ENDPOINT'),
 | 
			
		||||
                            help='Specify an endpoint to use instead of '
 | 
			
		||||
                                 'retrieving one from the service catalog '
 | 
			
		||||
                                 '(via authentication). '
 | 
			
		||||
                                 'Defaults to env[OS_SERVICE_ENDPOINT].')
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os_endpoint',
 | 
			
		||||
                            help=argparse.SUPPRESS)
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os-endpoint-type',
 | 
			
		||||
                            default=cliutils.env('OS_ENDPOINT_TYPE'),
 | 
			
		||||
                            help='Defaults to env[OS_ENDPOINT_TYPE] or '
 | 
			
		||||
                                 '"publicURL"')
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os_endpoint_type',
 | 
			
		||||
                            help=argparse.SUPPRESS)
 | 
			
		||||
 | 
			
		||||
        # 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)
 | 
			
		||||
 | 
			
		||||
        return parser
 | 
			
		||||
 | 
			
		||||
    def get_subcommand_parser(self, version):
 | 
			
		||||
        parser = self.get_base_parser()
 | 
			
		||||
 | 
			
		||||
        self.subcommands = {}
 | 
			
		||||
        subparsers = parser.add_subparsers(metavar='<subcommand>')
 | 
			
		||||
        submodule = utils.import_versioned_module(version, 'shell')
 | 
			
		||||
        submodule.enhance_parser(parser, subparsers, self.subcommands)
 | 
			
		||||
        utils.define_commands_from_module(subparsers, self, self.subcommands)
 | 
			
		||||
        return parser
 | 
			
		||||
 | 
			
		||||
    def _setup_debugging(self, debug):
 | 
			
		||||
        if debug:
 | 
			
		||||
            logging.basicConfig(
 | 
			
		||||
                format="%(levelname)s (%(module)s:%(lineno)d) %(message)s",
 | 
			
		||||
                level=logging.DEBUG)
 | 
			
		||||
        else:
 | 
			
		||||
            logging.basicConfig(
 | 
			
		||||
                format="%(levelname)s %(message)s",
 | 
			
		||||
                level=logging.CRITICAL)
 | 
			
		||||
 | 
			
		||||
    def do_bash_completion(self):
 | 
			
		||||
        """Prints all of the commands and options for bash-completion."""
 | 
			
		||||
        commands = set()
 | 
			
		||||
        options = set()
 | 
			
		||||
        for sc_str, sc in self.subcommands.items():
 | 
			
		||||
            commands.add(sc_str)
 | 
			
		||||
            for option in sc._optionals._option_string_actions.keys():
 | 
			
		||||
                options.add(option)
 | 
			
		||||
 | 
			
		||||
        commands.remove('bash-completion')
 | 
			
		||||
        print(' '.join(commands | options))
 | 
			
		||||
 | 
			
		||||
    def _discover_auth_versions(self, session, auth_url):
 | 
			
		||||
        # discover the API versions the server is supporting base on the
 | 
			
		||||
        # given URL
 | 
			
		||||
        v2_auth_url = None
 | 
			
		||||
        v3_auth_url = None
 | 
			
		||||
        try:
 | 
			
		||||
            ks_discover = discover.Discover(session=session, auth_url=auth_url)
 | 
			
		||||
            v2_auth_url = ks_discover.url_for('2.0')
 | 
			
		||||
            v3_auth_url = ks_discover.url_for('3.0')
 | 
			
		||||
        except ks_exc.ClientException:
 | 
			
		||||
            # Identity service may not support discover API version.
 | 
			
		||||
            # Let's try to figure out the API version from the original URL.
 | 
			
		||||
            url_parts = urlparse.urlparse(auth_url)
 | 
			
		||||
            (scheme, netloc, path, params, query, fragment) = url_parts
 | 
			
		||||
            path = path.lower()
 | 
			
		||||
            if path.startswith('/v3'):
 | 
			
		||||
                v3_auth_url = auth_url
 | 
			
		||||
            elif path.startswith('/v2'):
 | 
			
		||||
                v2_auth_url = auth_url
 | 
			
		||||
            else:
 | 
			
		||||
                # not enough information to determine the auth version
 | 
			
		||||
                msg = _('Unable to determine the Keystone version '
 | 
			
		||||
                        'to authenticate with using the given '
 | 
			
		||||
                        'auth_url. Identity service may not support API '
 | 
			
		||||
                        'version discovery. Please provide a versioned '
 | 
			
		||||
                        'auth_url instead. %s') % auth_url
 | 
			
		||||
                raise exc.CommandError(msg)
 | 
			
		||||
 | 
			
		||||
        return (v2_auth_url, v3_auth_url)
 | 
			
		||||
 | 
			
		||||
    def _get_keystone_v3_auth(self, v3_auth_url, **kwargs):
 | 
			
		||||
        auth_token = kwargs.pop('auth_token', None)
 | 
			
		||||
        if auth_token:
 | 
			
		||||
            return v3_auth.Token(v3_auth_url, auth_token)
 | 
			
		||||
        else:
 | 
			
		||||
            return v3_auth.Password(v3_auth_url, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def _get_keystone_v2_auth(self, v2_auth_url, **kwargs):
 | 
			
		||||
        auth_token = kwargs.pop('auth_token', None)
 | 
			
		||||
        if auth_token:
 | 
			
		||||
            return v2_auth.Token(
 | 
			
		||||
                v2_auth_url,
 | 
			
		||||
                auth_token,
 | 
			
		||||
                tenant_id=kwargs.pop('project_id', None),
 | 
			
		||||
                tenant_name=kwargs.pop('project_name', None))
 | 
			
		||||
        else:
 | 
			
		||||
            return v2_auth.Password(
 | 
			
		||||
                v2_auth_url,
 | 
			
		||||
                username=kwargs.pop('username', None),
 | 
			
		||||
                password=kwargs.pop('password', None),
 | 
			
		||||
                tenant_id=kwargs.pop('project_id', None),
 | 
			
		||||
                tenant_name=kwargs.pop('project_name', None))
 | 
			
		||||
 | 
			
		||||
    def _get_keystone_auth(self, session, auth_url, **kwargs):
 | 
			
		||||
        # FIXME(dhu): this code should come from keystoneclient
 | 
			
		||||
 | 
			
		||||
        # discover the supported keystone versions using the given url
 | 
			
		||||
        (v2_auth_url, v3_auth_url) = self._discover_auth_versions(
 | 
			
		||||
            session=session,
 | 
			
		||||
            auth_url=auth_url)
 | 
			
		||||
 | 
			
		||||
        # Determine which authentication plugin to use. First inspect the
 | 
			
		||||
        # auth_url to see the supported version. If both v3 and v2 are
 | 
			
		||||
        # supported, then use the highest version if possible.
 | 
			
		||||
        auth = None
 | 
			
		||||
        if v3_auth_url and v2_auth_url:
 | 
			
		||||
            user_domain_name = kwargs.get('user_domain_name', None)
 | 
			
		||||
            user_domain_id = kwargs.get('user_domain_id', None)
 | 
			
		||||
            project_domain_name = kwargs.get('project_domain_name', None)
 | 
			
		||||
            project_domain_id = kwargs.get('project_domain_id', None)
 | 
			
		||||
 | 
			
		||||
            # support both v2 and v3 auth. Use v3 if domain information is
 | 
			
		||||
            # provided.
 | 
			
		||||
            if (user_domain_name or user_domain_id or project_domain_name or
 | 
			
		||||
                    project_domain_id):
 | 
			
		||||
                auth = self._get_keystone_v3_auth(v3_auth_url, **kwargs)
 | 
			
		||||
            else:
 | 
			
		||||
                auth = self._get_keystone_v2_auth(v2_auth_url, **kwargs)
 | 
			
		||||
        elif v3_auth_url:
 | 
			
		||||
            # support only v3
 | 
			
		||||
            auth = self._get_keystone_v3_auth(v3_auth_url, **kwargs)
 | 
			
		||||
        elif v2_auth_url:
 | 
			
		||||
            # support only v2
 | 
			
		||||
            auth = self._get_keystone_v2_auth(v2_auth_url, **kwargs)
 | 
			
		||||
        else:
 | 
			
		||||
            raise exc.CommandError('Unable to determine the Keystone version '
 | 
			
		||||
                                   'to authenticate with using the given '
 | 
			
		||||
                                   'auth_url.')
 | 
			
		||||
 | 
			
		||||
        return auth
 | 
			
		||||
 | 
			
		||||
    def main(self, argv):
 | 
			
		||||
        # Parse args once to find version
 | 
			
		||||
        parser = self.get_base_parser()
 | 
			
		||||
        (options, args) = parser.parse_known_args(argv)
 | 
			
		||||
        self._setup_debugging(options.debug)
 | 
			
		||||
 | 
			
		||||
        # build available subcommands based on version
 | 
			
		||||
        api_version = options.watcher_api_version
 | 
			
		||||
        subcommand_parser = self.get_subcommand_parser(api_version)
 | 
			
		||||
        self.parser = subcommand_parser
 | 
			
		||||
 | 
			
		||||
        # Handle top-level --help/-h before attempting to parse
 | 
			
		||||
        # a command off the command line
 | 
			
		||||
        if options.help or not argv:
 | 
			
		||||
            self.do_help(options)
 | 
			
		||||
            return 0
 | 
			
		||||
 | 
			
		||||
        # Parse args again and call whatever callback was selected
 | 
			
		||||
        args = subcommand_parser.parse_args(argv)
 | 
			
		||||
 | 
			
		||||
        # Short-circuit and deal with these commands right away.
 | 
			
		||||
        if args.func == self.do_help:
 | 
			
		||||
            self.do_help(args)
 | 
			
		||||
            return 0
 | 
			
		||||
        elif args.func == self.do_bash_completion:
 | 
			
		||||
            self.do_bash_completion()
 | 
			
		||||
            return 0
 | 
			
		||||
 | 
			
		||||
        if not (args.os_auth_token and (args.watcher_url or args.os_endpoint)):
 | 
			
		||||
            if not args.os_username:
 | 
			
		||||
                raise exc.CommandError(_("You must provide a username via "
 | 
			
		||||
                                         "either --os-username or via "
 | 
			
		||||
                                         "env[OS_USERNAME]"))
 | 
			
		||||
 | 
			
		||||
            if not args.os_password:
 | 
			
		||||
                # No password, If we've got a tty, try prompting for it
 | 
			
		||||
                if hasattr(sys.stdin, 'isatty') and sys.stdin.isatty():
 | 
			
		||||
                    # Check for Ctl-D
 | 
			
		||||
                    try:
 | 
			
		||||
                        args.os_password = getpass.getpass(
 | 
			
		||||
                            'OpenStack Password: ')
 | 
			
		||||
                    except EOFError:
 | 
			
		||||
                        pass
 | 
			
		||||
            # No password because we didn't have a tty or the
 | 
			
		||||
            # user Ctl-D when prompted.
 | 
			
		||||
            if not args.os_password:
 | 
			
		||||
                raise exc.CommandError(_("You must provide a password via "
 | 
			
		||||
                                         "either --os-password, "
 | 
			
		||||
                                         "env[OS_PASSWORD], "
 | 
			
		||||
                                         "or prompted response"))
 | 
			
		||||
 | 
			
		||||
            if not (args.os_tenant_id or args.os_tenant_name or
 | 
			
		||||
                    args.os_project_id or args.os_project_name):
 | 
			
		||||
                raise exc.CommandError(_(
 | 
			
		||||
                    "You must provide a project name or"
 | 
			
		||||
                    " project id via --os-project-name, --os-project-id,"
 | 
			
		||||
                    " env[OS_PROJECT_ID] or env[OS_PROJECT_NAME].  You may"
 | 
			
		||||
                    " use os-project and os-tenant interchangeably."))
 | 
			
		||||
 | 
			
		||||
            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]"))
 | 
			
		||||
 | 
			
		||||
        endpoint = args.watcher_url or args.os_endpoint
 | 
			
		||||
        service_type = args.os_service_type or 'infra-optim'
 | 
			
		||||
        project_id = args.os_project_id or args.os_tenant_id
 | 
			
		||||
        project_name = args.os_project_name or args.os_tenant_name
 | 
			
		||||
 | 
			
		||||
        if (args.os_auth_token and (args.watcher_url or args.os_endpoint)):
 | 
			
		||||
            kwargs = {
 | 
			
		||||
                'token': args.os_auth_token,
 | 
			
		||||
                'insecure': args.insecure,
 | 
			
		||||
                'timeout': args.timeout,
 | 
			
		||||
                'ca_file': args.os_cacert,
 | 
			
		||||
                'cert_file': args.os_cert,
 | 
			
		||||
                'key_file': args.os_key,
 | 
			
		||||
                'auth_ref': None,
 | 
			
		||||
            }
 | 
			
		||||
        elif (args.os_username and
 | 
			
		||||
              args.os_password and
 | 
			
		||||
              args.os_auth_url and
 | 
			
		||||
              (project_id or project_name)):
 | 
			
		||||
 | 
			
		||||
            keystone_session = kssession.Session.load_from_cli_options(args)
 | 
			
		||||
 | 
			
		||||
            kwargs = {
 | 
			
		||||
                'username': args.os_username,
 | 
			
		||||
                'user_domain_id': args.os_user_domain_id,
 | 
			
		||||
                'user_domain_name': args.os_user_domain_name,
 | 
			
		||||
                'password': args.os_password,
 | 
			
		||||
                'auth_token': args.os_auth_token,
 | 
			
		||||
                'project_id': project_id,
 | 
			
		||||
                'project_name': project_name,
 | 
			
		||||
                'project_domain_id': args.os_project_domain_id,
 | 
			
		||||
                'project_domain_name': args.os_project_domain_name,
 | 
			
		||||
            }
 | 
			
		||||
            keystone_auth = self._get_keystone_auth(keystone_session,
 | 
			
		||||
                                                    args.os_auth_url,
 | 
			
		||||
                                                    **kwargs)
 | 
			
		||||
            if not endpoint:
 | 
			
		||||
                svc_type = args.os_service_type
 | 
			
		||||
                region_name = args.os_region_name
 | 
			
		||||
                endpoint = keystone_auth.get_endpoint(keystone_session,
 | 
			
		||||
                                                      service_type=svc_type,
 | 
			
		||||
                                                      region_name=region_name)
 | 
			
		||||
 | 
			
		||||
            endpoint_type = args.os_endpoint_type or 'publicURL'
 | 
			
		||||
            kwargs = {
 | 
			
		||||
                'auth_url': args.os_auth_url,
 | 
			
		||||
                'session': keystone_session,
 | 
			
		||||
                'auth': keystone_auth,
 | 
			
		||||
                'service_type': service_type,
 | 
			
		||||
                'endpoint_type': endpoint_type,
 | 
			
		||||
                'region_name': args.os_region_name,
 | 
			
		||||
                'username': args.os_username,
 | 
			
		||||
                'password': args.os_password,
 | 
			
		||||
            }
 | 
			
		||||
        client = watcher_client.Client(api_version, endpoint, **kwargs)
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            args.func(client, args)
 | 
			
		||||
        except exc.Unauthorized:
 | 
			
		||||
            raise exc.CommandError(_("Invalid OpenStack Identity credentials"))
 | 
			
		||||
 | 
			
		||||
    @cliutils.arg('command', metavar='<subcommand>', nargs='?',
 | 
			
		||||
                  help='Display help for <subcommand>')
 | 
			
		||||
    def do_help(self, args):
 | 
			
		||||
        """Display help about this program or one of its subcommands."""
 | 
			
		||||
        if getattr(args, 'command', None):
 | 
			
		||||
            if args.command in self.subcommands:
 | 
			
		||||
                self.subcommands[args.command].print_help()
 | 
			
		||||
            else:
 | 
			
		||||
                raise exc.CommandError(_("'%s' is not a valid subcommand") %
 | 
			
		||||
                                       args.command)
 | 
			
		||||
        else:
 | 
			
		||||
            self.parser.print_help()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HelpFormatter(argparse.HelpFormatter):
 | 
			
		||||
    def start_section(self, heading):
 | 
			
		||||
        # Title-case the headings
 | 
			
		||||
        heading = '%s%s' % (heading[0].upper(), heading[1:])
 | 
			
		||||
        super(HelpFormatter, self).start_section(heading)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
    try:
 | 
			
		||||
        WatcherShell().main(sys.argv[1:])
 | 
			
		||||
    except KeyboardInterrupt:
 | 
			
		||||
        print("... terminating watcher client", file=sys.stderr)
 | 
			
		||||
        sys.exit(130)
 | 
			
		||||
    except Exception as e:
 | 
			
		||||
        print(str(e), file=sys.stderr)
 | 
			
		||||
        sys.exit(1)
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    main()
 | 
			
		||||
							
								
								
									
										0
									
								
								watcherclient/tests/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								watcherclient/tests/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										81
									
								
								watcherclient/tests/keystone_client_fixtures.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								watcherclient/tests/keystone_client_fixtures.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,81 @@
 | 
			
		||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
			
		||||
# not use this file except in compliance with the License. You may obtain
 | 
			
		||||
# a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#      http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
# Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
# 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 uuid
 | 
			
		||||
 | 
			
		||||
from keystoneclient.fixture import v2 as ks_v2_fixture
 | 
			
		||||
from keystoneclient.fixture import v3 as ks_v3_fixture
 | 
			
		||||
 | 
			
		||||
# these are copied from python-keystoneclient tests
 | 
			
		||||
BASE_HOST = 'http://keystone.example.com'
 | 
			
		||||
BASE_URL = "%s:5000/" % BASE_HOST
 | 
			
		||||
UPDATED = '2013-03-06T00:00:00Z'
 | 
			
		||||
 | 
			
		||||
V2_URL = "%sv2.0" % BASE_URL
 | 
			
		||||
V2_DESCRIBED_BY_HTML = {'href': 'http://docs.openstack.org/api/'
 | 
			
		||||
                                'openstack-identity-service/2.0/content/',
 | 
			
		||||
                        'rel': 'describedby',
 | 
			
		||||
                        'type': 'text/html'}
 | 
			
		||||
V2_DESCRIBED_BY_PDF = {'href': 'http://docs.openstack.org/api/openstack-ident'
 | 
			
		||||
                               'ity-service/2.0/identity-dev-guide-2.0.pdf',
 | 
			
		||||
                       'rel': 'describedby',
 | 
			
		||||
                       'type': 'application/pdf'}
 | 
			
		||||
 | 
			
		||||
V2_VERSION = {'id': 'v2.0',
 | 
			
		||||
              'links': [{'href': V2_URL, 'rel': 'self'},
 | 
			
		||||
                        V2_DESCRIBED_BY_HTML, V2_DESCRIBED_BY_PDF],
 | 
			
		||||
              'status': 'stable',
 | 
			
		||||
              'updated': UPDATED}
 | 
			
		||||
 | 
			
		||||
V3_URL = "%sv3" % BASE_URL
 | 
			
		||||
V3_MEDIA_TYPES = [{'base': 'application/json',
 | 
			
		||||
                   'type': 'application/vnd.openstack.identity-v3+json'},
 | 
			
		||||
                  {'base': 'application/xml',
 | 
			
		||||
                   'type': 'application/vnd.openstack.identity-v3+xml'}]
 | 
			
		||||
 | 
			
		||||
V3_VERSION = {'id': 'v3.0',
 | 
			
		||||
              'links': [{'href': V3_URL, 'rel': 'self'}],
 | 
			
		||||
              'media-types': V3_MEDIA_TYPES,
 | 
			
		||||
              'status': 'stable',
 | 
			
		||||
              'updated': UPDATED}
 | 
			
		||||
 | 
			
		||||
TOKENID = uuid.uuid4().hex
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _create_version_list(versions):
 | 
			
		||||
    return json.dumps({'versions': {'values': versions}})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _create_single_version(version):
 | 
			
		||||
    return json.dumps({'version': version})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
V3_VERSION_LIST = _create_version_list([V3_VERSION, V2_VERSION])
 | 
			
		||||
V2_VERSION_LIST = _create_version_list([V2_VERSION])
 | 
			
		||||
 | 
			
		||||
V3_VERSION_ENTRY = _create_single_version(V3_VERSION)
 | 
			
		||||
V2_VERSION_ENTRY = _create_single_version(V2_VERSION)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def keystone_request_callback(request, uri, headers):
 | 
			
		||||
    response_headers = {"content-type": "application/json"}
 | 
			
		||||
    token_id = TOKENID
 | 
			
		||||
    if uri == BASE_URL:
 | 
			
		||||
        return (200, headers, V3_VERSION_LIST)
 | 
			
		||||
    elif uri == BASE_URL + "/v2.0":
 | 
			
		||||
        v2_token = ks_v2_fixture.Token(token_id)
 | 
			
		||||
        return (200, response_headers, json.dumps(v2_token))
 | 
			
		||||
    elif uri == BASE_URL + "/v3":
 | 
			
		||||
        v3_token = ks_v3_fixture.Token()
 | 
			
		||||
        response_headers["X-Subject-Token"] = token_id
 | 
			
		||||
        return (201, response_headers, json.dumps(v3_token))
 | 
			
		||||
							
								
								
									
										142
									
								
								watcherclient/tests/test_client.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								watcherclient/tests/test_client.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,142 @@
 | 
			
		||||
# -*- 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 fixtures
 | 
			
		||||
 | 
			
		||||
from watcherclient.client import get_client
 | 
			
		||||
from watcherclient import exceptions as exc
 | 
			
		||||
from watcherclient.tests import utils
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def fake_get_ksclient(**kwargs):
 | 
			
		||||
    return utils.FakeKeystone('KSCLIENT_AUTH_TOKEN')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ClientTest(utils.BaseTestCase):
 | 
			
		||||
 | 
			
		||||
    def test_get_client_with_auth_token_watcher_url(self):
 | 
			
		||||
        self.useFixture(fixtures.MonkeyPatch(
 | 
			
		||||
            'watcherclient.client._get_ksclient', fake_get_ksclient))
 | 
			
		||||
        kwargs = {
 | 
			
		||||
            'watcher_url': 'http://watcher.example.org:6385/',
 | 
			
		||||
            'os_auth_token': 'USER_AUTH_TOKEN',
 | 
			
		||||
        }
 | 
			
		||||
        client = get_client('1', **kwargs)
 | 
			
		||||
 | 
			
		||||
        self.assertEqual('USER_AUTH_TOKEN', client.http_client.auth_token)
 | 
			
		||||
        self.assertEqual('http://watcher.example.org:6385/',
 | 
			
		||||
                         client.http_client.endpoint)
 | 
			
		||||
 | 
			
		||||
    def test_get_client_no_auth_token(self):
 | 
			
		||||
        self.useFixture(fixtures.MonkeyPatch(
 | 
			
		||||
            'watcherclient.client._get_ksclient', fake_get_ksclient))
 | 
			
		||||
        kwargs = {
 | 
			
		||||
            'os_tenant_name': 'TENANT_NAME',
 | 
			
		||||
            'os_username': 'USERNAME',
 | 
			
		||||
            'os_password': 'PASSWORD',
 | 
			
		||||
            'os_auth_url': 'http://localhost:35357/v2.0',
 | 
			
		||||
            'os_auth_token': '',
 | 
			
		||||
        }
 | 
			
		||||
        client = get_client('1', **kwargs)
 | 
			
		||||
 | 
			
		||||
        self.assertEqual('KSCLIENT_AUTH_TOKEN', client.http_client.auth_token)
 | 
			
		||||
        self.assertEqual('http://localhost:6385/v1/f14b41234',
 | 
			
		||||
                         client.http_client.endpoint)
 | 
			
		||||
        self.assertEqual(fake_get_ksclient().auth_ref,
 | 
			
		||||
                         client.http_client.auth_ref)
 | 
			
		||||
 | 
			
		||||
    def test_get_client_with_region_no_auth_token(self):
 | 
			
		||||
        self.useFixture(fixtures.MonkeyPatch(
 | 
			
		||||
            'watcherclient.client._get_ksclient', fake_get_ksclient))
 | 
			
		||||
        kwargs = {
 | 
			
		||||
            'os_tenant_name': 'TENANT_NAME',
 | 
			
		||||
            'os_username': 'USERNAME',
 | 
			
		||||
            'os_password': 'PASSWORD',
 | 
			
		||||
            'os_region_name': 'REGIONONE',
 | 
			
		||||
            'os_auth_url': 'http://localhost:35357/v2.0',
 | 
			
		||||
            'os_auth_token': '',
 | 
			
		||||
        }
 | 
			
		||||
        client = get_client('1', **kwargs)
 | 
			
		||||
 | 
			
		||||
        self.assertEqual('KSCLIENT_AUTH_TOKEN', client.http_client.auth_token)
 | 
			
		||||
        self.assertEqual('http://regionhost:6385/v1/f14b41234',
 | 
			
		||||
                         client.http_client.endpoint)
 | 
			
		||||
        self.assertEqual(fake_get_ksclient().auth_ref,
 | 
			
		||||
                         client.http_client.auth_ref)
 | 
			
		||||
 | 
			
		||||
    def test_get_client_with_auth_token(self):
 | 
			
		||||
        self.useFixture(fixtures.MonkeyPatch(
 | 
			
		||||
            'watcherclient.client._get_ksclient', fake_get_ksclient))
 | 
			
		||||
        kwargs = {
 | 
			
		||||
            'os_tenant_name': 'TENANT_NAME',
 | 
			
		||||
            'os_username': 'USERNAME',
 | 
			
		||||
            'os_password': 'PASSWORD',
 | 
			
		||||
            'os_auth_url': 'http://localhost:35357/v2.0',
 | 
			
		||||
            'os_auth_token': 'USER_AUTH_TOKEN',
 | 
			
		||||
        }
 | 
			
		||||
        client = get_client('1', **kwargs)
 | 
			
		||||
 | 
			
		||||
        self.assertEqual('USER_AUTH_TOKEN', client.http_client.auth_token)
 | 
			
		||||
        self.assertEqual('http://localhost:6385/v1/f14b41234',
 | 
			
		||||
                         client.http_client.endpoint)
 | 
			
		||||
        self.assertEqual(fake_get_ksclient().auth_ref,
 | 
			
		||||
                         client.http_client.auth_ref)
 | 
			
		||||
 | 
			
		||||
    def test_get_client_with_region_name_auth_token(self):
 | 
			
		||||
        self.useFixture(fixtures.MonkeyPatch(
 | 
			
		||||
            'watcherclient.client._get_ksclient', fake_get_ksclient))
 | 
			
		||||
        kwargs = {
 | 
			
		||||
            'os_tenant_name': 'TENANT_NAME',
 | 
			
		||||
            'os_username': 'USERNAME',
 | 
			
		||||
            'os_password': 'PASSWORD',
 | 
			
		||||
            'os_auth_url': 'http://localhost:35357/v2.0',
 | 
			
		||||
            'os_region_name': 'REGIONONE',
 | 
			
		||||
            'os_auth_token': 'USER_AUTH_TOKEN',
 | 
			
		||||
        }
 | 
			
		||||
        client = get_client('1', **kwargs)
 | 
			
		||||
 | 
			
		||||
        self.assertEqual('USER_AUTH_TOKEN', client.http_client.auth_token)
 | 
			
		||||
        self.assertEqual('http://regionhost:6385/v1/f14b41234',
 | 
			
		||||
                         client.http_client.endpoint)
 | 
			
		||||
        self.assertEqual(fake_get_ksclient().auth_ref,
 | 
			
		||||
                         client.http_client.auth_ref)
 | 
			
		||||
 | 
			
		||||
    def test_get_client_no_url_and_no_token(self):
 | 
			
		||||
        self.useFixture(fixtures.MonkeyPatch(
 | 
			
		||||
            'watcherclient.client._get_ksclient', fake_get_ksclient))
 | 
			
		||||
        kwargs = {
 | 
			
		||||
            'os_tenant_name': 'TENANT_NAME',
 | 
			
		||||
            'os_username': 'USERNAME',
 | 
			
		||||
            'os_password': 'PASSWORD',
 | 
			
		||||
            'os_auth_url': '',
 | 
			
		||||
            'os_auth_token': '',
 | 
			
		||||
        }
 | 
			
		||||
        self.assertRaises(exc.AmbiguousAuthSystem, get_client, '1', **kwargs)
 | 
			
		||||
        # test the alias as well to ensure backwards compatibility
 | 
			
		||||
        self.assertRaises(exc.AmbigiousAuthSystem, get_client, '1', **kwargs)
 | 
			
		||||
 | 
			
		||||
    def test_ensure_auth_ref_propagated(self):
 | 
			
		||||
        ksclient = fake_get_ksclient
 | 
			
		||||
        self.useFixture(fixtures.MonkeyPatch(
 | 
			
		||||
            'watcherclient.client._get_ksclient', ksclient))
 | 
			
		||||
        kwargs = {
 | 
			
		||||
            'os_tenant_name': 'TENANT_NAME',
 | 
			
		||||
            'os_username': 'USERNAME',
 | 
			
		||||
            'os_password': 'PASSWORD',
 | 
			
		||||
            'os_auth_url': 'http://localhost:35357/v2.0',
 | 
			
		||||
            'os_auth_token': '',
 | 
			
		||||
        }
 | 
			
		||||
        client = get_client('1', **kwargs)
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(ksclient().auth_ref, client.http_client.auth_ref)
 | 
			
		||||
							
								
								
									
										283
									
								
								watcherclient/tests/test_http.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										283
									
								
								watcherclient/tests/test_http.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,283 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
# Copyright 2012 OpenStack LLC.
 | 
			
		||||
# All Rights Reserved.
 | 
			
		||||
#
 | 
			
		||||
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
			
		||||
#    not use this file except in compliance with the License. You may obtain
 | 
			
		||||
#    a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#         http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
#    Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
#    License for the specific language governing permissions and limitations
 | 
			
		||||
#    under the License.
 | 
			
		||||
 | 
			
		||||
import json
 | 
			
		||||
 | 
			
		||||
import six
 | 
			
		||||
 | 
			
		||||
from watcherclient.common import http
 | 
			
		||||
from watcherclient import exceptions as exc
 | 
			
		||||
from watcherclient.tests import utils
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
HTTP_CLASS = six.moves.http_client.HTTPConnection
 | 
			
		||||
HTTPS_CLASS = http.VerifiedHTTPSConnection
 | 
			
		||||
DEFAULT_TIMEOUT = 600
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _get_error_body(faultstring=None, debuginfo=None):
 | 
			
		||||
    error_body = {
 | 
			
		||||
        'faultstring': faultstring,
 | 
			
		||||
        'debuginfo': debuginfo
 | 
			
		||||
    }
 | 
			
		||||
    raw_error_body = json.dumps(error_body)
 | 
			
		||||
    body = {'error_message': raw_error_body}
 | 
			
		||||
    raw_body = json.dumps(body)
 | 
			
		||||
    return raw_body
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HttpClientTest(utils.BaseTestCase):
 | 
			
		||||
 | 
			
		||||
    def test_url_generation_trailing_slash_in_base(self):
 | 
			
		||||
        client = http.HTTPClient('http://localhost/')
 | 
			
		||||
        url = client._make_connection_url('/v1/resources')
 | 
			
		||||
        self.assertEqual('/v1/resources', url)
 | 
			
		||||
 | 
			
		||||
    def test_url_generation_without_trailing_slash_in_base(self):
 | 
			
		||||
        client = http.HTTPClient('http://localhost')
 | 
			
		||||
        url = client._make_connection_url('/v1/resources')
 | 
			
		||||
        self.assertEqual('/v1/resources', url)
 | 
			
		||||
 | 
			
		||||
    def test_url_generation_prefix_slash_in_path(self):
 | 
			
		||||
        client = http.HTTPClient('http://localhost/')
 | 
			
		||||
        url = client._make_connection_url('/v1/resources')
 | 
			
		||||
        self.assertEqual('/v1/resources', url)
 | 
			
		||||
 | 
			
		||||
    def test_url_generation_without_prefix_slash_in_path(self):
 | 
			
		||||
        client = http.HTTPClient('http://localhost')
 | 
			
		||||
        url = client._make_connection_url('v1/resources')
 | 
			
		||||
        self.assertEqual('/v1/resources', url)
 | 
			
		||||
 | 
			
		||||
    def test_server_exception_empty_body(self):
 | 
			
		||||
        error_body = _get_error_body()
 | 
			
		||||
        fake_resp = utils.FakeResponse({'content-type': 'application/json'},
 | 
			
		||||
                                       six.StringIO(error_body),
 | 
			
		||||
                                       version=1,
 | 
			
		||||
                                       status=500)
 | 
			
		||||
        client = http.HTTPClient('http://localhost/')
 | 
			
		||||
        client.get_connection = (
 | 
			
		||||
            lambda *a, **kw: utils.FakeConnection(fake_resp))
 | 
			
		||||
 | 
			
		||||
        error = self.assertRaises(exc.InternalServerError,
 | 
			
		||||
                                  client.json_request,
 | 
			
		||||
                                  'GET', '/v1/resources')
 | 
			
		||||
        self.assertEqual('Internal Server Error (HTTP 500)', str(error))
 | 
			
		||||
 | 
			
		||||
    def test_server_exception_msg_only(self):
 | 
			
		||||
        error_msg = 'test error msg'
 | 
			
		||||
        error_body = _get_error_body(error_msg)
 | 
			
		||||
        fake_resp = utils.FakeResponse({'content-type': 'application/json'},
 | 
			
		||||
                                       six.StringIO(error_body),
 | 
			
		||||
                                       version=1,
 | 
			
		||||
                                       status=500)
 | 
			
		||||
        client = http.HTTPClient('http://localhost/')
 | 
			
		||||
        client.get_connection = (
 | 
			
		||||
            lambda *a, **kw: utils.FakeConnection(fake_resp))
 | 
			
		||||
 | 
			
		||||
        error = self.assertRaises(exc.InternalServerError,
 | 
			
		||||
                                  client.json_request,
 | 
			
		||||
                                  'GET', '/v1/resources')
 | 
			
		||||
        self.assertEqual(error_msg + ' (HTTP 500)', str(error))
 | 
			
		||||
 | 
			
		||||
    def test_server_exception_msg_and_traceback(self):
 | 
			
		||||
        error_msg = 'another test error'
 | 
			
		||||
        error_trace = ("\"Traceback (most recent call last):\\n\\n  "
 | 
			
		||||
                       "File \\\"/usr/local/lib/python2.7/...")
 | 
			
		||||
        error_body = _get_error_body(error_msg, error_trace)
 | 
			
		||||
        fake_resp = utils.FakeResponse({'content-type': 'application/json'},
 | 
			
		||||
                                       six.StringIO(error_body),
 | 
			
		||||
                                       version=1,
 | 
			
		||||
                                       status=500)
 | 
			
		||||
        client = http.HTTPClient('http://localhost/')
 | 
			
		||||
        client.get_connection = (
 | 
			
		||||
            lambda *a, **kw: utils.FakeConnection(fake_resp))
 | 
			
		||||
 | 
			
		||||
        error = self.assertRaises(exc.InternalServerError,
 | 
			
		||||
                                  client.json_request,
 | 
			
		||||
                                  'GET', '/v1/resources')
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            '%(error)s (HTTP 500)\n%(trace)s' % {'error': error_msg,
 | 
			
		||||
                                                 'trace': error_trace},
 | 
			
		||||
            "%(error)s\n%(details)s" % {'error': str(error),
 | 
			
		||||
                                        'details': str(error.details)})
 | 
			
		||||
 | 
			
		||||
    def test_get_connection_params(self):
 | 
			
		||||
        endpoint = 'http://watcher-host:6385'
 | 
			
		||||
        expected = (HTTP_CLASS,
 | 
			
		||||
                    ('watcher-host', 6385, ''),
 | 
			
		||||
                    {'timeout': DEFAULT_TIMEOUT})
 | 
			
		||||
        params = http.HTTPClient.get_connection_params(endpoint)
 | 
			
		||||
        self.assertEqual(expected, params)
 | 
			
		||||
 | 
			
		||||
    def test_get_connection_params_with_trailing_slash(self):
 | 
			
		||||
        endpoint = 'http://watcher-host:6385/'
 | 
			
		||||
        expected = (HTTP_CLASS,
 | 
			
		||||
                    ('watcher-host', 6385, ''),
 | 
			
		||||
                    {'timeout': DEFAULT_TIMEOUT})
 | 
			
		||||
        params = http.HTTPClient.get_connection_params(endpoint)
 | 
			
		||||
        self.assertEqual(expected, params)
 | 
			
		||||
 | 
			
		||||
    def test_get_connection_params_with_ssl(self):
 | 
			
		||||
        endpoint = 'https://watcher-host:6385'
 | 
			
		||||
        expected = (HTTPS_CLASS,
 | 
			
		||||
                    ('watcher-host', 6385, ''),
 | 
			
		||||
                    {
 | 
			
		||||
                        'timeout': DEFAULT_TIMEOUT,
 | 
			
		||||
                        'ca_file': None,
 | 
			
		||||
                        'cert_file': None,
 | 
			
		||||
                        'key_file': None,
 | 
			
		||||
                        'insecure': False,
 | 
			
		||||
                    })
 | 
			
		||||
        params = http.HTTPClient.get_connection_params(endpoint)
 | 
			
		||||
        self.assertEqual(expected, params)
 | 
			
		||||
 | 
			
		||||
    def test_get_connection_params_with_ssl_params(self):
 | 
			
		||||
        endpoint = 'https://watcher-host:6385'
 | 
			
		||||
        ssl_args = {
 | 
			
		||||
            'ca_file': '/path/to/ca_file',
 | 
			
		||||
            'cert_file': '/path/to/cert_file',
 | 
			
		||||
            'key_file': '/path/to/key_file',
 | 
			
		||||
            'insecure': True,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        expected_kwargs = {'timeout': DEFAULT_TIMEOUT}
 | 
			
		||||
        expected_kwargs.update(ssl_args)
 | 
			
		||||
        expected = (HTTPS_CLASS,
 | 
			
		||||
                    ('watcher-host', 6385, ''),
 | 
			
		||||
                    expected_kwargs)
 | 
			
		||||
        params = http.HTTPClient.get_connection_params(endpoint, **ssl_args)
 | 
			
		||||
        self.assertEqual(expected, params)
 | 
			
		||||
 | 
			
		||||
    def test_get_connection_params_with_timeout(self):
 | 
			
		||||
        endpoint = 'http://watcher-host:6385'
 | 
			
		||||
        expected = (HTTP_CLASS,
 | 
			
		||||
                    ('watcher-host', 6385, ''),
 | 
			
		||||
                    {'timeout': 300.0})
 | 
			
		||||
        params = http.HTTPClient.get_connection_params(endpoint, timeout=300)
 | 
			
		||||
        self.assertEqual(expected, params)
 | 
			
		||||
 | 
			
		||||
    def test_get_connection_params_with_version(self):
 | 
			
		||||
        endpoint = 'http://watcher-host:6385/v1'
 | 
			
		||||
        expected = (HTTP_CLASS,
 | 
			
		||||
                    ('watcher-host', 6385, ''),
 | 
			
		||||
                    {'timeout': DEFAULT_TIMEOUT})
 | 
			
		||||
        params = http.HTTPClient.get_connection_params(endpoint)
 | 
			
		||||
        self.assertEqual(expected, params)
 | 
			
		||||
 | 
			
		||||
    def test_get_connection_params_with_version_trailing_slash(self):
 | 
			
		||||
        endpoint = 'http://watcher-host:6385/v1/'
 | 
			
		||||
        expected = (HTTP_CLASS,
 | 
			
		||||
                    ('watcher-host', 6385, ''),
 | 
			
		||||
                    {'timeout': DEFAULT_TIMEOUT})
 | 
			
		||||
        params = http.HTTPClient.get_connection_params(endpoint)
 | 
			
		||||
        self.assertEqual(expected, params)
 | 
			
		||||
 | 
			
		||||
    def test_get_connection_params_with_subpath(self):
 | 
			
		||||
        endpoint = 'http://watcher-host:6385/watcher'
 | 
			
		||||
        expected = (HTTP_CLASS,
 | 
			
		||||
                    ('watcher-host', 6385, '/watcher'),
 | 
			
		||||
                    {'timeout': DEFAULT_TIMEOUT})
 | 
			
		||||
        params = http.HTTPClient.get_connection_params(endpoint)
 | 
			
		||||
        self.assertEqual(expected, params)
 | 
			
		||||
 | 
			
		||||
    def test_get_connection_params_with_subpath_trailing_slash(self):
 | 
			
		||||
        endpoint = 'http://watcher-host:6385/watcher/'
 | 
			
		||||
        expected = (HTTP_CLASS,
 | 
			
		||||
                    ('watcher-host', 6385, '/watcher'),
 | 
			
		||||
                    {'timeout': DEFAULT_TIMEOUT})
 | 
			
		||||
        params = http.HTTPClient.get_connection_params(endpoint)
 | 
			
		||||
        self.assertEqual(expected, params)
 | 
			
		||||
 | 
			
		||||
    def test_get_connection_params_with_subpath_version(self):
 | 
			
		||||
        endpoint = 'http://watcher-host:6385/watcher/v1'
 | 
			
		||||
        expected = (HTTP_CLASS,
 | 
			
		||||
                    ('watcher-host', 6385, '/watcher'),
 | 
			
		||||
                    {'timeout': DEFAULT_TIMEOUT})
 | 
			
		||||
        params = http.HTTPClient.get_connection_params(endpoint)
 | 
			
		||||
        self.assertEqual(expected, params)
 | 
			
		||||
 | 
			
		||||
    def test_get_connection_params_with_subpath_version_trailing_slash(self):
 | 
			
		||||
        endpoint = 'http://watcher-host:6385/watcher/v1/'
 | 
			
		||||
        expected = (HTTP_CLASS,
 | 
			
		||||
                    ('watcher-host', 6385, '/watcher'),
 | 
			
		||||
                    {'timeout': DEFAULT_TIMEOUT})
 | 
			
		||||
        params = http.HTTPClient.get_connection_params(endpoint)
 | 
			
		||||
        self.assertEqual(expected, params)
 | 
			
		||||
 | 
			
		||||
    def test_401_unauthorized_exception(self):
 | 
			
		||||
        error_body = _get_error_body()
 | 
			
		||||
        fake_resp = utils.FakeResponse({'content-type': 'text/plain'},
 | 
			
		||||
                                       six.StringIO(error_body),
 | 
			
		||||
                                       version=1,
 | 
			
		||||
                                       status=401)
 | 
			
		||||
        client = http.HTTPClient('http://localhost/')
 | 
			
		||||
        client.get_connection = (
 | 
			
		||||
            lambda *a, **kw: utils.FakeConnection(fake_resp))
 | 
			
		||||
 | 
			
		||||
        self.assertRaises(exc.Unauthorized, client.json_request,
 | 
			
		||||
                          'GET', '/v1/resources')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SessionClientTest(utils.BaseTestCase):
 | 
			
		||||
 | 
			
		||||
    def test_server_exception_msg_and_traceback(self):
 | 
			
		||||
        error_msg = 'another test error'
 | 
			
		||||
        error_trace = ("\"Traceback (most recent call last):\\n\\n  "
 | 
			
		||||
                       "File \\\"/usr/local/lib/python2.7/...")
 | 
			
		||||
        error_body = _get_error_body(error_msg, error_trace)
 | 
			
		||||
 | 
			
		||||
        fake_session = utils.FakeSession({'Content-Type': 'application/json'},
 | 
			
		||||
                                         error_body,
 | 
			
		||||
                                         500)
 | 
			
		||||
 | 
			
		||||
        client = http.SessionClient(session=fake_session,
 | 
			
		||||
                                    auth=None,
 | 
			
		||||
                                    interface=None,
 | 
			
		||||
                                    service_type='publicURL',
 | 
			
		||||
                                    region_name='',
 | 
			
		||||
                                    service_name=None)
 | 
			
		||||
 | 
			
		||||
        error = self.assertRaises(exc.InternalServerError,
 | 
			
		||||
                                  client.json_request,
 | 
			
		||||
                                  'GET', '/v1/resources')
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            '%(error)s (HTTP 500)\n%(trace)s' % {'error': error_msg,
 | 
			
		||||
                                                 'trace': error_trace},
 | 
			
		||||
            "%(error)s\n%(details)s" % {'error': str(error),
 | 
			
		||||
                                        'details': str(error.details)})
 | 
			
		||||
 | 
			
		||||
    def test_server_exception_empty_body(self):
 | 
			
		||||
        error_body = _get_error_body()
 | 
			
		||||
 | 
			
		||||
        fake_session = utils.FakeSession({'Content-Type': 'application/json'},
 | 
			
		||||
                                         error_body,
 | 
			
		||||
                                         500)
 | 
			
		||||
 | 
			
		||||
        client = http.SessionClient(session=fake_session,
 | 
			
		||||
                                    auth=None,
 | 
			
		||||
                                    interface=None,
 | 
			
		||||
                                    service_type='publicURL',
 | 
			
		||||
                                    region_name='',
 | 
			
		||||
                                    service_name=None)
 | 
			
		||||
 | 
			
		||||
        error = self.assertRaises(exc.InternalServerError,
 | 
			
		||||
                                  client.json_request,
 | 
			
		||||
                                  'GET', '/v1/resources')
 | 
			
		||||
 | 
			
		||||
        self.assertEqual('Internal Server Error (HTTP 500)', str(error))
 | 
			
		||||
							
								
								
									
										38
									
								
								watcherclient/tests/test_import.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								watcherclient/tests/test_import.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
#   Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
			
		||||
#   not use this file except in compliance with the License. You may obtain
 | 
			
		||||
#   a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#       http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
#   Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
#   distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
#   WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
#   License for the specific language governing permissions and limitations
 | 
			
		||||
#   under the License.
 | 
			
		||||
 | 
			
		||||
from watcherclient.tests import utils
 | 
			
		||||
 | 
			
		||||
module_str = 'watcherclient'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ImportTest(utils.BaseTestCase):
 | 
			
		||||
 | 
			
		||||
    def check_exported_symbols(self, exported_symbols):
 | 
			
		||||
        self.assertIn('client', exported_symbols)
 | 
			
		||||
        self.assertIn('exceptions', exported_symbols)
 | 
			
		||||
 | 
			
		||||
    def test_import_objects(self):
 | 
			
		||||
        module = __import__(module_str)
 | 
			
		||||
        exported_symbols = dir(module)
 | 
			
		||||
        self.check_exported_symbols(exported_symbols)
 | 
			
		||||
 | 
			
		||||
    def test_default_import(self):
 | 
			
		||||
        default_imports = __import__(module_str, globals(), locals(), ['*'])
 | 
			
		||||
        exported_symbols = dir(default_imports)
 | 
			
		||||
        self.check_exported_symbols(exported_symbols)
 | 
			
		||||
 | 
			
		||||
    def test_import__all__(self):
 | 
			
		||||
        module = __import__(module_str)
 | 
			
		||||
        self.check_exported_symbols(module.__all__)
 | 
			
		||||
							
								
								
									
										306
									
								
								watcherclient/tests/test_shell.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										306
									
								
								watcherclient/tests/test_shell.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,306 @@
 | 
			
		||||
# -*- 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 json
 | 
			
		||||
import os
 | 
			
		||||
import re
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
import fixtures
 | 
			
		||||
import httpretty
 | 
			
		||||
from keystoneclient import exceptions as keystone_exc
 | 
			
		||||
from keystoneclient.fixture import v2 as ks_v2_fixture
 | 
			
		||||
from keystoneclient.fixture import v3 as ks_v3_fixture
 | 
			
		||||
import mock
 | 
			
		||||
import six
 | 
			
		||||
import testtools
 | 
			
		||||
from testtools import matchers
 | 
			
		||||
 | 
			
		||||
from watcherclient import exceptions as exc
 | 
			
		||||
from watcherclient import shell as watcher_shell
 | 
			
		||||
from watcherclient.tests import keystone_client_fixtures
 | 
			
		||||
from watcherclient.tests import utils
 | 
			
		||||
 | 
			
		||||
FAKE_ENV = {'OS_USERNAME': 'username',
 | 
			
		||||
            'OS_PASSWORD': 'password',
 | 
			
		||||
            'OS_TENANT_NAME': 'tenant_name',
 | 
			
		||||
            'OS_AUTH_URL': 'http://no.where/v2.0/'}
 | 
			
		||||
 | 
			
		||||
FAKE_ENV_KEYSTONE_V2 = {
 | 
			
		||||
    'OS_USERNAME': 'username',
 | 
			
		||||
    'OS_PASSWORD': 'password',
 | 
			
		||||
    'OS_TENANT_NAME': 'tenant_name',
 | 
			
		||||
    'OS_AUTH_URL': keystone_client_fixtures.BASE_URL,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
FAKE_ENV_KEYSTONE_V3 = {
 | 
			
		||||
    'OS_USERNAME': 'username',
 | 
			
		||||
    'OS_PASSWORD': 'password',
 | 
			
		||||
    'OS_TENANT_NAME': 'tenant_name',
 | 
			
		||||
    'OS_AUTH_URL': keystone_client_fixtures.BASE_URL,
 | 
			
		||||
    'OS_USER_DOMAIN_ID': 'default',
 | 
			
		||||
    'OS_PROJECT_DOMAIN_ID': 'default',
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ShellTest(utils.BaseTestCase):
 | 
			
		||||
    re_options = re.DOTALL | re.MULTILINE
 | 
			
		||||
 | 
			
		||||
    # Patch os.environ to avoid required auth info.
 | 
			
		||||
    def make_env(self, exclude=None):
 | 
			
		||||
        env = dict((k, v) for k, v in FAKE_ENV.items() if k != exclude)
 | 
			
		||||
        self.useFixture(fixtures.MonkeyPatch('os.environ', env))
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super(ShellTest, self).setUp()
 | 
			
		||||
 | 
			
		||||
    def shell(self, argstr):
 | 
			
		||||
        orig = sys.stdout
 | 
			
		||||
        try:
 | 
			
		||||
            sys.stdout = six.StringIO()
 | 
			
		||||
            _shell = watcher_shell.WatcherShell()
 | 
			
		||||
            _shell.main(argstr.split())
 | 
			
		||||
        except SystemExit:
 | 
			
		||||
            exc_type, exc_value, exc_traceback = sys.exc_info()
 | 
			
		||||
            self.assertEqual(0, exc_value.code)
 | 
			
		||||
        finally:
 | 
			
		||||
            out = sys.stdout.getvalue()
 | 
			
		||||
            sys.stdout.close()
 | 
			
		||||
            sys.stdout = orig
 | 
			
		||||
        return out
 | 
			
		||||
 | 
			
		||||
    def test_help_unknown_command(self):
 | 
			
		||||
        self.assertRaises(exc.CommandError, self.shell, 'help foofoo')
 | 
			
		||||
 | 
			
		||||
    def test_help(self):
 | 
			
		||||
        required = [
 | 
			
		||||
            '.*?^usage: watcher',
 | 
			
		||||
            '.*?^ +bash-completion',
 | 
			
		||||
            '.*?^See "watcher help COMMAND" '
 | 
			
		||||
            'for help on a specific command',
 | 
			
		||||
        ]
 | 
			
		||||
        for argstr in ['--help', 'help']:
 | 
			
		||||
            help_text = self.shell(argstr)
 | 
			
		||||
            for r in required:
 | 
			
		||||
                self.assertThat(help_text,
 | 
			
		||||
                                matchers.MatchesRegex(r,
 | 
			
		||||
                                                      self.re_options))
 | 
			
		||||
 | 
			
		||||
    def test_help_on_subcommand(self):
 | 
			
		||||
        required = [
 | 
			
		||||
            '.*?^usage: watcher action-show',
 | 
			
		||||
            ".*?^Show detailed information about an action",
 | 
			
		||||
        ]
 | 
			
		||||
        argstrings = [
 | 
			
		||||
            'help action-show',
 | 
			
		||||
        ]
 | 
			
		||||
        for argstr in argstrings:
 | 
			
		||||
            help_text = self.shell(argstr)
 | 
			
		||||
            for r in required:
 | 
			
		||||
                self.assertThat(help_text,
 | 
			
		||||
                                matchers.MatchesRegex(r, self.re_options))
 | 
			
		||||
 | 
			
		||||
    def test_auth_param(self):
 | 
			
		||||
        self.make_env(exclude='OS_USERNAME')
 | 
			
		||||
        self.test_help()
 | 
			
		||||
 | 
			
		||||
    @mock.patch('sys.stdin', side_effect=mock.MagicMock)
 | 
			
		||||
    @mock.patch('getpass.getpass', return_value='password')
 | 
			
		||||
    def test_password_prompted(self, mock_getpass, mock_stdin):
 | 
			
		||||
        self.make_env(exclude='OS_PASSWORD')
 | 
			
		||||
        # We will get a Connection Refused because there is no keystone.
 | 
			
		||||
        self.assertRaises(keystone_exc.ConnectionRefused,
 | 
			
		||||
                          self.shell, 'action-list')
 | 
			
		||||
        # Make sure we are actually prompted.
 | 
			
		||||
        mock_getpass.assert_called_with('OpenStack Password: ')
 | 
			
		||||
 | 
			
		||||
    @mock.patch('sys.stdin', side_effect=mock.MagicMock)
 | 
			
		||||
    @mock.patch('getpass.getpass', side_effect=EOFError)
 | 
			
		||||
    def test_password_prompted_ctrlD(self, mock_getpass, mock_stdin):
 | 
			
		||||
        self.make_env(exclude='OS_PASSWORD')
 | 
			
		||||
        # We should get Command Error because we mock Ctl-D.
 | 
			
		||||
        self.assertRaises(exc.CommandError,
 | 
			
		||||
                          self.shell, 'action-list')
 | 
			
		||||
        # Make sure we are actually prompted.
 | 
			
		||||
        mock_getpass.assert_called_with('OpenStack Password: ')
 | 
			
		||||
 | 
			
		||||
    @mock.patch('sys.stdin')
 | 
			
		||||
    def test_no_password_no_tty(self, mock_stdin):
 | 
			
		||||
        # delete the isatty attribute so that we do not get
 | 
			
		||||
        # prompted when manually running the tests
 | 
			
		||||
        del mock_stdin.isatty
 | 
			
		||||
        required = ('You must provide a password'
 | 
			
		||||
                    ' via either --os-password, env[OS_PASSWORD],'
 | 
			
		||||
                    ' or prompted response',)
 | 
			
		||||
        self.make_env(exclude='OS_PASSWORD')
 | 
			
		||||
        try:
 | 
			
		||||
            self.shell('action-list')
 | 
			
		||||
        except exc.CommandError as message:
 | 
			
		||||
            self.assertEqual(required, message.args)
 | 
			
		||||
        else:
 | 
			
		||||
            self.fail('CommandError not raised')
 | 
			
		||||
 | 
			
		||||
    def test_bash_completion(self):
 | 
			
		||||
        stdout = self.shell('bash-completion')
 | 
			
		||||
        # just check we have some output
 | 
			
		||||
        required = [
 | 
			
		||||
            '.*help',
 | 
			
		||||
            '.*audit-list',
 | 
			
		||||
            '.*audit-show',
 | 
			
		||||
            '.*audit-delete',
 | 
			
		||||
            '.*audit-update',
 | 
			
		||||
            '.*audit-template-create',
 | 
			
		||||
            '.*audit-template-update',
 | 
			
		||||
            '.*audit-template-list',
 | 
			
		||||
            '.*audit-template-show',
 | 
			
		||||
            '.*audit-template-delete',
 | 
			
		||||
            '.*action-list',
 | 
			
		||||
            '.*action-show',
 | 
			
		||||
            '.*action-update',
 | 
			
		||||
            '.*action-plan-list',
 | 
			
		||||
            '.*action-plan-show',
 | 
			
		||||
            '.*action-plan-update',
 | 
			
		||||
        ]
 | 
			
		||||
        for r in required:
 | 
			
		||||
            self.assertThat(stdout,
 | 
			
		||||
                            matchers.MatchesRegex(r, self.re_options))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestCase(testtools.TestCase):
 | 
			
		||||
 | 
			
		||||
    tokenid = keystone_client_fixtures.TOKENID
 | 
			
		||||
 | 
			
		||||
    def set_fake_env(self, fake_env):
 | 
			
		||||
        client_env = ('OS_USERNAME', 'OS_PASSWORD', 'OS_TENANT_ID',
 | 
			
		||||
                      'OS_TENANT_NAME', 'OS_AUTH_URL', 'OS_REGION_NAME',
 | 
			
		||||
                      'OS_AUTH_TOKEN', 'OS_NO_CLIENT_AUTH', 'OS_SERVICE_TYPE',
 | 
			
		||||
                      'OS_ENDPOINT_TYPE')
 | 
			
		||||
 | 
			
		||||
        for key in client_env:
 | 
			
		||||
            self.useFixture(
 | 
			
		||||
                fixtures.EnvironmentVariable(key, fake_env.get(key)))
 | 
			
		||||
 | 
			
		||||
    # required for testing with Python 2.6
 | 
			
		||||
    def assertRegexpMatches(self, text, expected_regexp, msg=None):
 | 
			
		||||
        """Fail the test unless the text matches the regular expression."""
 | 
			
		||||
        if isinstance(expected_regexp, six.string_types):
 | 
			
		||||
            expected_regexp = re.compile(expected_regexp)
 | 
			
		||||
        if not expected_regexp.search(text):
 | 
			
		||||
            msg = msg or "Regexp didn't match"
 | 
			
		||||
            msg = '%s: %r not found in %r' % (
 | 
			
		||||
                msg, expected_regexp.pattern, text)
 | 
			
		||||
            raise self.failureException(msg)
 | 
			
		||||
 | 
			
		||||
    def register_keystone_v2_token_fixture(self):
 | 
			
		||||
        v2_token = ks_v2_fixture.Token(token_id=self.tokenid)
 | 
			
		||||
        service = v2_token.add_service('baremetal')
 | 
			
		||||
        service.add_endpoint('http://watcher.example.com', region='RegionOne')
 | 
			
		||||
        httpretty.register_uri(
 | 
			
		||||
            httpretty.POST,
 | 
			
		||||
            '%s/tokens' % (keystone_client_fixtures.V2_URL),
 | 
			
		||||
            body=json.dumps(v2_token))
 | 
			
		||||
 | 
			
		||||
    def register_keystone_v3_token_fixture(self):
 | 
			
		||||
        v3_token = ks_v3_fixture.Token()
 | 
			
		||||
        service = v3_token.add_service('baremetal')
 | 
			
		||||
        service.add_standard_endpoints(public='http://watcher.example.com')
 | 
			
		||||
        httpretty.register_uri(
 | 
			
		||||
            httpretty.POST,
 | 
			
		||||
            '%s/auth/tokens' % (keystone_client_fixtures.V3_URL),
 | 
			
		||||
            body=json.dumps(v3_token),
 | 
			
		||||
            adding_headers={'X-Subject-Token': self.tokenid})
 | 
			
		||||
 | 
			
		||||
    def register_keystone_auth_fixture(self):
 | 
			
		||||
        self.register_keystone_v2_token_fixture()
 | 
			
		||||
        self.register_keystone_v3_token_fixture()
 | 
			
		||||
        httpretty.register_uri(
 | 
			
		||||
            httpretty.GET,
 | 
			
		||||
            keystone_client_fixtures.BASE_URL,
 | 
			
		||||
            body=keystone_client_fixtures.keystone_request_callback)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ShellTestNoMox(TestCase):
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super(ShellTestNoMox, self).setUp()
 | 
			
		||||
        # httpretty doesn't work as expected if http proxy environment
 | 
			
		||||
        # variable is set.
 | 
			
		||||
        os.environ = dict((k, v) for (k, v) in os.environ.items()
 | 
			
		||||
                          if k.lower() not in ('http_proxy', 'https_proxy'))
 | 
			
		||||
        self.set_fake_env(FAKE_ENV_KEYSTONE_V2)
 | 
			
		||||
 | 
			
		||||
    def shell(self, argstr):
 | 
			
		||||
        orig = sys.stdout
 | 
			
		||||
        try:
 | 
			
		||||
            sys.stdout = six.StringIO()
 | 
			
		||||
            _shell = watcher_shell.WatcherShell()
 | 
			
		||||
            _shell.main(argstr.split())
 | 
			
		||||
            self.subcommands = _shell.subcommands.keys()
 | 
			
		||||
        except SystemExit:
 | 
			
		||||
            exc_type, exc_value, exc_traceback = sys.exc_info()
 | 
			
		||||
            self.assertEqual(0, exc_value.code)
 | 
			
		||||
        finally:
 | 
			
		||||
            out = sys.stdout.getvalue()
 | 
			
		||||
            sys.stdout.close()
 | 
			
		||||
            sys.stdout = orig
 | 
			
		||||
 | 
			
		||||
        return out
 | 
			
		||||
 | 
			
		||||
    # @httpretty.activate
 | 
			
		||||
    # def test_action_list(self):
 | 
			
		||||
    #     self.register_keystone_auth_fixture()
 | 
			
		||||
    #     resp_dict = {"dummies": [
 | 
			
		||||
    #                  {"instance_uuid": "null",
 | 
			
		||||
    #                   "uuid": "351a82d6-9f04-4c36-b79a-a38b9e98ff71",
 | 
			
		||||
    #                   "links": [{"href": "http://watcher.example.com:6385/"
 | 
			
		||||
    #                              "v1/dummies/foo",
 | 
			
		||||
    #                              "rel": "self"},
 | 
			
		||||
    #                             {"href": "http://watcher.example.com:6385/"
 | 
			
		||||
    #                              "dummies/foo",
 | 
			
		||||
    #                              "rel": "bookmark"}],
 | 
			
		||||
    #                   "maintenance": "false",
 | 
			
		||||
    #                   "provision_state": "null",
 | 
			
		||||
    #                   "power_state": "power off"},
 | 
			
		||||
    #                  {"instance_uuid": "null",
 | 
			
		||||
    #                   "uuid": "66fbba13-29e8-4b8a-9e80-c655096a40d3",
 | 
			
		||||
    #                   "links": [{"href": "http://watcher.example.com:6385/"
 | 
			
		||||
    #                              "v1/dummies/foo2",
 | 
			
		||||
    #                              "rel": "self"},
 | 
			
		||||
    #                             {"href": "http://watcher.example.com:6385/"
 | 
			
		||||
    #                              "dummies/foo2",
 | 
			
		||||
    #                              "rel": "bookmark"}],
 | 
			
		||||
    #                   "maintenance": "false",
 | 
			
		||||
    #                   "provision_state": "null",
 | 
			
		||||
    #                   "power_state": "power off"}]}
 | 
			
		||||
    #     httpretty.register_uri(
 | 
			
		||||
    #         httpretty.GET,
 | 
			
		||||
    #         'http://watcher.example.com/v1/dummies',
 | 
			
		||||
    #         status=200,
 | 
			
		||||
    #         content_type='application/json; charset=UTF-8',
 | 
			
		||||
    #         body=json.dumps(resp_dict))
 | 
			
		||||
 | 
			
		||||
    #     event_list_text = self.shell('action-list')
 | 
			
		||||
 | 
			
		||||
    #     required = [
 | 
			
		||||
    #         '351a82d6-9f04-4c36-b79a-a38b9e98ff71',
 | 
			
		||||
    #         '66fbba13-29e8-4b8a-9e80-c655096a40d3',
 | 
			
		||||
    #     ]
 | 
			
		||||
 | 
			
		||||
    #     for r in required:
 | 
			
		||||
    #         self.assertRegexpMatches(event_list_text, r)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ShellTestNoMoxV3(ShellTestNoMox):
 | 
			
		||||
 | 
			
		||||
    def _set_fake_env(self):
 | 
			
		||||
        self.set_fake_env(FAKE_ENV_KEYSTONE_V3)
 | 
			
		||||
							
								
								
									
										173
									
								
								watcherclient/tests/test_utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								watcherclient/tests/test_utils.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,173 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
# Copyright 2013 OpenStack LLC.
 | 
			
		||||
# All Rights Reserved.
 | 
			
		||||
#
 | 
			
		||||
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
			
		||||
#    not use this file except in compliance with the License. You may obtain
 | 
			
		||||
#    a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#         http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
#    Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
#    License for the specific language governing permissions and limitations
 | 
			
		||||
#    under the License.
 | 
			
		||||
 | 
			
		||||
import mock
 | 
			
		||||
 | 
			
		||||
from watcherclient.common import utils
 | 
			
		||||
from watcherclient import exceptions as exc
 | 
			
		||||
from watcherclient.tests import utils as test_utils
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UtilsTest(test_utils.BaseTestCase):
 | 
			
		||||
 | 
			
		||||
    def test_args_array_to_dict(self):
 | 
			
		||||
        my_args = {
 | 
			
		||||
            'matching_metadata': ['str=foo', 'int=1', 'bool=true',
 | 
			
		||||
                                  'list=[1, 2, 3]', 'dict={"foo": "bar"}'],
 | 
			
		||||
            'other': 'value'
 | 
			
		||||
        }
 | 
			
		||||
        cleaned_dict = utils.args_array_to_dict(my_args,
 | 
			
		||||
                                                "matching_metadata")
 | 
			
		||||
        self.assertEqual({
 | 
			
		||||
            'matching_metadata': {'str': 'foo', 'int': 1, 'bool': True,
 | 
			
		||||
                                  'list': [1, 2, 3], 'dict': {'foo': 'bar'}},
 | 
			
		||||
            'other': 'value'
 | 
			
		||||
        }, cleaned_dict)
 | 
			
		||||
 | 
			
		||||
    def test_args_array_to_patch(self):
 | 
			
		||||
        my_args = {
 | 
			
		||||
            'attributes': ['str=foo', 'int=1', 'bool=true',
 | 
			
		||||
                           'list=[1, 2, 3]', 'dict={"foo": "bar"}'],
 | 
			
		||||
            'op': 'add',
 | 
			
		||||
        }
 | 
			
		||||
        patch = utils.args_array_to_patch(my_args['op'],
 | 
			
		||||
                                          my_args['attributes'])
 | 
			
		||||
        self.assertEqual([{'op': 'add', 'value': 'foo', 'path': '/str'},
 | 
			
		||||
                          {'op': 'add', 'value': 1, 'path': '/int'},
 | 
			
		||||
                          {'op': 'add', 'value': True, 'path': '/bool'},
 | 
			
		||||
                          {'op': 'add', 'value': [1, 2, 3], 'path': '/list'},
 | 
			
		||||
                          {'op': 'add', 'value': {"foo": "bar"},
 | 
			
		||||
                           'path': '/dict'}], patch)
 | 
			
		||||
 | 
			
		||||
    def test_args_array_to_patch_format_error(self):
 | 
			
		||||
        my_args = {
 | 
			
		||||
            'attributes': ['foobar'],
 | 
			
		||||
            'op': 'add',
 | 
			
		||||
        }
 | 
			
		||||
        self.assertRaises(exc.CommandError, utils.args_array_to_patch,
 | 
			
		||||
                          my_args['op'], my_args['attributes'])
 | 
			
		||||
 | 
			
		||||
    def test_args_array_to_patch_remove(self):
 | 
			
		||||
        my_args = {
 | 
			
		||||
            'attributes': ['/foo', 'extra/bar'],
 | 
			
		||||
            'op': 'remove',
 | 
			
		||||
        }
 | 
			
		||||
        patch = utils.args_array_to_patch(my_args['op'],
 | 
			
		||||
                                          my_args['attributes'])
 | 
			
		||||
        self.assertEqual([{'op': 'remove', 'path': '/foo'},
 | 
			
		||||
                          {'op': 'remove', 'path': '/extra/bar'}], patch)
 | 
			
		||||
 | 
			
		||||
    def test_split_and_deserialize(self):
 | 
			
		||||
        ret = utils.split_and_deserialize('str=foo')
 | 
			
		||||
        self.assertEqual(('str', 'foo'), ret)
 | 
			
		||||
 | 
			
		||||
        ret = utils.split_and_deserialize('int=1')
 | 
			
		||||
        self.assertEqual(('int', 1), ret)
 | 
			
		||||
 | 
			
		||||
        ret = utils.split_and_deserialize('bool=false')
 | 
			
		||||
        self.assertEqual(('bool', False), ret)
 | 
			
		||||
 | 
			
		||||
        ret = utils.split_and_deserialize('list=[1, "foo", 2]')
 | 
			
		||||
        self.assertEqual(('list', [1, "foo", 2]), ret)
 | 
			
		||||
 | 
			
		||||
        ret = utils.split_and_deserialize('dict={"foo": 1}')
 | 
			
		||||
        self.assertEqual(('dict', {"foo": 1}), ret)
 | 
			
		||||
 | 
			
		||||
        ret = utils.split_and_deserialize('str_int="1"')
 | 
			
		||||
        self.assertEqual(('str_int', "1"), ret)
 | 
			
		||||
 | 
			
		||||
    def test_split_and_deserialize_fail(self):
 | 
			
		||||
        self.assertRaises(exc.CommandError,
 | 
			
		||||
                          utils.split_and_deserialize, 'foo:bar')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CommonParamsForListTest(test_utils.BaseTestCase):
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super(CommonParamsForListTest, self).setUp()
 | 
			
		||||
        self.args = mock.Mock(limit=None, sort_key=None, sort_dir=None)
 | 
			
		||||
        self.args.detail = False
 | 
			
		||||
        self.expected_params = {'detail': False}
 | 
			
		||||
 | 
			
		||||
    def test_nothing_set(self):
 | 
			
		||||
        self.assertEqual(self.expected_params,
 | 
			
		||||
                         utils.common_params_for_list(self.args, [], []))
 | 
			
		||||
 | 
			
		||||
    def test_limit(self):
 | 
			
		||||
        self.args.limit = 42
 | 
			
		||||
        self.expected_params.update({'limit': 42})
 | 
			
		||||
        self.assertEqual(self.expected_params,
 | 
			
		||||
                         utils.common_params_for_list(self.args, [], []))
 | 
			
		||||
 | 
			
		||||
    def test_invalid_limit(self):
 | 
			
		||||
        self.args.limit = -42
 | 
			
		||||
        self.assertRaises(exc.CommandError,
 | 
			
		||||
                          utils.common_params_for_list,
 | 
			
		||||
                          self.args, [], [])
 | 
			
		||||
 | 
			
		||||
    def test_sort_key_and_sort_dir(self):
 | 
			
		||||
        self.args.sort_key = 'field'
 | 
			
		||||
        self.args.sort_dir = 'desc'
 | 
			
		||||
        self.expected_params.update({'sort_key': 'field', 'sort_dir': 'desc'})
 | 
			
		||||
        self.assertEqual(self.expected_params,
 | 
			
		||||
                         utils.common_params_for_list(self.args,
 | 
			
		||||
                                                      ['field'],
 | 
			
		||||
                                                      []))
 | 
			
		||||
 | 
			
		||||
    def test_sort_key_allows_label(self):
 | 
			
		||||
        self.args.sort_key = 'Label'
 | 
			
		||||
        self.expected_params.update({'sort_key': 'field'})
 | 
			
		||||
        self.assertEqual(self.expected_params,
 | 
			
		||||
                         utils.common_params_for_list(self.args,
 | 
			
		||||
                                                      ['field', 'field2'],
 | 
			
		||||
                                                      ['Label', 'Label2']))
 | 
			
		||||
 | 
			
		||||
    def test_sort_key_invalid(self):
 | 
			
		||||
        self.args.sort_key = 'something'
 | 
			
		||||
        self.assertRaises(exc.CommandError,
 | 
			
		||||
                          utils.common_params_for_list,
 | 
			
		||||
                          self.args,
 | 
			
		||||
                          ['field', 'field2'],
 | 
			
		||||
                          [])
 | 
			
		||||
 | 
			
		||||
    def test_sort_dir_invalid(self):
 | 
			
		||||
        self.args.sort_dir = 'something'
 | 
			
		||||
        self.assertRaises(exc.CommandError,
 | 
			
		||||
                          utils.common_params_for_list,
 | 
			
		||||
                          self.args,
 | 
			
		||||
                          [],
 | 
			
		||||
                          [])
 | 
			
		||||
 | 
			
		||||
    def test_detail(self):
 | 
			
		||||
        self.args.detail = True
 | 
			
		||||
        self.expected_params['detail'] = True
 | 
			
		||||
        self.assertEqual(self.expected_params,
 | 
			
		||||
                         utils.common_params_for_list(self.args, [], []))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CommonFiltersTest(test_utils.BaseTestCase):
 | 
			
		||||
    def test_limit(self):
 | 
			
		||||
        result = utils.common_filters(limit=42)
 | 
			
		||||
        self.assertEqual(['limit=42'], result)
 | 
			
		||||
 | 
			
		||||
    def test_limit_0(self):
 | 
			
		||||
        result = utils.common_filters(limit=0)
 | 
			
		||||
        self.assertEqual([], result)
 | 
			
		||||
 | 
			
		||||
    def test_other(self):
 | 
			
		||||
        for key in ('sort_key', 'sort_dir'):
 | 
			
		||||
            result = utils.common_filters(**{key: 'test'})
 | 
			
		||||
            self.assertEqual(['%s=test' % key], result)
 | 
			
		||||
							
								
								
									
										144
									
								
								watcherclient/tests/utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								watcherclient/tests/utils.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,144 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
# Copyright 2012 OpenStack LLC.
 | 
			
		||||
# All Rights Reserved.
 | 
			
		||||
#
 | 
			
		||||
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
			
		||||
#    not use this file except in compliance with the License. You may obtain
 | 
			
		||||
#    a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#         http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
#    Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
#    License for the specific language governing permissions and limitations
 | 
			
		||||
#    under the License.
 | 
			
		||||
 | 
			
		||||
import copy
 | 
			
		||||
import datetime
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
import fixtures
 | 
			
		||||
from oslo_utils import strutils
 | 
			
		||||
import six
 | 
			
		||||
import testtools
 | 
			
		||||
 | 
			
		||||
from watcherclient.common import http
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BaseTestCase(testtools.TestCase):
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super(BaseTestCase, self).setUp()
 | 
			
		||||
        self.useFixture(fixtures.FakeLogger())
 | 
			
		||||
 | 
			
		||||
        # If enabled, stdout and/or stderr is captured and will appear in
 | 
			
		||||
        # test results if that test fails.
 | 
			
		||||
        if strutils.bool_from_string(os.environ.get('OS_STDOUT_CAPTURE')):
 | 
			
		||||
            stdout = self.useFixture(fixtures.StringStream('stdout')).stream
 | 
			
		||||
            self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
 | 
			
		||||
        if strutils.bool_from_string(os.environ.get('OS_STDERR_CAPTURE')):
 | 
			
		||||
            stderr = self.useFixture(fixtures.StringStream('stderr')).stream
 | 
			
		||||
            self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FakeAPI(object):
 | 
			
		||||
    def __init__(self, responses):
 | 
			
		||||
        self.responses = responses
 | 
			
		||||
        self.calls = []
 | 
			
		||||
 | 
			
		||||
    def _request(self, method, url, headers=None, body=None):
 | 
			
		||||
        call = (method, url, headers or {}, body)
 | 
			
		||||
        self.calls.append(call)
 | 
			
		||||
        return self.responses[url][method]
 | 
			
		||||
 | 
			
		||||
    def raw_request(self, *args, **kwargs):
 | 
			
		||||
        response = self._request(*args, **kwargs)
 | 
			
		||||
        body_iter = http.ResponseBodyIterator(six.StringIO(response[1]))
 | 
			
		||||
        return FakeResponse(response[0]), body_iter
 | 
			
		||||
 | 
			
		||||
    def json_request(self, *args, **kwargs):
 | 
			
		||||
        response = self._request(*args, **kwargs)
 | 
			
		||||
        return FakeResponse(response[0]), response[1]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FakeConnection(object):
 | 
			
		||||
    def __init__(self, response=None):
 | 
			
		||||
        self._response = response
 | 
			
		||||
        self._last_request = None
 | 
			
		||||
 | 
			
		||||
    def request(self, method, conn_url, **kwargs):
 | 
			
		||||
        self._last_request = (method, conn_url, kwargs)
 | 
			
		||||
 | 
			
		||||
    def setresponse(self, response):
 | 
			
		||||
        self._response = response
 | 
			
		||||
 | 
			
		||||
    def getresponse(self):
 | 
			
		||||
        return self._response
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FakeResponse(object):
 | 
			
		||||
    def __init__(self, headers, body=None, version=None, status=None,
 | 
			
		||||
                 reason=None):
 | 
			
		||||
        """Fake object to help testing.
 | 
			
		||||
 | 
			
		||||
        :param headers: dict representing HTTP response headers
 | 
			
		||||
        :param body: file-like object
 | 
			
		||||
        """
 | 
			
		||||
        self.headers = headers
 | 
			
		||||
        self.body = body
 | 
			
		||||
        self.version = version
 | 
			
		||||
        self.status = status
 | 
			
		||||
        self.reason = reason
 | 
			
		||||
 | 
			
		||||
    def getheaders(self):
 | 
			
		||||
        return copy.deepcopy(self.headers).items()
 | 
			
		||||
 | 
			
		||||
    def getheader(self, key, default):
 | 
			
		||||
        return self.headers.get(key, default)
 | 
			
		||||
 | 
			
		||||
    def read(self, amt):
 | 
			
		||||
        return self.body.read(amt)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FakeServiceCatalog(object):
 | 
			
		||||
    def url_for(self, endpoint_type, service_type, attr=None,
 | 
			
		||||
                filter_value=None):
 | 
			
		||||
        if attr == 'region' and filter_value:
 | 
			
		||||
            return 'http://regionhost:6385/v1/f14b41234'
 | 
			
		||||
        else:
 | 
			
		||||
            return 'http://localhost:6385/v1/f14b41234'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FakeKeystone(object):
 | 
			
		||||
    service_catalog = FakeServiceCatalog()
 | 
			
		||||
    timestamp = datetime.datetime.utcnow() + datetime.timedelta(days=5)
 | 
			
		||||
 | 
			
		||||
    def __init__(self, auth_token):
 | 
			
		||||
        self.auth_token = auth_token
 | 
			
		||||
        self.auth_ref = {
 | 
			
		||||
            'token': {'expires': FakeKeystone.timestamp.strftime(
 | 
			
		||||
                      '%Y-%m-%dT%H:%M:%S.%f'),
 | 
			
		||||
                      'id': 'd1a541311782870742235'}
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FakeSessionResponse(object):
 | 
			
		||||
 | 
			
		||||
    def __init__(self, headers, content=None, status_code=None):
 | 
			
		||||
        self.headers = headers
 | 
			
		||||
        self.content = content
 | 
			
		||||
        self.status_code = status_code
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FakeSession(object):
 | 
			
		||||
 | 
			
		||||
    def __init__(self, headers, content=None, status_code=None):
 | 
			
		||||
        self.headers = headers
 | 
			
		||||
        self.content = content
 | 
			
		||||
        self.status_code = status_code
 | 
			
		||||
 | 
			
		||||
    def request(self, url, method, **kwargs):
 | 
			
		||||
        return FakeSessionResponse(self.headers, self.content,
 | 
			
		||||
                                   self.status_code)
 | 
			
		||||
							
								
								
									
										0
									
								
								watcherclient/tests/v1/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								watcherclient/tests/v1/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										277
									
								
								watcherclient/tests/v1/test_action.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										277
									
								
								watcherclient/tests/v1/test_action.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,277 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
# Copyright 2013 Red Hat, Inc.
 | 
			
		||||
# All Rights Reserved.
 | 
			
		||||
#
 | 
			
		||||
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
			
		||||
#    not use this file except in compliance with the License. You may obtain
 | 
			
		||||
#    a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#         http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
#    Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
#    License for the specific language governing permissions and limitations
 | 
			
		||||
#    under the License.
 | 
			
		||||
 | 
			
		||||
import copy
 | 
			
		||||
 | 
			
		||||
import testtools
 | 
			
		||||
from testtools.matchers import HasLength
 | 
			
		||||
 | 
			
		||||
from watcherclient.tests import utils
 | 
			
		||||
import watcherclient.v1.action
 | 
			
		||||
 | 
			
		||||
ACTION1 = {
 | 
			
		||||
    'id': 1,
 | 
			
		||||
    'uuid': '770ef053-ecb3-48b0-85b5-d55a2dbc6588',
 | 
			
		||||
    'action_plan': 'f8e47706-efcf-49a4-a5c4-af604eb492f2',
 | 
			
		||||
    'description': 'Action_1 description',
 | 
			
		||||
    'next': '239f02a5-9649-4e14-9d33-ac2bf67cb755',
 | 
			
		||||
    'state': 'PENDING',
 | 
			
		||||
    'alarm': None
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ACTION2 = {
 | 
			
		||||
    'id': 2,
 | 
			
		||||
    'uuid': '239f02a5-9649-4e14-9d33-ac2bf67cb755',
 | 
			
		||||
    'action_plan': 'f8e47706-efcf-49a4-a5c4-af604eb492f2',
 | 
			
		||||
    'description': 'Action_2 description',
 | 
			
		||||
    'next': '67653274-eb24-c7ba-70f6-a84e73d80843',
 | 
			
		||||
    'state': 'PENDING',
 | 
			
		||||
    'alarm': None
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ACTION3 = {
 | 
			
		||||
    'id': 3,
 | 
			
		||||
    'uuid': '67653274-eb24-c7ba-70f6-a84e73d80843',
 | 
			
		||||
    'action_plan': 'a5199d0e-0702-4613-9234-5ae2af8dafea',
 | 
			
		||||
    'description': 'Action_3 description',
 | 
			
		||||
    'next': None,
 | 
			
		||||
    'state': 'PENDING',
 | 
			
		||||
    'alarm': None
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ACTION_PLAN1 = {
 | 
			
		||||
    'id': 1,
 | 
			
		||||
    'uuid': 'a5199d0e-0702-4613-9234-5ae2af8dafea',
 | 
			
		||||
    'audit': '770ef053-ecb3-48b0-85b5-d55a2dbc6588',
 | 
			
		||||
    'state': 'RECOMMENDED'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
UPDATED_ACTION1 = copy.deepcopy(ACTION1)
 | 
			
		||||
NEW_EXTRA = 'key1=val1'
 | 
			
		||||
UPDATED_ACTION1['extra'] = NEW_EXTRA
 | 
			
		||||
 | 
			
		||||
fake_responses = {
 | 
			
		||||
    '/v1/actions':
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"actions": [ACTION1, ACTION2, ACTION3]},
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/v1/actions/?action_plan_uuid=%s' % ACTION1['action_plan']:
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"actions": [ACTION1, ACTION2]},
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/v1/actions/?audit_uuid=%s' % ACTION_PLAN1['audit']:
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"actions": [ACTION3]},
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/v1/actions/detail':
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"actions": [ACTION1, ACTION2, ACTION3]},
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/v1/actions/%s' % ACTION1['uuid']:
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            ACTION1,
 | 
			
		||||
        ),
 | 
			
		||||
        'DELETE': (
 | 
			
		||||
            {},
 | 
			
		||||
            None,
 | 
			
		||||
        ),
 | 
			
		||||
        'PATCH': (
 | 
			
		||||
            {},
 | 
			
		||||
            UPDATED_ACTION1,
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/v1/actions/detail?action_plan_uuid=%s' % ACTION1['action_plan']:
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"actions": [ACTION1, ACTION2]},
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/v1/actions/detail?audit_uuid=%s' % ACTION_PLAN1['audit']:
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"actions": [ACTION3]},
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fake_responses_pagination = {
 | 
			
		||||
    '/v1/actions':
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"actions": [ACTION1],
 | 
			
		||||
             "next": "http://127.0.0.1:6385/v1/actions/?limit=1"}
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/v1/actions/?limit=1':
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"actions": [ACTION2]}
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fake_responses_sorting = {
 | 
			
		||||
    '/v1/actions/?sort_key=updated_at':
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"actions": [ACTION3, ACTION2, ACTION1]}
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/v1/actions/?sort_dir=desc':
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"actions": [ACTION3, ACTION2, ACTION1]}
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ActionManagerTest(testtools.TestCase):
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super(ActionManagerTest, self).setUp()
 | 
			
		||||
        self.api = utils.FakeAPI(fake_responses)
 | 
			
		||||
        self.mgr = watcherclient.v1.action.ActionManager(self.api)
 | 
			
		||||
 | 
			
		||||
    def test_actions_list(self):
 | 
			
		||||
        actions = self.mgr.list()
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET', '/v1/actions', {}, None),
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertEqual(3, len(actions))
 | 
			
		||||
 | 
			
		||||
    def test_actions_list_by_action_plan(self):
 | 
			
		||||
        actions = self.mgr.list(action_plan=ACTION1['action_plan'])
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET',
 | 
			
		||||
             '/v1/actions/?action_plan_uuid=%s' % ACTION1['action_plan'],
 | 
			
		||||
             {}, None),
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertEqual(2, len(actions))
 | 
			
		||||
 | 
			
		||||
    def test_actions_list_detail(self):
 | 
			
		||||
        actions = self.mgr.list(detail=True)
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET', '/v1/actions/detail', {}, None),
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertEqual(3, len(actions))
 | 
			
		||||
 | 
			
		||||
    def test_actions_list_by_action_plan_detail(self):
 | 
			
		||||
        actions = self.mgr.list(action_plan=ACTION1['action_plan'],
 | 
			
		||||
                                detail=True)
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET',
 | 
			
		||||
             '/v1/actions/detail?action_plan_uuid=%s' % ACTION1['action_plan'],
 | 
			
		||||
             {},
 | 
			
		||||
             None),
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertEqual(2, len(actions))
 | 
			
		||||
 | 
			
		||||
    def test_actions_list_limit(self):
 | 
			
		||||
        self.api = utils.FakeAPI(fake_responses_pagination)
 | 
			
		||||
        self.mgr = watcherclient.v1.action.ActionManager(self.api)
 | 
			
		||||
        actions = self.mgr.list(limit=1)
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET', '/v1/actions/?limit=1', {}, None),
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertThat(actions, HasLength(1))
 | 
			
		||||
 | 
			
		||||
    def test_actions_list_pagination_no_limit(self):
 | 
			
		||||
        self.api = utils.FakeAPI(fake_responses_pagination)
 | 
			
		||||
        self.mgr = watcherclient.v1.action.ActionManager(self.api)
 | 
			
		||||
        actions = self.mgr.list(limit=0)
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET', '/v1/actions', {}, None),
 | 
			
		||||
            ('GET', '/v1/actions/?limit=1', {}, None)
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertThat(actions, HasLength(2))
 | 
			
		||||
 | 
			
		||||
    def test_actions_list_sort_key(self):
 | 
			
		||||
        self.api = utils.FakeAPI(fake_responses_sorting)
 | 
			
		||||
        self.mgr = watcherclient.v1.action.ActionManager(self.api)
 | 
			
		||||
        actions = self.mgr.list(sort_key='updated_at')
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET', '/v1/actions/?sort_key=updated_at', {}, None)
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertEqual(3, len(actions))
 | 
			
		||||
 | 
			
		||||
    def test_actions_list_sort_dir(self):
 | 
			
		||||
        self.api = utils.FakeAPI(fake_responses_sorting)
 | 
			
		||||
        self.mgr = watcherclient.v1.action.ActionManager(self.api)
 | 
			
		||||
        actions = self.mgr.list(sort_dir='desc')
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET', '/v1/actions/?sort_dir=desc', {}, None)
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertEqual(3, len(actions))
 | 
			
		||||
 | 
			
		||||
    def test_actions_show(self):
 | 
			
		||||
        action = self.mgr.get(ACTION1['uuid'])
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET', '/v1/actions/%s' % ACTION1['uuid'], {}, None),
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertEqual(ACTION1['uuid'], action.uuid)
 | 
			
		||||
        self.assertEqual(ACTION1['action_plan'], action.action_plan)
 | 
			
		||||
        self.assertEqual(ACTION1['alarm'], action.alarm)
 | 
			
		||||
        self.assertEqual(ACTION1['next'], action.next)
 | 
			
		||||
 | 
			
		||||
    def test_delete(self):
 | 
			
		||||
        action = self.mgr.delete(action_id=ACTION1['uuid'])
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('DELETE', '/v1/actions/%s' % ACTION1['uuid'], {}, None),
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertIsNone(action)
 | 
			
		||||
 | 
			
		||||
    def test_update(self):
 | 
			
		||||
        patch = {'op': 'replace',
 | 
			
		||||
                 'value': NEW_EXTRA,
 | 
			
		||||
                 'path': '/extra'}
 | 
			
		||||
        action = self.mgr.update(action_id=ACTION1['uuid'], patch=patch)
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('PATCH', '/v1/actions/%s' % ACTION1['uuid'], {}, patch),
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertEqual(NEW_EXTRA, action.extra)
 | 
			
		||||
							
								
								
									
										204
									
								
								watcherclient/tests/v1/test_action_plan.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										204
									
								
								watcherclient/tests/v1/test_action_plan.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,204 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
# Copyright 2013 Red Hat, Inc.
 | 
			
		||||
# All Rights Reserved.
 | 
			
		||||
#
 | 
			
		||||
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
			
		||||
#    not use this file except in compliance with the License. You may obtain
 | 
			
		||||
#    a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#         http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
#    Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
#    License for the specific language governing permissions and limitations
 | 
			
		||||
#    under the License.
 | 
			
		||||
 | 
			
		||||
import copy
 | 
			
		||||
 | 
			
		||||
import testtools
 | 
			
		||||
from testtools.matchers import HasLength
 | 
			
		||||
 | 
			
		||||
from watcherclient.tests import utils
 | 
			
		||||
import watcherclient.v1.action_plan
 | 
			
		||||
 | 
			
		||||
ACTION_PLAN1 = {
 | 
			
		||||
    'id': 1,
 | 
			
		||||
    'uuid': 'f8e47706-efcf-49a4-a5c4-af604eb492f2',
 | 
			
		||||
    'audit': '770ef053-ecb3-48b0-85b5-d55a2dbc6588',
 | 
			
		||||
    'state': 'RECOMMENDED'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ACTION_PLAN2 = {
 | 
			
		||||
    'id': 2,
 | 
			
		||||
    'uuid': 'a5199d0e-0702-4613-9234-5ae2af8dafea',
 | 
			
		||||
    'audit': '239f02a5-9649-4e14-9d33-ac2bf67cb755',
 | 
			
		||||
    'state': 'RECOMMENDED'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
UPDATED_ACTION_PLAN = copy.deepcopy(ACTION_PLAN1)
 | 
			
		||||
NEW_STATE = 'STARTING'
 | 
			
		||||
UPDATED_ACTION_PLAN['state'] = NEW_STATE
 | 
			
		||||
 | 
			
		||||
fake_responses = {
 | 
			
		||||
    '/v1/action_plans':
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"action_plans": [ACTION_PLAN1, ACTION_PLAN2]},
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/v1/action_plans/detail':
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"action_plans": [ACTION_PLAN1, ACTION_PLAN2]},
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/v1/action_plans/%s' % ACTION_PLAN1['uuid']:
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            ACTION_PLAN1,
 | 
			
		||||
        ),
 | 
			
		||||
        'DELETE': (
 | 
			
		||||
            {},
 | 
			
		||||
            None,
 | 
			
		||||
        ),
 | 
			
		||||
        'PATCH': (
 | 
			
		||||
            {},
 | 
			
		||||
            UPDATED_ACTION_PLAN,
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/v1/action_plans/detail?uuid=%s' % ACTION_PLAN1['uuid']:
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"action_plans": [ACTION_PLAN1]},
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fake_responses_pagination = {
 | 
			
		||||
    '/v1/action_plans':
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"action_plans": [ACTION_PLAN1],
 | 
			
		||||
             "next": "http://127.0.0.1:6385/v1/action_plans/?limit=1"}
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/v1/action_plans/?limit=1':
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"action_plans": [ACTION_PLAN2]}
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fake_responses_sorting = {
 | 
			
		||||
    '/v1/action_plans/?sort_key=updated_at':
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"action_plans": [ACTION_PLAN2, ACTION_PLAN1]}
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/v1/action_plans/?sort_dir=desc':
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"action_plans": [ACTION_PLAN2, ACTION_PLAN1]}
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ActionPlanManagerTest(testtools.TestCase):
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super(ActionPlanManagerTest, self).setUp()
 | 
			
		||||
        self.api = utils.FakeAPI(fake_responses)
 | 
			
		||||
        self.mgr = watcherclient.v1.action_plan.ActionPlanManager(self.api)
 | 
			
		||||
 | 
			
		||||
    def test_action_plans_list(self):
 | 
			
		||||
        action_plans = self.mgr.list()
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET', '/v1/action_plans', {}, None),
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertEqual(2, len(action_plans))
 | 
			
		||||
 | 
			
		||||
    def test_action_plans_list_detail(self):
 | 
			
		||||
        action_plans = self.mgr.list(detail=True)
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET', '/v1/action_plans/detail', {}, None),
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertEqual(2, len(action_plans))
 | 
			
		||||
 | 
			
		||||
    def test_action_plans_list_limit(self):
 | 
			
		||||
        self.api = utils.FakeAPI(fake_responses_pagination)
 | 
			
		||||
        self.mgr = watcherclient.v1.action_plan.ActionPlanManager(self.api)
 | 
			
		||||
        action_plans = self.mgr.list(limit=1)
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET', '/v1/action_plans/?limit=1', {}, None),
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertThat(action_plans, HasLength(1))
 | 
			
		||||
 | 
			
		||||
    def test_action_plans_list_pagination_no_limit(self):
 | 
			
		||||
        self.api = utils.FakeAPI(fake_responses_pagination)
 | 
			
		||||
        self.mgr = watcherclient.v1.action_plan.ActionPlanManager(self.api)
 | 
			
		||||
        action_plans = self.mgr.list(limit=0)
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET', '/v1/action_plans', {}, None),
 | 
			
		||||
            ('GET', '/v1/action_plans/?limit=1', {}, None)
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertThat(action_plans, HasLength(2))
 | 
			
		||||
 | 
			
		||||
    def test_action_plans_list_sort_key(self):
 | 
			
		||||
        self.api = utils.FakeAPI(fake_responses_sorting)
 | 
			
		||||
        self.mgr = watcherclient.v1.action_plan.ActionPlanManager(self.api)
 | 
			
		||||
        action_plans = self.mgr.list(sort_key='updated_at')
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET', '/v1/action_plans/?sort_key=updated_at', {}, None)
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertEqual(2, len(action_plans))
 | 
			
		||||
 | 
			
		||||
    def test_action_plans_list_sort_dir(self):
 | 
			
		||||
        self.api = utils.FakeAPI(fake_responses_sorting)
 | 
			
		||||
        self.mgr = watcherclient.v1.action_plan.ActionPlanManager(self.api)
 | 
			
		||||
        action_plans = self.mgr.list(sort_dir='desc')
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET', '/v1/action_plans/?sort_dir=desc', {}, None)
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertEqual(2, len(action_plans))
 | 
			
		||||
 | 
			
		||||
    def test_action_plans_show(self):
 | 
			
		||||
        action_plan = self.mgr.get(ACTION_PLAN1['uuid'])
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET', '/v1/action_plans/%s' % ACTION_PLAN1['uuid'], {}, None),
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertEqual(ACTION_PLAN1['uuid'], action_plan.uuid)
 | 
			
		||||
 | 
			
		||||
    def test_action_plan_update(self):
 | 
			
		||||
        patch = {'op': 'replace',
 | 
			
		||||
                 'value': NEW_STATE,
 | 
			
		||||
                 'path': '/state'}
 | 
			
		||||
        action_plan = self.mgr.update(action_plan_id=ACTION_PLAN1['uuid'],
 | 
			
		||||
                                      patch=patch)
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('PATCH',
 | 
			
		||||
             '/v1/action_plans/%s' % ACTION_PLAN1['uuid'],
 | 
			
		||||
             {},
 | 
			
		||||
             patch),
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertEqual(NEW_STATE, action_plan.state)
 | 
			
		||||
							
								
								
									
										148
									
								
								watcherclient/tests/v1/test_action_plan_shell.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								watcherclient/tests/v1/test_action_plan_shell.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,148 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
# Copyright 2013 IBM Corp
 | 
			
		||||
#
 | 
			
		||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
			
		||||
# not use this file except in compliance with the License. You may obtain
 | 
			
		||||
# a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#       http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
#   Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
#   distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
#   WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
#   License for the specific language governing permissions and limitations
 | 
			
		||||
#   under the License.
 | 
			
		||||
 | 
			
		||||
import mock
 | 
			
		||||
 | 
			
		||||
from watcherclient.common import utils as commonutils
 | 
			
		||||
from watcherclient.openstack.common.apiclient.exceptions import ValidationError
 | 
			
		||||
from watcherclient.openstack.common import cliutils
 | 
			
		||||
from watcherclient.tests import utils
 | 
			
		||||
import watcherclient.v1.action_plan_shell as ap_shell
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ActionPlanShellTest(utils.BaseTestCase):
 | 
			
		||||
    def test_do_action_plan_show(self):
 | 
			
		||||
        actual = {}
 | 
			
		||||
        fake_print_dict = lambda data, *args, **kwargs: actual.update(data)
 | 
			
		||||
        with mock.patch.object(cliutils, 'print_dict', fake_print_dict):
 | 
			
		||||
            action_plan = object()
 | 
			
		||||
            ap_shell._print_action_plan_show(action_plan)
 | 
			
		||||
        exp = ['uuid', 'created_at', 'updated_at', 'deleted_at',
 | 
			
		||||
               'state', 'audit_uuid']
 | 
			
		||||
        act = actual.keys()
 | 
			
		||||
        self.assertEqual(sorted(exp), sorted(act))
 | 
			
		||||
 | 
			
		||||
    def test_do_action_plan_show_by_uuid(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
        setattr(args, 'action-plan', 'a5199d0e-0702-4613-9234-5ae2af8dafea')
 | 
			
		||||
 | 
			
		||||
        ap_shell.do_action_plan_show(client_mock, args)
 | 
			
		||||
        client_mock.action_plan.get.assert_called_once_with(
 | 
			
		||||
            'a5199d0e-0702-4613-9234-5ae2af8dafea'
 | 
			
		||||
        )
 | 
			
		||||
        # assert get_by_name() wasn't called
 | 
			
		||||
        self.assertFalse(client_mock.action_plan.get_by_name.called)
 | 
			
		||||
 | 
			
		||||
    def test_do_action_plan_show_by_not_uuid(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
        setattr(args, 'action-plan', 'not_uuid')
 | 
			
		||||
 | 
			
		||||
        self.assertRaises(ValidationError, ap_shell.do_action_plan_show,
 | 
			
		||||
                          client_mock, args)
 | 
			
		||||
 | 
			
		||||
    def test_do_action_plan_delete(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
        delete = ['a5199d0e-0702-4613-9234-5ae2af8dafea']
 | 
			
		||||
        setattr(args, 'action-plan', delete)
 | 
			
		||||
 | 
			
		||||
        ap_shell.do_action_plan_delete(client_mock, args)
 | 
			
		||||
        client_mock.action_plan.delete.assert_called_once_with(
 | 
			
		||||
            'a5199d0e-0702-4613-9234-5ae2af8dafea')
 | 
			
		||||
 | 
			
		||||
    def test_do_action_plan_delete_not_uuid(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
        setattr(args, 'action-plan', ['not_uuid'])
 | 
			
		||||
 | 
			
		||||
        self.assertRaises(ValidationError, ap_shell.do_action_plan_delete,
 | 
			
		||||
                          client_mock, args)
 | 
			
		||||
 | 
			
		||||
    def test_do_action_plan_delete_multiple(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
        setattr(args, 'action-plan',
 | 
			
		||||
                ["a5199d0e-0702-4613-9234-5ae2af8dafea",
 | 
			
		||||
                 "a5199d0e-0702-4613-9234-5ae2af8dafeb"])
 | 
			
		||||
 | 
			
		||||
        ap_shell.do_action_plan_delete(client_mock, args)
 | 
			
		||||
        client_mock.action_plan.delete.assert_has_calls(
 | 
			
		||||
            [mock.call('a5199d0e-0702-4613-9234-5ae2af8dafea'),
 | 
			
		||||
             mock.call('a5199d0e-0702-4613-9234-5ae2af8dafeb')])
 | 
			
		||||
 | 
			
		||||
    def test_do_action_plan_delete_multiple_not_uuid(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
        setattr(args, 'action-plan',
 | 
			
		||||
                ["a5199d0e-0702-4613-9234-5ae2af8dafea",
 | 
			
		||||
                 "not_uuid",
 | 
			
		||||
                 "a5199d0e-0702-4613-9234-5ae2af8dafeb"])
 | 
			
		||||
 | 
			
		||||
        self.assertRaises(ValidationError, ap_shell.do_action_plan_delete,
 | 
			
		||||
                          client_mock, args)
 | 
			
		||||
        client_mock.action_plan.delete.assert_has_calls(
 | 
			
		||||
            [mock.call('a5199d0e-0702-4613-9234-5ae2af8dafea')])
 | 
			
		||||
 | 
			
		||||
    def test_do_action_plan_update(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
 | 
			
		||||
        setattr(args, 'action-plan', "a5199d0e-0702-4613-9234-5ae2af8dafea")
 | 
			
		||||
        args.op = 'add'
 | 
			
		||||
        args.attributes = [['arg1=val1', 'arg2=val2']]
 | 
			
		||||
 | 
			
		||||
        ap_shell.do_action_plan_update(client_mock, args)
 | 
			
		||||
        patch = commonutils.args_array_to_patch(
 | 
			
		||||
            args.op,
 | 
			
		||||
            args.attributes[0])
 | 
			
		||||
        client_mock.action_plan.update.assert_called_once_with(
 | 
			
		||||
            'a5199d0e-0702-4613-9234-5ae2af8dafea', patch)
 | 
			
		||||
 | 
			
		||||
    def test_do_action_plan_update_not_uuid(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
 | 
			
		||||
        setattr(args, 'action-plan', "not_uuid")
 | 
			
		||||
        args.op = 'add'
 | 
			
		||||
        args.attributes = [['arg1=val1', 'arg2=val2']]
 | 
			
		||||
 | 
			
		||||
        self.assertRaises(ValidationError, ap_shell.do_action_plan_update,
 | 
			
		||||
                          client_mock, args)
 | 
			
		||||
 | 
			
		||||
    def test_do_action_plan_start(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
 | 
			
		||||
        action_plan = 'a5199d0e-0702-4613-9234-5ae2af8dafea'
 | 
			
		||||
        setattr(args, 'action-plan', action_plan)
 | 
			
		||||
 | 
			
		||||
        ap_shell.do_action_plan_start(client_mock, args)
 | 
			
		||||
        patch = commonutils.args_array_to_patch(
 | 
			
		||||
            'replace', ['state=STARTING'])
 | 
			
		||||
        client_mock.action_plan.update.assert_called_once_with(
 | 
			
		||||
            'a5199d0e-0702-4613-9234-5ae2af8dafea', patch)
 | 
			
		||||
 | 
			
		||||
    def test_do_action_plan_start_not_uuid(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
 | 
			
		||||
        action_plan = 'not_uuid'
 | 
			
		||||
        setattr(args, 'action-plan', action_plan)
 | 
			
		||||
 | 
			
		||||
        self.assertRaises(ValidationError, ap_shell.do_action_plan_start,
 | 
			
		||||
                          client_mock, args)
 | 
			
		||||
							
								
								
									
										132
									
								
								watcherclient/tests/v1/test_action_shell.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								watcherclient/tests/v1/test_action_shell.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,132 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
# Copyright 2013 IBM Corp
 | 
			
		||||
#
 | 
			
		||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
			
		||||
#   not use this file except in compliance with the License. You may obtain
 | 
			
		||||
#   a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#       http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
#   Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
#   distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
#   WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
#   License for the specific language governing permissions and limitations
 | 
			
		||||
#   under the License.
 | 
			
		||||
 | 
			
		||||
import mock
 | 
			
		||||
 | 
			
		||||
from watcherclient.common import utils as commonutils
 | 
			
		||||
from watcherclient.openstack.common.apiclient.exceptions import ValidationError
 | 
			
		||||
from watcherclient.openstack.common import cliutils
 | 
			
		||||
from watcherclient.tests import utils
 | 
			
		||||
import watcherclient.v1.action_shell as a_shell
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ActionShellTest(utils.BaseTestCase):
 | 
			
		||||
    def test_do_action_show(self):
 | 
			
		||||
        actual = {}
 | 
			
		||||
        fake_print_dict = lambda data, *args, **kwargs: actual.update(data)
 | 
			
		||||
        with mock.patch.object(cliutils, 'print_dict', fake_print_dict):
 | 
			
		||||
            action = object()
 | 
			
		||||
            a_shell._print_action_show(action)
 | 
			
		||||
        exp = ['action_type',
 | 
			
		||||
               'alarm',
 | 
			
		||||
               'applies_to',
 | 
			
		||||
               'created_at',
 | 
			
		||||
               'deleted_at',
 | 
			
		||||
               'description',
 | 
			
		||||
               'dst',
 | 
			
		||||
               'next_uuid',
 | 
			
		||||
               'parameter',
 | 
			
		||||
               'src',
 | 
			
		||||
               'state',
 | 
			
		||||
               'action_plan_uuid',
 | 
			
		||||
               'updated_at',
 | 
			
		||||
               'uuid']
 | 
			
		||||
        act = actual.keys()
 | 
			
		||||
        self.assertEqual(sorted(exp), sorted(act))
 | 
			
		||||
 | 
			
		||||
    def test_do_action_show_by_uuid(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
        args.action = 'a5199d0e-0702-4613-9234-5ae2af8dafea'
 | 
			
		||||
 | 
			
		||||
        a_shell.do_action_show(client_mock, args)
 | 
			
		||||
        client_mock.action.get.assert_called_once_with(
 | 
			
		||||
            'a5199d0e-0702-4613-9234-5ae2af8dafea'
 | 
			
		||||
        )
 | 
			
		||||
        # assert get_by_name() wasn't called
 | 
			
		||||
        self.assertFalse(client_mock.action.get_by_name.called)
 | 
			
		||||
 | 
			
		||||
    def test_do_action_show_by_not_uuid(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
        args.action = 'not_uuid'
 | 
			
		||||
 | 
			
		||||
        self.assertRaises(ValidationError, a_shell.do_action_show,
 | 
			
		||||
                          client_mock, args)
 | 
			
		||||
 | 
			
		||||
    def test_do_action_delete(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
        args.action = ['a5199d0e-0702-4613-9234-5ae2af8dafea']
 | 
			
		||||
 | 
			
		||||
        a_shell.do_action_delete(client_mock, args)
 | 
			
		||||
        client_mock.action.delete.assert_called_once_with(
 | 
			
		||||
            'a5199d0e-0702-4613-9234-5ae2af8dafea')
 | 
			
		||||
 | 
			
		||||
    def test_do_action_delete_not_uuid(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
        args.action = ['not_uuid']
 | 
			
		||||
 | 
			
		||||
        self.assertRaises(ValidationError, a_shell.do_action_delete,
 | 
			
		||||
                          client_mock, args)
 | 
			
		||||
 | 
			
		||||
    def test_do_action_delete_multiple(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
        args.action = ['a5199d0e-0702-4613-9234-5ae2af8dafea',
 | 
			
		||||
                       'a5199d0e-0702-4613-9234-5ae2af8dafeb']
 | 
			
		||||
 | 
			
		||||
        a_shell.do_action_delete(client_mock, args)
 | 
			
		||||
        client_mock.action.delete.assert_has_calls(
 | 
			
		||||
            [mock.call('a5199d0e-0702-4613-9234-5ae2af8dafea'),
 | 
			
		||||
             mock.call('a5199d0e-0702-4613-9234-5ae2af8dafeb')])
 | 
			
		||||
 | 
			
		||||
    def test_do_action_delete_multiple_not_uuid(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
        args.action = ['a5199d0e-0702-4613-9234-5ae2af8dafea',
 | 
			
		||||
                       'not_uuid'
 | 
			
		||||
                       'a5199d0e-0702-4613-9234-5ae2af8dafeb']
 | 
			
		||||
 | 
			
		||||
        self.assertRaises(ValidationError, a_shell.do_action_delete,
 | 
			
		||||
                          client_mock, args)
 | 
			
		||||
        client_mock.action.delete.assert_has_calls(
 | 
			
		||||
            [mock.call('a5199d0e-0702-4613-9234-5ae2af8dafea')])
 | 
			
		||||
 | 
			
		||||
    def test_do_action_update(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
        args.action = 'a5199d0e-0702-4613-9234-5ae2af8dafea'
 | 
			
		||||
        args.op = 'add'
 | 
			
		||||
        args.attributes = [['arg1=val1', 'arg2=val2']]
 | 
			
		||||
 | 
			
		||||
        a_shell.do_action_update(client_mock, args)
 | 
			
		||||
        patch = commonutils.args_array_to_patch(
 | 
			
		||||
            args.op,
 | 
			
		||||
            args.attributes[0])
 | 
			
		||||
        client_mock.action.update.assert_called_once_with(
 | 
			
		||||
            'a5199d0e-0702-4613-9234-5ae2af8dafea', patch)
 | 
			
		||||
 | 
			
		||||
    def test_do_action_update_not_uuid(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
        args.action = 'not_uuid'
 | 
			
		||||
        args.op = 'add'
 | 
			
		||||
        args.attributes = [['arg1=val1', 'arg2=val2']]
 | 
			
		||||
 | 
			
		||||
        self.assertRaises(ValidationError, a_shell.do_action_update,
 | 
			
		||||
                          client_mock, args)
 | 
			
		||||
							
								
								
									
										227
									
								
								watcherclient/tests/v1/test_audit.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										227
									
								
								watcherclient/tests/v1/test_audit.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,227 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
# Copyright 2013 Red Hat, Inc.
 | 
			
		||||
# All Rights Reserved.
 | 
			
		||||
#
 | 
			
		||||
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
			
		||||
#    not use this file except in compliance with the License. You may obtain
 | 
			
		||||
#    a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#         http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
#    Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
#    License for the specific language governing permissions and limitations
 | 
			
		||||
#    under the License.
 | 
			
		||||
 | 
			
		||||
import copy
 | 
			
		||||
 | 
			
		||||
import testtools
 | 
			
		||||
from testtools.matchers import HasLength
 | 
			
		||||
 | 
			
		||||
from watcherclient.tests import utils
 | 
			
		||||
import watcherclient.v1.audit
 | 
			
		||||
 | 
			
		||||
AUDIT1 = {
 | 
			
		||||
    'id': 1,
 | 
			
		||||
    'uuid': 'f8e47706-efcf-49a4-a5c4-af604eb492f2',
 | 
			
		||||
    'deadline': None,
 | 
			
		||||
    'type': 'ONE_SHOT',
 | 
			
		||||
    'audit_template_uuid': '770ef053-ecb3-48b0-85b5-d55a2dbc6588'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AUDIT2 = {
 | 
			
		||||
    'id': 2,
 | 
			
		||||
    'uuid': 'a5199d0e-0702-4613-9234-5ae2af8dafea',
 | 
			
		||||
    'deadline': None,
 | 
			
		||||
    'type': 'ONE_SHOT',
 | 
			
		||||
    'audit_template_uuid': '770ef053-ecb3-48b0-85b5-d55a2dbc6588'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CREATE_AUDIT = copy.deepcopy(AUDIT1)
 | 
			
		||||
del CREATE_AUDIT['id']
 | 
			
		||||
del CREATE_AUDIT['uuid']
 | 
			
		||||
 | 
			
		||||
UPDATED_AUDIT1 = copy.deepcopy(AUDIT1)
 | 
			
		||||
NEW_STATE = 'SUCCESS'
 | 
			
		||||
UPDATED_AUDIT1['state'] = NEW_STATE
 | 
			
		||||
 | 
			
		||||
fake_responses = {
 | 
			
		||||
    '/v1/audits':
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"audits": [AUDIT1]},
 | 
			
		||||
        ),
 | 
			
		||||
        'POST': (
 | 
			
		||||
            {},
 | 
			
		||||
            CREATE_AUDIT,
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/v1/audits/detail':
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"audits": [AUDIT1]},
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/v1/audits/%s' % AUDIT1['uuid']:
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            AUDIT1,
 | 
			
		||||
        ),
 | 
			
		||||
        'DELETE': (
 | 
			
		||||
            {},
 | 
			
		||||
            None,
 | 
			
		||||
        ),
 | 
			
		||||
        'PATCH': (
 | 
			
		||||
            {},
 | 
			
		||||
            UPDATED_AUDIT1,
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/v1/audits/detail?uuid=%s' % AUDIT1['uuid']:
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"audits": [AUDIT1]},
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fake_responses_pagination = {
 | 
			
		||||
    '/v1/audits':
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"audits": [AUDIT1],
 | 
			
		||||
             "next": "http://127.0.0.1:6385/v1/audits/?limit=1"}
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/v1/audits/?limit=1':
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"audits": [AUDIT2]}
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fake_responses_sorting = {
 | 
			
		||||
    '/v1/audits/?sort_key=updated_at':
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"audits": [AUDIT2, AUDIT1]}
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/v1/audits/?sort_dir=desc':
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"audits": [AUDIT2, AUDIT1]}
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fake_responses_filters = {
 | 
			
		||||
    '/v1/audits/?audit_template=%s' % AUDIT2['audit_template_uuid']:
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"audits": [AUDIT2]}
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AuditManagerTest(testtools.TestCase):
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super(AuditManagerTest, self).setUp()
 | 
			
		||||
        self.api = utils.FakeAPI(fake_responses)
 | 
			
		||||
        self.mgr = watcherclient.v1.audit.AuditManager(self.api)
 | 
			
		||||
 | 
			
		||||
    def test_audits_list(self):
 | 
			
		||||
        audits = self.mgr.list()
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET', '/v1/audits', {}, None),
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertEqual(1, len(audits))
 | 
			
		||||
 | 
			
		||||
    def test_audits_list_detail(self):
 | 
			
		||||
        audits = self.mgr.list(detail=True)
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET', '/v1/audits/detail', {}, None),
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertEqual(1, len(audits))
 | 
			
		||||
 | 
			
		||||
    def test_audits_list_limit(self):
 | 
			
		||||
        self.api = utils.FakeAPI(fake_responses_pagination)
 | 
			
		||||
        self.mgr = watcherclient.v1.audit.AuditManager(self.api)
 | 
			
		||||
        audits = self.mgr.list(limit=1)
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET', '/v1/audits/?limit=1', {}, None),
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertThat(audits, HasLength(1))
 | 
			
		||||
 | 
			
		||||
    def test_audits_list_pagination_no_limit(self):
 | 
			
		||||
        self.api = utils.FakeAPI(fake_responses_pagination)
 | 
			
		||||
        self.mgr = watcherclient.v1.audit.AuditManager(self.api)
 | 
			
		||||
        audits = self.mgr.list(limit=0)
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET', '/v1/audits', {}, None),
 | 
			
		||||
            ('GET', '/v1/audits/?limit=1', {}, None)
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertThat(audits, HasLength(2))
 | 
			
		||||
 | 
			
		||||
    def test_audits_list_sort_key(self):
 | 
			
		||||
        self.api = utils.FakeAPI(fake_responses_sorting)
 | 
			
		||||
        self.mgr = watcherclient.v1.audit.AuditManager(self.api)
 | 
			
		||||
        audits = self.mgr.list(sort_key='updated_at')
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET', '/v1/audits/?sort_key=updated_at', {}, None)
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertEqual(2, len(audits))
 | 
			
		||||
 | 
			
		||||
    def test_audits_list_sort_dir(self):
 | 
			
		||||
        self.api = utils.FakeAPI(fake_responses_sorting)
 | 
			
		||||
        self.mgr = watcherclient.v1.audit.AuditManager(self.api)
 | 
			
		||||
        audits = self.mgr.list(sort_dir='desc')
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET', '/v1/audits/?sort_dir=desc', {}, None)
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertEqual(2, len(audits))
 | 
			
		||||
 | 
			
		||||
    def test_audits_list_filter_by_audit_template(self):
 | 
			
		||||
        self.api = utils.FakeAPI(fake_responses_filters)
 | 
			
		||||
        self.mgr = watcherclient.v1.audit.AuditManager(self.api)
 | 
			
		||||
        self.mgr.list(audit_template=AUDIT2['audit_template_uuid'])
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET', '/v1/audits/?audit_template=%s' %
 | 
			
		||||
             AUDIT2['audit_template_uuid'], {}, None),
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
 | 
			
		||||
    def test_audits_show(self):
 | 
			
		||||
        audit = self.mgr.get(AUDIT1['uuid'])
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET', '/v1/audits/%s' % AUDIT1['uuid'], {}, None),
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertEqual(AUDIT1['uuid'], audit.uuid)
 | 
			
		||||
 | 
			
		||||
    def test_create(self):
 | 
			
		||||
        audit = self.mgr.create(**CREATE_AUDIT)
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('POST', '/v1/audits', {}, CREATE_AUDIT),
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertTrue(audit)
 | 
			
		||||
							
								
								
									
										144
									
								
								watcherclient/tests/v1/test_audit_shell.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								watcherclient/tests/v1/test_audit_shell.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,144 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
# Copyright 2013 IBM Corp
 | 
			
		||||
#
 | 
			
		||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
			
		||||
# not use this file except in compliance with the License. You may obtain
 | 
			
		||||
#   a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#       http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
#   Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
#   distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
#   WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
#   License for the specific language governing permissions and limitations
 | 
			
		||||
#   under the License.
 | 
			
		||||
 | 
			
		||||
import mock
 | 
			
		||||
 | 
			
		||||
from watcherclient.common import utils as commonutils
 | 
			
		||||
from watcherclient.openstack.common.apiclient.exceptions import ValidationError
 | 
			
		||||
from watcherclient.openstack.common import cliutils
 | 
			
		||||
from watcherclient.tests import utils
 | 
			
		||||
import watcherclient.v1.audit_shell as a_shell
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AuditShellTest(utils.BaseTestCase):
 | 
			
		||||
    def test_do_audit_show(self):
 | 
			
		||||
        actual = {}
 | 
			
		||||
        fake_print_dict = lambda data, *args, **kwargs: actual.update(data)
 | 
			
		||||
        with mock.patch.object(cliutils, 'print_dict', fake_print_dict):
 | 
			
		||||
            audit = object()
 | 
			
		||||
            a_shell._print_audit_show(audit)
 | 
			
		||||
        exp = ['created_at', 'audit_template_uuid', 'updated_at', 'uuid',
 | 
			
		||||
               'deleted_at', 'state', 'type', 'deadline']
 | 
			
		||||
        act = actual.keys()
 | 
			
		||||
        self.assertEqual(sorted(exp), sorted(act))
 | 
			
		||||
 | 
			
		||||
    def test_do_audit_show_by_uuid(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
        args.audit = 'a5199d0e-0702-4613-9234-5ae2af8dafea'
 | 
			
		||||
 | 
			
		||||
        a_shell.do_audit_show(client_mock, args)
 | 
			
		||||
        client_mock.audit.get.assert_called_once_with(
 | 
			
		||||
            'a5199d0e-0702-4613-9234-5ae2af8dafea'
 | 
			
		||||
        )
 | 
			
		||||
        # assert get_by_name() wasn't called
 | 
			
		||||
        self.assertFalse(client_mock.audit.get_by_name.called)
 | 
			
		||||
 | 
			
		||||
    def test_do_audit_show_by_not_uuid(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
        args.audit = 'not_uuid'
 | 
			
		||||
 | 
			
		||||
        self.assertRaises(ValidationError, a_shell.do_audit_show,
 | 
			
		||||
                          client_mock, args)
 | 
			
		||||
 | 
			
		||||
    def test_do_audit_delete(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
        args.audit = ['a5199d0e-0702-4613-9234-5ae2af8dafea']
 | 
			
		||||
 | 
			
		||||
        a_shell.do_audit_delete(client_mock, args)
 | 
			
		||||
        client_mock.audit.delete.assert_called_once_with(
 | 
			
		||||
            'a5199d0e-0702-4613-9234-5ae2af8dafea')
 | 
			
		||||
 | 
			
		||||
    def test_do_audit_delete_with_not_uuid(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
        args.audit = ['not_uuid']
 | 
			
		||||
 | 
			
		||||
        self.assertRaises(ValidationError, a_shell.do_audit_delete,
 | 
			
		||||
                          client_mock, args)
 | 
			
		||||
 | 
			
		||||
    def test_do_audit_delete_multiple(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
        args.audit = ['a5199d0e-0702-4613-9234-5ae2af8dafea',
 | 
			
		||||
                      'a5199d0e-0702-4613-9234-5ae2af8dafeb']
 | 
			
		||||
 | 
			
		||||
        a_shell.do_audit_delete(client_mock, args)
 | 
			
		||||
        client_mock.audit.delete.assert_has_calls(
 | 
			
		||||
            [mock.call('a5199d0e-0702-4613-9234-5ae2af8dafea'),
 | 
			
		||||
             mock.call('a5199d0e-0702-4613-9234-5ae2af8dafeb')])
 | 
			
		||||
 | 
			
		||||
    def test_do_audit_delete_multiple_with_not_uuid(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
        args.audit = ['a5199d0e-0702-4613-9234-5ae2af8dafea',
 | 
			
		||||
                      'not_uuid',
 | 
			
		||||
                      'a5199d0e-0702-4613-9234-5ae2af8dafeb']
 | 
			
		||||
 | 
			
		||||
        self.assertRaises(ValidationError, a_shell.do_audit_delete,
 | 
			
		||||
                          client_mock, args)
 | 
			
		||||
        client_mock.audit.delete.assert_has_calls(
 | 
			
		||||
            [mock.call('a5199d0e-0702-4613-9234-5ae2af8dafea')])
 | 
			
		||||
 | 
			
		||||
    def test_do_audit_update(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
        args.audit = 'a5199d0e-0702-4613-9234-5ae2af8dafea'
 | 
			
		||||
        args.op = 'add'
 | 
			
		||||
        args.attributes = [['arg1=val1', 'arg2=val2']]
 | 
			
		||||
 | 
			
		||||
        a_shell.do_audit_update(client_mock, args)
 | 
			
		||||
        patch = commonutils.args_array_to_patch(
 | 
			
		||||
            args.op,
 | 
			
		||||
            args.attributes[0])
 | 
			
		||||
        client_mock.audit.update.assert_called_once_with(
 | 
			
		||||
            'a5199d0e-0702-4613-9234-5ae2af8dafea', patch)
 | 
			
		||||
 | 
			
		||||
    def test_do_audit_update_with_not_uuid(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
        args.audit = ['not_uuid']
 | 
			
		||||
        args.op = 'add'
 | 
			
		||||
        args.attributes = [['arg1=val1', 'arg2=val2']]
 | 
			
		||||
 | 
			
		||||
        self.assertRaises(ValidationError, a_shell.do_audit_update,
 | 
			
		||||
                          client_mock, args)
 | 
			
		||||
 | 
			
		||||
    def test_do_audit_create(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
 | 
			
		||||
        a_shell.do_audit_create(client_mock, args)
 | 
			
		||||
        client_mock.audit.create.assert_called_once_with()
 | 
			
		||||
 | 
			
		||||
    def test_do_audit_create_with_deadline(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
        args.deadline = 'deadline'
 | 
			
		||||
 | 
			
		||||
        a_shell.do_audit_create(client_mock, args)
 | 
			
		||||
        client_mock.audit.create.assert_called_once_with(
 | 
			
		||||
            deadline='deadline')
 | 
			
		||||
 | 
			
		||||
    def test_do_audit_create_with_type(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
        args.type = 'type'
 | 
			
		||||
 | 
			
		||||
        a_shell.do_audit_create(client_mock, args)
 | 
			
		||||
        client_mock.audit.create.assert_called_once_with(type='type')
 | 
			
		||||
							
								
								
									
										336
									
								
								watcherclient/tests/v1/test_audit_template.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										336
									
								
								watcherclient/tests/v1/test_audit_template.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,336 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
# Copyright 2013 Red Hat, Inc.
 | 
			
		||||
# All Rights Reserved.
 | 
			
		||||
#
 | 
			
		||||
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
			
		||||
#    not use this file except in compliance with the License. You may obtain
 | 
			
		||||
#    a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#         http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
#    Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
#    License for the specific language governing permissions and limitations
 | 
			
		||||
#    under the License.
 | 
			
		||||
 | 
			
		||||
import copy
 | 
			
		||||
 | 
			
		||||
from six.moves.urllib import parse as urlparse
 | 
			
		||||
import testtools
 | 
			
		||||
from testtools.matchers import HasLength
 | 
			
		||||
 | 
			
		||||
from watcherclient.tests import utils
 | 
			
		||||
import watcherclient.v1.audit_template
 | 
			
		||||
 | 
			
		||||
AUDIT_TMPL1 = {
 | 
			
		||||
    'id': 1,
 | 
			
		||||
    'uuid': 'f8e47706-efcf-49a4-a5c4-af604eb492f2',
 | 
			
		||||
    'name': 'Audit Template 1',
 | 
			
		||||
    'description': 'Audit Template 1 description',
 | 
			
		||||
    'host_aggregate': 5,
 | 
			
		||||
    'extra': {'automatic': False},
 | 
			
		||||
    'goal': 'MINIMIZE_LICENSING_COST'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AUDIT_TMPL2 = {
 | 
			
		||||
    'id': 2,
 | 
			
		||||
    'uuid': 'a5199d0e-0702-4613-9234-5ae2af8dafea',
 | 
			
		||||
    'name': 'Audit Template 2',
 | 
			
		||||
    'description': 'Audit Template 2 description',
 | 
			
		||||
    'host_aggregate': 8,
 | 
			
		||||
    'extra': {'automatic': True},
 | 
			
		||||
    'goal': 'SERVERS_CONSOLIDATION'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AUDIT_TMPL3 = {
 | 
			
		||||
    'id': 3,
 | 
			
		||||
    'uuid': '770ef053-ecb3-48b0-85b5-d55a2dbc6588',
 | 
			
		||||
    'name': 'Audit Template 3',
 | 
			
		||||
    'description': 'Audit Template 3 description',
 | 
			
		||||
    'host_aggregate': 7,
 | 
			
		||||
    'extra': {'automatic': True},
 | 
			
		||||
    'goal': 'MINIMIZE_LICENSING_COST'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CREATE_AUDIT_TEMPLATE = copy.deepcopy(AUDIT_TMPL1)
 | 
			
		||||
del CREATE_AUDIT_TEMPLATE['id']
 | 
			
		||||
del CREATE_AUDIT_TEMPLATE['uuid']
 | 
			
		||||
 | 
			
		||||
UPDATED_AUDIT_TMPL1 = copy.deepcopy(AUDIT_TMPL1)
 | 
			
		||||
NEW_NAME = 'Audit Template_1 new name'
 | 
			
		||||
UPDATED_AUDIT_TMPL1['name'] = NEW_NAME
 | 
			
		||||
 | 
			
		||||
fake_responses = {
 | 
			
		||||
    '/v1/audit_templates':
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"audit_templates": [AUDIT_TMPL1]},
 | 
			
		||||
        ),
 | 
			
		||||
        'POST': (
 | 
			
		||||
            {},
 | 
			
		||||
            CREATE_AUDIT_TEMPLATE,
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/v1/audit_templates/detail':
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"audit_templates": [AUDIT_TMPL1]},
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/v1/audit_templates/%s' % AUDIT_TMPL1['uuid']:
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            AUDIT_TMPL1,
 | 
			
		||||
        ),
 | 
			
		||||
        'DELETE': (
 | 
			
		||||
            {},
 | 
			
		||||
            None,
 | 
			
		||||
        ),
 | 
			
		||||
        'PATCH': (
 | 
			
		||||
            {},
 | 
			
		||||
            UPDATED_AUDIT_TMPL1,
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    urlparse.quote('/v1/audit_templates/%s' % AUDIT_TMPL1['name']):
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            AUDIT_TMPL1,
 | 
			
		||||
        ),
 | 
			
		||||
        'DELETE': (
 | 
			
		||||
            {},
 | 
			
		||||
            None,
 | 
			
		||||
        ),
 | 
			
		||||
        'PATCH': (
 | 
			
		||||
            {},
 | 
			
		||||
            UPDATED_AUDIT_TMPL1,
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/v1/audit_templates/detail?name=%s' % AUDIT_TMPL1['name']:
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"audit_templates": [AUDIT_TMPL1]},
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/v1/audit_templates/?name=%s' % AUDIT_TMPL1['name']:
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"audit_templates": [AUDIT_TMPL1]},
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/v1/audit_templates/detail?goal=%s' % AUDIT_TMPL1['goal']:
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"audit_templates": [AUDIT_TMPL1, AUDIT_TMPL3]},
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/v1/audit_templates/?goal=%s' % AUDIT_TMPL1['goal']:
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"audit_templates": [AUDIT_TMPL1, AUDIT_TMPL3]},
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fake_responses_pagination = {
 | 
			
		||||
    '/v1/audit_templates':
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"audit_templates": [AUDIT_TMPL1],
 | 
			
		||||
             "next": "http://127.0.0.1:6385/v1/audit_templates/?limit=1"}
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/v1/audit_templates/?limit=1':
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"audit_templates": [AUDIT_TMPL2]}
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fake_responses_sorting = {
 | 
			
		||||
    '/v1/audit_templates/?sort_key=updated_at':
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"audit_templates": [AUDIT_TMPL3, AUDIT_TMPL2, AUDIT_TMPL1]}
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/v1/audit_templates/?sort_dir=desc':
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"audit_templates": [AUDIT_TMPL3, AUDIT_TMPL2, AUDIT_TMPL1]}
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AuditTemplateManagerTest(testtools.TestCase):
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super(AuditTemplateManagerTest, self).setUp()
 | 
			
		||||
        self.api = utils.FakeAPI(fake_responses)
 | 
			
		||||
        self.mgr = watcherclient.v1.audit_template.AuditTemplateManager(
 | 
			
		||||
            self.api)
 | 
			
		||||
 | 
			
		||||
    def test_audit_templates_list(self):
 | 
			
		||||
        audit_templates = self.mgr.list()
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET', '/v1/audit_templates', {}, None),
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertEqual(1, len(audit_templates))
 | 
			
		||||
 | 
			
		||||
    def test_audit_templates_list_by_name(self):
 | 
			
		||||
        audit_templates = self.mgr.list(name=AUDIT_TMPL1['name'])
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET', '/v1/audit_templates/?name=%s' % AUDIT_TMPL1['name'],
 | 
			
		||||
             {}, None),
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertEqual(1, len(audit_templates))
 | 
			
		||||
 | 
			
		||||
    def test_audit_templates_list_detail(self):
 | 
			
		||||
        audit_templates = self.mgr.list(detail=True)
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET', '/v1/audit_templates/detail', {}, None),
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertEqual(1, len(audit_templates))
 | 
			
		||||
 | 
			
		||||
    def test_audit_templates_list_by_name_detail(self):
 | 
			
		||||
        audit_templates = self.mgr.list(name=AUDIT_TMPL1['name'], detail=True)
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET',
 | 
			
		||||
             '/v1/audit_templates/detail?name=%s' % AUDIT_TMPL1['name'],
 | 
			
		||||
             {},
 | 
			
		||||
             None),
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertEqual(1, len(audit_templates))
 | 
			
		||||
 | 
			
		||||
    def test_audit_templates_list_limit(self):
 | 
			
		||||
        self.api = utils.FakeAPI(fake_responses_pagination)
 | 
			
		||||
        self.mgr = watcherclient.v1.audit_template.AuditTemplateManager(
 | 
			
		||||
            self.api)
 | 
			
		||||
        audit_templates = self.mgr.list(limit=1)
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET', '/v1/audit_templates/?limit=1', {}, None),
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertThat(audit_templates, HasLength(1))
 | 
			
		||||
 | 
			
		||||
    def test_audit_templates_list_pagination_no_limit(self):
 | 
			
		||||
        self.api = utils.FakeAPI(fake_responses_pagination)
 | 
			
		||||
        self.mgr = watcherclient.v1.audit_template.AuditTemplateManager(
 | 
			
		||||
            self.api)
 | 
			
		||||
        audit_templates = self.mgr.list(limit=0)
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET', '/v1/audit_templates', {}, None),
 | 
			
		||||
            ('GET', '/v1/audit_templates/?limit=1', {}, None)
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertThat(audit_templates, HasLength(2))
 | 
			
		||||
 | 
			
		||||
    def test_audit_templates_list_sort_key(self):
 | 
			
		||||
        self.api = utils.FakeAPI(fake_responses_sorting)
 | 
			
		||||
        self.mgr = watcherclient.v1.audit_template.AuditTemplateManager(
 | 
			
		||||
            self.api)
 | 
			
		||||
        audit_templates = self.mgr.list(sort_key='updated_at')
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET', '/v1/audit_templates/?sort_key=updated_at', {}, None)
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertEqual(3, len(audit_templates))
 | 
			
		||||
 | 
			
		||||
    def test_audit_templates_list_sort_dir(self):
 | 
			
		||||
        self.api = utils.FakeAPI(fake_responses_sorting)
 | 
			
		||||
        self.mgr = watcherclient.v1.audit_template.AuditTemplateManager(
 | 
			
		||||
            self.api)
 | 
			
		||||
        audit_templates = self.mgr.list(sort_dir='desc')
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET', '/v1/audit_templates/?sort_dir=desc', {}, None)
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertEqual(3, len(audit_templates))
 | 
			
		||||
 | 
			
		||||
    def test_audit_templates_show(self):
 | 
			
		||||
        audit_template = self.mgr.get(AUDIT_TMPL1['uuid'])
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET', '/v1/audit_templates/%s' % AUDIT_TMPL1['uuid'], {}, None),
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertEqual(AUDIT_TMPL1['uuid'], audit_template.uuid)
 | 
			
		||||
        self.assertEqual(AUDIT_TMPL1['name'], audit_template.name)
 | 
			
		||||
        self.assertEqual(AUDIT_TMPL1['description'],
 | 
			
		||||
                         audit_template.description)
 | 
			
		||||
        self.assertEqual(AUDIT_TMPL1['host_aggregate'],
 | 
			
		||||
                         audit_template.host_aggregate)
 | 
			
		||||
        self.assertEqual(AUDIT_TMPL1['goal'], audit_template.goal)
 | 
			
		||||
        self.assertEqual(AUDIT_TMPL1['extra'],
 | 
			
		||||
                         audit_template.extra)
 | 
			
		||||
 | 
			
		||||
    def test_audit_templates_show_by_name(self):
 | 
			
		||||
        audit_template = self.mgr.get(urlparse.quote(AUDIT_TMPL1['name']))
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET',
 | 
			
		||||
             urlparse.quote('/v1/audit_templates/%s' % AUDIT_TMPL1['name']),
 | 
			
		||||
             {}, None),
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertEqual(AUDIT_TMPL1['uuid'], audit_template.uuid)
 | 
			
		||||
        self.assertEqual(AUDIT_TMPL1['name'], audit_template.name)
 | 
			
		||||
        self.assertEqual(AUDIT_TMPL1['description'],
 | 
			
		||||
                         audit_template.description)
 | 
			
		||||
        self.assertEqual(AUDIT_TMPL1['host_aggregate'],
 | 
			
		||||
                         audit_template.host_aggregate)
 | 
			
		||||
        self.assertEqual(AUDIT_TMPL1['goal'], audit_template.goal)
 | 
			
		||||
        self.assertEqual(AUDIT_TMPL1['extra'],
 | 
			
		||||
                         audit_template.extra)
 | 
			
		||||
 | 
			
		||||
    def test_create(self):
 | 
			
		||||
        audit_template = self.mgr.create(**CREATE_AUDIT_TEMPLATE)
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('POST', '/v1/audit_templates', {}, CREATE_AUDIT_TEMPLATE),
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertTrue(audit_template)
 | 
			
		||||
 | 
			
		||||
    def test_delete(self):
 | 
			
		||||
        audit_template = self.mgr.delete(audit_template_id=AUDIT_TMPL1['uuid'])
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('DELETE',
 | 
			
		||||
             '/v1/audit_templates/%s' % AUDIT_TMPL1['uuid'],
 | 
			
		||||
             {},
 | 
			
		||||
             None),
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertIsNone(audit_template)
 | 
			
		||||
 | 
			
		||||
    def test_update(self):
 | 
			
		||||
        patch = {'op': 'replace',
 | 
			
		||||
                 'value': NEW_NAME,
 | 
			
		||||
                 'path': '/name'}
 | 
			
		||||
        audit_template = self.mgr.update(audit_template_id=AUDIT_TMPL1['uuid'],
 | 
			
		||||
                                         patch=patch)
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('PATCH',
 | 
			
		||||
             '/v1/audit_templates/%s' % AUDIT_TMPL1['uuid'],
 | 
			
		||||
             {},
 | 
			
		||||
             patch),
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertEqual(NEW_NAME, audit_template.name)
 | 
			
		||||
							
								
								
									
										137
									
								
								watcherclient/tests/v1/test_audit_template_shell.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								watcherclient/tests/v1/test_audit_template_shell.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,137 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
# Copyright 2013 IBM Corp
 | 
			
		||||
#
 | 
			
		||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
			
		||||
#   not use this file except in compliance with the License. You may obtain
 | 
			
		||||
#   a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#       http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
#   Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
#   distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
#   WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
#   License for the specific language governing permissions and limitations
 | 
			
		||||
#   under the License.
 | 
			
		||||
 | 
			
		||||
import mock
 | 
			
		||||
 | 
			
		||||
from watcherclient.common import utils as commonutils
 | 
			
		||||
from watcherclient.openstack.common import cliutils
 | 
			
		||||
from watcherclient.tests import utils
 | 
			
		||||
import watcherclient.v1.audit_template_shell as at_shell
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AuditTemplateShellTest(utils.BaseTestCase):
 | 
			
		||||
    def test_do_audit_template_show(self):
 | 
			
		||||
        actual = {}
 | 
			
		||||
        fake_print_dict = lambda data, *args, **kwargs: actual.update(data)
 | 
			
		||||
        with mock.patch.object(cliutils, 'print_dict', fake_print_dict):
 | 
			
		||||
            audit_template = object()
 | 
			
		||||
            at_shell._print_audit_template_show(audit_template)
 | 
			
		||||
        exp = [
 | 
			
		||||
            'uuid', 'created_at', 'updated_at', 'deleted_at',
 | 
			
		||||
            'description', 'host_aggregate', 'name',
 | 
			
		||||
            'extra', 'goal']
 | 
			
		||||
        act = actual.keys()
 | 
			
		||||
        self.assertEqual(sorted(exp), sorted(act))
 | 
			
		||||
 | 
			
		||||
    def test_do_audit_template_show_by_uuid(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
        setattr(args, 'audit-template', 'a5199d0e-0702-4613-9234-5ae2af8dafea')
 | 
			
		||||
 | 
			
		||||
        at_shell.do_audit_template_show(client_mock, args)
 | 
			
		||||
        client_mock.audit_template.get.assert_called_once_with(
 | 
			
		||||
            'a5199d0e-0702-4613-9234-5ae2af8dafea'
 | 
			
		||||
        )
 | 
			
		||||
        # assert get_by_name() wasn't called
 | 
			
		||||
        self.assertFalse(client_mock.audit_template.get_by_name.called)
 | 
			
		||||
 | 
			
		||||
    def test_do_audit_template_show_by_name(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
        setattr(args, 'audit-template', "a5199d0e-0702-4613-9234-5ae2af8dafea")
 | 
			
		||||
 | 
			
		||||
        at_shell.do_audit_template_show(client_mock, args)
 | 
			
		||||
        client_mock.audit_template.get.assert_called_once_with(
 | 
			
		||||
            'a5199d0e-0702-4613-9234-5ae2af8dafea')
 | 
			
		||||
 | 
			
		||||
    def test_do_audit_template_delete(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
        setattr(args, 'audit-template',
 | 
			
		||||
                ['a5199d0e-0702-4613-9234-5ae2af8dafea'])
 | 
			
		||||
 | 
			
		||||
        at_shell.do_audit_template_delete(client_mock, args)
 | 
			
		||||
        client_mock.audit_template.delete.assert_called_once_with(
 | 
			
		||||
            'a5199d0e-0702-4613-9234-5ae2af8dafea')
 | 
			
		||||
 | 
			
		||||
    def test_do_audit_template_delete_multiple(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
        setattr(args, 'audit-template',
 | 
			
		||||
                ['a5199d0e-0702-4613-9234-5ae2af8dafea',
 | 
			
		||||
                 'a5199d0e-0702-4613-9234-5ae2af8dafeb'])
 | 
			
		||||
 | 
			
		||||
        at_shell.do_audit_template_delete(client_mock, args)
 | 
			
		||||
        client_mock.audit_template.delete.assert_has_calls(
 | 
			
		||||
            [mock.call('a5199d0e-0702-4613-9234-5ae2af8dafea'),
 | 
			
		||||
             mock.call('a5199d0e-0702-4613-9234-5ae2af8dafeb')])
 | 
			
		||||
 | 
			
		||||
    def test_do_audit_template_update(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
        setattr(args, 'audit-template', "a5199d0e-0702-4613-9234-5ae2af8dafea")
 | 
			
		||||
        args.op = 'add'
 | 
			
		||||
        args.attributes = [['arg1=val1', 'arg2=val2']]
 | 
			
		||||
 | 
			
		||||
        at_shell.do_audit_template_update(client_mock, args)
 | 
			
		||||
        patch = commonutils.args_array_to_patch(
 | 
			
		||||
            args.op,
 | 
			
		||||
            args.attributes[0])
 | 
			
		||||
        client_mock.audit_template.update.assert_called_once_with(
 | 
			
		||||
            'a5199d0e-0702-4613-9234-5ae2af8dafea', patch)
 | 
			
		||||
 | 
			
		||||
    def test_do_audit_template_create(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
 | 
			
		||||
        at_shell.do_audit_template_create(client_mock, args)
 | 
			
		||||
        client_mock.audit_template.create.assert_called_once_with()
 | 
			
		||||
 | 
			
		||||
    def test_do_audit_template_create_with_name(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
        args.name = 'my audit template'
 | 
			
		||||
 | 
			
		||||
        at_shell.do_audit_template_create(client_mock, args)
 | 
			
		||||
        client_mock.audit_template.create.assert_called_once_with(
 | 
			
		||||
            name='my audit template')
 | 
			
		||||
 | 
			
		||||
    def test_do_audit_template_create_with_description(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
        args.description = 'description'
 | 
			
		||||
 | 
			
		||||
        at_shell.do_audit_template_create(client_mock, args)
 | 
			
		||||
        client_mock.audit_template.create.assert_called_once_with(
 | 
			
		||||
            description='description')
 | 
			
		||||
 | 
			
		||||
    def test_do_audit_template_create_with_aggregate(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
        args.host_aggregate = 5
 | 
			
		||||
 | 
			
		||||
        at_shell.do_audit_template_create(client_mock, args)
 | 
			
		||||
        client_mock.audit_template.create.assert_called_once_with(
 | 
			
		||||
            host_aggregate=5)
 | 
			
		||||
 | 
			
		||||
    def test_do_audit_template_create_with_extra(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
        args.extra = ['automatic=true']
 | 
			
		||||
 | 
			
		||||
        at_shell.do_audit_template_create(client_mock, args)
 | 
			
		||||
        client_mock.audit_template.create.assert_called_once_with(
 | 
			
		||||
            extra={'automatic': True})
 | 
			
		||||
							
								
								
									
										321
									
								
								watcherclient/tests/v1/test_metric_collector.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										321
									
								
								watcherclient/tests/v1/test_metric_collector.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,321 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
# Copyright 2013 Red Hat, Inc.
 | 
			
		||||
# All Rights Reserved.
 | 
			
		||||
#
 | 
			
		||||
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
			
		||||
#    not use this file except in compliance with the License. You may obtain
 | 
			
		||||
#    a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#         http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
#    Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
#    License for the specific language governing permissions and limitations
 | 
			
		||||
#    under the License.
 | 
			
		||||
 | 
			
		||||
import copy
 | 
			
		||||
 | 
			
		||||
import testtools
 | 
			
		||||
from testtools.matchers import HasLength
 | 
			
		||||
 | 
			
		||||
from watcherclient.tests import utils
 | 
			
		||||
import watcherclient.v1.metric_collector
 | 
			
		||||
 | 
			
		||||
METRIC_COLLECTOR1 = {
 | 
			
		||||
    'id': 1,
 | 
			
		||||
    'uuid': '770ef053-ecb3-48b0-85b5-d55a2dbc6588',
 | 
			
		||||
    'category': 'cat1',
 | 
			
		||||
    'endpoint': 'http://metric_collector_1:6446',
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
METRIC_COLLECTOR2 = {
 | 
			
		||||
    'id': 2,
 | 
			
		||||
    'uuid': '67653274-eb24-c7ba-70f6-a84e73d80843',
 | 
			
		||||
    'category': 'cat2',
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
METRIC_COLLECTOR3 = {
 | 
			
		||||
    'id': 3,
 | 
			
		||||
    'uuid': 'f8e47706-efcf-49a4-a5c4-af604eb492f2',
 | 
			
		||||
    'category': 'cat2',
 | 
			
		||||
    'endpoint': 'http://metric_collector_3:6446',
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CREATE_METRIC_COLLECTOR = copy.deepcopy(METRIC_COLLECTOR1)
 | 
			
		||||
del CREATE_METRIC_COLLECTOR['id']
 | 
			
		||||
del CREATE_METRIC_COLLECTOR['uuid']
 | 
			
		||||
 | 
			
		||||
UPDATED_METRIC_COLLECTOR1 = copy.deepcopy(METRIC_COLLECTOR1)
 | 
			
		||||
NEW_ENDPOINT = 'http://metric_collector_1:6447'
 | 
			
		||||
UPDATED_METRIC_COLLECTOR1['endpoint'] = NEW_ENDPOINT
 | 
			
		||||
 | 
			
		||||
fake_responses = {
 | 
			
		||||
    '/v1/metric-collectors':
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"metric-collectors": [METRIC_COLLECTOR1]},
 | 
			
		||||
        ),
 | 
			
		||||
        'POST': (
 | 
			
		||||
            {},
 | 
			
		||||
            CREATE_METRIC_COLLECTOR,
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/v1/metric-collectors/?category=%s' % METRIC_COLLECTOR1['category']:
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"metric-collectors": [METRIC_COLLECTOR1]},
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/v1/metric-collectors/?category=%s' % METRIC_COLLECTOR2['category']:
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"metric-collectors": [METRIC_COLLECTOR2, METRIC_COLLECTOR3]},
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/v1/metric-collectors/detail':
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"metric-collectors": [METRIC_COLLECTOR1]},
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/v1/metric-collectors/%s' % METRIC_COLLECTOR1['uuid']:
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            METRIC_COLLECTOR1,
 | 
			
		||||
        ),
 | 
			
		||||
        'DELETE': (
 | 
			
		||||
            {},
 | 
			
		||||
            None,
 | 
			
		||||
        ),
 | 
			
		||||
        'PATCH': (
 | 
			
		||||
            {},
 | 
			
		||||
            UPDATED_METRIC_COLLECTOR1,
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/v1/metric-collectors/detail?category=%s' % METRIC_COLLECTOR1['category']:
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"metric-collectors": [METRIC_COLLECTOR1]},
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/v1/metric-collectors/detail?category=%s' % METRIC_COLLECTOR2['category']:
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"metric-collectors": [METRIC_COLLECTOR2, METRIC_COLLECTOR3]},
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fake_responses_pagination = {
 | 
			
		||||
    '/v1/metric-collectors':
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"metric-collectors": [METRIC_COLLECTOR1],
 | 
			
		||||
             "next": "http://127.0.0.1:6385/v1/metric-collectors/?limit=1"}
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/v1/metric-collectors/?limit=1':
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"metric-collectors": [METRIC_COLLECTOR2]}
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fake_responses_sorting = {
 | 
			
		||||
    '/v1/metric-collectors/?sort_key=updated_at':
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"metric-collectors": [METRIC_COLLECTOR2, METRIC_COLLECTOR1]}
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    '/v1/metric-collectors/?sort_dir=desc':
 | 
			
		||||
    {
 | 
			
		||||
        'GET': (
 | 
			
		||||
            {},
 | 
			
		||||
            {"metric-collectors": [METRIC_COLLECTOR2, METRIC_COLLECTOR1]}
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MetricCollectorManagerTest(testtools.TestCase):
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super(MetricCollectorManagerTest, self).setUp()
 | 
			
		||||
        self.api = utils.FakeAPI(fake_responses)
 | 
			
		||||
        self.mgr = watcherclient.v1.metric_collector \
 | 
			
		||||
                                .MetricCollectorManager(self.api)
 | 
			
		||||
 | 
			
		||||
    def test_metric_collectors_list(self):
 | 
			
		||||
        metric_collectors = self.mgr.list()
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET', '/v1/metric-collectors', {}, None),
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertEqual(1, len(metric_collectors))
 | 
			
		||||
 | 
			
		||||
    def test_metric_collectors_list_by_category(self):
 | 
			
		||||
        metric_collectors = self.mgr.list(
 | 
			
		||||
            category=METRIC_COLLECTOR1['category']
 | 
			
		||||
        )
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET',
 | 
			
		||||
             '/v1/metric-collectors/?category=%s' %
 | 
			
		||||
                METRIC_COLLECTOR1['category'],
 | 
			
		||||
             {},
 | 
			
		||||
             None),
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertEqual(1, len(metric_collectors))
 | 
			
		||||
 | 
			
		||||
    def test_metric_collectors_list_by_category_bis(self):
 | 
			
		||||
        metric_collectors = self.mgr.list(
 | 
			
		||||
            category=METRIC_COLLECTOR2['category']
 | 
			
		||||
        )
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET',
 | 
			
		||||
             '/v1/metric-collectors/?category=%s' %
 | 
			
		||||
                METRIC_COLLECTOR2['category'],
 | 
			
		||||
             {},
 | 
			
		||||
             None),
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertEqual(2, len(metric_collectors))
 | 
			
		||||
 | 
			
		||||
    def test_metric_collectors_list_detail(self):
 | 
			
		||||
        metric_collectors = self.mgr.list(detail=True)
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET', '/v1/metric-collectors/detail', {}, None),
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertEqual(1, len(metric_collectors))
 | 
			
		||||
 | 
			
		||||
    def test_metric_collectors_list_by_category_detail(self):
 | 
			
		||||
        metric_collectors = self.mgr.list(
 | 
			
		||||
            category=METRIC_COLLECTOR1['category'],
 | 
			
		||||
            detail=True)
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET',
 | 
			
		||||
             '/v1/metric-collectors/detail?category=%s' %
 | 
			
		||||
                METRIC_COLLECTOR1['category'],
 | 
			
		||||
             {},
 | 
			
		||||
             None),
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertEqual(1, len(metric_collectors))
 | 
			
		||||
 | 
			
		||||
    def test_metric_collectors_list_by_category_detail_bis(self):
 | 
			
		||||
        metric_collectors = self.mgr.list(
 | 
			
		||||
            category=METRIC_COLLECTOR2['category'],
 | 
			
		||||
            detail=True)
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET',
 | 
			
		||||
             '/v1/metric-collectors/detail?category=%s' %
 | 
			
		||||
                METRIC_COLLECTOR2['category'],
 | 
			
		||||
             {},
 | 
			
		||||
             None),
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertEqual(2, len(metric_collectors))
 | 
			
		||||
 | 
			
		||||
    def test_metric_collectors_list_limit(self):
 | 
			
		||||
        self.api = utils.FakeAPI(fake_responses_pagination)
 | 
			
		||||
        self.mgr = watcherclient.v1.metric_collector \
 | 
			
		||||
                                .MetricCollectorManager(self.api)
 | 
			
		||||
        metric_collectors = self.mgr.list(limit=1)
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET', '/v1/metric-collectors/?limit=1', {}, None),
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertThat(metric_collectors, HasLength(1))
 | 
			
		||||
 | 
			
		||||
    def test_metric_collectors_list_pagination_no_limit(self):
 | 
			
		||||
        self.api = utils.FakeAPI(fake_responses_pagination)
 | 
			
		||||
        self.mgr = watcherclient.v1.metric_collector \
 | 
			
		||||
                                .MetricCollectorManager(self.api)
 | 
			
		||||
        metric_collectors = self.mgr.list(limit=0)
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET', '/v1/metric-collectors', {}, None),
 | 
			
		||||
            ('GET', '/v1/metric-collectors/?limit=1', {}, None)
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertThat(metric_collectors, HasLength(2))
 | 
			
		||||
 | 
			
		||||
    def test_metric_collectors_list_sort_key(self):
 | 
			
		||||
        self.api = utils.FakeAPI(fake_responses_sorting)
 | 
			
		||||
        self.mgr = watcherclient.v1.metric_collector \
 | 
			
		||||
                                .MetricCollectorManager(self.api)
 | 
			
		||||
        metric_collectors = self.mgr.list(sort_key='updated_at')
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET', '/v1/metric-collectors/?sort_key=updated_at', {}, None)
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertEqual(2, len(metric_collectors))
 | 
			
		||||
 | 
			
		||||
    def test_metric_collectors_list_sort_dir(self):
 | 
			
		||||
        self.api = utils.FakeAPI(fake_responses_sorting)
 | 
			
		||||
        self.mgr = watcherclient.v1.metric_collector \
 | 
			
		||||
                                .MetricCollectorManager(self.api)
 | 
			
		||||
        metric_collectors = self.mgr.list(sort_dir='desc')
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET', '/v1/metric-collectors/?sort_dir=desc', {}, None)
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertEqual(2, len(metric_collectors))
 | 
			
		||||
 | 
			
		||||
    def test_metric_collectors_show(self):
 | 
			
		||||
        metric_collector = self.mgr.get(METRIC_COLLECTOR1['uuid'])
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('GET', '/v1/metric-collectors/%s' %
 | 
			
		||||
                METRIC_COLLECTOR1['uuid'], {}, None),
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertEqual(METRIC_COLLECTOR1['uuid'], metric_collector.uuid)
 | 
			
		||||
        self.assertEqual(METRIC_COLLECTOR1['category'],
 | 
			
		||||
                         metric_collector.category)
 | 
			
		||||
        self.assertEqual(METRIC_COLLECTOR1['endpoint'],
 | 
			
		||||
                         metric_collector.endpoint)
 | 
			
		||||
 | 
			
		||||
    def test_create(self):
 | 
			
		||||
        metric_collector = self.mgr.create(**CREATE_METRIC_COLLECTOR)
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('POST', '/v1/metric-collectors', {}, CREATE_METRIC_COLLECTOR),
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertTrue(metric_collector)
 | 
			
		||||
 | 
			
		||||
    def test_delete(self):
 | 
			
		||||
        metric_collector = self.mgr.delete(
 | 
			
		||||
            metric_collector_id=METRIC_COLLECTOR1['uuid'])
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('DELETE', '/v1/metric-collectors/%s' %
 | 
			
		||||
                METRIC_COLLECTOR1['uuid'], {}, None),
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertIsNone(metric_collector)
 | 
			
		||||
 | 
			
		||||
    def test_update(self):
 | 
			
		||||
        patch = {'op': 'replace',
 | 
			
		||||
                 'value': NEW_ENDPOINT,
 | 
			
		||||
                 'path': '/endpoint'}
 | 
			
		||||
        metric_collector = self.mgr.update(
 | 
			
		||||
            metric_collector_id=METRIC_COLLECTOR1['uuid'], patch=patch)
 | 
			
		||||
        expect = [
 | 
			
		||||
            ('PATCH', '/v1/metric-collectors/%s' %
 | 
			
		||||
                METRIC_COLLECTOR1['uuid'], {}, patch),
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEqual(expect, self.api.calls)
 | 
			
		||||
        self.assertEqual(NEW_ENDPOINT, metric_collector.endpoint)
 | 
			
		||||
							
								
								
									
										107
									
								
								watcherclient/tests/v1/test_metric_collector_shell.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								watcherclient/tests/v1/test_metric_collector_shell.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,107 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
# Copyright 2013 IBM Corp
 | 
			
		||||
#
 | 
			
		||||
#   Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
			
		||||
#   not use this file except in compliance with the License. You may obtain
 | 
			
		||||
#   a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#       http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
#   Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
#   distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
#   WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
#   License for the specific language governing permissions and limitations
 | 
			
		||||
#   under the License.
 | 
			
		||||
 | 
			
		||||
import mock
 | 
			
		||||
 | 
			
		||||
from watcherclient.common import utils as commonutils
 | 
			
		||||
from watcherclient.openstack.common import cliutils
 | 
			
		||||
from watcherclient.tests import utils
 | 
			
		||||
import watcherclient.v1.metric_collector_shell as mc_shell
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MetricCollectorShellTest(utils.BaseTestCase):
 | 
			
		||||
    def test_do_metric_collector_show(self):
 | 
			
		||||
        actual = {}
 | 
			
		||||
        fake_print_dict = lambda data, *args, **kwargs: actual.update(data)
 | 
			
		||||
        with mock.patch.object(cliutils, 'print_dict', fake_print_dict):
 | 
			
		||||
            metric_collector = object()
 | 
			
		||||
            mc_shell._print_metric_collector_show(metric_collector)
 | 
			
		||||
        exp = ['uuid', 'created_at', 'updated_at', 'deleted_at',
 | 
			
		||||
               'category', 'endpoint']
 | 
			
		||||
        act = actual.keys()
 | 
			
		||||
        self.assertEqual(sorted(exp), sorted(act))
 | 
			
		||||
 | 
			
		||||
    def test_do_metric_collector_show_by_uuid(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
        args.metric_collector = 'a5199d0e-0702-4613-9234-5ae2af8dafea'
 | 
			
		||||
 | 
			
		||||
        mc_shell.do_metric_collector_show(client_mock, args)
 | 
			
		||||
        client_mock.metric_collector.get.assert_called_once_with(
 | 
			
		||||
            'a5199d0e-0702-4613-9234-5ae2af8dafea'
 | 
			
		||||
        )
 | 
			
		||||
        # assert get_by_name() wasn't called
 | 
			
		||||
        self.assertFalse(client_mock.metric_collector.get_by_name.called)
 | 
			
		||||
 | 
			
		||||
    def test_do_metric_collector_delete(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
        args.metric_collector = ['metric_collector_uuid']
 | 
			
		||||
 | 
			
		||||
        mc_shell.do_metric_collector_delete(client_mock, args)
 | 
			
		||||
        client_mock.metric_collector.delete.assert_called_once_with(
 | 
			
		||||
            'metric_collector_uuid'
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_do_metric_collector_delete_multiple(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
        args.metric_collector = ['metric_collector_uuid1',
 | 
			
		||||
                                 'metric_collector_uuid2']
 | 
			
		||||
 | 
			
		||||
        mc_shell.do_metric_collector_delete(client_mock, args)
 | 
			
		||||
        client_mock.metric_collector.delete.assert_has_calls(
 | 
			
		||||
            [mock.call('metric_collector_uuid1'),
 | 
			
		||||
             mock.call('metric_collector_uuid2')])
 | 
			
		||||
 | 
			
		||||
    def test_do_metric_collector_update(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
        setattr(args, 'metric-collector', "metric_collector_uuid")
 | 
			
		||||
        args.op = 'add'
 | 
			
		||||
        args.attributes = [['arg1=val1', 'arg2=val2']]
 | 
			
		||||
 | 
			
		||||
        mc_shell.do_metric_collector_update(client_mock, args)
 | 
			
		||||
        patch = commonutils.args_array_to_patch(
 | 
			
		||||
            args.op,
 | 
			
		||||
            args.attributes[0])
 | 
			
		||||
        client_mock.metric_collector.update.assert_called_once_with(
 | 
			
		||||
            'metric_collector_uuid', patch)
 | 
			
		||||
 | 
			
		||||
    def test_do_metric_collector_create(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
 | 
			
		||||
        mc_shell.do_metric_collector_create(client_mock, args)
 | 
			
		||||
        client_mock.metric_collector.create.assert_called_once_with()
 | 
			
		||||
 | 
			
		||||
    def test_do_metric_collector_create_with_category(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
        args.category = 'mc_category'
 | 
			
		||||
 | 
			
		||||
        mc_shell.do_metric_collector_create(client_mock, args)
 | 
			
		||||
        client_mock.metric_collector.create.assert_called_once_with(
 | 
			
		||||
            category='mc_category')
 | 
			
		||||
 | 
			
		||||
    def test_do_metric_collector_create_with_endpoint(self):
 | 
			
		||||
        client_mock = mock.MagicMock()
 | 
			
		||||
        args = mock.MagicMock()
 | 
			
		||||
        args.endpoint = 'mc_endpoint'
 | 
			
		||||
 | 
			
		||||
        mc_shell.do_metric_collector_create(client_mock, args)
 | 
			
		||||
        client_mock.metric_collector.create.assert_called_once_with(
 | 
			
		||||
            endpoint='mc_endpoint')
 | 
			
		||||
							
								
								
									
										0
									
								
								watcherclient/v1/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								watcherclient/v1/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										90
									
								
								watcherclient/v1/action.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								watcherclient/v1/action.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,90 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
# Copyright 2013 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 watcherclient.common import base
 | 
			
		||||
from watcherclient.common import utils
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Action(base.Resource):
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        return "<Action %s>" % self._info
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ActionManager(base.Manager):
 | 
			
		||||
    resource_class = Action
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def _path(id=None):
 | 
			
		||||
        return '/v1/actions/%s' % id if id else '/v1/actions'
 | 
			
		||||
 | 
			
		||||
    def list(self, action_plan=None, audit=None, limit=None, sort_key=None,
 | 
			
		||||
             sort_dir=None, detail=False):
 | 
			
		||||
        """Retrieve a list of action.
 | 
			
		||||
 | 
			
		||||
        :param action_plan: UUID of the action plan
 | 
			
		||||
        :param audit: UUID of the audit
 | 
			
		||||
        :param limit: The maximum number of results to return per
 | 
			
		||||
                      request, if:
 | 
			
		||||
 | 
			
		||||
            1) limit > 0, the maximum number of actions to return.
 | 
			
		||||
            2) limit == 0, return the entire list of actions.
 | 
			
		||||
            3) limit param is NOT specified (None), the number of items
 | 
			
		||||
               returned respect the maximum imposed by the Watcher API
 | 
			
		||||
               (see Watcher's api.max_limit option).
 | 
			
		||||
 | 
			
		||||
        :param sort_key: Optional, field used for sorting.
 | 
			
		||||
 | 
			
		||||
        :param sort_dir: Optional, direction of sorting, either 'asc' (the
 | 
			
		||||
                         default) or 'desc'.
 | 
			
		||||
 | 
			
		||||
        :param detail: Optional, boolean whether to return detailed information
 | 
			
		||||
                       about actions.
 | 
			
		||||
 | 
			
		||||
        :returns: A list of actions.
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        if limit is not None:
 | 
			
		||||
            limit = int(limit)
 | 
			
		||||
 | 
			
		||||
        filters = utils.common_filters(limit, sort_key, sort_dir)
 | 
			
		||||
        if action_plan is not None:
 | 
			
		||||
            filters.append('action_plan_uuid=%s' % action_plan)
 | 
			
		||||
        if audit is not None:
 | 
			
		||||
            filters.append('audit_uuid=%s' % audit)
 | 
			
		||||
 | 
			
		||||
        path = ''
 | 
			
		||||
        if detail:
 | 
			
		||||
            path += 'detail'
 | 
			
		||||
        if filters:
 | 
			
		||||
            path += '?' + '&'.join(filters)
 | 
			
		||||
 | 
			
		||||
        if limit is None:
 | 
			
		||||
            return self._list(self._path(path), "actions")
 | 
			
		||||
        else:
 | 
			
		||||
            return self._list_pagination(self._path(path), "actions",
 | 
			
		||||
                                         limit=limit)
 | 
			
		||||
 | 
			
		||||
    def get(self, action_id):
 | 
			
		||||
        try:
 | 
			
		||||
            return self._list(self._path(action_id))[0]
 | 
			
		||||
        except IndexError:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    def delete(self, action_id):
 | 
			
		||||
        return self._delete(self._path(action_id))
 | 
			
		||||
 | 
			
		||||
    def update(self, action_id, patch):
 | 
			
		||||
        return self._update(self._path(action_id), patch)
 | 
			
		||||
							
								
								
									
										88
									
								
								watcherclient/v1/action_plan.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								watcherclient/v1/action_plan.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,88 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
# Copyright 2013 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 watcherclient.common import base
 | 
			
		||||
from watcherclient.common import utils
 | 
			
		||||
# from watcherclient import exceptions as exc
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ActionPlan(base.Resource):
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        return "<ActionPlan %s>" % self._info
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ActionPlanManager(base.Manager):
 | 
			
		||||
    resource_class = ActionPlan
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def _path(id=None):
 | 
			
		||||
        return '/v1/action_plans/%s' % id if id else '/v1/action_plans'
 | 
			
		||||
 | 
			
		||||
    def list(self, audit=None, limit=None, sort_key=None,
 | 
			
		||||
             sort_dir=None, detail=False):
 | 
			
		||||
        """Retrieve a list of action plan.
 | 
			
		||||
 | 
			
		||||
        :param audit: Name of the audit
 | 
			
		||||
        :param limit: The maximum number of results to return per
 | 
			
		||||
                      request, if:
 | 
			
		||||
 | 
			
		||||
            1) limit > 0, the maximum number of action plans to return.
 | 
			
		||||
            2) limit == 0, return the entire list of action plans.
 | 
			
		||||
            3) limit param is NOT specified (None), the number of items
 | 
			
		||||
               returned respect the maximum imposed by the Watcher API
 | 
			
		||||
               (see Watcher's api.max_limit option).
 | 
			
		||||
 | 
			
		||||
        :param sort_key: Optional, field used for sorting.
 | 
			
		||||
 | 
			
		||||
        :param sort_dir: Optional, direction of sorting, either 'asc' (the
 | 
			
		||||
                         default) or 'desc'.
 | 
			
		||||
 | 
			
		||||
        :param detail: Optional, boolean whether to return detailed information
 | 
			
		||||
                       about action plans.
 | 
			
		||||
 | 
			
		||||
        :returns: A list of action plans.
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        if limit is not None:
 | 
			
		||||
            limit = int(limit)
 | 
			
		||||
 | 
			
		||||
        filters = utils.common_filters(limit, sort_key, sort_dir)
 | 
			
		||||
        if audit is not None:
 | 
			
		||||
            filters.append('audit_uuid=%s' % audit)
 | 
			
		||||
 | 
			
		||||
        path = ''
 | 
			
		||||
        if detail:
 | 
			
		||||
            path += 'detail'
 | 
			
		||||
        if filters:
 | 
			
		||||
            path += '?' + '&'.join(filters)
 | 
			
		||||
 | 
			
		||||
        if limit is None:
 | 
			
		||||
            return self._list(self._path(path), "action_plans")
 | 
			
		||||
        else:
 | 
			
		||||
            return self._list_pagination(self._path(path), "action_plans",
 | 
			
		||||
                                         limit=limit)
 | 
			
		||||
 | 
			
		||||
    def get(self, action_plan_id):
 | 
			
		||||
        try:
 | 
			
		||||
            return self._list(self._path(action_plan_id))[0]
 | 
			
		||||
        except IndexError:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    def delete(self, action_plan_id):
 | 
			
		||||
        return self._delete(self._path(action_plan_id))
 | 
			
		||||
 | 
			
		||||
    def update(self, action_plan_id, patch):
 | 
			
		||||
        return self._update(self._path(action_plan_id), patch)
 | 
			
		||||
							
								
								
									
										156
									
								
								watcherclient/v1/action_plan_shell.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								watcherclient/v1/action_plan_shell.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,156 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
# Copyright 2013 Red Hat, Inc.
 | 
			
		||||
# All Rights Reserved.
 | 
			
		||||
#
 | 
			
		||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
			
		||||
#    not use this file except in compliance with the License. You may obtain
 | 
			
		||||
#    a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#         http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
#    Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
#    License for the specific language governing permissions and limitations
 | 
			
		||||
#    under the License.
 | 
			
		||||
 | 
			
		||||
# import argparse
 | 
			
		||||
 | 
			
		||||
from oslo_utils import uuidutils
 | 
			
		||||
from watcherclient.common import utils
 | 
			
		||||
from watcherclient.openstack.common.apiclient.exceptions import ValidationError
 | 
			
		||||
from watcherclient.openstack.common import cliutils
 | 
			
		||||
from watcherclient.v1 import resource_fields as res_fields
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _print_action_plan_show(action_plan):
 | 
			
		||||
    fields = res_fields.ACTION_PLAN_FIELDS
 | 
			
		||||
    data = dict([(f, getattr(action_plan, f, '')) for f in fields])
 | 
			
		||||
    cliutils.print_dict(data, wrap=72)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    'action-plan',
 | 
			
		||||
    metavar='<action-plan>',
 | 
			
		||||
    help="UUID of the action_plan.")
 | 
			
		||||
def do_action_plan_show(cc, args):
 | 
			
		||||
    """Show detailed information about an action plan."""
 | 
			
		||||
    action_plan_uuid = getattr(args, 'action-plan')
 | 
			
		||||
    if uuidutils.is_uuid_like(action_plan_uuid):
 | 
			
		||||
        action_plan = cc.action_plan.get(action_plan_uuid)
 | 
			
		||||
        _print_action_plan_show(action_plan)
 | 
			
		||||
    else:
 | 
			
		||||
        raise ValidationError()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    '--audit',
 | 
			
		||||
    metavar='<audit>',
 | 
			
		||||
    help='UUID of an audit used for filtering.')
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    '--detail',
 | 
			
		||||
    dest='detail',
 | 
			
		||||
    action='store_true',
 | 
			
		||||
    default=False,
 | 
			
		||||
    help="Show detailed information about action plans.")
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    '--limit',
 | 
			
		||||
    metavar='<limit>',
 | 
			
		||||
    type=int,
 | 
			
		||||
    help='Maximum number of action plans to return per request, '
 | 
			
		||||
         '0 for no limit. Default is the maximum number used '
 | 
			
		||||
         'by the Watcher API Service.')
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    '--sort-key',
 | 
			
		||||
    metavar='<field>',
 | 
			
		||||
    help='Action Plan field that will be used for sorting.')
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    '--sort-dir',
 | 
			
		||||
    metavar='<direction>',
 | 
			
		||||
    choices=['asc', 'desc'],
 | 
			
		||||
    help='Sort direction: "asc" (the default) or "desc".')
 | 
			
		||||
def do_action_plan_list(cc, args):
 | 
			
		||||
    """List the action plans."""
 | 
			
		||||
    params = {}
 | 
			
		||||
 | 
			
		||||
    if args.audit is not None:
 | 
			
		||||
        params['audit'] = args.audit
 | 
			
		||||
    if args.detail:
 | 
			
		||||
        fields = res_fields.ACTION_PLAN_FIELDS
 | 
			
		||||
        field_labels = res_fields.ACTION_PLAN_FIELD_LABELS
 | 
			
		||||
    else:
 | 
			
		||||
        fields = res_fields.ACTION_PLAN_SHORT_LIST_FIELDS
 | 
			
		||||
        field_labels = res_fields.ACTION_PLAN_SHORT_LIST_FIELD_LABELS
 | 
			
		||||
 | 
			
		||||
    params.update(utils.common_params_for_list(args,
 | 
			
		||||
                                               fields,
 | 
			
		||||
                                               field_labels))
 | 
			
		||||
 | 
			
		||||
    action_plan = cc.action_plan.list(**params)
 | 
			
		||||
    cliutils.print_list(action_plan, fields,
 | 
			
		||||
                        field_labels=field_labels,
 | 
			
		||||
                        sortby_index=None)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    'action-plan',
 | 
			
		||||
    metavar='<action-plan>',
 | 
			
		||||
    nargs='+',
 | 
			
		||||
    help="UUID of the action plan.")
 | 
			
		||||
def do_action_plan_delete(cc, args):
 | 
			
		||||
    """Delete an action plan."""
 | 
			
		||||
    for p in getattr(args, 'action-plan'):
 | 
			
		||||
        if uuidutils.is_uuid_like(p):
 | 
			
		||||
            cc.action_plan.delete(p)
 | 
			
		||||
            print ('Deleted action plan %s' % p)
 | 
			
		||||
        else:
 | 
			
		||||
            raise ValidationError()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    'action-plan',
 | 
			
		||||
    metavar='<action-plan>',
 | 
			
		||||
    help="UUID of the action plan.")
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    'op',
 | 
			
		||||
    metavar='<op>',
 | 
			
		||||
    choices=['add', 'replace', 'remove'],
 | 
			
		||||
    help="Operation: 'add', 'replace', or 'remove'.")
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    'attributes',
 | 
			
		||||
    metavar='<path=value>',
 | 
			
		||||
    nargs='+',
 | 
			
		||||
    action='append',
 | 
			
		||||
    default=[],
 | 
			
		||||
    help="Attribute to add, replace, or remove. Can be specified multiple  "
 | 
			
		||||
         "times. For 'remove', only <path> is necessary.")
 | 
			
		||||
def do_action_plan_update(cc, args):
 | 
			
		||||
    """Update information about an action plan."""
 | 
			
		||||
    action_plan_uuid = getattr(args, 'action-plan')
 | 
			
		||||
    if uuidutils.is_uuid_like(action_plan_uuid):
 | 
			
		||||
        patch = utils.args_array_to_patch(args.op, args.attributes[0])
 | 
			
		||||
        action_plan = cc.action_plan.update(action_plan_uuid, patch)
 | 
			
		||||
        _print_action_plan_show(action_plan)
 | 
			
		||||
    else:
 | 
			
		||||
        raise ValidationError()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@cliutils.arg('action-plan',
 | 
			
		||||
              metavar='<action-plan>',
 | 
			
		||||
              help="UUID of the action plan.")
 | 
			
		||||
def do_action_plan_start(cc, args):
 | 
			
		||||
    """Execute an action plan."""
 | 
			
		||||
    action_plan_uuid = getattr(args, 'action-plan')
 | 
			
		||||
    if uuidutils.is_uuid_like(action_plan_uuid):
 | 
			
		||||
        args.op = 'replace'
 | 
			
		||||
        args.attributes = [['state=STARTING']]
 | 
			
		||||
 | 
			
		||||
        patch = utils.args_array_to_patch(
 | 
			
		||||
            args.op,
 | 
			
		||||
            args.attributes[0])
 | 
			
		||||
 | 
			
		||||
        action_plan = cc.action_plan.update(action_plan_uuid, patch)
 | 
			
		||||
        _print_action_plan_show(action_plan)
 | 
			
		||||
    else:
 | 
			
		||||
        raise ValidationError()
 | 
			
		||||
							
								
								
									
										137
									
								
								watcherclient/v1/action_shell.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								watcherclient/v1/action_shell.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,137 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
# Copyright 2013 Red Hat, Inc.
 | 
			
		||||
# All Rights Reserved.
 | 
			
		||||
#
 | 
			
		||||
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
			
		||||
#    not use this file except in compliance with the License. You may obtain
 | 
			
		||||
#    a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#         http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
#    Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
#    License for the specific language governing permissions and limitations
 | 
			
		||||
#    under the License.
 | 
			
		||||
 | 
			
		||||
# import argparse
 | 
			
		||||
 | 
			
		||||
from oslo_utils import uuidutils
 | 
			
		||||
from watcherclient.common import utils
 | 
			
		||||
from watcherclient.openstack.common.apiclient.exceptions import ValidationError
 | 
			
		||||
from watcherclient.openstack.common import cliutils
 | 
			
		||||
from watcherclient.v1 import resource_fields as res_fields
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _print_action_show(action):
 | 
			
		||||
    fields = res_fields.ACTION_FIELDS
 | 
			
		||||
    data = dict([(f, getattr(action, f, '')) for f in fields])
 | 
			
		||||
    cliutils.print_dict(data, wrap=72)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    'action',
 | 
			
		||||
    metavar='<action>',
 | 
			
		||||
    help="UUID of the action")
 | 
			
		||||
def do_action_show(cc, args):
 | 
			
		||||
    """Show detailed information about an action."""
 | 
			
		||||
    if uuidutils.is_uuid_like(args.action):
 | 
			
		||||
        action = cc.action.get(args.action)
 | 
			
		||||
        _print_action_show(action)
 | 
			
		||||
    else:
 | 
			
		||||
        raise ValidationError()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    '--action-plan',
 | 
			
		||||
    metavar='<action_plan>',
 | 
			
		||||
    help='UUID of the action plan used for filtering.')
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    '--audit',
 | 
			
		||||
    metavar='<audit>',
 | 
			
		||||
    help=' UUID of the audit used for filtering.')
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    '--detail',
 | 
			
		||||
    dest='detail',
 | 
			
		||||
    action='store_true',
 | 
			
		||||
    default=False,
 | 
			
		||||
    help="Show detailed information about actions.")
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    '--limit',
 | 
			
		||||
    metavar='<limit>',
 | 
			
		||||
    type=int,
 | 
			
		||||
    help='Maximum number of actions to return per request, '
 | 
			
		||||
         '0 for no limit. Default is the maximum number used '
 | 
			
		||||
         'by the Watcher API Service.')
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    '--sort-key',
 | 
			
		||||
    metavar='<field>',
 | 
			
		||||
    help='Action field that will be used for sorting.')
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    '--sort-dir',
 | 
			
		||||
    metavar='<direction>',
 | 
			
		||||
    choices=['asc', 'desc'],
 | 
			
		||||
    help='Sort direction: "asc" (the default) or "desc".')
 | 
			
		||||
def do_action_list(cc, args):
 | 
			
		||||
    """List the actions."""
 | 
			
		||||
    params = {}
 | 
			
		||||
 | 
			
		||||
    if args.action_plan is not None:
 | 
			
		||||
        params['action_plan'] = args.action_plan
 | 
			
		||||
    if args.audit is not None:
 | 
			
		||||
        params['audit'] = args.audit
 | 
			
		||||
    if args.detail:
 | 
			
		||||
        fields = res_fields.ACTION_FIELDS
 | 
			
		||||
        field_labels = res_fields.ACTION_FIELD_LABELS
 | 
			
		||||
    else:
 | 
			
		||||
        fields = res_fields.ACTION_SHORT_LIST_FIELDS
 | 
			
		||||
        field_labels = res_fields.ACTION_SHORT_LIST_FIELD_LABELS
 | 
			
		||||
 | 
			
		||||
    params.update(utils.common_params_for_list(args,
 | 
			
		||||
                                               fields,
 | 
			
		||||
                                               field_labels))
 | 
			
		||||
 | 
			
		||||
    action = cc.action.list(**params)
 | 
			
		||||
    cliutils.print_list(action, fields,
 | 
			
		||||
                        field_labels=field_labels,
 | 
			
		||||
                        sortby_index=None)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    'action',
 | 
			
		||||
    metavar='<action>',
 | 
			
		||||
    nargs='+',
 | 
			
		||||
    help="UUID of the action.")
 | 
			
		||||
def do_action_delete(cc, args):
 | 
			
		||||
    """Delete an action."""
 | 
			
		||||
    for p in args.action:
 | 
			
		||||
        if uuidutils.is_uuid_like(p):
 | 
			
		||||
            cc.action.delete(p)
 | 
			
		||||
            print ('Deleted action %s' % p)
 | 
			
		||||
        else:
 | 
			
		||||
            raise ValidationError()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@cliutils.arg('action', metavar='<action>', help="UUID of the action.")
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    'op',
 | 
			
		||||
    metavar='<op>',
 | 
			
		||||
    choices=['add', 'replace', 'remove'],
 | 
			
		||||
    help="Operation: 'add', 'replace', or 'remove'.")
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    'attributes',
 | 
			
		||||
    metavar='<path=value>',
 | 
			
		||||
    nargs='+',
 | 
			
		||||
    action='append',
 | 
			
		||||
    default=[],
 | 
			
		||||
    help="Attribute to add, replace, or remove. Can be specified multiple  "
 | 
			
		||||
         "times. For 'remove', only <path> is necessary.")
 | 
			
		||||
def do_action_update(cc, args):
 | 
			
		||||
    """Update information about an action."""
 | 
			
		||||
    if uuidutils.is_uuid_like(args.action):
 | 
			
		||||
        patch = utils.args_array_to_patch(args.op, args.attributes[0])
 | 
			
		||||
        action = cc.action.update(args.action, patch)
 | 
			
		||||
        _print_action_show(action)
 | 
			
		||||
    else:
 | 
			
		||||
        raise ValidationError()
 | 
			
		||||
							
								
								
									
										100
									
								
								watcherclient/v1/audit.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								watcherclient/v1/audit.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,100 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
# Copyright 2013 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 watcherclient.common import base
 | 
			
		||||
from watcherclient.common import utils
 | 
			
		||||
from watcherclient import exceptions as exc
 | 
			
		||||
 | 
			
		||||
CREATION_ATTRIBUTES = ['audit_template_uuid', 'deadline', 'type']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Audit(base.Resource):
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        return "<Audit %s>" % self._info
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AuditManager(base.Manager):
 | 
			
		||||
    resource_class = Audit
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def _path(id=None):
 | 
			
		||||
        return '/v1/audits/%s' % id if id else '/v1/audits'
 | 
			
		||||
 | 
			
		||||
    def list(self, audit_template=None, limit=None, sort_key=None,
 | 
			
		||||
             sort_dir=None, detail=False):
 | 
			
		||||
        """Retrieve a list of audit.
 | 
			
		||||
 | 
			
		||||
        :param audit_template: Name of the audit
 | 
			
		||||
        :param name: Name of the audit
 | 
			
		||||
        :param limit: The maximum number of results to return per
 | 
			
		||||
                      request, if:
 | 
			
		||||
 | 
			
		||||
            1) limit > 0, the maximum number of audits to return.
 | 
			
		||||
            2) limit == 0, return the entire list of audits.
 | 
			
		||||
            3) limit param is NOT specified (None), the number of items
 | 
			
		||||
               returned respect the maximum imposed by the Watcher API
 | 
			
		||||
               (see Watcher's api.max_limit option).
 | 
			
		||||
 | 
			
		||||
        :param sort_key: Optional, field used for sorting.
 | 
			
		||||
 | 
			
		||||
        :param sort_dir: Optional, direction of sorting, either 'asc' (the
 | 
			
		||||
                         default) or 'desc'.
 | 
			
		||||
 | 
			
		||||
        :param detail: Optional, boolean whether to return detailed information
 | 
			
		||||
                       about audits.
 | 
			
		||||
 | 
			
		||||
        :returns: A list of audits.
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        if limit is not None:
 | 
			
		||||
            limit = int(limit)
 | 
			
		||||
 | 
			
		||||
        filters = utils.common_filters(limit, sort_key, sort_dir)
 | 
			
		||||
        if audit_template is not None:
 | 
			
		||||
            filters.append('audit_template=%s' % audit_template)
 | 
			
		||||
 | 
			
		||||
        path = ''
 | 
			
		||||
        if detail:
 | 
			
		||||
            path += 'detail'
 | 
			
		||||
        if filters:
 | 
			
		||||
            path += '?' + '&'.join(filters)
 | 
			
		||||
 | 
			
		||||
        if limit is None:
 | 
			
		||||
            return self._list(self._path(path), "audits")
 | 
			
		||||
        else:
 | 
			
		||||
            return self._list_pagination(self._path(path), "audits",
 | 
			
		||||
                                         limit=limit)
 | 
			
		||||
 | 
			
		||||
    def create(self, **kwargs):
 | 
			
		||||
        new = {}
 | 
			
		||||
        for (key, value) in kwargs.items():
 | 
			
		||||
            if key in CREATION_ATTRIBUTES:
 | 
			
		||||
                new[key] = value
 | 
			
		||||
            else:
 | 
			
		||||
                raise exc.InvalidAttribute()
 | 
			
		||||
        return self._create(self._path(), new)
 | 
			
		||||
 | 
			
		||||
    def get(self, audit_id):
 | 
			
		||||
        try:
 | 
			
		||||
            return self._list(self._path(audit_id))[0]
 | 
			
		||||
        except IndexError:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    def delete(self, audit_id):
 | 
			
		||||
        return self._delete(self._path(audit_id))
 | 
			
		||||
 | 
			
		||||
    def update(self, audit_id, patch):
 | 
			
		||||
        return self._update(self._path(audit_id), patch)
 | 
			
		||||
							
								
								
									
										162
									
								
								watcherclient/v1/audit_shell.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								watcherclient/v1/audit_shell.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,162 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
# Copyright 2013 Red Hat, Inc.
 | 
			
		||||
# All Rights Reserved.
 | 
			
		||||
#
 | 
			
		||||
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
			
		||||
#    not use this file except in compliance with the License. You may obtain
 | 
			
		||||
#    a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#         http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
#    Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
#    License for the specific language governing permissions and limitations
 | 
			
		||||
#    under the License.
 | 
			
		||||
 | 
			
		||||
# import argparse
 | 
			
		||||
 | 
			
		||||
from oslo_utils import uuidutils
 | 
			
		||||
from watcherclient.common import utils
 | 
			
		||||
from watcherclient.openstack.common.apiclient.exceptions import ValidationError
 | 
			
		||||
from watcherclient.openstack.common import cliutils
 | 
			
		||||
from watcherclient.v1 import resource_fields as res_fields
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _print_audit_show(audit):
 | 
			
		||||
    fields = res_fields.AUDIT_FIELDS
 | 
			
		||||
    data = dict([(f, getattr(audit, f, '')) for f in fields])
 | 
			
		||||
    cliutils.print_dict(data, wrap=72)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    'audit',
 | 
			
		||||
    metavar='<audit>',
 | 
			
		||||
    help="UUID of the audit.")
 | 
			
		||||
def do_audit_show(cc, args):
 | 
			
		||||
    """Show detailed information about an audit."""
 | 
			
		||||
 | 
			
		||||
    if uuidutils.is_uuid_like(args.audit):
 | 
			
		||||
        audit = cc.audit.get(args.audit)
 | 
			
		||||
        _print_audit_show(audit)
 | 
			
		||||
    else:
 | 
			
		||||
        raise ValidationError()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    '--audit-template',
 | 
			
		||||
    metavar='<audit_template>',
 | 
			
		||||
    dest='audit_template',
 | 
			
		||||
    help='Name or UUID of an audit template used for filtering.')
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    '--detail',
 | 
			
		||||
    dest='detail',
 | 
			
		||||
    action='store_true',
 | 
			
		||||
    default=False,
 | 
			
		||||
    help="Show detailed information about audits.")
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    '--limit',
 | 
			
		||||
    metavar='<limit>',
 | 
			
		||||
    type=int,
 | 
			
		||||
    help='Maximum number of audits to return per request, '
 | 
			
		||||
         '0 for no limit. Default is the maximum number used '
 | 
			
		||||
         'by the Watcher API Service.')
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    '--sort-key',
 | 
			
		||||
    metavar='<field>',
 | 
			
		||||
    help='Audit field that will be used for sorting.')
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    '--sort-dir',
 | 
			
		||||
    metavar='<direction>',
 | 
			
		||||
    choices=['asc', 'desc'],
 | 
			
		||||
    help='Sort direction: "asc" (the default) or "desc".')
 | 
			
		||||
def do_audit_list(cc, args):
 | 
			
		||||
    """List the audits."""
 | 
			
		||||
    params = {}
 | 
			
		||||
 | 
			
		||||
    if args.audit_template is not None:
 | 
			
		||||
        params['audit_template'] = args.audit_template
 | 
			
		||||
    if args.detail:
 | 
			
		||||
        fields = res_fields.AUDIT_FIELDS
 | 
			
		||||
        field_labels = res_fields.AUDIT_FIELD_LABELS
 | 
			
		||||
    else:
 | 
			
		||||
        fields = res_fields.AUDIT_SHORT_LIST_FIELDS
 | 
			
		||||
        field_labels = res_fields.AUDIT_SHORT_LIST_FIELD_LABELS
 | 
			
		||||
 | 
			
		||||
    # params.update(utils.common_params_for_list(args, fields, field_labels))
 | 
			
		||||
 | 
			
		||||
    audit = cc.audit.list(**params)
 | 
			
		||||
    cliutils.print_list(audit, fields,
 | 
			
		||||
                        field_labels=field_labels,
 | 
			
		||||
                        sortby_index=None)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    '-a', '--audit-template',
 | 
			
		||||
    required=True,
 | 
			
		||||
    dest='audit_template_uuid',
 | 
			
		||||
    metavar='<audit_template>',
 | 
			
		||||
    help='Audit template used for this audit (name or uuid).')
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    '-d', '--deadline',
 | 
			
		||||
    dest='deadline',
 | 
			
		||||
    metavar='<deadline>',
 | 
			
		||||
    help='Descrition of the audit.')
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    '-t', '--type',
 | 
			
		||||
    dest='type',
 | 
			
		||||
    metavar='<type>',
 | 
			
		||||
    default='ONESHOT',
 | 
			
		||||
    help="Audit type.")
 | 
			
		||||
def do_audit_create(cc, args):
 | 
			
		||||
    """Create a new audit."""
 | 
			
		||||
    field_list = ['audit_template_uuid', 'type', 'deadline']
 | 
			
		||||
    fields = dict((k, v) for (k, v) in vars(args).items()
 | 
			
		||||
                  if k in field_list and not (v is None))
 | 
			
		||||
    audit = cc.audit.create(**fields)
 | 
			
		||||
    field_list.append('uuid')
 | 
			
		||||
    data = dict([(f, getattr(audit, f, '')) for f in field_list])
 | 
			
		||||
    cliutils.print_dict(data, wrap=72)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    'audit',
 | 
			
		||||
    metavar='<audit>',
 | 
			
		||||
    nargs='+',
 | 
			
		||||
    help="UUID of the audit.")
 | 
			
		||||
def do_audit_delete(cc, args):
 | 
			
		||||
    """Delete an audit."""
 | 
			
		||||
    for p in args.audit:
 | 
			
		||||
        if uuidutils.is_uuid_like(p):
 | 
			
		||||
            cc.audit.delete(p)
 | 
			
		||||
            print ('Deleted audit %s' % p)
 | 
			
		||||
        else:
 | 
			
		||||
            raise ValidationError()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    'audit',
 | 
			
		||||
    metavar='<audit>',
 | 
			
		||||
    help="UUID of the audit.")
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    'op',
 | 
			
		||||
    metavar='<op>',
 | 
			
		||||
    choices=['add', 'replace', 'remove'],
 | 
			
		||||
    help="Operation: 'add', 'replace', or 'remove'.")
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    'attributes',
 | 
			
		||||
    metavar='<path=value>',
 | 
			
		||||
    nargs='+',
 | 
			
		||||
    action='append',
 | 
			
		||||
    default=[],
 | 
			
		||||
    help="Attribute to add, replace, or remove. Can be specified multiple  "
 | 
			
		||||
         "times. For 'remove', only <path> is necessary.")
 | 
			
		||||
def do_audit_update(cc, args):
 | 
			
		||||
    """Update information about an audit."""
 | 
			
		||||
    if uuidutils.is_uuid_like(args.audit):
 | 
			
		||||
        patch = utils.args_array_to_patch(args.op, args.attributes[0])
 | 
			
		||||
        audit = cc.audit.update(args.audit, patch)
 | 
			
		||||
        _print_audit_show(audit)
 | 
			
		||||
    else:
 | 
			
		||||
        raise ValidationError()
 | 
			
		||||
							
								
								
									
										100
									
								
								watcherclient/v1/audit_template.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								watcherclient/v1/audit_template.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,100 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
# Copyright 2013 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 watcherclient.common import base
 | 
			
		||||
from watcherclient.common import utils
 | 
			
		||||
from watcherclient import exceptions as exc
 | 
			
		||||
 | 
			
		||||
CREATION_ATTRIBUTES = ['host_aggregate', 'description', 'name',
 | 
			
		||||
                       'extra', 'goal']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AuditTemplate(base.Resource):
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        return "<AuditTemplate %s>" % self._info
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AuditTemplateManager(base.Manager):
 | 
			
		||||
    resource_class = AuditTemplate
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def _path(id=None):
 | 
			
		||||
        return '/v1/audit_templates/%s' % id if id else '/v1/audit_templates'
 | 
			
		||||
 | 
			
		||||
    def list(self, name=None, limit=None, sort_key=None,
 | 
			
		||||
             sort_dir=None, detail=False):
 | 
			
		||||
        """Retrieve a list of audit template.
 | 
			
		||||
 | 
			
		||||
        :param name: Name of the audit template
 | 
			
		||||
        :param limit: The maximum number of results to return per
 | 
			
		||||
                      request, if:
 | 
			
		||||
 | 
			
		||||
            1) limit > 0, the maximum number of audit templates to return.
 | 
			
		||||
            2) limit == 0, return the entire list of audit_templates.
 | 
			
		||||
            3) limit param is NOT specified (None), the number of items
 | 
			
		||||
               returned respect the maximum imposed by the Watcher API
 | 
			
		||||
               (see Watcher's api.max_limit option).
 | 
			
		||||
 | 
			
		||||
        :param sort_key: Optional, field used for sorting.
 | 
			
		||||
 | 
			
		||||
        :param sort_dir: Optional, direction of sorting, either 'asc' (the
 | 
			
		||||
                         default) or 'desc'.
 | 
			
		||||
 | 
			
		||||
        :param detail: Optional, boolean whether to return detailed information
 | 
			
		||||
                       about audit_templates.
 | 
			
		||||
 | 
			
		||||
        :returns: A list of audit templates.
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        if limit is not None:
 | 
			
		||||
            limit = int(limit)
 | 
			
		||||
 | 
			
		||||
        filters = utils.common_filters(limit, sort_key, sort_dir)
 | 
			
		||||
        if name is not None:
 | 
			
		||||
            filters.append('name=%s' % name)
 | 
			
		||||
 | 
			
		||||
        path = ''
 | 
			
		||||
        if detail:
 | 
			
		||||
            path += 'detail'
 | 
			
		||||
        if filters:
 | 
			
		||||
            path += '?' + '&'.join(filters)
 | 
			
		||||
 | 
			
		||||
        if limit is None:
 | 
			
		||||
            return self._list(self._path(path), "audit_templates")
 | 
			
		||||
        else:
 | 
			
		||||
            return self._list_pagination(self._path(path), "audit_templates",
 | 
			
		||||
                                         limit=limit)
 | 
			
		||||
 | 
			
		||||
    def get(self, audit_template_id):
 | 
			
		||||
        try:
 | 
			
		||||
            return self._list(self._path(audit_template_id))[0]
 | 
			
		||||
        except IndexError:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    def create(self, **kwargs):
 | 
			
		||||
        new = {}
 | 
			
		||||
        for (key, value) in kwargs.items():
 | 
			
		||||
            if key in CREATION_ATTRIBUTES:
 | 
			
		||||
                new[key] = value
 | 
			
		||||
            else:
 | 
			
		||||
                raise exc.InvalidAttribute()
 | 
			
		||||
        return self._create(self._path(), new)
 | 
			
		||||
 | 
			
		||||
    def delete(self, audit_template_id):
 | 
			
		||||
        return self._delete(self._path(audit_template_id))
 | 
			
		||||
 | 
			
		||||
    def update(self, audit_template_id, patch):
 | 
			
		||||
        return self._update(self._path(audit_template_id), patch)
 | 
			
		||||
							
								
								
									
										167
									
								
								watcherclient/v1/audit_template_shell.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										167
									
								
								watcherclient/v1/audit_template_shell.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,167 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
# Copyright 2013 Red Hat, Inc.
 | 
			
		||||
# All Rights Reserved.
 | 
			
		||||
#
 | 
			
		||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
			
		||||
# not use this file except in compliance with the License. You may obtain
 | 
			
		||||
#    a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#         http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
#    Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
#    License for the specific language governing permissions and limitations
 | 
			
		||||
#    under the License.
 | 
			
		||||
 | 
			
		||||
# import argparse
 | 
			
		||||
 | 
			
		||||
from watcherclient.common import utils
 | 
			
		||||
from watcherclient.openstack.common import cliutils
 | 
			
		||||
from watcherclient.v1 import resource_fields as res_fields
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _print_audit_template_show(audit_template):
 | 
			
		||||
    fields = res_fields.AUDIT_TEMPLATE_FIELDS
 | 
			
		||||
    data = dict([(f, getattr(audit_template, f, '')) for f in fields])
 | 
			
		||||
    cliutils.print_dict(data, wrap=72)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    'audit-template',
 | 
			
		||||
    metavar='<audit-template>',
 | 
			
		||||
    help="Name or UUID of the audit template.")
 | 
			
		||||
def do_audit_template_show(cc, args):
 | 
			
		||||
    """Show detailed information about a audit template."""
 | 
			
		||||
 | 
			
		||||
    audit_template = cc.audit_template.get(getattr(args, 'audit-template'))
 | 
			
		||||
    _print_audit_template_show(audit_template)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    '--detail',
 | 
			
		||||
    dest='detail',
 | 
			
		||||
    action='store_true',
 | 
			
		||||
    default=False,
 | 
			
		||||
    help="Show detailed information about audit templates.")
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    '--name',
 | 
			
		||||
    metavar='<name>',
 | 
			
		||||
    help='Only show information for the audit template with this name.')
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    '--goal',
 | 
			
		||||
    metavar='<goal>',
 | 
			
		||||
    help='Name the goal used for filtering.')
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    '--limit',
 | 
			
		||||
    metavar='<limit>',
 | 
			
		||||
    type=int,
 | 
			
		||||
    help='Maximum number of audit templates to return per request, '
 | 
			
		||||
         '0 for no limit. Default is the maximum number used '
 | 
			
		||||
         'by the Watcher API Service.')
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    '--sort-key',
 | 
			
		||||
    metavar='<field>',
 | 
			
		||||
    help='Audit template field that will be used for sorting.')
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    '--sort-dir',
 | 
			
		||||
    metavar='<direction>',
 | 
			
		||||
    choices=['asc', 'desc'],
 | 
			
		||||
    help='Sort direction: "asc" (the default) or "desc".')
 | 
			
		||||
def do_audit_template_list(cc, args):
 | 
			
		||||
    """List the audit templates."""
 | 
			
		||||
    params = {}
 | 
			
		||||
 | 
			
		||||
    if args.name is not None:
 | 
			
		||||
        params['name'] = args.name
 | 
			
		||||
    if args.goal is not None:
 | 
			
		||||
        params['goal'] = args.goal
 | 
			
		||||
    if args.detail:
 | 
			
		||||
        fields = res_fields.AUDIT_TEMPLATE_FIELDS
 | 
			
		||||
        field_labels = res_fields.AUDIT_TEMPLATE_FIELD_LABELS
 | 
			
		||||
    else:
 | 
			
		||||
        fields = res_fields.AUDIT_TEMPLATE_SHORT_LIST_FIELDS
 | 
			
		||||
        field_labels = res_fields.AUDIT_TEMPLATE_SHORT_LIST_FIELD_LABELS
 | 
			
		||||
 | 
			
		||||
    params.update(utils.common_params_for_list(args,
 | 
			
		||||
                                               fields,
 | 
			
		||||
                                               field_labels))
 | 
			
		||||
 | 
			
		||||
    audit_template = cc.audit_template.list(**params)
 | 
			
		||||
    cliutils.print_list(audit_template, fields,
 | 
			
		||||
                        field_labels=field_labels,
 | 
			
		||||
                        sortby_index=None)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    'name',
 | 
			
		||||
    metavar='<name>',
 | 
			
		||||
    help='Name for this audit template.')
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    'goal',
 | 
			
		||||
    metavar='<goal>',
 | 
			
		||||
    help='Goal Type associated to this audit template.')
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    '-d', '--description',
 | 
			
		||||
    metavar='<description>',
 | 
			
		||||
    help='Descrition of the audit template.')
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    '-e', '--extra',
 | 
			
		||||
    metavar='<key=value>',
 | 
			
		||||
    action='append',
 | 
			
		||||
    help="Record arbitrary key/value metadata. "
 | 
			
		||||
         "Can be specified multiple times.")
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    '-a', '--host-aggregate',
 | 
			
		||||
    dest='host_aggregate',
 | 
			
		||||
    metavar='<host-aggregate>',
 | 
			
		||||
    help='Name or ID of the host aggregate targeted by this audit template.')
 | 
			
		||||
def do_audit_template_create(cc, args):
 | 
			
		||||
    """Create a new audit template."""
 | 
			
		||||
    field_list = ['host_aggregate', 'description', 'name', 'extra', 'goal']
 | 
			
		||||
    fields = dict((k, v) for (k, v) in vars(args).items()
 | 
			
		||||
                  if k in field_list and not (v is None))
 | 
			
		||||
    fields = utils.args_array_to_dict(fields, 'extra')
 | 
			
		||||
    audit_template = cc.audit_template.create(**fields)
 | 
			
		||||
 | 
			
		||||
    field_list.append('uuid')
 | 
			
		||||
    data = dict([(f, getattr(audit_template, f, '')) for f in field_list])
 | 
			
		||||
    cliutils.print_dict(data, wrap=72)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    'audit-template',
 | 
			
		||||
    metavar='<audit-template>',
 | 
			
		||||
    nargs='+',
 | 
			
		||||
    help="UUID or name of the audit template.")
 | 
			
		||||
def do_audit_template_delete(cc, args):
 | 
			
		||||
    """Delete an audit template."""
 | 
			
		||||
    for p in getattr(args, 'audit-template'):
 | 
			
		||||
        cc.audit_template.delete(p)
 | 
			
		||||
        print ('Deleted audit template %s' % p)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    'audit-template',
 | 
			
		||||
    metavar='<audit-template>',
 | 
			
		||||
    help="UUID or name of the audit template.")
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    'op',
 | 
			
		||||
    metavar='<op>',
 | 
			
		||||
    choices=['add', 'replace', 'remove'],
 | 
			
		||||
    help="Operation: 'add', 'replace', or 'remove'.")
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    'attributes',
 | 
			
		||||
    metavar='<path=value>',
 | 
			
		||||
    nargs='+',
 | 
			
		||||
    action='append',
 | 
			
		||||
    default=[],
 | 
			
		||||
    help="Attribute to add, replace, or remove. Can be specified multiple  "
 | 
			
		||||
         "times. For 'remove', only <path> is necessary.")
 | 
			
		||||
def do_audit_template_update(cc, args):
 | 
			
		||||
    """Update information about an audit template."""
 | 
			
		||||
    patch = utils.args_array_to_patch(args.op, args.attributes[0])
 | 
			
		||||
    audit_template = cc.audit_template.update(getattr(args, 'audit-template'),
 | 
			
		||||
                                              patch)
 | 
			
		||||
    _print_audit_template_show(audit_template)
 | 
			
		||||
							
								
								
									
										46
									
								
								watcherclient/v1/client.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								watcherclient/v1/client.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
# Copyright 2012 OpenStack LLC.
 | 
			
		||||
# All Rights Reserved.
 | 
			
		||||
#
 | 
			
		||||
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
			
		||||
#    not use this file except in compliance with the License. You may obtain
 | 
			
		||||
#    a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#         http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
#    Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
#    License for the specific language governing permissions and limitations
 | 
			
		||||
#    under the License.
 | 
			
		||||
 | 
			
		||||
from watcherclient.common import http
 | 
			
		||||
from watcherclient.v1 import action
 | 
			
		||||
from watcherclient.v1 import action_plan
 | 
			
		||||
from watcherclient.v1 import audit
 | 
			
		||||
from watcherclient.v1 import audit_template
 | 
			
		||||
from watcherclient.v1 import metric_collector
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Client(object):
 | 
			
		||||
    """Client for the Watcher v1 API.
 | 
			
		||||
 | 
			
		||||
    :param string endpoint: A user-supplied endpoint URL for the watcher
 | 
			
		||||
                            service.
 | 
			
		||||
    :param function token: Provides token for authentication.
 | 
			
		||||
    :param integer timeout: Allows customization of the timeout for client
 | 
			
		||||
                            http requests. (optional)
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        """Initialize a new client for the Watcher v1 API."""
 | 
			
		||||
        self.http_client = http._construct_http_client(*args, **kwargs)
 | 
			
		||||
        self.audit = audit.AuditManager(self.http_client)
 | 
			
		||||
        self.audit_template = audit_template.AuditTemplateManager(
 | 
			
		||||
            self.http_client)
 | 
			
		||||
        self.action = action.ActionManager(self.http_client)
 | 
			
		||||
        self.action_plan = action_plan.ActionPlanManager(self.http_client)
 | 
			
		||||
        self.metric_collector = metric_collector.MetricCollectorManager(
 | 
			
		||||
            self.http_client
 | 
			
		||||
        )
 | 
			
		||||
							
								
								
									
										101
									
								
								watcherclient/v1/metric_collector.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								watcherclient/v1/metric_collector.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,101 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
# Copyright 2013 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 watcherclient.common import base
 | 
			
		||||
from watcherclient.common import utils
 | 
			
		||||
from watcherclient import exceptions as exc
 | 
			
		||||
 | 
			
		||||
CREATION_ATTRIBUTES = ['endpoint', 'category']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MetricCollector(base.Resource):
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        return "<MetricCollector %s>" % self._info
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MetricCollectorManager(base.Manager):
 | 
			
		||||
    resource_class = MetricCollector
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def _path(id=None):
 | 
			
		||||
        return \
 | 
			
		||||
            '/v1/metric-collectors/%s' % id if id else '/v1/metric-collectors'
 | 
			
		||||
 | 
			
		||||
    def list(self, category=None, limit=None, sort_key=None,
 | 
			
		||||
             sort_dir=None, detail=False):
 | 
			
		||||
        """Retrieve a list of metric collector.
 | 
			
		||||
 | 
			
		||||
        :param category: Optional, Metric category, to get all metric
 | 
			
		||||
                         collectors mapped with this category.
 | 
			
		||||
        :param limit: The maximum number of results to return per
 | 
			
		||||
                  request, if:
 | 
			
		||||
 | 
			
		||||
            1) limit > 0, the maximum number of metric collectors to return.
 | 
			
		||||
            2) limit == 0, return the entire list of metriccollectors.
 | 
			
		||||
            3) limit param is NOT specified (None), the number of items
 | 
			
		||||
               returned respect the maximum imposed by the Watcher API
 | 
			
		||||
               (see Watcher's api.max_limit option).
 | 
			
		||||
 | 
			
		||||
        :param sort_key: Optional, field used for sorting.
 | 
			
		||||
 | 
			
		||||
        :param sort_dir: Optional, direction of sorting, either 'asc' (the
 | 
			
		||||
                         default) or 'desc'.
 | 
			
		||||
 | 
			
		||||
        :param detail: Optional, boolean whether to return detailed information
 | 
			
		||||
                       about metric collectors.
 | 
			
		||||
 | 
			
		||||
        :returns: A list of metric collectors.
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        if limit is not None:
 | 
			
		||||
            limit = int(limit)
 | 
			
		||||
 | 
			
		||||
        filters = utils.common_filters(limit, sort_key, sort_dir)
 | 
			
		||||
        if category is not None:
 | 
			
		||||
            filters.append('category=%s' % category)
 | 
			
		||||
 | 
			
		||||
        path = ''
 | 
			
		||||
        if detail:
 | 
			
		||||
            path += 'detail'
 | 
			
		||||
        if filters:
 | 
			
		||||
            path += '?' + '&'.join(filters)
 | 
			
		||||
 | 
			
		||||
        if limit is None:
 | 
			
		||||
            return self._list(self._path(path), "metric-collectors")
 | 
			
		||||
        else:
 | 
			
		||||
            return self._list_pagination(self._path(path), "metric-collectors",
 | 
			
		||||
                                         limit=limit)
 | 
			
		||||
 | 
			
		||||
    def get(self, metric_collector_id):
 | 
			
		||||
        try:
 | 
			
		||||
            return self._list(self._path(metric_collector_id))[0]
 | 
			
		||||
        except IndexError:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    def create(self, **kwargs):
 | 
			
		||||
        new = {}
 | 
			
		||||
        for (key, value) in kwargs.items():
 | 
			
		||||
            if key in CREATION_ATTRIBUTES:
 | 
			
		||||
                new[key] = value
 | 
			
		||||
            else:
 | 
			
		||||
                raise exc.InvalidAttribute()
 | 
			
		||||
        return self._create(self._path(), new)
 | 
			
		||||
 | 
			
		||||
    def delete(self, metric_collector_id):
 | 
			
		||||
        return self._delete(self._path(metric_collector_id))
 | 
			
		||||
 | 
			
		||||
    def update(self, metric_collector_id, patch):
 | 
			
		||||
        return self._update(self._path(metric_collector_id), patch)
 | 
			
		||||
							
								
								
									
										144
									
								
								watcherclient/v1/metric_collector_shell.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								watcherclient/v1/metric_collector_shell.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,144 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
# Copyright 2013 Red Hat, Inc.
 | 
			
		||||
# All Rights Reserved.
 | 
			
		||||
#
 | 
			
		||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
			
		||||
# not use this file except in compliance with the License. You may obtain
 | 
			
		||||
#    a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#         http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
#    Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
#    License for the specific language governing permissions and limitations
 | 
			
		||||
#    under the License.
 | 
			
		||||
 | 
			
		||||
# import argparse
 | 
			
		||||
 | 
			
		||||
from watcherclient.common import utils
 | 
			
		||||
from watcherclient.openstack.common import cliutils
 | 
			
		||||
from watcherclient.v1 import resource_fields as res_fields
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _print_metric_collector_show(metric_collector):
 | 
			
		||||
    fields = res_fields.METRIC_COLLECTOR_FIELDS
 | 
			
		||||
    data = dict([(f, getattr(metric_collector, f, '')) for f in fields])
 | 
			
		||||
    cliutils.print_dict(data, wrap=72)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    'metric_collector',
 | 
			
		||||
    metavar='<metric_collector>',
 | 
			
		||||
    help="UUID of the metric collector")
 | 
			
		||||
def do_metric_collector_show(cc, args):
 | 
			
		||||
    """Show detailed information about a metric collector."""
 | 
			
		||||
    metric_collector = cc.metric_collector.get(args.metric_collector)
 | 
			
		||||
    _print_metric_collector_show(metric_collector)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    '--category',
 | 
			
		||||
    metavar='<category>',
 | 
			
		||||
    help='Only show information for metric collectors with this category.')
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    '--detail',
 | 
			
		||||
    dest='detail',
 | 
			
		||||
    action='store_true',
 | 
			
		||||
    default=False,
 | 
			
		||||
    help="Show detailed information about metric collectors.")
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    '--limit',
 | 
			
		||||
    metavar='<limit>',
 | 
			
		||||
    type=int,
 | 
			
		||||
    help='Maximum number of metric collectors to return per request, '
 | 
			
		||||
         '0 for no limit. Default is the maximum number used '
 | 
			
		||||
         'by the Watcher API Service.')
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    '--sort-key',
 | 
			
		||||
    metavar='<field>',
 | 
			
		||||
    help='Metric collector field that will be used for sorting.')
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    '--sort-dir',
 | 
			
		||||
    metavar='<direction>',
 | 
			
		||||
    choices=['asc', 'desc'],
 | 
			
		||||
    help='Sort direction: "asc" (the default) or "desc".')
 | 
			
		||||
def do_metric_collector_list(cc, args):
 | 
			
		||||
    """List the metric collectors."""
 | 
			
		||||
    params = {}
 | 
			
		||||
 | 
			
		||||
    if args.detail:
 | 
			
		||||
        fields = res_fields.METRIC_COLLECTOR_FIELDS
 | 
			
		||||
        field_labels = res_fields.METRIC_COLLECTOR_FIELD_LABELS
 | 
			
		||||
    else:
 | 
			
		||||
        fields = res_fields.METRIC_COLLECTOR_SHORT_LIST_FIELDS
 | 
			
		||||
        field_labels = res_fields.METRIC_COLLECTOR_SHORT_LIST_FIELD_LABELS
 | 
			
		||||
 | 
			
		||||
    params.update(utils.common_params_for_list(args,
 | 
			
		||||
                                               fields,
 | 
			
		||||
                                               field_labels))
 | 
			
		||||
 | 
			
		||||
    metric_collector = cc.metric_collector.list(**params)
 | 
			
		||||
    cliutils.print_list(metric_collector, fields,
 | 
			
		||||
                        field_labels=field_labels,
 | 
			
		||||
                        sortby_index=None)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    '-c', '--category',
 | 
			
		||||
    metavar='<category>',
 | 
			
		||||
    required=True,
 | 
			
		||||
    help='Metric category.')
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    '-e', '--endpoint-url',
 | 
			
		||||
    required=True,
 | 
			
		||||
    metavar='<goal>',
 | 
			
		||||
    help='URL towards which publish metric data.')
 | 
			
		||||
def do_metric_collector_create(cc, args):
 | 
			
		||||
    """Create a new metric collector."""
 | 
			
		||||
    field_list = ['category', 'endpoint']
 | 
			
		||||
    fields = dict((k, v) for (k, v) in vars(args).items()
 | 
			
		||||
                  if k in field_list and not (v is None))
 | 
			
		||||
    metric_collector = cc.metric_collector.create(**fields)
 | 
			
		||||
 | 
			
		||||
    field_list.append('uuid')
 | 
			
		||||
    data = dict([(f, getattr(metric_collector, f, '')) for f in field_list])
 | 
			
		||||
    cliutils.print_dict(data, wrap=72)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    'metric_collector',
 | 
			
		||||
    metavar='<metric_collector>',
 | 
			
		||||
    nargs='+',
 | 
			
		||||
    help="UUID of the metric collector.")
 | 
			
		||||
def do_metric_collector_delete(cc, args):
 | 
			
		||||
    """Delete a metric collector."""
 | 
			
		||||
    for p in args.metric_collector:
 | 
			
		||||
        cc.metric_collector.delete(p)
 | 
			
		||||
        print ('Deleted metric collector %s' % p)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    'metric_collector',
 | 
			
		||||
    metavar='<metric_collector>',
 | 
			
		||||
    help="UUID of the metric collector.")
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    'op',
 | 
			
		||||
    metavar='<op>',
 | 
			
		||||
    choices=['add', 'replace', 'remove'],
 | 
			
		||||
    help="Operation: 'add', 'replace', or 'remove'.")
 | 
			
		||||
@cliutils.arg(
 | 
			
		||||
    'attributes',
 | 
			
		||||
    metavar='<path=value>',
 | 
			
		||||
    nargs='+',
 | 
			
		||||
    action='append',
 | 
			
		||||
    default=[],
 | 
			
		||||
    help="Attribute to add, replace, or remove. Can be specified multiple  "
 | 
			
		||||
         "times. For 'remove', only <path> is necessary.")
 | 
			
		||||
def do_metric_collector_update(cc, args):
 | 
			
		||||
    """Update information about a metric collector."""
 | 
			
		||||
    patch = utils.args_array_to_patch(args.op, args.attributes[0])
 | 
			
		||||
    metric_collector = cc.metric_collector.update(
 | 
			
		||||
        getattr(args, 'metric-collector'), patch)
 | 
			
		||||
    _print_metric_collector_show(metric_collector)
 | 
			
		||||
							
								
								
									
										84
									
								
								watcherclient/v1/resource_fields.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								watcherclient/v1/resource_fields.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,84 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
# Copyright 2015 b<>com
 | 
			
		||||
# 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.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Audit Template
 | 
			
		||||
AUDIT_TEMPLATE_FIELDS = [
 | 
			
		||||
    'uuid', 'created_at', 'updated_at', 'deleted_at',
 | 
			
		||||
    'description', 'host_aggregate', 'name',
 | 
			
		||||
    'extra', 'goal']
 | 
			
		||||
 | 
			
		||||
AUDIT_TEMPLATE_FIELD_LABELS = [
 | 
			
		||||
    'UUID', 'Created At', 'Updated At', 'Deleted At',
 | 
			
		||||
    'Description', 'Host Aggregate ID or Name', 'Name',
 | 
			
		||||
    'Extra', 'Goal Type']
 | 
			
		||||
 | 
			
		||||
AUDIT_TEMPLATE_SHORT_LIST_FIELDS = ['uuid', 'name']
 | 
			
		||||
 | 
			
		||||
AUDIT_TEMPLATE_SHORT_LIST_FIELD_LABELS = ['UUID', 'Name']
 | 
			
		||||
 | 
			
		||||
# Audit
 | 
			
		||||
AUDIT_FIELDS = ['uuid', 'created_at', 'updated_at', 'deleted_at',
 | 
			
		||||
                'deadline', 'state', 'type', 'audit_template_uuid']
 | 
			
		||||
 | 
			
		||||
AUDIT_FIELD_LABELS = ['UUID', 'Created At', 'Updated At', 'Deleted At',
 | 
			
		||||
                      'Deadline', 'State', 'Type', 'Audit Template']
 | 
			
		||||
 | 
			
		||||
AUDIT_SHORT_LIST_FIELDS = ['uuid', 'type', 'audit_template_uuid', 'state']
 | 
			
		||||
 | 
			
		||||
AUDIT_SHORT_LIST_FIELD_LABELS = ['UUID', 'Type', 'Audit Template', 'State']
 | 
			
		||||
 | 
			
		||||
# Action Plan
 | 
			
		||||
ACTION_PLAN_FIELDS = ['uuid', 'created_at', 'updated_at', 'deleted_at',
 | 
			
		||||
                      'audit_uuid', 'state']
 | 
			
		||||
 | 
			
		||||
ACTION_PLAN_FIELD_LABELS = ['UUID', 'Created At', 'Updated At', 'Deleted At',
 | 
			
		||||
                            'Audit', 'State']
 | 
			
		||||
 | 
			
		||||
ACTION_PLAN_SHORT_LIST_FIELDS = ['uuid', 'audit_uuid', 'state', 'updated_at']
 | 
			
		||||
 | 
			
		||||
ACTION_PLAN_SHORT_LIST_FIELD_LABELS = ['UUID', 'Audit', 'State', 'Updated At']
 | 
			
		||||
 | 
			
		||||
# Action
 | 
			
		||||
ACTION_FIELDS = ['uuid', 'created_at', 'updated_at', 'deleted_at', 'next_uuid',
 | 
			
		||||
                 'description', 'alarm', 'state', 'action_plan_uuid',
 | 
			
		||||
                 'action_type', 'applies_to', 'src', 'dst', 'parameter']
 | 
			
		||||
 | 
			
		||||
ACTION_FIELD_LABELS = ['UUID', 'Created At', 'Updated At', 'Deleted At',
 | 
			
		||||
                       'Next Action', 'Description', 'Alarm', 'State',
 | 
			
		||||
                       'Action Plan', 'Action',
 | 
			
		||||
                       'Applies to', 'Hypervisor Source',
 | 
			
		||||
                       'Hypervisor Destination', 'Parameter']
 | 
			
		||||
 | 
			
		||||
ACTION_SHORT_LIST_FIELDS = ['uuid', 'next_uuid',
 | 
			
		||||
                            'state', 'action_plan_uuid', 'action_type']
 | 
			
		||||
 | 
			
		||||
ACTION_SHORT_LIST_FIELD_LABELS = ['UUID', 'Next Action', 'State',
 | 
			
		||||
                                  'Action Plan', 'Action']
 | 
			
		||||
 | 
			
		||||
# Metric Collector
 | 
			
		||||
METRIC_COLLECTOR_FIELDS = ['uuid', 'created_at', 'updated_at', 'deleted_at',
 | 
			
		||||
                           'endpoint', 'category']
 | 
			
		||||
 | 
			
		||||
METRIC_COLLECTOR_FIELD_LABELS = ['UUID', 'Created At', 'Updated At',
 | 
			
		||||
                                 'Deleted At', 'Endpoint URL',
 | 
			
		||||
                                 'Metric Category']
 | 
			
		||||
 | 
			
		||||
METRIC_COLLECTOR_SHORT_LIST_FIELDS = ['uuid', 'endpoint', 'category']
 | 
			
		||||
 | 
			
		||||
METRIC_COLLECTOR_SHORT_LIST_FIELD_LABELS = ['UUID', 'Endpoint URL',
 | 
			
		||||
                                            'Metric Category']
 | 
			
		||||
							
								
								
									
										43
									
								
								watcherclient/v1/shell.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								watcherclient/v1/shell.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
			
		||||
#    not use this file except in compliance with the License. You may obtain
 | 
			
		||||
#    a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#         http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
#    Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
#    License for the specific language governing permissions and limitations
 | 
			
		||||
#    under the License.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
from watcherclient.common import utils
 | 
			
		||||
from watcherclient.v1 import action_plan_shell
 | 
			
		||||
from watcherclient.v1 import action_shell
 | 
			
		||||
from watcherclient.v1 import audit_shell
 | 
			
		||||
from watcherclient.v1 import audit_template_shell
 | 
			
		||||
# from watcherclient.v1 import metric_collector_shell
 | 
			
		||||
 | 
			
		||||
COMMAND_MODULES = [
 | 
			
		||||
    audit_template_shell,
 | 
			
		||||
    audit_shell,
 | 
			
		||||
    action_plan_shell,
 | 
			
		||||
    action_shell,
 | 
			
		||||
    # metric_collector_shell,
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def enhance_parser(parser, subparsers, cmd_mapper):
 | 
			
		||||
    """Enhance parser with API version specific options.
 | 
			
		||||
 | 
			
		||||
    Take a basic (nonversioned) parser and enhance it with
 | 
			
		||||
    commands and options specific for this version of API.
 | 
			
		||||
 | 
			
		||||
    :param parser: top level parser :param subparsers: top level
 | 
			
		||||
        parser's subparsers collection where subcommands will go
 | 
			
		||||
    """
 | 
			
		||||
    for command_module in COMMAND_MODULES:
 | 
			
		||||
        utils.define_commands_from_module(subparsers, command_module,
 | 
			
		||||
                                          cmd_mapper)
 | 
			
		||||
							
								
								
									
										18
									
								
								watcherclient/version.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								watcherclient/version.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
# Copyright 2014
 | 
			
		||||
# The Cloudscaling Group, Inc.
 | 
			
		||||
#
 | 
			
		||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
 | 
			
		||||
# use this file except in compliance with the License. You may obtain a copy
 | 
			
		||||
# of the License at
 | 
			
		||||
#
 | 
			
		||||
# http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
# Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
# distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
# See the License for the specific language governing permissions and
 | 
			
		||||
# limitations under the License.
 | 
			
		||||
 | 
			
		||||
from pbr import version
 | 
			
		||||
 | 
			
		||||
version_info = version.VersionInfo('python-watcherclient')
 | 
			
		||||
		Reference in New Issue
	
	Block a user