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 @@
|
|||
========================
|
||||
Team and repository tags
|
||||
========================
|
||||
This project is no longer maintained.
|
||||
|
||||
.. image:: https://governance.openstack.org/tc/badges/python-qinlingclient.svg
|
||||
:target: https://governance.openstack.org/reference/tags/index.html
|
||||
The contents of this repository are still available in the Git
|
||||
source code management system. To see the contents of this
|
||||
repository before it reached its end of life, please check out the
|
||||
previous commit with "git checkout HEAD^1".
|
||||
|
||||
.. Change things from this point on
|
||||
|
||||
====================
|
||||
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 .
|
||||
For any further questions, please email
|
||||
openstack-discuss@lists.openstack.org or join #openstack-dev on
|
||||
Freenode.
|
||||
|
|
|
@ -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