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:
Ghanshyam Mann 2020-11-27 21:34:46 -06:00
parent 8e5fcbbca2
commit 4d184021c2
113 changed files with 8 additions and 11405 deletions

View File

@ -1,8 +0,0 @@
[run]
source = qinlingclient
omit =
.tox/*
qinlingclient/tests/*
[report]
ignore_errors = True

41
.gitignore vendored
View File

@ -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

View File

@ -1,4 +0,0 @@
[DEFAULT]
test_path=${OS_TEST_PATH:-./qinlingclient/tests/unit}
top_dir=./

View File

@ -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

View File

@ -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

View File

@ -1,4 +0,0 @@
Style Commandments
==================
Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/

176
LICENSE
View File

@ -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.

View File

@ -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.

View File

@ -1 +0,0 @@
[python: **.py]

View File

@ -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

View File

@ -1,2 +0,0 @@
.. include:: osc/v1/qinling.rst

View File

@ -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 *

View File

@ -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'

View File

@ -1,4 +0,0 @@
============
Contributing
============
.. include:: ../../../CONTRIBUTING.rst

View File

@ -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`

View File

@ -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.

View File

@ -1 +0,0 @@
.. include:: ../../README.rst

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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))

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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")

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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."""

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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")

View File

@ -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
)

View File

@ -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))

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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())

View File

@ -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)

View File

@ -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)

View File

@ -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
)

View File

@ -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
)

View File

@ -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])

View File

@ -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)

View File

@ -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
)

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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
)

View File

@ -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)

View File

@ -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)

View File

@ -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',
)

View File

@ -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')

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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')

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -1,4 +0,0 @@
---
fixes:
- Webhook function_version will not be reset automatically to 0 during
an update.

View File

@ -1,3 +0,0 @@
---
features:
- Support function alias CLI.

View File

@ -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.

View File

@ -1,3 +0,0 @@
---
features:
- Support function versioning CLI.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -1,4 +0,0 @@
---
features:
- The end user can create job by specifying function version, default value
is 0 if it's not provided.

View File

@ -1,4 +0,0 @@
---
features:
- The user can create or update webhook by specifying the function version
(``--function-version``) together with the function.

View File

@ -1,3 +0,0 @@
---
features:
- Support function name in most of the function operations CLI.

View File

@ -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.

View File

@ -1,3 +0,0 @@
---
features:
- Support ``--untrusted`` parameter when creating runtime.

View File

@ -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

View File

@ -1,13 +0,0 @@
=============================
Qinling Client Release Notes
=============================
.. toctree::
:maxdepth: 1
unreleased
victoria
ussuri
train
stein
rocky

View File

@ -1,6 +0,0 @@
===================================
Rocky Series Release Notes
===================================
.. release-notes::
:branch: stable/rocky

View File

@ -1,6 +0,0 @@
===================================
Stein Series Release Notes
===================================
.. release-notes::
:branch: stable/stein

View File

@ -1,6 +0,0 @@
==========================
Train Series Release Notes
==========================
.. release-notes::
:branch: stable/train

View File

@ -1,5 +0,0 @@
==============================
Current Series Release Notes
==============================
.. release-notes::

View File

@ -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