Retire the python-qinlingclient project
As announced in openstack-discuss ML[1], Qinling project is retiring in Wallaby cycle. This commit retires the python-qinlingclient repository as per process deinfed in project-guide[2]. Anyone would like to maintain it again, please revert back this commit and propose the re-adding it to governance. The community wishes to express our thanks and appreciation to all of those who have contributed to the python-qinlingclient project over the years. Depends-On: https://review.opendev.org/c/openstack/project-config/+/764520 Needed-By: https://review.opendev.org/c/openstack/governance/+/764523 Change-Id: Idc709077f3f1fbbc81795b32a0cf5fc005f3dd27
This commit is contained in:
parent
8e5fcbbca2
commit
4d184021c2
|
@ -1,8 +0,0 @@
|
||||||
[run]
|
|
||||||
source = qinlingclient
|
|
||||||
omit =
|
|
||||||
.tox/*
|
|
||||||
qinlingclient/tests/*
|
|
||||||
|
|
||||||
[report]
|
|
||||||
ignore_errors = True
|
|
|
@ -1,41 +0,0 @@
|
||||||
#IntelJ Idea
|
|
||||||
.idea/
|
|
||||||
|
|
||||||
#virtualenv
|
|
||||||
.venv/
|
|
||||||
|
|
||||||
#Build results
|
|
||||||
build/
|
|
||||||
dist/
|
|
||||||
*.egg-info/
|
|
||||||
.tox
|
|
||||||
AUTHORS
|
|
||||||
ChangeLog
|
|
||||||
eggs/
|
|
||||||
.eggs/
|
|
||||||
develop-eggs/
|
|
||||||
*.egg
|
|
||||||
|
|
||||||
#Python
|
|
||||||
*.pyc
|
|
||||||
|
|
||||||
#Translation build
|
|
||||||
*.mo
|
|
||||||
|
|
||||||
#SQLite Database files
|
|
||||||
*.sqlite
|
|
||||||
|
|
||||||
#Autogenerated Documentation
|
|
||||||
doc/source/api
|
|
||||||
|
|
||||||
#Testing framework
|
|
||||||
.stestr
|
|
||||||
.coverage
|
|
||||||
*,cover
|
|
||||||
cover
|
|
||||||
|
|
||||||
#swap file
|
|
||||||
*.swp
|
|
||||||
|
|
||||||
# Files created by releasenotes build
|
|
||||||
releasenotes/build
|
|
|
@ -1,4 +0,0 @@
|
||||||
[DEFAULT]
|
|
||||||
test_path=${OS_TEST_PATH:-./qinlingclient/tests/unit}
|
|
||||||
top_dir=./
|
|
||||||
|
|
12
.zuul.yaml
12
.zuul.yaml
|
@ -1,12 +0,0 @@
|
||||||
- project:
|
|
||||||
templates:
|
|
||||||
- openstack-cover-jobs
|
|
||||||
- openstack-lower-constraints-jobs
|
|
||||||
- openstack-python3-wallaby-jobs
|
|
||||||
- check-requirements
|
|
||||||
- publish-openstack-docs-pti
|
|
||||||
- release-notes-jobs-python3
|
|
||||||
- openstackclient-plugin-jobs
|
|
||||||
gate:
|
|
||||||
queue: qinling
|
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
If you would like to contribute to the development of OpenStack, you must
|
|
||||||
follow the steps in this page:
|
|
||||||
|
|
||||||
http://docs.openstack.org/infra/manual/developers.html
|
|
||||||
|
|
||||||
If you already have a good understanding of how the system works and your
|
|
||||||
OpenStack accounts are set up, you can skip to the development workflow
|
|
||||||
section of this documentation to learn how changes to OpenStack should be
|
|
||||||
submitted for review via the Gerrit tool:
|
|
||||||
|
|
||||||
http://docs.openstack.org/infra/manual/developers.html#development-workflow
|
|
||||||
|
|
||||||
Pull requests submitted through GitHub will be ignored.
|
|
||||||
|
|
||||||
Bugs should be filed on Storyboard, not GitHub:
|
|
||||||
|
|
||||||
https://storyboard.openstack.org/#!/project/926
|
|
|
@ -1,4 +0,0 @@
|
||||||
Style Commandments
|
|
||||||
==================
|
|
||||||
|
|
||||||
Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/
|
|
176
LICENSE
176
LICENSE
|
@ -1,176 +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.
|
|
||||||
|
|
52
README.rst
52
README.rst
|
@ -1,46 +1,10 @@
|
||||||
========================
|
This project is no longer maintained.
|
||||||
Team and repository tags
|
|
||||||
========================
|
|
||||||
|
|
||||||
.. image:: https://governance.openstack.org/tc/badges/python-qinlingclient.svg
|
The contents of this repository are still available in the Git
|
||||||
:target: https://governance.openstack.org/reference/tags/index.html
|
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".
|
||||||
|
|
||||||
.. Change things from this point on
|
For any further questions, please email
|
||||||
|
openstack-discuss@lists.openstack.org or join #openstack-dev on
|
||||||
====================
|
Freenode.
|
||||||
python-qinlingclient
|
|
||||||
====================
|
|
||||||
|
|
||||||
This is an OpenStack Client (OSC) plugin for Qinling, an OpenStack
|
|
||||||
Function as a Service project.
|
|
||||||
|
|
||||||
For more information about Qinling see:
|
|
||||||
https://docs.openstack.org/qinling/latest/
|
|
||||||
|
|
||||||
For more information about the OpenStack Client see:
|
|
||||||
https://docs.openstack.org/python-openstackclient/latest/
|
|
||||||
|
|
||||||
* Free software: Apache license
|
|
||||||
* Documentation: https://docs.openstack.org/python-qinlingclient/latest/
|
|
||||||
* Release notes: https://docs.openstack.org/releasenotes/python-qinlingclient/
|
|
||||||
* Source: https://opendev.org/openstack/python-qinlingclient
|
|
||||||
* Bugs: https://storyboard.openstack.org/#!/project/926
|
|
||||||
|
|
||||||
Getting Started
|
|
||||||
===============
|
|
||||||
|
|
||||||
.. note:: This is an OpenStack Client plugin. The ``python-openstackclient``
|
|
||||||
project should be installed to use this plugin.
|
|
||||||
|
|
||||||
Qinling client can be installed from PyPI using pip::
|
|
||||||
|
|
||||||
pip install python-qinlingclient
|
|
||||||
|
|
||||||
If you want to make changes to the Qinling client for testing and contribution,
|
|
||||||
make any changes and then run::
|
|
||||||
|
|
||||||
python setup.py develop
|
|
||||||
|
|
||||||
or::
|
|
||||||
|
|
||||||
pip install -e .
|
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
# The order of packages is significant, because pip processes them in the order
|
|
||||||
# of appearance. Changing the order has an impact on the overall integration
|
|
||||||
# process, which may cause wedges in the gate later.
|
|
||||||
|
|
||||||
sphinx>=2.0.0,!=2.1.0 # BSD
|
|
||||||
openstackdocstheme>=2.2.0 # Apache-2.0
|
|
||||||
reno>=3.1.0 # Apache-2.0
|
|
|
@ -1,2 +0,0 @@
|
||||||
|
|
||||||
.. include:: osc/v1/qinling.rst
|
|
|
@ -1,30 +0,0 @@
|
||||||
Using Qinling CLI extensions to OpenStack Client
|
|
||||||
================================================
|
|
||||||
|
|
||||||
=======
|
|
||||||
runtime
|
|
||||||
=======
|
|
||||||
|
|
||||||
.. autoprogram-cliff:: openstack.function_engine.v1
|
|
||||||
:command: runtime *
|
|
||||||
|
|
||||||
========
|
|
||||||
function
|
|
||||||
========
|
|
||||||
|
|
||||||
.. autoprogram-cliff:: openstack.function_engine.v1
|
|
||||||
:command: function *
|
|
||||||
|
|
||||||
===
|
|
||||||
job
|
|
||||||
===
|
|
||||||
|
|
||||||
.. autoprogram-cliff:: openstack.function_engine.v1
|
|
||||||
:command: job *
|
|
||||||
|
|
||||||
=======
|
|
||||||
webhook
|
|
||||||
=======
|
|
||||||
|
|
||||||
.. autoprogram-cliff:: openstack.function_engine.v1
|
|
||||||
:command: webhook *
|
|
|
@ -1,83 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
# implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
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',
|
|
||||||
'cliff.sphinxext'
|
|
||||||
]
|
|
||||||
|
|
||||||
# autodoc generation is a bit aggressive and a nuisance when doing heavy
|
|
||||||
# text edit cycles.
|
|
||||||
# execute "export SPHINX_DEBUG=1" in your terminal to disable
|
|
||||||
|
|
||||||
# The suffix of source filenames.
|
|
||||||
source_suffix = '.rst'
|
|
||||||
|
|
||||||
# The master toctree document.
|
|
||||||
master_doc = 'index'
|
|
||||||
|
|
||||||
# General information about the project.
|
|
||||||
project = u'python-qinlingclient'
|
|
||||||
copyright = u'2016, OpenStack Foundation'
|
|
||||||
|
|
||||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
|
||||||
add_function_parentheses = True
|
|
||||||
|
|
||||||
# If true, the current module name will be prepended to all description
|
|
||||||
# unit titles (such as .. function::).
|
|
||||||
add_module_names = True
|
|
||||||
|
|
||||||
# The name of the Pygments (syntax highlighting) style to use.
|
|
||||||
pygments_style = 'sphinx'
|
|
||||||
|
|
||||||
# -- Options for HTML output --------------------------------------------------
|
|
||||||
|
|
||||||
# The theme to use for HTML and HTML Help pages. Major themes that come with
|
|
||||||
# Sphinx are currently 'default' and 'sphinxdoc'.
|
|
||||||
# html_theme_path = ["."]
|
|
||||||
# html_theme = '_theme'
|
|
||||||
# html_static_path = ['static']
|
|
||||||
|
|
||||||
html_theme = 'openstackdocs'
|
|
||||||
|
|
||||||
# Output file base name for HTML help builder.
|
|
||||||
htmlhelp_basename = '%sdoc' % project
|
|
||||||
|
|
||||||
# Grouping the document tree into LaTeX files. List of tuples
|
|
||||||
# (source start file, target name, title, author, documentclass
|
|
||||||
# [howto/manual]).
|
|
||||||
latex_documents = [
|
|
||||||
('index',
|
|
||||||
'%s.tex' % project,
|
|
||||||
u'%s Documentation' % project,
|
|
||||||
u'OpenStack Foundation', 'manual'),
|
|
||||||
]
|
|
||||||
|
|
||||||
# Example configuration for intersphinx: refer to the Python standard library.
|
|
||||||
#intersphinx_mapping = {'http://docs.python.org/': None}
|
|
||||||
|
|
||||||
# openstackdocstheme options
|
|
||||||
openstackdocs_repo_name = 'openstack/python-qinlingclient'
|
|
||||||
openstackdocs_use_storyboard = True
|
|
||||||
|
|
||||||
autoprogram_cliff_application = 'openstack'
|
|
|
@ -1,4 +0,0 @@
|
||||||
============
|
|
||||||
Contributing
|
|
||||||
============
|
|
||||||
.. include:: ../../../CONTRIBUTING.rst
|
|
|
@ -1,29 +0,0 @@
|
||||||
Python Qinling Client
|
|
||||||
=====================
|
|
||||||
|
|
||||||
The Python Qinling Client (python-qinlingclient) is a command-line client for
|
|
||||||
the OpenStack Function as a Service.
|
|
||||||
|
|
||||||
Getting Started
|
|
||||||
---------------
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 2
|
|
||||||
|
|
||||||
Project Overview <readme>
|
|
||||||
install/index
|
|
||||||
contributor/index
|
|
||||||
|
|
||||||
Usage
|
|
||||||
-----
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 2
|
|
||||||
|
|
||||||
cli/index
|
|
||||||
|
|
||||||
Indices and tables
|
|
||||||
------------------
|
|
||||||
|
|
||||||
* :ref:`genindex`
|
|
||||||
* :ref:`search`
|
|
|
@ -1,17 +0,0 @@
|
||||||
============
|
|
||||||
Installation
|
|
||||||
============
|
|
||||||
|
|
||||||
This is an OpenStack Client plugin for the FaaS (Qinling) project.
|
|
||||||
|
|
||||||
Install the plugin::
|
|
||||||
|
|
||||||
$ pip install python-qinlingclient
|
|
||||||
|
|
||||||
Or, if you have virtualenvwrapper installed::
|
|
||||||
|
|
||||||
$ mkvirtualenv python-qinlingclient
|
|
||||||
$ pip install python-qinlingclient
|
|
||||||
|
|
||||||
.. note:: If python-openstackclient is not already installed it will be
|
|
||||||
installed as part of the requirements for the Qinling client plugin.
|
|
|
@ -1 +0,0 @@
|
||||||
.. include:: ../../README.rst
|
|
|
@ -1,81 +0,0 @@
|
||||||
alabaster==0.7.10
|
|
||||||
appdirs==1.3.0
|
|
||||||
asn1crypto==0.23.0
|
|
||||||
Babel==2.3.4
|
|
||||||
cffi==1.14.0
|
|
||||||
cliff==2.8.0
|
|
||||||
cmd2==0.8.0
|
|
||||||
coverage==4.0
|
|
||||||
cryptography==2.7
|
|
||||||
debtcollector==1.2.0
|
|
||||||
decorator==3.4.0
|
|
||||||
deprecation==1.0
|
|
||||||
docutils==0.11
|
|
||||||
dogpile.cache==0.6.2
|
|
||||||
dulwich==0.15.0
|
|
||||||
extras==1.0.0
|
|
||||||
fixtures==3.0.0
|
|
||||||
future==0.16.0
|
|
||||||
idna==2.6
|
|
||||||
imagesize==0.7.1
|
|
||||||
iso8601==0.1.11
|
|
||||||
Jinja2==2.10
|
|
||||||
jmespath==0.9.0
|
|
||||||
jsonpatch==1.16
|
|
||||||
jsonpointer==1.13
|
|
||||||
jsonschema==2.6.0
|
|
||||||
keystoneauth1==3.4.0
|
|
||||||
linecache2==1.0.0
|
|
||||||
MarkupSafe==1.0
|
|
||||||
monotonic==0.6
|
|
||||||
mox3==0.20.0
|
|
||||||
msgpack-python==0.4.0
|
|
||||||
munch==2.1.0
|
|
||||||
netaddr==0.7.18
|
|
||||||
netifaces==0.10.4
|
|
||||||
openstacksdk==0.11.2
|
|
||||||
os-client-config==1.28.0
|
|
||||||
os-service-types==1.2.0
|
|
||||||
os-testr==1.0.0
|
|
||||||
osc-lib==1.8.0
|
|
||||||
oslo.config==5.2.0
|
|
||||||
oslo.context==2.19.2
|
|
||||||
oslo.i18n==3.15.3
|
|
||||||
oslo.log==3.36.0
|
|
||||||
oslo.serialization==2.18.0
|
|
||||||
oslo.utils==3.33.0
|
|
||||||
oslotest==3.2.0
|
|
||||||
pbr==2.0.0
|
|
||||||
positional==1.2.1
|
|
||||||
prettytable==0.7.2
|
|
||||||
pycparser==2.18
|
|
||||||
Pygments==2.2.0
|
|
||||||
pyinotify==0.9.6
|
|
||||||
pyOpenSSL==17.1.0
|
|
||||||
pyparsing==2.1.0
|
|
||||||
pyperclip==1.5.27
|
|
||||||
python-cinderclient==3.3.0
|
|
||||||
python-dateutil==2.5.3
|
|
||||||
python-glanceclient==2.8.0
|
|
||||||
python-keystoneclient==3.8.0
|
|
||||||
python-mimeparse==1.6.0
|
|
||||||
python-novaclient==9.1.0
|
|
||||||
python-openstackclient==3.12.0
|
|
||||||
python-subunit==1.0.0
|
|
||||||
pytz==2013.6
|
|
||||||
PyYAML==3.13
|
|
||||||
reno==3.1.0
|
|
||||||
requests==2.14.2
|
|
||||||
requests-mock==1.5.2
|
|
||||||
requestsexceptions==1.2.0
|
|
||||||
rfc3986==0.3.1
|
|
||||||
simplejson==3.5.1
|
|
||||||
snowballstemmer==1.2.1
|
|
||||||
stevedore==1.20.0
|
|
||||||
stestr==2.0.0
|
|
||||||
testscenarios==0.4
|
|
||||||
testtools==2.2.0
|
|
||||||
traceback2==1.4.0
|
|
||||||
unittest2==1.1.0
|
|
||||||
warlock==1.2.0
|
|
||||||
wrapt==1.7.0
|
|
|
@ -1,22 +0,0 @@
|
||||||
# Copyright 2017 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 pbr.version
|
|
||||||
|
|
||||||
version_info = pbr.version.VersionInfo('python-qinlingclient')
|
|
||||||
|
|
||||||
try:
|
|
||||||
__version__ = version_info.version_string()
|
|
||||||
except AttributeError:
|
|
||||||
__version__ = None
|
|
|
@ -1,22 +0,0 @@
|
||||||
# Copyright 2017 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 oslo_utils import importutils
|
|
||||||
|
|
||||||
|
|
||||||
def Client(version, *args, **kwargs):
|
|
||||||
module = importutils.import_versioned_module('qinlingclient',
|
|
||||||
version, 'client')
|
|
||||||
client_class = getattr(module, 'Client')
|
|
||||||
return client_class(*args, **kwargs)
|
|
|
@ -1,232 +0,0 @@
|
||||||
# Copyright 2012 OpenStack LLC.
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
"""
|
|
||||||
Base utilities to build API operation managers and objects on top of.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import abc
|
|
||||||
import copy
|
|
||||||
|
|
||||||
|
|
||||||
from qinlingclient.common import exceptions
|
|
||||||
|
|
||||||
|
|
||||||
def getid(obj):
|
|
||||||
"""Get obj's id or object itself if no id
|
|
||||||
|
|
||||||
Abstracts the common pattern of allowing both an object or
|
|
||||||
an object's ID (UUID) as a parameter when dealing with relationships.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return obj.id
|
|
||||||
except AttributeError:
|
|
||||||
return obj
|
|
||||||
|
|
||||||
|
|
||||||
class Manager(object):
|
|
||||||
"""Interacts with type of API
|
|
||||||
|
|
||||||
Managers interact with a particular type of API (servers, flavors,
|
|
||||||
images, etc.) and provide CRUD operations for them.
|
|
||||||
"""
|
|
||||||
resource_class = None
|
|
||||||
|
|
||||||
def __init__(self, http_client):
|
|
||||||
self.http_client = http_client
|
|
||||||
|
|
||||||
def _list(self, url, response_key=None, obj_class=None,
|
|
||||||
data=None, headers=None):
|
|
||||||
|
|
||||||
if headers is None:
|
|
||||||
headers = {}
|
|
||||||
resp, body = self.http_client.json_request(url, 'GET', headers=headers)
|
|
||||||
|
|
||||||
if obj_class is None:
|
|
||||||
obj_class = self.resource_class
|
|
||||||
|
|
||||||
if response_key:
|
|
||||||
if response_key not in body:
|
|
||||||
body[response_key] = []
|
|
||||||
data = body[response_key]
|
|
||||||
else:
|
|
||||||
data = body
|
|
||||||
return [obj_class(self, res, loaded=True) for res in data if res]
|
|
||||||
|
|
||||||
def _delete(self, url, headers=None):
|
|
||||||
if headers is None:
|
|
||||||
headers = {}
|
|
||||||
self.http_client.request(url, 'DELETE', headers=headers)
|
|
||||||
|
|
||||||
def _update(self, url, data, response_key=None, return_raw=False,
|
|
||||||
headers=None, method='PUT', content_type='application/json'):
|
|
||||||
if headers is None:
|
|
||||||
headers = {}
|
|
||||||
resp, body = self.http_client.json_request(url, method,
|
|
||||||
content_type=content_type,
|
|
||||||
data=data, headers=headers)
|
|
||||||
# PUT or PATCH requests may not return a body
|
|
||||||
if body:
|
|
||||||
if return_raw:
|
|
||||||
if response_key:
|
|
||||||
return body[response_key]
|
|
||||||
return body
|
|
||||||
if response_key:
|
|
||||||
return self.resource_class(self, body[response_key])
|
|
||||||
return self.resource_class(self, body)
|
|
||||||
|
|
||||||
def _create(self, url, data=None, response_key=None,
|
|
||||||
return_raw=False, headers=None):
|
|
||||||
if headers is None:
|
|
||||||
headers = {}
|
|
||||||
if data:
|
|
||||||
resp, body = self.http_client.json_request(url, 'POST',
|
|
||||||
data=data,
|
|
||||||
headers=headers)
|
|
||||||
else:
|
|
||||||
resp, body = self.http_client.json_request(
|
|
||||||
url, 'POST', headers=headers)
|
|
||||||
if return_raw:
|
|
||||||
if response_key:
|
|
||||||
return body[response_key]
|
|
||||||
return body
|
|
||||||
if response_key:
|
|
||||||
return self.resource_class(self, body[response_key])
|
|
||||||
return self.resource_class(self, body)
|
|
||||||
|
|
||||||
def _get(self, url, response_key=None, return_raw=False, headers=None):
|
|
||||||
if headers is None:
|
|
||||||
headers = {}
|
|
||||||
resp, body = self.http_client.json_request(url, 'GET', headers=headers)
|
|
||||||
if return_raw:
|
|
||||||
if response_key:
|
|
||||||
return body[response_key]
|
|
||||||
return body
|
|
||||||
if response_key:
|
|
||||||
return self.resource_class(self, body[response_key])
|
|
||||||
return self.resource_class(self, body)
|
|
||||||
|
|
||||||
|
|
||||||
class ManagerWithFind(Manager, metaclass=abc.ABCMeta):
|
|
||||||
"""Manager with additional `find()`/`findall()` methods."""
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def list(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def find(self, **kwargs):
|
|
||||||
"""Find a single item with attributes matching ``**kwargs``.
|
|
||||||
|
|
||||||
This isn't very efficient: it loads the entire list then filters on
|
|
||||||
the Python side.
|
|
||||||
"""
|
|
||||||
rl = self.findall(**kwargs)
|
|
||||||
num = len(rl)
|
|
||||||
|
|
||||||
if num == 0:
|
|
||||||
msg = "No %s matching %s." % (self.resource_class.__name__, kwargs)
|
|
||||||
raise exceptions.NotFound(msg)
|
|
||||||
elif num > 1:
|
|
||||||
raise exceptions.NoUniqueMatch
|
|
||||||
else:
|
|
||||||
return self.get(rl[0].id)
|
|
||||||
|
|
||||||
def findall(self, **kwargs):
|
|
||||||
"""Find all items with attributes matching ``**kwargs``.
|
|
||||||
|
|
||||||
This isn't very efficient: it loads the entire list then filters on
|
|
||||||
the Python side.
|
|
||||||
"""
|
|
||||||
found = []
|
|
||||||
searches = kwargs.items()
|
|
||||||
|
|
||||||
for obj in self.list():
|
|
||||||
try:
|
|
||||||
if all(getattr(obj, attr) == value
|
|
||||||
for (attr, value) in searches):
|
|
||||||
found.append(obj)
|
|
||||||
except AttributeError:
|
|
||||||
continue
|
|
||||||
|
|
||||||
return found
|
|
||||||
|
|
||||||
|
|
||||||
class Resource(object):
|
|
||||||
"""Represents an instance of an object
|
|
||||||
|
|
||||||
A resource represents a particular instance of an object (tenant, user,
|
|
||||||
etc). This is pretty much just a bag for attributes.
|
|
||||||
|
|
||||||
:param manager: Manager object
|
|
||||||
:param info: dictionary representing resource attributes
|
|
||||||
:param loaded: prevent lazy-loading if set to True
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, manager, info, loaded=False):
|
|
||||||
self.manager = manager
|
|
||||||
self._info = info
|
|
||||||
self._add_details(info)
|
|
||||||
self._loaded = loaded
|
|
||||||
|
|
||||||
def _add_details(self, info):
|
|
||||||
for k, v in info.items():
|
|
||||||
setattr(self, k, v)
|
|
||||||
|
|
||||||
def __setstate__(self, d):
|
|
||||||
for k, v in d.items():
|
|
||||||
setattr(self, k, v)
|
|
||||||
|
|
||||||
def __getattr__(self, k):
|
|
||||||
if k not in self.__dict__:
|
|
||||||
# NOTE(bcwaldon): disallow lazy-loading if already loaded once
|
|
||||||
if not self.is_loaded():
|
|
||||||
self.get()
|
|
||||||
return self.__getattr__(k)
|
|
||||||
raise AttributeError(k)
|
|
||||||
else:
|
|
||||||
return self.__dict__[k]
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
reprkeys = sorted(k for k in self.__dict__.keys() if k[0] != '_' and
|
|
||||||
k != 'manager')
|
|
||||||
info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys)
|
|
||||||
return "<%s %s>" % (self.__class__.__name__, info)
|
|
||||||
|
|
||||||
def get(self):
|
|
||||||
# set_loaded() first ... so if we have to bail, we know we tried.
|
|
||||||
self.set_loaded(True)
|
|
||||||
if not hasattr(self.manager, 'get'):
|
|
||||||
return
|
|
||||||
|
|
||||||
new = self.manager.get(self.id)
|
|
||||||
if new:
|
|
||||||
self._add_details(new._info)
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
if not isinstance(other, self.__class__):
|
|
||||||
return False
|
|
||||||
return self._info == other._info
|
|
||||||
|
|
||||||
def __ne__(self, other):
|
|
||||||
return not self.__eq__(other)
|
|
||||||
|
|
||||||
def is_loaded(self):
|
|
||||||
return self._loaded
|
|
||||||
|
|
||||||
def set_loaded(self, val):
|
|
||||||
self._loaded = val
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
return copy.deepcopy(self._info)
|
|
|
@ -1,221 +0,0 @@
|
||||||
# Copyright 2012 OpenStack LLC.
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
|
|
||||||
|
|
||||||
# TODO(sjmc7): This module is likely redundant because it's replaced
|
|
||||||
# by openstack.common.apiclient; should be removed
|
|
||||||
class BaseException(Exception):
|
|
||||||
"""An error occurred."""
|
|
||||||
def __init__(self, message=None):
|
|
||||||
self.message = message
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.message or self.__class__.__doc__
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidEndpoint(BaseException):
|
|
||||||
"""The provided endpoint is invalid."""
|
|
||||||
|
|
||||||
|
|
||||||
class CommunicationError(BaseException):
|
|
||||||
"""Unable to communicate with server."""
|
|
||||||
|
|
||||||
|
|
||||||
class ClientException(Exception):
|
|
||||||
"""DEPRECATED!"""
|
|
||||||
|
|
||||||
|
|
||||||
class HTTPException(ClientException):
|
|
||||||
"""Base exception for all HTTP-derived exceptions."""
|
|
||||||
code = 'N/A'
|
|
||||||
|
|
||||||
def __init__(self, details=None):
|
|
||||||
self.details = details or self.__class__.__name__
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "%s (HTTP %s)" % (self.details, self.code)
|
|
||||||
|
|
||||||
|
|
||||||
class HTTPMultipleChoices(HTTPException):
|
|
||||||
code = 300
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
self.details = ("Requested version of Application Catalog API is not "
|
|
||||||
"available.")
|
|
||||||
return "%s (HTTP %s) %s" % (self.__class__.__name__, self.code,
|
|
||||||
self.details)
|
|
||||||
|
|
||||||
|
|
||||||
class BadRequest(HTTPException):
|
|
||||||
"""DEPRECATED!"""
|
|
||||||
code = 400
|
|
||||||
|
|
||||||
|
|
||||||
class HTTPBadRequest(BadRequest):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Unauthorized(HTTPException):
|
|
||||||
"""DEPRECATED!"""
|
|
||||||
code = 401
|
|
||||||
|
|
||||||
|
|
||||||
class HTTPUnauthorized(Unauthorized):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Forbidden(HTTPException):
|
|
||||||
"""DEPRECATED!"""
|
|
||||||
code = 403
|
|
||||||
|
|
||||||
|
|
||||||
class HTTPForbidden(Forbidden):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class NotFound(HTTPException):
|
|
||||||
"""DEPRECATED!"""
|
|
||||||
code = 404
|
|
||||||
|
|
||||||
|
|
||||||
class HTTPNotFound(NotFound):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class HTTPMethodNotAllowed(HTTPException):
|
|
||||||
code = 405
|
|
||||||
|
|
||||||
|
|
||||||
class Conflict(HTTPException):
|
|
||||||
"""DEPRECATED!"""
|
|
||||||
code = 409
|
|
||||||
|
|
||||||
|
|
||||||
class HTTPConflict(Conflict):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class OverLimit(HTTPException):
|
|
||||||
"""DEPRECATED!"""
|
|
||||||
code = 413
|
|
||||||
|
|
||||||
|
|
||||||
class HTTPOverLimit(OverLimit):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class HTTPInternalServerError(HTTPException):
|
|
||||||
code = 500
|
|
||||||
|
|
||||||
|
|
||||||
class HTTPNotImplemented(HTTPException):
|
|
||||||
code = 501
|
|
||||||
|
|
||||||
|
|
||||||
class HTTPBadGateway(HTTPException):
|
|
||||||
code = 502
|
|
||||||
|
|
||||||
|
|
||||||
class ServiceUnavailable(HTTPException):
|
|
||||||
"""DEPRECATED!"""
|
|
||||||
code = 503
|
|
||||||
|
|
||||||
|
|
||||||
class HTTPServiceUnavailable(ServiceUnavailable):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# NOTE(bcwaldon): Build a mapping of HTTP codes to corresponding exception
|
|
||||||
# classes
|
|
||||||
_code_map = {}
|
|
||||||
for obj_name in dir(sys.modules[__name__]):
|
|
||||||
if obj_name.startswith('HTTP'):
|
|
||||||
obj = getattr(sys.modules[__name__], obj_name)
|
|
||||||
_code_map[obj.code] = obj
|
|
||||||
|
|
||||||
|
|
||||||
def from_response(response):
|
|
||||||
"""Return an instance of an HTTPException based on httplib response."""
|
|
||||||
cls = _code_map.get(response.status_code, HTTPException)
|
|
||||||
body = response.content
|
|
||||||
header = response.headers['content-type'].lower()
|
|
||||||
|
|
||||||
if (body and header.startswith("application/json")):
|
|
||||||
return cls(details=response.json().get('faultstring'))
|
|
||||||
elif (body and header.startswith("text/html")):
|
|
||||||
# Split the lines, strip whitespace and inline HTML from the response.
|
|
||||||
details = [re.sub(r'<.+?>', '', i.strip())
|
|
||||||
for i in response.text.splitlines()]
|
|
||||||
details = [i for i in details if i]
|
|
||||||
# Remove duplicates from the list.
|
|
||||||
details_seen = set()
|
|
||||||
details_temp = []
|
|
||||||
for i in details:
|
|
||||||
if i not in details_seen:
|
|
||||||
details_temp.append(i)
|
|
||||||
details_seen.add(i)
|
|
||||||
# Return joined string separated by colons.
|
|
||||||
details = ': '.join(details_temp)
|
|
||||||
return cls(details=details)
|
|
||||||
elif body:
|
|
||||||
details = body.replace('\n\n', '\n')
|
|
||||||
return cls(details=details)
|
|
||||||
|
|
||||||
return cls()
|
|
||||||
|
|
||||||
|
|
||||||
def from_code(code):
|
|
||||||
cls = _code_map.get(code, HTTPException)
|
|
||||||
return cls()
|
|
||||||
|
|
||||||
|
|
||||||
class NoTokenLookupException(Exception):
|
|
||||||
"""DEPRECATED!"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class EndpointNotFound(Exception):
|
|
||||||
"""DEPRECATED!"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class SSLConfigurationError(BaseException):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class SSLCertificateError(BaseException):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class NoUniqueMatch(ClientException):
|
|
||||||
"""Multiple entities found instead of one."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class QinlingClientException(Exception):
|
|
||||||
"""Base Exception for Qinling client."""
|
|
||||||
message = "An unknown exception occurred"
|
|
||||||
code = "UNKNOWN_EXCEPTION"
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.message
|
|
||||||
|
|
||||||
def __init__(self, message=message):
|
|
||||||
self.message = message
|
|
||||||
super(QinlingClientException, self).__init__(
|
|
||||||
'%s: %s' % (self.code, self.message))
|
|
|
@ -1,350 +0,0 @@
|
||||||
# Copyright 2012 OpenStack LLC.
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
import copy
|
|
||||||
import hashlib
|
|
||||||
import os
|
|
||||||
import socket
|
|
||||||
|
|
||||||
import keystoneclient.adapter as keystone_adapter
|
|
||||||
from oslo_log import log as logging
|
|
||||||
from oslo_serialization import jsonutils
|
|
||||||
from oslo_utils import encodeutils
|
|
||||||
import requests
|
|
||||||
from six.moves import urllib
|
|
||||||
|
|
||||||
from qinlingclient.common import exceptions as exc
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
USER_AGENT = 'python-qinlingclient'
|
|
||||||
CHUNKSIZE = 1024 * 64 # 64kB
|
|
||||||
|
|
||||||
|
|
||||||
def get_system_ca_file():
|
|
||||||
"""Return path to system default CA file."""
|
|
||||||
# Standard CA file locations for Debian/Ubuntu, RedHat/Fedora,
|
|
||||||
# Suse, FreeBSD/OpenBSD, MacOSX, and the bundled ca
|
|
||||||
ca_path = ['/etc/ssl/certs/ca-certificates.crt',
|
|
||||||
'/etc/pki/tls/certs/ca-bundle.crt',
|
|
||||||
'/etc/ssl/ca-bundle.pem',
|
|
||||||
'/etc/ssl/cert.pem',
|
|
||||||
'/System/Library/OpenSSL/certs/cacert.pem',
|
|
||||||
requests.certs.where()]
|
|
||||||
for ca in ca_path:
|
|
||||||
LOG.debug("Looking for ca file %s", ca)
|
|
||||||
if os.path.exists(ca):
|
|
||||||
LOG.debug("Using ca file %s", ca)
|
|
||||||
return ca
|
|
||||||
LOG.warning("System ca file could not be found.")
|
|
||||||
|
|
||||||
|
|
||||||
class HTTPClient(object):
|
|
||||||
|
|
||||||
def __init__(self, endpoint, **kwargs):
|
|
||||||
self.endpoint = endpoint
|
|
||||||
self.auth_url = kwargs.get('auth_url')
|
|
||||||
self.auth_token = kwargs.get('token')
|
|
||||||
self.username = kwargs.get('username')
|
|
||||||
self.password = kwargs.get('password')
|
|
||||||
self.region_name = kwargs.get('region_name')
|
|
||||||
self.include_pass = kwargs.get('include_pass')
|
|
||||||
self.endpoint_url = endpoint
|
|
||||||
|
|
||||||
self.cert_file = kwargs.get('cert_file')
|
|
||||||
self.key_file = kwargs.get('key_file')
|
|
||||||
self.timeout = kwargs.get('timeout')
|
|
||||||
|
|
||||||
self.ssl_connection_params = {
|
|
||||||
'cacert': kwargs.get('cacert'),
|
|
||||||
'cert_file': kwargs.get('cert_file'),
|
|
||||||
'key_file': kwargs.get('key_file'),
|
|
||||||
'insecure': kwargs.get('insecure'),
|
|
||||||
}
|
|
||||||
|
|
||||||
self.verify_cert = None
|
|
||||||
if urllib.parse.urlparse(endpoint).scheme == "https":
|
|
||||||
if kwargs.get('insecure'):
|
|
||||||
self.verify_cert = False
|
|
||||||
else:
|
|
||||||
self.verify_cert = kwargs.get('cacert', get_system_ca_file())
|
|
||||||
|
|
||||||
def _safe_header(self, name, value):
|
|
||||||
if name in ['X-Auth-Token', 'X-Subject-Token']:
|
|
||||||
# because in python3 byte string handling is ... ug
|
|
||||||
v = value.encode('utf-8')
|
|
||||||
h = hashlib.sha1(v)
|
|
||||||
d = h.hexdigest()
|
|
||||||
return encodeutils.safe_decode(name), "{SHA1}%s" % d
|
|
||||||
else:
|
|
||||||
return (encodeutils.safe_decode(name),
|
|
||||||
encodeutils.safe_decode(value))
|
|
||||||
|
|
||||||
def log_curl_request(self, url, method, kwargs):
|
|
||||||
curl = ['curl -i -X %s' % method]
|
|
||||||
|
|
||||||
for (key, value) in kwargs['headers'].items():
|
|
||||||
header = '-H \'%s: %s\'' % self._safe_header(key, value)
|
|
||||||
curl.append(header)
|
|
||||||
|
|
||||||
conn_params_fmt = [
|
|
||||||
('key_file', '--key %s'),
|
|
||||||
('cert_file', '--cert %s'),
|
|
||||||
('cacert', '--cacert %s'),
|
|
||||||
]
|
|
||||||
for (key, fmt) in conn_params_fmt:
|
|
||||||
value = self.ssl_connection_params.get(key)
|
|
||||||
if value:
|
|
||||||
curl.append(fmt % value)
|
|
||||||
|
|
||||||
if self.ssl_connection_params.get('insecure'):
|
|
||||||
curl.append('-k')
|
|
||||||
|
|
||||||
if 'data' in kwargs:
|
|
||||||
curl.append('-d \'%s\'' % kwargs['data'])
|
|
||||||
|
|
||||||
curl.append('%s%s' % (self.endpoint, url))
|
|
||||||
LOG.debug(' '.join(curl))
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def log_http_response(resp):
|
|
||||||
status = (resp.raw.version / 10.0, resp.status_code, resp.reason)
|
|
||||||
dump = ['\nHTTP/%.1f %s %s' % status]
|
|
||||||
dump.extend(['%s: %s' % (k, v) for k, v in resp.headers.items()])
|
|
||||||
dump.append('')
|
|
||||||
if resp.content:
|
|
||||||
content = resp.content
|
|
||||||
if isinstance(content, bytes):
|
|
||||||
try:
|
|
||||||
content = encodeutils.safe_decode(resp.content)
|
|
||||||
except UnicodeDecodeError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
dump.extend([content, ''])
|
|
||||||
LOG.debug('\n'.join(dump))
|
|
||||||
|
|
||||||
def request(self, url, method, log=True, **kwargs):
|
|
||||||
"""Send an http request with the specified characteristics.
|
|
||||||
|
|
||||||
Wrapper around requests.request to handle tasks such
|
|
||||||
as setting headers and error handling.
|
|
||||||
"""
|
|
||||||
# Copy the kwargs so we can reuse the original in case of redirects
|
|
||||||
kwargs['headers'] = copy.deepcopy(kwargs.get('headers', {}))
|
|
||||||
kwargs['headers'].setdefault('User-Agent', USER_AGENT)
|
|
||||||
if self.auth_token:
|
|
||||||
kwargs['headers'].setdefault('X-Auth-Token', self.auth_token)
|
|
||||||
else:
|
|
||||||
kwargs['headers'].update(self.credentials_headers())
|
|
||||||
if self.auth_url:
|
|
||||||
kwargs['headers'].setdefault('X-Auth-Url', self.auth_url)
|
|
||||||
if self.region_name:
|
|
||||||
kwargs['headers'].setdefault('X-Region-Name', self.region_name)
|
|
||||||
|
|
||||||
self.log_curl_request(url, method, kwargs)
|
|
||||||
|
|
||||||
if self.cert_file and self.key_file:
|
|
||||||
kwargs['cert'] = (self.cert_file, self.key_file)
|
|
||||||
|
|
||||||
if self.verify_cert is not None:
|
|
||||||
kwargs['verify'] = self.verify_cert
|
|
||||||
|
|
||||||
if self.timeout is not None:
|
|
||||||
kwargs['timeout'] = float(self.timeout)
|
|
||||||
|
|
||||||
# Allow the option not to follow redirects
|
|
||||||
follow_redirects = kwargs.pop('follow_redirects', True)
|
|
||||||
|
|
||||||
# Since requests does not follow the RFC when doing redirection to sent
|
|
||||||
# back the same method on a redirect we are simply bypassing it. For
|
|
||||||
# example if we do a DELETE/POST/PUT on a URL and we get a 302 RFC says
|
|
||||||
# that we should follow that URL with the same method as before,
|
|
||||||
# requests doesn't follow that and send a GET instead for the method.
|
|
||||||
# Hopefully this could be fixed as they say in a comment in a future
|
|
||||||
# point version i.e.: 3.x
|
|
||||||
# See issue: https://github.com/kennethreitz/requests/issues/1704
|
|
||||||
allow_redirects = False
|
|
||||||
|
|
||||||
try:
|
|
||||||
resp = requests.request(
|
|
||||||
method,
|
|
||||||
self.endpoint_url + url,
|
|
||||||
allow_redirects=allow_redirects,
|
|
||||||
**kwargs)
|
|
||||||
except socket.gaierror as e:
|
|
||||||
message = ("Error finding address for %(url)s: %(e)s" %
|
|
||||||
{'url': self.endpoint_url + url, 'e': e})
|
|
||||||
raise exc.InvalidEndpoint(message=message)
|
|
||||||
except (socket.error,
|
|
||||||
socket.timeout,
|
|
||||||
requests.exceptions.ConnectionError) as e:
|
|
||||||
endpoint = self.endpoint
|
|
||||||
message = ("Error communicating with %(endpoint)s %(e)s" %
|
|
||||||
{'endpoint': endpoint, 'e': e})
|
|
||||||
raise exc.CommunicationError(message=message)
|
|
||||||
|
|
||||||
if log:
|
|
||||||
self.log_http_response(resp)
|
|
||||||
|
|
||||||
if 'X-Auth-Key' not in kwargs['headers'] and \
|
|
||||||
(resp.status_code == 401 or
|
|
||||||
(resp.status_code == 500 and
|
|
||||||
"(HTTP 401)" in resp.content)):
|
|
||||||
raise exc.HTTPUnauthorized("Authentication failed. Please try"
|
|
||||||
" again.\n%s"
|
|
||||||
% resp.content)
|
|
||||||
elif 400 <= resp.status_code < 600:
|
|
||||||
raise exc.from_response(resp)
|
|
||||||
elif resp.status_code in (301, 302, 305):
|
|
||||||
# Redirected. Reissue the request to the new location,
|
|
||||||
# unless caller specified follow_redirects=False
|
|
||||||
if follow_redirects:
|
|
||||||
location = resp.headers.get('location')
|
|
||||||
path = self.strip_endpoint(location)
|
|
||||||
resp = self.request(path, method, **kwargs)
|
|
||||||
elif resp.status_code == 300:
|
|
||||||
raise exc.from_response(resp)
|
|
||||||
|
|
||||||
return resp
|
|
||||||
|
|
||||||
def strip_endpoint(self, location):
|
|
||||||
if location is None:
|
|
||||||
message = "Location not returned with 302"
|
|
||||||
raise exc.InvalidEndpoint(message=message)
|
|
||||||
elif location.startswith(self.endpoint):
|
|
||||||
return location[len(self.endpoint):]
|
|
||||||
else:
|
|
||||||
message = "Prohibited endpoint redirect %s" % location
|
|
||||||
raise exc.InvalidEndpoint(message=message)
|
|
||||||
|
|
||||||
def credentials_headers(self):
|
|
||||||
creds = {}
|
|
||||||
if self.username:
|
|
||||||
creds['X-Auth-User'] = self.username
|
|
||||||
if self.password:
|
|
||||||
creds['X-Auth-Key'] = self.password
|
|
||||||
return creds
|
|
||||||
|
|
||||||
def json_request(self, url, method, content_type='application/json',
|
|
||||||
**kwargs):
|
|
||||||
|
|
||||||
kwargs.setdefault('headers', {})
|
|
||||||
kwargs['headers'].setdefault('Content-Type', content_type)
|
|
||||||
|
|
||||||
if 'data' in kwargs:
|
|
||||||
kwargs['data'] = jsonutils.dumps(kwargs['data'])
|
|
||||||
|
|
||||||
resp = self.request(url, method, **kwargs)
|
|
||||||
body = resp.content
|
|
||||||
|
|
||||||
if body and 'application/json' in resp.headers['content-type']:
|
|
||||||
try:
|
|
||||||
body = resp.json()
|
|
||||||
except ValueError:
|
|
||||||
LOG.error('Could not decode response body as JSON')
|
|
||||||
else:
|
|
||||||
body = None
|
|
||||||
|
|
||||||
return resp, body
|
|
||||||
|
|
||||||
def head(self, url, **kwargs):
|
|
||||||
return self.json_request(url, "HEAD", **kwargs)
|
|
||||||
|
|
||||||
def get(self, url, **kwargs):
|
|
||||||
return self.json_request(url, "GET", **kwargs)
|
|
||||||
|
|
||||||
def post(self, url, **kwargs):
|
|
||||||
return self.json_request(url, "POST", **kwargs)
|
|
||||||
|
|
||||||
def put(self, url, **kwargs):
|
|
||||||
return self.json_request(url, "PUT", **kwargs)
|
|
||||||
|
|
||||||
def delete(self, url, **kwargs):
|
|
||||||
return self.request(url, "DELETE", **kwargs)
|
|
||||||
|
|
||||||
def patch(self, url, **kwargs):
|
|
||||||
return self.json_request(url, "PATCH", **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class SessionClient(keystone_adapter.Adapter):
|
|
||||||
"""Qinling specific keystoneclient Adapter.
|
|
||||||
|
|
||||||
Qinling can't use keystoneclient LegacyJsonAdapter, because qinling has the
|
|
||||||
check for right content-type for "update" operation which is
|
|
||||||
'application/qinling-packages-json-patch'. So, we need to create our own
|
|
||||||
adapter.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def request(self, url, method, **kwargs):
|
|
||||||
raise_exc = kwargs.pop('raise_exc', True)
|
|
||||||
resp = super(SessionClient, self).request(
|
|
||||||
url,
|
|
||||||
method,
|
|
||||||
raise_exc=False,
|
|
||||||
**kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
if raise_exc and resp.status_code >= 400:
|
|
||||||
LOG.trace("Error communicating with {url}: {exc}"
|
|
||||||
.format(url=url, exc=exc.from_response(resp)))
|
|
||||||
raise exc.from_response(resp)
|
|
||||||
|
|
||||||
return resp
|
|
||||||
|
|
||||||
def json_request(self, url, method, **kwargs):
|
|
||||||
headers = kwargs.setdefault('headers', {})
|
|
||||||
headers['Content-Type'] = kwargs.pop('content_type',
|
|
||||||
'application/json')
|
|
||||||
|
|
||||||
if 'data' in kwargs:
|
|
||||||
kwargs['data'] = jsonutils.dumps(kwargs['data'])
|
|
||||||
# NOTE(starodubcevna): We need to prove that json field is empty,
|
|
||||||
# or it will be modified by keystone adapter.
|
|
||||||
kwargs['json'] = None
|
|
||||||
|
|
||||||
resp = self.request(url, method, **kwargs)
|
|
||||||
body = resp.text
|
|
||||||
if body:
|
|
||||||
try:
|
|
||||||
body = jsonutils.loads(body)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return resp, body
|
|
||||||
|
|
||||||
|
|
||||||
def _construct_http_client(*args, **kwargs):
|
|
||||||
session = kwargs.pop('session', None)
|
|
||||||
auth = kwargs.pop('auth', None)
|
|
||||||
endpoint = next(iter(args), None)
|
|
||||||
|
|
||||||
if session:
|
|
||||||
service_type = kwargs.pop('service_type', None)
|
|
||||||
endpoint_type = kwargs.pop('endpoint_type', None)
|
|
||||||
region_name = kwargs.pop('region_name', None)
|
|
||||||
service_name = kwargs.pop('service_name', None)
|
|
||||||
parameters = {
|
|
||||||
'endpoint_override': endpoint,
|
|
||||||
'session': session,
|
|
||||||
'auth': auth,
|
|
||||||
'interface': endpoint_type,
|
|
||||||
'service_type': service_type,
|
|
||||||
'region_name': region_name,
|
|
||||||
'service_name': service_name,
|
|
||||||
'user_agent': 'python-qinlingclient',
|
|
||||||
}
|
|
||||||
parameters.update(kwargs)
|
|
||||||
return SessionClient(**parameters)
|
|
||||||
else:
|
|
||||||
return HTTPClient(*args, **kwargs)
|
|
|
@ -1,24 +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/index.html
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
import oslo_i18n
|
|
||||||
|
|
||||||
_translators = oslo_i18n.TranslatorFactory(domain='qinlingclient')
|
|
||||||
|
|
||||||
# The primary translation function using the well-known name "_"
|
|
||||||
_ = _translators.primary
|
|
|
@ -1,78 +0,0 @@
|
||||||
# Copyright 2017 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.
|
|
||||||
|
|
||||||
"""OpenStackClient plugin for Function service."""
|
|
||||||
|
|
||||||
from osc_lib import utils
|
|
||||||
from oslo_log import log as logging
|
|
||||||
|
|
||||||
from qinlingclient.i18n import _
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
DEFAULT_FUNCTION_ENGINE_API_VERSION = "1"
|
|
||||||
API_NAME = "function_engine"
|
|
||||||
API_VERSION_OPTION = "os_function_engine_api_version"
|
|
||||||
API_VERSIONS = {
|
|
||||||
'1': 'qinlingclient.v1.client.Client',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def make_client(instance):
|
|
||||||
"""Returns an qinling service client"""
|
|
||||||
function_engine_client = utils.get_client_class(
|
|
||||||
API_NAME,
|
|
||||||
instance._api_version[API_NAME],
|
|
||||||
API_VERSIONS)
|
|
||||||
|
|
||||||
LOG.debug("Instantiating function-engine client: {0}".format(
|
|
||||||
function_engine_client))
|
|
||||||
|
|
||||||
kwargs = {
|
|
||||||
'session': instance.session,
|
|
||||||
'service_type': 'function-engine',
|
|
||||||
'region_name': instance._region_name
|
|
||||||
}
|
|
||||||
|
|
||||||
qinling_endpoint = instance.get_configuration().get('qinling_url')
|
|
||||||
if not qinling_endpoint:
|
|
||||||
qinling_endpoint = instance.get_endpoint_for_service_type(
|
|
||||||
'function-engine',
|
|
||||||
region_name=instance._region_name,
|
|
||||||
interface=instance._interface
|
|
||||||
)
|
|
||||||
|
|
||||||
client = function_engine_client(qinling_endpoint, **kwargs)
|
|
||||||
return client
|
|
||||||
|
|
||||||
|
|
||||||
def build_option_parser(parser):
|
|
||||||
"""Hook to add global options"""
|
|
||||||
parser.add_argument(
|
|
||||||
'--os-function-engine-api-version',
|
|
||||||
metavar='<function-engine-api-version>',
|
|
||||||
default=utils.env(
|
|
||||||
'OS_FUNCTION_ENGINE_API_VERSION',
|
|
||||||
default=DEFAULT_FUNCTION_ENGINE_API_VERSION
|
|
||||||
),
|
|
||||||
help=_(
|
|
||||||
"Function engine API version, default={0}"
|
|
||||||
"(Env:OS_FUNCTION_ENGINE_API_VERSION)").format(
|
|
||||||
DEFAULT_FUNCTION_ENGINE_API_VERSION
|
|
||||||
)
|
|
||||||
)
|
|
||||||
parser.add_argument('--qinling-url',
|
|
||||||
default=utils.env('QINLING_URL'),
|
|
||||||
help=_('Defaults to env[QINLING_URL].'))
|
|
||||||
return parser
|
|
|
@ -1,236 +0,0 @@
|
||||||
# Copyright 2017 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 abc
|
|
||||||
import textwrap
|
|
||||||
|
|
||||||
from osc_lib.command import command
|
|
||||||
from osc_lib import utils
|
|
||||||
|
|
||||||
from qinlingclient.common import exceptions
|
|
||||||
from qinlingclient.i18n import _
|
|
||||||
|
|
||||||
RUNTIME_COLUMNS = (
|
|
||||||
'id',
|
|
||||||
'name',
|
|
||||||
'image',
|
|
||||||
'status',
|
|
||||||
'description',
|
|
||||||
'is_public',
|
|
||||||
'trusted',
|
|
||||||
'project_id',
|
|
||||||
'created_at',
|
|
||||||
'updated_at'
|
|
||||||
)
|
|
||||||
RUNTIME_POOL_COLUMNS = (
|
|
||||||
'name',
|
|
||||||
'capacity'
|
|
||||||
)
|
|
||||||
FUNCTION_COLUMNS = (
|
|
||||||
'id',
|
|
||||||
'name',
|
|
||||||
'description',
|
|
||||||
'count',
|
|
||||||
'code',
|
|
||||||
'runtime_id',
|
|
||||||
'entry',
|
|
||||||
'project_id',
|
|
||||||
'created_at',
|
|
||||||
'updated_at',
|
|
||||||
'cpu',
|
|
||||||
'memory_size',
|
|
||||||
'timeout'
|
|
||||||
)
|
|
||||||
FILTERED_FUNCTION_COLUMNS = (
|
|
||||||
"all_projects",
|
|
||||||
"project_id"
|
|
||||||
)
|
|
||||||
EXECUTION_COLUMNS = (
|
|
||||||
'id',
|
|
||||||
'function_alias',
|
|
||||||
'function_id',
|
|
||||||
'function_version',
|
|
||||||
'description',
|
|
||||||
'input',
|
|
||||||
'result',
|
|
||||||
'status',
|
|
||||||
'sync',
|
|
||||||
'project_id',
|
|
||||||
'created_at',
|
|
||||||
'updated_at'
|
|
||||||
)
|
|
||||||
FILTERED_EXECUTION_COLUMNS = (
|
|
||||||
'function_id',
|
|
||||||
'project_id',
|
|
||||||
'all_projects',
|
|
||||||
'status',
|
|
||||||
'description',
|
|
||||||
)
|
|
||||||
JOB_COLUMNS = (
|
|
||||||
'id',
|
|
||||||
'name',
|
|
||||||
'count',
|
|
||||||
'status',
|
|
||||||
'function_alias',
|
|
||||||
'function_id',
|
|
||||||
'function_version',
|
|
||||||
'function_input',
|
|
||||||
'pattern',
|
|
||||||
'first_execution_time',
|
|
||||||
'next_execution_time',
|
|
||||||
'project_id',
|
|
||||||
'created_at',
|
|
||||||
'updated_at'
|
|
||||||
)
|
|
||||||
FILTERED_JOB_COLUMNS = (
|
|
||||||
"all_projects",
|
|
||||||
"project_id"
|
|
||||||
)
|
|
||||||
WORKER_COLUMNS = (
|
|
||||||
'function_id',
|
|
||||||
'worker_name'
|
|
||||||
)
|
|
||||||
WEBHOOK_COLUMNS = (
|
|
||||||
'id',
|
|
||||||
'function_alias',
|
|
||||||
'function_id',
|
|
||||||
'function_version',
|
|
||||||
'description',
|
|
||||||
'project_id',
|
|
||||||
'created_at',
|
|
||||||
'updated_at',
|
|
||||||
'webhook_url'
|
|
||||||
)
|
|
||||||
FILTERED_WEBHOOK_COLUMNS = (
|
|
||||||
"all_projects",
|
|
||||||
"project_id"
|
|
||||||
)
|
|
||||||
FUNCTION_VERSION_COLUMNS = (
|
|
||||||
'id',
|
|
||||||
'function_id',
|
|
||||||
'description',
|
|
||||||
'version_number',
|
|
||||||
'count',
|
|
||||||
'project_id',
|
|
||||||
'created_at',
|
|
||||||
'updated_at'
|
|
||||||
)
|
|
||||||
FUNCTION_ALIAS_COLUMNS = (
|
|
||||||
'name',
|
|
||||||
'function_id',
|
|
||||||
'description',
|
|
||||||
'function_version',
|
|
||||||
'project_id',
|
|
||||||
'created_at',
|
|
||||||
'updated_at'
|
|
||||||
)
|
|
||||||
FILTERED_FUNCTION_ALIAS_COLUMNS = (
|
|
||||||
"all_projects",
|
|
||||||
"project_id"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class QinlingLister(command.Lister, metaclass=abc.ABCMeta):
|
|
||||||
columns = ()
|
|
||||||
filtered_columns = ()
|
|
||||||
|
|
||||||
def get_parser(self, prog_name):
|
|
||||||
parser = super(QinlingLister, self).get_parser(prog_name)
|
|
||||||
parser.add_argument(
|
|
||||||
'--filter',
|
|
||||||
dest='filters',
|
|
||||||
action='append',
|
|
||||||
help=_(
|
|
||||||
'Filters for resource query that can be repeated. Supported '
|
|
||||||
'operands: eq, neq, in, nin, gt, gte, lt, lte, has. '
|
|
||||||
'E.g. --filter key="neq:123". The available keys: {0}'
|
|
||||||
).format(self.filtered_columns)
|
|
||||||
)
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
@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 _headers(self):
|
|
||||||
return [c.capitalize() for c in self.columns]
|
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
|
||||||
self._validate_parsed_args(parsed_args)
|
|
||||||
|
|
||||||
ret = self._get_resources(parsed_args)
|
|
||||||
if not isinstance(ret, list):
|
|
||||||
ret = [ret]
|
|
||||||
|
|
||||||
return (
|
|
||||||
self._headers(),
|
|
||||||
list(utils.get_item_properties(
|
|
||||||
s,
|
|
||||||
self.columns,
|
|
||||||
) for s in ret)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class QinlingDeleter(command.Command):
|
|
||||||
def delete_resources(self, ids):
|
|
||||||
"""Delete one or more resources."""
|
|
||||||
failure_flag = False
|
|
||||||
success_msg = "Request to delete %s %s has been accepted."
|
|
||||||
error_msg = "Unable to delete the specified %s(s)."
|
|
||||||
|
|
||||||
for id in ids:
|
|
||||||
try:
|
|
||||||
self.delete(id)
|
|
||||||
print(success_msg % (self.resource, id))
|
|
||||||
except Exception as e:
|
|
||||||
failure_flag = True
|
|
||||||
print(e)
|
|
||||||
|
|
||||||
if failure_flag:
|
|
||||||
raise exceptions.QinlingClientException(error_msg % self.resource)
|
|
||||||
|
|
||||||
|
|
||||||
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 hasattr(parsed_args, 'filters') and 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
|
|
|
@ -1,523 +0,0 @@
|
||||||
# Copyright 2017 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 os
|
|
||||||
import shutil
|
|
||||||
import tempfile
|
|
||||||
import zipfile
|
|
||||||
|
|
||||||
from osc_lib.command import command
|
|
||||||
from osc_lib import utils
|
|
||||||
from oslo_utils import uuidutils
|
|
||||||
|
|
||||||
from qinlingclient.common import exceptions
|
|
||||||
from qinlingclient.osc.v1 import base
|
|
||||||
from qinlingclient import utils as q_utils
|
|
||||||
|
|
||||||
MAX_ZIP_SIZE = 50 * 1024 * 1024
|
|
||||||
|
|
||||||
|
|
||||||
def _get_package_file(package_path=None, file_path=None):
|
|
||||||
if package_path:
|
|
||||||
if not zipfile.is_zipfile(package_path):
|
|
||||||
raise exceptions.QinlingClientException(
|
|
||||||
'Package %s is not a valid ZIP file.' % package_path
|
|
||||||
)
|
|
||||||
|
|
||||||
if os.path.getsize(package_path) > MAX_ZIP_SIZE:
|
|
||||||
raise exceptions.QinlingClientException(
|
|
||||||
'Package file size must be no more than %sM.' %
|
|
||||||
(MAX_ZIP_SIZE / 1024 / 1024)
|
|
||||||
)
|
|
||||||
|
|
||||||
return package_path
|
|
||||||
|
|
||||||
elif file_path:
|
|
||||||
if not os.path.isfile(file_path):
|
|
||||||
raise exceptions.QinlingClientException(
|
|
||||||
'File %s not exist.' % file_path
|
|
||||||
)
|
|
||||||
|
|
||||||
base_name, extension = os.path.splitext(file_path)
|
|
||||||
base_name = os.path.basename(base_name)
|
|
||||||
zip_file = os.path.join(
|
|
||||||
tempfile.gettempdir(),
|
|
||||||
'%s.zip' % base_name
|
|
||||||
)
|
|
||||||
|
|
||||||
zf = zipfile.ZipFile(zip_file, mode='w')
|
|
||||||
try:
|
|
||||||
# Use default compression mode, may change in future.
|
|
||||||
zf.write(
|
|
||||||
file_path,
|
|
||||||
'%s%s' % (base_name, extension),
|
|
||||||
compress_type=zipfile.ZIP_STORED
|
|
||||||
)
|
|
||||||
finally:
|
|
||||||
zf.close()
|
|
||||||
|
|
||||||
if os.path.getsize(zip_file) > MAX_ZIP_SIZE:
|
|
||||||
raise exceptions.QinlingClientException(
|
|
||||||
'Package file size must be no more than %sM.' %
|
|
||||||
(MAX_ZIP_SIZE / 1024 / 1024)
|
|
||||||
)
|
|
||||||
|
|
||||||
return zip_file
|
|
||||||
|
|
||||||
|
|
||||||
def worker_count(value):
|
|
||||||
try:
|
|
||||||
value = int(value)
|
|
||||||
if value <= 0:
|
|
||||||
raise ValueError
|
|
||||||
except ValueError:
|
|
||||||
raise exceptions.QinlingClientException(
|
|
||||||
'Worker count must be a positive integer.'
|
|
||||||
)
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
class List(base.QinlingLister):
|
|
||||||
columns = base.FUNCTION_COLUMNS
|
|
||||||
filtered_columns = base.FILTERED_FUNCTION_COLUMNS
|
|
||||||
|
|
||||||
def _get_resources(self, parsed_args):
|
|
||||||
client = self.app.client_manager.function_engine
|
|
||||||
|
|
||||||
return client.functions.list(**base.get_filters(parsed_args))
|
|
||||||
|
|
||||||
|
|
||||||
class Create(command.ShowOne):
|
|
||||||
columns = base.FUNCTION_COLUMNS
|
|
||||||
|
|
||||||
def get_parser(self, prog_name):
|
|
||||||
parser = super(Create, self).get_parser(prog_name)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"--runtime",
|
|
||||||
help="Runtime ID. Runtime is needed for function of package type "
|
|
||||||
"and swift type, but not for the image type function.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--name",
|
|
||||||
help="Function name.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--entry",
|
|
||||||
help="Function entry in the format of <module_name>.<method_name>"
|
|
||||||
)
|
|
||||||
protected_group = parser.add_mutually_exclusive_group(required=False)
|
|
||||||
protected_group.add_argument(
|
|
||||||
"--file",
|
|
||||||
metavar="CODE_FILE_PATH",
|
|
||||||
help="Code file path."
|
|
||||||
)
|
|
||||||
protected_group.add_argument(
|
|
||||||
"--package",
|
|
||||||
metavar="CODE_PACKAGE_PATH",
|
|
||||||
help="Code package zip file path."
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--container",
|
|
||||||
help="Container name in Swift.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--object",
|
|
||||||
help="Object name in Swift.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--image",
|
|
||||||
help="Image name in docker hub.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--cpu",
|
|
||||||
type=q_utils.check_positive,
|
|
||||||
help="Limit of cpu resource(unit: millicpu).",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--memory-size",
|
|
||||||
type=q_utils.check_positive,
|
|
||||||
help="Limit of memory resource(unit: bytes).",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--timeout",
|
|
||||||
type=q_utils.check_positive,
|
|
||||||
default=5,
|
|
||||||
help="The function execution time at which Qinling should "
|
|
||||||
"terminate the function. The default is 5 seconds",
|
|
||||||
)
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
|
||||||
client = self.app.client_manager.function_engine
|
|
||||||
code_type = None
|
|
||||||
|
|
||||||
if (parsed_args.file or parsed_args.package):
|
|
||||||
code_type = 'package'
|
|
||||||
elif (parsed_args.container or parsed_args.object):
|
|
||||||
code_type = 'swift'
|
|
||||||
elif parsed_args.image:
|
|
||||||
code_type = 'image'
|
|
||||||
else:
|
|
||||||
raise exceptions.QinlingClientException(
|
|
||||||
'Cannot create function with the parameters given.\nMust '
|
|
||||||
'provide required parameters for different type of '
|
|
||||||
'functions:\n'
|
|
||||||
' - for package type function, either --file or --package '
|
|
||||||
'is required,\n'
|
|
||||||
' - for swift type function, both --container and --object '
|
|
||||||
'are required,\n'
|
|
||||||
' - for image type function, --image is required.'
|
|
||||||
)
|
|
||||||
|
|
||||||
runtime = parsed_args.runtime
|
|
||||||
if runtime and not uuidutils.is_uuid_like(runtime):
|
|
||||||
# Try to find the runtime id with name
|
|
||||||
runtime = q_utils.find_resource_id_by_name(
|
|
||||||
client.runtimes, runtime)
|
|
||||||
|
|
||||||
if code_type == 'package':
|
|
||||||
if not runtime:
|
|
||||||
raise exceptions.QinlingClientException(
|
|
||||||
'Runtime needs to be specified for package type function.'
|
|
||||||
)
|
|
||||||
|
|
||||||
zip_file = _get_package_file(parsed_args.package, parsed_args.file)
|
|
||||||
md5sum = q_utils.md5(file=zip_file)
|
|
||||||
code = {"source": "package", "md5sum": md5sum}
|
|
||||||
|
|
||||||
with open(zip_file, 'rb') as package:
|
|
||||||
function = client.functions.create(
|
|
||||||
name=parsed_args.name,
|
|
||||||
runtime=runtime,
|
|
||||||
code=code,
|
|
||||||
package=package,
|
|
||||||
entry=parsed_args.entry,
|
|
||||||
cpu=parsed_args.cpu,
|
|
||||||
memory_size=parsed_args.memory_size,
|
|
||||||
timeout=parsed_args.timeout
|
|
||||||
)
|
|
||||||
|
|
||||||
# Delete zip file the client created
|
|
||||||
if parsed_args.file and not parsed_args.package:
|
|
||||||
os.remove(zip_file)
|
|
||||||
|
|
||||||
elif code_type == 'swift':
|
|
||||||
if not (parsed_args.container and parsed_args.object):
|
|
||||||
raise exceptions.QinlingClientException(
|
|
||||||
'Container name and object name need to be specified.'
|
|
||||||
)
|
|
||||||
if not runtime:
|
|
||||||
raise exceptions.QinlingClientException(
|
|
||||||
'Runtime needs to be specified for swift type function.'
|
|
||||||
)
|
|
||||||
|
|
||||||
code = {
|
|
||||||
"source": "swift",
|
|
||||||
"swift": {
|
|
||||||
"container": parsed_args.container,
|
|
||||||
"object": parsed_args.object
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function = client.functions.create(
|
|
||||||
name=parsed_args.name,
|
|
||||||
runtime=runtime,
|
|
||||||
code=code,
|
|
||||||
entry=parsed_args.entry,
|
|
||||||
cpu=parsed_args.cpu,
|
|
||||||
memory_size=parsed_args.memory_size,
|
|
||||||
timeout=parsed_args.timeout
|
|
||||||
)
|
|
||||||
|
|
||||||
elif code_type == 'image':
|
|
||||||
code = {
|
|
||||||
"source": "image",
|
|
||||||
"image": parsed_args.image
|
|
||||||
}
|
|
||||||
|
|
||||||
function = client.functions.create(
|
|
||||||
name=parsed_args.name,
|
|
||||||
code=code,
|
|
||||||
entry=parsed_args.entry,
|
|
||||||
cpu=parsed_args.cpu,
|
|
||||||
memory_size=parsed_args.memory_size,
|
|
||||||
timeout=parsed_args.timeout
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.columns, utils.get_item_properties(function, self.columns)
|
|
||||||
|
|
||||||
|
|
||||||
class Delete(base.QinlingDeleter):
|
|
||||||
def get_parser(self, prog_name):
|
|
||||||
parser = super(Delete, self).get_parser(prog_name)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
'function',
|
|
||||||
nargs='+',
|
|
||||||
metavar='FUNCTION',
|
|
||||||
help='Id or name of function(s).'
|
|
||||||
)
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
|
||||||
client = self.app.client_manager.function_engine
|
|
||||||
self.delete = client.functions.delete
|
|
||||||
self.resource = 'function'
|
|
||||||
|
|
||||||
ids = []
|
|
||||||
for function_id in parsed_args.function:
|
|
||||||
if not uuidutils.is_uuid_like(function_id):
|
|
||||||
function_id = q_utils.find_resource_id_by_name(
|
|
||||||
client.functions, function_id)
|
|
||||||
ids.append(function_id)
|
|
||||||
|
|
||||||
self.delete_resources(ids)
|
|
||||||
|
|
||||||
|
|
||||||
class Show(command.ShowOne):
|
|
||||||
columns = base.FUNCTION_COLUMNS
|
|
||||||
|
|
||||||
def get_parser(self, prog_name):
|
|
||||||
parser = super(Show, self).get_parser(prog_name)
|
|
||||||
parser.add_argument('function', help='Function ID or name.')
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
|
||||||
client = self.app.client_manager.function_engine
|
|
||||||
|
|
||||||
function_id = parsed_args.function
|
|
||||||
if not uuidutils.is_uuid_like(function_id):
|
|
||||||
function_id = q_utils.find_resource_id_by_name(
|
|
||||||
client.functions, function_id)
|
|
||||||
|
|
||||||
function = client.functions.get(function_id)
|
|
||||||
return self.columns, utils.get_item_properties(function,
|
|
||||||
self.columns)
|
|
||||||
|
|
||||||
|
|
||||||
class Update(command.ShowOne):
|
|
||||||
columns = base.FUNCTION_COLUMNS
|
|
||||||
|
|
||||||
def get_parser(self, prog_name):
|
|
||||||
parser = super(Update, self).get_parser(prog_name)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
'function',
|
|
||||||
help='Function ID or name.'
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--name",
|
|
||||||
help="Function name."
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--description",
|
|
||||||
help="Function description."
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--entry",
|
|
||||||
help="Function entry in the format of <module_name>.<method_name>"
|
|
||||||
)
|
|
||||||
|
|
||||||
package_group = parser.add_mutually_exclusive_group()
|
|
||||||
package_group.add_argument(
|
|
||||||
"--file",
|
|
||||||
metavar="CODE_FILE_PATH",
|
|
||||||
help="Code file path."
|
|
||||||
)
|
|
||||||
package_group.add_argument(
|
|
||||||
"--package",
|
|
||||||
metavar="CODE_PACKAGE_PATH",
|
|
||||||
help="Code package zip file path."
|
|
||||||
)
|
|
||||||
|
|
||||||
# For swift functions
|
|
||||||
parser.add_argument(
|
|
||||||
"--container",
|
|
||||||
help="Container name in Swift.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--object",
|
|
||||||
help="Object name in Swift.",
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"--cpu",
|
|
||||||
type=q_utils.check_positive,
|
|
||||||
help="Limit of cpu resource(unit: millicpu).",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--memory-size",
|
|
||||||
type=q_utils.check_positive,
|
|
||||||
help="Limit of memory resource(unit: bytes).",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--timeout",
|
|
||||||
type=q_utils.check_positive,
|
|
||||||
help="The function execution time at which Qinling should "
|
|
||||||
"terminate the function. The default is 5 seconds",
|
|
||||||
)
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
|
||||||
client = self.app.client_manager.function_engine
|
|
||||||
code = None
|
|
||||||
package = None
|
|
||||||
zip_file = None
|
|
||||||
|
|
||||||
function_id = parsed_args.function
|
|
||||||
if not uuidutils.is_uuid_like(function_id):
|
|
||||||
function_id = q_utils.find_resource_id_by_name(
|
|
||||||
client.functions, function_id)
|
|
||||||
|
|
||||||
if parsed_args.file or parsed_args.package:
|
|
||||||
code = {'source': 'package'}
|
|
||||||
zip_file = _get_package_file(parsed_args.package, parsed_args.file)
|
|
||||||
elif parsed_args.container or parsed_args.object:
|
|
||||||
swift = {}
|
|
||||||
if parsed_args.container:
|
|
||||||
swift["container"] = parsed_args.container
|
|
||||||
if parsed_args.object:
|
|
||||||
swift["object"] = parsed_args.object
|
|
||||||
code = {'source': 'swift', 'swift': swift}
|
|
||||||
|
|
||||||
if zip_file:
|
|
||||||
with open(zip_file, 'rb') as package:
|
|
||||||
func = client.functions.update(
|
|
||||||
function_id,
|
|
||||||
code=code,
|
|
||||||
package=package,
|
|
||||||
name=parsed_args.name,
|
|
||||||
description=parsed_args.description,
|
|
||||||
entry=parsed_args.entry,
|
|
||||||
cpu=parsed_args.cpu,
|
|
||||||
memory_size=parsed_args.memory_size,
|
|
||||||
timeout=parsed_args.timeout
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
func = client.functions.update(
|
|
||||||
function_id,
|
|
||||||
code=code,
|
|
||||||
name=parsed_args.name,
|
|
||||||
description=parsed_args.description,
|
|
||||||
entry=parsed_args.entry,
|
|
||||||
cpu=parsed_args.cpu,
|
|
||||||
memory_size=parsed_args.memory_size,
|
|
||||||
timeout=parsed_args.timeout
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.columns, utils.get_item_properties(func, self.columns)
|
|
||||||
|
|
||||||
|
|
||||||
class Detach(command.Command):
|
|
||||||
def get_parser(self, prog_name):
|
|
||||||
parser = super(Detach, self).get_parser(prog_name)
|
|
||||||
parser.add_argument('function', help='Function ID.')
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
|
||||||
client = self.app.client_manager.function_engine
|
|
||||||
success_msg = "Request to detach function %s has been accepted."
|
|
||||||
error_msg = "Unable to detach the specified function."
|
|
||||||
|
|
||||||
try:
|
|
||||||
client.functions.detach(parsed_args.function)
|
|
||||||
print(success_msg % parsed_args.function)
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
raise exceptions.QinlingClientException(error_msg)
|
|
||||||
|
|
||||||
|
|
||||||
class Download(command.Command):
|
|
||||||
def get_parser(self, prog_name):
|
|
||||||
parser = super(Download, self).get_parser(prog_name)
|
|
||||||
parser.add_argument('function', help='Function ID or name.')
|
|
||||||
parser.add_argument(
|
|
||||||
"-o",
|
|
||||||
"--output",
|
|
||||||
help="Target file path. If not provided, function ID will be used"
|
|
||||||
)
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
|
||||||
client = self.app.client_manager.function_engine
|
|
||||||
|
|
||||||
function_id = parsed_args.function
|
|
||||||
if not uuidutils.is_uuid_like(function_id):
|
|
||||||
function_id = q_utils.find_resource_id_by_name(
|
|
||||||
client.functions, function_id)
|
|
||||||
|
|
||||||
res = client.functions.get(function_id, download=True)
|
|
||||||
|
|
||||||
cwd = os.getcwd()
|
|
||||||
if parsed_args.output:
|
|
||||||
if os.path.isabs(parsed_args.output):
|
|
||||||
abs_path = parsed_args.output
|
|
||||||
else:
|
|
||||||
abs_path = os.path.join(cwd, parsed_args.output)
|
|
||||||
else:
|
|
||||||
abs_path = os.path.join(cwd, "%s.zip" % function_id)
|
|
||||||
|
|
||||||
with open(abs_path, 'wb') as target:
|
|
||||||
shutil.copyfileobj(res.raw, target)
|
|
||||||
print("Code package downloaded to %s" % (abs_path))
|
|
||||||
|
|
||||||
|
|
||||||
class Scaleup(command.Command):
|
|
||||||
def get_parser(self, prog_name):
|
|
||||||
parser = super(Scaleup, self).get_parser(prog_name)
|
|
||||||
parser.add_argument('function', help='Function ID.')
|
|
||||||
parser.add_argument('--count', type=worker_count, default=1,
|
|
||||||
help='Number of workers to scale up.')
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
|
||||||
client = self.app.client_manager.function_engine
|
|
||||||
success_msg = "Request to scale up function %s has been accepted."
|
|
||||||
error_msg = "Unable to scale up the specified function."
|
|
||||||
|
|
||||||
try:
|
|
||||||
client.functions.scaleup(parsed_args.function, parsed_args.count)
|
|
||||||
print(success_msg % parsed_args.function)
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
raise exceptions.QinlingClientException(error_msg)
|
|
||||||
|
|
||||||
|
|
||||||
class Scaledown(command.Command):
|
|
||||||
def get_parser(self, prog_name):
|
|
||||||
parser = super(Scaledown, self).get_parser(prog_name)
|
|
||||||
parser.add_argument('function', help='Function ID.')
|
|
||||||
parser.add_argument('--count', type=worker_count, default=1,
|
|
||||||
help='Number of workers to scale down.')
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
|
||||||
client = self.app.client_manager.function_engine
|
|
||||||
success_msg = "Request to scale down function %s has been accepted."
|
|
||||||
error_msg = "Unable to scale down the specified function."
|
|
||||||
|
|
||||||
try:
|
|
||||||
client.functions.scaledown(parsed_args.function, parsed_args.count)
|
|
||||||
print(success_msg % parsed_args.function)
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
raise exceptions.QinlingClientException(error_msg)
|
|
|
@ -1,164 +0,0 @@
|
||||||
# Copyright 2018 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 osc_lib import utils
|
|
||||||
from oslo_utils import uuidutils
|
|
||||||
|
|
||||||
from qinlingclient.osc.v1 import base
|
|
||||||
from qinlingclient import utils as q_utils
|
|
||||||
|
|
||||||
|
|
||||||
class List(base.QinlingLister):
|
|
||||||
columns = base.FUNCTION_ALIAS_COLUMNS
|
|
||||||
filtered_columns = base.FILTERED_FUNCTION_ALIAS_COLUMNS
|
|
||||||
|
|
||||||
def _get_resources(self, parsed_args):
|
|
||||||
client = self.app.client_manager.function_engine
|
|
||||||
|
|
||||||
return client.function_aliases.list(**base.get_filters(parsed_args))
|
|
||||||
|
|
||||||
|
|
||||||
class Create(command.ShowOne):
|
|
||||||
columns = base.FUNCTION_ALIAS_COLUMNS
|
|
||||||
|
|
||||||
def get_parser(self, prog_name):
|
|
||||||
parser = super(Create, self).get_parser(prog_name)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"name",
|
|
||||||
help="Function Alias name.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--function",
|
|
||||||
required=True,
|
|
||||||
help="Function ID or Name.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--function-version",
|
|
||||||
type=int,
|
|
||||||
default=0,
|
|
||||||
help="Function Version number.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--description",
|
|
||||||
default='',
|
|
||||||
help="Description for the new alias.",
|
|
||||||
)
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
|
||||||
client = self.app.client_manager.function_engine
|
|
||||||
|
|
||||||
function_id = parsed_args.function
|
|
||||||
if not uuidutils.is_uuid_like(function_id):
|
|
||||||
# Try to find the function id with name
|
|
||||||
function_id = q_utils.find_resource_id_by_name(
|
|
||||||
client.functions, function_id)
|
|
||||||
|
|
||||||
alias = client.function_aliases.create(
|
|
||||||
parsed_args.name,
|
|
||||||
function_id=function_id,
|
|
||||||
function_version=parsed_args.function_version,
|
|
||||||
description=parsed_args.description,
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.columns, utils.get_item_properties(alias, self.columns)
|
|
||||||
|
|
||||||
|
|
||||||
class Delete(base.QinlingDeleter):
|
|
||||||
def get_parser(self, prog_name):
|
|
||||||
parser = super(Delete, self).get_parser(prog_name)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"name",
|
|
||||||
nargs='+',
|
|
||||||
help="Function Alias name(s).",
|
|
||||||
)
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
|
||||||
client = self.app.client_manager.function_engine
|
|
||||||
|
|
||||||
self.delete = client.function_aliases.delete
|
|
||||||
self.resource = 'function_alias'
|
|
||||||
|
|
||||||
self.delete_resources(parsed_args.name)
|
|
||||||
|
|
||||||
|
|
||||||
class Show(command.ShowOne):
|
|
||||||
columns = base.FUNCTION_ALIAS_COLUMNS
|
|
||||||
|
|
||||||
def get_parser(self, prog_name):
|
|
||||||
parser = super(Show, self).get_parser(prog_name)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"name",
|
|
||||||
help="Function Alias name.",
|
|
||||||
)
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
|
||||||
client = self.app.client_manager.function_engine
|
|
||||||
|
|
||||||
alias = client.function_aliases.get(parsed_args.name)
|
|
||||||
|
|
||||||
return self.columns, utils.get_item_properties(alias, self.columns)
|
|
||||||
|
|
||||||
|
|
||||||
class Update(command.ShowOne):
|
|
||||||
columns = base.FUNCTION_ALIAS_COLUMNS
|
|
||||||
|
|
||||||
def get_parser(self, prog_name):
|
|
||||||
parser = super(Update, self).get_parser(prog_name)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"name",
|
|
||||||
help="Function Alias name.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--function",
|
|
||||||
help="Function ID or Name.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--function-version",
|
|
||||||
help="Function Version number.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--description",
|
|
||||||
help="Description for the new alias.",
|
|
||||||
)
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
|
||||||
client = self.app.client_manager.function_engine
|
|
||||||
|
|
||||||
function_id = parsed_args.function
|
|
||||||
if function_id and not uuidutils.is_uuid_like(function_id):
|
|
||||||
# Try to find the function id with name
|
|
||||||
function_id = q_utils.find_resource_id_by_name(
|
|
||||||
client.functions, function_id)
|
|
||||||
|
|
||||||
alias = client.function_aliases.update(
|
|
||||||
parsed_args.name,
|
|
||||||
function_id=function_id,
|
|
||||||
function_version=parsed_args.function_version,
|
|
||||||
description=parsed_args.description,
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.columns, utils.get_item_properties(alias, self.columns)
|
|
|
@ -1,162 +0,0 @@
|
||||||
# Copyright 2017 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 osc_lib import utils
|
|
||||||
from oslo_utils import uuidutils
|
|
||||||
|
|
||||||
from qinlingclient.osc.v1 import base
|
|
||||||
from qinlingclient import utils as q_utils
|
|
||||||
|
|
||||||
|
|
||||||
class List(base.QinlingLister):
|
|
||||||
columns = base.EXECUTION_COLUMNS
|
|
||||||
filtered_columns = base.FILTERED_EXECUTION_COLUMNS
|
|
||||||
|
|
||||||
def _get_resources(self, parsed_args):
|
|
||||||
client = self.app.client_manager.function_engine
|
|
||||||
|
|
||||||
return client.function_executions.list(**base.get_filters(parsed_args))
|
|
||||||
|
|
||||||
|
|
||||||
class Create(command.ShowOne):
|
|
||||||
columns = base.EXECUTION_COLUMNS
|
|
||||||
|
|
||||||
def get_parser(self, prog_name):
|
|
||||||
parser = super(Create, self).get_parser(prog_name)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"--function",
|
|
||||||
help="Function name or ID.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--function-version",
|
|
||||||
type=int,
|
|
||||||
default=0,
|
|
||||||
help="Function version number.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--function-alias",
|
|
||||||
help="Function alias which corresponds to a specific function and "
|
|
||||||
"version. When function alias is specified, function and "
|
|
||||||
"function version are not needed.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--input",
|
|
||||||
help="Input for the function.",
|
|
||||||
)
|
|
||||||
group = parser.add_mutually_exclusive_group()
|
|
||||||
group.add_argument(
|
|
||||||
"--sync",
|
|
||||||
action='store_true',
|
|
||||||
dest='sync',
|
|
||||||
default=True,
|
|
||||||
help="Run execution synchronously."
|
|
||||||
)
|
|
||||||
group.add_argument(
|
|
||||||
"--async",
|
|
||||||
action='store_false',
|
|
||||||
dest='sync',
|
|
||||||
default=True,
|
|
||||||
help="Run execution asynchronously.",
|
|
||||||
)
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
|
||||||
client = self.app.client_manager.function_engine
|
|
||||||
|
|
||||||
function_alias = parsed_args.function_alias
|
|
||||||
if function_alias:
|
|
||||||
function_id = None
|
|
||||||
function_version = None
|
|
||||||
else:
|
|
||||||
function_version = parsed_args.function_version
|
|
||||||
function_id = parsed_args.function
|
|
||||||
if not uuidutils.is_uuid_like(function_id):
|
|
||||||
# Try to find the function id with name
|
|
||||||
function_id = q_utils.find_resource_id_by_name(
|
|
||||||
client.functions, function_id)
|
|
||||||
|
|
||||||
execution = client.function_executions.create(
|
|
||||||
function_alias=function_alias,
|
|
||||||
function_id=function_id,
|
|
||||||
function_version=function_version,
|
|
||||||
sync=parsed_args.sync,
|
|
||||||
input=parsed_args.input
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.columns, utils.get_item_properties(execution, self.columns)
|
|
||||||
|
|
||||||
|
|
||||||
class Delete(base.QinlingDeleter):
|
|
||||||
def get_parser(self, prog_name):
|
|
||||||
parser = super(Delete, self).get_parser(prog_name)
|
|
||||||
|
|
||||||
group = parser.add_mutually_exclusive_group()
|
|
||||||
group.add_argument(
|
|
||||||
"--execution",
|
|
||||||
nargs='+',
|
|
||||||
help="ID of function execution(s)."
|
|
||||||
)
|
|
||||||
group.add_argument(
|
|
||||||
"--function",
|
|
||||||
nargs='+',
|
|
||||||
help="ID of function(s)."
|
|
||||||
)
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
|
||||||
client = self.app.client_manager.function_engine
|
|
||||||
self.delete = client.function_executions.delete
|
|
||||||
self.resource = 'execution'
|
|
||||||
|
|
||||||
if parsed_args.execution:
|
|
||||||
self.delete_resources(parsed_args.execution)
|
|
||||||
elif parsed_args.function:
|
|
||||||
for f in parsed_args.function:
|
|
||||||
execs = client.function_executions.list(function_id=f)
|
|
||||||
ids = [e.id for e in execs]
|
|
||||||
self.delete_resources(ids)
|
|
||||||
|
|
||||||
|
|
||||||
class Show(command.ShowOne):
|
|
||||||
columns = base.EXECUTION_COLUMNS
|
|
||||||
|
|
||||||
def get_parser(self, prog_name):
|
|
||||||
parser = super(Show, self).get_parser(prog_name)
|
|
||||||
parser.add_argument('execution', help='Execution ID.')
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
|
||||||
client = self.app.client_manager.function_engine
|
|
||||||
execution = client.function_executions.get(parsed_args.execution)
|
|
||||||
|
|
||||||
return self.columns, utils.get_item_properties(execution, self.columns)
|
|
||||||
|
|
||||||
|
|
||||||
class LogShow(command.Command):
|
|
||||||
def get_parser(self, prog_name):
|
|
||||||
parser = super(LogShow, self).get_parser(prog_name)
|
|
||||||
parser.add_argument('execution', help='Execution ID.')
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
|
||||||
client = self.app.client_manager.function_engine
|
|
||||||
log = client.function_executions.get_log(parsed_args.execution)
|
|
||||||
|
|
||||||
self.app.stdout.write(log or "\n")
|
|
|
@ -1,156 +0,0 @@
|
||||||
# Copyright 2017 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 osc_lib import utils
|
|
||||||
from oslo_utils import uuidutils
|
|
||||||
|
|
||||||
from qinlingclient.common import exceptions
|
|
||||||
from qinlingclient.osc.v1 import base
|
|
||||||
from qinlingclient import utils as q_utils
|
|
||||||
|
|
||||||
|
|
||||||
class List(base.QinlingLister):
|
|
||||||
columns = base.FUNCTION_VERSION_COLUMNS
|
|
||||||
|
|
||||||
def get_parser(self, prog_name):
|
|
||||||
parser = super(List, self).get_parser(prog_name)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"function_id",
|
|
||||||
help="Function ID.",
|
|
||||||
)
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def _get_resources(self, parsed_args):
|
|
||||||
client = self.app.client_manager.function_engine
|
|
||||||
|
|
||||||
return client.function_versions.list(parsed_args.function_id)
|
|
||||||
|
|
||||||
|
|
||||||
class Create(command.ShowOne):
|
|
||||||
columns = base.FUNCTION_VERSION_COLUMNS
|
|
||||||
|
|
||||||
def get_parser(self, prog_name):
|
|
||||||
parser = super(Create, self).get_parser(prog_name)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"function",
|
|
||||||
help="Function name or ID.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--description",
|
|
||||||
help="Description for the new version.",
|
|
||||||
)
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
|
||||||
client = self.app.client_manager.function_engine
|
|
||||||
|
|
||||||
function_id = parsed_args.function
|
|
||||||
if not uuidutils.is_uuid_like(function_id):
|
|
||||||
# Try to find the function id with name
|
|
||||||
function_id = q_utils.find_resource_id_by_name(
|
|
||||||
client.functions, function_id)
|
|
||||||
|
|
||||||
version = client.function_versions.create(
|
|
||||||
function_id,
|
|
||||||
description=parsed_args.description,
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.columns, utils.get_item_properties(version, self.columns)
|
|
||||||
|
|
||||||
|
|
||||||
class Delete(base.QinlingDeleter):
|
|
||||||
def get_parser(self, prog_name):
|
|
||||||
parser = super(Delete, self).get_parser(prog_name)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"function_id",
|
|
||||||
help="Function ID.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"version_number",
|
|
||||||
help="Function version.",
|
|
||||||
)
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
|
||||||
client = self.app.client_manager.function_engine
|
|
||||||
|
|
||||||
client.function_versions.delete(parsed_args.function_id,
|
|
||||||
parsed_args.version_number)
|
|
||||||
|
|
||||||
|
|
||||||
class Show(command.ShowOne):
|
|
||||||
columns = base.FUNCTION_VERSION_COLUMNS
|
|
||||||
|
|
||||||
def get_parser(self, prog_name):
|
|
||||||
parser = super(Show, self).get_parser(prog_name)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"function_id",
|
|
||||||
help="Function ID.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"version_number",
|
|
||||||
help="Function version.",
|
|
||||||
)
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
|
||||||
client = self.app.client_manager.function_engine
|
|
||||||
|
|
||||||
version = client.function_versions.get(parsed_args.function_id,
|
|
||||||
parsed_args.version_number)
|
|
||||||
|
|
||||||
return self.columns, utils.get_item_properties(version, self.columns)
|
|
||||||
|
|
||||||
|
|
||||||
class Detach(command.Command):
|
|
||||||
def get_parser(self, prog_name):
|
|
||||||
parser = super(Detach, self).get_parser(prog_name)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"function_id",
|
|
||||||
help="Function ID.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"version_number",
|
|
||||||
help="Function version.",
|
|
||||||
)
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
|
||||||
client = self.app.client_manager.function_engine
|
|
||||||
|
|
||||||
success_msg = "Request to detach function %s(version %s) has been " \
|
|
||||||
"accepted."
|
|
||||||
error_msg = "Unable to detach the specified function version."
|
|
||||||
|
|
||||||
try:
|
|
||||||
client.function_versions.detach(parsed_args.function_id,
|
|
||||||
parsed_args.version_number)
|
|
||||||
print(
|
|
||||||
success_msg %
|
|
||||||
(parsed_args.function_id, parsed_args.version_number)
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
raise exceptions.QinlingClientException(error_msg)
|
|
|
@ -1,29 +0,0 @@
|
||||||
# Copyright 2017 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 qinlingclient.osc.v1 import base
|
|
||||||
|
|
||||||
|
|
||||||
class List(base.QinlingLister):
|
|
||||||
columns = base.WORKER_COLUMNS
|
|
||||||
|
|
||||||
def get_parser(self, prog_name):
|
|
||||||
parser = super(List, self).get_parser(prog_name)
|
|
||||||
parser.add_argument('function', help='Function ID.')
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def _get_resources(self, parsed_args):
|
|
||||||
client = self.app.client_manager.function_engine
|
|
||||||
|
|
||||||
return client.function_workers.list(parsed_args.function)
|
|
|
@ -1,189 +0,0 @@
|
||||||
# Copyright 2017 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 osc_lib import utils as osc_utils
|
|
||||||
from oslo_utils import uuidutils
|
|
||||||
|
|
||||||
from qinlingclient.osc.v1 import base
|
|
||||||
from qinlingclient import utils as q_utils
|
|
||||||
|
|
||||||
|
|
||||||
class List(base.QinlingLister):
|
|
||||||
columns = base.JOB_COLUMNS
|
|
||||||
filtered_columns = base.FILTERED_JOB_COLUMNS
|
|
||||||
|
|
||||||
def _get_resources(self, parsed_args):
|
|
||||||
client = self.app.client_manager.function_engine
|
|
||||||
|
|
||||||
return client.jobs.list(**base.get_filters(parsed_args))
|
|
||||||
|
|
||||||
|
|
||||||
class Create(command.ShowOne):
|
|
||||||
columns = base.JOB_COLUMNS
|
|
||||||
|
|
||||||
def get_parser(self, prog_name):
|
|
||||||
parser = super(Create, self).get_parser(prog_name)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"--function",
|
|
||||||
help="Function name or ID.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--function-version",
|
|
||||||
type=int,
|
|
||||||
default=0,
|
|
||||||
help="Function version number. Default: 0",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--function-alias",
|
|
||||||
help="Function alias which corresponds to a specific function and "
|
|
||||||
"version. When function alias is specified, function and "
|
|
||||||
"function version are not needed.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--name",
|
|
||||||
help="Job name."
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--first-execution-time",
|
|
||||||
help="The earliest execution time(UTC) for the job."
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--pattern",
|
|
||||||
help="The cron pattern for job execution."
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--function-input",
|
|
||||||
help="Function input."
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--count",
|
|
||||||
type=int,
|
|
||||||
help="Expected number of executions triggered by job."
|
|
||||||
)
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
|
||||||
client = self.app.client_manager.function_engine
|
|
||||||
|
|
||||||
function_alias = parsed_args.function_alias
|
|
||||||
if function_alias:
|
|
||||||
function_id = None
|
|
||||||
function_version = None
|
|
||||||
else:
|
|
||||||
function_version = parsed_args.function_version
|
|
||||||
function_id = parsed_args.function
|
|
||||||
if not uuidutils.is_uuid_like(function_id):
|
|
||||||
# Try to find the function id with name
|
|
||||||
function_id = q_utils.find_resource_id_by_name(
|
|
||||||
client.functions, function_id)
|
|
||||||
|
|
||||||
job = client.jobs.create(
|
|
||||||
function_alias=function_alias,
|
|
||||||
function_id=function_id,
|
|
||||||
function_version=function_version,
|
|
||||||
name=parsed_args.name,
|
|
||||||
first_execution_time=parsed_args.first_execution_time,
|
|
||||||
pattern=parsed_args.pattern,
|
|
||||||
function_input=parsed_args.function_input,
|
|
||||||
count=parsed_args.count
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.columns, osc_utils.get_item_properties(job, self.columns)
|
|
||||||
|
|
||||||
|
|
||||||
class Delete(base.QinlingDeleter):
|
|
||||||
def get_parser(self, prog_name):
|
|
||||||
parser = super(Delete, self).get_parser(prog_name)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
'job',
|
|
||||||
nargs='+',
|
|
||||||
help='Job ID(s).'
|
|
||||||
)
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
|
||||||
client = self.app.client_manager.function_engine
|
|
||||||
self.delete = client.jobs.delete
|
|
||||||
self.resource = 'job'
|
|
||||||
|
|
||||||
self.delete_resources(parsed_args.job)
|
|
||||||
|
|
||||||
|
|
||||||
class Show(command.ShowOne):
|
|
||||||
columns = base.JOB_COLUMNS
|
|
||||||
|
|
||||||
def get_parser(self, prog_name):
|
|
||||||
parser = super(Show, self).get_parser(prog_name)
|
|
||||||
parser.add_argument('job', help='Job ID.')
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
|
||||||
client = self.app.client_manager.function_engine
|
|
||||||
job = client.jobs.get(parsed_args.job)
|
|
||||||
|
|
||||||
return self.columns, osc_utils.get_item_properties(job, self.columns)
|
|
||||||
|
|
||||||
|
|
||||||
class Update(command.ShowOne):
|
|
||||||
columns = base.JOB_COLUMNS
|
|
||||||
|
|
||||||
def get_parser(self, prog_name):
|
|
||||||
parser = super(Update, self).get_parser(prog_name)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
'id',
|
|
||||||
help='Job ID.'
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--name",
|
|
||||||
help="Job name."
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--status",
|
|
||||||
choices=['running', 'paused', 'done', 'cancelled'],
|
|
||||||
help="Job status."
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--next-execution-time",
|
|
||||||
help="The next execution time(UTC) for the job."
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--pattern",
|
|
||||||
help="The cron pattern for job execution."
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--function-input",
|
|
||||||
help="Function input."
|
|
||||||
)
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
|
||||||
client = self.app.client_manager.function_engine
|
|
||||||
job = client.jobs.update(
|
|
||||||
parsed_args.id,
|
|
||||||
name=parsed_args.name,
|
|
||||||
status=parsed_args.status,
|
|
||||||
pattern=parsed_args.pattern,
|
|
||||||
next_execution_time=parsed_args.next_execution_time,
|
|
||||||
function_input=parsed_args.function_input,
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.columns, osc_utils.get_item_properties(job, self.columns)
|
|
|
@ -1,131 +0,0 @@
|
||||||
# Copyright 2017 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 osc_lib import utils
|
|
||||||
|
|
||||||
from qinlingclient.osc.v1 import base
|
|
||||||
|
|
||||||
|
|
||||||
class List(base.QinlingLister):
|
|
||||||
columns = base.RUNTIME_COLUMNS
|
|
||||||
|
|
||||||
def _get_resources(self, parsed_args):
|
|
||||||
client = self.app.client_manager.function_engine
|
|
||||||
|
|
||||||
return client.runtimes.list(**base.get_filters(parsed_args))
|
|
||||||
|
|
||||||
|
|
||||||
class Create(command.ShowOne):
|
|
||||||
columns = base.RUNTIME_COLUMNS
|
|
||||||
|
|
||||||
def get_parser(self, prog_name):
|
|
||||||
parser = super(Create, self).get_parser(prog_name)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"image",
|
|
||||||
metavar='IMAGE',
|
|
||||||
help="Container image name used by runtime.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--name",
|
|
||||||
help="Runtime name.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--description",
|
|
||||||
help="Runtime description.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--private",
|
|
||||||
dest='is_public',
|
|
||||||
action='store_false',
|
|
||||||
help="Create private runtime or not, will create public"
|
|
||||||
"runtime if not specified",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--untrusted",
|
|
||||||
dest='trusted',
|
|
||||||
action='store_false',
|
|
||||||
help="Create untrusted runtime or not, will create trusted "
|
|
||||||
"runtime if not specified",
|
|
||||||
)
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
|
||||||
client = self.app.client_manager.function_engine
|
|
||||||
|
|
||||||
runtime = client.runtimes.create(
|
|
||||||
name=parsed_args.name,
|
|
||||||
description=parsed_args.description,
|
|
||||||
image=parsed_args.image,
|
|
||||||
is_public=parsed_args.is_public,
|
|
||||||
trusted=parsed_args.trusted
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.columns, utils.get_item_properties(runtime, self.columns)
|
|
||||||
|
|
||||||
|
|
||||||
class Delete(base.QinlingDeleter):
|
|
||||||
def get_parser(self, prog_name):
|
|
||||||
parser = super(Delete, self).get_parser(prog_name)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
'runtime',
|
|
||||||
nargs='+',
|
|
||||||
metavar='RUNTIME',
|
|
||||||
help='Id of runtime(s).'
|
|
||||||
)
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
|
||||||
client = self.app.client_manager.function_engine
|
|
||||||
self.delete = client.runtimes.delete
|
|
||||||
self.resource = 'runtime'
|
|
||||||
|
|
||||||
self.delete_resources(parsed_args.runtime)
|
|
||||||
|
|
||||||
|
|
||||||
class Show(command.ShowOne):
|
|
||||||
columns = base.RUNTIME_COLUMNS
|
|
||||||
|
|
||||||
def get_parser(self, prog_name):
|
|
||||||
parser = super(Show, self).get_parser(prog_name)
|
|
||||||
parser.add_argument('runtime', help='Runtime ID.')
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
|
||||||
client = self.app.client_manager.function_engine
|
|
||||||
runtime = client.runtimes.get(parsed_args.runtime)
|
|
||||||
|
|
||||||
return self.columns, utils.get_item_properties(runtime, self.columns)
|
|
||||||
|
|
||||||
|
|
||||||
class Pool(command.ShowOne):
|
|
||||||
columns = base.RUNTIME_POOL_COLUMNS
|
|
||||||
|
|
||||||
def get_parser(self, prog_name):
|
|
||||||
parser = super(Pool, self).get_parser(prog_name)
|
|
||||||
parser.add_argument('runtime', help='Runtime ID.')
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
|
||||||
client = self.app.client_manager.function_engine
|
|
||||||
|
|
||||||
pool = client.runtimes.get_pool(parsed_args.runtime)
|
|
||||||
return self.columns, utils.get_item_properties(pool,
|
|
||||||
self.columns)
|
|
|
@ -1,157 +0,0 @@
|
||||||
# Copyright 2017 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 osc_lib import utils
|
|
||||||
from oslo_utils import uuidutils
|
|
||||||
|
|
||||||
from qinlingclient.osc.v1 import base
|
|
||||||
from qinlingclient import utils as q_utils
|
|
||||||
|
|
||||||
|
|
||||||
class List(base.QinlingLister):
|
|
||||||
columns = base.WEBHOOK_COLUMNS
|
|
||||||
filtered_columns = base.FILTERED_WEBHOOK_COLUMNS
|
|
||||||
|
|
||||||
def _get_resources(self, parsed_args):
|
|
||||||
client = self.app.client_manager.function_engine
|
|
||||||
|
|
||||||
return client.webhooks.list(**base.get_filters(parsed_args))
|
|
||||||
|
|
||||||
|
|
||||||
class Create(command.ShowOne):
|
|
||||||
columns = base.WEBHOOK_COLUMNS
|
|
||||||
|
|
||||||
def get_parser(self, prog_name):
|
|
||||||
parser = super(Create, self).get_parser(prog_name)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"--function",
|
|
||||||
help="Function name or ID.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--function-version",
|
|
||||||
type=int,
|
|
||||||
default=0,
|
|
||||||
help="Function version number. Default: 0",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--function-alias",
|
|
||||||
help="Function alias which corresponds to a specific function and "
|
|
||||||
"version. When function alias is specified, function and "
|
|
||||||
"function version are not needed.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--description",
|
|
||||||
help="Webhook description.",
|
|
||||||
)
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
|
||||||
client = self.app.client_manager.function_engine
|
|
||||||
|
|
||||||
function_alias = parsed_args.function_alias
|
|
||||||
if function_alias:
|
|
||||||
function_id = None
|
|
||||||
function_version = None
|
|
||||||
else:
|
|
||||||
function_version = parsed_args.function_version
|
|
||||||
function_id = parsed_args.function
|
|
||||||
if not uuidutils.is_uuid_like(function_id):
|
|
||||||
# Try to find the function id with name
|
|
||||||
function_id = q_utils.find_resource_id_by_name(
|
|
||||||
client.functions, function_id)
|
|
||||||
|
|
||||||
webhook = client.webhooks.create(
|
|
||||||
function_alias=function_alias,
|
|
||||||
function_id=function_id,
|
|
||||||
function_version=function_version,
|
|
||||||
description=parsed_args.description,
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.columns, utils.get_item_properties(webhook, self.columns)
|
|
||||||
|
|
||||||
|
|
||||||
class Delete(base.QinlingDeleter):
|
|
||||||
def get_parser(self, prog_name):
|
|
||||||
parser = super(Delete, self).get_parser(prog_name)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
'webhook',
|
|
||||||
nargs='+',
|
|
||||||
help='Id of webhook(s).'
|
|
||||||
)
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
|
||||||
client = self.app.client_manager.function_engine
|
|
||||||
self.delete = client.webhooks.delete
|
|
||||||
self.resource = 'webhook'
|
|
||||||
|
|
||||||
self.delete_resources(parsed_args.webhook)
|
|
||||||
|
|
||||||
|
|
||||||
class Show(command.ShowOne):
|
|
||||||
columns = base.WEBHOOK_COLUMNS
|
|
||||||
|
|
||||||
def get_parser(self, prog_name):
|
|
||||||
parser = super(Show, self).get_parser(prog_name)
|
|
||||||
parser.add_argument('webhook', help='Webhook ID.')
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
|
||||||
client = self.app.client_manager.function_engine
|
|
||||||
webhook = client.webhooks.get(parsed_args.webhook)
|
|
||||||
|
|
||||||
return self.columns, utils.get_item_properties(webhook, self.columns)
|
|
||||||
|
|
||||||
|
|
||||||
class Update(command.ShowOne):
|
|
||||||
columns = base.WEBHOOK_COLUMNS
|
|
||||||
|
|
||||||
def get_parser(self, prog_name):
|
|
||||||
parser = super(Update, self).get_parser(prog_name)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
'id',
|
|
||||||
help='Webhook ID.'
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--function-id",
|
|
||||||
help="Function ID."
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--function-version",
|
|
||||||
help="Function version number.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--description",
|
|
||||||
help="Webhook description."
|
|
||||||
)
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
|
||||||
client = self.app.client_manager.function_engine
|
|
||||||
webhook = client.webhooks.update(
|
|
||||||
parsed_args.id,
|
|
||||||
function_id=parsed_args.function_id,
|
|
||||||
function_version=parsed_args.function_version,
|
|
||||||
description=parsed_args.description
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.columns, utils.get_item_properties(webhook, self.columns)
|
|
|
@ -1,20 +0,0 @@
|
||||||
# Copyright 2017 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 oslotest import base
|
|
||||||
|
|
||||||
|
|
||||||
class TestCase(base.BaseTestCase):
|
|
||||||
|
|
||||||
"""Test case base class for all unit tests."""
|
|
|
@ -1,85 +0,0 @@
|
||||||
# Copyright 2018 AWCloud Software 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 unittest import mock
|
|
||||||
|
|
||||||
|
|
||||||
AUTH_TOKEN = 'foobar'
|
|
||||||
AUTH_URL = 'http://0.0.0.0'
|
|
||||||
|
|
||||||
|
|
||||||
class FakeResource(object):
|
|
||||||
|
|
||||||
def __init__(self, manager=None, info=None, loaded=False, methods=None):
|
|
||||||
"""Set attributes and methods for a resource.
|
|
||||||
|
|
||||||
:param manager:
|
|
||||||
The resource manager
|
|
||||||
:param Dictionary info:
|
|
||||||
A dictionary with all attributes
|
|
||||||
:param bool loaded:
|
|
||||||
True if the resource is loaded in memory
|
|
||||||
:param Dictionary methods:
|
|
||||||
A dictionary with all methods
|
|
||||||
"""
|
|
||||||
info = info or {}
|
|
||||||
methods = methods or {}
|
|
||||||
|
|
||||||
self.__name__ = type(self).__name__
|
|
||||||
self.manager = manager
|
|
||||||
self._info = info
|
|
||||||
self._add_details(info)
|
|
||||||
self._add_methods(methods)
|
|
||||||
self._loaded = loaded
|
|
||||||
|
|
||||||
def _add_details(self, info):
|
|
||||||
for (k, v) in info.items():
|
|
||||||
setattr(self, k, v)
|
|
||||||
|
|
||||||
def _add_methods(self, methods):
|
|
||||||
"""Fake methods with MagicMock objects.
|
|
||||||
|
|
||||||
For each <@key, @value> pairs in methods, add an callable MagicMock
|
|
||||||
object named @key as an attribute, and set the mock's return_value to
|
|
||||||
@value. When users access the attribute with (), @value will be
|
|
||||||
returned, which looks like a function call.
|
|
||||||
"""
|
|
||||||
for (name, ret) in methods.items():
|
|
||||||
method = mock.Mock(return_value=ret)
|
|
||||||
setattr(self, name, method)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
reprkeys = sorted(k for k in self.__dict__.keys() if k[0] != '_' and
|
|
||||||
k != 'manager')
|
|
||||||
info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys)
|
|
||||||
return "<%s %s>" % (self.__class__.__name__, info)
|
|
||||||
|
|
||||||
def keys(self):
|
|
||||||
return self._info.keys()
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
return self._info
|
|
||||||
|
|
||||||
@property
|
|
||||||
def info(self):
|
|
||||||
return self._info
|
|
||||||
|
|
||||||
def __getitem__(self, item):
|
|
||||||
return self._info.get(item)
|
|
||||||
|
|
||||||
def get(self, item, default=None):
|
|
||||||
return self._info.get(item, default)
|
|
||||||
|
|
||||||
def pop(self, key, default_value=None):
|
|
||||||
return self.info.pop(key, default_value)
|
|
|
@ -1,694 +0,0 @@
|
||||||
# Copyright 2018 AWCloud Software 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 copy
|
|
||||||
import hashlib
|
|
||||||
from unittest import mock
|
|
||||||
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
from osc_lib.tests import utils
|
|
||||||
|
|
||||||
from qinlingclient.tests.unit import fakes
|
|
||||||
|
|
||||||
|
|
||||||
class FakeQinlingClient(object):
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
self.auth_token = kwargs['auth_token']
|
|
||||||
self.auth_url = kwargs['auth_url']
|
|
||||||
self.runtimes = mock.Mock()
|
|
||||||
self.functions = mock.Mock()
|
|
||||||
self.function_executions = mock.Mock()
|
|
||||||
self.function_versions = mock.Mock()
|
|
||||||
self.function_workers = mock.Mock()
|
|
||||||
self.jobs = mock.Mock()
|
|
||||||
self.webhooks = mock.Mock()
|
|
||||||
self.function_aliases = mock.Mock()
|
|
||||||
|
|
||||||
|
|
||||||
class TestQinlingClient(utils.TestCommand):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestQinlingClient, self).setUp()
|
|
||||||
self.app.client_manager.function_engine = FakeQinlingClient(
|
|
||||||
auth_token=fakes.AUTH_TOKEN,
|
|
||||||
auth_url=fakes.AUTH_URL
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class FakeRuntime(object):
|
|
||||||
"""Fake one or more runtimes."""
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create_one_runtime(attrs=None):
|
|
||||||
"""Create a fake runtime.
|
|
||||||
|
|
||||||
:param Dictionary attrs:
|
|
||||||
A dictionary with all atrributes
|
|
||||||
:return:
|
|
||||||
A FakeResource object, with id, name, etc.
|
|
||||||
"""
|
|
||||||
|
|
||||||
attrs = attrs or {}
|
|
||||||
# Set default attributes.
|
|
||||||
runtime_attrs = {
|
|
||||||
'id': str(uuid.uuid4()),
|
|
||||||
'name': 'runtime-name-' + uuid.uuid4().hex,
|
|
||||||
'image': 'openstackqinling/python-runtime',
|
|
||||||
'status': 'available',
|
|
||||||
'description': 'runtime-description-' + uuid.uuid4().hex,
|
|
||||||
'is_public': True,
|
|
||||||
'trusted': True,
|
|
||||||
'project_id': str(uuid.uuid4()),
|
|
||||||
'created_at': '2018-07-26 09:00:00',
|
|
||||||
'updated_at': '2018-07-26 09:00:30'
|
|
||||||
}
|
|
||||||
|
|
||||||
# Overwrite default attributes.
|
|
||||||
runtime_attrs.update(attrs)
|
|
||||||
|
|
||||||
runtime = fakes.FakeResource(info=copy.deepcopy(runtime_attrs),
|
|
||||||
loaded=True)
|
|
||||||
|
|
||||||
return runtime
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create_runtimes(attrs=None, count=2):
|
|
||||||
"""Create multiple fake runtimes.
|
|
||||||
|
|
||||||
:param Dictionary attrs:
|
|
||||||
A dictionary with all atrributes
|
|
||||||
:param int count:
|
|
||||||
The number of runtimes to fake
|
|
||||||
:return:
|
|
||||||
A list of FakeResource objects faking the runtimes.
|
|
||||||
"""
|
|
||||||
|
|
||||||
runtimes = []
|
|
||||||
for i in range(count):
|
|
||||||
runtimes.append(FakeRuntime.create_one_runtime(attrs))
|
|
||||||
|
|
||||||
return runtimes
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_runtimes(runtimes=None, count=2):
|
|
||||||
"""Get an iterable Mock object with a list of faked runtimes.
|
|
||||||
|
|
||||||
If runtimes list is provided, then initialize the Mock object with the
|
|
||||||
list. Otherwise create one.
|
|
||||||
|
|
||||||
:param List runtimes:
|
|
||||||
A list of FakeResource faking runtimes
|
|
||||||
:param int count:
|
|
||||||
The number of runtimes to fake
|
|
||||||
:return:
|
|
||||||
An iterable Mock object with side_effect set to a list of faked
|
|
||||||
runtimes.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if runtimes is None:
|
|
||||||
runtimes = FakeRuntime.create_runtimes(count=count)
|
|
||||||
|
|
||||||
return mock.Mock(side_effect=runtimes)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create_one_runtime_pool(attrs=None):
|
|
||||||
"""Create a fake runtime pool.
|
|
||||||
|
|
||||||
:param Dictionary attrs:
|
|
||||||
A dictionary with all atrributes
|
|
||||||
:return:
|
|
||||||
A FakeResource object, with name, capacity.
|
|
||||||
"""
|
|
||||||
|
|
||||||
attrs = attrs or {}
|
|
||||||
# Set default attributes.
|
|
||||||
pool_attrs = {
|
|
||||||
'name': 'runtime-id-' + uuid.uuid4().hex,
|
|
||||||
'capacity': {'available': 5, 'total': 5}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Overwrite default attributes.
|
|
||||||
pool_attrs.update(attrs)
|
|
||||||
|
|
||||||
runtime_pool = fakes.FakeResource(info=copy.deepcopy(pool_attrs),
|
|
||||||
loaded=True)
|
|
||||||
|
|
||||||
return runtime_pool
|
|
||||||
|
|
||||||
|
|
||||||
class FakeFunction(object):
|
|
||||||
"""Fake one or more functions."""
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_fake_md5():
|
|
||||||
content = uuid.uuid4().hex
|
|
||||||
if not isinstance(content, bytes):
|
|
||||||
content = content.encode('utf-8')
|
|
||||||
return hashlib.md5(content).hexdigest()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create_one_function(attrs=None):
|
|
||||||
"""Create a fake function.
|
|
||||||
|
|
||||||
:param Dictionary attrs:
|
|
||||||
A dictionary with all atrributes
|
|
||||||
:return:
|
|
||||||
A FakeResource object, with id, name, etc.
|
|
||||||
"""
|
|
||||||
|
|
||||||
attrs = attrs or {}
|
|
||||||
# Set default attributes.
|
|
||||||
function_attrs = {
|
|
||||||
'id': str(uuid.uuid4()),
|
|
||||||
'name': 'function-name-' + uuid.uuid4().hex,
|
|
||||||
'description': 'function-description-' + uuid.uuid4().hex,
|
|
||||||
'count': 0,
|
|
||||||
'code': {'md5sum': FakeFunction.get_fake_md5(),
|
|
||||||
'source': 'package'},
|
|
||||||
'runtime_id': str(uuid.uuid4()),
|
|
||||||
'entry': 'main.main',
|
|
||||||
'project_id': str(uuid.uuid4()),
|
|
||||||
'created_at': '2018-07-26 09:00:00',
|
|
||||||
'updated_at': '2018-07-26 09:00:30',
|
|
||||||
'cpu': 100,
|
|
||||||
'memory_size': 33554432,
|
|
||||||
'timeout': 5
|
|
||||||
}
|
|
||||||
|
|
||||||
# Overwrite default attributes.
|
|
||||||
function_attrs.update(attrs)
|
|
||||||
|
|
||||||
function = fakes.FakeResource(info=copy.deepcopy(function_attrs),
|
|
||||||
loaded=True)
|
|
||||||
|
|
||||||
return function
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create_functions(attrs=None, count=2):
|
|
||||||
"""Create multiple fake functions.
|
|
||||||
|
|
||||||
:param Dictionary attrs:
|
|
||||||
A dictionary with all atrributes
|
|
||||||
:param int count:
|
|
||||||
The number of functions to fake
|
|
||||||
:return:
|
|
||||||
A list of FakeResource objects faking the functions.
|
|
||||||
"""
|
|
||||||
|
|
||||||
functions = []
|
|
||||||
for i in range(count):
|
|
||||||
functions.append(FakeFunction.create_one_function(attrs))
|
|
||||||
|
|
||||||
return functions
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_functions(functions=None, count=2):
|
|
||||||
"""Get an iterable Mock object with a list of faked functions.
|
|
||||||
|
|
||||||
If functions list is provided, then initialize the Mock object with the
|
|
||||||
list. Otherwise create one.
|
|
||||||
|
|
||||||
:param List functions:
|
|
||||||
A list of FakeResource faking functions
|
|
||||||
:param int count:
|
|
||||||
The number of functions to fake
|
|
||||||
:return:
|
|
||||||
An iterable Mock object with side_effect set to a list of faked
|
|
||||||
functions.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if functions is None:
|
|
||||||
functions = FakeFunction.create_functions(count=count)
|
|
||||||
|
|
||||||
return mock.Mock(side_effect=functions)
|
|
||||||
|
|
||||||
|
|
||||||
class FakeExecution(object):
|
|
||||||
"""Fake one or more function executions."""
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create_one_execution(attrs=None):
|
|
||||||
"""Create a fake function execution.
|
|
||||||
|
|
||||||
:param Dictionary attrs:
|
|
||||||
A dictionary with all atrributes
|
|
||||||
:return:
|
|
||||||
A FakeResource object, with id, function_id, etc.
|
|
||||||
"""
|
|
||||||
|
|
||||||
attrs = attrs or {}
|
|
||||||
# Set default attributes.
|
|
||||||
execution_attrs = {
|
|
||||||
'id': str(uuid.uuid4()),
|
|
||||||
'function_alias': None,
|
|
||||||
'function_id': str(uuid.uuid4()),
|
|
||||||
'function_version': 0,
|
|
||||||
'description': 'execution-description-' + uuid.uuid4().hex,
|
|
||||||
'input': '{"FAKE_INPUT_KEY": "FAKE_INPUT_VALUE"}',
|
|
||||||
'result': '{"duration": 1.234, "output": "FAKE_OUTPUT"}',
|
|
||||||
'status': 'success',
|
|
||||||
'sync': True,
|
|
||||||
'project_id': str(uuid.uuid4()),
|
|
||||||
'created_at': '2018-07-26 09:00:00',
|
|
||||||
'updated_at': '2018-07-26 09:00:30',
|
|
||||||
}
|
|
||||||
|
|
||||||
# Overwrite default attributes.
|
|
||||||
execution_attrs.update(attrs)
|
|
||||||
|
|
||||||
execution = fakes.FakeResource(info=copy.deepcopy(execution_attrs),
|
|
||||||
loaded=True)
|
|
||||||
|
|
||||||
return execution
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create_executions(attrs=None, count=2):
|
|
||||||
"""Create multiple fake function executions.
|
|
||||||
|
|
||||||
:param Dictionary attrs:
|
|
||||||
A dictionary with all atrributes
|
|
||||||
:param int count:
|
|
||||||
The number of function executions to fake
|
|
||||||
:return:
|
|
||||||
A list of FakeResource objects faking the function executions.
|
|
||||||
"""
|
|
||||||
|
|
||||||
executions = []
|
|
||||||
for i in range(count):
|
|
||||||
executions.append(FakeExecution.create_one_execution(attrs))
|
|
||||||
|
|
||||||
return executions
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_executions(executions=None, count=2):
|
|
||||||
"""Get an iterable Mock object with a list of faked executions.
|
|
||||||
|
|
||||||
If function executions list is provided, then initialize the Mock
|
|
||||||
object with the list. Otherwise create one.
|
|
||||||
|
|
||||||
:param List executions:
|
|
||||||
A list of FakeResource faking function executions
|
|
||||||
:param int count:
|
|
||||||
The number of function executions to fake
|
|
||||||
:return:
|
|
||||||
An iterable Mock object with side_effect set to a list of faked
|
|
||||||
function executions.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if executions is None:
|
|
||||||
executions = FakeExecution.create_executions(count=count)
|
|
||||||
|
|
||||||
return mock.Mock(side_effect=executions)
|
|
||||||
|
|
||||||
|
|
||||||
class FakeFunctionVersion(object):
|
|
||||||
"""Fake one or more function versions."""
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create_one_function_version(attrs=None):
|
|
||||||
"""Create a fake function version.
|
|
||||||
|
|
||||||
:param Dictionary attrs:
|
|
||||||
A dictionary with all atrributes
|
|
||||||
:return:
|
|
||||||
A FakeResource object, with id, function_id, etc.
|
|
||||||
"""
|
|
||||||
|
|
||||||
attrs = attrs or {}
|
|
||||||
# Set default attributes.
|
|
||||||
function_version_attrs = {
|
|
||||||
'id': str(uuid.uuid4()),
|
|
||||||
'function_id': str(uuid.uuid4()),
|
|
||||||
'description': 'function-version-description-' + uuid.uuid4().hex,
|
|
||||||
'version_number': 1,
|
|
||||||
'count': 0,
|
|
||||||
'project_id': str(uuid.uuid4()),
|
|
||||||
'created_at': '2018-07-26 09:00:00',
|
|
||||||
'updated_at': '2018-07-26 09:00:30',
|
|
||||||
}
|
|
||||||
|
|
||||||
# Overwrite default attributes.
|
|
||||||
function_version_attrs.update(attrs)
|
|
||||||
|
|
||||||
function_version = fakes.FakeResource(
|
|
||||||
info=copy.deepcopy(function_version_attrs),
|
|
||||||
loaded=True)
|
|
||||||
|
|
||||||
return function_version
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create_function_versions(attrs=None, count=2):
|
|
||||||
"""Create multiple fake function versions.
|
|
||||||
|
|
||||||
:param Dictionary attrs:
|
|
||||||
A dictionary with all atrributes
|
|
||||||
:param int count:
|
|
||||||
The number of function versions to fake
|
|
||||||
:return:
|
|
||||||
A list of FakeResource objects faking the function versions.
|
|
||||||
"""
|
|
||||||
|
|
||||||
function_versions = []
|
|
||||||
for i in range(count):
|
|
||||||
function_versions.append(
|
|
||||||
FakeFunctionVersion.create_one_function_version(attrs)
|
|
||||||
)
|
|
||||||
|
|
||||||
return function_versions
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_function_versions(function_versions=None, count=2):
|
|
||||||
"""Get an iterable Mock object with a list of faked function versions.
|
|
||||||
|
|
||||||
If function versions list is provided, then initialize the Mock
|
|
||||||
object with the list. Otherwise create one.
|
|
||||||
|
|
||||||
:param List function_versions:
|
|
||||||
A list of FakeResource faking function versions
|
|
||||||
:param int count:
|
|
||||||
The number of function versions to fake
|
|
||||||
:return:
|
|
||||||
An iterable Mock object with side_effect set to a list of faked
|
|
||||||
function versions.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if function_versions is None:
|
|
||||||
function_versions = FakeFunctionVersion.create_function_versions(
|
|
||||||
count=count
|
|
||||||
)
|
|
||||||
|
|
||||||
return mock.Mock(side_effect=function_versions)
|
|
||||||
|
|
||||||
|
|
||||||
class FakeFunctionWorker(object):
|
|
||||||
"""Fake one or more function workers."""
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create_one_function_worker(attrs=None):
|
|
||||||
"""Create a fake function worker.
|
|
||||||
|
|
||||||
:param Dictionary attrs:
|
|
||||||
A dictionary with all atrributes
|
|
||||||
:return:
|
|
||||||
A FakeResource object, with function_id, worker_name, etc.
|
|
||||||
"""
|
|
||||||
|
|
||||||
attrs = attrs or {}
|
|
||||||
# Set default attributes.
|
|
||||||
function_worker_attrs = {
|
|
||||||
'function_id': str(uuid.uuid4()),
|
|
||||||
'worker_name': 'worker-' + uuid.uuid4().hex,
|
|
||||||
}
|
|
||||||
|
|
||||||
# Overwrite default attributes.
|
|
||||||
function_worker_attrs.update(attrs)
|
|
||||||
|
|
||||||
function_worker = fakes.FakeResource(
|
|
||||||
info=copy.deepcopy(function_worker_attrs),
|
|
||||||
loaded=True)
|
|
||||||
|
|
||||||
return function_worker
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create_function_workers(attrs=None, count=2):
|
|
||||||
"""Create multiple fake function workers.
|
|
||||||
|
|
||||||
:param Dictionary attrs:
|
|
||||||
A dictionary with all atrributes
|
|
||||||
:param int count:
|
|
||||||
The number of function workers to fake
|
|
||||||
:return:
|
|
||||||
A list of FakeResource objects faking the function workers.
|
|
||||||
"""
|
|
||||||
|
|
||||||
function_workers = []
|
|
||||||
for i in range(count):
|
|
||||||
function_workers.append(
|
|
||||||
FakeFunctionWorker.create_one_function_worker(attrs)
|
|
||||||
)
|
|
||||||
|
|
||||||
return function_workers
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_function_workers(function_workers=None, count=2):
|
|
||||||
"""Get an iterable Mock object with a list of faked function workers.
|
|
||||||
|
|
||||||
If function workers list is provided, then initialize the Mock
|
|
||||||
object with the list. Otherwise create one.
|
|
||||||
|
|
||||||
:param List function_workers:
|
|
||||||
A list of FakeResource faking function workers
|
|
||||||
:param int count:
|
|
||||||
The number of function workers to fake
|
|
||||||
:return:
|
|
||||||
An iterable Mock object with side_effect set to a list of faked
|
|
||||||
function workers.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if function_workers is None:
|
|
||||||
function_workers = FakeFunctionWorker.create_function_workers(
|
|
||||||
count=count
|
|
||||||
)
|
|
||||||
|
|
||||||
return mock.Mock(side_effect=function_workers)
|
|
||||||
|
|
||||||
|
|
||||||
class FakeJob(object):
|
|
||||||
"""Fake one or more jobs."""
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create_one_job(attrs=None):
|
|
||||||
"""Create a fake job.
|
|
||||||
|
|
||||||
:param Dictionary attrs:
|
|
||||||
A dictionary with all attributes
|
|
||||||
:return:
|
|
||||||
A FakeResource object, with id, name, etc.
|
|
||||||
"""
|
|
||||||
|
|
||||||
attrs = attrs or {}
|
|
||||||
# Set default attributes.
|
|
||||||
job_attrs = {
|
|
||||||
'id': str(uuid.uuid4()),
|
|
||||||
'name': 'job-name-' + uuid.uuid4().hex,
|
|
||||||
'count': 3,
|
|
||||||
'status': 'RUNNING',
|
|
||||||
'function_alias': None,
|
|
||||||
'function_id': str(uuid.uuid4()),
|
|
||||||
'function_version': 0,
|
|
||||||
'function_input': '{"FAKE_INPUT_KEY": "FAKE_INPUT_VALUE"}',
|
|
||||||
'pattern': '0 * * * *', # Once per hour
|
|
||||||
'first_execution_time': '2018-08-08T08:00:00',
|
|
||||||
'next_execution_time': '2018-08-08T10:00:00',
|
|
||||||
'project_id': str(uuid.uuid4()),
|
|
||||||
'created_at': '2018-07-26 09:00:00',
|
|
||||||
'updated_at': '2018-07-26 09:00:30',
|
|
||||||
}
|
|
||||||
|
|
||||||
# Overwrite default attributes.
|
|
||||||
job_attrs.update(attrs)
|
|
||||||
|
|
||||||
job = fakes.FakeResource(info=copy.deepcopy(job_attrs), loaded=True)
|
|
||||||
return job
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create_jobs(attrs=None, count=2):
|
|
||||||
"""Create multiple fake jobs.
|
|
||||||
|
|
||||||
:param Dictionary attrs:
|
|
||||||
A dictionary with all attributes
|
|
||||||
:param int count:
|
|
||||||
The number of jobs to fake
|
|
||||||
:return:
|
|
||||||
A list of FakeResource objects faking the jobs.
|
|
||||||
"""
|
|
||||||
|
|
||||||
jobs = []
|
|
||||||
for i in range(count):
|
|
||||||
jobs.append(FakeJob.create_one_job(attrs))
|
|
||||||
|
|
||||||
return jobs
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_jobs(jobs=None, count=2):
|
|
||||||
"""Get an iterable mock object with a list of faked jobs.
|
|
||||||
|
|
||||||
If jobs list is provided, then initialize the Mock object with the
|
|
||||||
list. Otherwise create one.
|
|
||||||
|
|
||||||
:param List jobs:
|
|
||||||
A list of FakeResource faking jobs
|
|
||||||
:param int count:
|
|
||||||
The number of jobs to fake
|
|
||||||
:return:
|
|
||||||
An iterable Mock object with side_effect set to a list of faked
|
|
||||||
jobs.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if jobs is None:
|
|
||||||
jobs = FakeJob.create_jobs(count=count)
|
|
||||||
|
|
||||||
return mock.Mock(side_effect=jobs)
|
|
||||||
|
|
||||||
|
|
||||||
class FakeWebhook(object):
|
|
||||||
"""Fake one or more webhooks."""
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create_one_webhook(attrs=None):
|
|
||||||
"""Create a fake webhook.
|
|
||||||
|
|
||||||
:param Dictionary attrs:
|
|
||||||
A dictionary with all attributes
|
|
||||||
:return:
|
|
||||||
A FakeResource object, with id, function_id, etc.
|
|
||||||
"""
|
|
||||||
|
|
||||||
attrs = attrs or {}
|
|
||||||
# Set default attributes.
|
|
||||||
webhook_attrs = {
|
|
||||||
'id': str(uuid.uuid4()),
|
|
||||||
'function_alias': None,
|
|
||||||
'function_id': str(uuid.uuid4()),
|
|
||||||
'function_version': 0,
|
|
||||||
'description': 'webhook-description-' + uuid.uuid4().hex,
|
|
||||||
'project_id': str(uuid.uuid4()),
|
|
||||||
'created_at': '2018-07-26 09:00:00',
|
|
||||||
'updated_at': '2018-07-26 09:00:30',
|
|
||||||
'webhook_url': 'http://HOST:PORT/v1/webhooks/FAKE_ID/invoke',
|
|
||||||
}
|
|
||||||
|
|
||||||
# Overwrite default attributes.
|
|
||||||
webhook_attrs.update(attrs)
|
|
||||||
|
|
||||||
webhook = fakes.FakeResource(info=copy.deepcopy(webhook_attrs),
|
|
||||||
loaded=True)
|
|
||||||
return webhook
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create_webhooks(attrs=None, count=2):
|
|
||||||
"""Create multiple fake webhooks.
|
|
||||||
|
|
||||||
:param Dictionary attrs:
|
|
||||||
A dictionary with all attributes
|
|
||||||
:param int count:
|
|
||||||
The number of webhooks to fake
|
|
||||||
:return:
|
|
||||||
A list of FakeResource objects faking the webhooks.
|
|
||||||
"""
|
|
||||||
|
|
||||||
webhooks = []
|
|
||||||
for i in range(count):
|
|
||||||
webhooks.append(FakeWebhook.create_one_webhook(attrs))
|
|
||||||
|
|
||||||
return webhooks
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_webhooks(webhooks=None, count=2):
|
|
||||||
"""Get an iterable mock object with a list of faked webhooks.
|
|
||||||
|
|
||||||
If webhooks list is provided, then initialize the Mock object with the
|
|
||||||
list. Otherwise create one.
|
|
||||||
|
|
||||||
:param List webhooks:
|
|
||||||
A list of FakeResource faking webhooks
|
|
||||||
:param int count:
|
|
||||||
The number of webhooks to fake
|
|
||||||
:return:
|
|
||||||
An iterable Mock object with side_effect set to a list of faked
|
|
||||||
webhooks.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if webhooks is None:
|
|
||||||
webhooks = FakeWebhook.create_webhooks(count=count)
|
|
||||||
|
|
||||||
return mock.Mock(side_effect=webhooks)
|
|
||||||
|
|
||||||
|
|
||||||
class FakeFunctionAlias(object):
|
|
||||||
"""Fake one or more function aliases."""
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create_one_function_alias(attrs=None):
|
|
||||||
"""Create a fake function alias.
|
|
||||||
|
|
||||||
:param Dictionary attrs:
|
|
||||||
A dictionary with all attributes
|
|
||||||
:return:
|
|
||||||
A FakeResource object, with name, function_id, etc.
|
|
||||||
"""
|
|
||||||
|
|
||||||
attrs = attrs or {}
|
|
||||||
# Set default attributes
|
|
||||||
function_alias_attrs = {
|
|
||||||
'name': 'function-alias-name-' + uuid.uuid4().hex,
|
|
||||||
'function_id': str(uuid.uuid4()),
|
|
||||||
'description': 'function-alias-description-' + uuid.uuid4().hex,
|
|
||||||
'function_version': 0,
|
|
||||||
'project_id': str(uuid.uuid4()),
|
|
||||||
'created_at': '2018-07-26 09:00:00',
|
|
||||||
'updated_at': '2018-07-26 09:00:30',
|
|
||||||
}
|
|
||||||
|
|
||||||
# Overwrite default attributes
|
|
||||||
function_alias_attrs.update(attrs)
|
|
||||||
|
|
||||||
function_alias = fakes.FakeResource(
|
|
||||||
info=copy.deepcopy(function_alias_attrs), loaded=True)
|
|
||||||
return function_alias
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create_function_aliases(attrs=None, count=2):
|
|
||||||
"""Create multiple fake function aliases.
|
|
||||||
|
|
||||||
:param Dictionary attrs:
|
|
||||||
A dictionary with all attributes
|
|
||||||
:param int count:
|
|
||||||
The number of function aliases to fake
|
|
||||||
:return:
|
|
||||||
A list of FakeResource objects faking the function aliases.
|
|
||||||
"""
|
|
||||||
|
|
||||||
function_aliases = []
|
|
||||||
for i in range(count):
|
|
||||||
function_aliases.append(
|
|
||||||
FakeFunctionAlias.create_one_function_alias(attrs)
|
|
||||||
)
|
|
||||||
|
|
||||||
return function_aliases
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_function_aliases(function_aliases=None, count=2):
|
|
||||||
"""Get an iterable mock object with a list of faked function aliases.
|
|
||||||
|
|
||||||
If function aliases list is provided, then initialize the Mock object
|
|
||||||
with the list. Otherwise create one.
|
|
||||||
|
|
||||||
:param List function_aliases:
|
|
||||||
A list of FakeResource faking function aliases
|
|
||||||
:param int count:
|
|
||||||
The number of function aliases to fake
|
|
||||||
:return
|
|
||||||
An iterable Mock object with side_effect set to a list of faked
|
|
||||||
function aliases.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if function_aliases is None:
|
|
||||||
function_aliases = FakeFunctionAlias.create_function_aliases(
|
|
||||||
count=count
|
|
||||||
)
|
|
||||||
|
|
||||||
return mock.Mock(side_effect=function_aliases)
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,404 +0,0 @@
|
||||||
# Copyright 2018 AWCloud Software 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 unittest import mock
|
|
||||||
|
|
||||||
from osc_lib.tests import utils as osc_tests_utils
|
|
||||||
|
|
||||||
from qinlingclient.common import exceptions
|
|
||||||
from qinlingclient.osc.v1 import base
|
|
||||||
from qinlingclient.osc.v1 import function_alias
|
|
||||||
from qinlingclient.tests.unit.osc.v1 import fakes
|
|
||||||
|
|
||||||
|
|
||||||
class TestFunctionAlias(fakes.TestQinlingClient):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestFunctionAlias, self).setUp()
|
|
||||||
# Get a shortcut
|
|
||||||
self.client = self.app.client_manager.function_engine
|
|
||||||
|
|
||||||
self.columns = base.FUNCTION_ALIAS_COLUMNS
|
|
||||||
self.data = []
|
|
||||||
|
|
||||||
aliases = fakes.FakeFunctionAlias.create_function_aliases(count=3)
|
|
||||||
self._function_aliases = aliases
|
|
||||||
for a in self._function_aliases:
|
|
||||||
self.data.append((a.name, a.function_id, a.description,
|
|
||||||
a.function_version, a.project_id,
|
|
||||||
a.created_at, a.updated_at))
|
|
||||||
|
|
||||||
|
|
||||||
class TestListFunctionAlias(TestFunctionAlias):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestListFunctionAlias, self).setUp()
|
|
||||||
self.cmd = function_alias.List(self.app, None)
|
|
||||||
|
|
||||||
self.columns = [c.capitalize() for c in base.FUNCTION_ALIAS_COLUMNS]
|
|
||||||
|
|
||||||
self.client.function_aliases.list = mock.Mock(
|
|
||||||
return_value=self._function_aliases
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_function_alias_list(self):
|
|
||||||
arglist = []
|
|
||||||
verifylist = []
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.client.function_aliases.list.assert_called_once_with()
|
|
||||||
self.assertEqual(self.columns, columns)
|
|
||||||
self.assertEqual(self.data, list(data))
|
|
||||||
|
|
||||||
def test_function_alias_list_with_filter(self):
|
|
||||||
arglist = ['--filter', 'name=has:alias',
|
|
||||||
'--filter', 'function_id=has:900dcafe']
|
|
||||||
verifylist = [
|
|
||||||
('filters', ['name=has:alias', 'function_id=has:900dcafe']),
|
|
||||||
]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.client.function_aliases.list.assert_called_once_with(
|
|
||||||
name='has:alias', function_id='has:900dcafe'
|
|
||||||
)
|
|
||||||
self.assertEqual(self.columns, columns)
|
|
||||||
self.assertEqual(self.data, list(data))
|
|
||||||
|
|
||||||
def test_function_alias_list_with_invalid_filter(self):
|
|
||||||
arglist = ['--filter', 'function_version']
|
|
||||||
verifylist = [
|
|
||||||
('filters', ['function_version']),
|
|
||||||
]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
self.assertRaisesRegex(
|
|
||||||
ValueError,
|
|
||||||
'^Invalid filter: function_version$',
|
|
||||||
self.cmd.take_action, parsed_args
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TestCreateFunctionAlias(TestFunctionAlias):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestCreateFunctionAlias, self).setUp()
|
|
||||||
self.cmd = function_alias.Create(self.app, None)
|
|
||||||
|
|
||||||
def _create_fake_function_alias(self, attrs=None):
|
|
||||||
# Allow to fake different create results
|
|
||||||
a = fakes.FakeFunctionAlias.create_one_function_alias(attrs)
|
|
||||||
self.client.function_aliases.create = mock.Mock(return_value=a)
|
|
||||||
data = (a.name, a.function_id, a.description, a.function_version,
|
|
||||||
a.project_id, a.created_at, a.updated_at)
|
|
||||||
return data
|
|
||||||
|
|
||||||
def test_function_alias_create_no_option(self):
|
|
||||||
arglist = []
|
|
||||||
verifylist = []
|
|
||||||
|
|
||||||
self.assertRaises(osc_tests_utils.ParserException,
|
|
||||||
self.check_parser,
|
|
||||||
self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
def test_function_alias_create_required_options(self):
|
|
||||||
"""Create a function alias.
|
|
||||||
|
|
||||||
1. use function_id,
|
|
||||||
2. all other params except the required ones are not set.
|
|
||||||
"""
|
|
||||||
alias_name = 'FAKE_ALIAS_NAME'
|
|
||||||
function_id = self._function_aliases[0].function_id
|
|
||||||
attrs = {'name': alias_name, 'function_id': function_id}
|
|
||||||
created_data = self._create_fake_function_alias(attrs)
|
|
||||||
|
|
||||||
arglist = [alias_name, '--function', function_id]
|
|
||||||
verifylist = [
|
|
||||||
('name', alias_name),
|
|
||||||
('function', function_id),
|
|
||||||
('function_version', 0),
|
|
||||||
('description', ''),
|
|
||||||
]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.client.function_aliases.create.assert_called_once_with(
|
|
||||||
alias_name,
|
|
||||||
**{'function_id': function_id,
|
|
||||||
'function_version': 0,
|
|
||||||
'description': ''}
|
|
||||||
)
|
|
||||||
self.assertEqual(self.columns, columns)
|
|
||||||
self.assertEqual(created_data, data)
|
|
||||||
|
|
||||||
def test_function_alias_create_all_options(self):
|
|
||||||
"""Create a function alias.
|
|
||||||
|
|
||||||
1. use function name to find the function_id,
|
|
||||||
2. all optional params are specified.
|
|
||||||
"""
|
|
||||||
alias_name = 'FAKE_ALIAS_NAME'
|
|
||||||
function = fakes.FakeFunction.create_one_function()
|
|
||||||
function_name = function.name
|
|
||||||
function_id = function.id
|
|
||||||
function_version = 1
|
|
||||||
alias_description = 'This is a newly created function alias.'
|
|
||||||
attrs = {'name': alias_name,
|
|
||||||
'function_id': function_id,
|
|
||||||
'function_version': function_version,
|
|
||||||
'description': alias_description}
|
|
||||||
created_data = self._create_fake_function_alias(attrs)
|
|
||||||
|
|
||||||
# Use to find the function id with its name
|
|
||||||
self.client.functions.find.return_value = function
|
|
||||||
|
|
||||||
arglist = [alias_name,
|
|
||||||
'--function', function_name,
|
|
||||||
'--function-version', str(function_version),
|
|
||||||
'--description', alias_description]
|
|
||||||
verifylist = [
|
|
||||||
('name', alias_name),
|
|
||||||
('function', function_name),
|
|
||||||
('function_version', function_version),
|
|
||||||
('description', alias_description),
|
|
||||||
]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.client.function_aliases.create.assert_called_once_with(
|
|
||||||
alias_name,
|
|
||||||
**{'function_id': function_id,
|
|
||||||
'function_version': function_version,
|
|
||||||
'description': alias_description}
|
|
||||||
)
|
|
||||||
self.assertEqual(self.columns, columns)
|
|
||||||
self.assertEqual(created_data, data)
|
|
||||||
|
|
||||||
self.client.functions.find.assert_called_once_with(name=function_name)
|
|
||||||
|
|
||||||
def test_function_alias_create_version_not_integer(self):
|
|
||||||
# function_version should be an integer value
|
|
||||||
alias_name = 'FAKE_ALIAS_NAME'
|
|
||||||
function_id = self._function_aliases[0].function_id
|
|
||||||
|
|
||||||
arglist = [alias_name,
|
|
||||||
'--function', function_id,
|
|
||||||
'--function-version', 'NOT_A_INTEGER']
|
|
||||||
verifylist = [
|
|
||||||
('name', alias_name),
|
|
||||||
('function', function_id),
|
|
||||||
('function_version', 0),
|
|
||||||
('description', None),
|
|
||||||
]
|
|
||||||
|
|
||||||
self.assertRaises(osc_tests_utils.ParserException,
|
|
||||||
self.check_parser,
|
|
||||||
self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
|
|
||||||
class TestDeleteFunctionAlias(TestFunctionAlias):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestDeleteFunctionAlias, self).setUp()
|
|
||||||
self.cmd = function_alias.Delete(self.app, None)
|
|
||||||
self.client.function_aliases.delete = mock.Mock(return_value=None)
|
|
||||||
|
|
||||||
def test_function_alias_delete_no_option(self):
|
|
||||||
arglist = []
|
|
||||||
verifylist = []
|
|
||||||
|
|
||||||
self.assertRaises(osc_tests_utils.ParserException,
|
|
||||||
self.check_parser,
|
|
||||||
self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
def test_function_alias_delete_one(self):
|
|
||||||
alias_name = self._function_aliases[0].name
|
|
||||||
arglist = [alias_name]
|
|
||||||
verifylist = [('name', [alias_name])]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
result = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.assertIsNone(result)
|
|
||||||
self.client.function_aliases.delete.assert_called_once_with(alias_name)
|
|
||||||
|
|
||||||
def test_function_alias_delete_multiple(self):
|
|
||||||
alias_names = [a.name for a in self._function_aliases]
|
|
||||||
arglist = alias_names
|
|
||||||
verifylist = [('name', alias_names)]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
result = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.assertIsNone(result)
|
|
||||||
calls = [mock.call(a_name) for a_name in alias_names]
|
|
||||||
self.assertEqual(len(alias_names),
|
|
||||||
self.client.function_aliases.delete.call_count)
|
|
||||||
self.client.function_aliases.delete.assert_has_calls(calls)
|
|
||||||
|
|
||||||
def test_function_alias_delete_multiple_exception(self):
|
|
||||||
alias_names = [a.name for a in self._function_aliases]
|
|
||||||
arglist = alias_names
|
|
||||||
verifylist = [('name', alias_names)]
|
|
||||||
|
|
||||||
self.client.function_aliases.delete = mock.Mock(side_effect=[
|
|
||||||
None, RuntimeError, None
|
|
||||||
])
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
self.assertRaisesRegex(
|
|
||||||
exceptions.QinlingClientException,
|
|
||||||
r'^Unable to delete the specified function_alias\(s\)\.$',
|
|
||||||
self.cmd.take_action, parsed_args)
|
|
||||||
|
|
||||||
# The second deleteion failed, but the third is done normally
|
|
||||||
calls = [mock.call(a_name) for a_name in alias_names]
|
|
||||||
self.assertEqual(len(alias_names),
|
|
||||||
self.client.function_aliases.delete.call_count)
|
|
||||||
self.client.function_aliases.delete.assert_has_calls(calls)
|
|
||||||
|
|
||||||
|
|
||||||
class TestShowFunctionAlias(TestFunctionAlias):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestShowFunctionAlias, self).setUp()
|
|
||||||
self.cmd = function_alias.Show(self.app, None)
|
|
||||||
self.client.function_aliases.get = mock.Mock(
|
|
||||||
return_value=self._function_aliases[0])
|
|
||||||
|
|
||||||
def test_function_alias_show_no_option(self):
|
|
||||||
arglist = []
|
|
||||||
verifylist = []
|
|
||||||
|
|
||||||
self.assertRaises(osc_tests_utils.ParserException,
|
|
||||||
self.check_parser,
|
|
||||||
self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
def test_function_alias_show(self):
|
|
||||||
alias_name = self._function_aliases[0].name
|
|
||||||
arglist = [alias_name]
|
|
||||||
verifylist = [('name', alias_name)]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.client.function_aliases.get.assert_called_once_with(alias_name)
|
|
||||||
self.assertEqual(self.columns, columns)
|
|
||||||
self.assertEqual(self.data[0], data)
|
|
||||||
|
|
||||||
|
|
||||||
class TestUpdateFunctionAlias(TestFunctionAlias):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestUpdateFunctionAlias, self).setUp()
|
|
||||||
self.cmd = function_alias.Update(self.app, None)
|
|
||||||
|
|
||||||
def _update_fake_function_alias(self, attrs=None):
|
|
||||||
# Allow to fake different update results
|
|
||||||
a = fakes.FakeFunctionAlias.create_one_function_alias(attrs)
|
|
||||||
self.client.function_aliases.update = mock.Mock(return_value=a)
|
|
||||||
data = (a.name, a.function_id, a.description, a.function_version,
|
|
||||||
a.project_id, a.created_at, a.updated_at)
|
|
||||||
return data
|
|
||||||
|
|
||||||
def test_function_alias_update_no_option(self):
|
|
||||||
arglist = []
|
|
||||||
verifylist = []
|
|
||||||
|
|
||||||
self.assertRaises(osc_tests_utils.ParserException,
|
|
||||||
self.check_parser,
|
|
||||||
self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
def test_function_alias_update_required_options(self):
|
|
||||||
"""Update a function alias.
|
|
||||||
|
|
||||||
Do nothing as only the alias name is specified.
|
|
||||||
"""
|
|
||||||
alias_name = self._function_aliases[0].name
|
|
||||||
attrs = {'name': alias_name}
|
|
||||||
updated_data = self._update_fake_function_alias(attrs)
|
|
||||||
|
|
||||||
arglist = [alias_name]
|
|
||||||
verifylist = [
|
|
||||||
('name', alias_name),
|
|
||||||
('function', None),
|
|
||||||
('function_version', None),
|
|
||||||
('description', None),
|
|
||||||
]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.client.function_aliases.update.assert_called_once_with(
|
|
||||||
alias_name,
|
|
||||||
**{'function_id': None,
|
|
||||||
'function_version': None,
|
|
||||||
'description': None}
|
|
||||||
)
|
|
||||||
self.assertEqual(self.columns, columns)
|
|
||||||
self.assertEqual(updated_data, data)
|
|
||||||
|
|
||||||
def test_function_alias_update_all_options(self):
|
|
||||||
"""Update a function alias.
|
|
||||||
|
|
||||||
use function name to find the function_id,
|
|
||||||
"""
|
|
||||||
alias_name = self._function_aliases[0].name
|
|
||||||
function = fakes.FakeFunction.create_one_function()
|
|
||||||
function_name = function.name
|
|
||||||
function_id = function.id
|
|
||||||
function_version = 1
|
|
||||||
alias_description = 'This is a updated function alias.'
|
|
||||||
attrs = {'name': alias_name,
|
|
||||||
'function_id': function_id,
|
|
||||||
'function_version': function_version,
|
|
||||||
'description': alias_description}
|
|
||||||
created_data = self._update_fake_function_alias(attrs)
|
|
||||||
|
|
||||||
# Use to find the function id with its name
|
|
||||||
self.client.functions.find.return_value = function
|
|
||||||
|
|
||||||
arglist = [alias_name,
|
|
||||||
'--function', function_name,
|
|
||||||
'--function-version', str(function_version),
|
|
||||||
'--description', alias_description]
|
|
||||||
verifylist = [
|
|
||||||
('name', alias_name),
|
|
||||||
('function', function_name),
|
|
||||||
('function_version', str(function_version)),
|
|
||||||
('description', alias_description),
|
|
||||||
]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.client.function_aliases.update.assert_called_once_with(
|
|
||||||
alias_name,
|
|
||||||
**{'function_id': function_id,
|
|
||||||
'function_version': str(function_version),
|
|
||||||
'description': alias_description}
|
|
||||||
)
|
|
||||||
self.assertEqual(self.columns, columns)
|
|
||||||
self.assertEqual(created_data, data)
|
|
||||||
|
|
||||||
self.client.functions.find.assert_called_once_with(name=function_name)
|
|
|
@ -1,438 +0,0 @@
|
||||||
# Copyright 2018 AWCloud Software 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 unittest import mock
|
|
||||||
|
|
||||||
from osc_lib.tests import utils as osc_tests_utils
|
|
||||||
|
|
||||||
from qinlingclient.common import exceptions
|
|
||||||
from qinlingclient.osc.v1 import base
|
|
||||||
from qinlingclient.osc.v1 import function_execution
|
|
||||||
from qinlingclient.tests.unit.osc.v1 import fakes
|
|
||||||
|
|
||||||
|
|
||||||
class TestFunctionExecution(fakes.TestQinlingClient):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestFunctionExecution, self).setUp()
|
|
||||||
# Get a shortcut
|
|
||||||
self.client = self.app.client_manager.function_engine
|
|
||||||
|
|
||||||
self.columns = base.EXECUTION_COLUMNS
|
|
||||||
self.data = []
|
|
||||||
|
|
||||||
self._executions = fakes.FakeExecution.create_executions(count=3)
|
|
||||||
for e in self._executions:
|
|
||||||
self.data.append((e.id, e.function_alias, e.function_id,
|
|
||||||
e.function_version, e.description, e.input,
|
|
||||||
e.result, e.status, e.sync, e.project_id,
|
|
||||||
e.created_at, e.updated_at))
|
|
||||||
|
|
||||||
|
|
||||||
class TestListFunctionExecution(TestFunctionExecution):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestListFunctionExecution, self).setUp()
|
|
||||||
self.cmd = function_execution.List(self.app, None)
|
|
||||||
|
|
||||||
self.columns = [c.capitalize() for c in base.EXECUTION_COLUMNS]
|
|
||||||
|
|
||||||
self.client.function_executions.list = mock.Mock(
|
|
||||||
return_value=self._executions)
|
|
||||||
|
|
||||||
def test_function_execution_list(self):
|
|
||||||
arglist = []
|
|
||||||
verifylist = []
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.client.function_executions.list.assert_called_once_with()
|
|
||||||
self.assertEqual(self.columns, columns)
|
|
||||||
self.assertEqual(self.data, list(data))
|
|
||||||
|
|
||||||
def test_function_execution_list_with_filter(self):
|
|
||||||
arglist = ['--filter', 'description=has:execution',
|
|
||||||
'--filter', 'status=eq:success']
|
|
||||||
verifylist = [
|
|
||||||
('filters', ['description=has:execution', 'status=eq:success'])
|
|
||||||
]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.client.function_executions.list.assert_called_once_with(
|
|
||||||
description='has:execution', status='eq:success'
|
|
||||||
)
|
|
||||||
self.assertEqual(self.columns, columns)
|
|
||||||
self.assertEqual(self.data, list(data))
|
|
||||||
|
|
||||||
def test_function_execution_list_with_invalid_filter(self):
|
|
||||||
arglist = ['--filter', 'function_id']
|
|
||||||
verifylist = [
|
|
||||||
('filters', ['function_id'])
|
|
||||||
]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
self.assertRaisesRegex(
|
|
||||||
ValueError,
|
|
||||||
'^Invalid filter: function_id$',
|
|
||||||
self.cmd.take_action, parsed_args
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TestCreateFunctionExecution(TestFunctionExecution):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestCreateFunctionExecution, self).setUp()
|
|
||||||
self.cmd = function_execution.Create(self.app, None)
|
|
||||||
|
|
||||||
def _create_fake_execution(self, attrs=None):
|
|
||||||
# Allow to fake different create results
|
|
||||||
e = fakes.FakeExecution.create_one_execution(attrs)
|
|
||||||
self.client.function_executions.create = mock.Mock(return_value=e)
|
|
||||||
data = (e.id, e.function_alias, e.function_id, e.function_version,
|
|
||||||
e.description, e.input, e.result, e.status, e.sync,
|
|
||||||
e.project_id, e.created_at, e.updated_at)
|
|
||||||
return data
|
|
||||||
|
|
||||||
def test_function_execution_create_function_id(self):
|
|
||||||
"""Create a function execution with function id."""
|
|
||||||
function_id = self._executions[0].function_id
|
|
||||||
attrs = {'function_id': function_id}
|
|
||||||
created_data = self._create_fake_execution(attrs)
|
|
||||||
|
|
||||||
arglist = ['--function', function_id]
|
|
||||||
verifylist = [
|
|
||||||
('function', function_id),
|
|
||||||
('function_version', 0),
|
|
||||||
('function_alias', None),
|
|
||||||
('input', None),
|
|
||||||
('sync', True),
|
|
||||||
]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.client.function_executions.create.assert_called_once_with(
|
|
||||||
**{'function_alias': None,
|
|
||||||
'function_id': function_id,
|
|
||||||
'function_version': 0,
|
|
||||||
'sync': True,
|
|
||||||
'input': None}
|
|
||||||
)
|
|
||||||
self.assertEqual(self.columns, columns)
|
|
||||||
self.assertEqual(created_data, data)
|
|
||||||
|
|
||||||
def test_function_execution_create_function_name(self):
|
|
||||||
"""Create a function execution.
|
|
||||||
|
|
||||||
1. use function name to find the function_id,
|
|
||||||
2. all optional params are specified.
|
|
||||||
"""
|
|
||||||
function = fakes.FakeFunction.create_one_function()
|
|
||||||
function_name = function.name
|
|
||||||
function_id = function.id
|
|
||||||
function_version = 1
|
|
||||||
function_input = '{"JSON_INPUT_KEY": "JSON_INPUT_VALUE"}'
|
|
||||||
is_sync = False
|
|
||||||
attrs = {'function_id': function_id,
|
|
||||||
'function_version': function_version,
|
|
||||||
'input': function_input,
|
|
||||||
'sync': is_sync}
|
|
||||||
created_data = self._create_fake_execution(attrs)
|
|
||||||
|
|
||||||
# Use to find the function id with its name
|
|
||||||
self.client.functions.find.return_value = function
|
|
||||||
|
|
||||||
arglist = ['--function', function_name, '--function-version',
|
|
||||||
str(function_version), '--input', function_input, '--async']
|
|
||||||
verifylist = [
|
|
||||||
('function', function_name),
|
|
||||||
('function_version', function_version),
|
|
||||||
('function_alias', None),
|
|
||||||
('input', function_input),
|
|
||||||
('sync', is_sync),
|
|
||||||
]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.client.function_executions.create.assert_called_once_with(
|
|
||||||
**{'function_alias': None,
|
|
||||||
'function_id': function_id,
|
|
||||||
'function_version': function_version,
|
|
||||||
'sync': is_sync,
|
|
||||||
'input': function_input}
|
|
||||||
)
|
|
||||||
self.assertEqual(self.columns, columns)
|
|
||||||
self.assertEqual(created_data, data)
|
|
||||||
|
|
||||||
self.client.functions.find.assert_called_once_with(name=function_name)
|
|
||||||
|
|
||||||
def test_function_execution_create_function_alias(self):
|
|
||||||
"""Create a function execution with function alias."""
|
|
||||||
function_alias = 'fake_alias'
|
|
||||||
function_input = '{"JSON_INPUT_KEY": "JSON_INPUT_VALUE"}'
|
|
||||||
is_sync = False
|
|
||||||
attrs = {'function_alias': function_alias,
|
|
||||||
'input': function_input,
|
|
||||||
'sync': is_sync}
|
|
||||||
created_data = self._create_fake_execution(attrs)
|
|
||||||
|
|
||||||
arglist = ['--function-alias', function_alias,
|
|
||||||
'--input', function_input, '--async']
|
|
||||||
verifylist = [
|
|
||||||
('function', None),
|
|
||||||
('function_version', 0),
|
|
||||||
('function_alias', function_alias),
|
|
||||||
('input', function_input),
|
|
||||||
('sync', is_sync),
|
|
||||||
]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.client.function_executions.create.assert_called_once_with(
|
|
||||||
**{'function_alias': function_alias,
|
|
||||||
'function_id': None,
|
|
||||||
'function_version': None,
|
|
||||||
'input': function_input,
|
|
||||||
'sync': is_sync}
|
|
||||||
)
|
|
||||||
self.assertEqual(self.columns, columns)
|
|
||||||
self.assertEqual(created_data, data)
|
|
||||||
|
|
||||||
def test_function_execution_create_sync_async_mutually_exclusive(self):
|
|
||||||
function_id = self._executions[0].function_id
|
|
||||||
# --sync and --async are mutually exclusive
|
|
||||||
arglist = ['--function', function_id, '--sync', '--async']
|
|
||||||
verifylist = [
|
|
||||||
('function', self._executions[0].function_id),
|
|
||||||
('function_version', 0),
|
|
||||||
('input', None),
|
|
||||||
('sync', True),
|
|
||||||
]
|
|
||||||
|
|
||||||
self.assertRaises(osc_tests_utils.ParserException,
|
|
||||||
self.check_parser,
|
|
||||||
self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
def test_function_execution_create_version_not_integer(self):
|
|
||||||
# function_version should be an integer value
|
|
||||||
function_id = self._executions[0].function_id
|
|
||||||
arglist = ['--function', function_id,
|
|
||||||
'--function-version', 'NOT_A_INTEGER']
|
|
||||||
verifylist = [
|
|
||||||
('function', function_id),
|
|
||||||
('function_version', 0),
|
|
||||||
('input', None),
|
|
||||||
('sync', True),
|
|
||||||
]
|
|
||||||
|
|
||||||
self.assertRaises(osc_tests_utils.ParserException,
|
|
||||||
self.check_parser,
|
|
||||||
self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
|
|
||||||
class TestDeleteFunctionExecution(TestFunctionExecution):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestDeleteFunctionExecution, self).setUp()
|
|
||||||
self.cmd = function_execution.Delete(self.app, None)
|
|
||||||
self.client.function_executions.delete = mock.Mock(return_value=None)
|
|
||||||
|
|
||||||
def test_function_execution_delete_no_option(self):
|
|
||||||
# Unlike other resources, function_execution.Delete does nothing when
|
|
||||||
# no arguments are specified.
|
|
||||||
arglist = []
|
|
||||||
verifylist = []
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
result = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.assertIsNone(result)
|
|
||||||
self.client.function_executions.delete.assert_not_called()
|
|
||||||
|
|
||||||
def test_function_execution_delete_one(self):
|
|
||||||
execution_id = self._executions[0].id
|
|
||||||
arglist = ['--execution', execution_id]
|
|
||||||
verifylist = [('execution', [execution_id]), ('function', None)]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
result = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.assertIsNone(result)
|
|
||||||
self.client.function_executions.delete.assert_called_once_with(
|
|
||||||
execution_id
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_function_execution_delete_multiple(self):
|
|
||||||
execution_ids = [e.id for e in self._executions]
|
|
||||||
arglist = ['--execution'] + execution_ids
|
|
||||||
verifylist = [('execution', execution_ids), ('function', None)]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
result = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.assertIsNone(result)
|
|
||||||
calls = [mock.call(e_id) for e_id in execution_ids]
|
|
||||||
self.assertEqual(len(execution_ids),
|
|
||||||
self.client.function_executions.delete.call_count)
|
|
||||||
self.client.function_executions.delete.assert_has_calls(calls)
|
|
||||||
|
|
||||||
def test_function_execution_delete_multiple_exception(self):
|
|
||||||
execution_ids = [e.id for e in self._executions]
|
|
||||||
arglist = ['--execution'] + execution_ids
|
|
||||||
verifylist = [('execution', execution_ids), ('function', None)]
|
|
||||||
|
|
||||||
self.client.function_executions.delete = mock.Mock(side_effect=[
|
|
||||||
None, RuntimeError, None
|
|
||||||
])
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
self.assertRaisesRegex(
|
|
||||||
exceptions.QinlingClientException,
|
|
||||||
r'^Unable to delete the specified execution\(s\)\.$',
|
|
||||||
self.cmd.take_action, parsed_args)
|
|
||||||
|
|
||||||
# The second deletion failed, but the third is done normally
|
|
||||||
calls = [mock.call(e_id) for e_id in execution_ids]
|
|
||||||
self.assertEqual(len(execution_ids),
|
|
||||||
self.client.function_executions.delete.call_count)
|
|
||||||
self.client.function_executions.delete.assert_has_calls(calls)
|
|
||||||
|
|
||||||
def test_function_execution_delete_by_function_id(self):
|
|
||||||
execution_id = self._executions[0].id
|
|
||||||
function_id = self._executions[0].function_id
|
|
||||||
|
|
||||||
self.client.function_executions.list.return_value = [
|
|
||||||
self._executions[0]
|
|
||||||
]
|
|
||||||
|
|
||||||
arglist = ['--function', function_id]
|
|
||||||
verifylist = [('execution', None), ('function', [function_id])]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
result = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.assertIsNone(result)
|
|
||||||
self.client.function_executions.delete.assert_called_once_with(
|
|
||||||
execution_id
|
|
||||||
)
|
|
||||||
self.client.function_executions.list.assert_called_once_with(
|
|
||||||
function_id=function_id
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_function_execution_delete_execution_function_mutually_exclusive(
|
|
||||||
self
|
|
||||||
):
|
|
||||||
# --execution and --function are mutually exclusive
|
|
||||||
execution_id = self._executions[0].id
|
|
||||||
function_id = self._executions[0].function_id
|
|
||||||
|
|
||||||
arglist = ['--execution', execution_id, '--function', function_id]
|
|
||||||
verifylist = [('execution', [execution_id]),
|
|
||||||
('function', [function_id])]
|
|
||||||
|
|
||||||
self.assertRaises(osc_tests_utils.ParserException,
|
|
||||||
self.check_parser,
|
|
||||||
self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
|
|
||||||
class TestShowFunctionExecution(TestFunctionExecution):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestShowFunctionExecution, self).setUp()
|
|
||||||
self.cmd = function_execution.Show(self.app, None)
|
|
||||||
self.client.function_executions.get = mock.Mock(
|
|
||||||
return_value=self._executions[0]
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_function_execution_show_no_option(self):
|
|
||||||
arglist = []
|
|
||||||
verifylist = []
|
|
||||||
|
|
||||||
self.assertRaises(osc_tests_utils.ParserException,
|
|
||||||
self.check_parser,
|
|
||||||
self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
def test_function_execution_show(self):
|
|
||||||
execution_id = self._executions[0].id
|
|
||||||
arglist = [execution_id]
|
|
||||||
verifylist = [('execution', execution_id)]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.client.function_executions.get.assert_called_once_with(
|
|
||||||
execution_id
|
|
||||||
)
|
|
||||||
self.assertEqual(self.columns, columns)
|
|
||||||
self.assertEqual(self.data[0], data)
|
|
||||||
|
|
||||||
|
|
||||||
class TestLogShowFunctionExecution(TestFunctionExecution):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestLogShowFunctionExecution, self).setUp()
|
|
||||||
self.cmd = function_execution.LogShow(self.app, None)
|
|
||||||
self.app_stdout_write = mock.Mock()
|
|
||||||
self.app.stdout.write = self.app_stdout_write
|
|
||||||
|
|
||||||
def test_function_execution_log_show_no_option(self):
|
|
||||||
arglist = []
|
|
||||||
verifylist = []
|
|
||||||
|
|
||||||
self.assertRaises(osc_tests_utils.ParserException,
|
|
||||||
self.check_parser,
|
|
||||||
self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
def test_function_execution_log_show(self):
|
|
||||||
execution_id = self._executions[0].id
|
|
||||||
fake_log = 'This is a fake log of an execution.'
|
|
||||||
|
|
||||||
self.client.function_executions.get_log.return_value = fake_log
|
|
||||||
|
|
||||||
arglist = [execution_id]
|
|
||||||
verifylist = [('execution', execution_id)]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
result = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.assertIsNone(result)
|
|
||||||
self.client.function_executions.get_log.assert_called_once_with(
|
|
||||||
execution_id
|
|
||||||
)
|
|
||||||
self.app_stdout_write.assert_called_once_with(fake_log)
|
|
||||||
|
|
||||||
def test_function_execution_log_show_empty_log(self):
|
|
||||||
execution_id = self._executions[0].id
|
|
||||||
|
|
||||||
self.client.function_executions.get_log.return_value = ''
|
|
||||||
|
|
||||||
arglist = [execution_id]
|
|
||||||
verifylist = [('execution', execution_id)]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
result = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.assertIsNone(result)
|
|
||||||
self.client.function_executions.get_log.assert_called_once_with(
|
|
||||||
execution_id
|
|
||||||
)
|
|
||||||
self.app_stdout_write.assert_called_once_with("\n")
|
|
|
@ -1,288 +0,0 @@
|
||||||
# Copyright 2018 AWCloud Software 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 unittest import mock
|
|
||||||
|
|
||||||
from osc_lib.tests import utils as osc_tests_utils
|
|
||||||
|
|
||||||
from qinlingclient.common import exceptions
|
|
||||||
from qinlingclient.osc.v1 import base
|
|
||||||
from qinlingclient.osc.v1 import function_version
|
|
||||||
from qinlingclient.tests.unit.osc.v1 import fakes
|
|
||||||
|
|
||||||
|
|
||||||
class TestFunctionVersion(fakes.TestQinlingClient):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestFunctionVersion, self).setUp()
|
|
||||||
# Get a shortcut
|
|
||||||
self.client = self.app.client_manager.function_engine
|
|
||||||
|
|
||||||
self.columns = base.FUNCTION_VERSION_COLUMNS
|
|
||||||
self.data = []
|
|
||||||
|
|
||||||
versions = fakes.FakeFunctionVersion.create_function_versions(count=3)
|
|
||||||
self._function_versions = versions
|
|
||||||
for v in self._function_versions:
|
|
||||||
self.data.append((v.id, v.function_id, v.description,
|
|
||||||
v.version_number, v.count, v.project_id,
|
|
||||||
v.created_at, v.updated_at))
|
|
||||||
|
|
||||||
|
|
||||||
class TestListFunctionVersion(TestFunctionVersion):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestListFunctionVersion, self).setUp()
|
|
||||||
self.cmd = function_version.List(self.app, None)
|
|
||||||
|
|
||||||
self.columns = [c.capitalize() for c in base.FUNCTION_VERSION_COLUMNS]
|
|
||||||
|
|
||||||
self.client.function_versions.list = mock.Mock(
|
|
||||||
return_value=self._function_versions)
|
|
||||||
|
|
||||||
def test_function_version_list_no_option(self):
|
|
||||||
arglist = []
|
|
||||||
verifylist = []
|
|
||||||
|
|
||||||
self.assertRaises(osc_tests_utils.ParserException,
|
|
||||||
self.check_parser,
|
|
||||||
self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
def test_function_version_list(self):
|
|
||||||
function_id = self._function_versions[0].function_id
|
|
||||||
|
|
||||||
arglist = [function_id]
|
|
||||||
verifylist = [('function_id', function_id)]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.client.function_versions.list.assert_called_once_with(function_id)
|
|
||||||
self.assertEqual(self.columns, columns)
|
|
||||||
self.assertEqual(self.data, list(data))
|
|
||||||
|
|
||||||
|
|
||||||
class TestCreateFunctionVersion(TestFunctionVersion):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestCreateFunctionVersion, self).setUp()
|
|
||||||
self.cmd = function_version.Create(self.app, None)
|
|
||||||
|
|
||||||
def _create_fake_function_version(self, attrs=None):
|
|
||||||
# Allow to fake different create results
|
|
||||||
v = fakes.FakeFunctionVersion.create_one_function_version(attrs)
|
|
||||||
self.client.function_versions.create = mock.Mock(return_value=v)
|
|
||||||
data = (v.id, v.function_id, v.description,
|
|
||||||
v.version_number, v.count, v.project_id,
|
|
||||||
v.created_at, v.updated_at)
|
|
||||||
return data
|
|
||||||
|
|
||||||
def test_function_version_create_no_option(self):
|
|
||||||
arglist = []
|
|
||||||
verifylist = []
|
|
||||||
|
|
||||||
self.assertRaises(osc_tests_utils.ParserException,
|
|
||||||
self.check_parser,
|
|
||||||
self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
def test_function_version_create_required_options(self):
|
|
||||||
"""Create a function version.
|
|
||||||
|
|
||||||
1. use function_id,
|
|
||||||
2. all other params except the required ones are not set.
|
|
||||||
"""
|
|
||||||
function_id = self._function_versions[0].function_id
|
|
||||||
attrs = {'function_id': function_id, 'version_number': '2'}
|
|
||||||
created_data = self._create_fake_function_version(attrs)
|
|
||||||
|
|
||||||
arglist = [function_id]
|
|
||||||
verifylist = [
|
|
||||||
('function', function_id),
|
|
||||||
('description', None),
|
|
||||||
]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.client.function_versions.create.assert_called_once_with(
|
|
||||||
function_id, description=None
|
|
||||||
)
|
|
||||||
self.assertEqual(self.columns, columns)
|
|
||||||
self.assertEqual(created_data, data)
|
|
||||||
|
|
||||||
def test_function_version_create_all_options(self):
|
|
||||||
"""Create a function version.
|
|
||||||
|
|
||||||
1. use function name to find the function_id,
|
|
||||||
2. all optional params are specified.
|
|
||||||
"""
|
|
||||||
function = fakes.FakeFunction.create_one_function()
|
|
||||||
function_name = function.name
|
|
||||||
function_id = function.id
|
|
||||||
description = 'This is a new function version.'
|
|
||||||
attrs = {'function_id': function_id,
|
|
||||||
'description': description,
|
|
||||||
'version_number': '2'}
|
|
||||||
created_data = self._create_fake_function_version(attrs)
|
|
||||||
|
|
||||||
# Use to find the function id with its name
|
|
||||||
self.client.functions.find.return_value = function
|
|
||||||
|
|
||||||
arglist = [function_name, '--description', description]
|
|
||||||
verifylist = [
|
|
||||||
('function', function_name),
|
|
||||||
('description', description),
|
|
||||||
]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.client.function_versions.create.assert_called_once_with(
|
|
||||||
function_id, description=description
|
|
||||||
)
|
|
||||||
self.assertEqual(self.columns, columns)
|
|
||||||
self.assertEqual(created_data, data)
|
|
||||||
|
|
||||||
self.client.functions.find.assert_called_once_with(name=function_name)
|
|
||||||
|
|
||||||
|
|
||||||
class TestDeleteFunctionVersion(TestFunctionVersion):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestDeleteFunctionVersion, self).setUp()
|
|
||||||
self.cmd = function_version.Delete(self.app, None)
|
|
||||||
self.client.function_versions.delete = mock.Mock(return_value=None)
|
|
||||||
|
|
||||||
def test_function_version_delete_no_option(self):
|
|
||||||
arglist = []
|
|
||||||
verifylist = []
|
|
||||||
|
|
||||||
self.assertRaises(osc_tests_utils.ParserException,
|
|
||||||
self.check_parser,
|
|
||||||
self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
def test_function_version_delete(self):
|
|
||||||
function_id = self._function_versions[0].function_id
|
|
||||||
version_number = str(self._function_versions[0].version_number)
|
|
||||||
|
|
||||||
arglist = [function_id, version_number]
|
|
||||||
verifylist = [
|
|
||||||
('function_id', function_id),
|
|
||||||
('version_number', version_number),
|
|
||||||
]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
result = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.assertIsNone(result)
|
|
||||||
self.client.function_versions.delete.assert_called_once_with(
|
|
||||||
function_id, version_number
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TestShowFunctionVersion(TestFunctionVersion):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestShowFunctionVersion, self).setUp()
|
|
||||||
self.cmd = function_version.Show(self.app, None)
|
|
||||||
self.client.function_versions.get = mock.Mock(
|
|
||||||
return_value=self._function_versions[0]
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_function_version_show_no_option(self):
|
|
||||||
arglist = []
|
|
||||||
verifylist = []
|
|
||||||
|
|
||||||
self.assertRaises(osc_tests_utils.ParserException,
|
|
||||||
self.check_parser,
|
|
||||||
self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
def test_function_version_show(self):
|
|
||||||
function_id = self._function_versions[0].function_id
|
|
||||||
version_number = str(self._function_versions[0].version_number)
|
|
||||||
|
|
||||||
arglist = [function_id, version_number]
|
|
||||||
verifylist = [
|
|
||||||
('function_id', function_id),
|
|
||||||
('version_number', version_number),
|
|
||||||
]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.client.function_versions.get.assert_called_once_with(
|
|
||||||
function_id, version_number
|
|
||||||
)
|
|
||||||
self.assertEqual(self.columns, columns)
|
|
||||||
self.assertEqual(self.data[0], data)
|
|
||||||
|
|
||||||
|
|
||||||
class TestDetachFunctionVersion(TestFunctionVersion):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestDetachFunctionVersion, self).setUp()
|
|
||||||
self.cmd = function_version.Detach(self.app, None)
|
|
||||||
self.client.function_versions.detach = mock.Mock(return_value=None)
|
|
||||||
|
|
||||||
def test_function_version_detach_no_option(self):
|
|
||||||
arglist = []
|
|
||||||
verifylist = []
|
|
||||||
|
|
||||||
self.assertRaises(osc_tests_utils.ParserException,
|
|
||||||
self.check_parser,
|
|
||||||
self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
def test_function_version_detach(self):
|
|
||||||
function_id = self._function_versions[0].function_id
|
|
||||||
version_number = str(self._function_versions[0].version_number)
|
|
||||||
|
|
||||||
arglist = [function_id, version_number]
|
|
||||||
verifylist = [
|
|
||||||
('function_id', function_id),
|
|
||||||
('version_number', version_number),
|
|
||||||
]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
result = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.assertIsNone(result)
|
|
||||||
self.client.function_versions.detach.assert_called_once_with(
|
|
||||||
function_id, version_number
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_function_version_detach_exception(self):
|
|
||||||
function_id = self._function_versions[0].function_id
|
|
||||||
version_number = str(self._function_versions[0].version_number)
|
|
||||||
|
|
||||||
self.client.function_versions.detach = mock.Mock(
|
|
||||||
side_effect=RuntimeError
|
|
||||||
)
|
|
||||||
|
|
||||||
arglist = [function_id, version_number]
|
|
||||||
verifylist = [
|
|
||||||
('function_id', function_id),
|
|
||||||
('version_number', version_number),
|
|
||||||
]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
self.assertRaisesRegex(
|
|
||||||
exceptions.QinlingClientException,
|
|
||||||
r'^Unable to detach the specified function version\.$',
|
|
||||||
self.cmd.take_action, parsed_args)
|
|
||||||
|
|
||||||
self.client.function_versions.detach.assert_called_once_with(
|
|
||||||
function_id, version_number
|
|
||||||
)
|
|
|
@ -1,71 +0,0 @@
|
||||||
# Copyright 2018 AWCloud Software 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 unittest import mock
|
|
||||||
|
|
||||||
from osc_lib.tests import utils as osc_tests_utils
|
|
||||||
|
|
||||||
from qinlingclient.osc.v1 import base
|
|
||||||
from qinlingclient.osc.v1 import function_worker
|
|
||||||
from qinlingclient.tests.unit.osc.v1 import fakes
|
|
||||||
|
|
||||||
|
|
||||||
class TestFunctionWorker(fakes.TestQinlingClient):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestFunctionWorker, self).setUp()
|
|
||||||
# Get a shortcut
|
|
||||||
self.client = self.app.client_manager.function_engine
|
|
||||||
|
|
||||||
self.columns = base.WORKER_COLUMNS
|
|
||||||
self.data = []
|
|
||||||
|
|
||||||
workers = fakes.FakeFunctionWorker.create_function_workers(count=3)
|
|
||||||
self._function_workers = workers
|
|
||||||
for w in self._function_workers:
|
|
||||||
self.data.append((w.function_id, w.worker_name))
|
|
||||||
|
|
||||||
|
|
||||||
class TestListFunctionWorker(TestFunctionWorker):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestListFunctionWorker, self).setUp()
|
|
||||||
self.cmd = function_worker.List(self.app, None)
|
|
||||||
|
|
||||||
self.columns = [c.capitalize() for c in base.WORKER_COLUMNS]
|
|
||||||
|
|
||||||
self.client.function_workers.list = mock.Mock(
|
|
||||||
return_value=self._function_workers)
|
|
||||||
|
|
||||||
def test_function_worker_list_no_option(self):
|
|
||||||
arglist = []
|
|
||||||
verifylist = []
|
|
||||||
|
|
||||||
self.assertRaises(osc_tests_utils.ParserException,
|
|
||||||
self.check_parser,
|
|
||||||
self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
def test_function_worker_list(self):
|
|
||||||
function_id = self._function_workers[0].function_id
|
|
||||||
|
|
||||||
arglist = [function_id]
|
|
||||||
verifylist = [('function', function_id)]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.client.function_workers.list.assert_called_once_with(function_id)
|
|
||||||
self.assertEqual(self.columns, columns)
|
|
||||||
self.assertEqual(self.data, list(data))
|
|
|
@ -1,487 +0,0 @@
|
||||||
# Copyright 2018 AWCloud Software 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 datetime
|
|
||||||
from unittest import mock
|
|
||||||
|
|
||||||
from osc_lib.tests import utils as osc_tests_utils
|
|
||||||
|
|
||||||
from qinlingclient.common import exceptions
|
|
||||||
from qinlingclient.osc.v1 import base
|
|
||||||
from qinlingclient.osc.v1 import job
|
|
||||||
from qinlingclient.tests.unit.osc.v1 import fakes
|
|
||||||
|
|
||||||
|
|
||||||
class TestJob(fakes.TestQinlingClient):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestJob, self).setUp()
|
|
||||||
# Get a shortcut
|
|
||||||
self.client = self.app.client_manager.function_engine
|
|
||||||
|
|
||||||
self.columns = base.JOB_COLUMNS
|
|
||||||
self.data = []
|
|
||||||
|
|
||||||
self._jobs = fakes.FakeJob.create_jobs(count=3)
|
|
||||||
for j in self._jobs:
|
|
||||||
self.data.append((j.id, j.name, j.count, j.status,
|
|
||||||
j.function_alias,
|
|
||||||
j.function_id, j.function_version,
|
|
||||||
j.function_input, j.pattern,
|
|
||||||
j.first_execution_time, j.next_execution_time,
|
|
||||||
j.project_id, j.created_at, j.updated_at))
|
|
||||||
|
|
||||||
|
|
||||||
class TestListJob(TestJob):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestListJob, self).setUp()
|
|
||||||
self.cmd = job.List(self.app, None)
|
|
||||||
|
|
||||||
self.columns = [c.capitalize() for c in base.JOB_COLUMNS]
|
|
||||||
|
|
||||||
self.client.jobs.list = mock.Mock(return_value=self._jobs)
|
|
||||||
|
|
||||||
def test_job_list(self):
|
|
||||||
arglist = []
|
|
||||||
verifylist = []
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.client.jobs.list.assert_called_once_with()
|
|
||||||
self.assertEqual(self.columns, columns)
|
|
||||||
self.assertEqual(self.data, list(data))
|
|
||||||
|
|
||||||
def test_job_list_with_filter(self):
|
|
||||||
arglist = ['--filter', 'name=has:job',
|
|
||||||
'--filter', 'status=eq:running']
|
|
||||||
verifylist = [
|
|
||||||
('filters', ['name=has:job', 'status=eq:running']),
|
|
||||||
]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.client.jobs.list.assert_called_once_with(
|
|
||||||
name='has:job', status='eq:running'
|
|
||||||
)
|
|
||||||
self.assertEqual(self.columns, columns)
|
|
||||||
self.assertEqual(self.data, list(data))
|
|
||||||
|
|
||||||
def test_job_list_with_invalid_filter(self):
|
|
||||||
arglist = ['--filter', 'name']
|
|
||||||
verifylist = [
|
|
||||||
('filters', ['name'])
|
|
||||||
]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
self.assertRaisesRegex(
|
|
||||||
ValueError,
|
|
||||||
'^Invalid filter: name$',
|
|
||||||
self.cmd.take_action, parsed_args
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TestCreateJob(TestJob):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestCreateJob, self).setUp()
|
|
||||||
self.cmd = job.Create(self.app, None)
|
|
||||||
|
|
||||||
def _create_fake_job(self, attrs=None):
|
|
||||||
# Allow to fake different create results
|
|
||||||
j = fakes.FakeJob.create_one_job(attrs)
|
|
||||||
self.client.jobs.create = mock.Mock(return_value=j)
|
|
||||||
data = (j.id, j.name, j.count, j.status,
|
|
||||||
j.function_alias,
|
|
||||||
j.function_id, j.function_version,
|
|
||||||
j.function_input, j.pattern,
|
|
||||||
j.first_execution_time, j.next_execution_time,
|
|
||||||
j.project_id, j.created_at, j.updated_at)
|
|
||||||
return data
|
|
||||||
|
|
||||||
def test_job_create_function_id(self):
|
|
||||||
"""Create a job with function id."""
|
|
||||||
function_id = self._jobs[0].function_id
|
|
||||||
attrs = {'function_id': function_id}
|
|
||||||
created_data = self._create_fake_job(attrs)
|
|
||||||
|
|
||||||
arglist = ['--function', function_id]
|
|
||||||
verifylist = [
|
|
||||||
('function', function_id),
|
|
||||||
('function_version', 0),
|
|
||||||
('function_alias', None),
|
|
||||||
('name', None),
|
|
||||||
('first_execution_time', None),
|
|
||||||
('pattern', None),
|
|
||||||
('function_input', None),
|
|
||||||
('count', None),
|
|
||||||
]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.client.jobs.create.assert_called_once_with(
|
|
||||||
**{'function_alias': None,
|
|
||||||
'function_id': function_id, 'function_version': 0,
|
|
||||||
'name': None,
|
|
||||||
'first_execution_time': None,
|
|
||||||
'pattern': None,
|
|
||||||
'function_input': None,
|
|
||||||
'count': None}
|
|
||||||
)
|
|
||||||
self.assertEqual(self.columns, columns)
|
|
||||||
self.assertEqual(created_data, data)
|
|
||||||
|
|
||||||
def test_job_create_function_name(self):
|
|
||||||
"""Create a job.
|
|
||||||
|
|
||||||
1. use function name to find the function_id,
|
|
||||||
2. all optional params are specified.
|
|
||||||
"""
|
|
||||||
function = fakes.FakeFunction.create_one_function()
|
|
||||||
function_name = function.name
|
|
||||||
function_id = function.id
|
|
||||||
job_name = 'FAKE_JOB_NAME'
|
|
||||||
count = 3
|
|
||||||
function_version = 1
|
|
||||||
function_input = '{"JSON_INPUT_KEY": "JSON_INPUT_VALUE"}'
|
|
||||||
pattern = '1 * * * *'
|
|
||||||
first_execution_time = str(datetime.datetime.utcnow())
|
|
||||||
attrs = {'name': job_name,
|
|
||||||
'count': count,
|
|
||||||
'function_id': function_id,
|
|
||||||
'function_version': function_version,
|
|
||||||
'function_input': function_input,
|
|
||||||
'pattern': pattern,
|
|
||||||
'first_execution_time': first_execution_time}
|
|
||||||
created_data = self._create_fake_job(attrs)
|
|
||||||
|
|
||||||
# Use to find the function id with its name
|
|
||||||
self.client.functions.find.return_value = function
|
|
||||||
|
|
||||||
arglist = ['--function', function_name,
|
|
||||||
'--function-version', str(function_version),
|
|
||||||
'--name', job_name,
|
|
||||||
'--first-execution-time', first_execution_time,
|
|
||||||
'--pattern', pattern,
|
|
||||||
'--function-input', function_input,
|
|
||||||
'--count', str(count)]
|
|
||||||
verifylist = [
|
|
||||||
('function', function_name),
|
|
||||||
('function_version', function_version),
|
|
||||||
('function_alias', None),
|
|
||||||
('name', job_name),
|
|
||||||
('first_execution_time', first_execution_time),
|
|
||||||
('pattern', pattern),
|
|
||||||
('function_input', function_input),
|
|
||||||
('count', count),
|
|
||||||
]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.client.jobs.create.assert_called_once_with(
|
|
||||||
**{'function_alias': None, 'function_id': function_id,
|
|
||||||
'function_version': function_version,
|
|
||||||
'name': job_name,
|
|
||||||
'first_execution_time': first_execution_time,
|
|
||||||
'pattern': pattern,
|
|
||||||
'function_input': function_input,
|
|
||||||
'count': count}
|
|
||||||
)
|
|
||||||
self.assertEqual(self.columns, columns)
|
|
||||||
self.assertEqual(created_data, data)
|
|
||||||
|
|
||||||
self.client.functions.find.assert_called_once_with(name=function_name)
|
|
||||||
|
|
||||||
def test_job_create_function_alias(self):
|
|
||||||
"""Create a job with function alias."""
|
|
||||||
function_alias = 'fake_alias'
|
|
||||||
attrs = {'function_alias': function_alias}
|
|
||||||
created_data = self._create_fake_job(attrs)
|
|
||||||
|
|
||||||
arglist = ['--function-alias', function_alias]
|
|
||||||
verifylist = [
|
|
||||||
('function', None),
|
|
||||||
('function_version', 0),
|
|
||||||
('function_alias', function_alias),
|
|
||||||
('name', None),
|
|
||||||
('first_execution_time', None),
|
|
||||||
('pattern', None),
|
|
||||||
('function_input', None),
|
|
||||||
('count', None),
|
|
||||||
]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.client.jobs.create.assert_called_once_with(
|
|
||||||
**{'function_alias': function_alias,
|
|
||||||
'function_id': None, 'function_version': None,
|
|
||||||
'name': None,
|
|
||||||
'first_execution_time': None,
|
|
||||||
'pattern': None,
|
|
||||||
'function_input': None,
|
|
||||||
'count': None}
|
|
||||||
)
|
|
||||||
self.assertEqual(self.columns, columns)
|
|
||||||
self.assertEqual(created_data, data)
|
|
||||||
|
|
||||||
def test_job_create_version_not_integer(self):
|
|
||||||
# function_version should be an integer value
|
|
||||||
function_id = self._jobs[0].function_id
|
|
||||||
|
|
||||||
arglist = ['--function', function_id, '--function-version',
|
|
||||||
'NOT_A_INTEGER']
|
|
||||||
verifylist = [
|
|
||||||
('function', function_id),
|
|
||||||
('function_version', 0),
|
|
||||||
('name', None),
|
|
||||||
('first_execution_time', None),
|
|
||||||
('pattern', None),
|
|
||||||
('function_input', None),
|
|
||||||
('count', None),
|
|
||||||
]
|
|
||||||
|
|
||||||
self.assertRaises(osc_tests_utils.ParserException,
|
|
||||||
self.check_parser,
|
|
||||||
self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
def test_job_create_count_not_integer(self):
|
|
||||||
# count should be an integer value
|
|
||||||
function_id = self._jobs[0].function_id
|
|
||||||
|
|
||||||
arglist = ['--function', function_id, '--count', 'NOT_A_INTEGER']
|
|
||||||
verifylist = [
|
|
||||||
('function', function_id),
|
|
||||||
('function_version', 0),
|
|
||||||
('name', None),
|
|
||||||
('first_execution_time', None),
|
|
||||||
('pattern', None),
|
|
||||||
('function_input', None),
|
|
||||||
('count', 'NOT_A_INTEGER'),
|
|
||||||
]
|
|
||||||
|
|
||||||
self.assertRaises(osc_tests_utils.ParserException,
|
|
||||||
self.check_parser,
|
|
||||||
self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
|
|
||||||
class TestDeleteJob(TestJob):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestDeleteJob, self).setUp()
|
|
||||||
self.cmd = job.Delete(self.app, None)
|
|
||||||
self.client.jobs.delete = mock.Mock(return_value=None)
|
|
||||||
|
|
||||||
def test_job_delete_no_option(self):
|
|
||||||
arglist = []
|
|
||||||
verifylist = []
|
|
||||||
|
|
||||||
self.assertRaises(osc_tests_utils.ParserException,
|
|
||||||
self.check_parser,
|
|
||||||
self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
def test_job_delete_one(self):
|
|
||||||
job_id = self._jobs[0].id
|
|
||||||
arglist = [job_id]
|
|
||||||
verifylist = [('job', [job_id])]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
result = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.assertIsNone(result)
|
|
||||||
self.client.jobs.delete.assert_called_once_with(job_id)
|
|
||||||
|
|
||||||
def test_job_delete_multiple(self):
|
|
||||||
job_ids = [j.id for j in self._jobs]
|
|
||||||
arglist = job_ids
|
|
||||||
verifylist = [('job', job_ids)]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
result = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.assertIsNone(result)
|
|
||||||
calls = [mock.call(j_id) for j_id in job_ids]
|
|
||||||
self.assertEqual(len(job_ids), self.client.jobs.delete.call_count)
|
|
||||||
self.client.jobs.delete.assert_has_calls(calls)
|
|
||||||
|
|
||||||
def test_job_delete_multiple_exception(self):
|
|
||||||
job_ids = [j.id for j in self._jobs]
|
|
||||||
arglist = job_ids
|
|
||||||
verifylist = [('job', job_ids)]
|
|
||||||
|
|
||||||
self.client.jobs.delete = mock.Mock(side_effect=[
|
|
||||||
None, RuntimeError, None
|
|
||||||
])
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
self.assertRaisesRegex(
|
|
||||||
exceptions.QinlingClientException,
|
|
||||||
r'^Unable to delete the specified job\(s\)\.$',
|
|
||||||
self.cmd.take_action, parsed_args)
|
|
||||||
|
|
||||||
# The second deleteion failed, but the third is done normally
|
|
||||||
calls = [mock.call(j_id) for j_id in job_ids]
|
|
||||||
self.assertEqual(len(job_ids), self.client.jobs.delete.call_count)
|
|
||||||
self.client.jobs.delete.assert_has_calls(calls)
|
|
||||||
|
|
||||||
|
|
||||||
class TestShowJob(TestJob):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestShowJob, self).setUp()
|
|
||||||
self.cmd = job.Show(self.app, None)
|
|
||||||
self.client.jobs.get = mock.Mock(return_value=self._jobs[0])
|
|
||||||
|
|
||||||
def test_job_show_no_option(self):
|
|
||||||
arglist = []
|
|
||||||
verifylist = []
|
|
||||||
|
|
||||||
self.assertRaises(osc_tests_utils.ParserException,
|
|
||||||
self.check_parser,
|
|
||||||
self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
def test_job_show(self):
|
|
||||||
job_id = self._jobs[0].id
|
|
||||||
arglist = [job_id]
|
|
||||||
verifylist = [('job', job_id)]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.client.jobs.get.assert_called_once_with(job_id)
|
|
||||||
self.assertEqual(self.columns, columns)
|
|
||||||
self.assertEqual(self.data[0], data)
|
|
||||||
|
|
||||||
|
|
||||||
class TestUpdateJob(TestJob):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestUpdateJob, self).setUp()
|
|
||||||
self.cmd = job.Update(self.app, None)
|
|
||||||
|
|
||||||
def _update_fake_job(self, attrs=None):
|
|
||||||
# Allow to fake different update results
|
|
||||||
j = fakes.FakeJob.create_one_job(attrs)
|
|
||||||
self.client.jobs.update = mock.Mock(return_value=j)
|
|
||||||
data = (j.id, j.name, j.count, j.status,
|
|
||||||
j.function_alias,
|
|
||||||
j.function_id, j.function_version,
|
|
||||||
j.function_input, j.pattern,
|
|
||||||
j.first_execution_time, j.next_execution_time,
|
|
||||||
j.project_id, j.created_at, j.updated_at)
|
|
||||||
return data
|
|
||||||
|
|
||||||
def test_job_update_no_option(self):
|
|
||||||
arglist = []
|
|
||||||
verifylist = []
|
|
||||||
|
|
||||||
self.assertRaises(osc_tests_utils.ParserException,
|
|
||||||
self.check_parser,
|
|
||||||
self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
def test_job_update_required_options(self):
|
|
||||||
"""Update a job.
|
|
||||||
|
|
||||||
Do nothing as only the job_id is specified.
|
|
||||||
"""
|
|
||||||
job_id = self._jobs[0].id
|
|
||||||
attrs = {'id': job_id}
|
|
||||||
updated_data = self._update_fake_job(attrs)
|
|
||||||
|
|
||||||
arglist = [job_id]
|
|
||||||
verifylist = [
|
|
||||||
('id', job_id),
|
|
||||||
('name', None),
|
|
||||||
('status', None),
|
|
||||||
('next_execution_time', None),
|
|
||||||
('pattern', None),
|
|
||||||
('function_input', None),
|
|
||||||
]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.client.jobs.update.assert_called_once_with(
|
|
||||||
job_id,
|
|
||||||
**{'name': None,
|
|
||||||
'status': None,
|
|
||||||
'pattern': None,
|
|
||||||
'next_execution_time': None,
|
|
||||||
'function_input': None}
|
|
||||||
)
|
|
||||||
self.assertEqual(self.columns, columns)
|
|
||||||
self.assertEqual(updated_data, data)
|
|
||||||
|
|
||||||
def test_job_update_all_options(self):
|
|
||||||
job_id = self._jobs[0].id
|
|
||||||
name = 'UPDATED_FAKE_JOB_NAME'
|
|
||||||
status = 'paused'
|
|
||||||
next_execution_time = str(
|
|
||||||
datetime.datetime.utcnow() + datetime.timedelta(0, 3600))
|
|
||||||
pattern = '* 1 * * *'
|
|
||||||
function_input = '{"JSON_INPUT_KEY": "JSON_INPUT_VALUE"}'
|
|
||||||
attrs = {'id': job_id, 'name': name, 'status': status,
|
|
||||||
'next_execution_time': next_execution_time,
|
|
||||||
'pattern': pattern, 'function_input': function_input}
|
|
||||||
updated_data = self._update_fake_job(attrs)
|
|
||||||
|
|
||||||
arglist = [job_id, '--name', name, '--status', status,
|
|
||||||
'--next-execution-time', next_execution_time,
|
|
||||||
'--pattern', pattern, '--function-input', function_input]
|
|
||||||
verifylist = [
|
|
||||||
('id', job_id),
|
|
||||||
('name', name),
|
|
||||||
('status', status),
|
|
||||||
('next_execution_time', next_execution_time),
|
|
||||||
('pattern', pattern),
|
|
||||||
('function_input', function_input),
|
|
||||||
]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.client.jobs.update.assert_called_once_with(
|
|
||||||
job_id,
|
|
||||||
**{'name': name,
|
|
||||||
'status': status,
|
|
||||||
'pattern': pattern,
|
|
||||||
'next_execution_time': next_execution_time,
|
|
||||||
'function_input': function_input}
|
|
||||||
)
|
|
||||||
self.assertEqual(self.columns, columns)
|
|
||||||
self.assertEqual(updated_data, data)
|
|
||||||
|
|
||||||
def test_job_update_status_not_in_choices(self):
|
|
||||||
job_id = self._jobs[0].id
|
|
||||||
arglist = [job_id, '--status', 'NOT_IN_CHOICES']
|
|
||||||
verifylist = [
|
|
||||||
('id', job_id),
|
|
||||||
('name', None),
|
|
||||||
('status', 'NOT_IN_CHOICES'),
|
|
||||||
('next_execution_time', None),
|
|
||||||
('pattern', None),
|
|
||||||
('function_input', None),
|
|
||||||
]
|
|
||||||
|
|
||||||
self.assertRaises(osc_tests_utils.ParserException,
|
|
||||||
self.check_parser,
|
|
||||||
self.cmd, arglist, verifylist)
|
|
|
@ -1,307 +0,0 @@
|
||||||
# Copyright 2018 AWCloud Software 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 unittest import mock
|
|
||||||
|
|
||||||
from osc_lib.tests import utils as osc_tests_utils
|
|
||||||
|
|
||||||
from qinlingclient.common import exceptions
|
|
||||||
from qinlingclient.osc.v1 import base
|
|
||||||
from qinlingclient.osc.v1 import runtime
|
|
||||||
from qinlingclient.tests.unit.osc.v1 import fakes
|
|
||||||
|
|
||||||
|
|
||||||
class TestRuntime(fakes.TestQinlingClient):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestRuntime, self).setUp()
|
|
||||||
# Get a shortcut
|
|
||||||
self.client = self.app.client_manager.function_engine
|
|
||||||
|
|
||||||
self.columns = base.RUNTIME_COLUMNS
|
|
||||||
self.data = []
|
|
||||||
|
|
||||||
self._runtimes = fakes.FakeRuntime.create_runtimes(count=3)
|
|
||||||
for r in self._runtimes:
|
|
||||||
self.data.append((r.id, r.name, r.image, r.status, r.description,
|
|
||||||
r.is_public, r.trusted, r.project_id,
|
|
||||||
r.created_at, r.updated_at))
|
|
||||||
|
|
||||||
|
|
||||||
class TestListRuntime(TestRuntime):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestListRuntime, self).setUp()
|
|
||||||
self.cmd = runtime.List(self.app, None)
|
|
||||||
|
|
||||||
self.columns = [c.capitalize() for c in base.RUNTIME_COLUMNS]
|
|
||||||
|
|
||||||
self.client.runtimes.list = mock.Mock(return_value=self._runtimes)
|
|
||||||
|
|
||||||
def test_runtime_list(self):
|
|
||||||
arglist = []
|
|
||||||
verifylist = []
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.client.runtimes.list.assert_called_once_with()
|
|
||||||
self.assertEqual(self.columns, columns)
|
|
||||||
self.assertEqual(self.data, list(data))
|
|
||||||
|
|
||||||
def test_runtime_list_with_filter(self):
|
|
||||||
arglist = ['--filter', 'name=has:runtime',
|
|
||||||
'--filter', 'status=eq:available']
|
|
||||||
verifylist = [
|
|
||||||
('filters', ['name=has:runtime', 'status=eq:available'])
|
|
||||||
]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.client.runtimes.list.assert_called_once_with(
|
|
||||||
name='has:runtime', status='eq:available'
|
|
||||||
)
|
|
||||||
self.assertEqual(self.columns, columns)
|
|
||||||
self.assertEqual(self.data, list(data))
|
|
||||||
|
|
||||||
def test_runtime_list_with_invalid_filter(self):
|
|
||||||
arglist = ['--filter', 'name']
|
|
||||||
verifylist = [
|
|
||||||
('filters', ['name'])
|
|
||||||
]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
self.assertRaisesRegex(
|
|
||||||
ValueError,
|
|
||||||
'^Invalid filter: name$',
|
|
||||||
self.cmd.take_action, parsed_args
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TestCreateRuntime(TestRuntime):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestCreateRuntime, self).setUp()
|
|
||||||
self.cmd = runtime.Create(self.app, None)
|
|
||||||
|
|
||||||
def _create_fake_runtime(self, attrs=None):
|
|
||||||
# Allow to fake different create results
|
|
||||||
r = fakes.FakeRuntime.create_one_runtime(attrs)
|
|
||||||
self.client.runtimes.create = mock.Mock(return_value=r)
|
|
||||||
data = (r.id, r.name, r.image, r.status, r.description,
|
|
||||||
r.is_public, r.trusted, r.project_id,
|
|
||||||
r.created_at, r.updated_at)
|
|
||||||
return data
|
|
||||||
|
|
||||||
def test_runtime_create_no_option(self):
|
|
||||||
arglist = []
|
|
||||||
verifylist = []
|
|
||||||
|
|
||||||
self.assertRaises(osc_tests_utils.ParserException,
|
|
||||||
self.check_parser,
|
|
||||||
self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
def test_runtime_create_required_options(self):
|
|
||||||
image = 'specified-image-name'
|
|
||||||
attrs = {'image': image}
|
|
||||||
created_data = self._create_fake_runtime(attrs)
|
|
||||||
|
|
||||||
arglist = [image]
|
|
||||||
verifylist = [
|
|
||||||
('image', image),
|
|
||||||
('name', None),
|
|
||||||
('description', None),
|
|
||||||
('trusted', True),
|
|
||||||
('is_public', True),
|
|
||||||
]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.client.runtimes.create.assert_called_once_with(**{
|
|
||||||
'name': None,
|
|
||||||
'description': None,
|
|
||||||
'image': image,
|
|
||||||
'trusted': True,
|
|
||||||
'is_public': True,
|
|
||||||
})
|
|
||||||
self.assertEqual(self.columns, columns)
|
|
||||||
self.assertEqual(created_data, data)
|
|
||||||
|
|
||||||
def test_runtime_create_all_options(self):
|
|
||||||
image = 'specified-image-name'
|
|
||||||
name = 'specified-runtime-name'
|
|
||||||
description = 'specified-runtime-description'
|
|
||||||
trusted = False
|
|
||||||
is_public = False
|
|
||||||
attrs = {'image': image, 'name': name,
|
|
||||||
'description': description, 'trusted': trusted,
|
|
||||||
'is_public': is_public}
|
|
||||||
created_data = self._create_fake_runtime(attrs)
|
|
||||||
|
|
||||||
arglist = [
|
|
||||||
'--name', name,
|
|
||||||
'--description', description,
|
|
||||||
'--untrusted',
|
|
||||||
'--private',
|
|
||||||
image,
|
|
||||||
]
|
|
||||||
verifylist = [
|
|
||||||
('image', image),
|
|
||||||
('name', name),
|
|
||||||
('description', description),
|
|
||||||
('trusted', trusted),
|
|
||||||
('is_public', is_public)
|
|
||||||
]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.client.runtimes.create.assert_called_once_with(**{
|
|
||||||
'name': name,
|
|
||||||
'description': description,
|
|
||||||
'image': image,
|
|
||||||
'trusted': trusted,
|
|
||||||
'is_public': is_public,
|
|
||||||
})
|
|
||||||
self.assertEqual(self.columns, columns)
|
|
||||||
self.assertEqual(created_data, data)
|
|
||||||
|
|
||||||
|
|
||||||
class TestDeleteRuntime(TestRuntime):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestDeleteRuntime, self).setUp()
|
|
||||||
self.cmd = runtime.Delete(self.app, None)
|
|
||||||
self.client.runtimes.delete = mock.Mock(return_value=None)
|
|
||||||
|
|
||||||
def test_runtime_delete_no_option(self):
|
|
||||||
arglist = []
|
|
||||||
verifylist = []
|
|
||||||
|
|
||||||
self.assertRaises(osc_tests_utils.ParserException,
|
|
||||||
self.check_parser,
|
|
||||||
self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
def test_runtime_delete_one(self):
|
|
||||||
runtime_id = self._runtimes[0].id
|
|
||||||
arglist = [runtime_id]
|
|
||||||
verifylist = [('runtime', [runtime_id])]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
result = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.assertIsNone(result)
|
|
||||||
self.client.runtimes.delete.assert_called_once_with(runtime_id)
|
|
||||||
|
|
||||||
def test_runtime_delete_multiple(self):
|
|
||||||
runtime_ids = [r.id for r in self._runtimes]
|
|
||||||
arglist = runtime_ids
|
|
||||||
verifylist = [('runtime', runtime_ids)]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
result = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.assertIsNone(result)
|
|
||||||
calls = [mock.call(r_id) for r_id in runtime_ids]
|
|
||||||
self.assertEqual(len(runtime_ids),
|
|
||||||
self.client.runtimes.delete.call_count)
|
|
||||||
self.client.runtimes.delete.assert_has_calls(calls)
|
|
||||||
|
|
||||||
def test_runtime_delete_multiple_exception(self):
|
|
||||||
runtime_ids = [r.id for r in self._runtimes]
|
|
||||||
arglist = runtime_ids
|
|
||||||
verifylist = [('runtime', runtime_ids)]
|
|
||||||
|
|
||||||
self.client.runtimes.delete = mock.Mock(side_effect=[
|
|
||||||
None, RuntimeError, None
|
|
||||||
])
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
self.assertRaisesRegex(
|
|
||||||
exceptions.QinlingClientException,
|
|
||||||
r'^Unable to delete the specified runtime\(s\)\.$',
|
|
||||||
self.cmd.take_action, parsed_args)
|
|
||||||
|
|
||||||
# The second deletion failed, but the third is done normally
|
|
||||||
calls = [mock.call(r_id) for r_id in runtime_ids]
|
|
||||||
self.assertEqual(len(runtime_ids),
|
|
||||||
self.client.runtimes.delete.call_count)
|
|
||||||
self.client.runtimes.delete.assert_has_calls(calls)
|
|
||||||
|
|
||||||
|
|
||||||
class TestShowRuntime(TestRuntime):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestShowRuntime, self).setUp()
|
|
||||||
self.cmd = runtime.Show(self.app, None)
|
|
||||||
self.client.runtimes.get = mock.Mock(return_value=self._runtimes[0])
|
|
||||||
|
|
||||||
def test_runtime_show_no_option(self):
|
|
||||||
arglist = []
|
|
||||||
verifylist = []
|
|
||||||
|
|
||||||
self.assertRaises(osc_tests_utils.ParserException,
|
|
||||||
self.check_parser,
|
|
||||||
self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
def test_runtime_show(self):
|
|
||||||
runtime_id = self._runtimes[0].id
|
|
||||||
arglist = [runtime_id]
|
|
||||||
verifylist = [('runtime', runtime_id)]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.client.runtimes.get.assert_called_once_with(runtime_id)
|
|
||||||
self.assertEqual(self.columns, columns)
|
|
||||||
self.assertEqual(self.data[0], data)
|
|
||||||
|
|
||||||
|
|
||||||
class TestShowRuntimePool(TestRuntime):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestShowRuntimePool, self).setUp()
|
|
||||||
self.cmd = runtime.Pool(self.app, None)
|
|
||||||
|
|
||||||
self.columns = base.RUNTIME_POOL_COLUMNS
|
|
||||||
pool_attrs = {'name': self._runtimes[0].id}
|
|
||||||
pool = fakes.FakeRuntime.create_one_runtime_pool(pool_attrs)
|
|
||||||
self.pool_data = (pool.name, pool.capacity)
|
|
||||||
|
|
||||||
self.client.runtimes.get_pool = mock.Mock(return_value=pool)
|
|
||||||
|
|
||||||
def test_runtime_pool_show_no_option(self):
|
|
||||||
arglist = []
|
|
||||||
verifylist = []
|
|
||||||
|
|
||||||
self.assertRaises(osc_tests_utils.ParserException,
|
|
||||||
self.check_parser,
|
|
||||||
self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
def test_runtime_pool_show(self):
|
|
||||||
runtime_id = self._runtimes[0].id
|
|
||||||
arglist = [runtime_id]
|
|
||||||
verifylist = [('runtime', runtime_id)]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.client.runtimes.get_pool.assert_called_once_with(runtime_id)
|
|
||||||
self.assertEqual(self.columns, columns)
|
|
||||||
self.assertEqual(self.pool_data, data)
|
|
|
@ -1,415 +0,0 @@
|
||||||
# Copyright 2018 AWCloud Software 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 unittest import mock
|
|
||||||
|
|
||||||
from osc_lib.tests import utils as osc_tests_utils
|
|
||||||
|
|
||||||
from qinlingclient.common import exceptions
|
|
||||||
from qinlingclient.osc.v1 import base
|
|
||||||
from qinlingclient.osc.v1 import webhook
|
|
||||||
from qinlingclient.tests.unit.osc.v1 import fakes
|
|
||||||
|
|
||||||
|
|
||||||
class TestWebhook(fakes.TestQinlingClient):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestWebhook, self).setUp()
|
|
||||||
# Get a shortcut
|
|
||||||
self.client = self.app.client_manager.function_engine
|
|
||||||
|
|
||||||
self.columns = base.WEBHOOK_COLUMNS
|
|
||||||
self.data = []
|
|
||||||
|
|
||||||
webhooks = fakes.FakeWebhook.create_webhooks(count=3)
|
|
||||||
self._webhooks = webhooks
|
|
||||||
for w in self._webhooks:
|
|
||||||
self.data.append((w.id, w.function_alias, w.function_id,
|
|
||||||
w.function_version, w.description, w.project_id,
|
|
||||||
w.created_at, w.updated_at,
|
|
||||||
w.webhook_url))
|
|
||||||
|
|
||||||
|
|
||||||
class TestListWebhook(TestWebhook):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestListWebhook, self).setUp()
|
|
||||||
self.cmd = webhook.List(self.app, None)
|
|
||||||
|
|
||||||
self.columns = [c.capitalize() for c in base.WEBHOOK_COLUMNS]
|
|
||||||
|
|
||||||
self.client.webhooks.list = mock.Mock(return_value=self._webhooks)
|
|
||||||
|
|
||||||
def test_webhook_list(self):
|
|
||||||
arglist = []
|
|
||||||
verifylist = []
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.client.webhooks.list.assert_called_once_with()
|
|
||||||
self.assertEqual(self.columns, columns)
|
|
||||||
self.assertEqual(self.data, list(data))
|
|
||||||
|
|
||||||
def test_webhook_list_with_filter(self):
|
|
||||||
arglist = ['--filter', 'function_version=neq:0',
|
|
||||||
'--filter', 'description=has:webhook']
|
|
||||||
verifylist = [
|
|
||||||
('filters', ['function_version=neq:0', 'description=has:webhook']),
|
|
||||||
]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.client.webhooks.list.assert_called_once_with(
|
|
||||||
function_version='neq:0', description='has:webhook'
|
|
||||||
)
|
|
||||||
self.assertEqual(self.columns, columns)
|
|
||||||
self.assertEqual(self.data, list(data))
|
|
||||||
|
|
||||||
def test_webhook_list_with_invalid_filter(self):
|
|
||||||
arglist = ['--filter', 'function_version']
|
|
||||||
verifylist = [
|
|
||||||
('filters', ['function_version']),
|
|
||||||
]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
self.assertRaisesRegex(
|
|
||||||
ValueError,
|
|
||||||
'^Invalid filter: function_version$',
|
|
||||||
self.cmd.take_action, parsed_args
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TestCreateWebhook(TestWebhook):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestCreateWebhook, self).setUp()
|
|
||||||
self.cmd = webhook.Create(self.app, None)
|
|
||||||
|
|
||||||
def _create_fake_webhook(self, attrs=None):
|
|
||||||
# Allow to fake different create results
|
|
||||||
w = fakes.FakeWebhook.create_one_webhook(attrs)
|
|
||||||
self.client.webhooks.create = mock.Mock(return_value=w)
|
|
||||||
data = (w.id, w.function_alias, w.function_id, w.function_version,
|
|
||||||
w.description, w.project_id, w.created_at, w.updated_at,
|
|
||||||
w.webhook_url)
|
|
||||||
return data
|
|
||||||
|
|
||||||
def test_webhook_create_function_id(self):
|
|
||||||
"""Create a webhook with function id."""
|
|
||||||
function_id = self._webhooks[0].function_id
|
|
||||||
attrs = {'function_id': function_id}
|
|
||||||
created_data = self._create_fake_webhook(attrs)
|
|
||||||
|
|
||||||
arglist = ['--function', function_id]
|
|
||||||
verifylist = [
|
|
||||||
('function', function_id),
|
|
||||||
('function_version', 0),
|
|
||||||
('function_alias', None),
|
|
||||||
('description', None),
|
|
||||||
]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.client.webhooks.create.assert_called_once_with(
|
|
||||||
**{'function_id': function_id,
|
|
||||||
'function_version': 0,
|
|
||||||
'function_alias': None,
|
|
||||||
'description': None}
|
|
||||||
)
|
|
||||||
self.assertEqual(self.columns, columns)
|
|
||||||
self.assertEqual(created_data, data)
|
|
||||||
|
|
||||||
def test_webhook_create_function_name(self):
|
|
||||||
"""Create a webhook.
|
|
||||||
|
|
||||||
1. use function name to find the function_id,
|
|
||||||
2. all optional params are specified.
|
|
||||||
"""
|
|
||||||
function = fakes.FakeFunction.create_one_function()
|
|
||||||
function_name = function.name
|
|
||||||
function_id = function.id
|
|
||||||
function_version = 1
|
|
||||||
webhook_description = 'This is a newly created webhook.'
|
|
||||||
attrs = {'function_id': function_id,
|
|
||||||
'function_version': function_version,
|
|
||||||
'description': webhook_description}
|
|
||||||
created_data = self._create_fake_webhook(attrs)
|
|
||||||
|
|
||||||
# Use to find the function id with its name
|
|
||||||
self.client.functions.find.return_value = function
|
|
||||||
|
|
||||||
arglist = ['--function', function_name,
|
|
||||||
'--function-version', str(function_version),
|
|
||||||
'--description', webhook_description]
|
|
||||||
verifylist = [
|
|
||||||
('function', function_name),
|
|
||||||
('function_version', function_version),
|
|
||||||
('function_alias', None),
|
|
||||||
('description', webhook_description),
|
|
||||||
]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.client.webhooks.create.assert_called_once_with(
|
|
||||||
**{'function_id': function_id,
|
|
||||||
'function_version': function_version,
|
|
||||||
'function_alias': None,
|
|
||||||
'description': webhook_description}
|
|
||||||
)
|
|
||||||
self.assertEqual(self.columns, columns)
|
|
||||||
self.assertEqual(created_data, data)
|
|
||||||
|
|
||||||
self.client.functions.find.assert_called_once_with(name=function_name)
|
|
||||||
|
|
||||||
def test_webhook_create_function_alias(self):
|
|
||||||
"""Create a webhook with function alias."""
|
|
||||||
function_alias = 'fake_alias'
|
|
||||||
attrs = {'function_alias': function_alias}
|
|
||||||
created_data = self._create_fake_webhook(attrs)
|
|
||||||
|
|
||||||
arglist = ['--function-alias', function_alias]
|
|
||||||
verifylist = [
|
|
||||||
('function', None),
|
|
||||||
('function_version', 0),
|
|
||||||
('function_alias', function_alias),
|
|
||||||
('description', None),
|
|
||||||
]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.client.webhooks.create.assert_called_once_with(
|
|
||||||
**{'function_id': None,
|
|
||||||
'function_version': None,
|
|
||||||
'function_alias': function_alias,
|
|
||||||
'description': None}
|
|
||||||
)
|
|
||||||
self.assertEqual(self.columns, columns)
|
|
||||||
self.assertEqual(created_data, data)
|
|
||||||
|
|
||||||
def test_webhook_create_version_not_integer(self):
|
|
||||||
# function_version should be an integer value
|
|
||||||
function_id = self._webhooks[0].function_id
|
|
||||||
|
|
||||||
arglist = [function_id, '--function-version', 'NOT_A_INTEGER']
|
|
||||||
verifylist = [
|
|
||||||
('function', function_id),
|
|
||||||
('function_version', 'NOT_A_INTEGER'),
|
|
||||||
('description', None),
|
|
||||||
]
|
|
||||||
|
|
||||||
self.assertRaises(osc_tests_utils.ParserException,
|
|
||||||
self.check_parser,
|
|
||||||
self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
|
|
||||||
class TestDeleteWebhook(TestWebhook):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestDeleteWebhook, self).setUp()
|
|
||||||
self.cmd = webhook.Delete(self.app, None)
|
|
||||||
self.client.webhooks.delete = mock.Mock(return_value=None)
|
|
||||||
|
|
||||||
def test_webhook_delete_no_option(self):
|
|
||||||
arglist = []
|
|
||||||
verifylist = []
|
|
||||||
|
|
||||||
self.assertRaises(osc_tests_utils.ParserException,
|
|
||||||
self.check_parser,
|
|
||||||
self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
def test_webhook_delete_one(self):
|
|
||||||
webhook_id = self._webhooks[0].id
|
|
||||||
arglist = [webhook_id]
|
|
||||||
verifylist = [('webhook', [webhook_id])]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
result = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.assertIsNone(result)
|
|
||||||
self.client.webhooks.delete.assert_called_once_with(webhook_id)
|
|
||||||
|
|
||||||
def test_webhook_delete_multiple(self):
|
|
||||||
webhook_ids = [w.id for w in self._webhooks]
|
|
||||||
arglist = webhook_ids
|
|
||||||
verifylist = [('webhook', webhook_ids)]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
result = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.assertIsNone(result)
|
|
||||||
calls = [mock.call(w_id) for w_id in webhook_ids]
|
|
||||||
self.assertEqual(len(webhook_ids),
|
|
||||||
self.client.webhooks.delete.call_count)
|
|
||||||
self.client.webhooks.delete.assert_has_calls(calls)
|
|
||||||
|
|
||||||
def test_webhook_delete_multiple_exception(self):
|
|
||||||
webhook_ids = [w.id for w in self._webhooks]
|
|
||||||
arglist = webhook_ids
|
|
||||||
verifylist = [('webhook', webhook_ids)]
|
|
||||||
|
|
||||||
self.client.webhooks.delete = mock.Mock(side_effect=[
|
|
||||||
None, RuntimeError, None
|
|
||||||
])
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
self.assertRaisesRegex(
|
|
||||||
exceptions.QinlingClientException,
|
|
||||||
r'^Unable to delete the specified webhook\(s\)\.$',
|
|
||||||
self.cmd.take_action, parsed_args)
|
|
||||||
|
|
||||||
# The second deleteion failed, but the third is done normally
|
|
||||||
calls = [mock.call(w_id) for w_id in webhook_ids]
|
|
||||||
self.assertEqual(len(webhook_ids),
|
|
||||||
self.client.webhooks.delete.call_count)
|
|
||||||
self.client.webhooks.delete.assert_has_calls(calls)
|
|
||||||
|
|
||||||
|
|
||||||
class TestShowWebhook(TestWebhook):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestShowWebhook, self).setUp()
|
|
||||||
self.cmd = webhook.Show(self.app, None)
|
|
||||||
self.client.webhooks.get = mock.Mock(return_value=self._webhooks[0])
|
|
||||||
|
|
||||||
def test_webhook_show_no_option(self):
|
|
||||||
arglist = []
|
|
||||||
verifylist = []
|
|
||||||
|
|
||||||
self.assertRaises(osc_tests_utils.ParserException,
|
|
||||||
self.check_parser,
|
|
||||||
self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
def test_webhook_show(self):
|
|
||||||
webhook_id = self._webhooks[0].id
|
|
||||||
arglist = [webhook_id]
|
|
||||||
verifylist = [('webhook', webhook_id)]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.client.webhooks.get.assert_called_once_with(webhook_id)
|
|
||||||
self.assertEqual(self.columns, columns)
|
|
||||||
self.assertEqual(self.data[0], data)
|
|
||||||
|
|
||||||
|
|
||||||
class TestUpdateWebhook(TestWebhook):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestUpdateWebhook, self).setUp()
|
|
||||||
self.cmd = webhook.Update(self.app, None)
|
|
||||||
|
|
||||||
def _update_fake_webhook(self, attrs=None):
|
|
||||||
# Allow to fake different update results
|
|
||||||
w = fakes.FakeWebhook.create_one_webhook(attrs)
|
|
||||||
self.client.webhooks.update = mock.Mock(return_value=w)
|
|
||||||
data = (w.id, w.function_alias, w.function_id, w.function_version,
|
|
||||||
w.description, w.project_id, w.created_at, w.updated_at,
|
|
||||||
w.webhook_url)
|
|
||||||
return data
|
|
||||||
|
|
||||||
def test_webhook_update_no_option(self):
|
|
||||||
arglist = []
|
|
||||||
verifylist = []
|
|
||||||
|
|
||||||
self.assertRaises(osc_tests_utils.ParserException,
|
|
||||||
self.check_parser,
|
|
||||||
self.cmd, arglist, verifylist)
|
|
||||||
|
|
||||||
def test_webhook_update_required_options(self):
|
|
||||||
"""Update a webhook.
|
|
||||||
|
|
||||||
Do nothing as only the webhook id is specified.
|
|
||||||
"""
|
|
||||||
webhook_id = self._webhooks[0].id
|
|
||||||
attrs = {'id': webhook_id}
|
|
||||||
updated_data = self._update_fake_webhook(attrs)
|
|
||||||
|
|
||||||
arglist = [webhook_id]
|
|
||||||
verifylist = [
|
|
||||||
('id', webhook_id),
|
|
||||||
('function_id', None),
|
|
||||||
('function_version', None),
|
|
||||||
('description', None),
|
|
||||||
]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.client.webhooks.update.assert_called_once_with(
|
|
||||||
webhook_id,
|
|
||||||
**{'function_id': None,
|
|
||||||
'function_version': None,
|
|
||||||
'description': None}
|
|
||||||
)
|
|
||||||
self.assertEqual(self.columns, columns)
|
|
||||||
self.assertEqual(updated_data, data)
|
|
||||||
|
|
||||||
def test_webhook_update_all_options(self):
|
|
||||||
webhook_id = self._webhooks[0].id
|
|
||||||
function_id = self._webhooks[1].function_id
|
|
||||||
function_version = 1
|
|
||||||
webhook_description = 'This is a updated webhook.'
|
|
||||||
attrs = {'id': webhook_id, 'function_id': function_id,
|
|
||||||
'function_version': function_version,
|
|
||||||
'description': webhook_description}
|
|
||||||
updated_data = self._update_fake_webhook(attrs)
|
|
||||||
|
|
||||||
arglist = [webhook_id, '--function-id', function_id,
|
|
||||||
'--function-version', str(function_version),
|
|
||||||
'--description', webhook_description]
|
|
||||||
verifylist = [
|
|
||||||
('id', webhook_id),
|
|
||||||
('function_id', function_id),
|
|
||||||
('function_version', str(function_version)),
|
|
||||||
('description', webhook_description),
|
|
||||||
]
|
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
|
||||||
|
|
||||||
self.client.webhooks.update.assert_called_once_with(
|
|
||||||
webhook_id,
|
|
||||||
**{'function_id': function_id,
|
|
||||||
'function_version': str(function_version),
|
|
||||||
'description': webhook_description}
|
|
||||||
)
|
|
||||||
self.assertEqual(self.columns, columns)
|
|
||||||
self.assertEqual(updated_data, data)
|
|
||||||
|
|
||||||
def test_webhook_update_version_not_integer(self):
|
|
||||||
# function_version should be an integer value
|
|
||||||
webhook_id = self._webhooks[0].id
|
|
||||||
function_id = self._webhooks[0].function_id
|
|
||||||
|
|
||||||
arglist = [webhook_id, function_id,
|
|
||||||
'--function-version', 'NOT_A_INTEGER']
|
|
||||||
verifylist = [
|
|
||||||
('id', webhook_id),
|
|
||||||
('function_id', function_id),
|
|
||||||
('function_version', 'NOT_A_INTEGER'),
|
|
||||||
('description', None),
|
|
||||||
]
|
|
||||||
|
|
||||||
self.assertRaises(osc_tests_utils.ParserException,
|
|
||||||
self.check_parser,
|
|
||||||
self.cmd, arglist, verifylist)
|
|
|
@ -1,31 +0,0 @@
|
||||||
# Copyright 2018 AWCloud Software 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 keystoneauth1 import session
|
|
||||||
from requests_mock.contrib import fixture
|
|
||||||
|
|
||||||
from osc_lib.tests import utils
|
|
||||||
|
|
||||||
from qinlingclient.v1 import client
|
|
||||||
|
|
||||||
QINLING_URL = 'http://example.com:7070'
|
|
||||||
|
|
||||||
|
|
||||||
class TestQinlingClient(utils.TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestQinlingClient, self).setUp()
|
|
||||||
sess = session.Session()
|
|
||||||
self.client = client.Client(QINLING_URL, session=sess)
|
|
||||||
self.requests_mock = self.useFixture(fixture.Fixture())
|
|
|
@ -1,350 +0,0 @@
|
||||||
# Copyright 2018 AWCloud Software 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 io
|
|
||||||
from urllib.parse import urlencode
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
from oslo_serialization import jsonutils
|
|
||||||
|
|
||||||
from qinlingclient.common import exceptions
|
|
||||||
from qinlingclient.tests.unit.v1 import test_client
|
|
||||||
|
|
||||||
FUNCTION_1 = {'id': str(uuid.uuid4()), 'name': 'function_1'}
|
|
||||||
FUNCTION_2 = {'id': str(uuid.uuid4()), 'name': 'function_2'}
|
|
||||||
|
|
||||||
LIST_FUNCTIONS_RESP = {
|
|
||||||
'functions': [FUNCTION_1, FUNCTION_2]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class TestFunction(test_client.TestQinlingClient):
|
|
||||||
|
|
||||||
_error_message = "Test error message."
|
|
||||||
|
|
||||||
def test_list_function(self):
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'GET',
|
|
||||||
test_client.QINLING_URL + '/v1/functions',
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
json=LIST_FUNCTIONS_RESP,
|
|
||||||
status_code=200
|
|
||||||
)
|
|
||||||
ret = self.client.functions.list()
|
|
||||||
self.assertIsInstance(ret, list)
|
|
||||||
self.assertEqual(
|
|
||||||
[FUNCTION_1, FUNCTION_2],
|
|
||||||
[resource.to_dict() for resource in ret])
|
|
||||||
|
|
||||||
def test_list_function_with_params(self):
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'GET',
|
|
||||||
test_client.QINLING_URL + '/v1/functions?name=test&count=0',
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
json=LIST_FUNCTIONS_RESP,
|
|
||||||
status_code=200
|
|
||||||
)
|
|
||||||
ret = self.client.functions.list(name='test', count=0)
|
|
||||||
self.assertIsInstance(ret, list)
|
|
||||||
self.assertEqual(
|
|
||||||
[FUNCTION_1, FUNCTION_2],
|
|
||||||
[resource.to_dict() for resource in ret])
|
|
||||||
|
|
||||||
def test_create_function(self):
|
|
||||||
runtime_id = 'runtime_id'
|
|
||||||
code = {'source': 'package', 'md5sum': 'MD5SUM'}
|
|
||||||
data = {'runtime_id': runtime_id, 'code': jsonutils.dumps(code)}
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'POST',
|
|
||||||
test_client.QINLING_URL + '/v1/functions',
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
json=FUNCTION_1,
|
|
||||||
status_code=201
|
|
||||||
)
|
|
||||||
ret = self.client.functions.create(code, runtime=runtime_id)
|
|
||||||
self.assertEqual(FUNCTION_1, ret.to_dict())
|
|
||||||
self.assertEqual(urlencode(data), self.requests_mock.last_request.text)
|
|
||||||
|
|
||||||
def test_create_function_all_options(self):
|
|
||||||
runtime_id = 'runtime_id'
|
|
||||||
code = {'source': 'package', 'md5sum': 'MD5SUM'}
|
|
||||||
package_content = 'package file content'
|
|
||||||
package = io.StringIO(package_content)
|
|
||||||
cpu = '100'
|
|
||||||
memory_size = '33554432'
|
|
||||||
data = {'runtime_id': runtime_id, 'code': jsonutils.dumps(code),
|
|
||||||
'cpu': cpu, 'memory_size': memory_size}
|
|
||||||
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'POST',
|
|
||||||
test_client.QINLING_URL + '/v1/functions',
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
json=FUNCTION_1,
|
|
||||||
status_code=201
|
|
||||||
)
|
|
||||||
ret = self.client.functions.create(
|
|
||||||
code, runtime=runtime_id, package=package,
|
|
||||||
cpu=cpu, memory_size=memory_size)
|
|
||||||
self.assertEqual(FUNCTION_1, ret.to_dict())
|
|
||||||
|
|
||||||
# Request body is a multipart/form-data
|
|
||||||
request_body = self.requests_mock.last_request.body.decode('utf-8')
|
|
||||||
param_base_str = ('Content-Disposition: form-data; name="{key}"'
|
|
||||||
'\r\n\r\n{value}')
|
|
||||||
file_base_str = ('Content-Disposition: form-data; name="{name}"; '
|
|
||||||
'filename="{filename}"\r\n\r\n{content}')
|
|
||||||
for k, v in data.items():
|
|
||||||
param_str = param_base_str.format(key=k, value=v)
|
|
||||||
self.assertIn(param_str, request_body)
|
|
||||||
# filename is same as name since we use a StringIO instead of a
|
|
||||||
# real file object in this test.
|
|
||||||
file_str = file_base_str.format(name='package', filename='package',
|
|
||||||
content=package_content)
|
|
||||||
self.assertIn(file_str, request_body)
|
|
||||||
|
|
||||||
def test_create_function_error(self):
|
|
||||||
runtime_id = 'runtime_id'
|
|
||||||
code = {'source': 'package', 'md5sum': 'MD5SUM'}
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'POST',
|
|
||||||
test_client.QINLING_URL + '/v1/functions',
|
|
||||||
text='{"faultstring": "%s"}' % self._error_message,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
status_code=400
|
|
||||||
)
|
|
||||||
self.assertRaisesRegex(
|
|
||||||
exceptions.HTTPBadRequest,
|
|
||||||
self._error_message,
|
|
||||||
self.client.functions.create,
|
|
||||||
code, runtime=runtime_id)
|
|
||||||
|
|
||||||
def test_delete_function(self):
|
|
||||||
function_id = FUNCTION_1['id']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'DELETE',
|
|
||||||
test_client.QINLING_URL + '/v1/functions/%s' % function_id,
|
|
||||||
status_code=204
|
|
||||||
)
|
|
||||||
ret = self.client.functions.delete(function_id)
|
|
||||||
self.assertIsNone(ret)
|
|
||||||
|
|
||||||
def test_delete_function_error(self):
|
|
||||||
function_id = FUNCTION_1['id']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'DELETE',
|
|
||||||
test_client.QINLING_URL + '/v1/functions/%s' % function_id,
|
|
||||||
text='{"faultstring": "%s"}' % self._error_message,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
status_code=403
|
|
||||||
)
|
|
||||||
self.assertRaisesRegex(
|
|
||||||
exceptions.HTTPForbidden,
|
|
||||||
self._error_message,
|
|
||||||
self.client.functions.delete,
|
|
||||||
function_id
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_get_function(self):
|
|
||||||
function_id = FUNCTION_2['id']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'GET',
|
|
||||||
test_client.QINLING_URL + '/v1/functions/%s' % function_id,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
json=FUNCTION_2,
|
|
||||||
status_code=200
|
|
||||||
)
|
|
||||||
ret = self.client.functions.get(function_id)
|
|
||||||
self.assertEqual(FUNCTION_2, ret.to_dict())
|
|
||||||
self.assertFalse(self.requests_mock.last_request.stream)
|
|
||||||
|
|
||||||
def test_get_function_download(self):
|
|
||||||
function_id = FUNCTION_2['id']
|
|
||||||
function_data = 'function package data'
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'GET',
|
|
||||||
(test_client.QINLING_URL +
|
|
||||||
'/v1/functions/%s?download=true' % function_id),
|
|
||||||
headers={'Content-Type': 'application/zip'},
|
|
||||||
text=function_data,
|
|
||||||
status_code=200
|
|
||||||
)
|
|
||||||
ret = self.client.functions.get(function_id, download=True)
|
|
||||||
self.assertEqual(function_data, ret.text)
|
|
||||||
self.assertEqual('application/zip', ret.headers['content-type'])
|
|
||||||
self.assertTrue(self.requests_mock.last_request.stream)
|
|
||||||
|
|
||||||
def test_get_function_error(self):
|
|
||||||
function_id = FUNCTION_2['id']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'GET',
|
|
||||||
test_client.QINLING_URL + '/v1/functions/%s' % function_id,
|
|
||||||
text='{"faultstring": "%s"}' % self._error_message,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
status_code=404
|
|
||||||
)
|
|
||||||
self.assertRaisesRegex(
|
|
||||||
exceptions.HTTPNotFound,
|
|
||||||
self._error_message,
|
|
||||||
self.client.functions.get,
|
|
||||||
function_id
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_update_function(self):
|
|
||||||
function_id = FUNCTION_2['id']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'PUT',
|
|
||||||
test_client.QINLING_URL + '/v1/functions/%s' % function_id,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
json=FUNCTION_2,
|
|
||||||
status_code=200
|
|
||||||
)
|
|
||||||
ret = self.client.functions.update(function_id)
|
|
||||||
self.assertEqual(FUNCTION_2, ret.to_dict())
|
|
||||||
|
|
||||||
def test_update_function_all_options(self):
|
|
||||||
function_id = FUNCTION_2['id']
|
|
||||||
code = {'source': 'package'}
|
|
||||||
package_content = 'updated package file content'
|
|
||||||
package = io.StringIO(package_content)
|
|
||||||
cpu = '100'
|
|
||||||
memory_size = '33554432'
|
|
||||||
data = {'source': 'package', 'cpu': cpu, 'memory_size': memory_size}
|
|
||||||
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'PUT',
|
|
||||||
test_client.QINLING_URL + '/v1/functions/%s' % function_id,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
json=FUNCTION_2,
|
|
||||||
status_code=200
|
|
||||||
)
|
|
||||||
ret = self.client.functions.update(
|
|
||||||
function_id, code=code, package=package,
|
|
||||||
cpu=cpu, memory_size=memory_size)
|
|
||||||
self.assertEqual(FUNCTION_2, ret.to_dict())
|
|
||||||
|
|
||||||
# Request body is a multipart/form-data
|
|
||||||
request_body = self.requests_mock.last_request.body.decode('utf-8')
|
|
||||||
param_base_str = ('Content-Disposition: form-data; name="{key}"'
|
|
||||||
'\r\n\r\n{value}')
|
|
||||||
file_base_str = ('Content-Disposition: form-data; name="{name}"; '
|
|
||||||
'filename="{filename}"\r\n\r\n{content}')
|
|
||||||
for k, v in data.items():
|
|
||||||
param_str = param_base_str.format(key=k, value=v)
|
|
||||||
self.assertIn(param_str, request_body)
|
|
||||||
# filename is same as name since we use a StringIO instead of a
|
|
||||||
# real file object in this test.
|
|
||||||
file_str = file_base_str.format(name='package', filename='package',
|
|
||||||
content=package_content)
|
|
||||||
self.assertIn(file_str, request_body)
|
|
||||||
|
|
||||||
def test_update_function_error(self):
|
|
||||||
function_id = FUNCTION_2['id']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'PUT',
|
|
||||||
test_client.QINLING_URL + '/v1/functions/%s' % function_id,
|
|
||||||
text='{"faultstring": "%s"}' % self._error_message,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
status_code=403
|
|
||||||
)
|
|
||||||
self.assertRaisesRegex(
|
|
||||||
exceptions.HTTPForbidden,
|
|
||||||
self._error_message,
|
|
||||||
self.client.functions.update,
|
|
||||||
function_id)
|
|
||||||
|
|
||||||
def test_detach_function(self):
|
|
||||||
function_id = FUNCTION_2['id']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'POST',
|
|
||||||
test_client.QINLING_URL + '/v1/functions/%s/detach' % function_id,
|
|
||||||
status_code=202
|
|
||||||
)
|
|
||||||
ret = self.client.functions.detach(function_id)
|
|
||||||
self.assertEqual('', ret.text)
|
|
||||||
self.assertEqual(202, ret.status_code)
|
|
||||||
|
|
||||||
def test_detach_function_error(self):
|
|
||||||
function_id = FUNCTION_2['id']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'POST',
|
|
||||||
test_client.QINLING_URL + '/v1/functions/%s/detach' % function_id,
|
|
||||||
text='{"faultstring": "%s"}' % self._error_message,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
status_code=404
|
|
||||||
)
|
|
||||||
self.assertRaisesRegex(
|
|
||||||
exceptions.HTTPNotFound,
|
|
||||||
self._error_message,
|
|
||||||
self.client.functions.detach,
|
|
||||||
function_id)
|
|
||||||
|
|
||||||
def test_scaleup_function(self):
|
|
||||||
function_id = FUNCTION_1['id']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'POST',
|
|
||||||
(test_client.QINLING_URL +
|
|
||||||
'/v1/functions/%s/scale_up' % function_id),
|
|
||||||
status_code=202
|
|
||||||
)
|
|
||||||
resp, text = self.client.functions.scaleup(function_id)
|
|
||||||
self.assertEqual('', text)
|
|
||||||
self.assertEqual(202, resp.status_code)
|
|
||||||
self.assertEqual(jsonutils.dumps({'count': 1}),
|
|
||||||
self.requests_mock.last_request.text)
|
|
||||||
|
|
||||||
def test_scaleup_function_error(self):
|
|
||||||
function_id = FUNCTION_1['id']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'POST',
|
|
||||||
(test_client.QINLING_URL +
|
|
||||||
'/v1/functions/%s/scale_up' % function_id),
|
|
||||||
text='{"faultstring": "%s"}' % self._error_message,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
status_code=404
|
|
||||||
)
|
|
||||||
self.assertRaisesRegex(
|
|
||||||
exceptions.HTTPNotFound,
|
|
||||||
self._error_message,
|
|
||||||
self.client.functions.scaleup,
|
|
||||||
function_id)
|
|
||||||
|
|
||||||
def test_scaledown_function(self):
|
|
||||||
function_id = FUNCTION_1['id']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'POST',
|
|
||||||
(test_client.QINLING_URL +
|
|
||||||
'/v1/functions/%s/scale_down' % function_id),
|
|
||||||
status_code=202
|
|
||||||
)
|
|
||||||
resp, text = self.client.functions.scaledown(function_id, count=2)
|
|
||||||
self.assertEqual('', text)
|
|
||||||
self.assertEqual(202, resp.status_code)
|
|
||||||
self.assertEqual(jsonutils.dumps({'count': 2}),
|
|
||||||
self.requests_mock.last_request.text)
|
|
||||||
|
|
||||||
def test_scaledown_function_error(self):
|
|
||||||
function_id = FUNCTION_1['id']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'POST',
|
|
||||||
(test_client.QINLING_URL +
|
|
||||||
'/v1/functions/%s/scale_down' % function_id),
|
|
||||||
text='{"faultstring": "%s"}' % self._error_message,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
status_code=404
|
|
||||||
)
|
|
||||||
self.assertRaisesRegex(
|
|
||||||
exceptions.HTTPNotFound,
|
|
||||||
self._error_message,
|
|
||||||
self.client.functions.scaledown,
|
|
||||||
function_id)
|
|
|
@ -1,206 +0,0 @@
|
||||||
# Copyright 2018 AWCloud Software 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 uuid
|
|
||||||
|
|
||||||
from oslo_serialization import jsonutils
|
|
||||||
|
|
||||||
from qinlingclient.common import exceptions
|
|
||||||
from qinlingclient.tests.unit.v1 import test_client
|
|
||||||
|
|
||||||
ALIAS_1 = {'name': 'function_alias_1', 'function_id': str(uuid.uuid4())}
|
|
||||||
ALIAS_2 = {'name': 'function_alias_2', 'function_id': str(uuid.uuid4())}
|
|
||||||
|
|
||||||
LIST_FUNCTION_ALIASES_RESP = {
|
|
||||||
'function_aliases': [ALIAS_1, ALIAS_2]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class TestFunctionAlias(test_client.TestQinlingClient):
|
|
||||||
|
|
||||||
_error_message = "Test error message."
|
|
||||||
|
|
||||||
def test_list_function_alias(self):
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'GET',
|
|
||||||
test_client.QINLING_URL + '/v1/aliases',
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
json=LIST_FUNCTION_ALIASES_RESP,
|
|
||||||
status_code=200
|
|
||||||
)
|
|
||||||
ret = self.client.function_aliases.list()
|
|
||||||
self.assertIsInstance(ret, list)
|
|
||||||
self.assertEqual(
|
|
||||||
[ALIAS_1, ALIAS_2],
|
|
||||||
[resource.to_dict() for resource in ret])
|
|
||||||
|
|
||||||
def test_create_function_alias(self):
|
|
||||||
name = ALIAS_1['name']
|
|
||||||
function_id = ALIAS_1['function_id']
|
|
||||||
request_data = {'name': name, 'function_id': function_id,
|
|
||||||
'function_version': 0, 'description': ''}
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'POST',
|
|
||||||
test_client.QINLING_URL + '/v1/aliases',
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
json=ALIAS_1,
|
|
||||||
status_code=201
|
|
||||||
)
|
|
||||||
ret = self.client.function_aliases.create(name, function_id)
|
|
||||||
self.assertEqual(ALIAS_1, ret.to_dict())
|
|
||||||
self.assertEqual(jsonutils.dumps(request_data),
|
|
||||||
self.requests_mock.last_request.text)
|
|
||||||
|
|
||||||
def test_create_function_alias_all_options(self):
|
|
||||||
name = ALIAS_1['name']
|
|
||||||
function_id = ALIAS_1['function_id']
|
|
||||||
function_version = 1
|
|
||||||
description = 'A newly created function alias.'
|
|
||||||
request_data = {'name': name, 'function_id': function_id,
|
|
||||||
'function_version': function_version,
|
|
||||||
'description': description}
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'POST',
|
|
||||||
test_client.QINLING_URL + '/v1/aliases',
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
json=ALIAS_1,
|
|
||||||
status_code=201
|
|
||||||
)
|
|
||||||
ret = self.client.function_aliases.create(
|
|
||||||
name, function_id, function_version=function_version,
|
|
||||||
description=description
|
|
||||||
)
|
|
||||||
self.assertEqual(ALIAS_1, ret.to_dict())
|
|
||||||
self.assertEqual(jsonutils.dumps(request_data),
|
|
||||||
self.requests_mock.last_request.text)
|
|
||||||
|
|
||||||
def test_create_function_alias_error(self):
|
|
||||||
name = ALIAS_1['name']
|
|
||||||
function_id = ALIAS_1['function_id']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'POST',
|
|
||||||
test_client.QINLING_URL + '/v1/aliases',
|
|
||||||
text='{"faultstring": "%s"}' % self._error_message,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
status_code=400
|
|
||||||
)
|
|
||||||
self.assertRaisesRegex(
|
|
||||||
exceptions.HTTPBadRequest,
|
|
||||||
self._error_message,
|
|
||||||
self.client.function_aliases.create,
|
|
||||||
name, function_id)
|
|
||||||
|
|
||||||
def test_delete_function_alias(self):
|
|
||||||
name = ALIAS_1['name']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'DELETE',
|
|
||||||
test_client.QINLING_URL + '/v1/aliases/%s' % name,
|
|
||||||
status_code=204
|
|
||||||
)
|
|
||||||
ret = self.client.function_aliases.delete(name)
|
|
||||||
self.assertIsNone(ret)
|
|
||||||
|
|
||||||
def test_delete_function_alias_error(self):
|
|
||||||
name = ALIAS_1['name']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'DELETE',
|
|
||||||
test_client.QINLING_URL + '/v1/aliases/%s' % name,
|
|
||||||
text='{"faultstring": "%s"}' % self._error_message,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
status_code=404
|
|
||||||
)
|
|
||||||
self.assertRaisesRegex(
|
|
||||||
exceptions.HTTPNotFound,
|
|
||||||
self._error_message,
|
|
||||||
self.client.function_aliases.delete,
|
|
||||||
name
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_get_function_alias(self):
|
|
||||||
name = ALIAS_2['name']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'GET',
|
|
||||||
test_client.QINLING_URL + '/v1/aliases/%s' % name,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
json=ALIAS_2,
|
|
||||||
status_code=200
|
|
||||||
)
|
|
||||||
ret = self.client.function_aliases.get(name)
|
|
||||||
self.assertEqual(ALIAS_2, ret.to_dict())
|
|
||||||
|
|
||||||
def test_get_function_alias_error(self):
|
|
||||||
name = ALIAS_2['name']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'GET',
|
|
||||||
test_client.QINLING_URL + '/v1/aliases/%s' % name,
|
|
||||||
text='{"faultstring": "%s"}' % self._error_message,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
status_code=404
|
|
||||||
)
|
|
||||||
self.assertRaisesRegex(
|
|
||||||
exceptions.HTTPNotFound,
|
|
||||||
self._error_message,
|
|
||||||
self.client.function_aliases.get,
|
|
||||||
name
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_update_function_alias(self):
|
|
||||||
name = ALIAS_2['name']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'PUT',
|
|
||||||
test_client.QINLING_URL + '/v1/aliases/%s' % name,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
json=ALIAS_2,
|
|
||||||
status_code=200
|
|
||||||
)
|
|
||||||
ret = self.client.function_aliases.update(name)
|
|
||||||
self.assertEqual(ALIAS_2, ret.to_dict())
|
|
||||||
|
|
||||||
def test_update_function_alias_all_options(self):
|
|
||||||
name = ALIAS_2['name']
|
|
||||||
function_id = ALIAS_2['function_id']
|
|
||||||
function_version = 2
|
|
||||||
description = 'An updated function alias.'
|
|
||||||
request_data = {'function_id': function_id,
|
|
||||||
'function_version': function_version,
|
|
||||||
'description': description}
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'PUT',
|
|
||||||
test_client.QINLING_URL + '/v1/aliases/%s' % name,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
json=ALIAS_2,
|
|
||||||
status_code=200
|
|
||||||
)
|
|
||||||
ret = self.client.function_aliases.update(
|
|
||||||
name, function_id=function_id, function_version=function_version,
|
|
||||||
description=description
|
|
||||||
)
|
|
||||||
self.assertEqual(ALIAS_2, ret.to_dict())
|
|
||||||
self.assertEqual(jsonutils.dumps(request_data),
|
|
||||||
self.requests_mock.last_request.text)
|
|
||||||
|
|
||||||
def test_update_function_alias_error(self):
|
|
||||||
name = ALIAS_2['name']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'PUT',
|
|
||||||
test_client.QINLING_URL + '/v1/aliases/%s' % name,
|
|
||||||
text='{"faultstring": "%s"}' % self._error_message,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
status_code=404
|
|
||||||
)
|
|
||||||
self.assertRaisesRegex(
|
|
||||||
exceptions.HTTPNotFound,
|
|
||||||
self._error_message,
|
|
||||||
self.client.function_aliases.update,
|
|
||||||
name)
|
|
|
@ -1,199 +0,0 @@
|
||||||
# Copyright 2018 AWCloud Software 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 uuid
|
|
||||||
|
|
||||||
from oslo_serialization import jsonutils
|
|
||||||
|
|
||||||
from qinlingclient.common import exceptions
|
|
||||||
from qinlingclient.tests.unit.v1 import test_client
|
|
||||||
|
|
||||||
EXECUTION_1 = {'id': str(uuid.uuid4()), 'function_id': str(uuid.uuid4())}
|
|
||||||
EXECUTION_2 = {'id': str(uuid.uuid4()), 'function_id': str(uuid.uuid4())}
|
|
||||||
EXECUTION_3 = {'id': str(uuid.uuid4()), 'function_alias': 'alias_1'}
|
|
||||||
|
|
||||||
LIST_FUNCTION_EXECUTIONS_RESP = {
|
|
||||||
'executions': [EXECUTION_1, EXECUTION_2]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class TestFunctionExecution(test_client.TestQinlingClient):
|
|
||||||
|
|
||||||
_error_message = "Test error message."
|
|
||||||
|
|
||||||
def test_list_function_execution(self):
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'GET',
|
|
||||||
test_client.QINLING_URL + '/v1/executions',
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
json=LIST_FUNCTION_EXECUTIONS_RESP,
|
|
||||||
status_code=200
|
|
||||||
)
|
|
||||||
ret = self.client.function_executions.list()
|
|
||||||
self.assertIsInstance(ret, list)
|
|
||||||
self.assertEqual(
|
|
||||||
[EXECUTION_1, EXECUTION_2],
|
|
||||||
[resource.to_dict() for resource in ret])
|
|
||||||
|
|
||||||
def test_list_function_execution_with_params(self):
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'GET',
|
|
||||||
(test_client.QINLING_URL +
|
|
||||||
'/v1/executions?status=success&sync=True'),
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
json=LIST_FUNCTION_EXECUTIONS_RESP,
|
|
||||||
status_code=200
|
|
||||||
)
|
|
||||||
ret = self.client.function_executions.list(status='success', sync=True)
|
|
||||||
self.assertIsInstance(ret, list)
|
|
||||||
self.assertEqual(
|
|
||||||
[EXECUTION_1, EXECUTION_2],
|
|
||||||
[resource.to_dict() for resource in ret])
|
|
||||||
|
|
||||||
def test_create_function_execution(self):
|
|
||||||
function_id = EXECUTION_1['function_id']
|
|
||||||
request_data = {'function_id': function_id, 'function_version': 0,
|
|
||||||
'function_alias': None, 'sync': True, 'input': None}
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'POST',
|
|
||||||
test_client.QINLING_URL + '/v1/executions',
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
json=EXECUTION_1,
|
|
||||||
status_code=201
|
|
||||||
)
|
|
||||||
ret = self.client.function_executions.create(function_id)
|
|
||||||
self.assertEqual(EXECUTION_1, ret.to_dict())
|
|
||||||
self.assertEqual(jsonutils.dumps(request_data),
|
|
||||||
self.requests_mock.last_request.text)
|
|
||||||
|
|
||||||
def test_create_function_execution_all_options(self):
|
|
||||||
function_id = EXECUTION_1['function_id']
|
|
||||||
function_version = 1
|
|
||||||
sync = False
|
|
||||||
function_input = '{"name": "Qinling"}'
|
|
||||||
request_data = {'function_id': function_id,
|
|
||||||
'function_version': function_version,
|
|
||||||
'function_alias': None,
|
|
||||||
'sync': sync, 'input': function_input}
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'POST',
|
|
||||||
test_client.QINLING_URL + '/v1/executions',
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
json=EXECUTION_1,
|
|
||||||
status_code=201
|
|
||||||
)
|
|
||||||
ret = self.client.function_executions.create(
|
|
||||||
function_id, function_version=function_version, sync=sync,
|
|
||||||
input=function_input)
|
|
||||||
self.assertEqual(EXECUTION_1, ret.to_dict())
|
|
||||||
self.assertEqual(jsonutils.dumps(request_data),
|
|
||||||
self.requests_mock.last_request.text)
|
|
||||||
|
|
||||||
def test_create_function_execution_error(self):
|
|
||||||
function_id = EXECUTION_1['function_id']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'POST',
|
|
||||||
test_client.QINLING_URL + '/v1/executions',
|
|
||||||
text='{"faultstring": "%s"}' % self._error_message,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
status_code=400
|
|
||||||
)
|
|
||||||
self.assertRaisesRegex(
|
|
||||||
exceptions.HTTPBadRequest,
|
|
||||||
self._error_message,
|
|
||||||
self.client.function_executions.create,
|
|
||||||
function_id)
|
|
||||||
|
|
||||||
def test_delete_function_execution(self):
|
|
||||||
execution_id = EXECUTION_1['id']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'DELETE',
|
|
||||||
test_client.QINLING_URL + '/v1/executions/%s' % execution_id,
|
|
||||||
status_code=204
|
|
||||||
)
|
|
||||||
ret = self.client.function_executions.delete(execution_id)
|
|
||||||
self.assertIsNone(ret)
|
|
||||||
|
|
||||||
def test_delete_function_execution_error(self):
|
|
||||||
execution_id = EXECUTION_1['id']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'DELETE',
|
|
||||||
test_client.QINLING_URL + '/v1/executions/%s' % execution_id,
|
|
||||||
text='{"faultstring": "%s"}' % self._error_message,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
status_code=404
|
|
||||||
)
|
|
||||||
self.assertRaisesRegex(
|
|
||||||
exceptions.HTTPNotFound,
|
|
||||||
self._error_message,
|
|
||||||
self.client.function_executions.delete,
|
|
||||||
execution_id
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_get_function_execution(self):
|
|
||||||
execution_id = EXECUTION_2['id']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'GET',
|
|
||||||
test_client.QINLING_URL + '/v1/executions/%s' % execution_id,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
json=EXECUTION_2,
|
|
||||||
status_code=200
|
|
||||||
)
|
|
||||||
ret = self.client.function_executions.get(execution_id)
|
|
||||||
self.assertEqual(EXECUTION_2, ret.to_dict())
|
|
||||||
|
|
||||||
def test_get_function_execution_error(self):
|
|
||||||
execution_id = EXECUTION_2['id']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'GET',
|
|
||||||
test_client.QINLING_URL + '/v1/executions/%s' % execution_id,
|
|
||||||
text='{"faultstring": "%s"}' % self._error_message,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
status_code=404
|
|
||||||
)
|
|
||||||
self.assertRaisesRegex(
|
|
||||||
exceptions.HTTPNotFound,
|
|
||||||
self._error_message,
|
|
||||||
self.client.function_executions.get,
|
|
||||||
execution_id
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_get_function_execution_log(self):
|
|
||||||
execution_id = EXECUTION_2['id']
|
|
||||||
execution_log = 'Preparing...\nRunning...\nDone.'
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'GET',
|
|
||||||
test_client.QINLING_URL + '/v1/executions/%s/log' % execution_id,
|
|
||||||
text=execution_log,
|
|
||||||
headers={'Content-Type': 'text/plain'},
|
|
||||||
status_code=200
|
|
||||||
)
|
|
||||||
ret = self.client.function_executions.get_log(execution_id)
|
|
||||||
self.assertEqual(execution_log, ret)
|
|
||||||
|
|
||||||
def test_get_function_execution_log_error(self):
|
|
||||||
execution_id = EXECUTION_2['id']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'GET',
|
|
||||||
test_client.QINLING_URL + '/v1/executions/%s/log' % execution_id,
|
|
||||||
text='{"faultstring": "%s"}' % self._error_message,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
status_code=404
|
|
||||||
)
|
|
||||||
self.assertRaisesRegex(
|
|
||||||
exceptions.HTTPNotFound,
|
|
||||||
self._error_message,
|
|
||||||
self.client.function_executions.get_log,
|
|
||||||
execution_id
|
|
||||||
)
|
|
|
@ -1,191 +0,0 @@
|
||||||
# Copyright 2018 AWCloud Software 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 uuid
|
|
||||||
|
|
||||||
from oslo_serialization import jsonutils
|
|
||||||
|
|
||||||
from qinlingclient.common import exceptions
|
|
||||||
from qinlingclient.tests.unit.v1 import test_client
|
|
||||||
|
|
||||||
VERSION_1 = {'id': str(uuid.uuid4()), 'function_id': str(uuid.uuid4()),
|
|
||||||
'version_number': 1}
|
|
||||||
VERSION_2 = {'id': str(uuid.uuid4()), 'function_id': VERSION_1['function_id'],
|
|
||||||
'version_number': 2}
|
|
||||||
|
|
||||||
LIST_FUNCTION_VERSIONS_RESP = {
|
|
||||||
'function_versions': [VERSION_1, VERSION_2]
|
|
||||||
}
|
|
||||||
|
|
||||||
URL_TEMPLATE = "/v1/functions/%s/versions"
|
|
||||||
|
|
||||||
|
|
||||||
class TestFunctionVersion(test_client.TestQinlingClient):
|
|
||||||
|
|
||||||
_error_message = "Test error message."
|
|
||||||
|
|
||||||
def test_list_function_version(self):
|
|
||||||
function_id = VERSION_1['function_id']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'GET',
|
|
||||||
test_client.QINLING_URL + URL_TEMPLATE % function_id,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
json=LIST_FUNCTION_VERSIONS_RESP,
|
|
||||||
status_code=200
|
|
||||||
)
|
|
||||||
ret = self.client.function_versions.list(function_id)
|
|
||||||
self.assertIsInstance(ret, list)
|
|
||||||
self.assertEqual(
|
|
||||||
[VERSION_1, VERSION_2],
|
|
||||||
[resource.to_dict() for resource in ret])
|
|
||||||
|
|
||||||
def test_create_function_version(self):
|
|
||||||
function_id = VERSION_1['function_id']
|
|
||||||
request_data = {'description': ''}
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'POST',
|
|
||||||
test_client.QINLING_URL + URL_TEMPLATE % function_id,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
json=VERSION_1,
|
|
||||||
status_code=201
|
|
||||||
)
|
|
||||||
ret = self.client.function_versions.create(function_id)
|
|
||||||
self.assertEqual(VERSION_1, ret.to_dict())
|
|
||||||
self.assertEqual(jsonutils.dumps(request_data),
|
|
||||||
self.requests_mock.last_request.text)
|
|
||||||
|
|
||||||
def test_create_function_version_all_options(self):
|
|
||||||
function_id = VERSION_1['function_id']
|
|
||||||
description = 'This a newly created function version.'
|
|
||||||
request_data = {'description': description}
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'POST',
|
|
||||||
test_client.QINLING_URL + URL_TEMPLATE % function_id,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
json=VERSION_1,
|
|
||||||
status_code=201
|
|
||||||
)
|
|
||||||
ret = self.client.function_versions.create(
|
|
||||||
function_id, description=description)
|
|
||||||
self.assertEqual(VERSION_1, ret.to_dict())
|
|
||||||
self.assertEqual(jsonutils.dumps(request_data),
|
|
||||||
self.requests_mock.last_request.text)
|
|
||||||
|
|
||||||
def test_create_function_version_error(self):
|
|
||||||
function_id = VERSION_1['function_id']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'POST',
|
|
||||||
test_client.QINLING_URL + URL_TEMPLATE % function_id,
|
|
||||||
text='{"faultstring": "%s"}' % self._error_message,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
status_code=500
|
|
||||||
)
|
|
||||||
self.assertRaisesRegex(
|
|
||||||
exceptions.HTTPInternalServerError,
|
|
||||||
self._error_message,
|
|
||||||
self.client.function_versions.create,
|
|
||||||
function_id)
|
|
||||||
|
|
||||||
def test_delete_function_version(self):
|
|
||||||
function_id = VERSION_1['function_id']
|
|
||||||
version = VERSION_1['version_number']
|
|
||||||
url = URL_TEMPLATE % function_id + '/%s' % version
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'DELETE',
|
|
||||||
test_client.QINLING_URL + url,
|
|
||||||
status_code=204
|
|
||||||
)
|
|
||||||
ret = self.client.function_versions.delete(function_id, version)
|
|
||||||
self.assertIsNone(ret)
|
|
||||||
|
|
||||||
def test_delete_function_version_error(self):
|
|
||||||
function_id = VERSION_1['function_id']
|
|
||||||
version = VERSION_1['version_number']
|
|
||||||
url = URL_TEMPLATE % function_id + '/%s' % version
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'DELETE',
|
|
||||||
test_client.QINLING_URL + url,
|
|
||||||
text='{"faultstring": "%s"}' % self._error_message,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
status_code=403
|
|
||||||
)
|
|
||||||
self.assertRaisesRegex(
|
|
||||||
exceptions.HTTPForbidden,
|
|
||||||
self._error_message,
|
|
||||||
self.client.function_versions.delete,
|
|
||||||
function_id, version
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_get_function_version(self):
|
|
||||||
function_id = VERSION_2['function_id']
|
|
||||||
version = VERSION_2['version_number']
|
|
||||||
url = URL_TEMPLATE % function_id + '/%s' % version
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'GET',
|
|
||||||
test_client.QINLING_URL + url,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
json=VERSION_2,
|
|
||||||
status_code=200
|
|
||||||
)
|
|
||||||
ret = self.client.function_versions.get(function_id, version)
|
|
||||||
self.assertEqual(VERSION_2, ret.to_dict())
|
|
||||||
|
|
||||||
def test_get_function_version_error(self):
|
|
||||||
function_id = VERSION_2['function_id']
|
|
||||||
version = VERSION_2['version_number']
|
|
||||||
url = URL_TEMPLATE % function_id + '/%s' % version
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'GET',
|
|
||||||
test_client.QINLING_URL + url,
|
|
||||||
text='{"faultstring": "%s"}' % self._error_message,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
status_code=404
|
|
||||||
)
|
|
||||||
self.assertRaisesRegex(
|
|
||||||
exceptions.HTTPNotFound,
|
|
||||||
self._error_message,
|
|
||||||
self.client.function_versions.get,
|
|
||||||
function_id, version
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_detach_function_version(self):
|
|
||||||
function_id = VERSION_2['function_id']
|
|
||||||
version = VERSION_2['version_number']
|
|
||||||
url = URL_TEMPLATE % function_id + '/%s/detach' % version
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'POST',
|
|
||||||
test_client.QINLING_URL + url,
|
|
||||||
status_code=202
|
|
||||||
)
|
|
||||||
ret = self.client.function_versions.detach(function_id, version)
|
|
||||||
self.assertEqual('', ret.text)
|
|
||||||
self.assertEqual(202, ret.status_code)
|
|
||||||
|
|
||||||
def test_detach_function_version_error(self):
|
|
||||||
function_id = VERSION_2['function_id']
|
|
||||||
version = VERSION_2['version_number']
|
|
||||||
url = URL_TEMPLATE % function_id + '/%s/detach' % version
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'POST',
|
|
||||||
test_client.QINLING_URL + url,
|
|
||||||
text='{"faultstring": "%s"}' % self._error_message,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
status_code=404
|
|
||||||
)
|
|
||||||
self.assertRaisesRegex(
|
|
||||||
exceptions.HTTPNotFound,
|
|
||||||
self._error_message,
|
|
||||||
self.client.function_versions.detach,
|
|
||||||
function_id, version
|
|
||||||
)
|
|
|
@ -1,42 +0,0 @@
|
||||||
# Copyright 2018 AWCloud Software 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 uuid
|
|
||||||
|
|
||||||
from qinlingclient.tests.unit.v1 import test_client
|
|
||||||
|
|
||||||
WORKER_1 = {'function_id': str(uuid.uuid4()), 'worker_name': 'worker_1'}
|
|
||||||
WORKER_2 = {'function_id': WORKER_1['function_id'], 'worker_name': 'worker_2'}
|
|
||||||
|
|
||||||
LIST_FUNCTION_WORKERS_RESP = {
|
|
||||||
'workers': [WORKER_1, WORKER_2]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class TestFunctionWorker(test_client.TestQinlingClient):
|
|
||||||
|
|
||||||
def test_list_function_worker(self):
|
|
||||||
function_id = WORKER_1['function_id']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'GET',
|
|
||||||
test_client.QINLING_URL + '/v1/functions/%s/workers' % function_id,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
json=LIST_FUNCTION_WORKERS_RESP,
|
|
||||||
status_code=200
|
|
||||||
)
|
|
||||||
ret = self.client.function_workers.list(function_id)
|
|
||||||
self.assertIsInstance(ret, list)
|
|
||||||
self.assertEqual(
|
|
||||||
[WORKER_1, WORKER_2],
|
|
||||||
[resource.to_dict() for resource in ret])
|
|
|
@ -1,220 +0,0 @@
|
||||||
# Copyright 2018 AWCloud Software 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 uuid
|
|
||||||
|
|
||||||
from oslo_serialization import jsonutils
|
|
||||||
|
|
||||||
from qinlingclient.common import exceptions
|
|
||||||
from qinlingclient.tests.unit.v1 import test_client
|
|
||||||
|
|
||||||
JOB_1 = {'id': str(uuid.uuid4()), 'name': 'job_1',
|
|
||||||
'function_id': str(uuid.uuid4())}
|
|
||||||
JOB_2 = {'id': str(uuid.uuid4()), 'name': 'job_2',
|
|
||||||
'function_id': JOB_1['function_id']}
|
|
||||||
JOB_3 = {
|
|
||||||
'id': str(uuid.uuid4()),
|
|
||||||
'name': 'job_2',
|
|
||||||
'function_alias': 'alias_1',
|
|
||||||
}
|
|
||||||
|
|
||||||
LIST_JOBS_RESP = {
|
|
||||||
'jobs': [JOB_1, JOB_2]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class TestJob(test_client.TestQinlingClient):
|
|
||||||
|
|
||||||
_error_message = "Test error message."
|
|
||||||
|
|
||||||
def test_list_job(self):
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'GET',
|
|
||||||
test_client.QINLING_URL + '/v1/jobs',
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
json=LIST_JOBS_RESP,
|
|
||||||
status_code=200
|
|
||||||
)
|
|
||||||
ret = self.client.jobs.list()
|
|
||||||
self.assertIsInstance(ret, list)
|
|
||||||
self.assertEqual(
|
|
||||||
[JOB_1, JOB_2],
|
|
||||||
[resource.to_dict() for resource in ret])
|
|
||||||
|
|
||||||
def test_create_job(self):
|
|
||||||
function_id = JOB_1['function_id']
|
|
||||||
request_data = {'function_alias': None, 'function_id': function_id,
|
|
||||||
'function_version': 0,
|
|
||||||
'name': None, 'first_execution_time': None,
|
|
||||||
'pattern': None, 'function_input': None,
|
|
||||||
'count': None}
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'POST',
|
|
||||||
test_client.QINLING_URL + '/v1/jobs',
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
json=JOB_1,
|
|
||||||
status_code=201
|
|
||||||
)
|
|
||||||
|
|
||||||
ret = self.client.jobs.create(function_id=function_id)
|
|
||||||
|
|
||||||
self.assertEqual(JOB_1, ret.to_dict())
|
|
||||||
self.assertEqual(jsonutils.dumps(request_data),
|
|
||||||
self.requests_mock.last_request.text)
|
|
||||||
|
|
||||||
def test_create_job_all_options(self):
|
|
||||||
function_id = JOB_1['function_id']
|
|
||||||
function_version = 1
|
|
||||||
name = JOB_1['name']
|
|
||||||
first_execution_time = '2018-08-16T08:00:00'
|
|
||||||
pattern = '0 * * * *'
|
|
||||||
function_input = '{"name": "Qinling"}'
|
|
||||||
count = 3
|
|
||||||
request_data = {'function_alias': None,
|
|
||||||
'function_id': function_id,
|
|
||||||
'function_version': function_version,
|
|
||||||
'name': name,
|
|
||||||
'first_execution_time': first_execution_time,
|
|
||||||
'pattern': pattern, 'function_input': function_input,
|
|
||||||
'count': count}
|
|
||||||
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'POST',
|
|
||||||
test_client.QINLING_URL + '/v1/jobs',
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
json=JOB_1,
|
|
||||||
status_code=201
|
|
||||||
)
|
|
||||||
|
|
||||||
ret = self.client.jobs.create(
|
|
||||||
function_id=function_id, function_version=function_version,
|
|
||||||
name=name,
|
|
||||||
first_execution_time=first_execution_time, pattern=pattern,
|
|
||||||
function_input=function_input, count=count)
|
|
||||||
|
|
||||||
self.assertEqual(JOB_1, ret.to_dict())
|
|
||||||
self.assertEqual(jsonutils.dumps(request_data),
|
|
||||||
self.requests_mock.last_request.text)
|
|
||||||
|
|
||||||
def test_create_job_error(self):
|
|
||||||
function_id = JOB_1['function_id']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'POST',
|
|
||||||
test_client.QINLING_URL + '/v1/jobs',
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
text='{"faultstring": "%s"}' % self._error_message,
|
|
||||||
status_code=400
|
|
||||||
)
|
|
||||||
self.assertRaisesRegex(
|
|
||||||
exceptions.HTTPBadRequest,
|
|
||||||
self._error_message,
|
|
||||||
self.client.jobs.create,
|
|
||||||
function_id)
|
|
||||||
|
|
||||||
def test_delete_job(self):
|
|
||||||
job_id = JOB_1['id']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'DELETE',
|
|
||||||
test_client.QINLING_URL + '/v1/jobs/%s' % job_id,
|
|
||||||
status_code=204
|
|
||||||
)
|
|
||||||
ret = self.client.jobs.delete(job_id)
|
|
||||||
self.assertIsNone(ret)
|
|
||||||
|
|
||||||
def test_delete_job_error(self):
|
|
||||||
job_id = JOB_1['id']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'DELETE',
|
|
||||||
test_client.QINLING_URL + '/v1/jobs/%s' % job_id,
|
|
||||||
text='{"faultstring": "%s"}' % self._error_message,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
status_code=404
|
|
||||||
)
|
|
||||||
self.assertRaisesRegex(
|
|
||||||
exceptions.HTTPNotFound,
|
|
||||||
self._error_message,
|
|
||||||
self.client.jobs.delete,
|
|
||||||
job_id)
|
|
||||||
|
|
||||||
def test_get_job(self):
|
|
||||||
job_id = JOB_2['id']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'GET',
|
|
||||||
test_client.QINLING_URL + '/v1/jobs/%s' % job_id,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
json=JOB_2,
|
|
||||||
status_code=200
|
|
||||||
)
|
|
||||||
ret = self.client.jobs.get(job_id)
|
|
||||||
self.assertEqual(JOB_2, ret.to_dict())
|
|
||||||
|
|
||||||
def test_get_job_error(self):
|
|
||||||
job_id = JOB_2['id']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'GET',
|
|
||||||
test_client.QINLING_URL + '/v1/jobs/%s' % job_id,
|
|
||||||
text='{"faultstring": "%s"}' % self._error_message,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
status_code=404
|
|
||||||
)
|
|
||||||
self.assertRaisesRegex(
|
|
||||||
exceptions.HTTPNotFound,
|
|
||||||
self._error_message,
|
|
||||||
self.client.jobs.get,
|
|
||||||
job_id)
|
|
||||||
|
|
||||||
def test_update_job(self):
|
|
||||||
job_id = JOB_2['id']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'PUT',
|
|
||||||
test_client.QINLING_URL + '/v1/jobs/%s' % job_id,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
json=JOB_2,
|
|
||||||
status_code=200
|
|
||||||
)
|
|
||||||
ret = self.client.jobs.update(job_id)
|
|
||||||
self.assertEqual(JOB_2, ret.to_dict())
|
|
||||||
|
|
||||||
def test_update_job_with_params(self):
|
|
||||||
job_id = JOB_2['id']
|
|
||||||
name = 'renamed_job'
|
|
||||||
pattern = '0 1 * * *'
|
|
||||||
request_data = {'name': name, 'pattern': pattern}
|
|
||||||
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'PUT',
|
|
||||||
test_client.QINLING_URL + '/v1/jobs/%s' % job_id,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
json=JOB_2,
|
|
||||||
status_code=200
|
|
||||||
)
|
|
||||||
ret = self.client.jobs.update(job_id, name=name, pattern=pattern)
|
|
||||||
self.assertEqual(JOB_2, ret.to_dict())
|
|
||||||
self.assertEqual(jsonutils.dumps(request_data),
|
|
||||||
self.requests_mock.last_request.text)
|
|
||||||
|
|
||||||
def test_update_job_error(self):
|
|
||||||
job_id = JOB_2['id']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'PUT',
|
|
||||||
test_client.QINLING_URL + '/v1/jobs/%s' % job_id,
|
|
||||||
text='{"faultstring": "%s"}' % self._error_message,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
status_code=400
|
|
||||||
)
|
|
||||||
self.assertRaisesRegex(
|
|
||||||
exceptions.HTTPBadRequest,
|
|
||||||
self._error_message,
|
|
||||||
self.client.jobs.update,
|
|
||||||
job_id)
|
|
|
@ -1,191 +0,0 @@
|
||||||
# Copyright 2018 AWCloud Software 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 uuid
|
|
||||||
|
|
||||||
from oslo_serialization import jsonutils
|
|
||||||
|
|
||||||
from qinlingclient.common import exceptions
|
|
||||||
from qinlingclient.tests.unit.v1 import test_client
|
|
||||||
|
|
||||||
RUNTIME_1 = {'id': str(uuid.uuid4()), 'name': 'runtime_1'}
|
|
||||||
RUNTIME_2 = {'id': str(uuid.uuid4()), 'name': 'runtime_2'}
|
|
||||||
|
|
||||||
LIST_RUNTIMES_RESP = {
|
|
||||||
'runtimes': [RUNTIME_1, RUNTIME_2]
|
|
||||||
}
|
|
||||||
|
|
||||||
RUNTIME_POOL = {'name': RUNTIME_2['id'],
|
|
||||||
'capacity': {'available': 5, 'total': 5}}
|
|
||||||
|
|
||||||
|
|
||||||
class TestRuntime(test_client.TestQinlingClient):
|
|
||||||
|
|
||||||
_error_message = "Test error message."
|
|
||||||
|
|
||||||
def test_list_runtime(self):
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'GET',
|
|
||||||
test_client.QINLING_URL + '/v1/runtimes',
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
json=LIST_RUNTIMES_RESP,
|
|
||||||
status_code=200
|
|
||||||
)
|
|
||||||
ret = self.client.runtimes.list()
|
|
||||||
self.assertIsInstance(ret, list)
|
|
||||||
self.assertEqual(
|
|
||||||
[RUNTIME_1, RUNTIME_2],
|
|
||||||
[resource.to_dict() for resource in ret])
|
|
||||||
|
|
||||||
def test_create_runtime(self):
|
|
||||||
image_name = 'image_name'
|
|
||||||
request_data = {'image': image_name, 'trusted': True,
|
|
||||||
'is_public': True}
|
|
||||||
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'POST',
|
|
||||||
test_client.QINLING_URL + '/v1/runtimes',
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
json=RUNTIME_1,
|
|
||||||
status_code=201
|
|
||||||
)
|
|
||||||
ret = self.client.runtimes.create(image_name)
|
|
||||||
|
|
||||||
self.assertEqual(RUNTIME_1, ret.to_dict())
|
|
||||||
self.assertEqual(request_data,
|
|
||||||
jsonutils.loads(self.requests_mock.last_request.text))
|
|
||||||
|
|
||||||
def test_create_runtime_all_options(self):
|
|
||||||
image_name = 'image_name'
|
|
||||||
runtime_name = 'runtime_name'
|
|
||||||
description = 'A newly created runtime.'
|
|
||||||
trusted = False
|
|
||||||
is_public = False
|
|
||||||
|
|
||||||
request_data = {'image': image_name, 'trusted': trusted,
|
|
||||||
'name': runtime_name, 'is_public': is_public,
|
|
||||||
'description': description}
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'POST',
|
|
||||||
test_client.QINLING_URL + '/v1/runtimes',
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
json=RUNTIME_1,
|
|
||||||
status_code=201
|
|
||||||
)
|
|
||||||
|
|
||||||
ret = self.client.runtimes.create(
|
|
||||||
image_name, name=runtime_name, description=description,
|
|
||||||
trusted=False, is_public = False,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertEqual(RUNTIME_1, ret.to_dict())
|
|
||||||
self.assertEqual(request_data,
|
|
||||||
jsonutils.loads(self.requests_mock.last_request.text))
|
|
||||||
|
|
||||||
def test_create_runtime_error(self):
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'POST',
|
|
||||||
test_client.QINLING_URL + '/v1/runtimes',
|
|
||||||
text='{"faultstring": "%s"}' % self._error_message,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
status_code=400
|
|
||||||
)
|
|
||||||
self.assertRaisesRegex(
|
|
||||||
exceptions.HTTPBadRequest,
|
|
||||||
self._error_message,
|
|
||||||
self.client.runtimes.create,
|
|
||||||
'image_name'
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_delete_runtime(self):
|
|
||||||
runtime_id = RUNTIME_1['id']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'DELETE',
|
|
||||||
test_client.QINLING_URL + '/v1/runtimes/%s' % runtime_id,
|
|
||||||
status_code=204
|
|
||||||
)
|
|
||||||
ret = self.client.runtimes.delete(runtime_id)
|
|
||||||
self.assertIsNone(ret)
|
|
||||||
|
|
||||||
def test_delete_runtime_error(self):
|
|
||||||
runtime_id = RUNTIME_1['id']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'DELETE',
|
|
||||||
test_client.QINLING_URL + '/v1/runtimes/%s' % runtime_id,
|
|
||||||
text='{"faultstring": "%s"}' % self._error_message,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
status_code=403
|
|
||||||
)
|
|
||||||
self.assertRaisesRegex(
|
|
||||||
exceptions.HTTPForbidden,
|
|
||||||
self._error_message,
|
|
||||||
self.client.runtimes.delete,
|
|
||||||
runtime_id
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_get_runtime(self):
|
|
||||||
runtime_id = RUNTIME_2['id']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'GET',
|
|
||||||
test_client.QINLING_URL + '/v1/runtimes/%s' % runtime_id,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
json=RUNTIME_2,
|
|
||||||
status_code=200
|
|
||||||
)
|
|
||||||
ret = self.client.runtimes.get(runtime_id)
|
|
||||||
self.assertEqual(RUNTIME_2, ret.to_dict())
|
|
||||||
|
|
||||||
def test_get_runtime_error(self):
|
|
||||||
runtime_id = RUNTIME_2['id']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'GET',
|
|
||||||
test_client.QINLING_URL + '/v1/runtimes/%s' % runtime_id,
|
|
||||||
text='{"faultstring": "%s"}' % self._error_message,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
status_code=404
|
|
||||||
)
|
|
||||||
self.assertRaisesRegex(
|
|
||||||
exceptions.HTTPNotFound,
|
|
||||||
self._error_message,
|
|
||||||
self.client.runtimes.get,
|
|
||||||
runtime_id
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_get_pool_runtime(self):
|
|
||||||
runtime_id = RUNTIME_2['id']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'GET',
|
|
||||||
test_client.QINLING_URL + '/v1/runtimes/%s/pool' % runtime_id,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
json=RUNTIME_POOL,
|
|
||||||
status_code=200
|
|
||||||
)
|
|
||||||
ret = self.client.runtimes.get_pool(runtime_id)
|
|
||||||
self.assertEqual(RUNTIME_POOL, ret.to_dict())
|
|
||||||
|
|
||||||
def test_get_pool_runtime_error(self):
|
|
||||||
runtime_id = RUNTIME_2['id']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'GET',
|
|
||||||
test_client.QINLING_URL + '/v1/runtimes/%s/pool' % runtime_id,
|
|
||||||
text='{"faultstring": "%s"}' % self._error_message,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
status_code=404
|
|
||||||
)
|
|
||||||
self.assertRaisesRegex(
|
|
||||||
exceptions.HTTPNotFound,
|
|
||||||
self._error_message,
|
|
||||||
self.client.runtimes.get_pool,
|
|
||||||
runtime_id
|
|
||||||
)
|
|
|
@ -1,214 +0,0 @@
|
||||||
# Copyright 2018 AWCloud Software 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 uuid
|
|
||||||
|
|
||||||
from oslo_serialization import jsonutils
|
|
||||||
|
|
||||||
from qinlingclient.common import exceptions
|
|
||||||
from qinlingclient.tests.unit.v1 import test_client
|
|
||||||
|
|
||||||
WEBHOOK_1 = {'id': str(uuid.uuid4()), 'function_id': str(uuid.uuid4())}
|
|
||||||
WEBHOOK_2 = {'id': str(uuid.uuid4()), 'function_id': str(uuid.uuid4())}
|
|
||||||
WEBHOOK_3 = {'id': str(uuid.uuid4()), 'function_alias': 'alias_1'}
|
|
||||||
|
|
||||||
LIST_WEBHOOKS_RESP = {
|
|
||||||
'webhooks': [WEBHOOK_1, WEBHOOK_2]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class TestWebhook(test_client.TestQinlingClient):
|
|
||||||
|
|
||||||
_error_message = "Test error message."
|
|
||||||
|
|
||||||
def test_list_webhook(self):
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'GET',
|
|
||||||
test_client.QINLING_URL + '/v1/webhooks',
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
json=LIST_WEBHOOKS_RESP,
|
|
||||||
status_code=200
|
|
||||||
)
|
|
||||||
ret = self.client.webhooks.list()
|
|
||||||
self.assertIsInstance(ret, list)
|
|
||||||
self.assertEqual(
|
|
||||||
[WEBHOOK_1, WEBHOOK_2],
|
|
||||||
[resource.to_dict() for resource in ret])
|
|
||||||
|
|
||||||
def test_list_webhook_with_params(self):
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'GET',
|
|
||||||
test_client.QINLING_URL + '/v1/webhooks?function_alias=alias',
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
json=LIST_WEBHOOKS_RESP,
|
|
||||||
status_code=200
|
|
||||||
)
|
|
||||||
ret = self.client.webhooks.list(function_alias='alias')
|
|
||||||
self.assertIsInstance(ret, list)
|
|
||||||
self.assertEqual(
|
|
||||||
[WEBHOOK_1, WEBHOOK_2],
|
|
||||||
[resource.to_dict() for resource in ret])
|
|
||||||
|
|
||||||
def test_create_webhook(self):
|
|
||||||
function_id = WEBHOOK_1['function_id']
|
|
||||||
request_data = {'function_id': function_id, 'function_version': 0,
|
|
||||||
'function_alias': None}
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'POST',
|
|
||||||
test_client.QINLING_URL + '/v1/webhooks',
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
json=WEBHOOK_1,
|
|
||||||
status_code=201
|
|
||||||
)
|
|
||||||
ret = self.client.webhooks.create(function_id)
|
|
||||||
self.assertEqual(WEBHOOK_1, ret.to_dict())
|
|
||||||
self.assertEqual(jsonutils.dumps(request_data),
|
|
||||||
self.requests_mock.last_request.text)
|
|
||||||
|
|
||||||
def test_create_webhook_all_options(self):
|
|
||||||
function_id = WEBHOOK_1['function_id']
|
|
||||||
function_version = 1
|
|
||||||
description = 'A newly created webhook'
|
|
||||||
request_data = {'function_id': function_id,
|
|
||||||
'function_version': function_version,
|
|
||||||
'function_alias': None,
|
|
||||||
'description': description}
|
|
||||||
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'POST',
|
|
||||||
test_client.QINLING_URL + '/v1/webhooks',
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
json=WEBHOOK_1,
|
|
||||||
status_code=201
|
|
||||||
)
|
|
||||||
ret = self.client.webhooks.create(
|
|
||||||
function_id, function_version=function_version,
|
|
||||||
description=description)
|
|
||||||
self.assertEqual(WEBHOOK_1, ret.to_dict())
|
|
||||||
self.assertEqual(jsonutils.dumps(request_data),
|
|
||||||
self.requests_mock.last_request.text)
|
|
||||||
|
|
||||||
def test_create_webhook_error(self):
|
|
||||||
function_id = WEBHOOK_1['function_id']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'POST',
|
|
||||||
test_client.QINLING_URL + '/v1/webhooks',
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
text='{"faultstring": "%s"}' % self._error_message,
|
|
||||||
status_code=400
|
|
||||||
)
|
|
||||||
self.assertRaisesRegex(
|
|
||||||
exceptions.HTTPBadRequest,
|
|
||||||
self._error_message,
|
|
||||||
self.client.webhooks.create,
|
|
||||||
function_id)
|
|
||||||
|
|
||||||
def test_delete_webhook(self):
|
|
||||||
webhook_id = WEBHOOK_1['id']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'DELETE',
|
|
||||||
test_client.QINLING_URL + '/v1/webhooks/%s' % webhook_id,
|
|
||||||
status_code=204
|
|
||||||
)
|
|
||||||
ret = self.client.webhooks.delete(webhook_id)
|
|
||||||
self.assertIsNone(ret)
|
|
||||||
|
|
||||||
def test_delete_webhook_error(self):
|
|
||||||
webhook_id = WEBHOOK_1['id']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'DELETE',
|
|
||||||
test_client.QINLING_URL + '/v1/webhooks/%s' % webhook_id,
|
|
||||||
text='{"faultstring": "%s"}' % self._error_message,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
status_code=404
|
|
||||||
)
|
|
||||||
self.assertRaisesRegex(
|
|
||||||
exceptions.HTTPNotFound,
|
|
||||||
self._error_message,
|
|
||||||
self.client.webhooks.delete,
|
|
||||||
webhook_id)
|
|
||||||
|
|
||||||
def test_get_webhook(self):
|
|
||||||
webhook_id = WEBHOOK_2['id']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'GET',
|
|
||||||
test_client.QINLING_URL + '/v1/webhooks/%s' % webhook_id,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
json=WEBHOOK_2,
|
|
||||||
status_code=200
|
|
||||||
)
|
|
||||||
ret = self.client.webhooks.get(webhook_id)
|
|
||||||
self.assertEqual(WEBHOOK_2, ret.to_dict())
|
|
||||||
|
|
||||||
def test_get_webhook_error(self):
|
|
||||||
webhook_id = WEBHOOK_2['id']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'GET',
|
|
||||||
test_client.QINLING_URL + '/v1/webhooks/%s' % webhook_id,
|
|
||||||
text='{"faultstring": "%s"}' % self._error_message,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
status_code=404
|
|
||||||
)
|
|
||||||
self.assertRaisesRegex(
|
|
||||||
exceptions.HTTPNotFound,
|
|
||||||
self._error_message,
|
|
||||||
self.client.webhooks.get,
|
|
||||||
webhook_id)
|
|
||||||
|
|
||||||
def test_update_webhook(self):
|
|
||||||
webhook_id = WEBHOOK_2['id']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'PUT',
|
|
||||||
test_client.QINLING_URL + '/v1/webhooks/%s' % webhook_id,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
json=WEBHOOK_2,
|
|
||||||
status_code=200
|
|
||||||
)
|
|
||||||
ret = self.client.webhooks.update(webhook_id)
|
|
||||||
self.assertEqual(WEBHOOK_2, ret.to_dict())
|
|
||||||
|
|
||||||
def test_update_webhook_with_params(self):
|
|
||||||
webhook_id = WEBHOOK_2['id']
|
|
||||||
function_id = str(uuid.uuid4())
|
|
||||||
description = 'Updated webhook description.'
|
|
||||||
request_data = {'function_id': function_id, 'description': description}
|
|
||||||
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'PUT',
|
|
||||||
test_client.QINLING_URL + '/v1/webhooks/%s' % webhook_id,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
json=WEBHOOK_2,
|
|
||||||
status_code=200
|
|
||||||
)
|
|
||||||
ret = self.client.webhooks.update(webhook_id,
|
|
||||||
function_id=function_id,
|
|
||||||
description=description)
|
|
||||||
self.assertEqual(WEBHOOK_2, ret.to_dict())
|
|
||||||
self.assertEqual(jsonutils.dumps(request_data),
|
|
||||||
self.requests_mock.last_request.text)
|
|
||||||
|
|
||||||
def test_update_webhook_error(self):
|
|
||||||
webhook_id = WEBHOOK_2['id']
|
|
||||||
self.requests_mock.register_uri(
|
|
||||||
'PUT',
|
|
||||||
test_client.QINLING_URL + '/v1/webhooks/%s' % webhook_id,
|
|
||||||
text='{"faultstring": "%s"}' % self._error_message,
|
|
||||||
headers={'Content-Type': 'application/json'},
|
|
||||||
status_code=400
|
|
||||||
)
|
|
||||||
self.assertRaisesRegex(
|
|
||||||
exceptions.HTTPBadRequest,
|
|
||||||
self._error_message,
|
|
||||||
self.client.webhooks.update,
|
|
||||||
webhook_id)
|
|
|
@ -1,45 +0,0 @@
|
||||||
# Copyright 2018 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 argparse
|
|
||||||
import hashlib
|
|
||||||
|
|
||||||
|
|
||||||
def md5(file=None, content=None):
|
|
||||||
hash_md5 = hashlib.md5()
|
|
||||||
|
|
||||||
if file:
|
|
||||||
with open(file, "rb") as f:
|
|
||||||
for chunk in iter(lambda: f.read(4096), b""):
|
|
||||||
hash_md5.update(chunk)
|
|
||||||
elif content:
|
|
||||||
hash_md5.update(content)
|
|
||||||
|
|
||||||
return hash_md5.hexdigest()
|
|
||||||
|
|
||||||
|
|
||||||
def find_resource_id_by_name(manager, name):
|
|
||||||
# An exception will be raised when resource is not found or multiple
|
|
||||||
# resources for the name are found.
|
|
||||||
resource = manager.find(name=name)
|
|
||||||
return resource.id
|
|
||||||
|
|
||||||
|
|
||||||
def check_positive(value):
|
|
||||||
ivalue = int(value)
|
|
||||||
if ivalue <= 0:
|
|
||||||
raise argparse.ArgumentTypeError(
|
|
||||||
"%s is an invalid positive int value" % value
|
|
||||||
)
|
|
||||||
return ivalue
|
|
|
@ -1,49 +0,0 @@
|
||||||
# Copyright 2017 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 qinlingclient.common import http
|
|
||||||
from qinlingclient.v1 import function
|
|
||||||
from qinlingclient.v1 import function_alias
|
|
||||||
from qinlingclient.v1 import function_execution
|
|
||||||
from qinlingclient.v1 import function_version
|
|
||||||
from qinlingclient.v1 import function_worker
|
|
||||||
from qinlingclient.v1 import job
|
|
||||||
from qinlingclient.v1 import runtime
|
|
||||||
from qinlingclient.v1 import webhook
|
|
||||||
|
|
||||||
|
|
||||||
class Client(object):
|
|
||||||
"""Client for the Qinling v1 API.
|
|
||||||
|
|
||||||
:param string endpoint: A user-supplied endpoint URL for the service.
|
|
||||||
:param string token: Token for authentication.
|
|
||||||
:param integer timeout: Allows customization of the timeout for client
|
|
||||||
http requests. (optional)
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
"""Initialize a new client for the Qinling v1 API."""
|
|
||||||
self.http_client = http._construct_http_client(*args, **kwargs)
|
|
||||||
|
|
||||||
self.runtimes = runtime.RuntimeManager(self.http_client)
|
|
||||||
self.functions = function.FunctionManager(self.http_client)
|
|
||||||
self.function_executions = function_execution.ExecutionManager(
|
|
||||||
self.http_client)
|
|
||||||
self.jobs = job.JobManager(self.http_client)
|
|
||||||
self.function_workers = function_worker.WorkerManager(self.http_client)
|
|
||||||
self.webhooks = webhook.WebhookManager(self.http_client)
|
|
||||||
self.function_versions = function_version.FunctionVersionManager(
|
|
||||||
self.http_client)
|
|
||||||
self.function_aliases = function_alias.FunctionAliasManager(
|
|
||||||
self.http_client)
|
|
|
@ -1,109 +0,0 @@
|
||||||
# Copyright 2017 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 oslo_serialization import jsonutils
|
|
||||||
|
|
||||||
from qinlingclient.common import base
|
|
||||||
|
|
||||||
|
|
||||||
class Function(base.Resource):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class FunctionManager(base.ManagerWithFind):
|
|
||||||
resource_class = Function
|
|
||||||
|
|
||||||
def list(self, **kwargs):
|
|
||||||
q_list = []
|
|
||||||
for key, value in kwargs.items():
|
|
||||||
q_list.append('%s=%s' % (key, value))
|
|
||||||
q_params = '&'.join(q_list)
|
|
||||||
|
|
||||||
url = '/v1/functions'
|
|
||||||
if q_params:
|
|
||||||
url += '?%s' % q_params
|
|
||||||
return self._list(url, response_key='functions')
|
|
||||||
|
|
||||||
def create(self, code, runtime=None, package=None, **kwargs):
|
|
||||||
data = {
|
|
||||||
'runtime_id': runtime,
|
|
||||||
'code': jsonutils.dumps(code)
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v in kwargs.items():
|
|
||||||
if v is not None:
|
|
||||||
data.update({k: v})
|
|
||||||
|
|
||||||
params = {"data": data}
|
|
||||||
if package:
|
|
||||||
params.update({"files": {'package': package}})
|
|
||||||
|
|
||||||
response = self.http_client.request(
|
|
||||||
'/v1/functions',
|
|
||||||
'POST',
|
|
||||||
**params
|
|
||||||
)
|
|
||||||
body = jsonutils.loads(response.text)
|
|
||||||
|
|
||||||
return self.resource_class(self, body)
|
|
||||||
|
|
||||||
def delete(self, id):
|
|
||||||
self._delete('/v1/functions/%s' % id)
|
|
||||||
|
|
||||||
def get(self, id, download=False):
|
|
||||||
url = '/v1/functions/%s' % id
|
|
||||||
if not download:
|
|
||||||
return self._get('/v1/functions/%s' % id)
|
|
||||||
|
|
||||||
url = url + '?download=true'
|
|
||||||
return self.http_client.request(url, 'GET', stream=True)
|
|
||||||
|
|
||||||
def update(self, id, code=None, package=None, **kwargs):
|
|
||||||
if code:
|
|
||||||
kwargs.update(code)
|
|
||||||
|
|
||||||
params = {"data": kwargs}
|
|
||||||
if package:
|
|
||||||
params.update({"files": {'package': package}})
|
|
||||||
|
|
||||||
response = self.http_client.request(
|
|
||||||
'/v1/functions/%s' % id,
|
|
||||||
'PUT',
|
|
||||||
**params
|
|
||||||
)
|
|
||||||
body = jsonutils.loads(response.text)
|
|
||||||
|
|
||||||
return self.resource_class(self, body)
|
|
||||||
|
|
||||||
def detach(self, id):
|
|
||||||
return self.http_client.request(
|
|
||||||
'/v1/functions/%s/detach' % id,
|
|
||||||
'POST',
|
|
||||||
)
|
|
||||||
|
|
||||||
def scaleup(self, id, count=1):
|
|
||||||
params = {'data': {'count': count}}
|
|
||||||
return self.http_client.json_request(
|
|
||||||
'/v1/functions/%s/scale_up' % id,
|
|
||||||
'POST',
|
|
||||||
**params
|
|
||||||
)
|
|
||||||
|
|
||||||
def scaledown(self, id, count=1):
|
|
||||||
params = {'data': {'count': count}}
|
|
||||||
return self.http_client.json_request(
|
|
||||||
'/v1/functions/%s/scale_down' % id,
|
|
||||||
'POST',
|
|
||||||
**params
|
|
||||||
)
|
|
|
@ -1,59 +0,0 @@
|
||||||
# Copyright 2018 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 qinlingclient.common import base
|
|
||||||
|
|
||||||
URL_TEMPLATE = "/v1/aliases"
|
|
||||||
|
|
||||||
|
|
||||||
class FunctionAlias(base.Resource):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class FunctionAliasManager(base.Manager):
|
|
||||||
resource_class = FunctionAlias
|
|
||||||
|
|
||||||
def list(self, **kwargs):
|
|
||||||
url = URL_TEMPLATE
|
|
||||||
return self._list(url, response_key='function_aliases')
|
|
||||||
|
|
||||||
def create(self, name, function_id, function_version=0, description=""):
|
|
||||||
url = URL_TEMPLATE
|
|
||||||
request_data = {
|
|
||||||
'name': name,
|
|
||||||
'function_id': function_id,
|
|
||||||
'function_version': function_version,
|
|
||||||
'description': description
|
|
||||||
}
|
|
||||||
|
|
||||||
return self._create(url, data=request_data)
|
|
||||||
|
|
||||||
def delete(self, name):
|
|
||||||
url = URL_TEMPLATE + '/%s' % name
|
|
||||||
self._delete(url)
|
|
||||||
|
|
||||||
def get(self, name):
|
|
||||||
url = URL_TEMPLATE + '/%s' % name
|
|
||||||
return self._get(url)
|
|
||||||
|
|
||||||
def update(self, name, function_id=None, function_version=None,
|
|
||||||
description=None):
|
|
||||||
url = URL_TEMPLATE + '/%s' % name
|
|
||||||
request_data = {
|
|
||||||
'function_id': function_id,
|
|
||||||
'function_version': function_version,
|
|
||||||
'description': description
|
|
||||||
}
|
|
||||||
|
|
||||||
return self._update(url, data=request_data)
|
|
|
@ -1,54 +0,0 @@
|
||||||
# Copyright 2017 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 qinlingclient.common import base
|
|
||||||
|
|
||||||
|
|
||||||
class FunctionExecution(base.Resource):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class ExecutionManager(base.Manager):
|
|
||||||
resource_class = FunctionExecution
|
|
||||||
|
|
||||||
def list(self, **kwargs):
|
|
||||||
q_list = []
|
|
||||||
for key, value in kwargs.items():
|
|
||||||
q_list.append('%s=%s' % (key, value))
|
|
||||||
q_params = '&'.join(q_list)
|
|
||||||
|
|
||||||
url = '/v1/executions'
|
|
||||||
if q_params:
|
|
||||||
url += '?%s' % q_params
|
|
||||||
return self._list(url, response_key='executions')
|
|
||||||
|
|
||||||
def create(self, function_id=None, function_alias=None, function_version=0,
|
|
||||||
sync=True, input=None):
|
|
||||||
data = {
|
|
||||||
'function_id': function_id,
|
|
||||||
'function_version': function_version,
|
|
||||||
'function_alias': function_alias,
|
|
||||||
'sync': sync,
|
|
||||||
'input': input
|
|
||||||
}
|
|
||||||
return self._create('/v1/executions', data)
|
|
||||||
|
|
||||||
def delete(self, id):
|
|
||||||
self._delete('/v1/executions/%s' % id)
|
|
||||||
|
|
||||||
def get(self, id):
|
|
||||||
return self._get('/v1/executions/%s' % id)
|
|
||||||
|
|
||||||
def get_log(self, id):
|
|
||||||
return self._get('/v1/executions/%s/log' % id, return_raw=True)
|
|
|
@ -1,49 +0,0 @@
|
||||||
# Copyright 2018 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 qinlingclient.common import base
|
|
||||||
|
|
||||||
URL_TEMPLATE = "/v1/functions/%s/versions"
|
|
||||||
|
|
||||||
|
|
||||||
class FunctionVersion(base.Resource):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class FunctionVersionManager(base.Manager):
|
|
||||||
resource_class = FunctionVersion
|
|
||||||
|
|
||||||
def list(self, function_id):
|
|
||||||
url = URL_TEMPLATE % function_id
|
|
||||||
return self._list(url, response_key='function_versions')
|
|
||||||
|
|
||||||
def create(self, function_id, description=""):
|
|
||||||
url = URL_TEMPLATE % function_id
|
|
||||||
request_data = {"description": description}
|
|
||||||
|
|
||||||
return self._create(url, data=request_data)
|
|
||||||
|
|
||||||
def delete(self, function_id, version):
|
|
||||||
url = URL_TEMPLATE % function_id + '/%s' % version
|
|
||||||
self._delete(url)
|
|
||||||
|
|
||||||
def get(self, function_id, version):
|
|
||||||
url = URL_TEMPLATE % function_id + '/%s' % version
|
|
||||||
return self._get(url)
|
|
||||||
|
|
||||||
def detach(self, function_id, version):
|
|
||||||
return self.http_client.request(
|
|
||||||
URL_TEMPLATE % function_id + '/%s/detach' % version,
|
|
||||||
'POST',
|
|
||||||
)
|
|
|
@ -1,27 +0,0 @@
|
||||||
# Copyright 2017 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 qinlingclient.common import base
|
|
||||||
|
|
||||||
|
|
||||||
class FunctionWorker(base.Resource):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class WorkerManager(base.Manager):
|
|
||||||
resource_class = FunctionWorker
|
|
||||||
|
|
||||||
def list(self, function_id):
|
|
||||||
url = '/v1/functions/%s/workers' % function_id
|
|
||||||
return self._list(url, response_key='workers')
|
|
|
@ -1,50 +0,0 @@
|
||||||
# Copyright 2017 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 qinlingclient.common import base
|
|
||||||
|
|
||||||
|
|
||||||
class Job(base.Resource):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class JobManager(base.Manager):
|
|
||||||
resource_class = Job
|
|
||||||
|
|
||||||
def list(self, **kwargs):
|
|
||||||
return self._list("/v1/jobs", response_key='jobs')
|
|
||||||
|
|
||||||
def create(self, function_alias=None, function_id=None, function_version=0,
|
|
||||||
name=None, first_execution_time=None, pattern=None,
|
|
||||||
function_input=None, count=None):
|
|
||||||
body = {
|
|
||||||
"function_alias": function_alias,
|
|
||||||
"function_id": function_id,
|
|
||||||
"function_version": function_version,
|
|
||||||
"name": name,
|
|
||||||
"first_execution_time": first_execution_time,
|
|
||||||
"pattern": pattern,
|
|
||||||
"function_input": function_input,
|
|
||||||
"count": count
|
|
||||||
}
|
|
||||||
return self._create('/v1/jobs', data=body)
|
|
||||||
|
|
||||||
def delete(self, id):
|
|
||||||
self._delete('/v1/jobs/%s' % id)
|
|
||||||
|
|
||||||
def get(self, id):
|
|
||||||
return self._get('/v1/jobs/%s' % id)
|
|
||||||
|
|
||||||
def update(self, id, **kwargs):
|
|
||||||
return self._update('/v1/jobs/%s' % id, kwargs)
|
|
|
@ -1,45 +0,0 @@
|
||||||
# Copyright 2017 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 qinlingclient.common import base
|
|
||||||
|
|
||||||
|
|
||||||
class Runtime(base.Resource):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class RuntimeManager(base.ManagerWithFind):
|
|
||||||
resource_class = Runtime
|
|
||||||
|
|
||||||
def list(self, **kwargs):
|
|
||||||
return self._list("/v1/runtimes", response_key='runtimes')
|
|
||||||
|
|
||||||
def create(self, image, name=None, description=None,
|
|
||||||
is_public=True, trusted=True):
|
|
||||||
data = {'image': image, 'is_public': is_public, 'trusted': trusted}
|
|
||||||
if name:
|
|
||||||
data.update({'name': name})
|
|
||||||
if description:
|
|
||||||
data.update({'description': description})
|
|
||||||
|
|
||||||
return self._create('/v1/runtimes', data)
|
|
||||||
|
|
||||||
def delete(self, id):
|
|
||||||
self._delete('/v1/runtimes/%s' % id)
|
|
||||||
|
|
||||||
def get(self, id):
|
|
||||||
return self._get('/v1/runtimes/%s' % id)
|
|
||||||
|
|
||||||
def get_pool(self, id):
|
|
||||||
return self._get('/v1/runtimes/%s/pool' % id)
|
|
|
@ -1,26 +0,0 @@
|
||||||
# Copyright 2015 OpenStack Foundation
|
|
||||||
# Copyright 2015 Huawei Corp.
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
|
|
||||||
class VersionController(object):
|
|
||||||
def __init__(self, http_client):
|
|
||||||
self.http_client = http_client
|
|
||||||
|
|
||||||
def list(self):
|
|
||||||
"""List all versions."""
|
|
||||||
url = '/versions'
|
|
||||||
resp, body = self.http_client.get(url)
|
|
||||||
return body.get('versions', None)
|
|
|
@ -1,64 +0,0 @@
|
||||||
# Copyright 2017 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 qinlingclient.common import base
|
|
||||||
|
|
||||||
|
|
||||||
class Webhook(base.Resource):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class WebhookManager(base.Manager):
|
|
||||||
resource_class = Webhook
|
|
||||||
|
|
||||||
def list(self, **kwargs):
|
|
||||||
q_list = []
|
|
||||||
for key, value in kwargs.items():
|
|
||||||
q_list.append('%s=%s' % (key, value))
|
|
||||||
q_params = '&'.join(q_list)
|
|
||||||
|
|
||||||
url = '/v1/webhooks'
|
|
||||||
if q_params:
|
|
||||||
url += '?%s' % q_params
|
|
||||||
return self._list(url, response_key='webhooks')
|
|
||||||
|
|
||||||
def create(self, function_id, function_alias=None, function_version=0,
|
|
||||||
description=None):
|
|
||||||
data = {
|
|
||||||
'function_id': function_id,
|
|
||||||
'function_version': function_version,
|
|
||||||
'function_alias': function_alias
|
|
||||||
}
|
|
||||||
if description:
|
|
||||||
data.update({'description': description})
|
|
||||||
|
|
||||||
return self._create('/v1/webhooks', data)
|
|
||||||
|
|
||||||
def delete(self, id):
|
|
||||||
self._delete('/v1/webhooks/%s' % id)
|
|
||||||
|
|
||||||
def get(self, id):
|
|
||||||
return self._get('/v1/webhooks/%s' % id)
|
|
||||||
|
|
||||||
def update(self, id, **kwargs):
|
|
||||||
"""Update webhook.
|
|
||||||
|
|
||||||
function_id, function_version and description are supported.
|
|
||||||
"""
|
|
||||||
body = {}
|
|
||||||
for k, v in kwargs.items():
|
|
||||||
if v is not None:
|
|
||||||
body.update({k: v})
|
|
||||||
|
|
||||||
return self._update('/v1/webhooks/%s' % id, kwargs)
|
|
|
@ -1,18 +0,0 @@
|
||||||
# Copyright 2017 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 pbr import version
|
|
||||||
|
|
||||||
version_info = version.VersionInfo('python-qinlingclient')
|
|
|
@ -1,6 +0,0 @@
|
||||||
---
|
|
||||||
features:
|
|
||||||
- Support ``--function-alias`` for creating function execution and webhook,
|
|
||||||
change the positional argument of function to optional ``--function``,
|
|
||||||
so that the end user can either specify a function alias or a function
|
|
||||||
identifier to create function execution or webhook.
|
|
|
@ -1,4 +0,0 @@
|
||||||
---
|
|
||||||
deprecations:
|
|
||||||
- The param ``--code-type`` of function creation command is deprecated, the
|
|
||||||
actual code type can be deduced by other given params.
|
|
|
@ -1,6 +0,0 @@
|
||||||
---
|
|
||||||
upgrade:
|
|
||||||
- |
|
|
||||||
Python 2.7 support has been dropped. Last release of python-qinlingclient
|
|
||||||
to support python 2.7 is OpenStack Train. The minimum version of Python now
|
|
||||||
supported is Python 3.6.
|
|
|
@ -1,4 +0,0 @@
|
||||||
---
|
|
||||||
fixes:
|
|
||||||
- Webhook function_version will not be reset automatically to 0 during
|
|
||||||
an update.
|
|
|
@ -1,3 +0,0 @@
|
||||||
---
|
|
||||||
features:
|
|
||||||
- Support function alias CLI.
|
|
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
features:
|
|
||||||
- Support to specify function timeout when creating and updating the
|
|
||||||
functions. When the specified timeout is reached, Qinling will terminate
|
|
||||||
the function execution.
|
|
|
@ -1,3 +0,0 @@
|
||||||
---
|
|
||||||
features:
|
|
||||||
- Support function versioning CLI.
|
|
|
@ -1,6 +0,0 @@
|
||||||
---
|
|
||||||
features:
|
|
||||||
- Add an administrative command ``openstack runtime pool show`` to get the
|
|
||||||
runtime pool information. This command is useful for admin user to check
|
|
||||||
the information such as the current capacity of the runtime, in order to
|
|
||||||
adjust the pool size according to the users' need.
|
|
|
@ -1,4 +0,0 @@
|
||||||
features:
|
|
||||||
- Support ``--function-alias`` for creating jobs, change the positional
|
|
||||||
argument of function to optional ``--function``, so that the end user can
|
|
||||||
either specify a function alias or a function identifier to create job.
|
|
|
@ -1,10 +0,0 @@
|
||||||
---
|
|
||||||
prelude: >
|
|
||||||
Support to specify the resource(memory/cpu) limitation when creating the
|
|
||||||
function.
|
|
||||||
features:
|
|
||||||
- |
|
|
||||||
End user could restrict the resource consumption for the function execution
|
|
||||||
by specifying ``--cpu`` and ``--memory-size`` when creating the function.
|
|
||||||
Those resource limitation has the default value if not provided, please
|
|
||||||
refer to Qinling documentation for more details.
|
|
|
@ -1,4 +0,0 @@
|
||||||
---
|
|
||||||
features:
|
|
||||||
- The end user can create job by specifying function version, default value
|
|
||||||
is 0 if it's not provided.
|
|
|
@ -1,4 +0,0 @@
|
||||||
---
|
|
||||||
features:
|
|
||||||
- The user can create or update webhook by specifying the function version
|
|
||||||
(``--function-version``) together with the function.
|
|
|
@ -1,3 +0,0 @@
|
||||||
---
|
|
||||||
features:
|
|
||||||
- Support function name in most of the function operations CLI.
|
|
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
features:
|
|
||||||
- Support resource name in CLI, specifically runtime name in function
|
|
||||||
operations and function name in the operations of other resources like
|
|
||||||
execution, function version, job and webhook.
|
|
|
@ -1,3 +0,0 @@
|
||||||
---
|
|
||||||
features:
|
|
||||||
- Support ``--untrusted`` parameter when creating runtime.
|
|
|
@ -1,279 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
# implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
# Qinling Release Notes documentation build configuration file, created by
|
|
||||||
# sphinx-quickstart on Tue Nov 3 17:40:50 2015.
|
|
||||||
#
|
|
||||||
# This file is execfile()d with the current directory set to its
|
|
||||||
# containing dir.
|
|
||||||
#
|
|
||||||
# Note that not all possible configuration values are present in this
|
|
||||||
# autogenerated file.
|
|
||||||
#
|
|
||||||
# All configuration values have a default; values that are commented out
|
|
||||||
# serve to show the default.
|
|
||||||
|
|
||||||
# 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('.'))
|
|
||||||
|
|
||||||
# -- General configuration ------------------------------------------------
|
|
||||||
|
|
||||||
# If your documentation needs a minimal Sphinx version, state it here.
|
|
||||||
# needs_sphinx = '1.0'
|
|
||||||
|
|
||||||
# Add any Sphinx extension module names here, as strings. They can be
|
|
||||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
|
||||||
# ones.
|
|
||||||
extensions = [
|
|
||||||
'reno.sphinxext',
|
|
||||||
'openstackdocstheme'
|
|
||||||
]
|
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
|
||||||
templates_path = ['_templates']
|
|
||||||
|
|
||||||
# The suffix of source filenames.
|
|
||||||
source_suffix = '.rst'
|
|
||||||
|
|
||||||
# The encoding of source files.
|
|
||||||
# source_encoding = 'utf-8-sig'
|
|
||||||
|
|
||||||
# The master toctree document.
|
|
||||||
master_doc = 'index'
|
|
||||||
|
|
||||||
# General information about the project.
|
|
||||||
project = u'Qinling Client Release Notes'
|
|
||||||
copyright = u'2017, Qinling Developers'
|
|
||||||
|
|
||||||
# Release notes are version independent.
|
|
||||||
# The full version, including alpha/beta/rc tags.
|
|
||||||
release = ''
|
|
||||||
# The short X.Y version.
|
|
||||||
version = ''
|
|
||||||
|
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
|
||||||
# for a list of supported languages.
|
|
||||||
# language = None
|
|
||||||
|
|
||||||
# There are two options for replacing |today|: either, you set today to some
|
|
||||||
# non-false value, then it is used:
|
|
||||||
# today = ''
|
|
||||||
# Else, today_fmt is used as the format for a strftime call.
|
|
||||||
# today_fmt = '%B %d, %Y'
|
|
||||||
|
|
||||||
# List of patterns, relative to source directory, that match files and
|
|
||||||
# directories to ignore when looking for source files.
|
|
||||||
exclude_patterns = []
|
|
||||||
|
|
||||||
# The reST default role (used for this markup: `text`) to use for all
|
|
||||||
# documents.
|
|
||||||
# default_role = None
|
|
||||||
|
|
||||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
|
||||||
# add_function_parentheses = True
|
|
||||||
|
|
||||||
# If true, the current module name will be prepended to all description
|
|
||||||
# unit titles (such as .. function::).
|
|
||||||
# add_module_names = True
|
|
||||||
|
|
||||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
|
||||||
# output. They are ignored by default.
|
|
||||||
# show_authors = False
|
|
||||||
|
|
||||||
# The name of the Pygments (syntax highlighting) style to use.
|
|
||||||
pygments_style = 'sphinx'
|
|
||||||
|
|
||||||
# A list of ignored prefixes for module index sorting.
|
|
||||||
# modindex_common_prefix = []
|
|
||||||
|
|
||||||
# If true, keep warnings as "system message" paragraphs in the built documents.
|
|
||||||
# keep_warnings = False
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for HTML output ----------------------------------------------
|
|
||||||
|
|
||||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
|
||||||
# a list of builtin themes.
|
|
||||||
html_theme = 'openstackdocs'
|
|
||||||
|
|
||||||
# Theme options are theme-specific and customize the look and feel of a theme
|
|
||||||
# further. For a list of options available for each theme, see the
|
|
||||||
# documentation.
|
|
||||||
# html_theme_options = {}
|
|
||||||
|
|
||||||
# Add any paths that contain custom themes here, relative to this directory.
|
|
||||||
# html_theme_path = []
|
|
||||||
|
|
||||||
# The name for this set of Sphinx documents. If None, it defaults to
|
|
||||||
# "<project> v<release> documentation".
|
|
||||||
# html_title = None
|
|
||||||
|
|
||||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
|
||||||
# html_short_title = None
|
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top
|
|
||||||
# of the sidebar.
|
|
||||||
# html_logo = None
|
|
||||||
|
|
||||||
# The name of an image file (within the static path) to use as favicon of the
|
|
||||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
|
||||||
# pixels large.
|
|
||||||
# html_favicon = None
|
|
||||||
|
|
||||||
# Add any paths that contain custom static files (such as style sheets) here,
|
|
||||||
# relative to this directory. They are copied after the builtin static files,
|
|
||||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
|
||||||
html_static_path = ['_static']
|
|
||||||
|
|
||||||
# Add any extra paths that contain custom files (such as robots.txt or
|
|
||||||
# .htaccess) here, relative to this directory. These files are copied
|
|
||||||
# directly to the root of the documentation.
|
|
||||||
# html_extra_path = []
|
|
||||||
|
|
||||||
# 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'
|
|
||||||
|
|
||||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
|
||||||
# typographically correct entities.
|
|
||||||
# html_use_smartypants = True
|
|
||||||
|
|
||||||
# Custom sidebar templates, maps document names to template names.
|
|
||||||
# html_sidebars = {}
|
|
||||||
|
|
||||||
# Additional templates that should be rendered to pages, maps page names to
|
|
||||||
# template names.
|
|
||||||
# html_additional_pages = {}
|
|
||||||
|
|
||||||
# If false, no module index is generated.
|
|
||||||
# html_domain_indices = True
|
|
||||||
|
|
||||||
# If false, no index is generated.
|
|
||||||
# html_use_index = True
|
|
||||||
|
|
||||||
# If true, the index is split into individual pages for each letter.
|
|
||||||
# html_split_index = False
|
|
||||||
|
|
||||||
# If true, links to the reST sources are added to the pages.
|
|
||||||
# html_show_sourcelink = True
|
|
||||||
|
|
||||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
|
||||||
# html_show_sphinx = True
|
|
||||||
|
|
||||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
|
||||||
# html_show_copyright = True
|
|
||||||
|
|
||||||
# If true, an OpenSearch description file will be output, and all pages will
|
|
||||||
# contain a <link> tag referring to it. The value of this option must be the
|
|
||||||
# base URL from which the finished HTML is served.
|
|
||||||
# html_use_opensearch = ''
|
|
||||||
|
|
||||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
|
||||||
# html_file_suffix = None
|
|
||||||
|
|
||||||
# Output file base name for HTML help builder.
|
|
||||||
htmlhelp_basename = 'QinlingClientReleaseNotesdoc'
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for LaTeX output ---------------------------------------------
|
|
||||||
|
|
||||||
latex_elements = {
|
|
||||||
# The paper size ('letterpaper' or 'a4paper').
|
|
||||||
# 'papersize': 'letterpaper',
|
|
||||||
|
|
||||||
# The font size ('10pt', '11pt' or '12pt').
|
|
||||||
# 'pointsize': '10pt',
|
|
||||||
|
|
||||||
# Additional stuff for the LaTeX preamble.
|
|
||||||
# 'preamble': '',
|
|
||||||
}
|
|
||||||
|
|
||||||
# Grouping the document tree into LaTeX files. List of tuples
|
|
||||||
# (source start file, target name, title,
|
|
||||||
# author, documentclass [howto, manual, or own class]).
|
|
||||||
latex_documents = [
|
|
||||||
('index', 'QinlingClientReleaseNotes.tex',
|
|
||||||
u'Qinling Client Release Notes Documentation',
|
|
||||||
u'Qinling Developers', 'manual'),
|
|
||||||
]
|
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top of
|
|
||||||
# the title page.
|
|
||||||
# latex_logo = None
|
|
||||||
|
|
||||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
|
||||||
# not chapters.
|
|
||||||
# latex_use_parts = False
|
|
||||||
|
|
||||||
# If true, show page references after internal links.
|
|
||||||
# latex_show_pagerefs = False
|
|
||||||
|
|
||||||
# If true, show URL addresses after external links.
|
|
||||||
# latex_show_urls = False
|
|
||||||
|
|
||||||
# Documents to append as an appendix to all manuals.
|
|
||||||
# latex_appendices = []
|
|
||||||
|
|
||||||
# If false, no module index is generated.
|
|
||||||
# latex_domain_indices = True
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for manual page output ---------------------------------------
|
|
||||||
|
|
||||||
# One entry per manual page. List of tuples
|
|
||||||
# (source start file, name, description, authors, manual section).
|
|
||||||
man_pages = [
|
|
||||||
('index', 'qinlingclientreleasenotes',
|
|
||||||
u'Qinling Client Release Notes Documentation',
|
|
||||||
[u'Qinling Developers'], 1)
|
|
||||||
]
|
|
||||||
|
|
||||||
# If true, show URL addresses after external links.
|
|
||||||
# man_show_urls = False
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for Texinfo output -------------------------------------------
|
|
||||||
|
|
||||||
# Grouping the document tree into Texinfo files. List of tuples
|
|
||||||
# (source start file, target name, title, author,
|
|
||||||
# dir menu entry, description, category)
|
|
||||||
texinfo_documents = [
|
|
||||||
('index', 'QinlingClientReleaseNotes',
|
|
||||||
u'Qinling Client Release Notes Documentation',
|
|
||||||
u'Qinling Developers', 'QinlingClientReleaseNotes',
|
|
||||||
'One line description of project.',
|
|
||||||
'Miscellaneous'),
|
|
||||||
]
|
|
||||||
|
|
||||||
# Documents to append as an appendix to all manuals.
|
|
||||||
# texinfo_appendices = []
|
|
||||||
|
|
||||||
# If false, no module index is generated.
|
|
||||||
# texinfo_domain_indices = True
|
|
||||||
|
|
||||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
|
||||||
# texinfo_show_urls = 'footnote'
|
|
||||||
|
|
||||||
# If true, do not generate a @detailmenu in the "Top" node's menu.
|
|
||||||
# texinfo_no_detailmenu = False
|
|
||||||
|
|
||||||
# -- Options for Internationalization output ------------------------------
|
|
||||||
locale_dirs = ['locale/']
|
|
||||||
|
|
||||||
# openstackdocstheme options
|
|
||||||
openstackdocs_repo_name = 'openstack/python-qinlingclient'
|
|
||||||
openstackdocs_use_storyboard = True
|
|
||||||
openstackdocs_auto_name = False
|
|
|
@ -1,13 +0,0 @@
|
||||||
=============================
|
|
||||||
Qinling Client Release Notes
|
|
||||||
=============================
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 1
|
|
||||||
|
|
||||||
unreleased
|
|
||||||
victoria
|
|
||||||
ussuri
|
|
||||||
train
|
|
||||||
stein
|
|
||||||
rocky
|
|
|
@ -1,6 +0,0 @@
|
||||||
===================================
|
|
||||||
Rocky Series Release Notes
|
|
||||||
===================================
|
|
||||||
|
|
||||||
.. release-notes::
|
|
||||||
:branch: stable/rocky
|
|
|
@ -1,6 +0,0 @@
|
||||||
===================================
|
|
||||||
Stein Series Release Notes
|
|
||||||
===================================
|
|
||||||
|
|
||||||
.. release-notes::
|
|
||||||
:branch: stable/stein
|
|
|
@ -1,6 +0,0 @@
|
||||||
==========================
|
|
||||||
Train Series Release Notes
|
|
||||||
==========================
|
|
||||||
|
|
||||||
.. release-notes::
|
|
||||||
:branch: stable/train
|
|
|
@ -1,5 +0,0 @@
|
||||||
==============================
|
|
||||||
Current Series Release Notes
|
|
||||||
==============================
|
|
||||||
|
|
||||||
.. release-notes::
|
|
|
@ -1,6 +0,0 @@
|
||||||
===========================
|
|
||||||
Ussuri Series Release Notes
|
|
||||||
===========================
|
|
||||||
|
|
||||||
.. release-notes::
|
|
||||||
:branch: stable/ussuri
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue