Retire Packaging Deb project repos

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

Change-Id: I59dd6c58e15d7d0b5b12bbc892aadfdc662305c3
This commit is contained in:
Tony Breeds 2017-09-12 16:02:12 -06:00
parent 6696c55574
commit 2c4701236a
132 changed files with 14 additions and 13965 deletions

53
.gitignore vendored
View File

@ -1,53 +0,0 @@
*.py[cod]
*.sqlite
# C extensions
*.so
# Packages
*.egg*
dist
build
.venv
.eggs
eggs
parts
bin
var
sdist
develop-eggs
.installed.cfg
lib
lib64
# Installer logs
pip-log.txt
# Unit test / coverage reports
.coverage
cover/*
.tox
nosetests.xml
functional_creds.conf
AUTHORS
ChangeLog
# Translations
*.mo
# Mr Developer
.mr.developer.cfg
.project
.pydevproject
.idea
.DS_Store
etc/mistral.conf
#swap file
*.swp
# Files created by releasenotes build
releasenotes/build
# Files created by doc build
doc/source/api

View File

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

View File

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

175
LICENSE
View File

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

14
README Normal file
View File

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

View File

@ -1,100 +0,0 @@
========================
Team and repository tags
========================
.. image:: https://governance.openstack.org/badges/python-mistralclient.svg
:target: https://governance.openstack.org/reference/tags/index.html
Mistral
=======
.. image:: https://img.shields.io/pypi/v/python-mistralclient.svg
:target: https://pypi.python.org/pypi/python-mistralclient/
:alt: Latest Version
.. image:: https://img.shields.io/pypi/dm/python-mistralclient.svg
:target: https://pypi.python.org/pypi/python-mistralclient/
:alt: Downloads
Mistral is a workflow service. Most business processes consist of multiple
distinct interconnected steps that need to be executed in a particular
order in a distributed environment. A user can describe such a process as a set
of tasks and their transitions. After that, it is possible to upload such a
description to Mistral, which will take care of state management, correct
execution order, parallelism, synchronization and high availability.
Mistral also provides flexible task scheduling so that it can run a process
according to a specified schedule (for example, every Sunday at 4.00pm) instead
of running it immediately. In Mistral terminology such a set of tasks and
relations between them is called a workflow.
Mistral client
==============
Python client for Mistral REST API. Includes python library for Mistral API and
Command Line Interface (CLI) library.
Installation
------------
First of all, clone the repo and go to the repo directory:
$ git clone git://git.openstack.org/openstack/python-mistralclient.git
$ cd python-mistralclient
Then just run:
$ pip install -e .
or
$ pip install -r requirements.txt
$ python setup.py install
Running Mistral client
----------------------
If Mistral authentication is enabled, provide the information about OpenStack
auth to environment variables. Type:
$ export OS_AUTH_URL=http://<Keystone_host>:5000/v2.0
$ export OS_USERNAME=admin
$ export OS_TENANT_NAME=tenant
$ export OS_PASSWORD=secret
$ export OS_MISTRAL_URL=http://<Mistral host>:8989/v2 (optional, by
default URL=http://localhost:8989/v2)
and in the case that you are authenticating against keystone over https:
$ export OS_CACERT=<path_to_ca_cert>
.. note:: In client, we can use both Keystone auth versions - v2.0 and v3. But
server supports only v3.*
To make sure Mistral client works, type:
$ mistral workbook-list
You can see the list of available commands typing:
$ mistral --help
Useful Links
============
* `PyPi`_ - package installation
* `Launchpad project`_ - release management
* `Blueprints`_ - feature specifications
* `Bugs`_ - issue tracking
* `Source`_
* `Specs`_
* `How to Contribute`_
.. _PyPi: https://pypi.python.org/pypi/python-mistralclient
.. _Launchpad project: https://launchpad.net/python-mistralclient
.. _Blueprints: https://blueprints.launchpad.net/python-mistralclient
.. _Bugs: https://bugs.launchpad.net/python-mistralclient
.. _Source: https://git.openstack.org/cgit/openstack/python-mistralclient
.. _How to Contribute: https://docs.openstack.org/infra/manual/developers.html
.. _Specs: https://specs.openstack.org/openstack/mistral-specs/

View File

@ -1,7 +0,0 @@
Python Mistral bindings class refrence
======================================
.. toctree::
:maxdepth: 1
api/autoindex

View File

@ -1,2 +0,0 @@
Using Mistral with KeyCloak Server
==================================

View File

@ -1,36 +0,0 @@
Using Mistral with OpenStack
============================
The **mistral** shell utility interacts with OpenStack Mistral API from the
command-line. It supports the features in the OpenStack Mistral API.
Basic Usage
-----------
In order to use the CLI, you must provide your OpenStack credentials
(for both user and project), and auth endpoint. Use the corresponding
configuration options (``--os-username``, ``--os-password``,
``--os-project-name``, ``--os-user-domain-id``, ``os-project-domain-id``, and
``--os-auth-url``), but it is easier to set them in environment variables.
.. code-block:: shell
$ export OS_AUTH_URL=http://<Keystone_host>:5000/v2.0
$ export OS_USERNAME=admin
$ export OS_TENANT_NAME=tenant
$ export OS_PASSWORD=secret
$ export OS_MISTRAL_URL=http://<Mistral host>:8989/v2
When authenticating against keystone over https:
.. code-block:: shell
$ export OS_CACERT=<path_to_ca_cert>
Once you've configured your authentication parameters, you can run **mistral**
commands. All commands take the form of::
mistral <command> [arguments...]
Run **mistral --help** to get a full list of all possible commands, and run
**mistral help <command>** to get detailed help for that command.

View File

@ -1,42 +0,0 @@
Using Mistral without Authentication
====================================
It is possible to execute a workflow on any arbitrary cloud without additional
configuration on the Mistral server side. If authentication is turned off in
the Mistral server (Pecan's `auth_enable = False` option in `mistral.conf`),
there is no need to set the `keystone_authtoken` section. It is possible to
have Mistral use an external OpenStack cloud even when it isn't deployed in
an OpenStack environment (i.e. no Keystone integration).
This setup is particularly useful when Mistral is used in standalone mode,
where the Mistral service is not part of the OpenStack cloud and runs
separately.
To enable this operation, the user can use ``--os-target-username``,
``--os-target-password``, ``--os-target-tenant-id``,
``--os-target-tenant-name``, ``--os-target-auth-token``,
``--os-target-auth-url``, ``--os-target_cacert``, and
``--os-target-region-name`` parameters.
For example, the user can return the heat stack list with this setup as shown
below:
.. code-block:: shell
$ mistral \
--os-target-auth-url=http://keystone2.example.com:5000/v3 \
--os-target-username=testuser \
--os-target-tenant=testtenant \
--os-target-password="MistralRuleZ" \
--os-mistral-url=http://mistral.example.com:8989/v2 \
run-action heat.stacks_list
The OS-TARGET-* parameters can be set in environment variables as:
.. code-block:: shell
$ export OS_TARGET_AUTH_URL=http://keystone2.example.com:5000/v3
$ export OS_TARGET_USERNAME=admin
$ export OS_TARGET_TENANT_NAME=tenant
$ export OS_TARGET_PASSWORD=secret
$ export OS_TARGET_REGION_NAME=region

View File

@ -1,111 +0,0 @@
# Mistral documentation build configuration file
import os
import pbr.version
import sys
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath('../../'))
sys.path.insert(0, os.path.abspath('../'))
sys.path.insert(0, os.path.abspath('./'))
# -- General configuration ---------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [
'sphinx.ext.autodoc',
'openstackdocstheme',
]
# Add any paths that contain templates here, relative to this directory.
# 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'Mistral Client'
copyright = u'2016, Mistral Contributors'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
version_info = pbr.version.VersionInfo('python-mistralclient')
release = version_info.release_string()
version = version_info.version_string()
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = []
# If true, '()' will be appended to :func: etc. cross-reference text.
add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
modindex_common_prefix = ['mistralclient.']
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'openstackdocs'
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
# html_last_updated_fmt = '%b %d, %Y'
# Must set this variable to include year, month, day, hours, and minutes.
html_last_updated_fmt = '%Y-%m-%d %H:%M'
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
html_title = 'MistralClient'
# Custom sidebar templates, maps document names to template names.
html_sidebars = {
'index': [
'sidebarlinks.html', 'localtoc.html', 'searchbox.html',
'sourcelink.html'
],
'**': [
'localtoc.html', 'relations.html',
'searchbox.html', 'sourcelink.html'
]
}
# Output file base name for HTML help builder.
htmlhelp_basename = 'Mistraldoc'
# -- Options for manual page output ------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'mistral_client', u'Mistral Client Documentation',
[u'Mistral Contributors'], 1)
]
# -- Options for openstackdocstheme -------------------------------------------
repository_name = 'openstack/python-mistralclient'
bug_project = 'python-mistralclient'
bug_tag = ''

View File

@ -1,40 +0,0 @@
Python bindings to the OpenStack Workflow API
=============================================
This is a client for OpenStack Mistral API. There's a Python API
(the :mod:`mistralclient` module), and a command-line script
(installed as :program:`mistral`).
Using mistralclient
-------------------
.. toctree::
:maxdepth: 2
cli/cli_usage_with_openstack
cli/cli_usage_with_keycloak
cli/cli_usage_without_auth
class_reference
For information about using the mistral command-line client, see
`Workflow service command-line client`_.
.. _Workflow service command-line client: https://docs.openstack.org/mistral/latest/cli/index.html
Python API Reference
--------------------
* `REST API Specification`_
.. _REST API Specification: https://docs.openstack.org/mistral/latest/api/v2.html
Contributing
------------
Code is hosted `on GitHub`_. Submit bugs to the python-mistralclient project on
`Launchpad`_. Submit code to the openstack/python-mistralclient project
using `Gerrit`_.
.. _on GitHub: https://github.com/openstack/python-mistralclient
.. _Launchpad: https://launchpad.net/python-mistralclient
.. _Gerrit: https://docs.openstack.org/infra/manual/developers.html#development-workflow

View File

@ -1,12 +0,0 @@
# Credentials for functional testing
[auth]
uri = http://10.42.0.50:5000/v2.0
[admin]
user = admin
tenant = admin
pass = secrete
[demo]
user = demo
tenant = demo
pass = demo_secret

View File

@ -1,26 +0,0 @@
#!/bin/bash
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
# This script is executed inside post_test_hook function in devstack gate.
RETVAL=0
sudo chmod -R a+rw /opt/stack/new/
cd /opt/stack/new/python-mistralclient
echo "Running CLI python-mistralclient tests"
./functionaltests/run_tests.sh
RETVAL=$?
exit $RETVAL

View File

@ -1,20 +0,0 @@
---
version: "2.0"
greeting:
description: "This action says 'Hello'"
base: std.echo
base-input:
output: 'Hello, <% $.name %>'
input:
- name
output:
string: <% $.output %>
farewell:
base: std.echo
base-input:
output: 'Bye!'
output:
info: <% $.output %>

View File

@ -1,13 +0,0 @@
---
version: "2.0"
greeting:
description: "This action says 'Hello'"
tags: [tag, tag1]
base: std.echo
base-input:
output: 'Hello, <% $.name %>'
input:
- name
output:
string: <% $.output %>

View File

@ -1,14 +0,0 @@
---
version: '2.0'
name: wb
workflows:
wf1:
type: direct
tasks:
hello:
action: std.echo output="Hello"
publish:
result: <% task(hello).result %>

View File

@ -1,15 +0,0 @@
---
version: '2.0'
name: wb
tags: [tag]
workflows:
wf1:
type: direct
tasks:
hello:
action: std.echo output="Hello"
publish:
result: <% task(hello).result %>

View File

@ -1,20 +0,0 @@
---
version: '2.0'
wf:
type: direct
tags: [tag]
tasks:
hello:
action: std.echo output="Hello"
publish:
result: <% task(hello).result %>
wait-after: 1
on-success:
- task2
task2:
action: std.echo output="Task 2"
publish:
task2: <% task(task2).result %>

View File

@ -1,11 +0,0 @@
---
version: '2.0'
wf_single:
type: direct
tasks:
hello:
action: std.echo output="Hello"
publish:
result: <% task(hello).result %>

View File

@ -1,28 +0,0 @@
---
version: '2.0'
wf:
type: direct
tasks:
hello:
action: std.echo output="Hello"
wait-before: 5
publish:
result: <% task(hello).result %>
wf1:
type: reverse
tags: [tag]
input:
- farewell
tasks:
addressee:
action: std.echo output="John"
publish:
name: <% task(addressee).result %>
goodbye:
action: std.echo output="<% $.farewell %>, <% $.name %>"
requires: [addressee]

View File

@ -1,8 +0,0 @@
---
version: '2.0'
wrapping_wf:
type: direct
tasks:
hello:
workflow: wf

View File

@ -1,58 +0,0 @@
#!/bin/bash
#
# 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.
# How many seconds to wait for the API to be responding before giving up
API_RESPONDING_TIMEOUT=20
if ! timeout ${API_RESPONDING_TIMEOUT} sh -c "until curl --output /dev/null --silent --head --fail http://localhost:8989; do sleep 1; done"; then
echo "Mistral API failed to respond within ${API_RESPONDING_TIMEOUT} seconds"
exit 1
fi
echo "Successfully contacted Mistral API"
export BASE=/opt/stack
export MISTRALCLIENT_DIR="$BASE/new/python-mistralclient"
# Get demo credentials.
cd ${BASE}/new/devstack
source openrc alt_demo alt_demo
export OS_ALT_USERNAME=${OS_USERNAME}
export OS_ALT_TENANT_NAME=${OS_TENANT_NAME}
export OS_ALT_PASSWORD=${OS_PASSWORD}
# Get admin credentials.
source openrc admin admin
# Store these credentials into the config file.
CREDS_FILE=${MISTRALCLIENT_DIR}/functional_creds.conf
cat <<EOF > ${CREDS_FILE}
# Credentials for functional testing
[auth]
uri = $OS_AUTH_URL
[admin]
user = $OS_USERNAME
tenant = $OS_TENANT_NAME
pass = $OS_PASSWORD
[demo]
user = $OS_ALT_USERNAME
tenant = $OS_ALT_TENANT_NAME
pass = $OS_ALT_PASSWORD
EOF
cd $MISTRALCLIENT_DIR
# Run tests
tox -efunctional -- nosetests -sv mistralclient/tests/functional

View File

@ -1,19 +0,0 @@
# Copyright 2014 Rackspace Hosting
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import pbr.version
__version__ = pbr.version.VersionInfo(
'python-mistralclient').version_string()

View File

@ -1,164 +0,0 @@
# Copyright 2013 - Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import copy
import json
class Resource(object):
resource_name = 'Something'
defaults = {}
def __init__(self, manager, data):
self.manager = manager
self._data = data
self._set_defaults()
self._set_attributes()
def _set_defaults(self):
for k, v in self.defaults.items():
if k not in self._data:
self._data[k] = v
def _set_attributes(self):
for k, v in self._data.items():
try:
setattr(self, k, v)
except AttributeError:
# In this case we already defined the attribute on the class
pass
def to_dict(self):
return copy.deepcopy(self._data)
def __str__(self):
vals = ", ".join(["%s='%s'" % (n, v)
for n, v in self._data.items()])
return "%s [%s]" % (self.resource_name, vals)
def _check_items(obj, searches):
try:
return all(getattr(obj, attr) == value for (attr, value) in searches)
except AttributeError:
return False
def extract_json(response, response_key):
if response_key is not None:
return get_json(response)[response_key]
else:
return get_json(response)
class ResourceManager(object):
resource_class = None
def __init__(self, http_client):
self.http_client = http_client
def find(self, **kwargs):
return [i for i in self.list() if _check_items(i, kwargs.items())]
def _ensure_not_empty(self, **kwargs):
for name, value in kwargs.items():
if value is None or (isinstance(value, str) and len(value) == 0):
raise APIException(
400,
'%s is missing field "%s"' %
(self.resource_class.__name__, name)
)
def _copy_if_defined(self, data, **kwargs):
for name, value in kwargs.items():
if value is not None:
data[name] = value
def _create(self, url, data, response_key=None, dump_json=True):
if dump_json:
data = json.dumps(data)
resp = self.http_client.post(url, data)
if resp.status_code != 201:
self._raise_api_exception(resp)
return self.resource_class(self, extract_json(resp, response_key))
def _update(self, url, data, response_key=None, dump_json=True):
if dump_json:
data = json.dumps(data)
resp = self.http_client.put(url, data)
if resp.status_code != 200:
self._raise_api_exception(resp)
return self.resource_class(self, extract_json(resp, response_key))
def _list(self, url, response_key=None):
resp = self.http_client.get(url)
if resp.status_code != 200:
self._raise_api_exception(resp)
return [self.resource_class(self, resource_data)
for resource_data in extract_json(resp, response_key)]
def _get(self, url, response_key=None):
resp = self.http_client.get(url)
if resp.status_code == 200:
return self.resource_class(self, extract_json(resp, response_key))
else:
self._raise_api_exception(resp)
def _delete(self, url):
resp = self.http_client.delete(url)
if resp.status_code != 204:
self._raise_api_exception(resp)
def _plurify_resource_name(self):
return self.resource_class.resource_name + 's'
def _raise_api_exception(self, resp):
try:
error_data = (resp.headers.get("Server-Error-Message", None) or
get_json(resp).get("faultstring"))
except ValueError:
error_data = resp.content
raise APIException(error_code=resp.status_code,
error_message=error_data)
def get_json(response):
"""Gets JSON representation of response.
This method provided backward compatibility with old versions
of requests library.
"""
json_field_or_function = getattr(response, 'json', None)
if callable(json_field_or_function):
return response.json()
else:
return json.loads(response.content)
class APIException(Exception):
def __init__(self, error_code=None, error_message=None):
super(APIException, self).__init__(error_message)
self.error_code = error_code
self.error_message = error_message

View File

@ -1,26 +0,0 @@
# Copyright 2013 - Mirantis, 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 mistralclient.api.v2 import client as client_v2
def client(auth_type='keystone', **kwargs):
return client_v2.Client(auth_type=auth_type, **kwargs)
def determine_client_version(mistral_version):
if mistral_version.find("v2") != -1:
return 2
raise RuntimeError("Cannot determine mistral API version")

View File

@ -1,203 +0,0 @@
# Copyright 2013 - Mirantis, Inc.
# Copyright 2016 - StackStorm, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import base64
import copy
import os
from oslo_utils import importutils
import requests
import logging
AUTH_TOKEN = 'auth_token'
CACERT = 'cacert'
CERT_FILE = 'cert'
CERT_KEY = 'key'
INSECURE = 'insecure'
PROJECT_ID = 'project_id'
USER_ID = 'user_id'
REGION_NAME = 'region_name'
TARGET_AUTH_TOKEN = 'target_auth_token'
TARGET_AUTH_URI = 'target_auth_url'
TARGET_PROJECT_ID = 'target_project_id'
TARGET_USER_ID = 'target_user_id'
TARGET_INSECURE = 'target_insecure'
TARGET_SERVICE_CATALOG = 'target_service_catalog'
TARGET_REGION_NAME = 'target_region_name'
TARGET_USER_DOMAIN_NAME = 'target_user_domain_name'
TARGET_PROJECT_DOMAIN_NAME = 'target_project_domain_name'
osprofiler_web = importutils.try_import("osprofiler.web")
LOG = logging.getLogger(__name__)
def log_request(func):
def decorator(self, *args, **kwargs):
resp = func(self, *args, **kwargs)
LOG.debug("HTTP %s %s %d", resp.request.method, resp.url,
resp.status_code)
return resp
return decorator
class HTTPClient(object):
def __init__(self, base_url, **kwargs):
self.base_url = base_url
self.session = kwargs.pop('session', None)
self.auth_token = kwargs.get(AUTH_TOKEN)
self.project_id = kwargs.get(PROJECT_ID)
self.user_id = kwargs.get(USER_ID)
self.cacert = kwargs.get(CACERT)
self.insecure = kwargs.get(INSECURE, False)
self.region_name = kwargs.get(REGION_NAME)
self.ssl_options = {}
self.target_auth_token = kwargs.get(TARGET_AUTH_TOKEN)
self.target_auth_uri = kwargs.get(TARGET_AUTH_URI)
self.target_user_id = kwargs.get(TARGET_USER_ID)
self.target_project_id = kwargs.get(TARGET_PROJECT_ID)
self.target_service_catalog = kwargs.get(TARGET_SERVICE_CATALOG)
self.target_region_name = kwargs.get(TARGET_REGION_NAME)
self.target_insecure = kwargs.get(TARGET_INSECURE)
self.target_user_domain_name = kwargs.get(TARGET_USER_DOMAIN_NAME)
self.target_project_domain_name = kwargs.get(
TARGET_PROJECT_DOMAIN_NAME
)
if self.session:
self.crud_provider = self.session
else:
self.crud_provider = requests
if self.base_url.startswith('https'):
if self.cacert and not os.path.exists(self.cacert):
raise ValueError('Unable to locate cacert file '
'at %s.' % self.cacert)
if self.cacert and self.insecure:
LOG.warning('Client is set to not verify even though '
'cacert is provided.')
# These are already set by the session, so it's not needed
if not self.session:
if self.insecure:
self.ssl_options['verify'] = False
else:
if self.cacert:
self.ssl_options['verify'] = self.cacert
else:
self.ssl_options['verify'] = True
self.ssl_options['cert'] = (
kwargs.get(CERT_FILE),
kwargs.get(CERT_KEY)
)
@log_request
def get(self, url, headers=None):
options = self._get_request_options('get', headers)
return self.crud_provider.get(self.base_url + url, **options)
@log_request
def post(self, url, body, headers=None):
options = self._get_request_options('post', headers)
return self.crud_provider.post(self.base_url + url,
data=body, **options)
@log_request
def put(self, url, body, headers=None):
options = self._get_request_options('put', headers)
return self.crud_provider.put(self.base_url + url,
data=body, **options)
@log_request
def delete(self, url, headers=None):
options = self._get_request_options('delete', headers)
return self.crud_provider.delete(self.base_url + url,
**options)
def _get_request_options(self, method, headers):
headers = self._update_headers(headers)
if method in ['post', 'put']:
content_type = headers.get('content-type', 'application/json')
headers['content-type'] = content_type
options = copy.deepcopy(self.ssl_options)
options['headers'] = headers
return options
def _update_headers(self, headers):
if not headers:
headers = {}
if not self.session:
if self.auth_token:
headers['x-auth-token'] = self.auth_token
if self.project_id:
headers['X-Project-Id'] = self.project_id
if self.user_id:
headers['X-User-Id'] = self.user_id
if self.region_name:
headers['X-Region-Name'] = self.region_name
if self.target_auth_token:
headers['X-Target-Auth-Token'] = self.target_auth_token
if self.target_auth_uri:
headers['X-Target-Auth-Uri'] = self.target_auth_uri
if self.target_project_id:
headers['X-Target-Project-Id'] = self.target_project_id
if self.target_user_id:
headers['X-Target-User-Id'] = self.target_user_id
if self.target_insecure:
headers['X-Target-Insecure'] = self.target_insecure
if self.target_region_name:
headers['X-Target-Region-Name'] = self.target_region_name
if self.target_user_domain_name:
headers['X-Target-User-Domain-Name'] = self.target_user_domain_name
if self.target_project_domain_name:
h_name = 'X-Target-Project-Domain-Name'
headers[h_name] = self.target_project_domain_name
if self.target_service_catalog:
headers['X-Target-Service-Catalog'] = base64.b64encode(
self.target_service_catalog.encode('utf-8')
)
if osprofiler_web:
# Add headers for osprofiler.
headers.update(osprofiler_web.get_trace_id_headers())
return headers

View File

@ -1,87 +0,0 @@
# Copyright 2014 - Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
from mistralclient.api import base
class ActionExecution(base.Resource):
resource_name = 'ActionExecution'
class ActionExecutionManager(base.ResourceManager):
resource_class = ActionExecution
def create(self, name, input=None, **params):
self._ensure_not_empty(name=name)
data = {'name': name}
if input:
data['input'] = json.dumps(input)
if params:
data['params'] = json.dumps(params)
resp = self.http_client.post(
'/action_executions',
json.dumps(data)
)
if resp.status_code != 201:
self._raise_api_exception(resp)
return self.resource_class(self, base.get_json(resp))
def update(self, id, state=None, output=None):
self._ensure_not_empty(id=id)
if not (state or output):
raise base.APIException(
400,
"Please provide either state or output for action execution."
)
data = {}
if state:
data['state'] = state
if output:
data['output'] = output
return self._update('/action_executions/%s' % id, data)
def list(self, task_execution_id=None, limit=None):
url = '/action_executions'
qparams = {}
if task_execution_id:
url = '/tasks/%s/action_executions' % task_execution_id
if limit:
qparams['limit'] = limit
return self._list(url, response_key='action_executions')
def get(self, id):
self._ensure_not_empty(id=id)
return self._get('/action_executions/%s' % id)
def delete(self, id):
self._ensure_not_empty(id=id)
self._delete('/action_executions/%s' % id)

View File

@ -1,123 +0,0 @@
# Copyright 2014 - Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import six
from mistralclient.api import base
from mistralclient import utils
urlparse = six.moves.urllib.parse
class Action(base.Resource):
resource_name = 'Action'
class ActionManager(base.ResourceManager):
resource_class = Action
def create(self, definition, scope='private'):
self._ensure_not_empty(definition=definition)
# If the specified definition is actually a file, read in the
# definition file
definition = utils.get_contents_if_file(definition)
resp = self.http_client.post(
'/actions?scope=%s' % scope,
definition,
headers={'content-type': 'text/plain'}
)
if resp.status_code != 201:
self._raise_api_exception(resp)
return [self.resource_class(self, resource_data)
for resource_data in base.extract_json(resp, 'actions')]
def update(self, definition, scope='private', id=None):
self._ensure_not_empty(definition=definition)
url_pre = ('/actions/%s' % id) if id else '/actions'
# If the specified definition is actually a file, read in the
# definition file
definition = utils.get_contents_if_file(definition)
resp = self.http_client.put(
'%s?scope=%s' % (url_pre, scope),
definition,
headers={'content-type': 'text/plain'}
)
if resp.status_code != 200:
self._raise_api_exception(resp)
return [self.resource_class(self, resource_data)
for resource_data in base.extract_json(resp, 'actions')]
def list(self, marker='', limit=None, sort_keys='', sort_dirs='',
**filters):
qparams = {}
if marker:
qparams['marker'] = marker
if limit:
qparams['limit'] = limit
if sort_keys:
qparams['sort_keys'] = sort_keys
if sort_dirs:
qparams['sort_dirs'] = sort_dirs
for name, val in filters.items():
qparams[name] = val
query_string = ("?%s" % urlparse.urlencode(list(qparams.items()))
if qparams else "")
return self._list(
'/actions%s' % query_string,
response_key='actions',
)
def get(self, identifier):
self._ensure_not_empty(identifier=identifier)
return self._get('/actions/%s' % identifier)
def delete(self, identifier):
self._ensure_not_empty(identifier=identifier)
self._delete('/actions/%s' % identifier)
def validate(self, definition):
self._ensure_not_empty(definition=definition)
# If the specified definition is actually a file, read in the
# definition file
definition = utils.get_contents_if_file(definition)
resp = self.http_client.post(
'/actions/validate',
definition,
headers={'content-type': 'text/plain'}
)
if resp.status_code != 200:
self._raise_api_exception(resp)
return base.extract_json(resp, None)

View File

@ -1,82 +0,0 @@
# Copyright 2014 - Mirantis, Inc.
# Copyright 2015 - StackStorm, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import copy
import six
from oslo_utils import importutils
from mistralclient.api import httpclient
from mistralclient.api.v2 import action_executions
from mistralclient.api.v2 import actions
from mistralclient.api.v2 import cron_triggers
from mistralclient.api.v2 import environments
from mistralclient.api.v2 import event_triggers
from mistralclient.api.v2 import executions
from mistralclient.api.v2 import members
from mistralclient.api.v2 import services
from mistralclient.api.v2 import tasks
from mistralclient.api.v2 import workbooks
from mistralclient.api.v2 import workflows
from mistralclient import auth
osprofiler_profiler = importutils.try_import("osprofiler.profiler")
_DEFAULT_MISTRAL_URL = "http://localhost:8989/v2"
class Client(object):
def __init__(self, auth_type='keystone', **kwargs):
# We get the session at this point, as some instances of session
# objects might have mutexes that can't be deep-copied.
session = kwargs.pop('session', None)
req = copy.deepcopy(kwargs)
mistral_url = req.get('mistral_url')
profile = req.get('profile')
if mistral_url and not isinstance(mistral_url, six.string_types):
raise RuntimeError('Mistral url should be a string.')
auth_handler = auth.get_auth_handler(auth_type)
auth_response = auth_handler.authenticate(req, session=session) or {}
req.update(auth_response)
mistral_url = auth_response.get('mistral_url') or mistral_url
if not mistral_url:
mistral_url = _DEFAULT_MISTRAL_URL
if profile:
osprofiler_profiler.init(profile)
http_client = httpclient.HTTPClient(mistral_url, session=session,
**req)
# Create all resource managers.
self.workbooks = workbooks.WorkbookManager(http_client)
self.executions = executions.ExecutionManager(http_client)
self.tasks = tasks.TaskManager(http_client)
self.actions = actions.ActionManager(http_client)
self.workflows = workflows.WorkflowManager(http_client)
self.cron_triggers = cron_triggers.CronTriggerManager(http_client)
self.event_triggers = event_triggers.EventTriggerManager(http_client)
self.environments = environments.EnvironmentManager(http_client)
self.action_executions = action_executions.ActionExecutionManager(
http_client)
self.services = services.ServiceManager(http_client)
self.members = members.MemberManager(http_client)

View File

@ -1,68 +0,0 @@
# Copyright 2014 - Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
from oslo_utils import uuidutils
from mistralclient.api import base
class CronTrigger(base.Resource):
resource_name = 'CronTrigger'
class CronTriggerManager(base.ResourceManager):
resource_class = CronTrigger
def create(self, name, workflow_identifier, workflow_input=None,
workflow_params=None, pattern=None,
first_time=None, count=None):
self._ensure_not_empty(
name=name,
workflow_identifier=workflow_identifier
)
data = {
'name': name,
'pattern': pattern,
'first_execution_time': first_time,
'remaining_executions': count
}
if uuidutils.is_uuid_like(workflow_identifier):
data.update({'workflow_id': workflow_identifier})
else:
data.update({'workflow_name': workflow_identifier})
if workflow_input:
data.update({'workflow_input': json.dumps(workflow_input)})
if workflow_params:
data.update({'workflow_params': json.dumps(workflow_params)})
return self._create('/cron_triggers', data)
def list(self):
return self._list('/cron_triggers', response_key='cron_triggers')
def get(self, name):
self._ensure_not_empty(name=name)
return self._get('/cron_triggers/%s' % name)
def delete(self, name):
self._ensure_not_empty(name=name)
self._delete('/cron_triggers/%s' % name)

View File

@ -1,85 +0,0 @@
# Copyright 2015 - StackStorm, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
import six
from mistralclient.api import base
from mistralclient import utils
class Environment(base.Resource):
resource_name = 'Environment'
def _set_attributes(self):
"""Override loading of the "variables" attribute from text to dict."""
for k, v in self._data.items():
if k == 'variables' and isinstance(v, six.string_types):
v = json.loads(v)
try:
setattr(self, k, v)
except AttributeError:
# In this case we already defined the attribute on the class
pass
class EnvironmentManager(base.ResourceManager):
resource_class = Environment
def create(self, **kwargs):
# Check to see if the file name or URI is being passed in. If so,
# read it's contents first.
if 'file' in kwargs:
file = kwargs['file']
kwargs = utils.load_content(utils.get_contents_if_file(file))
self._ensure_not_empty(name=kwargs.get('name', None),
variables=kwargs.get('variables', None))
# Convert dict to text for the variables attribute.
if isinstance(kwargs['variables'], dict):
kwargs['variables'] = json.dumps(kwargs['variables'])
return self._create('/environments', kwargs)
def update(self, **kwargs):
# Check to see if the file name or URI is being passed in. If so,
# read it's contents first.
if 'file' in kwargs:
file = kwargs['file']
kwargs = utils.load_content(utils.get_contents_if_file(file))
name = kwargs.get('name', None)
self._ensure_not_empty(name=name)
# Convert dict to text for the variables attribute.
if kwargs.get('variables') and isinstance(kwargs['variables'], dict):
kwargs['variables'] = json.dumps(kwargs['variables'])
return self._update('/environments', kwargs)
def list(self):
return self._list('/environments', response_key='environments')
def get(self, name):
self._ensure_not_empty(name=name)
return self._get('/environments/%s' % name)
def delete(self, name):
self._ensure_not_empty(name=name)
self._delete('/environments/%s' % name)

View File

@ -1,61 +0,0 @@
# Copyright 2017, OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
from mistralclient.api import base
class EventTrigger(base.Resource):
resource_name = 'EventTrigger'
class EventTriggerManager(base.ResourceManager):
resource_class = EventTrigger
def create(self, name, workflow_id, exchange, topic, event,
workflow_input=None, workflow_params=None):
self._ensure_not_empty(
name=name,
workflow_id=workflow_id
)
data = {
'workflow_id': workflow_id,
'name': name,
'exchange': exchange,
'topic': topic,
'event': event
}
if workflow_input:
data.update({'workflow_input': json.dumps(workflow_input)})
if workflow_params:
data.update({'workflow_params': json.dumps(workflow_params)})
return self._create('/event_triggers', data)
def list(self):
return self._list('/event_triggers', response_key='event_triggers')
def get(self, id):
self._ensure_not_empty(id=id)
return self._get('/event_triggers/%s' % id)
def delete(self, id):
self._ensure_not_empty(id=id)
self._delete('/event_triggers/%s' % id)

View File

@ -1,110 +0,0 @@
# Copyright 2014 - Mirantis, Inc.
# Copyright 2015 - StackStorm, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
from oslo_utils import uuidutils
import six
from mistralclient.api import base
urlparse = six.moves.urllib.parse
class Execution(base.Resource):
resource_name = 'Execution'
class ExecutionManager(base.ResourceManager):
resource_class = Execution
def create(self, workflow_identifier, workflow_input=None, description='',
**params):
self._ensure_not_empty(workflow_identifier=workflow_identifier)
data = {
'description': description
}
if uuidutils.is_uuid_like(workflow_identifier):
data.update({'workflow_id': workflow_identifier})
else:
data.update({'workflow_name': workflow_identifier})
if workflow_input:
if isinstance(workflow_input, six.string_types):
data.update({'input': workflow_input})
else:
data.update({'input': json.dumps(workflow_input)})
if params:
data.update({'params': json.dumps(params)})
return self._create('/executions', data)
def update(self, id, state, description=None, env=None):
data = {}
if state:
data['state'] = state
if description:
data['description'] = description
if env:
data['params'] = {'env': env}
return self._update('/executions/%s' % id, data)
def list(self, task=None, marker='', limit=None, sort_keys='',
sort_dirs='', **filters):
qparams = {}
if task:
qparams['task_execution_id'] = task
if marker:
qparams['marker'] = marker
if limit:
qparams['limit'] = limit
if sort_keys:
qparams['sort_keys'] = sort_keys
if sort_dirs:
qparams['sort_dirs'] = sort_dirs
for name, val in filters.items():
qparams[name] = val
query_string = ("?%s" % urlparse.urlencode(list(qparams.items()))
if qparams else "")
return self._list(
'/executions%s' % query_string,
response_key='executions',
)
def get(self, id):
self._ensure_not_empty(id=id)
return self._get('/executions/%s' % id)
def delete(self, id):
self._ensure_not_empty(id=id)
self._delete('/executions/%s' % id)

View File

@ -1,76 +0,0 @@
# Copyright 2016 - Catalyst IT Limited
#
# 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 mistralclient.api import base
class Member(base.Resource):
resource_name = 'Member'
class MemberManager(base.ResourceManager):
resource_class = Member
def create(self, resource_id, resource_type, member_id):
self._ensure_not_empty(
resource_id=resource_id,
resource_type=resource_type,
member_id=member_id
)
data = {
'member_id': member_id,
}
url = '/%ss/%s/members' % (resource_type, resource_id)
return self._create(url, data)
def update(self, resource_id, resource_type, member_id='',
status='accepted'):
if not member_id:
member_id = self.http_client.project_id
url = '/%ss/%s/members/%s' % (resource_type, resource_id, member_id)
return self._update(url, {'status': status})
def list(self, resource_id, resource_type):
url = '/%ss/%s/members' % (resource_type, resource_id)
return self._list(url, response_key='members')
def get(self, resource_id, resource_type, member_id=None):
self._ensure_not_empty(
resource_id=resource_id,
resource_type=resource_type,
)
if not member_id:
member_id = self.http_client.project_id
url = '/%ss/%s/members/%s' % (resource_type, resource_id, member_id)
return self._get(url)
def delete(self, resource_id, resource_type, member_id):
self._ensure_not_empty(
resource_id=resource_id,
resource_type=resource_type,
member_id=member_id
)
url = '/%ss/%s/members/%s' % (resource_type, resource_id, member_id)
self._delete(url)

View File

@ -1,26 +0,0 @@
# Copyright 2015 Huawei Technologies Co., Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from mistralclient.api import base
class Service(base.Resource):
resource_name = 'Service'
class ServiceManager(base.ResourceManager):
resource_class = Service
def list(self):
return self._list('/services', response_key='services')

View File

@ -1,79 +0,0 @@
# Copyright 2014 - Mirantis, Inc.
# Copyright 2015 - StackStorm, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
import six
from mistralclient.api import base
urlparse = six.moves.urllib.parse
class Task(base.Resource):
resource_name = 'Task'
class TaskManager(base.ResourceManager):
resource_class = Task
def list(self, workflow_execution_id=None, marker='', limit=None,
sort_keys='', sort_dirs='', **filters):
url = '/tasks'
if workflow_execution_id:
url = '/executions/%s/tasks' % workflow_execution_id
url += '%s'
qparams = {}
if marker:
qparams['marker'] = marker
if limit:
qparams['limit'] = limit
if sort_keys:
qparams['sort_keys'] = sort_keys
if sort_dirs:
qparams['sort_dirs'] = sort_dirs
for name, val in filters.items():
qparams[name] = val
query_string = ("?%s" % urlparse.urlencode(list(qparams.items()))
if qparams else "")
return self._list(url % query_string, response_key='tasks')
def get(self, id):
self._ensure_not_empty(id=id)
return self._get('/tasks/%s' % id)
def rerun(self, task_ex_id, reset=True, env=None):
url = '/tasks/%s' % task_ex_id
body = {
'id': task_ex_id,
'state': 'RUNNING',
'reset': reset
}
if env:
body['env'] = json.dumps(env)
return self._update(url, body)

View File

@ -1,92 +0,0 @@
# Copyright 2014 - Mirantis, Inc.
# Copyright 2015 - StackStorm, 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 mistralclient.api import base
from mistralclient import utils
class Workbook(base.Resource):
resource_name = 'Workbook'
class WorkbookManager(base.ResourceManager):
resource_class = Workbook
def create(self, definition):
self._ensure_not_empty(definition=definition)
# If the specified definition is actually a file, read in the
# definition file
definition = utils.get_contents_if_file(definition)
resp = self.http_client.post(
'/workbooks',
definition,
headers={'content-type': 'text/plain'}
)
if resp.status_code != 201:
self._raise_api_exception(resp)
return self.resource_class(self, base.extract_json(resp, None))
def update(self, definition):
self._ensure_not_empty(definition=definition)
# If the specified definition is actually a file, read in the
# definition file
definition = utils.get_contents_if_file(definition)
resp = self.http_client.put(
'/workbooks',
definition,
headers={'content-type': 'text/plain'}
)
if resp.status_code != 200:
self._raise_api_exception(resp)
return self.resource_class(self, base.extract_json(resp, None))
def list(self):
return self._list('/workbooks', response_key='workbooks')
def get(self, name):
self._ensure_not_empty(name=name)
return self._get('/workbooks/%s' % name)
def delete(self, name):
self._ensure_not_empty(name=name)
self._delete('/workbooks/%s' % name)
def validate(self, definition):
self._ensure_not_empty(definition=definition)
# If the specified definition is actually a file, read in the
# definition file
definition = utils.get_contents_if_file(definition)
resp = self.http_client.post(
'/workbooks/validate',
definition,
headers={'content-type': 'text/plain'}
)
if resp.status_code != 200:
self._raise_api_exception(resp)
return base.extract_json(resp, None)

View File

@ -1,128 +0,0 @@
# Copyright 2014 - Mirantis, Inc.
# Copyright 2015 - StackStorm, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import six
from mistralclient.api import base
from mistralclient import utils
urlparse = six.moves.urllib.parse
class Workflow(base.Resource):
resource_name = 'Workflow'
class WorkflowManager(base.ResourceManager):
resource_class = Workflow
def create(self, definition, scope='private'):
self._ensure_not_empty(definition=definition)
# If the specified definition is actually a file, read in the
# definition file
definition = utils.get_contents_if_file(definition)
resp = self.http_client.post(
'/workflows?scope=%s' % scope,
definition,
headers={'content-type': 'text/plain'}
)
if resp.status_code != 201:
self._raise_api_exception(resp)
return [self.resource_class(self, resource_data)
for resource_data in base.extract_json(resp, 'workflows')]
def update(self, definition, scope='private', id=None):
self._ensure_not_empty(definition=definition)
url_pre = ('/workflows/%s' % id) if id else '/workflows'
# If the specified definition is actually a file, read in the
# definition file
definition = utils.get_contents_if_file(definition)
resp = self.http_client.put(
'%s?scope=%s' % (url_pre, scope),
definition,
headers={'content-type': 'text/plain'}
)
if resp.status_code != 200:
self._raise_api_exception(resp)
if id:
return self.resource_class(self, base.extract_json(resp, None))
return [self.resource_class(self, resource_data)
for resource_data in base.extract_json(resp, 'workflows')]
def list(self, marker='', limit=None, sort_keys='', sort_dirs='',
**filters):
qparams = {}
if marker:
qparams['marker'] = marker
if limit:
qparams['limit'] = limit
if sort_keys:
qparams['sort_keys'] = sort_keys
if sort_dirs:
qparams['sort_dirs'] = sort_dirs
for name, val in filters.items():
qparams[name] = val
query_string = ("?%s" % urlparse.urlencode(list(qparams.items()))
if qparams else "")
return self._list(
'/workflows%s' % query_string,
response_key='workflows',
)
def get(self, identifier):
self._ensure_not_empty(identifier=identifier)
return self._get('/workflows/%s' % identifier)
def delete(self, identifier):
self._ensure_not_empty(identifier=identifier)
self._delete('/workflows/%s' % identifier)
def validate(self, definition):
self._ensure_not_empty(definition=definition)
# If the specified definition is actually a file, read in the
# definition file
definition = utils.get_contents_if_file(definition)
resp = self.http_client.post(
'/workflows/validate',
definition,
headers={'content-type': 'text/plain'}
)
if resp.status_code != 200:
self._raise_api_exception(resp)
return base.extract_json(resp, None)

View File

@ -1,37 +0,0 @@
# Copyright 2016 - Brocade Communications Systems, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import abc
import six
from stevedore import driver
def get_auth_handler(auth_type):
mgr = driver.DriverManager(
'mistralclient.auth',
auth_type,
invoke_on_load=True
)
return mgr.driver
@six.add_metaclass(abc.ABCMeta)
class AuthHandler(object):
"""Abstract base class for an authentication plugin."""
@abc.abstractmethod
def authenticate(self, req):
raise NotImplementedError()

View File

@ -1,21 +0,0 @@
# Copyright 2016 - Nokia Networks
#
# 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 stevedore import extension
# Valid authentication types.
ALL = extension.ExtensionManager(
namespace='mistralclient.auth',
invoke_on_load=False
).names()

View File

@ -1,170 +0,0 @@
# Copyright 2016 - Nokia Networks
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
import pprint
import requests
from mistralclient import auth
LOG = logging.getLogger(__name__)
class KeycloakAuthHandler(auth.AuthHandler):
def authenticate(self, req, session=None):
"""Performs authentication using Keycloak OpenID Protocol.
:param req: Request dict containing list of parameters required
for Keycloak authentication.
* auth_url: Base authentication url of KeyCloak server (e.g.
"https://my.keycloak:8443/auth"
* client_id: Client ID (according to OpenID Connect protocol).
* client_secret: Client secret (according to OpenID Connect
protocol).
* realm_name: KeyCloak realm name.
* username: User name (Optional, if None then access_token must be
provided).
* password: Password (Optional).
* access_token: Access token. If passed, username and password are
not used and this method just validates the token and refreshes
it if needed (Optional, if None then username must be
provided).
* cacert: SSL certificate file (Optional).
* insecure: If True, SSL certificate is not verified (Optional).
:param session: Keystone session object. Not used by this plugin.
"""
if not isinstance(req, dict):
raise TypeError('The input "req" is not typeof dict.')
auth_url = req.get('auth_url')
client_id = req.get('client_id')
client_secret = req.get('client_secret')
realm_name = req.get('realm_name')
username = req.get('username')
password = req.get('password')
access_token = req.get('access_token')
cacert = req.get('cacert')
insecure = req.get('insecure', False)
if not auth_url:
raise ValueError('Base authentication url is not provided.')
if not client_id:
raise ValueError('Client ID is not provided.')
if not client_secret:
raise ValueError('Client secret is not provided.')
if not realm_name:
raise ValueError('Project(realm) name is not provided.')
if username and access_token:
raise ValueError(
"User name and access token can't be "
"provided at the same time."
)
if not username and not access_token:
raise ValueError(
'Either user name or access token must be provided.'
)
if access_token:
response = self._authenticate_with_token(
auth_url,
client_id,
client_secret,
access_token,
cacert,
insecure
)
else:
response = self._authenticate_with_password(
auth_url,
client_id,
client_secret,
realm_name,
username,
password,
cacert,
insecure
)
response['project_id'] = realm_name
return response
def _authenticate_with_token(auth_url, client_id, client_secret,
auth_token, cacert=None, insecure=None):
# TODO(rakhmerov): Implement.
raise NotImplementedError
def _authenticate_with_password(auth_url, client_id, client_secret,
realm_name, username, password,
cacert=None, insecure=None):
access_token_endpoint = (
"%s/realms/%s/protocol/openid-connect/token" %
(auth_url, realm_name)
)
client_auth = (client_id, client_secret)
body = {
'grant_type': 'password',
'username': username,
'password': password,
'scope': 'profile'
}
resp = requests.post(
access_token_endpoint,
auth=client_auth,
data=body,
verify=not insecure
)
try:
resp.raise_for_status()
except Exception as e:
raise Exception("Failed to get access token:\n %s" % str(e))
LOG.debug("HTTP response from OIDC provider: %s",
pprint.pformat(resp.json()))
return resp.json()['access_token']
# An example of using KeyCloak OpenID authentication.
if __name__ == '__main__':
print("Using username/password to get access token from KeyCloak...")
auth_handler = KeycloakAuthHandler()
a_token = auth_handler.authenticate(
"https://my.keycloak:8443/auth",
client_id="mistral_client",
client_secret="4a080907-921b-409a-b793-c431609c3a47",
realm_name="mistral",
username="user",
password="secret",
insecure=True
)
print("Access token: %s" % a_token)

View File

@ -1,146 +0,0 @@
# Copyright 2016 - Nokia Networks
#
# 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 import client
from mistralclient import auth
from oslo_serialization import jsonutils
import mistralclient.api.httpclient as api
class KeystoneAuthHandler(auth.AuthHandler):
def authenticate(self, req, session=None):
"""Performs authentication via Keystone.
:param req: Request dict containing list of parameters required
for Keystone authentication.
"""
if not isinstance(req, dict):
raise TypeError('The input "req" is not typeof dict.')
session = session
mistral_url = req.get('mistral_url')
endpoint_type = req.get('endpoint_type', 'publicURL')
service_type = req.get('service_type', 'workflowv2')
auth_url = req.get('auth_url')
username = req.get('username')
user_id = req.get('user_id')
api_key = req.get('api_key')
auth_token = req.get('auth_token')
project_name = req.get('project_name')
project_id = req.get('project_id')
region_name = req.get('region_name')
user_domain_name = req.get('user_domain_name', 'Default')
project_domain_name = req.get('project_domain_name', 'Default')
cacert = req.get('cacert')
insecure = req.get('insecure', False)
target_auth_url = req.get('target_auth_url')
target_username = req.get('target_username')
target_user_id = req.get('target_user_id')
target_api_key = req.get('target_api_key')
target_auth_token = req.get('target_auth_token')
target_project_name = req.get('target_project_name')
target_project_id = req.get('target_project_id')
target_region_name = req.get('target_region_name')
target_user_domain_name = req.get('target_user_domain_name', 'Default')
target_project_domain_name = req.get(
'target_project_domain_name',
'Default'
)
target_cacert = req.get('target_cacert')
target_insecure = req.get('target_insecure')
if project_name and project_id:
raise RuntimeError(
'Only project name or project id should be set'
)
if username and user_id:
raise RuntimeError(
'Only user name or user id should be set'
)
auth_response = {}
if session:
keystone = client.Client(session=session)
elif auth_url:
keystone = client.Client(
username=username,
user_id=user_id,
password=api_key,
token=auth_token,
tenant_id=project_id,
tenant_name=project_name,
auth_url=auth_url,
cacert=cacert,
insecure=insecure,
user_domain_name=user_domain_name,
project_domain_name=project_domain_name
)
keystone.authenticate()
auth_response.update({
api.AUTH_TOKEN: keystone.auth_token,
api.PROJECT_ID: keystone.project_id,
api.USER_ID: keystone.user_id,
})
if session or auth_url:
if not mistral_url:
try:
mistral_url = keystone.service_catalog.url_for(
service_type=service_type,
endpoint_type=endpoint_type,
region_name=region_name
)
except Exception:
mistral_url = None
auth_response['mistral_url'] = mistral_url
if target_auth_url:
target_keystone = client.Client(
username=target_username,
user_id=target_user_id,
password=target_api_key,
token=target_auth_token,
tenant_id=target_project_id,
tenant_name=target_project_name,
project_id=target_project_id,
project_name=target_project_name,
auth_url=target_auth_url,
cacert=target_cacert,
insecure=target_insecure,
region_name=target_region_name,
user_domain_name=target_user_domain_name,
project_domain_name=target_project_domain_name
)
target_keystone.authenticate()
auth_response.update({
api.TARGET_AUTH_TOKEN: target_keystone.auth_token,
api.TARGET_PROJECT_ID: target_keystone.project_id,
api.TARGET_USER_ID: target_keystone.user_id,
api.TARGET_AUTH_URI: target_auth_url,
api.TARGET_SERVICE_CATALOG: jsonutils.dumps(
target_keystone.auth_ref
)
})
return auth_response

View File

@ -1,338 +0,0 @@
# Copyright 2014 - Mirantis, Inc.
# Copyright 2016 - Brocade Communications Systems, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
import json
import logging
from osc_lib.command import command
from mistralclient.commands.v2 import base
from mistralclient import utils
LOG = logging.getLogger(__name__)
def format_list(action_ex=None):
columns = (
'ID',
'Name',
'Workflow name',
'Task name',
'Task ID',
'State',
'Accepted',
'Created at',
'Updated at'
)
if action_ex:
data = (
action_ex.id,
action_ex.name,
action_ex.workflow_name,
action_ex.task_name if hasattr(action_ex, 'task_name') else None,
action_ex.task_execution_id,
action_ex.state,
action_ex.accepted,
action_ex.created_at,
action_ex.updated_at or '<none>'
)
else:
data = (tuple('<none>' for _ in range(len(columns))),)
return columns, data
def format(action_ex=None):
columns = (
'ID',
'Name',
'Workflow name',
'Task name',
'Task ID',
'State',
'State info',
'Accepted',
'Created at',
'Updated at',
)
if action_ex:
data = (
action_ex.id,
action_ex.name,
action_ex.workflow_name,
action_ex.task_name if hasattr(action_ex, 'task_name') else None,
action_ex.task_execution_id,
action_ex.state,
action_ex.state_info,
action_ex.accepted,
action_ex.created_at,
action_ex.updated_at or '<none>'
)
else:
data = (tuple('<none>' for _ in range(len(columns))),)
return columns, data
class Create(command.ShowOne):
"""Create new Action execution or just run specific action."""
def produce_output(self, parsed_args, column_names, data):
if not column_names:
return 0
return super(Create, self).produce_output(
parsed_args,
column_names,
data
)
def get_parser(self, prog_name):
parser = super(Create, self).get_parser(prog_name)
parser.add_argument(
'name',
help='Action name to execute.'
)
parser.add_argument(
dest='input',
nargs='?',
help='Action input.'
)
parser.add_argument(
'-s',
'--save-result',
dest='save_result',
action='store_true',
help='Save the result into DB.'
)
parser.add_argument(
'--run-sync',
dest='run_sync',
action='store_true',
help='Run the action synchronously.'
)
parser.add_argument(
'-t',
'--target',
dest='target',
help='Action will be executed on <target> executor.'
)
return parser
def take_action(self, parsed_args):
params = {}
if parsed_args.save_result:
params['save_result'] = parsed_args.save_result
if parsed_args.run_sync:
params['run_sync'] = parsed_args.run_sync
if parsed_args.target:
params['target'] = parsed_args.target
action_input = None
if parsed_args.input:
action_input = utils.load_json(parsed_args.input)
mistral_client = self.app.client_manager.workflow_engine
action_ex = mistral_client.action_executions.create(
parsed_args.name,
action_input,
**params
)
if not parsed_args.run_sync and parsed_args.save_result:
return format(action_ex)
else:
self.app.stdout.write("%s\n" % action_ex.output)
return None, None
class List(base.MistralLister):
"""List all Action executions."""
def _get_format_function(self):
return format_list
def get_parser(self, prog_name):
parser = super(List, self).get_parser(prog_name)
parser.add_argument(
'task_execution_id',
nargs='?',
help='Task execution ID.'
)
parser.add_argument(
'--limit',
type=int,
help='Maximum number of action-executions to return in a single '
'result. limit is set to %s by default. Use --limit -1 to '
'fetch the full result set.' % base.DEFAULT_LIMIT,
nargs='?'
)
return parser
def _get_resources(self, parsed_args):
if parsed_args.limit is None:
parsed_args.limit = base.DEFAULT_LIMIT
LOG.info("limit is set to %s by default. Set "
"the limit explicitly using \'--limit\', if required. "
"Use \'--limit\' -1 to fetch the full result set.",
base.DEFAULT_LIMIT)
mistral_client = self.app.client_manager.workflow_engine
return mistral_client.action_executions.list(
parsed_args.task_execution_id,
limit=parsed_args.limit,
)
class Get(command.ShowOne):
"""Show specific Action execution."""
def get_parser(self, prog_name):
parser = super(Get, self).get_parser(prog_name)
parser.add_argument('action_execution', help='Action execution ID.')
return parser
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
execution = mistral_client.action_executions.get(
parsed_args.action_execution
)
return format(execution)
class Update(command.ShowOne):
"""Update specific Action execution."""
def get_parser(self, prog_name):
parser = super(Update, self).get_parser(prog_name)
parser.add_argument(
'id',
help='Action execution ID.')
parser.add_argument(
'--state',
dest='state',
choices=['IDLE', 'RUNNING', 'SUCCESS', 'ERROR', 'CANCELLED'],
help='Action execution state')
parser.add_argument(
'--output',
dest='output',
help='Action execution output')
return parser
def take_action(self, parsed_args):
output = None
if parsed_args.output:
output = utils.load_json(parsed_args.output)
mistral_client = self.app.client_manager.workflow_engine
execution = mistral_client.action_executions.update(
parsed_args.id,
parsed_args.state,
output
)
return format(execution)
class GetOutput(command.Command):
"""Show Action execution output data."""
def get_parser(self, prog_name):
parser = super(GetOutput, self).get_parser(prog_name)
parser.add_argument(
'id',
help='Action execution ID.')
return parser
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
output = mistral_client.action_executions.get(parsed_args.id).output
try:
output = json.loads(output)
output = json.dumps(output, indent=4) + "\n"
except Exception:
LOG.debug("Task result is not JSON.")
self.app.stdout.write(output or "\n")
class GetInput(command.Command):
"""Show Action execution input data."""
def get_parser(self, prog_name):
parser = super(GetInput, self).get_parser(prog_name)
parser.add_argument(
'id',
help='Action execution ID.'
)
return parser
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
result = mistral_client.action_executions.get(parsed_args.id).input
try:
result = json.loads(result)
result = json.dumps(result, indent=4) + "\n"
except Exception:
LOG.debug("Task result is not JSON.")
self.app.stdout.write(result or "\n")
class Delete(command.Command):
"""Delete action execution."""
def get_parser(self, prog_name):
parser = super(Delete, self).get_parser(prog_name)
parser.add_argument(
'action_execution',
nargs='+',
help='Id of action execution identifier(s).'
)
return parser
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
utils.do_action_on_many(
lambda s: mistral_client.action_executions.delete(s),
parsed_args.action_execution,
"Request to delete action execution %s has been accepted.",
"Unable to delete the specified action execution(s)."
)

View File

@ -1,255 +0,0 @@
# Copyright 2014 - Mirantis, 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 osc_lib.command import command
from mistralclient.commands.v2 import base
from mistralclient import utils
def format_list(action=None):
return format(action, lister=True)
def format(action=None, lister=False):
columns = (
'ID',
'Name',
'Is system',
'Input',
'Description',
'Tags',
'Created at',
'Updated at'
)
if action:
tags = getattr(action, 'tags', None) or []
input = action.input if not lister else base.cut(action.input)
desc = (action.description if not lister
else base.cut(action.description))
data = (
action.id,
action.name,
action.is_system,
input,
desc,
base.wrap(', '.join(tags)) or '<none>',
action.created_at,
)
if hasattr(action, 'updated_at'):
data += (action.updated_at,)
else:
data += (None,)
else:
data = (tuple('<none>' for _ in range(len(columns))),)
return columns, data
class List(base.MistralLister):
"""List all actions."""
def _get_format_function(self):
return format_list
def get_parser(self, prog_name):
parser = super(List, self).get_parser(prog_name)
parser.add_argument(
'--filter',
dest='filters',
action='append',
help='Filters. Can be repeated.'
)
return parser
def _get_resources(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
return mistral_client.actions.list(
**base.get_filters(parsed_args)
)
class Get(command.ShowOne):
"""Show specific action."""
def get_parser(self, prog_name):
parser = super(Get, self).get_parser(prog_name)
parser.add_argument('action', help='Action (name or ID)')
return parser
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
action = mistral_client.actions.get(parsed_args.action)
return format(action)
class Create(base.MistralLister):
"""Create new action."""
def get_parser(self, prog_name):
parser = super(Create, self).get_parser(prog_name)
parser.add_argument(
'definition',
type=argparse.FileType('r'),
help='Action definition file'
)
parser.add_argument(
'--public',
action='store_true',
help='With this flag action will be marked as "public".'
)
return parser
def _validate_parsed_args(self, parsed_args):
if not parsed_args.definition:
raise RuntimeError("Provide action definition file.")
def _get_format_function(self):
return format_list
def _get_resources(self, parsed_args):
scope = 'public' if parsed_args.public else 'private'
mistral_client = self.app.client_manager.workflow_engine
return mistral_client.actions.create(
parsed_args.definition.read(),
scope=scope
)
class Delete(command.Command):
"""Delete action."""
def get_parser(self, prog_name):
parser = super(Delete, self).get_parser(prog_name)
parser.add_argument(
'action',
nargs='+',
help='Name or ID of action(s).'
)
return parser
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
utils.do_action_on_many(
lambda s: mistral_client.actions.delete(s),
parsed_args.action,
"Request to delete action %s has been accepted.",
"Unable to delete the specified action(s)."
)
class Update(base.MistralLister):
"""Update action."""
def get_parser(self, prog_name):
parser = super(Update, self).get_parser(prog_name)
parser.add_argument(
'definition',
type=argparse.FileType('r'),
help='Action definition file'
)
parser.add_argument('--id', help='Action ID.')
parser.add_argument(
'--public',
action='store_true',
help='With this flag action will be marked as "public".'
)
return parser
def _get_format_function(self):
return format_list
def _get_resources(self, parsed_args):
scope = 'public' if parsed_args.public else 'private'
mistral_client = self.app.client_manager.workflow_engine
return mistral_client.actions.update(
parsed_args.definition.read(),
scope=scope,
id=parsed_args.id
)
class GetDefinition(command.Command):
"""Show action definition."""
def get_parser(self, prog_name):
parser = super(GetDefinition, self).get_parser(prog_name)
parser.add_argument('name', help='Action name')
return parser
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
definition = mistral_client.actions.get(parsed_args.name).definition
self.app.stdout.write(definition or "\n")
class Validate(command.ShowOne):
"""Validate action."""
def _format(self, result=None):
columns = ('Valid', 'Error')
if result:
data = (result.get('valid'), result.get('error'))
else:
data = (tuple('<none>' for _ in range(len(columns))),)
return columns, data
def get_parser(self, prog_name):
parser = super(Validate, self).get_parser(prog_name)
parser.add_argument(
'definition',
type=argparse.FileType('r'),
help='action definition file'
)
return parser
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
result = mistral_client.actions.validate(
parsed_args.definition.read()
)
return self._format(result)

View File

@ -1,85 +0,0 @@
# Copyright 2014 - Mirantis, 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 abc
import textwrap
from osc_lib.command import command
import six
DEFAULT_LIMIT = 100
@six.add_metaclass(abc.ABCMeta)
class MistralLister(command.Lister):
@abc.abstractmethod
def _get_format_function(self):
raise NotImplementedError
@abc.abstractmethod
def _get_resources(self, parsed_args):
"""Gets a list of API resources (e.g. using client)."""
raise NotImplementedError
def _validate_parsed_args(self, parsed_args):
# No-op by default.
pass
def take_action(self, parsed_args):
self._validate_parsed_args(parsed_args)
f = self._get_format_function()
ret = self._get_resources(parsed_args)
if not isinstance(ret, list):
ret = [ret]
data = [f(r)[1] for r in ret]
if data:
return f()[0], data
else:
return f()
def cut(string, length=25):
if string and len(string) > length:
return "%s..." % string[:length]
else:
return string
def wrap(string, width=25):
if string and len(string) > width:
return textwrap.fill(string, width)
else:
return string
def get_filters(parsed_args):
filters = {}
if parsed_args.filters:
for f in parsed_args.filters:
arr = f.split('=')
if len(arr) != 2:
raise ValueError('Invalid filter: %s' % f)
filters[arr[0]] = arr[1]
return filters

View File

@ -1,216 +0,0 @@
# Copyright 2014 - Mirantis, 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 datetime
import time
from osc_lib.command import command
from mistralclient.commands.v2 import base
from mistralclient import utils
def format_list(trigger=None):
return format(trigger, lister=True)
def format(trigger=None, lister=False):
columns = (
'Name',
'Workflow',
'Params',
'Pattern',
# TODO(rakhmerov): Uncomment when passwords are handled properly.
# TODO(rakhmerov): Add 'Workflow input' column.
'Next execution time',
'Remaining executions',
'Created at',
'Updated at'
)
if trigger:
# TODO(rakhmerov): Add following here:
# TODO(rakhmerov): wf_input = trigger.workflow_input if not lister
# TODO(rakhmerov:): else base.cut(trigger.workflow_input)
data = (
trigger.name,
trigger.workflow_name,
trigger.workflow_params,
trigger.pattern,
# TODO(rakhmerov): Uncomment when passwords are handled properly.
# TODo(rakhmerov): Add 'wf_input' here.
trigger.next_execution_time,
trigger.remaining_executions,
trigger.created_at,
)
if hasattr(trigger, 'updated_at'):
data += (trigger.updated_at,)
else:
data += (None,)
else:
data = (tuple('<none>' for _ in range(len(columns))),)
return columns, data
class List(base.MistralLister):
"""List all cron triggers."""
def _get_format_function(self):
return format_list
def _get_resources(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
return mistral_client.cron_triggers.list()
class Get(command.ShowOne):
"""Show specific cron trigger."""
def get_parser(self, prog_name):
parser = super(Get, self).get_parser(prog_name)
parser.add_argument('cron_trigger', help='Cron trigger name')
return parser
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
return format(mistral_client.cron_triggers.get(
parsed_args.cron_trigger
))
class Create(command.ShowOne):
"""Create new trigger."""
def get_parser(self, prog_name):
parser = super(Create, self).get_parser(prog_name)
parser.add_argument('name', help='Cron trigger name')
parser.add_argument('workflow_identifier', help='Workflow name or ID')
parser.add_argument(
'workflow_input',
nargs='?',
help='Workflow input'
)
parser.add_argument(
'--params',
help='Workflow params',
)
parser.add_argument(
'--pattern',
type=str,
help='Cron trigger pattern',
metavar='<* * * * *>'
)
parser.add_argument(
'--first-time',
type=str,
default=None,
help=("Date and time of the first execution. Time is treated as "
"local time unless --utc is also specified"),
metavar='<YYYY-MM-DD HH:MM>'
)
parser.add_argument(
'--count',
type=int,
help="Number of wanted executions",
metavar='<integer>'
)
parser.add_argument(
'--utc',
action='store_true',
help="All times specified should be treated as UTC"
)
return parser
@staticmethod
def _get_file_content_or_dict(string):
if string:
return utils.load_json(string)
else:
return {}
@staticmethod
def _convert_time_string_to_utc(time_string):
datetime_format = '%Y-%m-%d %H:%M'
the_time = time_string
if the_time:
the_time = datetime.datetime.strptime(
the_time, datetime_format)
is_dst = time.daylight and time.localtime().tm_isdst > 0
utc_offset = - (time.altzone if is_dst else time.timezone)
the_time = (the_time - datetime.timedelta(
0, utc_offset)).strftime(datetime_format)
return the_time
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
wf_input = self._get_file_content_or_dict(parsed_args.workflow_input)
wf_params = self._get_file_content_or_dict(parsed_args.params)
first_time = parsed_args.first_time
if not parsed_args.utc:
first_time = self._convert_time_string_to_utc(
parsed_args.first_time)
trigger = mistral_client.cron_triggers.create(
parsed_args.name,
parsed_args.workflow_identifier,
wf_input,
wf_params,
parsed_args.pattern,
first_time,
parsed_args.count
)
return format(trigger)
class Delete(command.Command):
"""Delete trigger."""
def get_parser(self, prog_name):
parser = super(Delete, self).get_parser(prog_name)
parser.add_argument(
'cron_trigger',
nargs='+', help='Name of cron trigger(s).'
)
return parser
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
utils.do_action_on_many(
lambda s: mistral_client.cron_triggers.delete(s),
parsed_args.cron_trigger,
"Request to delete cron trigger %s has been accepted.",
"Unable to delete the specified cron trigger(s)."
)

View File

@ -1,186 +0,0 @@
# Copyright 2015 - StackStorm, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import argparse
import json
from osc_lib.command import command
from mistralclient.commands.v2 import base
from mistralclient import utils
def format_list(environment=None):
columns = (
'Name',
'Description',
'Scope',
'Created at',
'Updated at'
)
if environment:
data = (
environment.name,
environment.description,
environment.scope,
environment.created_at,
)
if hasattr(environment, 'updated_at'):
data += (environment.updated_at or '<none>',)
else:
data += (None,)
else:
data = (tuple('<none>' for _ in range(len(columns))),)
return columns, data
def format(environment=None):
columns = (
'Name',
'Description',
'Variables',
'Scope',
'Created at',
'Updated at'
)
if environment:
data = (environment.name,)
if hasattr(environment, 'description'):
data += (environment.description or '<none>',)
else:
data += (None,)
data += (
json.dumps(environment.variables, indent=4),
environment.scope,
environment.created_at,
)
if hasattr(environment, 'updated_at'):
data += (environment.updated_at or '<none>',)
else:
data += (None,)
else:
data = (tuple('<none>' for _ in range(len(columns))),)
return columns, data
class List(base.MistralLister):
"""List all environments."""
def _get_format_function(self):
return format_list
def _get_resources(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
return mistral_client.environments.list()
class Get(command.ShowOne):
"""Show specific environment."""
def get_parser(self, prog_name):
parser = super(Get, self).get_parser(prog_name)
parser.add_argument(
'environment',
help='Environment name'
)
return parser
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
environment = mistral_client.environments.get(parsed_args.environment)
return format(environment)
class Create(command.ShowOne):
"""Create new environment."""
def get_parser(self, prog_name):
parser = super(Create, self).get_parser(prog_name)
parser.add_argument(
'file',
type=argparse.FileType('r'),
help='Environment configuration file in JSON or YAML'
)
return parser
def take_action(self, parsed_args):
data = utils.load_content(parsed_args.file.read())
mistral_client = self.app.client_manager.workflow_engine
environment = mistral_client.environments.create(**data)
return format(environment)
class Delete(command.Command):
"""Delete environment."""
def get_parser(self, prog_name):
parser = super(Delete, self).get_parser(prog_name)
parser.add_argument(
'environment',
nargs='+',
help='Name of environment(s).'
)
return parser
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
utils.do_action_on_many(
lambda s: mistral_client.environments.delete(s),
parsed_args.environment,
"Request to delete environment %s has been accepted.",
"Unable to delete the specified environment(s)."
)
class Update(command.ShowOne):
"""Update environment."""
def get_parser(self, prog_name):
parser = super(Update, self).get_parser(prog_name)
parser.add_argument(
'file',
type=argparse.FileType('r'),
help='Environment configuration file in JSON or YAML'
)
return parser
def take_action(self, parsed_args):
data = utils.load_content(parsed_args.file.read())
mistral_client = self.app.client_manager.workflow_engine
environment = mistral_client.environments.update(**data)
return format(environment)

View File

@ -1,167 +0,0 @@
# Copyright 2017, OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
from osc_lib.command import command
from mistralclient.commands.v2 import base
from mistralclient import utils
def format_list(trigger=None):
return format(trigger, lister=True)
def format(trigger=None, lister=False):
columns = (
'ID',
'Name',
'Workflow ID',
'Params',
'Exchange',
'Topic',
'Event',
'Created at',
'Updated at'
)
if trigger:
data = (
trigger.id,
trigger.name,
trigger.workflow_id,
trigger.workflow_params,
trigger.exchange,
trigger.topic,
trigger.event,
trigger.created_at,
)
if hasattr(trigger, 'updated_at'):
data += (trigger.updated_at,)
else:
data += (None,)
else:
data = (tuple('<none>' for _ in range(len(columns))),)
return columns, data
class List(base.MistralLister):
"""List all event triggers."""
def _get_format_function(self):
return format_list
def _get_resources(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
return mistral_client.event_triggers.list()
class Get(command.ShowOne):
"""Show specific event trigger."""
def get_parser(self, prog_name):
parser = super(Get, self).get_parser(prog_name)
parser.add_argument('event_trigger', help='Event trigger ID')
return parser
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
return format(mistral_client.event_triggers.get(
parsed_args.event_trigger
))
class Create(command.ShowOne):
"""Create new trigger."""
def get_parser(self, prog_name):
parser = super(Create, self).get_parser(prog_name)
parser.add_argument('name', help='Event trigger name')
parser.add_argument('workflow_id', help='Workflow ID')
parser.add_argument('exchange',
type=str,
help='Event trigger exchange')
parser.add_argument('topic',
type=str,
help='Event trigger topic')
parser.add_argument('event',
type=str,
help='Event trigger event name')
parser.add_argument('workflow_input',
nargs='?',
help='Workflow input')
parser.add_argument('--params',
help='Workflow params')
return parser
@staticmethod
def _get_json_string_or_dict(string):
if string:
return utils.load_json(string)
else:
return {}
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
wf_input = self._get_json_string_or_dict(parsed_args.workflow_input)
wf_params = self._get_json_string_or_dict(parsed_args.params)
trigger = mistral_client.event_triggers.create(
parsed_args.name,
parsed_args.workflow_id,
parsed_args.exchange,
parsed_args.topic,
parsed_args.event,
wf_input,
wf_params,
)
return format(trigger)
class Delete(command.Command):
"""Delete trigger."""
def get_parser(self, prog_name):
parser = super(Delete, self).get_parser(prog_name)
parser.add_argument(
'event_trigger_id',
nargs='+', help='ID of event trigger(s).'
)
return parser
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
utils.do_action_on_many(
lambda s: mistral_client.event_triggers.delete(s),
parsed_args.event_trigger_id,
"Request to delete event trigger %s has been accepted.",
"Unable to delete the specified event trigger(s)."
)

View File

@ -1,338 +0,0 @@
# Copyright 2014 - Mirantis, Inc.
# Copyright 2015 - StackStorm, Inc.
# Copyright 2016 - Brocade Communications Systems, 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 json
import logging
import os.path
from osc_lib.command import command
from mistralclient.commands.v2 import base
from mistralclient import utils
LOG = logging.getLogger(__name__)
def format_list(execution=None):
return format(execution, lister=True)
def format(execution=None, lister=False):
columns = (
'ID',
'Workflow ID',
'Workflow name',
'Description',
'Task Execution ID',
'State',
'State info',
'Created at',
'Updated at'
)
# TODO(nmakhotkin) Add parent task id when it's implemented in API.
if execution:
state_info = (execution.state_info if not lister
else base.cut(execution.state_info))
data = (
execution.id,
execution.workflow_id,
execution.workflow_name,
execution.description,
execution.task_execution_id or '<none>',
execution.state,
state_info,
execution.created_at,
execution.updated_at or '<none>'
)
else:
data = (tuple('<none>' for _ in range(len(columns))),)
return columns, data
class List(base.MistralLister):
"""List all executions."""
def _get_format_function(self):
return format_list
def get_parser(self, parsed_args):
parser = super(List, self).get_parser(parsed_args)
parser.add_argument(
'--task',
nargs='?',
help="Parent task execution ID associated with workflow "
"execution list.",
)
parser.add_argument(
'--marker',
type=str,
help='The last execution uuid of the previous page, displays list '
'of executions after "marker".',
default='',
nargs='?'
)
parser.add_argument(
'--limit',
type=int,
help='Maximum number of executions to return in a single result. '
'limit is set to %s by default. Use --limit -1 to fetch the '
'full result set.' % base.DEFAULT_LIMIT,
nargs='?'
)
parser.add_argument(
'--sort_keys',
help='Comma-separated list of sort keys to sort results by. '
'Default: created_at. '
'Example: mistral execution-list --sort_keys=id,description',
default='created_at',
nargs='?'
)
parser.add_argument(
'--sort_dirs',
help='Comma-separated list of sort directions. Default: asc. '
'Example: mistral execution-list --sort_keys=id,description '
'--sort_dirs=asc,desc',
default='asc',
nargs='?'
)
parser.add_argument(
'--filter',
dest='filters',
action='append',
help='Filters. Can be repeated.'
)
return parser
def _get_resources(self, parsed_args):
if parsed_args.limit is None:
parsed_args.limit = base.DEFAULT_LIMIT
LOG.info("limit is set to %s by default. Set "
"the limit explicitly using \'--limit\', if required. "
"Use \'--limit\' -1 to fetch the full result set.",
base.DEFAULT_LIMIT)
mistral_client = self.app.client_manager.workflow_engine
return mistral_client.executions.list(
task=parsed_args.task,
marker=parsed_args.marker,
limit=parsed_args.limit,
sort_keys=parsed_args.sort_keys,
sort_dirs=parsed_args.sort_dirs,
**base.get_filters(parsed_args)
)
class Get(command.ShowOne):
"""Show specific execution."""
def get_parser(self, prog_name):
parser = super(Get, self).get_parser(prog_name)
parser.add_argument('execution', help='Execution identifier')
return parser
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
execution = mistral_client.executions.get(parsed_args.execution)
return format(execution)
class Create(command.ShowOne):
"""Create new execution."""
def get_parser(self, prog_name):
parser = super(Create, self).get_parser(prog_name)
parser.add_argument(
'workflow_identifier',
help='Workflow ID or name. Workflow name will be deprecated since '
'Mitaka.'
)
parser.add_argument(
'workflow_input',
nargs='?',
help='Workflow input'
)
parser.add_argument(
'params',
nargs='?',
help='Workflow additional parameters'
)
parser.add_argument(
'-d',
'--description',
dest='description',
default='',
help='Execution description'
)
return parser
def take_action(self, parsed_args):
if parsed_args.workflow_input:
wf_input = utils.load_json(parsed_args.workflow_input)
else:
wf_input = {}
if parsed_args.params:
params = utils.load_json(parsed_args.params)
else:
params = {}
mistral_client = self.app.client_manager.workflow_engine
execution = mistral_client.executions.create(
parsed_args.workflow_identifier,
wf_input,
parsed_args.description,
**params
)
return format(execution)
class Delete(command.Command):
"""Delete execution."""
def get_parser(self, prog_name):
parser = super(Delete, self).get_parser(prog_name)
parser.add_argument(
'execution',
nargs='+',
help='Id of execution identifier(s).'
)
return parser
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
utils.do_action_on_many(
lambda s: mistral_client.executions.delete(s),
parsed_args.execution,
"Request to delete execution %s has been accepted.",
"Unable to delete the specified execution(s)."
)
class Update(command.ShowOne):
"""Update execution."""
def get_parser(self, prog_name):
parser = super(Update, self).get_parser(prog_name)
parser.add_argument(
'id',
help='Execution identifier'
)
parser.add_argument(
'-s',
'--state',
dest='state',
choices=['RUNNING', 'PAUSED', 'SUCCESS', 'ERROR', 'CANCELLED'],
help='Execution state'
)
parser.add_argument(
'-e',
'--env',
dest='env',
help='Environment variables'
)
parser.add_argument(
'-d',
'--description',
dest='description',
help='Execution description'
)
return parser
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
env = (
utils.load_file(parsed_args.env)
if parsed_args.env and os.path.isfile(parsed_args.env)
else utils.load_content(parsed_args.env)
)
execution = mistral_client.executions.update(
parsed_args.id,
parsed_args.state,
description=parsed_args.description,
env=env
)
return format(execution)
class GetInput(command.Command):
"""Show execution input data."""
def get_parser(self, prog_name):
parser = super(GetInput, self).get_parser(prog_name)
parser.add_argument('id', help='Execution ID')
return parser
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
ex_input = mistral_client.executions.get(parsed_args.id).input
try:
ex_input = json.loads(ex_input)
ex_input = json.dumps(ex_input, indent=4) + "\n"
except Exception:
LOG.debug("Execution input is not JSON.")
self.app.stdout.write(ex_input or "\n")
class GetOutput(command.Command):
"""Show execution output data."""
def get_parser(self, prog_name):
parser = super(GetOutput, self).get_parser(prog_name)
parser.add_argument('id', help='Execution ID')
return parser
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
output = mistral_client.executions.get(parsed_args.id).output
try:
output = json.loads(output)
output = json.dumps(output, indent=4) + "\n"
except Exception:
LOG.debug("Execution output is not JSON.")
self.app.stdout.write(output or "\n")

View File

@ -1,235 +0,0 @@
# Copyright 2016 - Catalyst IT Limited
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
from osc_lib.command import command
from mistralclient.commands.v2 import base
from mistralclient import exceptions
def format_list(member=None):
return format(member, lister=True)
def format(member=None, lister=False):
columns = (
'Resource ID',
'Resource Type',
'Resource Owner',
'Member ID',
'Status',
'Created at',
'Updated at'
)
if member:
data = (
member.resource_id,
member.resource_type,
member.project_id,
member.member_id,
member.status,
member.created_at,
)
if hasattr(member, 'updated_at'):
data += (member.updated_at,)
else:
data += (None,)
else:
data = (tuple('<none>' for _ in range(len(columns))),)
return columns, data
class List(base.MistralLister):
"""List all members."""
def _get_format_function(self):
return format_list
def get_parser(self, parsed_args):
parser = super(List, self).get_parser(parsed_args)
parser.add_argument(
'resource_id',
help='Resource id to be shared.'
)
parser.add_argument(
'resource_type',
help='Resource type.'
)
return parser
def _get_resources(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
return mistral_client.members.list(
parsed_args.resource_id,
parsed_args.resource_type
)
class Get(command.ShowOne):
"""Show specific member information."""
def get_parser(self, prog_name):
parser = super(Get, self).get_parser(prog_name)
parser.add_argument(
'resource',
help='Resource ID to be shared.'
)
parser.add_argument(
'resource_type',
help='Resource type.'
)
parser.add_argument(
'-m',
'--member-id',
default='',
help='Project ID to whom the resource is shared to. No need to '
'provide this param if you are the resource member.'
)
return parser
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
member = mistral_client.members.get(
parsed_args.resource,
parsed_args.resource_type,
parsed_args.member_id,
)
return format(member)
class Create(command.ShowOne):
"""Shares a resource to another tenant."""
def get_parser(self, prog_name):
parser = super(Create, self).get_parser(prog_name)
parser.add_argument(
'resource_id',
help='Resource ID to be shared.'
)
parser.add_argument(
'resource_type',
help='Resource type.'
)
parser.add_argument(
'member_id',
help='Project ID to whom the resource is shared to.'
)
return parser
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
member = mistral_client.members.create(
parsed_args.resource_id,
parsed_args.resource_type,
parsed_args.member_id,
)
return format(member)
class Delete(command.Command):
"""Delete a resource sharing relationship."""
def get_parser(self, prog_name):
parser = super(Delete, self).get_parser(prog_name)
parser.add_argument(
'resource',
help='Resource ID to be shared.'
)
parser.add_argument(
'resource_type',
help='Resource type.'
)
parser.add_argument(
'member_id',
help='Project ID to whom the resource is shared to.'
)
return parser
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
try:
mistral_client.members.delete(
parsed_args.resource,
parsed_args.resource_type,
parsed_args.member_id,
)
print(
"Request to delete %s member %s has been accepted." %
(parsed_args.resource_type, parsed_args.member_id)
)
except Exception as e:
print(e)
error_msg = "Unable to delete the specified member."
raise exceptions.MistralClientException(error_msg)
class Update(command.ShowOne):
"""Update resource sharing status."""
def get_parser(self, prog_name):
parser = super(Update, self).get_parser(prog_name)
parser.add_argument(
'resource_id',
help='Resource ID to be shared.'
)
parser.add_argument(
'resource_type',
help='Resource type.'
)
parser.add_argument(
'-m',
'--member-id',
default='',
help='Project ID to whom the resource is shared to. No need to '
'provide this param if you are the resource member.'
)
parser.add_argument(
'-s',
'--status',
default='accepted',
choices=['pending', 'accepted', 'rejected'],
help='status of the sharing.'
)
return parser
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
member = mistral_client.members.update(
parsed_args.resource_id,
parsed_args.resource_type,
parsed_args.member_id,
status=parsed_args.status
)
return format(member)

View File

@ -1,37 +0,0 @@
# Copyright 2015 Huawei Technologies Co., Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from mistralclient.commands.v2 import base
def format_list(service=None):
columns = ('Name', 'Type')
if service:
data = (service.name, service.type)
else:
data = (tuple('<none>' for _ in range(len(columns))),)
return columns, data
class List(base.MistralLister):
"""List all services."""
def _get_format_function(self):
return format_list
def _get_resources(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
return mistral_client.services.list()

View File

@ -1,222 +0,0 @@
# Copyright 2014 - Mirantis, Inc.
# Copyright 2015 - StackStorm, 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 json
import logging
import os.path
from osc_lib.command import command
from mistralclient.commands.v2 import base
from mistralclient import utils
LOG = logging.getLogger(__name__)
def format_list(task=None):
return format(task, lister=True)
def format(task=None, lister=False):
columns = (
'ID',
'Name',
'Workflow name',
'Execution ID',
'State',
'State info',
'Created at',
'Updated at'
)
if task:
state_info = (task.state_info if not lister
else base.cut(task.state_info))
data = (
task.id,
task.name,
task.workflow_name,
task.workflow_execution_id,
task.state,
state_info,
task.created_at,
task.updated_at or '<none>'
)
else:
data = (tuple('<none>' for _ in range(len(columns))),)
return columns, data
class List(base.MistralLister):
"""List all tasks."""
def get_parser(self, prog_name):
parser = super(List, self).get_parser(prog_name)
parser.add_argument(
'workflow_execution',
nargs='?',
help='Workflow execution ID associated with list of Tasks.'
)
parser.add_argument(
'--filter',
dest='filters',
action='append',
help='Filters. Can be repeated.'
)
parser.add_argument(
'--limit',
type=int,
help='Maximum number of tasks to return in a single result. '
'limit is set to %s by default. Use --limit -1 to fetch the '
'full result set.' % base.DEFAULT_LIMIT,
nargs='?'
)
return parser
def _get_format_function(self):
return format_list
def _get_resources(self, parsed_args):
if parsed_args.limit is None:
parsed_args.limit = base.DEFAULT_LIMIT
LOG.info("limit is set to %s by default. Set "
"the limit explicitly using \'--limit\', if required. "
"Use \'--limit\' -1 to fetch the full result set.",
base.DEFAULT_LIMIT)
mistral_client = self.app.client_manager.workflow_engine
return mistral_client.tasks.list(
parsed_args.workflow_execution,
limit=parsed_args.limit,
**base.get_filters(parsed_args)
)
class Get(command.ShowOne):
"""Show specific task."""
def get_parser(self, prog_name):
parser = super(Get, self).get_parser(prog_name)
parser.add_argument('task', help='Task identifier')
return parser
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
execution = mistral_client.tasks.get(parsed_args.task)
return format(execution)
class GetResult(command.Command):
"""Show task output data."""
def get_parser(self, prog_name):
parser = super(GetResult, self).get_parser(prog_name)
parser.add_argument(
'id',
help='Task ID')
return parser
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
result = mistral_client.tasks.get(parsed_args.id).result
try:
result = json.loads(result)
result = json.dumps(result, indent=4) + "\n"
except Exception:
LOG.debug("Task result is not JSON.")
self.app.stdout.write(result or "\n")
class GetPublished(command.Command):
"""Show task published variables."""
def get_parser(self, prog_name):
parser = super(GetPublished, self).get_parser(prog_name)
parser.add_argument(
'id',
help='Task ID')
return parser
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
result = mistral_client.tasks.get(parsed_args.id).published
try:
result = json.loads(result)
result = json.dumps(result, indent=4) + "\n"
except Exception:
LOG.debug("Task result is not JSON.")
self.app.stdout.write(result or "\n")
class Rerun(command.ShowOne):
"""Rerun an existing task."""
def get_parser(self, prog_name):
parser = super(Rerun, self).get_parser(prog_name)
parser.add_argument(
'id',
help='Task identifier'
)
parser.add_argument(
'--resume',
action='store_true',
dest='resume',
default=False,
help=('rerun only failed or unstarted action '
'executions for with-items task')
)
parser.add_argument(
'-e',
'--env',
dest='env',
help='Environment variables'
)
return parser
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
env = (
utils.load_file(parsed_args.env)
if parsed_args.env and os.path.isfile(parsed_args.env)
else utils.load_content(parsed_args.env)
)
execution = mistral_client.tasks.rerun(
parsed_args.id,
reset=(not parsed_args.resume),
env=env
)
return format(execution)

View File

@ -1,194 +0,0 @@
# Copyright 2014 - Mirantis, Inc.
# Copyright 2015 - StackStorm, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import argparse
from osc_lib.command import command
from mistralclient.commands.v2 import base
from mistralclient import utils
def format(workbook=None):
columns = (
'Name',
'Tags',
'Created at',
'Updated at'
)
if workbook:
data = (
workbook.name,
base.wrap(', '.join(workbook.tags or '')) or '<none>',
workbook.created_at,
)
if hasattr(workbook, 'updated_at'):
data += (workbook.updated_at,)
else:
data += (None,)
else:
data = (tuple('<none>' for _ in range(len(columns))),)
return columns, data
class List(base.MistralLister):
"""List all workbooks."""
def _get_format_function(self):
return format
def _get_resources(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
return mistral_client.workbooks.list()
class Get(command.ShowOne):
"""Show specific workbook."""
def get_parser(self, prog_name):
parser = super(Get, self).get_parser(prog_name)
parser.add_argument(
'workbook',
help='Workbook name'
)
return parser
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
workbook = mistral_client.workbooks.get(parsed_args.workbook)
return format(workbook)
class Create(command.ShowOne):
"""Create new workbook."""
def get_parser(self, prog_name):
parser = super(Create, self).get_parser(prog_name)
parser.add_argument(
'definition',
type=argparse.FileType('r'),
help='Workbook definition file'
)
return parser
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
workbook = mistral_client.workbooks.create(
parsed_args.definition.read()
)
return format(workbook)
class Delete(command.Command):
"""Delete workbook."""
def get_parser(self, prog_name):
parser = super(Delete, self).get_parser(prog_name)
parser.add_argument('workbook', nargs='+', help='Name of workbook(s).')
return parser
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
utils.do_action_on_many(
lambda s: mistral_client.workbooks.delete(s),
parsed_args.workbook,
"Request to delete workbook %s has been accepted.",
"Unable to delete the specified workbook(s)."
)
class Update(command.ShowOne):
"""Update workbook."""
def get_parser(self, prog_name):
parser = super(Update, self).get_parser(prog_name)
parser.add_argument(
'definition',
type=argparse.FileType('r'),
help='Workbook definition file'
)
return parser
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
workbook = mistral_client.workbooks.update(
parsed_args.definition.read()
)
return format(workbook)
class GetDefinition(command.Command):
"""Show workbook definition."""
def get_parser(self, prog_name):
parser = super(GetDefinition, self).get_parser(prog_name)
parser.add_argument('name', help='Workbook name')
return parser
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
definition = mistral_client.workbooks.get(parsed_args.name).definition
self.app.stdout.write(definition or "\n")
class Validate(command.ShowOne):
"""Validate workbook."""
def _format(self, result=None):
columns = ('Valid', 'Error')
if result:
data = (result.get('valid'), result.get('error'),)
else:
data = (tuple('<none>' for _ in range(len(columns))),)
return columns, data
def get_parser(self, prog_name):
parser = super(Validate, self).get_parser(prog_name)
parser.add_argument(
'definition',
type=argparse.FileType('r'),
help='Workbook definition file'
)
return parser
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
result = mistral_client.workbooks.validate(
parsed_args.definition.read()
)
return self._format(result)

View File

@ -1,247 +0,0 @@
# Copyright 2014 - Mirantis, Inc.
# Copyright 2015 - StackStorm, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import argparse
from cliff import command
from cliff import show
from mistralclient.commands.v2 import base
from mistralclient import utils
def format_list(workflow=None):
return format(workflow, lister=True)
def format(workflow=None, lister=False):
columns = (
'ID',
'Name',
'Project ID',
'Tags',
'Input',
'Created at',
'Updated at'
)
if workflow:
tags = getattr(workflow, 'tags', None) or []
data = (
workflow.id,
workflow.name,
workflow.project_id,
base.wrap(', '.join(tags)) or '<none>',
workflow.input if not lister else base.cut(workflow.input),
workflow.created_at
)
if hasattr(workflow, 'updated_at'):
data += (workflow.updated_at,)
else:
data += (None,)
else:
data = (tuple('<none>' for _ in range(len(columns))),)
return columns, data
class List(base.MistralLister):
"""List all workflows."""
def _get_format_function(self):
return format_list
def get_parser(self, prog_name):
parser = super(List, self).get_parser(prog_name)
parser.add_argument(
'--filter',
dest='filters',
action='append',
help='Filters. Can be repeated.'
)
return parser
def _get_resources(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
return mistral_client.workflows.list(
**base.get_filters(parsed_args)
)
class Get(show.ShowOne):
"""Show specific workflow."""
def get_parser(self, prog_name):
parser = super(Get, self).get_parser(prog_name)
parser.add_argument('workflow', help='Workflow ID or name.')
return parser
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
wf = mistral_client.workflows.get(parsed_args.workflow)
return format(wf)
class Create(base.MistralLister):
"""Create new workflow."""
def get_parser(self, prog_name):
parser = super(Create, self).get_parser(prog_name)
parser.add_argument(
'definition',
type=argparse.FileType('r'),
help='Workflow definition file.'
)
parser.add_argument(
'--public',
action='store_true',
help='With this flag workflow will be marked as "public".'
)
return parser
def _get_format_function(self):
return format_list
def _validate_parsed_args(self, parsed_args):
if not parsed_args.definition:
raise RuntimeError("You must provide path to workflow "
"definition file.")
def _get_resources(self, parsed_args):
scope = 'public' if parsed_args.public else 'private'
mistral_client = self.app.client_manager.workflow_engine
return mistral_client.workflows.create(
parsed_args.definition.read(),
scope=scope
)
class Delete(command.Command):
"""Delete workflow."""
def get_parser(self, prog_name):
parser = super(Delete, self).get_parser(prog_name)
parser.add_argument(
'workflow',
nargs='+',
help='Name or ID of workflow(s).'
)
return parser
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
utils.do_action_on_many(
lambda s: mistral_client.workflows.delete(s),
parsed_args.workflow,
"Request to delete workflow %s has been accepted.",
"Unable to delete the specified workflow(s)."
)
class Update(base.MistralLister):
"""Update workflow."""
def get_parser(self, prog_name):
parser = super(Update, self).get_parser(prog_name)
parser.add_argument(
'definition',
type=argparse.FileType('r'),
help='Workflow definition'
)
parser.add_argument('--id', help='Workflow ID.')
parser.add_argument(
'--public',
action='store_true',
help='With this flag workflow will be marked as "public".'
)
return parser
def _get_format_function(self):
return format_list
def _get_resources(self, parsed_args):
scope = 'public' if parsed_args.public else 'private'
mistral_client = self.app.client_manager.workflow_engine
return mistral_client.workflows.update(
parsed_args.definition.read(),
scope=scope,
id=parsed_args.id
)
class GetDefinition(command.Command):
"""Show workflow definition."""
def get_parser(self, prog_name):
parser = super(GetDefinition, self).get_parser(prog_name)
parser.add_argument('identifier', help='Workflow ID or name.')
return parser
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
wf = mistral_client.workflows.get(parsed_args.identifier)
self.app.stdout.write(wf.definition or "\n")
class Validate(show.ShowOne):
"""Validate workflow."""
def _format(self, result=None):
columns = ('Valid', 'Error')
if result:
data = (result.get('valid'), result.get('error'),)
else:
data = (tuple('<none>' for _ in range(len(columns))),)
return columns, data
def get_parser(self, prog_name):
parser = super(Validate, self).get_parser(prog_name)
parser.add_argument(
'definition',
type=argparse.FileType('r'),
help='Workflow definition file'
)
return parser
def take_action(self, parsed_args):
mistral_client = self.app.client_manager.workflow_engine
result = mistral_client.workflows.validate(
parsed_args.definition.read()
)
return self._format(result)

View File

@ -1,40 +0,0 @@
# Copyright 2013 - Mirantis, 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.
class MistralClientException(Exception):
"""Base Exception for Mistral client
To correctly use this class, inherit from it and define
a 'message' and 'code' properties.
"""
message = "An unknown exception occurred"
code = "UNKNOWN_EXCEPTION"
def __str__(self):
return self.message
def __init__(self, message=message):
self.message = message
super(MistralClientException, self).__init__(
'%s: %s' % (self.code, self.message))
class IllegalArgumentException(MistralClientException):
message = "IllegalArgumentException occurred"
code = "ILLEGAL_ARGUMENT_EXCEPTION"
def __init__(self, message=None):
if message:
self.message = message

View File

@ -1,22 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""oslo.i18n integration module.
See https://docs.openstack.org/oslo.i18n/latest/user/usage.html
"""
import oslo_i18n
_translators = oslo_i18n.TranslatorFactory(domain='mistralclient')
# The primary translation function using the well-known name "_"
_ = _translators.primary

View File

@ -1,62 +0,0 @@
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""OpenStackClient plugin for Workflow service."""
import logging
from osc_lib import utils
LOG = logging.getLogger(__name__)
DEFAULT_WORKFLOW_API_VERSION = '2'
API_VERSION_OPTION = 'os_workflow_api_version'
API_NAME = 'workflow_engine'
API_VERSIONS = {
'2': 'mistralclient.api.v2.client.Client',
}
def make_client(instance):
"""Returns a workflow_engine service client."""
version = instance._api_version[API_NAME]
workflow_client = utils.get_client_class(
API_NAME,
version,
API_VERSIONS)
LOG.debug('Instantiating workflow engine client: %s', workflow_client)
mistral_url = instance.get_endpoint_for_service_type(
'workflowv2',
interface='publicURL'
)
client = workflow_client(mistral_url=mistral_url, session=instance.session)
return client
def build_option_parser(parser):
"""Hook to add global options."""
parser.add_argument(
'--os-workflow-api-version',
metavar='<workflow-api-version>',
default=utils.env(
'OS_WORKFLOW_API_VERSION',
default=DEFAULT_WORKFLOW_API_VERSION),
help='Workflow API version, default=' +
DEFAULT_WORKFLOW_API_VERSION +
' (Env: OS_WORKFLOW_API_VERSION)')
return parser

View File

@ -1,709 +0,0 @@
# Copyright 2015 - StackStorm, 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.
"""
Command-line interface to the Mistral APIs
"""
import argparse
import logging
import os
import sys
from cliff import app
from cliff import commandmanager
from osc_lib.command import command
from mistralclient.api import client
from mistralclient.auth import auth_types
import mistralclient.commands.v2.action_executions
import mistralclient.commands.v2.actions
import mistralclient.commands.v2.cron_triggers
import mistralclient.commands.v2.environments
import mistralclient.commands.v2.event_triggers
import mistralclient.commands.v2.executions
import mistralclient.commands.v2.members
import mistralclient.commands.v2.services
import mistralclient.commands.v2.tasks
import mistralclient.commands.v2.workbooks
import mistralclient.commands.v2.workflows
from mistralclient import exceptions as exe
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', '')
class OpenStackHelpFormatter(argparse.HelpFormatter):
def __init__(self, prog, indent_increment=2, max_help_position=32,
width=None):
super(OpenStackHelpFormatter, self).__init__(
prog,
indent_increment,
max_help_position,
width
)
def start_section(self, heading):
# Title-case the headings.
heading = '%s%s' % (heading[0].upper(), heading[1:])
super(OpenStackHelpFormatter, self).start_section(heading)
class HelpAction(argparse.Action):
"""Custom help action.
Provide a custom action so the -h and --help options
to the main app will print a list of the commands.
The commands are determined by checking the CommandManager
instance, passed in as the "default" value for the action.
"""
def __call__(self, parser, namespace, values, option_string=None):
outputs = []
max_len = 0
app = self.default
parser.print_help(app.stdout)
app.stdout.write('\nCommands for API v2 :\n')
for name, ep in sorted(app.command_manager):
factory = ep.load()
cmd = factory(self, None)
one_liner = cmd.get_description().split('\n')[0]
outputs.append((name, one_liner))
max_len = max(len(name), max_len)
for (name, one_liner) in outputs:
app.stdout.write(' %s %s\n' % (name.ljust(max_len), one_liner))
sys.exit(0)
class BashCompletionCommand(command.Command):
"""Prints all of the commands and options for bash-completion."""
def take_action(self, parsed_args):
commands = set()
options = set()
for option, _action in self.app.parser._option_string_actions.items():
options.add(option)
for command_name, _cmd in self.app.command_manager:
commands.add(command_name)
print(' '.join(commands | options))
class MistralShell(app.App):
def __init__(self):
super(MistralShell, self).__init__(
description=__doc__.strip(),
version=mistralclient.__version__,
command_manager=commandmanager.CommandManager('mistral.cli'),
)
# Set v2 commands by default
self._set_shell_commands(self._get_commands_v2())
def configure_logging(self):
log_lvl = logging.DEBUG if self.options.debug else logging.WARNING
logging.basicConfig(
format="%(levelname)s (%(module)s) %(message)s",
level=log_lvl
)
logging.getLogger('iso8601').setLevel(logging.WARNING)
if self.options.verbose_level <= 1:
logging.getLogger('requests').setLevel(logging.WARNING)
def build_option_parser(self, description, version,
argparse_kwargs=None):
"""Return an argparse option parser for this application.
Subclasses may override this method to extend
the parser with more global options.
:param description: full description of the application
:paramtype description: str
:param version: version number for the application
:paramtype version: str
:param argparse_kwargs: extra keyword argument passed to the
ArgumentParser constructor
:paramtype extra_kwargs: dict
"""
argparse_kwargs = argparse_kwargs or {}
parser = argparse.ArgumentParser(
description=description,
add_help=False,
formatter_class=OpenStackHelpFormatter,
**argparse_kwargs
)
parser.add_argument(
'--version',
action='version',
version='%(prog)s {0}'.format(version),
help='Show program\'s version number and exit.'
)
parser.add_argument(
'-v', '--verbose',
action='count',
dest='verbose_level',
default=self.DEFAULT_VERBOSE_LEVEL,
help='Increase verbosity of output. Can be repeated.',
)
parser.add_argument(
'--log-file',
action='store',
default=None,
help='Specify a file to log output. Disabled by default.',
)
parser.add_argument(
'-q', '--quiet',
action='store_const',
dest='verbose_level',
const=0,
help='Suppress output except warnings and errors.',
)
parser.add_argument(
'-h', '--help',
action=HelpAction,
nargs=0,
default=self, # tricky
help="Show this help message and exit.",
)
parser.add_argument(
'--debug',
default=False,
action='store_true',
help='Show tracebacks on errors.',
)
parser.add_argument(
'--os-mistral-url',
action='store',
dest='mistral_url',
default=env('OS_MISTRAL_URL'),
help='Mistral API host (Env: OS_MISTRAL_URL)'
)
parser.add_argument(
'--os-mistral-version',
action='store',
dest='mistral_version',
default=env('OS_MISTRAL_VERSION', default='v2'),
help='Mistral API version (default = v2) (Env: '
'OS_MISTRAL_VERSION)'
)
parser.add_argument(
'--os-mistral-service-type',
action='store',
dest='service_type',
default=env('OS_MISTRAL_SERVICE_TYPE', default='workflowv2'),
help='Mistral service-type (should be the same name as in '
'keystone-endpoint) (default = workflowv2) (Env: '
'OS_MISTRAL_SERVICE_TYPE)'
)
parser.add_argument(
'--os-mistral-endpoint-type',
action='store',
dest='endpoint_type',
default=env('OS_MISTRAL_ENDPOINT_TYPE', default='publicURL'),
help='Mistral endpoint-type (should be the same name as in '
'keystone-endpoint) (default = publicURL) (Env: '
'OS_MISTRAL_ENDPOINT_TYPE)'
)
parser.add_argument(
'--os-username',
action='store',
dest='username',
default=env('OS_USERNAME'),
help='Authentication username (Env: OS_USERNAME)'
)
parser.add_argument(
'--os-password',
action='store',
dest='password',
default=env('OS_PASSWORD'),
help='Authentication password (Env: OS_PASSWORD)'
)
parser.add_argument(
'--os-tenant-id',
action='store',
dest='tenant_id',
default=env('OS_TENANT_ID', 'OS_PROJECT_ID'),
help='Authentication tenant identifier (Env: OS_TENANT_ID'
' or OS_PROJECT_ID)'
)
parser.add_argument(
'--os-project-id',
action='store',
dest='project_id',
default=env('OS_TENANT_ID', 'OS_PROJECT_ID'),
help='Authentication project identifier (Env: OS_TENANT_ID'
' or OS_PROJECT_ID), will use tenant_id if both tenant_id'
' and project_id are set'
)
parser.add_argument(
'--os-tenant-name',
action='store',
dest='tenant_name',
default=env('OS_TENANT_NAME', 'OS_PROJECT_NAME',
default='Default'),
help='Authentication tenant name (Env: OS_TENANT_NAME'
' or OS_PROJECT_NAME)'
)
parser.add_argument(
'--os-project-name',
action='store',
dest='project_name',
default=env('OS_TENANT_NAME', 'OS_PROJECT_NAME',
default='Default'),
help='Authentication project name (Env: OS_TENANT_NAME'
' or OS_PROJECT_NAME), will use tenant_name if both'
' tenant_name and project_name are set'
)
parser.add_argument(
'--os-auth-token',
action='store',
dest='token',
default=env('OS_AUTH_TOKEN'),
help='Authentication token (Env: OS_AUTH_TOKEN)'
)
parser.add_argument(
'--os-project-domain-name',
action='store',
dest='project_domain_name',
default=env('OS_PROJECT_DOMAIN_NAME', default='Default'),
help='Authentication project domain name'
' (Env: OS_PROJECT_DOMAIN_NAME)'
)
parser.add_argument(
'--os-user-domain-name',
action='store',
dest='user_domain_name',
default=env('OS_USER_DOMAIN_NAME', default='Default'),
help='Authentication user domain name'
' (Env: OS_USER_DOMAIN_NAME)'
)
parser.add_argument(
'--os-auth-url',
action='store',
dest='auth_url',
default=env('OS_AUTH_URL'),
help='Authentication URL (Env: OS_AUTH_URL)'
)
parser.add_argument(
'--os-cert',
action='store',
dest='os_cert',
default=env('OS_CERT'),
help='Client Certificate (Env: OS_CERT)'
)
parser.add_argument(
'--os-key',
action='store',
dest='os_key',
default=env('OS_KEY'),
help='Client Key (Env: OS_KEY)'
)
parser.add_argument(
'--os-cacert',
action='store',
dest='os_cacert',
default=env('OS_CACERT'),
help='Authentication CA Certificate (Env: OS_CACERT)'
)
parser.add_argument(
'--os-region-name',
action='store',
dest='region_name',
default=env('OS_REGION_NAME'),
help='Region name (Env: OS_REGION_NAME)'
)
parser.add_argument(
'--insecure',
action='store_true',
dest='insecure',
default=env('MISTRALCLIENT_INSECURE', default=False),
help='Disables SSL/TLS certificate verification '
'(Env: MISTRALCLIENT_INSECURE)'
)
parser.add_argument(
'--auth-type',
action='store',
dest='auth_type',
default=env('MISTRAL_AUTH_TYPE', default='keystone'),
help='Authentication type. Valid options are: %s.'
' (Env: MISTRAL_AUTH_TYPE)' % ', '.join(auth_types.ALL)
)
parser.add_argument(
'--openid-client-id',
action='store',
dest='client_id',
default=env('OPENID_CLIENT_ID'),
help='Client ID (according to OpenID Connect).'
' (Env: OPENID_CLIENT_ID)'
)
parser.add_argument(
'--openid-client-secret',
action='store',
dest='client_secret',
default=env('OPENID_CLIENT_SECRET'),
help='Client secret (according to OpenID Connect)'
' (Env: OPENID_CLIENT_SECRET)'
)
parser.add_argument(
'--os-target-username',
action='store',
dest='target_username',
default=env('OS_TARGET_USERNAME', default='admin'),
help='Authentication username for target cloud'
' (Env: OS_TARGET_USERNAME)'
)
parser.add_argument(
'--os-target-password',
action='store',
dest='target_password',
default=env('OS_TARGET_PASSWORD'),
help='Authentication password for target cloud'
' (Env: OS_TARGET_PASSWORD)'
)
parser.add_argument(
'--os-target-tenant-id',
action='store',
dest='target_tenant_id',
default=env('OS_TARGET_TENANT_ID'),
help='Authentication tenant identifier for target cloud'
' (Env: OS_TARGET_TENANT_ID)'
)
parser.add_argument(
'--os-target-tenant-name',
action='store',
dest='target_tenant_name',
default=env('OS_TARGET_TENANT_NAME', 'Default'),
help='Authentication tenant name for target cloud'
' (Env: OS_TARGET_TENANT_NAME)'
)
parser.add_argument(
'--os-target-auth-token',
action='store',
dest='target_token',
default=env('OS_TARGET_AUTH_TOKEN'),
help='Authentication token for target cloud'
' (Env: OS_TARGET_AUTH_TOKEN)'
)
parser.add_argument(
'--os-target-auth-url',
action='store',
dest='target_auth_url',
default=env('OS_TARGET_AUTH_URL'),
help='Authentication URL for target cloud'
' (Env: OS_TARGET_AUTH_URL)'
)
parser.add_argument(
'--os-target_cacert',
action='store',
dest='target_cacert',
default=env('OS_TARGET_CACERT'),
help='Authentication CA Certificate for target cloud'
' (Env: OS_TARGET_CACERT)'
)
parser.add_argument(
'--os-target-region-name',
action='store',
dest='target_region_name',
default=env('OS_TARGET_REGION_NAME'),
help='Region name for target cloud'
'(Env: OS_TARGET_REGION_NAME)'
)
parser.add_argument(
'--os-target-user-domain-name',
action='store',
dest='target_user_domain_name',
default=env('OS_TARGET_USER_DOMAIN_NAME'),
help='User domain name for target cloud'
'(Env: OS_TARGET_USER_DOMAIN_NAME)'
)
parser.add_argument(
'--os-target-project-domain-name',
action='store',
dest='target_project_domain_name',
default=env('OS_TARGET_PROJECT_DOMAIN_NAME'),
help='Project domain name for target cloud'
'(Env: OS_TARGET_PROJECT_DOMAIN_NAME)'
)
parser.add_argument(
'--target_insecure',
action='store_true',
dest='target_insecure',
default=env('TARGET_MISTRALCLIENT_INSECURE', default=False),
help='Disables SSL/TLS certificate verification for target cloud '
'(Env: TARGET_MISTRALCLIENT_INSECURE)'
)
parser.add_argument(
'--profile',
dest='profile',
metavar='HMAC_KEY',
default=env('OS_PROFILE'),
help='HMAC key to use for encrypting context data for performance '
'profiling of operation. This key should be one of the '
'values configured for the osprofiler middleware in mistral, '
'it is specified in the profiler section of the mistral '
'configuration (i.e. /etc/mistral/mistral.conf). Without the '
'key, profiling will not be triggered even if osprofiler is '
'enabled on the server side.'
)
return parser
def initialize_app(self, argv):
self._clear_shell_commands()
ver = client.determine_client_version(self.options.mistral_version)
self._set_shell_commands(self._get_commands(ver))
do_help = ('help' in argv) or ('-h' in argv) or not argv
# Set default for auth_url if not supplied. The default is not
# set at the parser to support use cases where auth is not enabled.
# An example use case would be a developer's environment.
if not self.options.auth_url:
if self.options.password or self.options.token:
self.options.auth_url = 'http://localhost:35357/v3'
# bash-completion should not require authentification.
if do_help or ('bash-completion' in argv):
self.options.auth_url = None
if self.options.auth_url and not self.options.token:
if not self.options.username:
raise exe.IllegalArgumentException(
("You must provide a username "
"via --os-username env[OS_USERNAME]")
)
if not self.options.password:
raise exe.IllegalArgumentException(
("You must provide a password "
"via --os-password env[OS_PASSWORD]")
)
kwargs = {
'cert': self.options.os_cert,
'key': self.options.os_key,
'user_domain_name': self.options.user_domain_name,
'project_domain_name': self.options.project_domain_name
}
self.client = client.client(
mistral_url=self.options.mistral_url,
username=self.options.username,
api_key=self.options.password,
project_name=self.options.tenant_name or self.options.project_name,
auth_url=self.options.auth_url,
project_id=self.options.tenant_id or self.options.project_id,
endpoint_type=self.options.endpoint_type,
service_type=self.options.service_type,
region_name=self.options.region_name,
auth_token=self.options.token,
cacert=self.options.os_cacert,
insecure=self.options.insecure,
profile=self.options.profile,
auth_type=self.options.auth_type,
client_id=self.options.client_id,
client_secret=self.options.client_secret,
target_username=self.options.target_username,
target_api_key=self.options.target_password,
target_project_name=self.options.target_tenant_name,
target_auth_url=self.options.target_auth_url,
target_project_id=self.options.target_tenant_id,
target_auth_token=self.options.target_token,
target_cacert=self.options.target_cacert,
target_region_name=self.options.target_region_name,
target_insecure=self.options.target_insecure,
**kwargs
)
# Adding client_manager variable to make mistral client work with
# unified OpenStack client.
ClientManager = type(
'ClientManager',
(object,),
dict(workflow_engine=self.client)
)
self.client_manager = ClientManager()
def _set_shell_commands(self, cmds_dict):
for k, v in cmds_dict.items():
self.command_manager.add_command(k, v)
def _clear_shell_commands(self):
exclude_cmds = ['help', 'complete']
cmds = self.command_manager.commands.copy()
for k, v in cmds.items():
if k not in exclude_cmds:
self.command_manager.commands.pop(k)
def _get_commands(self, version):
if version == 2:
return self._get_commands_v2()
return {}
@staticmethod
def _get_commands_v2():
return {
'bash-completion': BashCompletionCommand,
'workbook-list': mistralclient.commands.v2.workbooks.List,
'workbook-get': mistralclient.commands.v2.workbooks.Get,
'workbook-create': mistralclient.commands.v2.workbooks.Create,
'workbook-delete': mistralclient.commands.v2.workbooks.Delete,
'workbook-update': mistralclient.commands.v2.workbooks.Update,
'workbook-get-definition':
mistralclient.commands.v2.workbooks.GetDefinition,
'workbook-validate': mistralclient.commands.v2.workbooks.Validate,
'workflow-list': mistralclient.commands.v2.workflows.List,
'workflow-get': mistralclient.commands.v2.workflows.Get,
'workflow-create': mistralclient.commands.v2.workflows.Create,
'workflow-delete': mistralclient.commands.v2.workflows.Delete,
'workflow-update': mistralclient.commands.v2.workflows.Update,
'workflow-get-definition':
mistralclient.commands.v2.workflows.GetDefinition,
'workflow-validate': mistralclient.commands.v2.workflows.Validate,
'environment-create':
mistralclient.commands.v2.environments.Create,
'environment-delete':
mistralclient.commands.v2.environments.Delete,
'environment-update':
mistralclient.commands.v2.environments.Update,
'environment-list': mistralclient.commands.v2.environments.List,
'environment-get': mistralclient.commands.v2.environments.Get,
'run-action': mistralclient.commands.v2.action_executions.Create,
'action-execution-list':
mistralclient.commands.v2.action_executions.List,
'action-execution-get':
mistralclient.commands.v2.action_executions.Get,
'action-execution-get-input':
mistralclient.commands.v2.action_executions.GetInput,
'action-execution-get-output':
mistralclient.commands.v2.action_executions.GetOutput,
'action-execution-update':
mistralclient.commands.v2.action_executions.Update,
'action-execution-delete':
mistralclient.commands.v2.action_executions.Delete,
'execution-create': mistralclient.commands.v2.executions.Create,
'execution-delete': mistralclient.commands.v2.executions.Delete,
'execution-update': mistralclient.commands.v2.executions.Update,
'execution-list': mistralclient.commands.v2.executions.List,
'execution-get': mistralclient.commands.v2.executions.Get,
'execution-get-input':
mistralclient.commands.v2.executions.GetInput,
'execution-get-output':
mistralclient.commands.v2.executions.GetOutput,
'task-list': mistralclient.commands.v2.tasks.List,
'task-get': mistralclient.commands.v2.tasks.Get,
'task-get-published': mistralclient.commands.v2.tasks.GetPublished,
'task-get-result': mistralclient.commands.v2.tasks.GetResult,
'task-rerun': mistralclient.commands.v2.tasks.Rerun,
'action-list': mistralclient.commands.v2.actions.List,
'action-get': mistralclient.commands.v2.actions.Get,
'action-create': mistralclient.commands.v2.actions.Create,
'action-delete': mistralclient.commands.v2.actions.Delete,
'action-update': mistralclient.commands.v2.actions.Update,
'action-get-definition':
mistralclient.commands.v2.actions.GetDefinition,
'action-validate': mistralclient.commands.v2.actions.Validate,
'cron-trigger-list': mistralclient.commands.v2.cron_triggers.List,
'cron-trigger-get': mistralclient.commands.v2.cron_triggers.Get,
'cron-trigger-create':
mistralclient.commands.v2.cron_triggers.Create,
'cron-trigger-delete':
mistralclient.commands.v2.cron_triggers.Delete,
'event-trigger-list':
mistralclient.commands.v2.event_triggers.List,
'event-trigger-get': mistralclient.commands.v2.event_triggers.Get,
'event-trigger-create':
mistralclient.commands.v2.event_triggers.Create,
'event-trigger-delete':
mistralclient.commands.v2.event_triggers.Delete,
'service-list': mistralclient.commands.v2.services.List,
'member-create': mistralclient.commands.v2.members.Create,
'member-delete': mistralclient.commands.v2.members.Delete,
'member-update': mistralclient.commands.v2.members.Update,
'member-list': mistralclient.commands.v2.members.List,
'member-get': mistralclient.commands.v2.members.Get,
}
def main(argv=sys.argv[1:]):
return MistralShell().run(argv)
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))

View File

@ -1,148 +0,0 @@
# Copyright (c) 2014 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
from six.moves import configparser
from tempest.lib.cli import base
CLI_DIR = os.environ.get(
'OS_MISTRALCLIENT_EXEC_DIR',
os.path.join(os.path.abspath('.'), '.tox/functional/bin')
)
_CREDS_FILE = 'functional_creds.conf'
def credentials(group='admin'):
"""Retrieves credentials to run functional tests.
Credentials are either read from the environment or from a config file
('functional_creds.conf'). Environment variables override those from the
config file.
The 'functional_creds.conf' file is the clean and new way to use (by
default tox 2.0 does not pass environment variables).
"""
if group == 'admin':
username = os.environ.get('OS_USERNAME')
password = os.environ.get('OS_PASSWORD')
tenant_name = os.environ.get('OS_TENANT_NAME')
else:
username = os.environ.get('OS_ALT_USERNAME')
password = os.environ.get('OS_ALT_PASSWORD')
tenant_name = os.environ.get('OS_ALT_TENANT_NAME')
auth_url = os.environ.get('OS_AUTH_URL')
config = configparser.RawConfigParser()
if config.read(_CREDS_FILE):
username = username or config.get(group, 'user')
password = password or config.get(group, 'pass')
tenant_name = tenant_name or config.get(group, 'tenant')
auth_url = auth_url or config.get('auth', 'uri')
# TODO(ddeja): Default value of OS_AUTH_URL is to provide url to v3 API.
# Since tempest openstack client doesn't properly handle it, we switch
# it back to v2. Once tempest openstack starts to use v3, this can be
# deleted.
# https://github.com/openstack/tempest/blob/master/tempest/lib/cli/base.py#L363
return {
'username': username,
'password': password,
'tenant_name': tenant_name,
'auth_url': auth_url.replace('v3', 'v2.0')
}
class MistralCLIAuth(base.ClientTestBase):
_mistral_url = None
def _get_admin_clients(self):
creds = credentials()
clients = base.CLIClient(
username=creds['username'],
password=creds['password'],
tenant_name=creds['tenant_name'],
uri=creds['auth_url'],
cli_dir=CLI_DIR
)
return clients
def _get_clients(self):
return self._get_admin_clients()
def mistral(self, action, flags='', params='', fail_ok=False):
"""Executes Mistral command."""
mistral_url_op = "--os-mistral-url %s" % self._mistral_url
if 'WITHOUT_AUTH' in os.environ:
return base.execute(
'mistral %s' % mistral_url_op,
action,
flags,
params,
fail_ok,
merge_stderr=False,
cli_dir=''
)
else:
return self.clients.cmd_with_auth(
'mistral %s' % mistral_url_op,
action,
flags,
params,
fail_ok
)
def get_project_id(self, project='admin'):
project_name = credentials(project)['tenant_name']
admin_clients = self._get_clients()
projects = self.parser.listing(
admin_clients.openstack('project show', params=project_name)
)
return [o['Value'] for o in projects if o['Field'] == 'id'][0]
class MistralCLIAltAuth(base.ClientTestBase):
_mistral_url = None
def _get_alt_clients(self):
creds = credentials('demo')
clients = base.CLIClient(
username=creds['username'],
password=creds['password'],
tenant_name=creds['tenant_name'],
uri=creds['auth_url'],
cli_dir=CLI_DIR
)
return clients
def _get_clients(self):
return self._get_alt_clients()
def mistral_alt(self, action, flags='', params='', mode='alt_user'):
"""Executes Mistral command for alt_user from alt_tenant."""
mistral_url_op = "--os-mistral-url %s" % self._mistral_url
return self.clients.cmd_with_auth(
'mistral %s' % mistral_url_op, action, flags, params)

View File

@ -1,279 +0,0 @@
# Copyright (c) 2014 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
import time
from tempest.lib import exceptions
from mistralclient.tests.functional.cli import base
MISTRAL_URL = "http://localhost:8989/v2"
class MistralClientTestBase(base.MistralCLIAuth, base.MistralCLIAltAuth):
_mistral_url = MISTRAL_URL
@classmethod
def setUpClass(cls):
super(MistralClientTestBase, cls).setUpClass()
cls.wb_def = os.path.relpath(
'functionaltests/resources/v2/wb_v2.yaml', os.getcwd()
)
cls.wb_with_tags_def = os.path.relpath(
'functionaltests/resources/v2/wb_with_tags_v2.yaml', os.getcwd()
)
cls.wf_def = os.path.relpath(
'functionaltests/resources/v2/wf_v2.yaml', os.getcwd()
)
cls.wf_single_def = os.path.relpath(
'functionaltests/resources/v2/wf_single_v2.yaml', os.getcwd()
)
cls.wf_with_delay_def = os.path.relpath(
'functionaltests/resources/v2/wf_delay_v2.yaml', os.getcwd()
)
cls.wf_wrapping_wf = os.path.relpath(
'functionaltests/resources/v2/wf_wrapping_wf_v2.yaml', os.getcwd()
)
cls.act_def = os.path.relpath(
'functionaltests/resources/v2/action_v2.yaml', os.getcwd()
)
cls.act_tag_def = os.path.relpath(
'functionaltests/resources/v2/action_v2_tags.yaml', os.getcwd()
)
def setUp(self):
super(MistralClientTestBase, self).setUp()
def get_field_value(self, obj, field):
return [
o['Value'] for o in obj
if o['Field'] == "{0}".format(field)
][0]
def get_item_info(self, get_from, get_by, value):
return [i for i in get_from if i[get_by] == value][0]
def mistral_admin(self, cmd, params=""):
self.clients = self._get_admin_clients()
return self.parser.listing(
self.mistral('{0}'.format(cmd), params='{0}'.format(params))
)
def mistral_alt_user(self, cmd, params=""):
self.clients = self._get_alt_clients()
return self.parser.listing(
self.mistral_alt('{0}'.format(cmd), params='{0}'.format(params))
)
def mistral_cli(self, admin, cmd, params=''):
if admin:
return self.mistral_admin(cmd, params)
else:
return self.mistral_alt_user(cmd, params)
def workbook_create(self, wb_def, admin=True):
wb = self.mistral_cli(
admin,
'workbook-create',
params='{0}'.format(wb_def)
)
wb_name = self.get_field_value(wb, "Name")
self.addCleanup(
self.mistral_cli,
admin,
'workbook-delete',
params=wb_name
)
self.addCleanup(
self.mistral_cli,
admin,
'workflow-delete',
params='wb.wf1'
)
return wb
def workflow_create(self, wf_def, admin=True, scope='private'):
params = '{0}'.format(wf_def)
if scope == 'public':
params += ' --public'
wf = self.mistral_cli(
admin,
'workflow-create',
params=params
)
for workflow in wf:
self.addCleanup(
self.mistral_cli,
admin,
'workflow-delete',
params=workflow['ID']
)
return wf
def workflow_member_create(self, wf_id):
cmd_param = (
'%s workflow %s' % (wf_id, self.get_project_id("demo"))
)
member = self.mistral_admin("member-create", params=cmd_param)
self.addCleanup(
self.mistral_admin,
'member-delete',
params=cmd_param
)
return member
def action_create(self, act_def, admin=True, scope='private'):
params = '{0}'.format(act_def)
if scope == 'public':
params += ' --public'
acts = self.mistral_cli(
admin,
'action-create',
params=params
)
for action in acts:
self.addCleanup(
self.mistral_cli,
admin,
'action-delete',
params=action['Name']
)
return acts
def cron_trigger_create(self, name, wf_name, wf_input, pattern=None,
count=None, first_time=None, admin=True):
optional_params = ""
if pattern:
optional_params += ' --pattern "{}"'.format(pattern)
if count:
optional_params += ' --count {}'.format(count)
if first_time:
optional_params += ' --first-time "{}"'.format(first_time)
trigger = self.mistral_cli(
admin,
'cron-trigger-create',
params='{} {} {} {}'.format(name, wf_name, wf_input,
optional_params))
self.addCleanup(self.mistral_cli,
admin,
'cron-trigger-delete',
params=name)
return trigger
def event_trigger_create(self, name, wf_id, exchange,
topic, event, wf_input, admin=True):
trigger = self.mistral_cli(
admin,
'event-trigger-create',
params=' '.join((name, wf_id, exchange, topic, event, wf_input)))
ev_tr_id = self.get_field_value(trigger, 'ID')
self.addCleanup(self.mistral_cli,
admin,
'event-trigger-delete',
params=ev_tr_id)
return trigger
def execution_create(self, params, admin=True):
ex = self.mistral_cli(admin, 'execution-create', params=params)
exec_id = self.get_field_value(ex, 'ID')
self.addCleanup(
self.mistral_cli,
admin,
'execution-delete',
params=exec_id
)
return ex
def environment_create(self, params, admin=True):
env = self.mistral_cli(admin, 'environment-create', params=params)
env_name = self.get_field_value(env, 'Name')
self.addCleanup(
self.mistral_cli,
admin,
'environment-delete',
params=env_name
)
return env
def create_file(self, file_name, file_body=""):
f = open(file_name, 'w')
f.write(file_body)
f.close()
self.addCleanup(os.remove, file_name)
def wait_execution_success(self, exec_id, timeout=180):
start_time = time.time()
ex = self.mistral_admin('execution-get', params=exec_id)
exec_state = self.get_field_value(ex, 'State')
expected_states = ['SUCCESS', 'RUNNING']
while exec_state != 'SUCCESS':
if time.time() - start_time > timeout:
msg = ("Execution exceeds timeout {0} to change state "
"to SUCCESS. Execution: {1}".format(timeout, ex))
raise exceptions.TimeoutException(msg)
ex = self.mistral_admin('execution-get', params=exec_id)
exec_state = self.get_field_value(ex, 'State')
if exec_state not in expected_states:
msg = ("Execution state %s is not in expected "
"states: %s" % (exec_state, expected_states))
raise exceptions.TempestException(msg)
time.sleep(2)
return True

View File

@ -1,488 +0,0 @@
# Copyright (c) 2014 Mirantis, 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 tempest.lib import exceptions
from mistralclient.tests.functional.cli.v2 import base_v2
class StandardItemsAvailabilityCLITests(base_v2.MistralClientTestBase):
def test_std_workflows_availability(self):
wfs = self.mistral_admin("workflow-list")
self.assertTableStruct(
wfs,
["Name", "Tags", "Input", "Created at", "Updated at"]
)
self.assertIn("std.create_instance",
[workflow["Name"] for workflow in wfs])
wfs = self.mistral_alt_user("workflow-list")
self.assertTableStruct(
wfs,
["Name", "Tags", "Input", "Created at", "Updated at"]
)
self.assertIn("std.create_instance",
[workflow["Name"] for workflow in wfs])
def test_std_actions_availability(self):
acts = self.mistral_admin("action-list")
self.assertTableStruct(
acts,
["Name", "Is system", "Input", "Description",
"Tags", "Created at", "Updated at"]
)
self.assertIn("glance.images_list",
[action["Name"] for action in acts])
acts = self.mistral_alt_user("action-list")
self.assertTableStruct(
acts,
["Name", "Is system", "Input", "Description",
"Tags", "Created at", "Updated at"]
)
self.assertIn("glance.images_list",
[action["Name"] for action in acts])
class WorkbookIsolationCLITests(base_v2.MistralClientTestBase):
def test_workbook_name_uniqueness(self):
self.workbook_create(self.wb_def)
self.assertRaises(
exceptions.CommandFailed,
self.mistral_admin,
"workbook-create",
params="{0}".format(self.wb_def)
)
self.workbook_create(self.wb_def, admin=False)
self.assertRaises(
exceptions.CommandFailed,
self.mistral_alt_user,
"workbook-create",
params="{0}".format(self.wb_def)
)
def test_wb_isolation(self):
wb = self.workbook_create(self.wb_def)
wb_name = self.get_field_value(wb, "Name")
wbs = self.mistral_admin("workbook-list")
self.assertIn(wb_name, [w["Name"] for w in wbs])
alt_wbs = self.mistral_alt_user("workbook-list")
self.assertNotIn(wb_name, [w["Name"] for w in alt_wbs])
def test_get_wb_from_another_tenant(self):
wb = self.workbook_create(self.wb_def)
name = self.get_field_value(wb, "Name")
self.assertRaises(
exceptions.CommandFailed,
self.mistral_alt_user,
"workbook-get",
params=name
)
def test_delete_wb_from_another_tenant(self):
wb = self.workbook_create(self.wb_def)
name = self.get_field_value(wb, "Name")
self.assertRaises(
exceptions.CommandFailed,
self.mistral_alt_user,
"workbook-delete",
params=name
)
class WorkflowIsolationCLITests(base_v2.MistralClientTestBase):
def test_workflow_name_uniqueness(self):
self.workflow_create(self.wf_def)
self.assertRaises(
exceptions.CommandFailed,
self.mistral_admin,
"workflow-create",
params="{0}".format(self.wf_def)
)
self.workflow_create(self.wf_def, admin=False)
self.assertRaises(
exceptions.CommandFailed,
self.mistral_alt_user,
"workflow-create",
params="{0}".format(self.wf_def)
)
def test_wf_isolation(self):
wf = self.workflow_create(self.wf_def)
wfs = self.mistral_admin("workflow-list")
self.assertIn(wf[0]["Name"], [w["Name"] for w in wfs])
alt_wfs = self.mistral_alt_user("workflow-list")
self.assertNotIn(wf[0]["Name"], [w["Name"] for w in alt_wfs])
def test_get_wf_from_another_tenant(self):
wf = self.workflow_create(self.wf_def)
self.assertRaises(
exceptions.CommandFailed,
self.mistral_alt_user,
"workflow-get",
params=wf[0]["ID"]
)
def test_create_public_workflow(self):
wf = self.workflow_create(self.wf_def, scope='public')
same_wf = self.mistral_alt_user(
"workflow-get",
params=wf[0]["Name"]
)
self.assertEqual(
wf[0]["Name"],
self.get_field_value(same_wf, "Name")
)
def test_delete_wf_from_another_tenant(self):
wf = self.workflow_create(self.wf_def)
self.assertRaises(
exceptions.CommandFailed,
self.mistral_alt_user,
"workflow-delete",
params=wf[0]["ID"]
)
class WorkflowSharingCLITests(base_v2.MistralClientTestBase):
def setUp(self):
super(WorkflowSharingCLITests, self).setUp()
self.wf = self.workflow_create(self.wf_def, admin=True)
def _update_shared_workflow(self, new_status='accepted'):
member = self.workflow_member_create(self.wf[0]["ID"])
status = self.get_field_value(member, 'Status')
self.assertEqual('pending', status)
cmd_param = '%s workflow --status %s' % (self.wf[0]["ID"], new_status)
member = self.mistral_alt_user("member-update", params=cmd_param)
status = self.get_field_value(member, 'Status')
self.assertEqual(new_status, status)
def test_list_accepted_shared_workflow(self):
wfs = self.mistral_alt_user("workflow-list")
self.assertNotIn(self.wf[0]["ID"], [w["ID"] for w in wfs])
self._update_shared_workflow(new_status='accepted')
alt_wfs = self.mistral_alt_user("workflow-list")
self.assertIn(self.wf[0]["ID"], [w["ID"] for w in alt_wfs])
self.assertIn(
self.get_project_id("admin"),
[w["Project ID"] for w in alt_wfs]
)
def test_list_rejected_shared_workflow(self):
self._update_shared_workflow(new_status='rejected')
alt_wfs = self.mistral_alt_user("workflow-list")
self.assertNotIn(self.wf[0]["ID"], [w["ID"] for w in alt_wfs])
def test_create_execution_using_shared_workflow(self):
self._update_shared_workflow(new_status='accepted')
execution = self.execution_create(self.wf[0]["ID"], admin=False)
wf_name = self.get_field_value(execution, 'Workflow name')
self.assertEqual(self.wf[0]["Name"], wf_name)
def test_create_contrigger_using_shared_workflow(self):
self._update_shared_workflow(new_status='accepted')
trigger = self.cron_trigger_create(
"test_trigger",
self.wf[0]["ID"],
"{}",
"5 * * * *",
admin=False
)
wf_name = self.get_field_value(trigger, 'Workflow')
self.assertEqual(self.wf[0]["Name"], wf_name)
# Admin project can not delete the shared workflow, because it is used
# in a cron-trigger of another project.
self.assertRaises(
exceptions.CommandFailed,
self.mistral_admin,
'workflow-delete',
params=self.wf[0]["ID"]
)
class ActionIsolationCLITests(base_v2.MistralClientTestBase):
def test_actions_name_uniqueness(self):
self.action_create(self.act_def)
self.assertRaises(
exceptions.CommandFailed,
self.mistral_admin,
"action-create",
params="{0}".format(self.act_def)
)
self.action_create(self.act_def, admin=False)
self.assertRaises(
exceptions.CommandFailed,
self.mistral_alt_user,
"action-create",
params="{0}".format(self.act_def)
)
def test_action_isolation(self):
act = self.action_create(self.act_def)
acts = self.mistral_admin("action-list")
self.assertIn(act[0]["Name"], [a["Name"] for a in acts])
alt_acts = self.mistral_alt_user("action-list")
self.assertNotIn(act[0]["Name"], [a["Name"] for a in alt_acts])
def test_get_action_from_another_tenant(self):
act = self.action_create(self.act_def)
self.assertRaises(
exceptions.CommandFailed,
self.mistral_alt_user,
"action-get",
params=act[0]["Name"]
)
def test_delete_action_from_another_tenant(self):
act = self.action_create(self.act_def)
self.assertRaises(
exceptions.CommandFailed,
self.mistral_alt_user,
"action-delete",
params=act[0]["Name"]
)
def test_create_public_action(self):
act = self.action_create(self.act_def, scope='public')
same_act = self.mistral_alt_user(
"action-get",
params=act[0]["Name"]
)
self.assertEqual(
act[0]["Name"],
self.get_field_value(same_act, "Name")
)
class CronTriggerIsolationCLITests(base_v2.MistralClientTestBase):
def test_cron_trigger_name_uniqueness(self):
wf = self.workflow_create(self.wf_def)
self.cron_trigger_create(
"admin_trigger",
wf[0]["ID"],
"{}",
"5 * * * *"
)
self.assertRaises(
exceptions.CommandFailed,
self.cron_trigger_create,
"admin_trigger",
wf[0]["ID"],
"{}"
"5 * * * *",
)
wf = self.workflow_create(self.wf_def, admin=False)
self.cron_trigger_create(
"user_trigger",
wf[0]["ID"],
"{}",
"5 * * * *",
None,
None,
admin=False
)
self.assertRaises(
exceptions.CommandFailed,
self.cron_trigger_create,
"user_trigger",
wf[0]["ID"],
"{}",
"5 * * * *",
None,
None,
admin=False
)
def test_cron_trigger_isolation(self):
wf = self.workflow_create(self.wf_def)
self.cron_trigger_create(
"trigger", wf[0]["Name"], "{}", "5 * * * *")
alt_trs = self.mistral_alt_user("cron-trigger-list")
self.assertNotIn("trigger", [t["Name"] for t in alt_trs])
class ExecutionIsolationCLITests(base_v2.MistralClientTestBase):
def test_execution_isolation(self):
wf = self.workflow_create(self.wf_def)
ex = self.execution_create(wf[0]["Name"])
exec_id = self.get_field_value(ex, "ID")
execs = self.mistral_admin("execution-list")
self.assertIn(exec_id, [e["ID"] for e in execs])
alt_execs = self.mistral_alt_user("execution-list")
self.assertNotIn(exec_id, [e["ID"] for e in alt_execs])
def test_get_execution_from_another_tenant(self):
wf = self.workflow_create(self.wf_def)
ex = self.execution_create(wf[0]["Name"])
exec_id = self.get_field_value(ex, "ID")
self.assertRaises(
exceptions.CommandFailed,
self.mistral_alt_user,
"execution-get",
params=exec_id
)
class EnvironmentIsolationCLITests(base_v2.MistralClientTestBase):
def setUp(self):
super(EnvironmentIsolationCLITests, self).setUp()
self.env_file = "env.yaml"
self.create_file("{0}".format(self.env_file),
"name: env\n"
"description: Test env\n"
"variables:\n"
" var: value")
def test_environment_name_uniqueness(self):
self.environment_create(self.env_file)
self.assertRaises(
exceptions.CommandFailed,
self.mistral_admin,
"environment-create",
params=self.env_file
)
self.environment_create(self.env_file, admin=False)
self.assertRaises(
exceptions.CommandFailed,
self.mistral_alt_user,
"environment-create",
params=self.env_file
)
def test_environment_isolation(self):
env = self.environment_create(self.env_file)
env_name = self.get_field_value(env, "Name")
envs = self.mistral_admin("environment-list")
self.assertIn(env_name, [en["Name"] for en in envs])
alt_envs = self.mistral_alt_user("environment-list")
self.assertNotIn(env_name, [en["Name"] for en in alt_envs])
def test_get_env_from_another_tenant(self):
env = self.environment_create(self.env_file)
env_name = self.get_field_value(env, "Name")
self.assertRaises(
exceptions.CommandFailed,
self.mistral_alt_user,
"environment-get",
params=env_name
)
def test_delete_env_from_another_tenant(self):
env = self.environment_create(self.env_file)
env_name = self.get_field_value(env, "Name")
self.assertRaises(
exceptions.CommandFailed,
self.mistral_alt_user,
"environment-delete",
params=env_name
)
class ActionExecutionIsolationCLITests(base_v2.MistralClientTestBase):
def test_action_execution_isolation(self):
wf = self.workflow_create(self.wf_def)
wf_exec = self.execution_create(wf[0]["Name"])
direct_ex_id = self.get_field_value(wf_exec, 'ID')
self.wait_execution_success(direct_ex_id)
act_execs = self.mistral_admin("action-execution-list")
self.assertIn(wf[0]["Name"],
[act["Workflow name"] for act in act_execs])
alt_act_execs = self.mistral_alt_user("action-execution-list")
self.assertNotIn(wf[0]["Name"],
[act["Workflow name"] for act in alt_act_execs])
def test_get_action_execution_from_another_tenant(self):
wf = self.workflow_create(self.wf_def)
ex = self.execution_create(wf[0]["Name"])
exec_id = self.get_field_value(ex, "ID")
self.assertRaises(
exceptions.CommandFailed,
self.mistral_alt_user,
"action-execution-get",
params=exec_id
)

File diff suppressed because it is too large Load Diff

View File

@ -1,39 +0,0 @@
# Copyright 2013 - Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import mock
from oslotest import base
from requests_mock.contrib import fixture
class BaseClientTest(base.BaseTestCase):
_client = None
def setUp(self):
super(BaseClientTest, self).setUp()
self.requests_mock = self.useFixture(fixture.Fixture())
class BaseCommandTest(base.BaseTestCase):
def setUp(self):
super(BaseCommandTest, self).setUp()
self.app = mock.Mock()
self.client = self.app.client_manager.workflow_engine
def call(self, command, app_args=[], prog_name=''):
cmd = command(self.app, app_args)
parsed_args = cmd.get_parser(prog_name).parse_args(app_args)
return cmd.take_action(parsed_args)

View File

@ -1,47 +0,0 @@
# Copyright 2015 Huawei Technologies Co., Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import sys
from oslotest import base
import six
from mistralclient import shell
class BaseShellTests(base.BaseTestCase):
def shell(self, argstr):
orig = (sys.stdout, sys.stderr)
clean_env = {}
_old_env, os.environ = os.environ, clean_env.copy()
try:
sys.stdout = six.moves.cStringIO()
sys.stderr = six.moves.cStringIO()
_shell = shell.MistralShell()
_shell.run(argstr.split())
except SystemExit:
exc_type, exc_value, exc_traceback = sys.exc_info()
self.assertEqual(0, exc_value.code)
finally:
stdout = sys.stdout.getvalue()
stderr = sys.stderr.getvalue()
sys.stdout.close()
sys.stderr.close()
sys.stdout, sys.stderr = orig
os.environ = _old_env
return stdout, stderr

View File

@ -1,10 +0,0 @@
---
version: 2.0
my_action:
base: std.echo
base-input:
output: 'Bye!'
output:
info: <% $.output %>

View File

@ -1,7 +0,0 @@
{
"context": {
"server": {
"name": "name"
}
}
}

View File

@ -1,8 +0,0 @@
{
"name": "env1",
"description": "Test Environment #1",
"scope": "private",
"variables": {
"server": "localhost"
}
}

View File

@ -1,7 +0,0 @@
---
"name": "env1"
"description": "Test Environment #1"
"scope": "private"
"variables":
"server": "localhost"

View File

@ -1,21 +0,0 @@
---
version: 2.0
name: wb
workflows:
wf1:
type: direct
input:
- param1
- param2
tasks:
task1:
action: std.http url="localhost:8989"
on-success:
- test_subsequent
test_subsequent:
action: std.http url="http://some_url" server_id=1

View File

@ -1,10 +0,0 @@
---
version: 2.0
my_wf:
type: direct
tasks:
task1:
action: std.echo output="hello, world"

View File

@ -1,276 +0,0 @@
# Copyright 2015 - Huawei Technologies Co., Ltd.
# Copyright 2016 - StackStorm, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
import os
import tempfile
import mock
from oslo_utils import uuidutils
from oslotest import base
import osprofiler.profiler
from mistralclient.api import client
AUTH_HTTP_URL_v3 = 'http://localhost:35357/v3'
AUTH_HTTP_URL_v2_0 = 'http://localhost:35357/v2.0'
AUTH_HTTPS_URL = AUTH_HTTP_URL_v3.replace('http', 'https')
MISTRAL_HTTP_URL = 'http://localhost:8989/v2'
MISTRAL_HTTPS_URL = MISTRAL_HTTP_URL.replace('http', 'https')
PROFILER_HMAC_KEY = 'SECRET_HMAC_KEY'
class BaseClientTests(base.BaseTestCase):
@staticmethod
def setup_keystone_mock(keystone_client_mock):
keystone_client_instance = keystone_client_mock.return_value
keystone_client_instance.auth_token = uuidutils.generate_uuid()
keystone_client_instance.project_id = uuidutils.generate_uuid()
keystone_client_instance.user_id = uuidutils.generate_uuid()
keystone_client_instance.auth_ref = str(json.dumps({}))
return keystone_client_instance
@mock.patch('keystoneclient.client.Client')
def test_mistral_url_from_catalog_v2(self, keystone_client_mock):
keystone_client_instance = self.setup_keystone_mock(
keystone_client_mock
)
url_for = mock.Mock(return_value='http://mistral_host:8989/v2')
keystone_client_instance.service_catalog.url_for = url_for
mistralclient = client.client(
username='mistral',
project_name='mistral',
auth_url=AUTH_HTTP_URL_v2_0,
service_type='workflowv2'
)
self.assertEqual(
'http://mistral_host:8989/v2',
mistralclient.actions.http_client.base_url
)
@mock.patch('keystoneclient.client.Client')
def test_mistral_url_from_catalog(self, keystone_client_mock):
keystone_client_instance = self.setup_keystone_mock(
keystone_client_mock
)
url_for = mock.Mock(return_value='http://mistral_host:8989/v2')
keystone_client_instance.service_catalog.url_for = url_for
mistralclient = client.client(
username='mistral',
project_name='mistral',
auth_url=AUTH_HTTP_URL_v3,
service_type='workflowv2'
)
self.assertEqual(
'http://mistral_host:8989/v2',
mistralclient.actions.http_client.base_url
)
@mock.patch('keystoneclient.client.Client')
@mock.patch('mistralclient.api.httpclient.HTTPClient')
def test_mistral_url_default(self, http_client_mock, keystone_client_mock):
keystone_client_instance = self.setup_keystone_mock(
keystone_client_mock
)
url_for = mock.Mock(side_effect=Exception)
keystone_client_instance.service_catalog.url_for = url_for
client.client(
username='mistral',
project_name='mistral',
auth_url=AUTH_HTTP_URL_v3
)
self.assertTrue(http_client_mock.called)
mistral_url_for_http = http_client_mock.call_args[0][0]
kwargs = http_client_mock.call_args[1]
self.assertEqual(MISTRAL_HTTP_URL, mistral_url_for_http)
self.assertEqual(
keystone_client_instance.auth_token, kwargs['auth_token']
)
self.assertEqual(
keystone_client_instance.project_id, kwargs['project_id']
)
self.assertEqual(
keystone_client_instance.user_id, kwargs['user_id']
)
@mock.patch('keystoneclient.client.Client')
@mock.patch('mistralclient.api.httpclient.HTTPClient')
def test_target_parameters_processed(
self,
http_client_mock,
keystone_client_mock
):
keystone_client_instance = self.setup_keystone_mock(
keystone_client_mock
)
url_for = mock.Mock(return_value='http://mistral_host:8989/v2')
keystone_client_instance.service_catalog.url_for = url_for
client.client(
target_username='tmistral',
target_project_name='tmistralp',
target_auth_url=AUTH_HTTP_URL_v3,
target_region_name='tregion'
)
self.assertTrue(http_client_mock.called)
mistral_url_for_http = http_client_mock.call_args[0][0]
kwargs = http_client_mock.call_args[1]
self.assertEqual(MISTRAL_HTTP_URL, mistral_url_for_http)
expected_values = {
'target_project_id': keystone_client_instance.project_id,
'target_auth_token': keystone_client_instance.auth_token,
'target_user_id': keystone_client_instance.user_id,
'target_auth_url': AUTH_HTTP_URL_v3,
'target_project_name': 'tmistralp',
'target_username': 'tmistral',
'target_region_name': 'tregion',
'target_service_catalog': '"{}"'
}
for key in expected_values:
self.assertEqual(expected_values[key], kwargs[key])
@mock.patch('keystoneclient.client.Client')
@mock.patch('mistralclient.api.httpclient.HTTPClient')
def test_mistral_url_https_insecure(self, http_client_mock,
keystone_client_mock):
keystone_client_instance = self.setup_keystone_mock( # noqa
keystone_client_mock
)
expected_args = (
MISTRAL_HTTPS_URL,
)
client.client(
mistral_url=MISTRAL_HTTPS_URL,
username='mistral',
project_name='mistral',
auth_url=AUTH_HTTP_URL_v3,
cacert=None,
insecure=True
)
self.assertTrue(http_client_mock.called)
self.assertEqual(http_client_mock.call_args[0], expected_args)
self.assertEqual(http_client_mock.call_args[1]['insecure'], True)
@mock.patch('keystoneclient.client.Client')
@mock.patch('mistralclient.api.httpclient.HTTPClient')
def test_mistral_url_https_secure(self, http_client_mock,
keystone_client_mock):
fd, cert_path = tempfile.mkstemp(suffix='.pem')
keystone_client_instance = self.setup_keystone_mock( # noqa
keystone_client_mock
)
expected_args = (
MISTRAL_HTTPS_URL,
)
try:
client.client(
mistral_url=MISTRAL_HTTPS_URL,
username='mistral',
project_name='mistral',
auth_url=AUTH_HTTP_URL_v3,
cacert=cert_path,
insecure=False
)
finally:
os.close(fd)
os.unlink(cert_path)
self.assertTrue(http_client_mock.called)
self.assertEqual(http_client_mock.call_args[0], expected_args)
self.assertEqual(http_client_mock.call_args[1]['cacert'], cert_path)
@mock.patch('keystoneclient.client.Client')
def test_mistral_url_https_bad_cacert(self, keystone_client_mock):
keystone_client_instance = self.setup_keystone_mock( # noqa
keystone_client_mock
)
self.assertRaises(
ValueError,
client.client,
mistral_url=MISTRAL_HTTPS_URL,
username='mistral',
project_name='mistral',
auth_url=AUTH_HTTP_URL_v3,
cacert='/path/to/foobar',
insecure=False
)
@mock.patch('logging.Logger.warning')
@mock.patch('keystoneclient.client.Client')
def test_mistral_url_https_bad_insecure(self, keystone_client_mock,
log_warning_mock):
fd, path = tempfile.mkstemp(suffix='.pem')
keystone_client_instance = self.setup_keystone_mock(
keystone_client_mock
)
try:
client.client(
mistral_url=MISTRAL_HTTPS_URL,
user_id=keystone_client_instance.user_id,
project_id=keystone_client_instance.project_id,
auth_url=AUTH_HTTP_URL_v3,
cacert=path,
insecure=True
)
finally:
os.close(fd)
os.unlink(path)
self.assertTrue(log_warning_mock.called)
@mock.patch('keystoneclient.client.Client')
@mock.patch('mistralclient.api.httpclient.HTTPClient')
def test_mistral_profile_enabled(self, http_client_mock,
keystone_client_mock):
keystone_client_instance = self.setup_keystone_mock( # noqa
keystone_client_mock
)
client.client(
username='mistral',
project_name='mistral',
auth_url=AUTH_HTTP_URL_v3,
profile=PROFILER_HMAC_KEY
)
self.assertTrue(http_client_mock.called)
profiler = osprofiler.profiler.get()
self.assertEqual(profiler.hmac_key, PROFILER_HMAC_KEY)

View File

@ -1,276 +0,0 @@
# Copyright 2016 - StackStorm, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import base64
import copy
import mock
from six.moves.urllib import parse as urlparse
from oslo_utils import uuidutils
from osprofiler import _utils as osprofiler_utils
import osprofiler.profiler
from mistralclient.api import httpclient
from mistralclient.tests.unit import base
API_BASE_URL = 'http://localhost:8989/v2'
API_URL = '/executions'
EXPECTED_URL = API_BASE_URL + API_URL
AUTH_TOKEN = uuidutils.generate_uuid()
PROJECT_ID = uuidutils.generate_uuid()
USER_ID = uuidutils.generate_uuid()
REGION_NAME = 'fake_region'
PROFILER_HMAC_KEY = 'SECRET_HMAC_KEY'
PROFILER_TRACE_ID = uuidutils.generate_uuid()
EXPECTED_AUTH_HEADERS = {
'x-auth-token': AUTH_TOKEN,
'X-Project-Id': PROJECT_ID,
'X-User-Id': USER_ID,
'X-Region-Name': REGION_NAME
}
EXPECTED_REQ_OPTIONS = {
'headers': EXPECTED_AUTH_HEADERS
}
EXPECTED_BODY = {
'k1': 'abc',
'k2': 123,
'k3': True
}
class HTTPClientTest(base.BaseClientTest):
def setUp(self):
super(HTTPClientTest, self).setUp()
osprofiler.profiler.init(None)
self.client = httpclient.HTTPClient(
API_BASE_URL,
auth_token=AUTH_TOKEN,
project_id=PROJECT_ID,
user_id=USER_ID,
region_name=REGION_NAME
)
def assertExpectedAuthHeaders(self):
headers = self.requests_mock.last_request.headers
self.assertEqual(AUTH_TOKEN, headers['X-Auth-Token'])
self.assertEqual(PROJECT_ID, headers['X-Project-Id'])
self.assertEqual(USER_ID, headers['X-User-Id'])
return headers
def assertExpectedBody(self):
text = self.requests_mock.last_request.text
form = urlparse.parse_qs(text, strict_parsing=True)
self.assertEqual(len(EXPECTED_BODY), len(form))
for k, v in EXPECTED_BODY.items():
self.assertEqual([str(v)], form[k])
return form
def test_get_request_options(self):
m = self.requests_mock.get(EXPECTED_URL, text='text')
self.client.get(API_URL)
self.assertTrue(m.called_once)
self.assertExpectedAuthHeaders()
@mock.patch.object(
osprofiler.profiler._Profiler,
'get_base_id',
mock.MagicMock(return_value=PROFILER_TRACE_ID)
)
@mock.patch.object(
osprofiler.profiler._Profiler,
'get_id',
mock.MagicMock(return_value=PROFILER_TRACE_ID)
)
def test_get_request_options_with_profile_enabled(self):
m = self.requests_mock.get(EXPECTED_URL, text='text')
osprofiler.profiler.init(PROFILER_HMAC_KEY)
data = {'base_id': PROFILER_TRACE_ID, 'parent_id': PROFILER_TRACE_ID}
signed_data = osprofiler_utils.signed_pack(data, PROFILER_HMAC_KEY)
headers = {
'X-Trace-Info': signed_data[0],
'X-Trace-HMAC': signed_data[1]
}
self.client.get(API_URL)
self.assertTrue(m.called_once)
headers = self.assertExpectedAuthHeaders()
self.assertEqual(signed_data[0], headers['X-Trace-Info'])
self.assertEqual(signed_data[1], headers['X-Trace-HMAC'])
def test_get_request_options_with_headers_for_get(self):
m = self.requests_mock.get(EXPECTED_URL, text='text')
target_auth_url = uuidutils.generate_uuid()
target_auth_token = uuidutils.generate_uuid()
target_user_id = 'target_user'
target_project_id = 'target_project'
target_service_catalog = 'this should be there'
target_insecure = 'target insecure'
target_region = 'target region name'
target_user_domain_name = 'target user domain name'
target_project_domain_name = 'target project domain name'
target_client = httpclient.HTTPClient(
API_BASE_URL,
auth_token=AUTH_TOKEN,
project_id=PROJECT_ID,
user_id=USER_ID,
region_name=REGION_NAME,
target_auth_url=target_auth_url,
target_auth_token=target_auth_token,
target_project_id=target_project_id,
target_user_id=target_user_id,
target_service_catalog=target_service_catalog,
target_region_name=target_region,
target_user_domain_name=target_user_domain_name,
target_project_domain_name=target_project_domain_name,
target_insecure=target_insecure
)
target_client.get(API_URL)
self.assertTrue(m.called_once)
headers = self.assertExpectedAuthHeaders()
self.assertEqual(target_auth_url, headers['X-Target-Auth-Uri'])
self.assertEqual(target_auth_token, headers['X-Target-Auth-Token'])
self.assertEqual(target_user_id, headers['X-Target-User-Id'])
self.assertEqual(target_project_id, headers['X-Target-Project-Id'])
self.assertEqual(target_insecure, headers['X-Target-Insecure'])
self.assertEqual(target_region, headers['X-Target-Region-Name'])
self.assertEqual(target_user_domain_name,
headers['X-Target-User-Domain-Name'])
self.assertEqual(target_project_domain_name,
headers['X-Target-Project-Domain-Name'])
catalog = base64.b64encode(target_service_catalog.encode('utf-8'))
self.assertEqual(catalog, headers['X-Target-Service-Catalog'])
def test_get_request_options_with_headers_for_post(self):
m = self.requests_mock.post(EXPECTED_URL, text='text')
headers = {'foo': 'bar'}
self.client.post(API_URL, EXPECTED_BODY, headers=headers)
self.assertTrue(m.called_once)
headers = self.assertExpectedAuthHeaders()
self.assertEqual('application/json', headers['Content-Type'])
self.assertEqual('bar', headers['foo'])
self.assertExpectedBody()
def test_get_request_options_with_headers_for_put(self):
m = self.requests_mock.put(EXPECTED_URL, text='text')
headers = {'foo': 'bar'}
self.client.put(API_URL, EXPECTED_BODY, headers=headers)
self.assertTrue(m.called_once)
headers = self.assertExpectedAuthHeaders()
self.assertEqual('application/json', headers['Content-Type'])
self.assertEqual('bar', headers['foo'])
self.assertExpectedBody()
def test_get_request_options_with_headers_for_delete(self):
m = self.requests_mock.delete(EXPECTED_URL, text='text')
headers = {'foo': 'bar'}
self.client.delete(API_URL, headers=headers)
self.assertTrue(m.called_once)
headers = self.assertExpectedAuthHeaders()
self.assertEqual('bar', headers['foo'])
@mock.patch.object(
httpclient.HTTPClient,
'_get_request_options',
mock.MagicMock(return_value=copy.deepcopy(EXPECTED_REQ_OPTIONS))
)
def test_http_get(self):
m = self.requests_mock.get(EXPECTED_URL, text='text')
self.client.get(API_URL)
httpclient.HTTPClient._get_request_options.assert_called_with(
'get',
None
)
self.assertTrue(m.called_once)
self.assertExpectedAuthHeaders()
@mock.patch.object(
httpclient.HTTPClient,
'_get_request_options',
mock.MagicMock(return_value=copy.deepcopy(EXPECTED_REQ_OPTIONS))
)
def test_http_post(self):
m = self.requests_mock.post(EXPECTED_URL, status_code=201, text='text')
self.client.post(API_URL, EXPECTED_BODY)
httpclient.HTTPClient._get_request_options.assert_called_with(
'post',
None
)
self.assertTrue(m.called_once)
self.assertExpectedAuthHeaders()
self.assertExpectedBody()
@mock.patch.object(
httpclient.HTTPClient,
'_get_request_options',
mock.MagicMock(return_value=copy.deepcopy(EXPECTED_REQ_OPTIONS))
)
def test_http_put(self):
m = self.requests_mock.put(EXPECTED_URL, json={})
self.client.put(API_URL, EXPECTED_BODY)
httpclient.HTTPClient._get_request_options.assert_called_with(
'put',
None
)
self.assertTrue(m.called_once)
self.assertExpectedAuthHeaders()
self.assertExpectedBody()
@mock.patch.object(
httpclient.HTTPClient,
'_get_request_options',
mock.MagicMock(return_value=copy.deepcopy(EXPECTED_REQ_OPTIONS))
)
def test_http_delete(self):
m = self.requests_mock.delete(EXPECTED_URL, text='text')
self.client.delete(API_URL)
httpclient.HTTPClient._get_request_options.assert_called_with(
'delete',
None
)
self.assertTrue(m.called_once)
self.assertExpectedAuthHeaders()

View File

@ -1,136 +0,0 @@
# Copyright 2015 Huawei Technologies Co., Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import mock
import mistralclient.tests.unit.base_shell_test as base
class TestShell(base.BaseShellTests):
@mock.patch('mistralclient.api.client.client')
def test_command_no_mistral_url(self, client_mock):
self.shell(
'workbook-list'
)
self.assertTrue(client_mock.called)
params = client_mock.call_args
self.assertEqual('', params[1]['mistral_url'])
@mock.patch('mistralclient.api.client.client')
def test_command_with_mistral_url(self, client_mock):
self.shell(
'--os-mistral-url=http://localhost:8989/v2 workbook-list'
)
self.assertTrue(client_mock.called)
params = client_mock.call_args
self.assertEqual('http://localhost:8989/v2',
params[1]['mistral_url'])
@mock.patch('mistralclient.api.client.determine_client_version')
def test_mistral_version(self, client_mock):
self.shell(
'--os-mistral-version=v1 workbook-list'
)
self.assertTrue(client_mock.called)
mistral_version = client_mock.call_args
self.assertEqual('v1', mistral_version[0][0])
@mock.patch('mistralclient.api.client.determine_client_version')
def test_no_mistral_version(self, client_mock):
self.shell('workbook-list')
self.assertTrue(client_mock.called)
mistral_version = client_mock.call_args
self.assertEqual('v2', mistral_version[0][0])
@mock.patch('mistralclient.api.client.client')
def test_service_type(self, client_mock):
self.shell('--os-mistral-service-type=test workbook-list')
self.assertTrue(client_mock.called)
parmters = client_mock.call_args
self.assertEqual('test', parmters[1]['service_type'])
@mock.patch('mistralclient.api.client.client')
def test_no_service_type(self, client_mock):
self.shell('workbook-list')
self.assertTrue(client_mock.called)
params = client_mock.call_args
self.assertEqual('workflowv2', params[1]['service_type'])
@mock.patch('mistralclient.api.client.client')
def test_endpoint_type(self, client_mock):
self.shell('--os-mistral-endpoint-type=adminURL workbook-list')
self.assertTrue(client_mock.called)
params = client_mock.call_args
self.assertEqual('adminURL', params[1]['endpoint_type'])
@mock.patch('mistralclient.api.client.client')
def test_no_endpoint_type(self, client_mock):
self.shell('workbook-list')
self.assertTrue(client_mock.called)
params = client_mock.call_args
self.assertEqual('publicURL', params[1]['endpoint_type'])
@mock.patch('mistralclient.api.client.client')
def test_auth_url(self, client_mock):
self.shell(
'--os-auth-url=https://127.0.0.1:35357/v3 '
'--os-username=admin '
'--os-password=1234 '
'workbook-list'
)
self.assertTrue(client_mock.called)
params = client_mock.call_args
self.assertEqual('https://127.0.0.1:35357/v3', params[1]['auth_url'])
@mock.patch('mistralclient.api.client.client')
def test_no_auth_url(self, client_mock):
self.shell('workbook-list')
self.assertTrue(client_mock.called)
params = client_mock.call_args
self.assertEqual('', params[1]['auth_url'])
@mock.patch('mistralclient.api.client.client')
def test_default_auth_url_with_os_password(self, client_mock):
self.shell('--os-username=admin --os-password=1234 workbook-list')
self.assertTrue(client_mock.called)
params = client_mock.call_args
self.assertEqual('http://localhost:35357/v3', params[1]['auth_url'])
@mock.patch('mistralclient.api.client.client')
def test_default_auth_url_with_os_auth_token(self, client_mock):
self.shell(
'--os-auth-token=abcd1234 '
'workbook-list'
)
self.assertTrue(client_mock.called)
params = client_mock.call_args
self.assertEqual('http://localhost:35357/v3', params[1]['auth_url'])
@mock.patch('mistralclient.api.client.client')
def test_profile(self, client_mock):
self.shell('--profile=SECRET_HMAC_KEY workbook-list')
self.assertTrue(client_mock.called)
params = client_mock.call_args
self.assertEqual('SECRET_HMAC_KEY', params[1]['profile'])
@mock.patch('mistralclient.api.client.client')
def test_region_name(self, client_mock):
self.shell('--os-region-name=RegionOne workbook-list')
self.assertTrue(client_mock.called)
params = client_mock.call_args
self.assertEqual('RegionOne', params[1]['region_name'])

View File

@ -1,65 +0,0 @@
# Copyright 2015 - StackStorm, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
import os.path
import tempfile
import yaml
from mistralclient import utils
from oslotest import base
ENV_DICT = {'k1': 'abc', 'k2': 123, 'k3': True}
ENV_STR = json.dumps(ENV_DICT)
ENV_YAML = yaml.safe_dump(ENV_DICT, default_flow_style=False)
class UtilityTest(base.BaseTestCase):
def test_load_empty(self):
self.assertDictEqual(dict(), utils.load_content(None))
self.assertDictEqual(dict(), utils.load_content(''))
self.assertDictEqual(dict(), utils.load_content('{}'))
self.assertListEqual(list(), utils.load_content('[]'))
def test_load_json_content(self):
self.assertDictEqual(ENV_DICT, utils.load_content(ENV_STR))
def test_load_json_file(self):
with tempfile.NamedTemporaryFile() as f:
f.write(ENV_STR.encode('utf-8'))
f.flush()
file_path = os.path.abspath(f.name)
self.assertDictEqual(ENV_DICT, utils.load_file(file_path))
def test_load_yaml_content(self):
self.assertDictEqual(ENV_DICT, utils.load_content(ENV_YAML))
def test_load_yaml_file(self):
with tempfile.NamedTemporaryFile() as f:
f.write(ENV_YAML.encode('utf-8'))
f.flush()
file_path = os.path.abspath(f.name)
self.assertDictEqual(ENV_DICT, utils.load_file(file_path))
def test_load_json(self):
with tempfile.NamedTemporaryFile() as f:
f.write(ENV_STR.encode('utf-8'))
f.flush()
self.assertDictEqual(ENV_DICT, utils.load_json(f.name))
self.assertDictEqual(ENV_DICT, utils.load_json(ENV_STR))

View File

@ -1,37 +0,0 @@
# Copyright 2014 - Mirantis, Inc.
# Copyright 2015 - StackStorm, 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 mistralclient.api.v2 import client
from mistralclient.tests.unit import base
class BaseClientV2Test(base.BaseClientTest):
TEST_URL = 'http://mistral.example.com'
def setUp(self):
super(BaseClientV2Test, self).setUp()
self._client = client.Client(project_name="test",
mistral_url=self.TEST_URL)
self.workbooks = self._client.workbooks
self.executions = self._client.executions
self.tasks = self._client.tasks
self.workflows = self._client.workflows
self.environments = self._client.environments
self.action_executions = self._client.action_executions
self.actions = self._client.actions
self.services = self._client.services
self.members = self._client.members

View File

@ -1,114 +0,0 @@
# Copyright 2014 - Mirantis, 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 mistralclient.api.v2 import action_executions
from mistralclient.tests.unit.v2 import base
# TODO(everyone): later we need additional tests verifying all the errors etc.
ACTION_EXEC = {
'id': "1",
'name': 'my_action_execution',
'workflow_name': 'my_wf',
'state': 'RUNNING',
}
URL_TEMPLATE = '/action_executions'
URL_TEMPLATE_ID = '/action_executions/%s'
class TestActionExecutions(base.BaseClientV2Test):
def test_create(self):
self.requests_mock.post(self.TEST_URL + URL_TEMPLATE,
json=ACTION_EXEC,
status_code=201)
body = {
'name': ACTION_EXEC['name']
}
action_execution = self.action_executions.create(
'my_action_execution',
{}
)
self.assertIsNotNone(action_execution)
self.assertEqual(action_executions.ActionExecution(
self.action_executions, ACTION_EXEC
).to_dict(), action_execution.to_dict())
self.assertEqual(body, self.requests_mock.last_request.json())
def test_update(self):
url = self.TEST_URL + URL_TEMPLATE_ID % ACTION_EXEC['id']
self.requests_mock.put(url, json=ACTION_EXEC)
body = {
'state': ACTION_EXEC['state']
}
action_execution = self.action_executions.update(
ACTION_EXEC['id'],
ACTION_EXEC['state']
)
self.assertIsNotNone(action_execution)
expected = action_executions.ActionExecution(
self.action_executions,
ACTION_EXEC
).to_dict()
self.assertEqual(
expected,
action_execution.to_dict()
)
self.assertEqual(body, self.requests_mock.last_request.json())
def test_list(self):
self.requests_mock.get(self.TEST_URL + URL_TEMPLATE,
json={'action_executions': [ACTION_EXEC]})
action_execution_list = self.action_executions.list()
self.assertEqual(1, len(action_execution_list))
action_execution = action_execution_list[0]
expected = action_executions.ActionExecution(
self.action_executions,
ACTION_EXEC
).to_dict()
self.assertEqual(expected, action_execution.to_dict())
def test_get(self):
url = self.TEST_URL + URL_TEMPLATE_ID % ACTION_EXEC['id']
self.requests_mock.get(url, json=ACTION_EXEC)
action_execution = self.action_executions.get(ACTION_EXEC['id'])
expected = action_executions.ActionExecution(
self.action_executions,
ACTION_EXEC
).to_dict()
self.assertEqual(expected, action_execution.to_dict())
def test_delete(self):
url = self.TEST_URL + URL_TEMPLATE_ID % ACTION_EXEC['id']
self.requests_mock.delete(url, status_code=204)
self.action_executions.delete(ACTION_EXEC['id'])

View File

@ -1,269 +0,0 @@
# Copyright 2015 Huawei Technologies Co., Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import pkg_resources as pkg
from six.moves.urllib import parse
from six.moves.urllib import request
from mistralclient.api import base as api_base
from mistralclient.api.v2 import actions
from mistralclient.tests.unit.v2 import base
ACTION_DEF = """
---
version: 2.0
my_action:
base: std.echo
base-input:
output: 'Bye!'
output:
info: <% $.output %>
"""
INVALID_ACTION_DEF = """
---
version: 2.0
my_action:
base: std.echo
unexpected-property: 'this should fail'
base-input:
output: 'Bye!'
output:
info: <% $.output %>
"""
ACTION = {
'id': '123',
'name': 'my_action',
'input': '',
'definition': ACTION_DEF
}
URL_TEMPLATE = '/actions'
URL_TEMPLATE_SCOPE = '/actions?scope=private'
URL_TEMPLATE_NAME = '/actions/%s'
URL_TEMPLATE_VALIDATE = '/actions/validate'
class TestActionsV2(base.BaseClientV2Test):
def test_create(self):
self.requests_mock.post(self.TEST_URL + URL_TEMPLATE,
json={'actions': [ACTION]},
status_code=201)
actions = self.actions.create(ACTION_DEF)
self.assertIsNotNone(actions)
self.assertEqual(ACTION_DEF, actions[0].definition)
last_request = self.requests_mock.last_request
self.assertEqual('text/plain', last_request.headers['content-type'])
self.assertEqual(ACTION_DEF, last_request.text)
def test_create_with_file(self):
self.requests_mock.post(self.TEST_URL + URL_TEMPLATE,
json={'actions': [ACTION]},
status_code=201)
# The contents of action_v2.yaml must be identical to ACTION_DEF
path = pkg.resource_filename(
'mistralclient',
'tests/unit/resources/action_v2.yaml'
)
actions = self.actions.create(path)
self.assertIsNotNone(actions)
self.assertEqual(ACTION_DEF, actions[0].definition)
last_request = self.requests_mock.last_request
self.assertEqual('text/plain', last_request.headers['content-type'])
self.assertEqual(ACTION_DEF, last_request.text)
def test_update_with_id(self):
self.requests_mock.put(self.TEST_URL + URL_TEMPLATE_NAME % 123,
json={'actions': [ACTION]})
actions = self.actions.update(ACTION_DEF, id=123)
self.assertIsNotNone(actions)
self.assertEqual(ACTION_DEF, actions[0].definition)
last_request = self.requests_mock.last_request
self.assertEqual('scope=private', last_request.query)
self.assertEqual('text/plain', last_request.headers['content-type'])
self.assertEqual(ACTION_DEF, last_request.text)
def test_update(self):
self.requests_mock.put(self.TEST_URL + URL_TEMPLATE,
json={'actions': [ACTION]})
actions = self.actions.update(ACTION_DEF)
self.assertIsNotNone(actions)
self.assertEqual(ACTION_DEF, actions[0].definition)
last_request = self.requests_mock.last_request
self.assertEqual('scope=private', last_request.query)
self.assertEqual('text/plain', last_request.headers['content-type'])
self.assertEqual(ACTION_DEF, last_request.text)
def test_update_with_file_uri(self):
self.requests_mock.put(self.TEST_URL + URL_TEMPLATE,
json={'actions': [ACTION]})
# The contents of action_v2.yaml must be identical to ACTION_DEF
path = pkg.resource_filename(
'mistralclient',
'tests/unit/resources/action_v2.yaml'
)
# Convert the file path to file URI
uri = parse.urljoin('file:', request.pathname2url(path))
actions = self.actions.update(uri)
self.assertIsNotNone(actions)
self.assertEqual(ACTION_DEF, actions[0].definition)
last_request = self.requests_mock.last_request
self.assertEqual('scope=private', last_request.query)
self.assertEqual('text/plain', last_request.headers['content-type'])
self.assertEqual(ACTION_DEF, last_request.text)
def test_list(self):
self.requests_mock.get(self.TEST_URL + URL_TEMPLATE,
json={'actions': [ACTION]})
action_list = self.actions.list()
self.assertEqual(1, len(action_list))
action = action_list[0]
self.assertEqual(
actions.Action(self.actions, ACTION).to_dict(),
action.to_dict()
)
def test_list_with_pagination(self):
self.requests_mock.get(self.TEST_URL + URL_TEMPLATE,
json={'actions': [ACTION],
'next': '/actions?fake'})
action_list = self.actions.list(
limit=1,
sort_keys='created_at',
sort_dirs='asc'
)
self.assertEqual(1, len(action_list))
last_request = self.requests_mock.last_request
# The url param order is unpredictable.
self.assertEqual(['1'], last_request.qs['limit'])
self.assertEqual(['created_at'], last_request.qs['sort_keys'])
self.assertEqual(['asc'], last_request.qs['sort_dirs'])
def test_get(self):
self.requests_mock.get(self.TEST_URL + URL_TEMPLATE_NAME % 'action',
json=ACTION)
action = self.actions.get('action')
self.assertIsNotNone(action)
self.assertEqual(
actions.Action(self.actions, ACTION).to_dict(),
action.to_dict()
)
def test_delete(self):
url = self.TEST_URL + URL_TEMPLATE_NAME % 'action'
m = self.requests_mock.delete(url, status_code=204)
self.actions.delete('action')
self.assertEqual(1, m.call_count)
def test_validate(self):
self.requests_mock.post(self.TEST_URL + URL_TEMPLATE_VALIDATE,
json={'valid': True})
result = self.actions.validate(ACTION_DEF)
self.assertIsNotNone(result)
self.assertIn('valid', result)
self.assertTrue(result['valid'])
last_request = self.requests_mock.last_request
self.assertEqual(ACTION_DEF, last_request.text)
self.assertEqual('text/plain', last_request.headers['content-type'])
def test_validate_with_file(self):
self.requests_mock.post(self.TEST_URL + URL_TEMPLATE_VALIDATE,
json={'valid': True})
# The contents of action_v2.yaml must be identical to ACTION_DEF
path = pkg.resource_filename(
'mistralclient',
'tests/unit/resources/action_v2.yaml'
)
result = self.actions.validate(path)
self.assertIsNotNone(result)
self.assertIn('valid', result)
self.assertTrue(result['valid'])
last_request = self.requests_mock.last_request
self.assertEqual(ACTION_DEF, last_request.text)
self.assertEqual('text/plain', last_request.headers['content-type'])
def test_validate_failed(self):
self.requests_mock.post(self.TEST_URL + URL_TEMPLATE_VALIDATE,
json={"valid": False,
"error": "mocked error message"})
result = self.actions.validate(INVALID_ACTION_DEF)
self.assertIsNotNone(result)
self.assertIn('valid', result)
self.assertFalse(result['valid'])
self.assertIn('error', result)
self.assertIn("mocked error message", result['error'])
last_request = self.requests_mock.last_request
self.assertEqual('text/plain', last_request.headers['content-type'])
def test_validate_api_failed(self):
self.requests_mock.post(self.TEST_URL + URL_TEMPLATE_VALIDATE,
status_code=500,
json={})
self.assertRaises(
api_base.APIException,
self.actions.validate,
ACTION_DEF
)
last_request = self.requests_mock.last_request
self.assertEqual('text/plain', last_request.headers['content-type'])
self.assertEqual(ACTION_DEF, last_request.text)

View File

@ -1,228 +0,0 @@
# Copyright 2014 - Mirantis, Inc.
# Copyright 2016 - Brocade Communications Systems, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
import copy
import json
import sys
from six import StringIO
import mock
from mistralclient.api.v2 import action_executions as action_ex
from mistralclient.commands.v2 import action_executions as action_ex_cmd
from mistralclient.tests.unit import base
ACTION_EX_DICT = {
'id': '123',
'name': 'some',
'workflow_name': 'thing',
'task_name': 'task1',
'task_execution_id': "1-2-3-4",
'state': 'RUNNING',
'state_info': 'RUNNING somehow.',
'accepted': True,
'created_at': '1',
'updated_at': '1',
}
ACTION_EX_RESULT = {"test": "is", "passed": "successfully"}
ACTION_EX_INPUT = {"param1": "val1", "param2": 2}
ACTION_EX_WITH_OUTPUT_DICT = ACTION_EX_DICT.copy()
ACTION_EX_WITH_OUTPUT_DICT.update({'output': json.dumps(ACTION_EX_RESULT)})
ACTION_EX_WITH_INPUT_DICT = ACTION_EX_DICT.copy()
ACTION_EX_WITH_INPUT_DICT.update({'input': json.dumps(ACTION_EX_INPUT)})
ACTION_EX = action_ex.ActionExecution(mock, ACTION_EX_DICT)
ACTION_EX_WITH_OUTPUT = action_ex.ActionExecution(
mock,
ACTION_EX_WITH_OUTPUT_DICT
)
ACTION_EX_WITH_INPUT = action_ex.ActionExecution(
mock,
ACTION_EX_WITH_INPUT_DICT
)
class TestCLIActionExecutions(base.BaseCommandTest):
def test_create(self):
(self.client.action_executions.create.
return_value) = ACTION_EX_WITH_OUTPUT
self.call(
action_ex_cmd.Create,
app_args=['some', '{"output": "Hello!"}']
)
self.assertDictEqual(
ACTION_EX_RESULT,
json.loads(self.app.stdout.write.call_args[0][0])
)
def test_create_save_result(self):
(self.client.action_executions.create.
return_value) = ACTION_EX_WITH_OUTPUT
result = self.call(
action_ex_cmd.Create,
app_args=[
'some', '{"output": "Hello!"}', '--save-result'
]
)
self.assertEqual(
('123', 'some', 'thing', 'task1', '1-2-3-4', 'RUNNING',
'RUNNING somehow.', True, '1', '1'),
result[1]
)
def test_create_run_sync(self):
(self.client.action_executions.create.
return_value) = ACTION_EX_WITH_OUTPUT
self.call(
action_ex_cmd.Create,
app_args=[
'some', '{"output": "Hello!"}', '--run-sync'
]
)
self.assertDictEqual(
ACTION_EX_RESULT,
json.loads(self.app.stdout.write.call_args[0][0])
)
def test_create_run_sync_and_save_result(self):
(self.client.action_executions.create.
return_value) = ACTION_EX_WITH_OUTPUT
self.call(
action_ex_cmd.Create,
app_args=[
'some', '{"output": "Hello!"}', '--save-result', '--run-sync'
]
)
self.assertDictEqual(
ACTION_EX_RESULT,
json.loads(self.app.stdout.write.call_args[0][0])
)
def test_update(self):
states = ['IDLE', 'RUNNING', 'SUCCESS', 'ERROR', 'CANCELLED']
for state in states:
action_ex_dict = copy.deepcopy(ACTION_EX_DICT)
action_ex_dict['state'] = state
action_ex_dict['state_info'] = 'testing update'
action_ex_obj = action_ex.ActionExecution(mock, action_ex_dict)
self.client.action_executions.update.return_value = action_ex_obj
result = self.call(
action_ex_cmd.Update,
app_args=['id', '--state', state]
)
expected_result = (
action_ex_dict['id'],
action_ex_dict['name'],
action_ex_dict['workflow_name'],
action_ex_dict['task_name'],
action_ex_dict['task_execution_id'],
action_ex_dict['state'],
action_ex_dict['state_info'],
action_ex_dict['accepted'],
action_ex_dict['created_at'],
action_ex_dict['updated_at']
)
self.assertEqual(expected_result, result[1])
def test_update_invalid_state(self):
states = ['PAUSED', 'WAITING', 'DELAYED']
# Redirect the stderr so it doesn't show during tox
_stderr = sys.stderr
sys.stderr = StringIO()
for state in states:
self.assertRaises(
SystemExit,
self.call,
action_ex_cmd.Update,
app_args=['id', '--state', state]
)
# Stop the redirection
print(sys.stderr.getvalue())
sys.stderr = _stderr
def test_list(self):
self.client.action_executions.list.return_value = [ACTION_EX]
result = self.call(action_ex_cmd.List)
self.assertEqual(
[('123', 'some', 'thing', 'task1', '1-2-3-4', 'RUNNING', True,
'1', '1')],
result[1]
)
def test_get(self):
self.client.action_executions.get.return_value = ACTION_EX
result = self.call(action_ex_cmd.Get, app_args=['id'])
self.assertEqual(
('123', 'some', 'thing', 'task1', '1-2-3-4', 'RUNNING',
'RUNNING somehow.', True, '1', '1'), result[1]
)
def test_get_output(self):
self.client.action_executions.get.return_value = ACTION_EX_WITH_OUTPUT
self.call(action_ex_cmd.GetOutput, app_args=['id'])
self.assertDictEqual(
ACTION_EX_RESULT,
json.loads(self.app.stdout.write.call_args[0][0])
)
def test_get_input(self):
self.client.action_executions.get.return_value = ACTION_EX_WITH_INPUT
self.call(action_ex_cmd.GetInput, app_args=['id'])
self.assertDictEqual(
ACTION_EX_INPUT,
json.loads(self.app.stdout.write.call_args[0][0])
)
def test_delete(self):
self.call(action_ex_cmd.Delete, app_args=['id'])
self.client.action_executions.delete.assert_called_once_with('id')
def test_delete_with_multi_names(self):
self.call(action_ex_cmd.Delete, app_args=['id1', 'id2'])
self.assertEqual(2, self.client.action_executions.delete.call_count)
self.assertEqual(
[mock.call('id1'), mock.call('id2')],
self.client.action_executions.delete.call_args_list
)

View File

@ -1,199 +0,0 @@
# Copyright 2014 Mirantis, 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 mock
import six
from mistralclient.api.v2 import actions
from mistralclient.commands.v2 import actions as action_cmd
from mistralclient.commands.v2 import base as cmd_base
from mistralclient.tests.unit import base
ACTION_DICT = {
'id': '1234-4567-7894-7895',
'name': 'a',
'is_system': True,
'input': "param1",
'description': 'My cool action',
'tags': ['test'],
'created_at': '1',
'updated_at': '1'
}
ACTION_DEF = """
---
version: '2.0'
base: std.echo
base-parameters:
output: "<% $.str1 %><% $.str2 %>"
output: "<% $ %><% $ %>"
"""
ACTION_WITH_DEF_DICT = ACTION_DICT.copy()
ACTION_WITH_DEF_DICT.update({'definition': ACTION_DEF})
ACTION = actions.Action(mock, ACTION_DICT)
ACTION_WITH_DEF = actions.Action(mock, ACTION_WITH_DEF_DICT)
class TestCLIActionsV2(base.BaseCommandTest):
@mock.patch('argparse.open', create=True)
def test_create(self, mock_open):
self.client.actions.create.return_value = [ACTION]
result = self.call(action_cmd.Create, app_args=['1.txt'])
self.assertEqual(
[('1234-4567-7894-7895', 'a', True, "param1",
'My cool action', 'test', '1', '1')],
result[1]
)
@mock.patch('argparse.open', create=True)
def test_create_public(self, mock_open):
self.client.actions.create.return_value = [ACTION]
result = self.call(
action_cmd.Create,
app_args=['1.txt', '--public']
)
self.assertEqual(
[('1234-4567-7894-7895', 'a', True, "param1",
'My cool action', 'test', '1', '1')],
result[1]
)
self.assertEqual(
'public',
self.client.actions.create.call_args[1]['scope']
)
@mock.patch('argparse.open', create=True)
def test_create_long_input(self, mock_open):
action_long_input_dict = ACTION_DICT.copy()
long_input = ', '.join(
['var%s' % i for i in six.moves.xrange(10)]
)
action_long_input_dict['input'] = long_input
workflow_long_input = actions.Action(
mock.Mock(),
action_long_input_dict
)
self.client.actions.create.return_value = [workflow_long_input]
result = self.call(action_cmd.Create, app_args=['1.txt'])
self.assertEqual(
[('1234-4567-7894-7895', 'a', True, cmd_base.cut(long_input),
'My cool action', 'test', '1', '1')],
result[1]
)
@mock.patch('argparse.open', create=True)
def test_update(self, mock_open):
self.client.actions.update.return_value = [ACTION]
result = self.call(action_cmd.Update, app_args=['my_action.yaml'])
self.assertEqual(
[('1234-4567-7894-7895', 'a', True, "param1",
'My cool action', 'test', '1', '1')],
result[1]
)
@mock.patch('argparse.open', create=True)
def test_update_public(self, mock_open):
self.client.actions.update.return_value = [ACTION]
result = self.call(
action_cmd.Update,
app_args=['my_action.yaml', '--public']
)
self.assertEqual(
[('1234-4567-7894-7895', 'a', True, "param1",
'My cool action', 'test', '1', '1')],
result[1]
)
self.assertEqual(
'public',
self.client.actions.update.call_args[1]['scope']
)
def test_list(self):
self.client.actions.list.return_value = [ACTION]
result = self.call(action_cmd.List)
self.assertEqual(
[('1234-4567-7894-7895', 'a', True, "param1",
'My cool action', 'test', '1', '1')],
result[1]
)
def test_get(self):
self.client.actions.get.return_value = ACTION
result = self.call(action_cmd.Get, app_args=['name'])
self.assertEqual(
('1234-4567-7894-7895', 'a', True, "param1",
'My cool action', 'test', '1', '1'),
result[1]
)
def test_delete(self):
self.call(action_cmd.Delete, app_args=['name'])
self.client.actions.delete.assert_called_once_with('name')
def test_delete_with_multi_names(self):
self.call(action_cmd.Delete, app_args=['name1', 'name2'])
self.assertEqual(2, self.client.actions.delete.call_count)
self.assertEqual(
[mock.call('name1'), mock.call('name2')],
self.client.actions.delete.call_args_list
)
def test_get_definition(self):
self.client.actions.get.return_value = ACTION_WITH_DEF
self.call(action_cmd.GetDefinition, app_args=['name'])
self.app.stdout.write.assert_called_with(ACTION_DEF)
@mock.patch('argparse.open', create=True)
def test_validate(self, mock_open):
self.client.actions.validate.return_value = {'valid': True}
result = self.call(action_cmd.Validate, app_args=['action.yaml'])
self.assertEqual((True, None), result[1])
@mock.patch('argparse.open', create=True)
def test_validate_failed(self, mock_open):
self.client.actions.validate.return_value = {
'valid': False,
'error': 'Invalid DSL...'
}
result = self.call(action_cmd.Validate, app_args=['action.yaml'])
self.assertEqual((False, 'Invalid DSL...'), result[1])

View File

@ -1,23 +0,0 @@
# Copyright 2015 Huawei Technologies Co., Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import mistralclient.tests.unit.base_shell_test as base
class TestCLIBashCompletionV2(base.BaseShellTests):
def test_bash_completion(self):
bash_completion, stderr = self.shell('bash-completion')
self.assertIn('bash-completion', bash_completion)
self.assertFalse(stderr)

View File

@ -1,178 +0,0 @@
# Copyright 2014 Mirantis, 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 mock
from mistralclient.api.v2 import cron_triggers
from mistralclient.commands.v2 import cron_triggers as cron_triggers_cmd
from mistralclient.tests.unit import base
TRIGGER_DICT = {
'name': 'my_trigger',
'workflow_name': 'flow1',
'workflow_input': {},
'workflow_params': {},
'pattern': '* * * * *',
'next_execution_time': '4242-12-20 13:37',
'remaining_executions': 5,
'created_at': '1',
'updated_at': '1'
}
TRIGGER = cron_triggers.CronTrigger(mock, TRIGGER_DICT)
class TestCLITriggersV2(base.BaseCommandTest):
@mock.patch('mistralclient.commands.v2.cron_triggers.Create.'
'_convert_time_string_to_utc')
@mock.patch('argparse.open', create=True)
def test_create(self, mock_open, mock_convert):
self.client.cron_triggers.create.return_value = TRIGGER
mock_open.return_value = mock.MagicMock(spec=open)
result = self.call(
cron_triggers_cmd.Create,
app_args=['my_trigger', 'flow1', '--pattern', '* * * * *',
'--params', '{}', '--count', '5', '--first-time',
'4242-12-20 13:37', '--utc']
)
mock_convert.assert_not_called()
self.assertEqual(
(
'my_trigger', 'flow1', {}, '* * * * *',
'4242-12-20 13:37', 5, '1', '1'
),
result[1]
)
@mock.patch('mistralclient.commands.v2.cron_triggers.Create.'
'_convert_time_string_to_utc')
@mock.patch('argparse.open', create=True)
def test_create_no_utc(self, mock_open, mock_convert):
self.client.cron_triggers.create.return_value = TRIGGER
mock_open.return_value = mock.MagicMock(spec=open)
mock_convert.return_value = '4242-12-20 18:37'
result = self.call(
cron_triggers_cmd.Create,
app_args=['my_trigger', 'flow1', '--pattern', '* * * * *',
'--params', '{}', '--count', '5', '--first-time',
'4242-12-20 13:37']
)
mock_convert.assert_called_once_with('4242-12-20 13:37')
self.client.cron_triggers.create.assert_called_once_with(
'my_trigger', 'flow1', {}, {}, '* * * * *', '4242-12-20 18:37', 5)
self.assertEqual(
(
'my_trigger', 'flow1', {}, '* * * * *',
'4242-12-20 13:37', 5, '1', '1'
),
result[1]
)
@mock.patch('mistralclient.commands.v2.cron_triggers.time')
def test_convert_time_string_to_utc_from_utc(self, mock_time):
cmd = cron_triggers_cmd.Create(self.app, None)
mock_time.daylight = 0
mock_time.altzone = 0
mock_time.timezone = 0
mock_localtime = mock.Mock()
mock_localtime.tm_isdst = 0
mock_time.localtime.return_value = mock_localtime
utc_value = cmd._convert_time_string_to_utc('4242-12-20 13:37')
expected_time = '4242-12-20 13:37'
self.assertEqual(expected_time, utc_value)
@mock.patch('mistralclient.commands.v2.cron_triggers.time')
def test_convert_time_string_to_utc_from_dst(self, mock_time):
cmd = cron_triggers_cmd.Create(self.app, None)
mock_time.daylight = 1
mock_time.altzone = (4 * 60 * 60)
mock_time.timezone = (5 * 60 * 60)
mock_localtime = mock.Mock()
mock_localtime.tm_isdst = 1
mock_time.localtime.return_value = mock_localtime
utc_value = cmd._convert_time_string_to_utc('4242-12-20 13:37')
expected_time = '4242-12-20 17:37'
self.assertEqual(expected_time, utc_value)
@mock.patch('mistralclient.commands.v2.cron_triggers.time')
def test_convert_time_string_to_utc_no_dst(self, mock_time):
cmd = cron_triggers_cmd.Create(self.app, None)
mock_time.daylight = 1
mock_time.altzone = (4 * 60 * 60)
mock_time.timezone = (5 * 60 * 60)
mock_localtime = mock.Mock()
mock_localtime.tm_isdst = 0
mock_time.localtime.return_value = mock_localtime
utc_value = cmd._convert_time_string_to_utc('4242-12-20 13:37')
expected_time = '4242-12-20 18:37'
self.assertEqual(expected_time, utc_value)
def test_list(self):
self.client.cron_triggers.list.return_value = [TRIGGER]
result = self.call(cron_triggers_cmd.List)
self.assertEqual(
[(
'my_trigger', 'flow1', {}, '* * * * *',
'4242-12-20 13:37', 5, '1', '1'
)],
result[1]
)
def test_get(self):
self.client.cron_triggers.get.return_value = TRIGGER
result = self.call(cron_triggers_cmd.Get, app_args=['name'])
self.assertEqual(
(
'my_trigger', 'flow1', {}, '* * * * *',
'4242-12-20 13:37', 5, '1', '1'
),
result[1]
)
def test_delete(self):
self.call(cron_triggers_cmd.Delete, app_args=['name'])
self.client.cron_triggers.delete.assert_called_once_with('name')
def test_delete_with_multi_names(self):
self.call(cron_triggers_cmd.Delete, app_args=['name1', 'name2'])
self.assertEqual(2, self.client.cron_triggers.delete.call_count)
self.assertEqual(
[mock.call('name1'), mock.call('name2')],
self.client.cron_triggers.delete.call_args_list
)

View File

@ -1,125 +0,0 @@
# Copyright 2015 - StackStorm, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import copy
import datetime
import json
import os
import tempfile
import mock
import yaml
from mistralclient.api.v2 import environments
from mistralclient.commands.v2 import environments as environment_cmd
from mistralclient.tests.unit import base
ENVIRONMENT_DICT = {
'name': 'env1',
'description': 'Test Environment #1',
'scope': 'private',
'variables': {
'server': 'localhost',
'database': 'test',
'timeout': 600,
'verbose': True
},
'created_at': str(datetime.datetime.utcnow()),
'updated_at': str(datetime.datetime.utcnow())
}
ENVIRONMENT = environments.Environment(mock, ENVIRONMENT_DICT)
EXPECTED_RESULT = (ENVIRONMENT_DICT['name'],
ENVIRONMENT_DICT['description'],
json.dumps(ENVIRONMENT_DICT['variables'], indent=4),
ENVIRONMENT_DICT['scope'],
ENVIRONMENT_DICT['created_at'],
ENVIRONMENT_DICT['updated_at'])
class TestCLIEnvironmentsV2(base.BaseCommandTest):
def _test_create(self, content):
self.client.environments.create.return_value = ENVIRONMENT
with tempfile.NamedTemporaryFile() as f:
f.write(content.encode('utf-8'))
f.flush()
file_path = os.path.abspath(f.name)
result = self.call(environment_cmd.Create, app_args=[file_path])
self.assertEqual(EXPECTED_RESULT, result[1])
def test_create_from_json(self):
self._test_create(json.dumps(ENVIRONMENT_DICT, indent=4))
def test_create_from_yaml(self):
yml = yaml.dump(ENVIRONMENT_DICT, default_flow_style=False)
self._test_create(yml)
def _test_update(self, content):
self.client.environments.update.return_value = ENVIRONMENT
with tempfile.NamedTemporaryFile() as f:
f.write(content.encode('utf-8'))
f.flush()
file_path = os.path.abspath(f.name)
result = self.call(environment_cmd.Update, app_args=[file_path])
self.assertEqual(EXPECTED_RESULT, result[1])
def test_update_from_json(self):
env = copy.deepcopy(ENVIRONMENT_DICT)
del env['created_at']
del env['updated_at']
self._test_update(json.dumps(env, indent=4))
def test_update_from_yaml(self):
env = copy.deepcopy(ENVIRONMENT_DICT)
del env['created_at']
del env['updated_at']
yml = yaml.dump(env, default_flow_style=False)
self._test_update(yml)
def test_list(self):
self.client.environments.list.return_value = [ENVIRONMENT]
expected = (ENVIRONMENT_DICT['name'],
ENVIRONMENT_DICT['description'],
ENVIRONMENT_DICT['scope'],
ENVIRONMENT_DICT['created_at'],
ENVIRONMENT_DICT['updated_at'])
result = self.call(environment_cmd.List)
self.assertListEqual([expected], result[1])
def test_get(self):
self.client.environments.get.return_value = ENVIRONMENT
result = self.call(environment_cmd.Get, app_args=['name'])
self.assertEqual(EXPECTED_RESULT, result[1])
def test_delete(self):
self.call(environment_cmd.Delete, app_args=['name'])
self.client.environments.delete.assert_called_once_with('name')
def test_delete_with_multi_names(self):
self.call(environment_cmd.Delete, app_args=['name1', 'name2'])
self.assertEqual(2, self.client.environments.delete.call_count)
self.assertEqual(
[mock.call('name1'), mock.call('name2')],
self.client.environments.delete.call_args_list
)

View File

@ -1,100 +0,0 @@
# Copyright 2014 Mirantis, 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 mock
from mistralclient.api.v2 import event_triggers
from mistralclient.commands.v2 import event_triggers as event_triggers_cmd
from mistralclient.tests.unit import base
TRIGGER_DICT = {
'id': '456',
'name': 'my_trigger',
'workflow_id': '123e4567-e89b-12d3-a456-426655440000',
'workflow_input': {},
'workflow_params': {},
'exchange': 'dummy_exchange',
'topic': 'dummy_topic',
'event': 'event.dummy',
'created_at': '1',
'updated_at': '1'
}
TRIGGER = event_triggers.EventTrigger(mock, TRIGGER_DICT)
class TestCLITriggersV2(base.BaseCommandTest):
@mock.patch('argparse.open', create=True)
def test_create(self, mock_open):
self.client.event_triggers.create.return_value = TRIGGER
mock_open.return_value = mock.MagicMock(spec=open)
result = self.call(
event_triggers_cmd.Create,
app_args=['my_trigger', '123e4567-e89b-12d3-a456-426655440000',
'dummy_exchange', 'dummy_topic', 'event.dummy',
'--params', '{}']
)
self.assertEqual(
(
'456', 'my_trigger', '123e4567-e89b-12d3-a456-426655440000',
{}, 'dummy_exchange', 'dummy_topic', 'event.dummy', '1', '1'
),
result[1]
)
def test_list(self):
self.client.event_triggers.list.return_value = [TRIGGER]
result = self.call(event_triggers_cmd.List)
self.assertEqual(
[(
'456', 'my_trigger', '123e4567-e89b-12d3-a456-426655440000',
{}, 'dummy_exchange', 'dummy_topic', 'event.dummy', '1', '1'
)],
result[1]
)
def test_get(self):
self.client.event_triggers.get.return_value = TRIGGER
result = self.call(event_triggers_cmd.Get, app_args=['id'])
self.assertEqual(
(
'456', 'my_trigger', '123e4567-e89b-12d3-a456-426655440000',
{}, 'dummy_exchange', 'dummy_topic', 'event.dummy', '1', '1'
),
result[1]
)
def test_delete(self):
self.call(event_triggers_cmd.Delete, app_args=['id'])
self.client.event_triggers.delete.assert_called_once_with('id')
def test_delete_with_multi_names(self):
self.call(event_triggers_cmd.Delete, app_args=['id1', 'id2'])
self.assertEqual(2, self.client.event_triggers.delete.call_count)
self.assertEqual(
[mock.call('id1'), mock.call('id2')],
self.client.event_triggers.delete.call_args_list
)

View File

@ -1,289 +0,0 @@
# Copyright 2014 - Mirantis, Inc.
# Copyright 2015 - StackStorm, Inc.
# Copyright 2016 - Brocade Communications Systems, 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 mock
import pkg_resources as pkg
import six
import sys
from mistralclient.api.v2 import executions
from mistralclient.commands.v2 import executions as execution_cmd
from mistralclient.tests.unit import base
EXEC = executions.Execution(
mock,
{
'id': '123',
'workflow_id': '123e4567-e89b-12d3-a456-426655440000',
'workflow_name': 'some',
'description': '',
'state': 'RUNNING',
'state_info': None,
'created_at': '1',
'updated_at': '1',
'task_execution_id': None
}
)
SUB_WF_EXEC = executions.Execution(
mock,
{
'id': '456',
'workflow_id': '123e4567-e89b-12d3-a456-426655440000',
'workflow_name': 'some_sub_wf',
'description': '',
'state': 'RUNNING',
'state_info': None,
'created_at': '1',
'updated_at': '1',
'task_execution_id': 'abc'
}
)
EX_RESULT = (
'123',
'123e4567-e89b-12d3-a456-426655440000',
'some',
'',
'<none>',
'RUNNING',
None,
'1',
'1'
)
SUB_WF_EX_RESULT = (
'456',
'123e4567-e89b-12d3-a456-426655440000',
'some_sub_wf',
'',
'abc',
'RUNNING',
None,
'1',
'1'
)
class TestCLIExecutionsV2(base.BaseCommandTest):
stdout = six.moves.StringIO()
stderr = six.moves.StringIO()
def setUp(self):
super(TestCLIExecutionsV2, self).setUp()
# Redirect stdout and stderr so it doesn't pollute the test result.
sys.stdout = self.stdout
sys.stderr = self.stderr
def tearDown(self):
super(TestCLIExecutionsV2, self).tearDown()
# Reset to original stdout and stderr.
sys.stdout = sys.__stdout__
sys.stderr = sys.__stderr__
def test_create_wf_input_string(self):
self.client.executions.create.return_value = EXEC
result = self.call(
execution_cmd.Create,
app_args=['id', '{ "context": true }']
)
self.assertEqual(
EX_RESULT,
result[1]
)
def test_create_wf_input_file(self):
self.client.executions.create.return_value = EXEC
path = pkg.resource_filename(
'mistralclient',
'tests/unit/resources/ctx.json'
)
result = self.call(
execution_cmd.Create,
app_args=['id', path]
)
self.assertEqual(
EX_RESULT,
result[1]
)
def test_create_with_description(self):
self.client.executions.create.return_value = EXEC
result = self.call(
execution_cmd.Create,
app_args=['id', '{ "context": true }', '-d', '']
)
self.assertEqual(
EX_RESULT,
result[1]
)
def test_update_state(self):
states = ['RUNNING', 'SUCCESS', 'PAUSED', 'ERROR', 'CANCELLED']
for state in states:
self.client.executions.update.return_value = executions.Execution(
mock,
{
'id': '123',
'workflow_id': '123e4567-e89b-12d3-a456-426655440000',
'workflow_name': 'some',
'description': '',
'state': state,
'state_info': None,
'created_at': '1',
'updated_at': '1',
'task_execution_id': None
}
)
ex_result = list(EX_RESULT)
ex_result[5] = state
ex_result = tuple(ex_result)
result = self.call(
execution_cmd.Update,
app_args=['id', '-s', state]
)
self.assertEqual(
ex_result,
result[1]
)
def test_update_invalid_state(self):
states = ['IDLE', 'WAITING', 'DELAYED']
for state in states:
self.assertRaises(
SystemExit,
self.call,
execution_cmd.Update,
app_args=['id', '-s', state]
)
def test_resume_update_env(self):
self.client.executions.update.return_value = EXEC
result = self.call(
execution_cmd.Update,
app_args=['id', '-s', 'RUNNING', '--env', '{"k1": "foobar"}']
)
self.assertEqual(
EX_RESULT,
result[1]
)
def test_update_description(self):
self.client.executions.update.return_value = EXEC
result = self.call(
execution_cmd.Update,
app_args=['id', '-d', 'foobar']
)
self.assertEqual(
EX_RESULT,
result[1]
)
def test_list(self):
self.client.executions.list.return_value = [EXEC, SUB_WF_EXEC]
result = self.call(execution_cmd.List)
self.assertEqual(
[EX_RESULT, SUB_WF_EX_RESULT],
result[1]
)
def test_list_with_pagination(self):
self.client.executions.list.return_value = [EXEC]
self.call(execution_cmd.List)
self.client.executions.list.assert_called_once_with(
limit=100,
marker='',
sort_dirs='asc',
sort_keys='created_at',
task=None
)
self.call(
execution_cmd.List,
app_args=[
'--limit', '5',
'--sort_dirs', 'id, Workflow',
'--sort_keys', 'desc',
'--marker', 'abc'
]
)
self.client.executions.list.assert_called_with(
limit=5,
marker='abc',
sort_dirs='id, Workflow',
sort_keys='desc',
task=None
)
def test_get(self):
self.client.executions.get.return_value = EXEC
result = self.call(execution_cmd.Get, app_args=['id'])
self.assertEqual(
EX_RESULT,
result[1]
)
def test_get_sub_wf_ex(self):
self.client.executions.get.return_value = SUB_WF_EXEC
result = self.call(execution_cmd.Get, app_args=['id'])
self.assertEqual(
SUB_WF_EX_RESULT,
result[1]
)
def test_delete(self):
self.call(execution_cmd.Delete, app_args=['id'])
self.client.executions.delete.assert_called_once_with('id')
def test_delete_with_multi_names(self):
self.call(execution_cmd.Delete, app_args=['id1', 'id2'])
self.assertEqual(2, self.client.executions.delete.call_count)
self.assertEqual(
[mock.call('id1'), mock.call('id2')],
self.client.executions.delete.call_args_list
)

View File

@ -1,102 +0,0 @@
# Copyright 2016 Catalyst IT Limited
#
# 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 mistralclient.api.v2 import members
from mistralclient.commands.v2 import members as member_cmd
from mistralclient.tests.unit import base
MEMBER_DICT = {
'id': '123',
'resource_id': '456',
'resource_type': 'workflow',
'project_id': '1111',
'member_id': '2222',
'status': 'pending',
'created_at': '1',
'updated_at': '1'
}
MEMBER = members.Member(mock, MEMBER_DICT)
class TestCLIWorkflowMembers(base.BaseCommandTest):
def test_create(self):
self.client.members.create.return_value = MEMBER
result = self.call(
member_cmd.Create,
app_args=[MEMBER_DICT['resource_id'], MEMBER_DICT['resource_type'],
MEMBER_DICT['member_id']]
)
self.assertEqual(
('456', 'workflow', '1111', '2222', 'pending', '1', '1'),
result[1]
)
def test_update(self):
self.client.members.update.return_value = MEMBER
result = self.call(
member_cmd.Update,
app_args=[MEMBER_DICT['resource_id'], MEMBER_DICT['resource_type'],
'-m', MEMBER_DICT['member_id']]
)
self.assertEqual(
('456', 'workflow', '1111', '2222', 'pending', '1', '1'),
result[1]
)
def test_list(self):
self.client.members.list.return_value = [MEMBER]
result = self.call(
member_cmd.List,
app_args=[MEMBER_DICT['resource_id'], MEMBER_DICT['resource_type']]
)
self.assertListEqual(
[('456', 'workflow', '1111', '2222', 'pending', '1', '1')],
result[1]
)
def test_get(self):
self.client.members.get.return_value = MEMBER
result = self.call(
member_cmd.Get,
app_args=[MEMBER_DICT['resource_id'], MEMBER_DICT['resource_type'],
'-m', MEMBER_DICT['member_id']]
)
self.assertEqual(
('456', 'workflow', '1111', '2222', 'pending', '1', '1'),
result[1]
)
def test_delete(self):
self.call(
member_cmd.Delete,
app_args=[MEMBER_DICT['resource_id'], MEMBER_DICT['resource_type'],
MEMBER_DICT['member_id']]
)
self.client.members.delete.assert_called_once_with(
MEMBER_DICT['resource_id'],
MEMBER_DICT['resource_type'],
MEMBER_DICT['member_id']
)

View File

@ -1,37 +0,0 @@
# Copyright 2015 Huawei Technologies Co., Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import mock
from mistralclient.api.v2 import services
from mistralclient.commands.v2 import services as service_cmd
from mistralclient.tests.unit import base
SERVICE_DICT = {
'name': 'service_name',
'type': 'service_type',
}
SERVICE = services.Service(mock, SERVICE_DICT)
class TestCLIServicesV2(base.BaseCommandTest):
def test_list(self):
self.client.services.list.return_value = [SERVICE]
expected = (SERVICE_DICT['name'], SERVICE_DICT['type'],)
result = self.call(service_cmd.List)
self.assertListEqual([expected], result[1])

View File

@ -1,128 +0,0 @@
# Copyright 2014 - Mirantis, Inc.
# Copyright 2015 - StackStorm, 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 json
import mock
from mistralclient.api.v2 import tasks
from mistralclient.commands.v2 import tasks as task_cmd
from mistralclient.tests.unit import base
TASK_DICT = {
'id': '123',
'name': 'some',
'workflow_name': 'thing',
'workflow_execution_id': '321',
'state': 'RUNNING',
'state_info': None,
'created_at': '1',
'updated_at': '1',
}
TASK_RESULT = {"test": "is", "passed": "successfully"}
TASK_PUBLISHED = {"bar1": "val1", "var2": 2}
TASK_WITH_RESULT_DICT = TASK_DICT.copy()
TASK_WITH_RESULT_DICT.update({'result': json.dumps(TASK_RESULT)})
TASK_WITH_PUBLISHED_DICT = TASK_DICT.copy()
TASK_WITH_PUBLISHED_DICT.update({'published': json.dumps(TASK_PUBLISHED)})
TASK = tasks.Task(mock, TASK_DICT)
TASK_WITH_RESULT = tasks.Task(mock, TASK_WITH_RESULT_DICT)
TASK_WITH_PUBLISHED = tasks.Task(mock, TASK_WITH_PUBLISHED_DICT)
EXPECTED_TASK_RESULT = (
'123', 'some', 'thing', '321', 'RUNNING', None, '1', '1'
)
class TestCLITasksV2(base.BaseCommandTest):
def test_list(self):
self.client.tasks.list.return_value = [TASK]
result = self.call(task_cmd.List)
self.assertEqual([EXPECTED_TASK_RESULT], result[1])
def test_list_with_workflow_execution(self):
self.client.tasks.list.return_value = [TASK]
result = self.call(task_cmd.List, app_args=['workflow_execution'])
self.assertEqual([EXPECTED_TASK_RESULT], result[1])
def test_get(self):
self.client.tasks.get.return_value = TASK
result = self.call(task_cmd.Get, app_args=['id'])
self.assertEqual(EXPECTED_TASK_RESULT, result[1])
def test_get_result(self):
self.client.tasks.get.return_value = TASK_WITH_RESULT
self.call(task_cmd.GetResult, app_args=['id'])
self.assertDictEqual(
TASK_RESULT,
json.loads(self.app.stdout.write.call_args[0][0])
)
def test_get_published(self):
self.client.tasks.get.return_value = TASK_WITH_PUBLISHED
self.call(task_cmd.GetPublished, app_args=['id'])
self.assertDictEqual(
TASK_PUBLISHED,
json.loads(self.app.stdout.write.call_args[0][0])
)
def test_rerun(self):
self.client.tasks.rerun.return_value = TASK
result = self.call(task_cmd.Rerun, app_args=['id'])
self.assertEqual(EXPECTED_TASK_RESULT, result[1])
def test_rerun_no_reset(self):
self.client.tasks.rerun.return_value = TASK
result = self.call(task_cmd.Rerun, app_args=['id', '--resume'])
self.assertEqual(EXPECTED_TASK_RESULT, result[1])
def test_rerun_update_env(self):
self.client.tasks.rerun.return_value = TASK
result = self.call(
task_cmd.Rerun,
app_args=['id', '--env', '{"k1": "foobar"}']
)
self.assertEqual(EXPECTED_TASK_RESULT, result[1])
def test_rerun_no_reset_update_env(self):
self.client.tasks.rerun.return_value = TASK
result = self.call(
task_cmd.Rerun,
app_args=['id', '--resume', '--env', '{"k1": "foobar"}']
)
self.assertEqual(EXPECTED_TASK_RESULT, result[1])

View File

@ -1,119 +0,0 @@
# Copyright 2014 - Mirantis, Inc.
# Copyright 2015 - StackStorm, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import mock
from mistralclient.api.v2 import workbooks
from mistralclient.commands.v2 import workbooks as workbook_cmd
from mistralclient.tests.unit import base
WORKBOOK_DICT = {
'name': 'a',
'tags': ['a', 'b'],
'created_at': '1',
'updated_at': '1'
}
WB_DEF = """
---
version: '2.0
name: wb
workflows:
wf1:
tasks:
task1:
action: nova.servers_get server="1"
"""
WB_WITH_DEF_DICT = WORKBOOK_DICT.copy()
WB_WITH_DEF_DICT.update({'definition': WB_DEF})
WORKBOOK = workbooks.Workbook(mock, WORKBOOK_DICT)
WORKBOOK_WITH_DEF = workbooks.Workbook(mock, WB_WITH_DEF_DICT)
class TestCLIWorkbooksV2(base.BaseCommandTest):
@mock.patch('argparse.open', create=True)
def test_create(self, mock_open):
self.client.workbooks.create.return_value = WORKBOOK
result = self.call(workbook_cmd.Create, app_args=['wb.yaml'])
self.assertEqual(('a', 'a, b', '1', '1'), result[1])
@mock.patch('argparse.open', create=True)
def test_update(self, mock_open):
self.client.workbooks.update.return_value = WORKBOOK
result = self.call(workbook_cmd.Update, app_args=['definition'])
self.assertEqual(('a', 'a, b', '1', '1'), result[1])
def test_list(self):
self.client.workbooks.list.return_value = [WORKBOOK]
result = self.call(workbook_cmd.List)
self.assertEqual([('a', 'a, b', '1', '1')], result[1])
def test_get(self):
self.client.workbooks.get.return_value = WORKBOOK
result = self.call(workbook_cmd.Get, app_args=['name'])
self.assertEqual(('a', 'a, b', '1', '1'), result[1])
def test_delete(self):
self.call(workbook_cmd.Delete, app_args=['name'])
self.client.workbooks.delete.assert_called_once_with('name')
def test_delete_with_multi_names(self):
self.call(workbook_cmd.Delete, app_args=['name1', 'name2'])
self.assertEqual(2, self.client.workbooks.delete.call_count)
self.assertEqual(
[mock.call('name1'), mock.call('name2')],
self.client.workbooks.delete.call_args_list
)
def test_get_definition(self):
self.client.workbooks.get.return_value = WORKBOOK_WITH_DEF
self.call(workbook_cmd.GetDefinition, app_args=['name'])
self.app.stdout.write.assert_called_with(WB_DEF)
@mock.patch('argparse.open', create=True)
def test_validate(self, mock_open):
self.client.workbooks.validate.return_value = {'valid': True}
result = self.call(workbook_cmd.Validate, app_args=['wb.yaml'])
self.assertEqual((True, None), result[1])
@mock.patch('argparse.open', create=True)
def test_validate_failed(self, mock_open):
self.client.workbooks.validate.return_value = {
'valid': False,
'error': 'Invalid DSL...'
}
result = self.call(workbook_cmd.Validate, app_args=['wb.yaml'])
self.assertEqual((False, 'Invalid DSL...'), result[1])

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