python-qinlingclient-5.0.1/0000775000175000017500000000000013643577506015736 5ustar zuulzuul00000000000000python-qinlingclient-5.0.1/babel.cfg0000664000175000017500000000002013643577416017454 0ustar zuulzuul00000000000000[python: **.py] python-qinlingclient-5.0.1/setup.sh0000664000175000017500000001266113643577416017440 0ustar zuulzuul00000000000000#!/bin/sh # Copyright (c) 2013 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # # Ubuntu script. LOGLVL=1 SERVICE_CONTENT_DIRECTORY=`cd $(dirname "$0") && pwd` PREREQ_PKGS="wget make git python-pip python-dev python-mysqldb libxml2-dev libxslt-dev" SERVICE_SRV_NAME="python-qinlingclient" GIT_CLONE_DIR=`echo $SERVICE_CONTENT_DIRECTORY | sed -e "s/$SERVICE_SRV_NAME//"` # Functions # Loger function log() { MSG=$1 if [ $LOGLVL -gt 0 ]; then echo "LOG:> $MSG" fi } # Check or install package in_sys_pkg() { PKG=$1 dpkg -s $PKG > /dev/null 2>&1 if [ $? -eq 0 ]; then log "Package \"$PKG\" already installed" else log "Installing \"$PKG\"..." apt-get install $PKG --yes > /dev/null 2>&1 if [ $? -ne 0 ];then log "installation fails, exiting!!!" exit fi fi } # git clone gitclone() { FROM=$1 CLONEROOT=$2 log "Cloning from \"$FROM\" repo to \"$CLONEROOT\"" cd $CLONEROOT && git clone $FROM > /dev/null 2>&1 if [ $? -ne 0 ];then log "cloning from \"$FROM\" fails, exiting!!!" exit fi } # install inst() { CLONE_FROM_GIT=$1 # Checking packages for PKG in $PREREQ_PKGS do in_sys_pkg $PKG done # If clone from git set if [ ! -z $CLONE_FROM_GIT ]; then # Preparing clone root directory if [ ! -d $GIT_CLONE_DIR ];then log "Creating $GIT_CLONE_DIR directory..." mkdir -p $GIT_CLONE_DIR if [ $? -ne 0 ];then log "Can't create $GIT_CLONE_DIR, exiting!!!" exit fi fi # Cloning from GIT GIT_WEBPATH_PRFX="https://opendev.org/openstack/" gitclone "$GIT_WEBPATH_PRFX$SERVICE_SRV_NAME.git" $GIT_CLONE_DIR # End clone from git section fi # Setupping... log "Running setup.py" #MRN_CND_SPY=$GIT_CLONE_DIR/$SERVICE_SRV_NAME/setup.py MRN_CND_SPY=$SERVICE_CONTENT_DIRECTORY/setup.py if [ -e $MRN_CND_SPY ]; then chmod +x $MRN_CND_SPY log "$MRN_CND_SPY output:_____________________________________________________________" #cd $GIT_CLONE_DIR/$SERVICE_SRV_NAME && $MRN_CND_SPY install #if [ $? -ne 0 ]; then # log "\"$MRN_CND_SPY\" python setup FAILS, exiting!" # exit 1 #fi ## Setup through pip # Creating tarball #cd $GIT_CLONE_DIR/$SERVICE_SRV_NAME && $MRN_CND_SPY sdist rm -rf $SERVICE_CONTENT_DIRECTORY/*.egg-info cd $SERVICE_CONTENT_DIRECTORY && python $MRN_CND_SPY egg_info if [ $? -ne 0 ];then log "\"$MRN_CND_SPY\" egg info creation FAILS, exiting!!!" exit 1 fi rm -rf $SERVICE_CONTENT_DIRECTORY/dist/* cd $SERVICE_CONTENT_DIRECTORY && python $MRN_CND_SPY sdist if [ $? -ne 0 ];then log "\"$MRN_CND_SPY\" tarball creation FAILS, exiting!!!" exit 1 fi # Running tarball install #TRBL_FILE=$(basename `ls $GIT_CLONE_DIR/$SERVICE_SRV_NAME/dist/*.tar.gz`) #pip install $GIT_CLONE_DIR/$SERVICE_SRV_NAME/dist/$TRBL_FILE TRBL_FILE=$(basename `ls $SERVICE_CONTENT_DIRECTORY/dist/*.tar.gz`) pip install $SERVICE_CONTENT_DIRECTORY/dist/$TRBL_FILE if [ $? -ne 0 ];then log "pip install \"$TRBL_FILE\" FAILS, exiting!!!" exit 1 fi else log "$MRN_CND_SPY not found!" fi } # uninstall uninst() { # Uninstall trough pip # looking up for python package installed #PYPKG=`echo $SERVICE_SRV_NAME | tr -d '-'` PYPKG="qinlingclient" pip freeze | grep $PYPKG if [ $? -eq 0 ]; then log "Removing package \"$PYPKG\" with pip" pip uninstall $PYPKG --yes else log "Python package \"$PYPKG\" not found" fi } # Command line args' COMMAND="$1" case $COMMAND in install ) inst ;; installfromgit ) inst "yes" ;; uninstall ) log "Uninstalling qinlingclient \"$SERVICE_SRV_NAME\" from system..." uninst ;; * ) echo "Usage: $(basename "$0") command \nCommands:\n\tinstall - Install $SERVICE_SRV_NAME software\n\tuninstall - Uninstall $SERVICE_SRV_NAME software" exit 1 ;; esac python-qinlingclient-5.0.1/.zuul.yaml0000664000175000017500000000045313643577416017701 0ustar zuulzuul00000000000000- project: templates: - openstack-cover-jobs - openstack-lower-constraints-jobs - openstack-python3-ussuri-jobs - check-requirements - publish-openstack-docs-pti - release-notes-jobs-python3 - openstackclient-plugin-jobs gate: queue: qinling python-qinlingclient-5.0.1/AUTHORS0000664000175000017500000000167513643577506017017 0ustar zuulzuul0000000000000098k <18552437190@163.com> Andreas Jaeger Chuck Short Corey Bryant Dong Ma Doug Hellmann Gaƫtan Trellu Ghanshyam Mann Hunt Xu Jiangyuan Lingxian Kong Neerja Narayan OpenStack Release Bot ShangXiao Vieri <15050873171@163.com> Vu Cong Tuan ZhongShengping cao.yuan huang.zhiping jacky06 kangyufei melissaml pengyuesheng qingszhao zhang.lei zhangboye zhubingbing python-qinlingclient-5.0.1/setup.cfg0000664000175000017500000000643113643577506017563 0ustar zuulzuul00000000000000[metadata] name = python-qinlingclient summary = python-qinlingclient description-file = README.rst license = Apache License, Version 2.0 author = OpenStack author-email = openstack-discuss@lists.openstack.org home-page = https://docs.openstack.org/qinling/latest/ python-requires = >=3.6 classifier = Development Status :: 4 - Beta Environment :: Console Environment :: OpenStack Intended Audience :: Developers Intended Audience :: Information Technology Intended Audience :: System Administrators License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: 3 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 [files] packages = qinlingclient [entry_points] openstack.cli.extension = function_engine = qinlingclient.osc.plugin openstack.function_engine.v1 = runtime_list = qinlingclient.osc.v1.runtime:List runtime_create = qinlingclient.osc.v1.runtime:Create runtime_delete = qinlingclient.osc.v1.runtime:Delete runtime_show = qinlingclient.osc.v1.runtime:Show runtime_pool_show = qinlingclient.osc.v1.runtime:Pool function_list = qinlingclient.osc.v1.function:List function_create = qinlingclient.osc.v1.function:Create function_delete = qinlingclient.osc.v1.function:Delete function_show = qinlingclient.osc.v1.function:Show function_update = qinlingclient.osc.v1.function:Update function_detach = qinlingclient.osc.v1.function:Detach function_download = qinlingclient.osc.v1.function:Download function_scaleup = qinlingclient.osc.v1.function:Scaleup function_scaledown = qinlingclient.osc.v1.function:Scaledown function_worker_list = qinlingclient.osc.v1.function_worker:List function_execution_list = qinlingclient.osc.v1.function_execution:List function_execution_create = qinlingclient.osc.v1.function_execution:Create function_execution_delete = qinlingclient.osc.v1.function_execution:Delete function_execution_show = qinlingclient.osc.v1.function_execution:Show function_execution_log_show = qinlingclient.osc.v1.function_execution:LogShow job_list = qinlingclient.osc.v1.job:List job_create = qinlingclient.osc.v1.job:Create job_delete = qinlingclient.osc.v1.job:Delete job_show = qinlingclient.osc.v1.job:Show job_update = qinlingclient.osc.v1.job:Update webhook_list = qinlingclient.osc.v1.webhook:List webhook_create = qinlingclient.osc.v1.webhook:Create webhook_delete = qinlingclient.osc.v1.webhook:Delete webhook_show = qinlingclient.osc.v1.webhook:Show webhook_update = qinlingclient.osc.v1.webhook:Update function_version_create = qinlingclient.osc.v1.function_version:Create function_version_list = qinlingclient.osc.v1.function_version:List function_version_show = qinlingclient.osc.v1.function_version:Show function_version_delete = qinlingclient.osc.v1.function_version:Delete function_version_detach = qinlingclient.osc.v1.function_version:Detach function_alias_create = qinlingclient.osc.v1.function_alias:Create function_alias_list = qinlingclient.osc.v1.function_alias:List function_alias_show = qinlingclient.osc.v1.function_alias:Show function_alias_delete = qinlingclient.osc.v1.function_alias:Delete function_alias_update = qinlingclient.osc.v1.function_alias:Update [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 python-qinlingclient-5.0.1/HACKING.rst0000664000175000017500000000017013643577416017532 0ustar zuulzuul00000000000000Style Commandments ================== Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/ python-qinlingclient-5.0.1/setup.py0000664000175000017500000000127113643577416017451 0ustar zuulzuul00000000000000# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import setuptools setuptools.setup( setup_requires=['pbr>=2.0.0'], pbr=True) python-qinlingclient-5.0.1/requirements.txt0000664000175000017500000000120513643577416021220 0ustar zuulzuul00000000000000# 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. pbr!=2.1.0,>=2.0.0 # Apache-2.0 PrettyTable<0.8,>=0.7.2 # BSD python-keystoneclient>=3.8.0 # Apache-2.0 python-openstackclient>=3.12.0 # Apache-2.0 iso8601>=0.1.11 # MIT six>=1.10.0 # MIT Babel!=2.4.0,>=2.3.4 # BSD requests>=2.14.2 # Apache-2.0 PyYAML>=3.12 # MIT osc-lib>=1.8.0 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 oslo.log>=3.36.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0 python-qinlingclient-5.0.1/LICENSE0000664000175000017500000002363713643577416016756 0ustar zuulzuul00000000000000 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. python-qinlingclient-5.0.1/doc/0000775000175000017500000000000013643577506016503 5ustar zuulzuul00000000000000python-qinlingclient-5.0.1/doc/source/0000775000175000017500000000000013643577506020003 5ustar zuulzuul00000000000000python-qinlingclient-5.0.1/doc/source/conf.py0000775000175000017500000000512613643577416021311 0ustar zuulzuul00000000000000# -*- 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 repository_name = 'openstack/python-qinlingclient' use_storyboard = True autoprogram_cliff_application = 'openstack' python-qinlingclient-5.0.1/doc/source/install/0000775000175000017500000000000013643577506021451 5ustar zuulzuul00000000000000python-qinlingclient-5.0.1/doc/source/install/index.rst0000664000175000017500000000070013643577416023307 0ustar zuulzuul00000000000000============ 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. python-qinlingclient-5.0.1/doc/source/readme.rst0000664000175000017500000000003613643577416021771 0ustar zuulzuul00000000000000.. include:: ../../README.rst python-qinlingclient-5.0.1/doc/source/contributor/0000775000175000017500000000000013643577506022355 5ustar zuulzuul00000000000000python-qinlingclient-5.0.1/doc/source/contributor/index.rst0000664000175000017500000000011613643577416024214 0ustar zuulzuul00000000000000============ Contributing ============ .. include:: ../../../CONTRIBUTING.rst python-qinlingclient-5.0.1/doc/source/cli/0000775000175000017500000000000013643577506020552 5ustar zuulzuul00000000000000python-qinlingclient-5.0.1/doc/source/cli/osc/0000775000175000017500000000000013643577506021336 5ustar zuulzuul00000000000000python-qinlingclient-5.0.1/doc/source/cli/osc/v1/0000775000175000017500000000000013643577506021664 5ustar zuulzuul00000000000000python-qinlingclient-5.0.1/doc/source/cli/osc/v1/qinling.rst0000664000175000017500000000075613643577416024067 0ustar zuulzuul00000000000000Using 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 * python-qinlingclient-5.0.1/doc/source/cli/index.rst0000664000175000017500000000004113643577416022406 0ustar zuulzuul00000000000000 .. include:: osc/v1/qinling.rst python-qinlingclient-5.0.1/doc/source/index.rst0000664000175000017500000000064613643577416021652 0ustar zuulzuul00000000000000Python 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 install/index contributor/index Usage ----- .. toctree:: :maxdepth: 2 cli/index Indices and tables ------------------ * :ref:`genindex` * :ref:`search` python-qinlingclient-5.0.1/doc/requirements.txt0000664000175000017500000000050113643577416021763 0ustar zuulzuul00000000000000# 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!=1.6.6,!=1.6.7,!=2.1.0,>=1.6.5 # BSD openstackdocstheme>=1.24.0 # Apache-2.0 reno>=2.5.0 # Apache-2.0 python-qinlingclient-5.0.1/PKG-INFO0000664000175000017500000000507013643577506017035 0ustar zuulzuul00000000000000Metadata-Version: 1.2 Name: python-qinlingclient Version: 5.0.1 Summary: python-qinlingclient Home-page: https://docs.openstack.org/qinling/latest/ Author: OpenStack Author-email: openstack-discuss@lists.openstack.org License: Apache License, Version 2.0 Description: ======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/python-qinlingclient.svg :target: https://governance.openstack.org/reference/tags/index.html .. 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 . Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Environment :: Console Classifier: Environment :: OpenStack Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Requires-Python: >=3.6 python-qinlingclient-5.0.1/run_tests.sh0000775000175000017500000000556613643577416020337 0ustar zuulzuul00000000000000#!/bin/bash function usage { echo "Usage: $0 [OPTION]..." echo "Run python-qinlingclient's test suite(s)" echo "" echo " -p, --pep8 Just run pep8" echo " -h, --help Print this usage message" echo " -V, --virtual-env Always use virtualenv. Install automatically if not present" echo " -N, --no-virtual-env Don't use virtualenv. Run tests in local environment" echo " -f, --force Force a clean re-build of the virtual environment. Useful when dependencies have been added." echo " -u, --update Update the virtual environment with any newer package versions" echo "" echo "This script is deprecated and currently retained for compatibility." echo 'You can run the full test suite for multiple environments by running "tox".' echo 'You can run tests for only python 3.7 by running "tox -e py37", or run only' echo 'the pep8 tests with "tox -e pep8".' exit } just_pep8=0 always_venv=0 never_venv=0 wrapper= update=0 force=0 export NOSE_WITH_OPENSTACK=1 export NOSE_OPENSTACK_COLOR=1 export NOSE_OPENSTACK_RED=0.05 export NOSE_OPENSTACK_YELLOW=0.025 export NOSE_OPENSTACK_SHOW_ELAPSED=1 export NOSE_OPENSTACK_STDOUT=1 function process_option { case "$1" in -h|--help) usage;; -p|--pep8) let just_pep8=1;; -V|--virtual-env) let always_venv=1; let never_venv=0;; -f|--force) let force=1;; -u|--update) update=1;; -N|--no-virtual-env) let always_venv=0; let never_venv=1;; esac } for arg in "$@"; do process_option $arg done function run_tests { # Cleanup *pyc and *pyo ${wrapper} find . -type f -name "*.py[c|o]" -delete # Just run the test suites in current environment ${wrapper} $NOSETESTS } function run_pep8 { echo "Running pep8 ..." PEP8_EXCLUDE=".venv,.tox,dist,doc,openstack,build" PEP8_OPTIONS="--exclude=$PEP8_EXCLUDE --repeat --select=H402" PEP8_IGNORE="--ignore=E125,E126,E711,E712" PEP8_INCLUDE="." pep8 $PEP8_OPTIONS $PEP8_INCLUDE $PEP8_IGNORE } NOSETESTS="nosetests $noseopts $noseargs" if [ $never_venv -eq 0 ] then # Remove the virtual environment if --force used if [ $force -eq 1 ]; then echo "Cleaning virtualenv..." rm -rf ${venv} fi if [ $update -eq 1 ]; then echo "Updating virtualenv..." python tools/install_venv.py fi if [ -e ${venv} ]; then wrapper="${with_venv}" else if [ $always_venv -eq 1 ]; then # Automatically install the virtualenv python tools/install_venv.py wrapper="${with_venv}" else echo -e "No virtual environment found...create one? (Y/n) \c" read use_ve if [ "x$use_ve" = "xY" -o "x$use_ve" = "x" -o "x$use_ve" = "xy" ]; then # Install the virtualenv and run the test suite in it python tools/install_venv.py wrapper=${with_venv} fi fi fi fi if [ $just_pep8 -eq 1 ]; then run_pep8 exit fi run_tests || exit run_pep8 python-qinlingclient-5.0.1/releasenotes/0000775000175000017500000000000013643577506020427 5ustar zuulzuul00000000000000python-qinlingclient-5.0.1/releasenotes/source/0000775000175000017500000000000013643577506021727 5ustar zuulzuul00000000000000python-qinlingclient-5.0.1/releasenotes/source/unreleased.rst0000664000175000017500000000016013643577416024605 0ustar zuulzuul00000000000000============================== Current Series Release Notes ============================== .. release-notes:: python-qinlingclient-5.0.1/releasenotes/source/conf.py0000664000175000017500000002142413643577416023231 0ustar zuulzuul00000000000000# -*- 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 # " v 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 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/'] python-qinlingclient-5.0.1/releasenotes/source/_static/0000775000175000017500000000000013643577506023355 5ustar zuulzuul00000000000000python-qinlingclient-5.0.1/releasenotes/source/_static/.placeholder0000664000175000017500000000000013643577416025626 0ustar zuulzuul00000000000000python-qinlingclient-5.0.1/releasenotes/source/rocky.rst0000664000175000017500000000022113643577416023603 0ustar zuulzuul00000000000000=================================== Rocky Series Release Notes =================================== .. release-notes:: :branch: stable/rocky python-qinlingclient-5.0.1/releasenotes/source/train.rst0000664000175000017500000000017613643577416023602 0ustar zuulzuul00000000000000========================== Train Series Release Notes ========================== .. release-notes:: :branch: stable/train python-qinlingclient-5.0.1/releasenotes/source/_templates/0000775000175000017500000000000013643577506024064 5ustar zuulzuul00000000000000python-qinlingclient-5.0.1/releasenotes/source/_templates/.placeholder0000664000175000017500000000000013643577416026335 0ustar zuulzuul00000000000000python-qinlingclient-5.0.1/releasenotes/source/stein.rst0000664000175000017500000000022113643577416023576 0ustar zuulzuul00000000000000=================================== Stein Series Release Notes =================================== .. release-notes:: :branch: stable/stein python-qinlingclient-5.0.1/releasenotes/source/index.rst0000664000175000017500000000024213643577416023566 0ustar zuulzuul00000000000000============================= Qinling Client Release Notes ============================= .. toctree:: :maxdepth: 1 unreleased train stein rocky python-qinlingclient-5.0.1/releasenotes/notes/0000775000175000017500000000000013643577506021557 5ustar zuulzuul00000000000000python-qinlingclient-5.0.1/releasenotes/notes/.placeholder0000664000175000017500000000000013643577416024030 0ustar zuulzuul00000000000000python-qinlingclient-5.0.1/releasenotes/notes/drop-py-2-7-1fefd2071a107cc7.yaml0000664000175000017500000000031613643577416026623 0ustar zuulzuul00000000000000--- 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. ././@LongLink0000000000000000000000000000015100000000000011212 Lustar 00000000000000python-qinlingclient-5.0.1/releasenotes/notes/support-function-resource-limitation-f6f519999f5e23cd.yamlpython-qinlingclient-5.0.1/releasenotes/notes/support-function-resource-limitation-f6f519999f5e23cd.0000664000175000017500000000063113643577416033360 0ustar zuulzuul00000000000000--- 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. python-qinlingclient-5.0.1/releasenotes/notes/support-name-function-operation-1d32c4c2fc58bf26.yaml0000664000175000017500000000012013643577416033200 0ustar zuulzuul00000000000000--- features: - Support function name in most of the function operations CLI. python-qinlingclient-5.0.1/releasenotes/notes/function-aliasing-299f134f370d0681.yaml0000664000175000017500000000005613643577416030060 0ustar zuulzuul00000000000000--- features: - Support function alias CLI. python-qinlingclient-5.0.1/releasenotes/notes/support-function-alias-for-job-eb10994cac2c11e9.yaml0000664000175000017500000000035613643577416032720 0ustar zuulzuul00000000000000features: - 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. python-qinlingclient-5.0.1/releasenotes/notes/fix-webhook-update-version-4c79ac51af5656dd.yaml0000664000175000017500000000014313643577416032131 0ustar zuulzuul00000000000000--- fixes: - Webhook function_version will not be reset automatically to 0 during an update. python-qinlingclient-5.0.1/releasenotes/notes/function-versioning-81881bc35bc3eb64.yaml0000664000175000017500000000006313643577416030665 0ustar zuulzuul00000000000000--- features: - Support function versioning CLI. python-qinlingclient-5.0.1/releasenotes/notes/get-runtime-pool-d263e74d21b27091.yaml0000664000175000017500000000045413643577416027725 0ustar zuulzuul00000000000000--- 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. python-qinlingclient-5.0.1/releasenotes/notes/support-untrusted-runtime-72739ab479265d81.yaml0000664000175000017500000000011313643577416031700 0ustar zuulzuul00000000000000--- features: - Support ``--untrusted`` parameter when creating runtime. ././@LongLink0000000000000000000000000000017100000000000011214 Lustar 00000000000000python-qinlingclient-5.0.1/releasenotes/notes/add-support-function-alias-for-execution-and-webhook-8df54e2fe4a27779.yamlpython-qinlingclient-5.0.1/releasenotes/notes/add-support-function-alias-for-execution-and-webhook-80000664000175000017500000000045213643577416034005 0ustar zuulzuul00000000000000--- 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. python-qinlingclient-5.0.1/releasenotes/notes/function-timeout-f4c9d6d8ea2c7204.yaml0000664000175000017500000000027613643577416030262 0ustar zuulzuul00000000000000--- features: - Support to specify function timeout when creating and updating the functions. When the specified timeout is reached, Qinling will terminate the function execution. python-qinlingclient-5.0.1/releasenotes/notes/deprecate-code-type-05211c5cf8cf5c51.yaml0000664000175000017500000000023313643577416030472 0ustar zuulzuul00000000000000--- deprecations: - The param ``--code-type`` of function creation command is deprecated, the actual code type can be deduced by other given params. ././@LongLink0000000000000000000000000000015100000000000011212 Lustar 00000000000000python-qinlingclient-5.0.1/releasenotes/notes/support-function-version-for-webhook-67beca9f1c78eb58.yamlpython-qinlingclient-5.0.1/releasenotes/notes/support-function-version-for-webhook-67beca9f1c78eb58.0000664000175000017500000000022413643577416033413 0ustar zuulzuul00000000000000--- features: - The user can create or update webhook by specifying the function version (``--function-version``) together with the function. python-qinlingclient-5.0.1/releasenotes/notes/support-resource-name-cd26d0edbd56bdc5.yaml0000664000175000017500000000032313643577416031430 0ustar zuulzuul00000000000000--- 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. python-qinlingclient-5.0.1/releasenotes/notes/support-function-version-for-job-7cb12ebb9fd64456.yaml0000664000175000017500000000017313643577416033323 0ustar zuulzuul00000000000000--- features: - The end user can create job by specifying function version, default value is 0 if it's not provided. python-qinlingclient-5.0.1/README.rst0000664000175000017500000000251613643577416017431 0ustar zuulzuul00000000000000======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/python-qinlingclient.svg :target: https://governance.openstack.org/reference/tags/index.html .. 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 . python-qinlingclient-5.0.1/.stestr.conf0000664000175000017500000000011413643577416020203 0ustar zuulzuul00000000000000[DEFAULT] test_path=${OS_TEST_PATH:-./qinlingclient/tests/unit} top_dir=./ python-qinlingclient-5.0.1/qinlingclient/0000775000175000017500000000000013643577506020576 5ustar zuulzuul00000000000000python-qinlingclient-5.0.1/qinlingclient/i18n.py0000664000175000017500000000152513643577416021732 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 python-qinlingclient-5.0.1/qinlingclient/osc/0000775000175000017500000000000013643577506021362 5ustar zuulzuul00000000000000python-qinlingclient-5.0.1/qinlingclient/osc/plugin.py0000664000175000017500000000473413643577416023242 0ustar zuulzuul00000000000000# 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='', 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 python-qinlingclient-5.0.1/qinlingclient/osc/v1/0000775000175000017500000000000013643577506021710 5ustar zuulzuul00000000000000python-qinlingclient-5.0.1/qinlingclient/osc/v1/function_alias.py0000664000175000017500000001141613643577416025263 0ustar zuulzuul00000000000000# 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) python-qinlingclient-5.0.1/qinlingclient/osc/v1/function_version.py0000664000175000017500000001105713643577416025660 0ustar zuulzuul00000000000000# 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) python-qinlingclient-5.0.1/qinlingclient/osc/v1/base.py0000664000175000017500000001225513643577416023201 0ustar zuulzuul00000000000000# 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 import six 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" ) @six.add_metaclass(abc.ABCMeta) class QinlingLister(command.Lister): 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 python-qinlingclient-5.0.1/qinlingclient/osc/v1/webhook.py0000664000175000017500000001135313643577416023723 0ustar zuulzuul00000000000000# 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) python-qinlingclient-5.0.1/qinlingclient/osc/v1/function_execution.py0000664000175000017500000001217513643577416026200 0ustar zuulzuul00000000000000# 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") python-qinlingclient-5.0.1/qinlingclient/osc/v1/job.py0000664000175000017500000001327013643577416023037 0ustar zuulzuul00000000000000# 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) python-qinlingclient-5.0.1/qinlingclient/osc/v1/function.py0000664000175000017500000004214413643577416024114 0ustar zuulzuul00000000000000# 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 ." ) 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 ." ) 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) python-qinlingclient-5.0.1/qinlingclient/osc/v1/runtime.py0000664000175000017500000000714513643577416023754 0ustar zuulzuul00000000000000# 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( "--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, 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) python-qinlingclient-5.0.1/qinlingclient/osc/v1/__init__.py0000664000175000017500000000000013643577416024007 0ustar zuulzuul00000000000000python-qinlingclient-5.0.1/qinlingclient/osc/v1/function_worker.py0000664000175000017500000000204713643577416025503 0ustar zuulzuul00000000000000# 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) python-qinlingclient-5.0.1/qinlingclient/osc/__init__.py0000664000175000017500000000000013643577416023461 0ustar zuulzuul00000000000000python-qinlingclient-5.0.1/qinlingclient/utils.py0000664000175000017500000000252113643577416022310 0ustar zuulzuul00000000000000# 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 python-qinlingclient-5.0.1/qinlingclient/common/0000775000175000017500000000000013643577506022066 5ustar zuulzuul00000000000000python-qinlingclient-5.0.1/qinlingclient/common/exceptions.py0000664000175000017500000001200613643577416024620 0ustar zuulzuul00000000000000# 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)) python-qinlingclient-5.0.1/qinlingclient/common/base.py0000664000175000017500000001633113643577416023356 0ustar zuulzuul00000000000000# 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 import six 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) @six.add_metaclass(abc.ABCMeta) class ManagerWithFind(Manager): """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) python-qinlingclient-5.0.1/qinlingclient/common/__init__.py0000664000175000017500000000000013643577416024165 0ustar zuulzuul00000000000000python-qinlingclient-5.0.1/qinlingclient/common/http.py0000664000175000017500000003066313643577416023427 0ustar zuulzuul00000000000000# 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 import six 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, six.binary_type): 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) python-qinlingclient-5.0.1/qinlingclient/client.py0000664000175000017500000000161213643577416022426 0ustar zuulzuul00000000000000# 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) python-qinlingclient-5.0.1/qinlingclient/version.py0000664000175000017500000000127013643577416022635 0ustar zuulzuul00000000000000# 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') python-qinlingclient-5.0.1/qinlingclient/v1/0000775000175000017500000000000013643577506021124 5ustar zuulzuul00000000000000python-qinlingclient-5.0.1/qinlingclient/v1/function_alias.py0000664000175000017500000000345213643577416024500 0ustar zuulzuul00000000000000# 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) python-qinlingclient-5.0.1/qinlingclient/v1/function_version.py0000664000175000017500000000310513643577416025067 0ustar zuulzuul00000000000000# 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', ) python-qinlingclient-5.0.1/qinlingclient/v1/webhook.py0000664000175000017500000000364013643577416023137 0ustar zuulzuul00000000000000# 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) python-qinlingclient-5.0.1/qinlingclient/v1/function_execution.py0000664000175000017500000000331613643577416025411 0ustar zuulzuul00000000000000# 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) python-qinlingclient-5.0.1/qinlingclient/v1/job.py0000664000175000017500000000320113643577416022244 0ustar zuulzuul00000000000000# 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) python-qinlingclient-5.0.1/qinlingclient/v1/client.py0000664000175000017500000000410313643577416022752 0ustar zuulzuul00000000000000# 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) python-qinlingclient-5.0.1/qinlingclient/v1/function.py0000664000175000017500000000605713643577416023333 0ustar zuulzuul00000000000000# 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 ) python-qinlingclient-5.0.1/qinlingclient/v1/versions.py0000664000175000017500000000166113643577416023352 0ustar zuulzuul00000000000000# 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) python-qinlingclient-5.0.1/qinlingclient/v1/runtime.py0000664000175000017500000000257213643577416023167 0ustar zuulzuul00000000000000# 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, trusted=True): data = {'image': image, '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) python-qinlingclient-5.0.1/qinlingclient/v1/__init__.py0000664000175000017500000000000013643577416023223 0ustar zuulzuul00000000000000python-qinlingclient-5.0.1/qinlingclient/v1/function_worker.py0000664000175000017500000000162213643577416024715 0ustar zuulzuul00000000000000# 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') python-qinlingclient-5.0.1/qinlingclient/__init__.py0000664000175000017500000000143213643577416022707 0ustar zuulzuul00000000000000# 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 python-qinlingclient-5.0.1/qinlingclient/tests/0000775000175000017500000000000013643577506021740 5ustar zuulzuul00000000000000python-qinlingclient-5.0.1/qinlingclient/tests/unit/0000775000175000017500000000000013643577506022717 5ustar zuulzuul00000000000000python-qinlingclient-5.0.1/qinlingclient/tests/unit/fakes.py0000664000175000017500000000526013643577416024365 0ustar zuulzuul00000000000000# 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 mock import six 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 six.iteritems(info): 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 six.iteritems(methods): 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) python-qinlingclient-5.0.1/qinlingclient/tests/unit/osc/0000775000175000017500000000000013643577506023503 5ustar zuulzuul00000000000000python-qinlingclient-5.0.1/qinlingclient/tests/unit/osc/v1/0000775000175000017500000000000013643577506024031 5ustar zuulzuul00000000000000python-qinlingclient-5.0.1/qinlingclient/tests/unit/osc/v1/test_runtime.py0000664000175000017500000002367013643577416027135 0ustar zuulzuul00000000000000# 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 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), ] 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, }) 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 attrs = {'image': image, 'name': name, 'description': description, 'trusted': trusted} created_data = self._create_fake_runtime(attrs) arglist = [ '--name', name, '--description', description, '--untrusted', image, ] verifylist = [ ('image', image), ('name', name), ('description', description), ('trusted', trusted), ] 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, }) 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) python-qinlingclient-5.0.1/qinlingclient/tests/unit/osc/v1/fakes.py0000664000175000017500000005162313643577416025503 0ustar zuulzuul00000000000000# 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 import mock import six 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, six.binary_type): 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) python-qinlingclient-5.0.1/qinlingclient/tests/unit/osc/v1/test_function.py0000664000175000017500000015460513643577416027302 0ustar zuulzuul00000000000000# 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 mock import zipfile from osc_lib.tests import utils as osc_tests_utils import testtools from qinlingclient.common import exceptions from qinlingclient.osc.v1 import base from qinlingclient.osc.v1 import function from qinlingclient.tests.unit.osc.v1 import fakes class TestFunctionUtils(testtools.TestCase): def setUp(self): super(TestFunctionUtils, self).setUp() @mock.patch('zipfile.is_zipfile') @mock.patch('os.path.getsize') def test__get_package_file_package(self, getsize_mock, is_zipfile_mock): is_zipfile_mock.return_value = True getsize_mock.return_value = function.MAX_ZIP_SIZE package_path = '/PATH/TO/FAKE_PACKAGE.ZIP' ret = function._get_package_file(package_path=package_path) self.assertEqual(package_path, ret) is_zipfile_mock.assert_called_once_with(package_path) getsize_mock.assert_called_once_with(package_path) @mock.patch('zipfile.is_zipfile') def test__get_package_file_package_not_a_valid_zip_file(self, is_zipfile_mock): is_zipfile_mock.return_value = False package_path = '/PATH/TO/NOT_A_ZIP_FILE.py' self.assertRaisesRegex( exceptions.QinlingClientException, r"^Package %s is not a valid ZIP file\.$" % package_path, function._get_package_file, package_path=package_path) is_zipfile_mock.assert_called_once_with(package_path) @mock.patch('zipfile.is_zipfile') @mock.patch('os.path.getsize') def test__get_package_file_package_size_exceed(self, getsize_mock, is_zipfile_mock): is_zipfile_mock.return_value = True getsize_mock.return_value = function.MAX_ZIP_SIZE + 1 package_path = '/PATH/TO/FAKE_PACKAGE.ZIP' self.assertRaisesRegex( exceptions.QinlingClientException, r'^Package file size must be no more than %sM\.$' % ( function.MAX_ZIP_SIZE / 1024 / 1024), function._get_package_file, package_path=package_path) is_zipfile_mock.assert_called_once_with(package_path) getsize_mock.assert_called_once_with(package_path) @mock.patch('os.path.isfile') @mock.patch('tempfile.gettempdir') @mock.patch('zipfile.ZipFile') @mock.patch('os.path.getsize') def test__get_package_file_file(self, getsize_mock, ZipFileClassMock, gettempdir_mock, isfile_mock): isfile_mock.return_value = True fake_tempdir_path = '/FAKE/TEMP_DIR' gettempdir_mock.return_value = fake_tempdir_path fake_zf = mock.Mock() ZipFileClassMock.return_value = fake_zf getsize_mock.return_value = function.MAX_ZIP_SIZE base_name = 'FAKE_FILE' extension = '.py' file_path = '/PATH/TO/%s%s' % (base_name, extension) zip_file_path = '%s/%s.zip' % (fake_tempdir_path, base_name) ret = function._get_package_file(file_path=file_path) self.assertEqual(zip_file_path, ret) isfile_mock.assert_called_once_with(file_path) ZipFileClassMock.assert_called_once_with(zip_file_path, mode='w') fake_zf.write.assert_called_once_with( file_path, '%s%s' % (base_name, extension), compress_type=zipfile.ZIP_STORED) fake_zf.close.assert_called_once_with() getsize_mock.assert_called_once_with(zip_file_path) @mock.patch('os.path.isfile') def test__get_package_file_file_not_exist(self, isfile_mock): isfile_mock.return_value = False file_path = '/PATH/TO/A_NON_EXIST_FILE.py' self.assertRaisesRegex( exceptions.QinlingClientException, r"^File %s not exist\.$" % file_path, function._get_package_file, file_path=file_path) isfile_mock.assert_called_once_with(file_path) @mock.patch('os.path.isfile') @mock.patch('tempfile.gettempdir') @mock.patch('zipfile.ZipFile') @mock.patch('os.path.getsize') def test__get_package_file_file_zipped_size_exceed(self, getsize_mock, ZipFileClassMock, gettempdir_mock, isfile_mock): isfile_mock.return_value = True fake_tempdir_path = '/FAKE/TEMP_DIR' gettempdir_mock.return_value = fake_tempdir_path fake_zf = mock.Mock() ZipFileClassMock.return_value = fake_zf getsize_mock.return_value = function.MAX_ZIP_SIZE + 1 base_name = 'FAKE_FILE' extension = '.py' file_path = '/PATH/TO/%s%s' % (base_name, extension) zip_file_path = '%s/%s.zip' % (fake_tempdir_path, base_name) self.assertRaisesRegex( exceptions.QinlingClientException, r'^Package file size must be no more than %sM\.$' % ( function.MAX_ZIP_SIZE / 1024 / 1024), function._get_package_file, file_path=file_path) isfile_mock.assert_called_once_with(file_path) ZipFileClassMock.assert_called_once_with(zip_file_path, mode='w') fake_zf.write.assert_called_once_with( file_path, '%s%s' % (base_name, extension), compress_type=zipfile.ZIP_STORED) fake_zf.close.assert_called_once_with() getsize_mock.assert_called_once_with(zip_file_path) class TestFunction(fakes.TestQinlingClient): def setUp(self): super(TestFunction, self).setUp() # Get a shortcut self.client = self.app.client_manager.function_engine self.columns = base.FUNCTION_COLUMNS self.data = [] self._runtimes = fakes.FakeRuntime.create_runtimes(count=2) self._functions = fakes.FakeFunction.create_functions(count=3) for f in self._functions: self.data.append((f.id, f.name, f.description, f.count, f.code, f.runtime_id, f.entry, f.project_id, f.created_at, f.updated_at, f.cpu, f.memory_size, f.timeout)) class TestListFunction(TestFunction): def setUp(self): super(TestListFunction, self).setUp() self.cmd = function.List(self.app, None) self.columns = [c.capitalize() for c in base.FUNCTION_COLUMNS] self.client.functions.list = mock.Mock(return_value=self._functions) def test_function_list(self): arglist = [] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.client.functions.list.assert_called_once_with() self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) def test_function_list_with_filter(self): arglist = ['--filter', 'name=has:function', '--filter', 'cpu=lt:200'] verifylist = [ ('filters', ['name=has:function', 'cpu=lt:200']) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.client.functions.list.assert_called_once_with( name='has:function', cpu='lt:200' ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) def test_function_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 TestCreateFunction(TestFunction): def setUp(self): super(TestCreateFunction, self).setUp() self.cmd = function.Create(self.app, None) self.runtime_id = self._runtimes[0].id self.runtime_name = self._runtimes[0].name self.function_name = 'FAKE_FUNCTION_NAME' self.function_entry = 'fake_module.fake_method' self.file_path = '/PATH/TO/functon.py' self.package_path = '/FAKE_TEMP_DIR/ZIPPED_function.zip' self.container_name = 'FAKE_SWIFT_CONTAINER' self.object_name = 'FAKE_SWIFT_OBJECT' self.image = 'FAKE_IMAGE' self.cpu = 200 self.memory_size = 64 * 1024 * 1024 self.timeout = 10 # The arguments below are used in every create call despite of the # function type. self.base_call_arguments_default_values = { 'name': None, 'entry': None, 'cpu': None, 'memory_size': None, 'timeout': 5 } self.base_call_arguments_passed_values = { 'name': self.function_name, 'entry': self.function_entry, 'cpu': self.cpu, 'memory_size': self.memory_size, 'timeout': self.timeout } def _create_fake_function(self, attrs=None): # Allow to fake different create results f = fakes.FakeFunction.create_one_function(attrs) self.client.functions.create = mock.Mock(return_value=f) data = (f.id, f.name, f.description, f.count, f.code, f.runtime_id, f.entry, f.project_id, f.created_at, f.updated_at, f.cpu, f.memory_size, f.timeout) return data def _get_verify_list(self, runtime=None, name=None, entry=None, file_path=None, package_path=None, container_name=None, object_name=None, image=None, cpu=None, memory_size=None, timeout=5): return [ ('runtime', runtime), ('name', name), ('entry', entry), ('file', file_path), ('package', package_path), ('container', container_name), ('object', object_name), ('image', image), ('cpu', cpu), ('memory_size', memory_size), ('timeout', timeout) ] def _get_verify_list_with_passed_value(self, **kwargs): kwargs.update({'name': self.function_name, 'entry': self.function_entry, 'cpu': self.cpu, 'memory_size': self.memory_size, 'timeout': self.timeout}) return self._get_verify_list(**kwargs) def test_function_create_no_option(self): arglist = [] verifylist = self._get_verify_list() parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.assertRaisesRegex( exceptions.QinlingClientException, '^Cannot create function with the parameters given.+', self.cmd.take_action, parsed_args) def test_function_create_file_and_package_mutually_exclusive(self): # --file and --package are mutually exclusive arglist = ['--file', self.file_path, '--package', self.package_path] verifylist = self._get_verify_list(file_path=self.file_path, package_path=self.package_path) self.assertRaises(osc_tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) @mock.patch('qinlingclient.osc.v1.function._get_package_file') @mock.patch('qinlingclient.utils.md5') @mock.patch('qinlingclient.osc.v1.function.open') @mock.patch('os.remove') def test_function_create_package_required_options( self, remove_mock, open_mock, md5_mock, _get_package_file_mock): """Create a package type function. 1. file param is specified, 2. use runtime id, 3. all other params except the required ones are not set. """ md5sum = 'FAKE_MD5_SUM' code = {'source': 'package', 'md5sum': md5sum} attrs = {'runtime_id': self.runtime_id, 'code': code} created_data = self._create_fake_function(attrs) _get_package_file_mock.return_value = self.package_path md5_mock.return_value = md5sum package_file = mock.Mock() open_mock.return_value.__enter__.return_value = package_file arglist = ['--runtime', self.runtime_id, '--file', self.file_path] verifylist = self._get_verify_list(runtime=self.runtime_id, file_path=self.file_path) parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) call_arguments = {'runtime': self.runtime_id, 'code': code, 'package': package_file} call_arguments.update(self.base_call_arguments_default_values) self.client.functions.create.assert_called_once_with(**call_arguments) self.assertEqual(self.columns, columns) self.assertEqual(created_data, data) _get_package_file_mock.assert_called_once_with(None, self.file_path) md5_mock.assert_called_once_with(file=self.package_path) open_mock.assert_called_once_with(self.package_path, 'rb') remove_mock.assert_called_once_with(self.package_path) @mock.patch('qinlingclient.osc.v1.function._get_package_file') @mock.patch('qinlingclient.utils.md5') @mock.patch('qinlingclient.osc.v1.function.open') @mock.patch('os.remove') def test_function_create_package_all_options( self, remove_mock, open_mock, md5_mock, _get_package_file_mock): """Create a package type function. 1. package param is specified 2. use runtime name, 3. if acceptable, all options are specified with some value, so this also tests that package type is taking precedence of other types. """ md5sum = 'FAKE_MD5_SUM' code = {'source': 'package', 'md5sum': md5sum} attrs = { 'name': self.function_name, 'code': code, 'runtime_id': self.runtime_id, 'entry': self.function_entry, 'cpu': self.cpu, 'memory_size': self.memory_size, 'timeout': self.timeout } created_data = self._create_fake_function(attrs) _get_package_file_mock.return_value = self.package_path md5_mock.return_value = md5sum package_file = mock.Mock() open_mock.return_value.__enter__.return_value = package_file # Use to find the runtime id with its name self.client.runtimes.find.return_value = self._runtimes[0] arglist = [ '--runtime', self.runtime_name, '--name', self.function_name, '--entry', self.function_entry, '--package', self.package_path, '--container', self.container_name, '--object', self.object_name, '--image', self.image, '--cpu', str(self.cpu), '--memory-size', str(self.memory_size), '--timeout', str(self.timeout) ] verifylist = self._get_verify_list_with_passed_value( runtime=self.runtime_name, package_path=self.package_path, container_name=self.container_name, object_name=self.object_name, image=self.image, cpu=self.cpu, memory_size=self.memory_size, ) parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) call_arguments = {'runtime': self.runtime_id, 'code': code, 'package': package_file} call_arguments.update(self.base_call_arguments_passed_values) self.client.functions.create.assert_called_once_with(**call_arguments) self.assertEqual(self.columns, columns) self.assertEqual(created_data, data) self.client.runtimes.find.assert_called_once_with( name=self.runtime_name) _get_package_file_mock.assert_called_once_with(self.package_path, None) md5_mock.assert_called_once_with(file=self.package_path) open_mock.assert_called_once_with(self.package_path, 'rb') def test_function_create_package_runtime_needed(self): """Create a package type function without runtime specified.""" arglist = ['--file', self.file_path] verifylist = self._get_verify_list(file_path=self.file_path) parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.assertRaisesRegex( exceptions.QinlingClientException, r'^Runtime needs to be specified for package type function\.$', self.cmd.take_action, parsed_args) def test_function_create_swift_required_options(self): """Create a swift type function. 1. use runtime id, 2. all other params except the required ones are not set. """ code = { 'source': 'swift', 'swift': { 'container': self.container_name, 'object': self.object_name } } attrs = {'runtime_id': self.runtime_id, 'code': code} created_data = self._create_fake_function(attrs) arglist = ['--runtime', self.runtime_id, '--container', self.container_name, '--object', self.object_name] verifylist = self._get_verify_list(runtime=self.runtime_id, container_name=self.container_name, object_name=self.object_name) parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) call_arguments = {'runtime': self.runtime_id, 'code': code} call_arguments.update(self.base_call_arguments_default_values) self.client.functions.create.assert_called_once_with(**call_arguments) self.assertEqual(self.columns, columns) self.assertEqual(created_data, data) def test_function_create_swift_all_options(self): """Create a swift type function. 1. use runtime name, 2. if acceptable, all options are specified with some value, so this also tests that swift type is taking precedence of image type. """ code = { 'source': 'swift', 'swift': { 'container': self.container_name, 'object': self.object_name } } attrs = { 'name': self.function_name, 'code': code, 'runtime_id': self.runtime_id, 'entry': self.function_entry, 'cpu': self.cpu, 'memory_size': self.memory_size, 'timeout': self.timeout } created_data = self._create_fake_function(attrs) # Use to find the runtime id with its name self.client.runtimes.find.return_value = self._runtimes[0] arglist = [ '--runtime', self.runtime_name, '--name', self.function_name, '--entry', self.function_entry, '--container', self.container_name, '--object', self.object_name, '--image', self.image, '--cpu', str(self.cpu), '--memory-size', str(self.memory_size), '--timeout', str(self.timeout) ] verifylist = self._get_verify_list_with_passed_value( runtime=self.runtime_name, container_name=self.container_name, object_name=self.object_name, image=self.image ) parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) call_arguments = {'runtime': self.runtime_id, 'code': code} call_arguments.update(self.base_call_arguments_passed_values) self.client.functions.create.assert_called_once_with(**call_arguments) self.assertEqual(self.columns, columns) self.assertEqual(created_data, data) self.client.runtimes.find.assert_called_once_with( name=self.runtime_name) def test_function_create_swift_container_needed(self): """Create a swift type function with only object given.""" arglist = ['--object', self.object_name] verifylist = self._get_verify_list(object_name=self.object_name) parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.assertRaisesRegex( exceptions.QinlingClientException, r'^Container name and object name need to be specified\.$', self.cmd.take_action, parsed_args) def test_function_create_swift_object_needed(self): """Create a swift type function with only container given.""" arglist = ['--container', self.container_name] verifylist = self._get_verify_list(container_name=self.container_name) parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.assertRaisesRegex( exceptions.QinlingClientException, r'^Container name and object name need to be specified\.$', self.cmd.take_action, parsed_args) def test_function_create_swift_runtime_needed(self): """Create a swift type function without runtime given.""" arglist = ['--container', self.container_name, '--object', self.object_name] verifylist = self._get_verify_list(container_name=self.container_name, object_name=self.object_name) parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.assertRaisesRegex( exceptions.QinlingClientException, r'^Runtime needs to be specified for swift type function\.$', self.cmd.take_action, parsed_args) def test_function_create_image_required_options(self): """Create a image type function with only required options.""" code = { 'source': 'image', 'image': self.image } attrs = {'code': code} created_data = self._create_fake_function(attrs) arglist = ['--image', self.image] verifylist = self._get_verify_list(image=self.image) parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) call_arguments = {'code': code} call_arguments.update(self.base_call_arguments_default_values) self.client.functions.create.assert_called_once_with(**call_arguments) self.assertEqual(self.columns, columns) self.assertEqual(created_data, data) def test_function_create_image_all_options(self): code = { 'source': 'image', 'image': self.image } attrs = { 'name': self.function_name, 'code': code, 'entry': self.function_entry, 'cpu': self.cpu, 'memory_size': self.memory_size, 'timeout': self.timeout } created_data = self._create_fake_function(attrs) arglist = [ '--name', self.function_name, '--entry', self.function_entry, '--image', self.image, '--cpu', str(self.cpu), '--memory-size', str(self.memory_size), '--timeout', str(self.timeout) ] verifylist = self._get_verify_list_with_passed_value(image=self.image) parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) call_arguments = {'code': code} call_arguments.update(self.base_call_arguments_passed_values) self.client.functions.create.assert_called_once_with(**call_arguments) self.assertEqual(self.columns, columns) self.assertEqual(created_data, data) def test_function_create_timeout_invalid(self): arglist = ['--timeout', '-1'] verifylist = self._get_verify_list(timeout=-1) self.assertRaises(osc_tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) class TestDeleteFunction(TestFunction): def setUp(self): super(TestDeleteFunction, self).setUp() self.cmd = function.Delete(self.app, None) self.client.functions.delete = mock.Mock(return_value=None) def test_function_delete_no_option(self): arglist = [] verifylist = [] self.assertRaises(osc_tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_function_delete_one(self): function_id = self._functions[0].id arglist = [function_id] verifylist = [('function', [function_id])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.assertIsNone(result) self.client.functions.delete.assert_called_once_with(function_id) @mock.patch('qinlingclient.utils.find_resource_id_by_name') def test_function_delete_one_by_name(self, mock_find): name = self._functions[0].name id = self._functions[0].id mock_find.return_value = id arglist = [name] verifylist = [('function', [name])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.assertIsNone(result) self.client.functions.delete.assert_called_once_with(id) def test_function_delete_multiple(self): function_ids = [r.id for r in self._functions] arglist = function_ids verifylist = [('function', function_ids)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.assertIsNone(result) calls = [mock.call(f_id) for f_id in function_ids] self.assertEqual(len(function_ids), self.client.functions.delete.call_count) self.client.functions.delete.assert_has_calls(calls) @mock.patch('qinlingclient.utils.find_resource_id_by_name') def test_function_delete_multiple_with_names(self, mock_find): function_ids = [r.id for r in self._functions[:-1]] function_names = [self._functions[-1].name] mock_find.return_value = self._functions[-1].id arglist = function_ids + function_names verifylist = [('function', function_ids + function_names)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.assertIsNone(result) expected = [mock.call(f_id) for f_id in function_ids] expected.append(mock.call(self._functions[-1].id)) self.client.functions.delete.assert_has_calls(expected) def test_function_delete_multiple_exception(self): function_ids = [r.id for r in self._functions] arglist = function_ids verifylist = [('function', function_ids)] self.client.functions.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\(s\)\.$', self.cmd.take_action, parsed_args) # The second deletion failed, but the third is done normally calls = [mock.call(f_id) for f_id in function_ids] self.assertEqual(len(function_ids), self.client.functions.delete.call_count) self.client.functions.delete.assert_has_calls(calls) class TestShowFunction(TestFunction): def setUp(self): super(TestShowFunction, self).setUp() self.cmd = function.Show(self.app, None) self.client.functions.get = mock.Mock(return_value=self._functions[0]) def test_function_show_no_option(self): arglist = [] verifylist = [] self.assertRaises(osc_tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_function_show(self): function_id = self._functions[0].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.functions.get.assert_called_once_with(function_id) self.assertEqual(self.columns, columns) self.assertEqual(self.data[0], data) @mock.patch("qinlingclient.utils.find_resource_id_by_name") def test_function_show_by_name(self, mock_find): name = self._functions[0].name id = self._functions[0].id mock_find.return_value = id arglist = [name] verifylist = [('function', name)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) mock_find.assert_called_once_with(self.client.functions, name) self.client.functions.get.assert_called_once_with(id) self.assertEqual(self.columns, columns) self.assertEqual(self.data[0], data) class TestUpdateFunction(TestFunction): def setUp(self): super(TestUpdateFunction, self).setUp() self.cmd = function.Update(self.app, None) self.function_id = self._functions[0].id self.function_name = 'FAKE_FUNCTION_NAME' self.function_description = 'This is a updated function.' self.function_entry = 'fake_module.fake_method' self.file_path = '/PATH/TO/functon.py' self.package_path = '/FAKE_TEMP_DIR/ZIPPED_function.zip' self.container_name = 'FAKE_SWIFT_CONTAINER' self.object_name = 'FAKE_SWIFT_OBJECT' self.image = 'FAKE_IMAGE' self.cpu = 200 self.memory_size = 64 * 1024 * 1024 self.timeout = 10 # The arguments below are used in every update despite of the # function type. self.base_call_arguments_default_values = { 'name': None, 'description': None, 'entry': None, 'cpu': None, 'memory_size': None, 'timeout': None, } self.base_call_arguments_passed_values = { 'name': self.function_name, 'description': self.function_description, 'entry': self.function_entry, 'cpu': self.cpu, 'memory_size': self.memory_size, 'timeout': self.timeout } def _update_fake_function(self, attrs=None): # Allow to fake different update results f = fakes.FakeFunction.create_one_function(attrs) self.client.functions.update = mock.Mock(return_value=f) data = (f.id, f.name, f.description, f.count, f.code, f.runtime_id, f.entry, f.project_id, f.created_at, f.updated_at, f.cpu, f.memory_size, f.timeout) return data def _get_verify_list(self, function_id=None, name=None, description=None, entry=None, file_path=None, package_path=None, container_name=None, object_name=None, cpu=None, memory_size=None, timeout=None): return [ ('function', function_id), ('name', name), ('description', description), ('entry', entry), ('file', file_path), ('package', package_path), ('container', container_name), ('object', object_name), ('cpu', cpu), ('memory_size', memory_size), ('timeout', timeout) ] def _get_verify_list_with_passed_value(self, **kwargs): kwargs.update({'name': self.function_name, 'description': self.function_description, 'entry': self.function_entry, 'cpu': self.cpu, 'memory_size': self.memory_size, 'timeout': self.timeout}) return self._get_verify_list(**kwargs) def test_function_update_no_option(self): arglist = [] verifylist = self._get_verify_list() self.assertRaises(osc_tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_function_update_required_options(self): """Update a function. Do nothing as only the function_id is specified. """ attrs = {'id': self.function_id} updated_data = self._update_fake_function(attrs) arglist = [self.function_id] verifylist = self._get_verify_list(function_id=self.function_id) parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) call_arguments = {'code': None} call_arguments.update(self.base_call_arguments_default_values) self.client.functions.update.assert_called_once_with( self.function_id, **call_arguments) self.assertEqual(self.columns, columns) self.assertEqual(updated_data, data) @mock.patch('qinlingclient.utils.find_resource_id_by_name') def test_function_update_by_name(self, mock_find): attrs = {'name': self.function_name, 'timeout': self.timeout} updated_data = self._update_fake_function(attrs) mock_find.return_value = self.function_id arglist = [self.function_name, '--timeout', str(self.timeout)] verifylist = self._get_verify_list(function_id=self.function_name, timeout=self.timeout) parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) mock_find.assert_called_once_with(self.client.functions, self.function_name) call_arguments = {'code': None} call_arguments.update(self.base_call_arguments_default_values) call_arguments.update({'timeout': self.timeout}) self.client.functions.update.assert_called_once_with( self.function_id, **call_arguments) self.assertEqual(self.columns, columns) self.assertEqual(updated_data, data) def test_function_update_code_not_updated(self): # All other options except code are updated. attrs = {'id': self.function_id, 'name': self.function_name, 'description': self.function_description, 'entry': self.function_entry, 'cpu': self.cpu, 'memory_size': self.memory_size, 'timeout': self.timeout} updated_data = self._update_fake_function(attrs) arglist = [ self.function_id, '--name', self.function_name, '--description', self.function_description, '--entry', self.function_entry, '--cpu', str(self.cpu), '--memory-size', str(self.memory_size), '--timeout', str(self.timeout) ] verifylist = self._get_verify_list_with_passed_value( function_id=self.function_id) parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) call_arguments = {'code': None} call_arguments.update(self.base_call_arguments_passed_values) self.client.functions.update.assert_called_once_with( self.function_id, **call_arguments) self.assertEqual(self.columns, columns) self.assertEqual(updated_data, data) def test_function_update_file_and_package_mutually_exclusive(self): # --file and --package are mutually exclusive arglist = [self.function_id, '--file', self.file_path, '--package', self.package_path] verifylist = self._get_verify_list(function_id=self.function_id, file_path=self.file_path, package_path=self.package_path) self.assertRaises(osc_tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) @mock.patch('qinlingclient.osc.v1.function._get_package_file') @mock.patch('qinlingclient.osc.v1.function.open') def test_function_update_package_required_options( self, open_mock, _get_package_file_mock): """Update a package type function. 1. use --file to update the code, 2. only required options are specified. """ code = {'source': 'package'} attrs = {'id': self.function_id, 'code': code} updated_data = self._update_fake_function(attrs) _get_package_file_mock.return_value = self.package_path package_file = mock.Mock() open_mock.return_value.__enter__.return_value = package_file arglist = [self.function_id, '--file', self.file_path] verifylist = self._get_verify_list(function_id=self.function_id, file_path=self.file_path) parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) call_arguments = {'code': code, 'package': package_file} call_arguments.update(self.base_call_arguments_default_values) self.client.functions.update.assert_called_once_with( self.function_id, **call_arguments) self.assertEqual(self.columns, columns) self.assertEqual(updated_data, data) _get_package_file_mock.assert_called_once_with(None, self.file_path) open_mock.assert_called_once_with(self.package_path, 'rb') @mock.patch('qinlingclient.osc.v1.function._get_package_file') @mock.patch('qinlingclient.osc.v1.function.open') def test_function_update_package_all_options( self, open_mock, _get_package_file_mock): """Update a package type function. 1. use --package to update the code, 2. all the options are specified if acceptable, this also tests that package type is taking precedence of the swift type. """ code = {'source': 'package'} attrs = {'id': self.function_id, 'code': code} updated_data = self._update_fake_function(attrs) _get_package_file_mock.return_value = self.package_path package_file = mock.Mock() open_mock.return_value.__enter__.return_value = package_file arglist = [self.function_id, '--package', self.package_path, '--name', self.function_name, '--description', self.function_description, '--entry', self.function_entry, '--container', self.container_name, '--object', self.object_name, '--cpu', str(self.cpu), '--memory-size', str(self.memory_size), '--timeout', str(self.timeout)] verifylist = self._get_verify_list_with_passed_value( function_id=self.function_id, package_path=self.package_path, container_name=self.container_name, object_name=self.object_name) parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) call_arguments = {'code': code, 'package': package_file} call_arguments.update(self.base_call_arguments_passed_values) self.client.functions.update.assert_called_once_with( self.function_id, **call_arguments) self.assertEqual(self.columns, columns) self.assertEqual(updated_data, data) _get_package_file_mock.assert_called_once_with(self.package_path, None) open_mock.assert_called_once_with(self.package_path, 'rb') def test_function_update_swift_required_options(self): """Update a swift type function. 1. only use --container to update the code. """ code = { 'source': 'swift', 'swift': { 'container': self.container_name, 'object': "origin_obj" } } attrs = {'id': self.function_id, 'code': code} updated_data = self._update_fake_function(attrs) arglist = [self.function_id, '--container', self.container_name] verifylist = self._get_verify_list(function_id=self.function_id, container_name=self.container_name) parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) code = { 'source': 'swift', 'swift': { 'container': self.container_name, } } call_arguments = {'code': code} call_arguments.update(self.base_call_arguments_default_values) self.client.functions.update.assert_called_once_with( self.function_id, **call_arguments) self.assertEqual(self.columns, columns) self.assertEqual(updated_data, data) def test_function_update_swift_all_options(self): """Update a swift type function. 1. only both --container and --object to update the code, 2. all other options are specified if appropriate. """ code = { 'source': 'swift', 'swift': { 'container': self.container_name, 'object': self.object_name } } attrs = {'id': self.function_id, 'code': code} updated_data = self._update_fake_function(attrs) arglist = [self.function_id, '--container', self.container_name, '--object', self.object_name, '--name', self.function_name, '--description', self.function_description, '--entry', self.function_entry, '--cpu', str(self.cpu), '--memory-size', str(self.memory_size), '--timeout', str(self.timeout)] verifylist = self._get_verify_list_with_passed_value( function_id=self.function_id, container_name=self.container_name, object_name=self.object_name) parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) call_arguments = {'code': code} call_arguments.update(self.base_call_arguments_passed_values) self.client.functions.update.assert_called_once_with( self.function_id, **call_arguments) self.assertEqual(self.columns, columns) self.assertEqual(updated_data, data) def test_function_update_timeout_invalid(self): arglist = [self.function_id, '--timeout', '-1'] verifylist = self._get_verify_list(function_id=self.function_id, timeout=-1) self.assertRaises(osc_tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) class TestDetachFunction(TestFunction): def setUp(self): super(TestDetachFunction, self).setUp() self.cmd = function.Detach(self.app, None) self.client.functions.detach = mock.Mock(return_value=None) def test_function_detach_no_option(self): arglist = [] verifylist = [] self.assertRaises(osc_tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_function_detach(self): function_id = self._functions[0].id arglist = [function_id] verifylist = [('function', function_id)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.assertIsNone(result) self.client.functions.detach.assert_called_once_with(function_id) def test_function_detach_exception(self): function_id = self._functions[0].id self.client.functions.detach = mock.Mock(side_effect=RuntimeError) arglist = [function_id] verifylist = [('function', function_id)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.assertRaisesRegex( exceptions.QinlingClientException, r'^Unable to detach the specified function\.$', self.cmd.take_action, parsed_args) self.client.functions.detach.assert_called_once_with(function_id) class TestDownloadFunction(TestFunction): def setUp(self): super(TestDownloadFunction, self).setUp() self.cmd = function.Download(self.app, None) self.raw_data = 'RAW_DATA' self.cwd = '/FAKE/CWD' res = mock.Mock(raw=self.raw_data) self.client.functions.get = mock.Mock(return_value=res) def test_function_download_no_option(self): arglist = [] verifylist = [] self.assertRaises(osc_tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) @mock.patch('os.getcwd') @mock.patch('qinlingclient.osc.v1.function.open') @mock.patch('shutil.copyfileobj') def test_function_download_required_options(self, copyfile_mock, open_mock, getcwd_mock): function_id = self._functions[0].id getcwd_mock.return_value = self.cwd target = mock.Mock() open_mock.return_value.__enter__.return_value = target arglist = [function_id] verifylist = [('function', function_id)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.assertIsNone(result) self.client.functions.get.assert_called_once_with(function_id, download=True) open_mock.assert_called_once_with( '%s/%s.zip' % (self.cwd, function_id), 'wb') copyfile_mock.assert_called_once_with(self.raw_data, target) @mock.patch('os.getcwd') @mock.patch('qinlingclient.osc.v1.function.open') @mock.patch('shutil.copyfileobj') @mock.patch('qinlingclient.utils.find_resource_id_by_name') def test_function_download_by_name(self, find_mock, copyfile_mock, open_mock, getcwd_mock): name = self._functions[0].name id = self._functions[0].id find_mock.return_value = id getcwd_mock.return_value = self.cwd target = mock.Mock() open_mock.return_value.__enter__.return_value = target arglist = [name] verifylist = [('function', name)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.assertIsNone(result) find_mock.assert_called_once_with(self.client.functions, name) self.client.functions.get.assert_called_once_with(id, download=True) open_mock.assert_called_once_with( '%s/%s.zip' % (self.cwd, id), 'wb') copyfile_mock.assert_called_once_with(self.raw_data, target) @mock.patch('os.getcwd') @mock.patch('qinlingclient.osc.v1.function.open') @mock.patch('shutil.copyfileobj') def test_function_download_all_options(self, copyfile_mock, open_mock, getcwd_mock): function_id = self._functions[0].id output_name = 'output.zip' getcwd_mock.return_value = self.cwd target = mock.Mock() open_mock.return_value.__enter__.return_value = target arglist = [function_id, '--output', output_name] verifylist = [('function', function_id), ('output', output_name)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.assertIsNone(result) self.client.functions.get.assert_called_once_with(function_id, download=True) open_mock.assert_called_once_with( '%s/%s' % (self.cwd, output_name), 'wb') copyfile_mock.assert_called_once_with(self.raw_data, target) @mock.patch('os.getcwd') @mock.patch('qinlingclient.osc.v1.function.open') @mock.patch('shutil.copyfileobj') def test_function_download_abs_path(self, copyfile_mock, open_mock, getcwd_mock): function_id = self._functions[0].id output_name = '/ABS_PATH/output.zip' getcwd_mock.return_value = self.cwd target = mock.Mock() open_mock.return_value.__enter__.return_value = target arglist = [function_id, '-o', output_name] verifylist = [('function', function_id), ('output', output_name)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.assertIsNone(result) self.client.functions.get.assert_called_once_with(function_id, download=True) open_mock.assert_called_once_with(output_name, 'wb') copyfile_mock.assert_called_once_with(self.raw_data, target) class TestScaleupFunction(TestFunction): def setUp(self): super(TestScaleupFunction, self).setUp() self.cmd = function.Scaleup(self.app, None) self.client.functions.scaleup = mock.Mock(return_value=None) def test_function_scaleup_no_option(self): arglist = [] verifylist = [] self.assertRaises(osc_tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_function_scaleup_required_options(self): function_id = self._functions[0].id arglist = [function_id] verifylist = [('function', function_id), ('count', 1)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.assertIsNone(result) self.client.functions.scaleup.assert_called_once_with(function_id, 1) def test_function_scaleup_all_options(self): function_id = self._functions[0].id count = 3 arglist = [function_id, '--count', str(count)] verifylist = [('function', function_id), ('count', count)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.assertIsNone(result) self.client.functions.scaleup.assert_called_once_with(function_id, count) def test_function_scaleup_worker_count_not_int(self): function_id = self._functions[0].id arglist = [function_id, '--count', 'NOT_INTEGER'] verifylist = [('function_id', function_id), ('count', 'NOT_INTEGER')] self.assertRaisesRegex( exceptions.QinlingClientException, r'^Worker count must be a positive integer\.$', self.check_parser, self.cmd, arglist, verifylist) def test_function_scaleup_worker_count_zero(self): function_id = self._functions[0].id arglist = [function_id, '--count', '0'] verifylist = [('function_id', function_id), ('count', 0)] self.assertRaisesRegex( exceptions.QinlingClientException, r'^Worker count must be a positive integer\.$', self.check_parser, self.cmd, arglist, verifylist) def test_function_scaleup_exception(self): function_id = self._functions[0].id self.client.functions.scaleup = mock.Mock(side_effect=RuntimeError) arglist = [function_id] verifylist = [('function', function_id), ('count', 1)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.assertRaisesRegex( exceptions.QinlingClientException, r'^Unable to scale up the specified function\.$', self.cmd.take_action, parsed_args) self.client.functions.scaleup.assert_called_once_with(function_id, 1) class TestScaledownFunction(TestFunction): def setUp(self): super(TestScaledownFunction, self).setUp() self.cmd = function.Scaledown(self.app, None) self.client.functions.scaledown = mock.Mock(return_value=None) def test_function_scaledown_no_option(self): arglist = [] verifylist = [] self.assertRaises(osc_tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_function_scaledown_required_options(self): function_id = self._functions[0].id arglist = [function_id] verifylist = [('function', function_id), ('count', 1)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.assertIsNone(result) self.client.functions.scaledown.assert_called_once_with(function_id, 1) def test_function_scaledown_all_options(self): function_id = self._functions[0].id count = 3 arglist = [function_id, '--count', str(count)] verifylist = [('function', function_id), ('count', 3)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.assertIsNone(result) self.client.functions.scaledown.assert_called_once_with(function_id, count) def test_function_scaledown_exception(self): function_id = self._functions[0].id self.client.functions.scaledown = mock.Mock(side_effect=RuntimeError) arglist = [function_id] verifylist = [('function', function_id), ('count', 1)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.assertRaisesRegex( exceptions.QinlingClientException, r'^Unable to scale down the specified function\.$', self.cmd.take_action, parsed_args) self.client.functions.scaledown.assert_called_once_with(function_id, 1) python-qinlingclient-5.0.1/qinlingclient/tests/unit/osc/v1/test_function_alias.py0000664000175000017500000003464113643577416030450 0ustar zuulzuul00000000000000# 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 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) python-qinlingclient-5.0.1/qinlingclient/tests/unit/osc/v1/test_function_version.py0000664000175000017500000002364713643577416031050 0ustar zuulzuul00000000000000# 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 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 ) python-qinlingclient-5.0.1/qinlingclient/tests/unit/osc/v1/test_webhook.py0000664000175000017500000003502513643577416027105 0ustar zuulzuul00000000000000# 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 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) python-qinlingclient-5.0.1/qinlingclient/tests/unit/osc/v1/test_function_execution.py0000664000175000017500000003771113643577416031363 0ustar zuulzuul00000000000000# 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 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") python-qinlingclient-5.0.1/qinlingclient/tests/unit/osc/v1/test_job.py0000664000175000017500000004121513643577416026217 0ustar zuulzuul00000000000000# 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 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) python-qinlingclient-5.0.1/qinlingclient/tests/unit/osc/v1/test_function_worker.py0000664000175000017500000000457013643577416030666 0ustar zuulzuul00000000000000# 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 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)) python-qinlingclient-5.0.1/qinlingclient/tests/unit/osc/v1/__init__.py0000664000175000017500000000000013643577416026130 0ustar zuulzuul00000000000000python-qinlingclient-5.0.1/qinlingclient/tests/unit/osc/__init__.py0000664000175000017500000000000013643577416025602 0ustar zuulzuul00000000000000python-qinlingclient-5.0.1/qinlingclient/tests/unit/base.py0000664000175000017500000000132613643577416024205 0ustar zuulzuul00000000000000# 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.""" python-qinlingclient-5.0.1/qinlingclient/tests/unit/v1/0000775000175000017500000000000013643577506023245 5ustar zuulzuul00000000000000python-qinlingclient-5.0.1/qinlingclient/tests/unit/v1/test_runtime.py0000664000175000017500000001447213643577416026351 0ustar zuulzuul00000000000000# 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} 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 request_data = {'image': image_name, 'trusted': trusted, 'name': runtime_name, '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 ) 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 ) python-qinlingclient-5.0.1/qinlingclient/tests/unit/v1/test_function.py0000664000175000017500000003226013643577416026506 0ustar zuulzuul00000000000000# 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 six from six.moves.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 = six.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 = six.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) python-qinlingclient-5.0.1/qinlingclient/tests/unit/v1/test_function_alias.py0000664000175000017500000001666613643577416027673 0ustar zuulzuul00000000000000# 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) python-qinlingclient-5.0.1/qinlingclient/tests/unit/v1/test_function_version.py0000664000175000017500000001605513643577416030257 0ustar zuulzuul00000000000000# 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 ) python-qinlingclient-5.0.1/qinlingclient/tests/unit/v1/test_webhook.py0000664000175000017500000001743213643577416026323 0ustar zuulzuul00000000000000# 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) python-qinlingclient-5.0.1/qinlingclient/tests/unit/v1/test_function_execution.py0000664000175000017500000001675613643577416030605 0ustar zuulzuul00000000000000# 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 ) python-qinlingclient-5.0.1/qinlingclient/tests/unit/v1/test_job.py0000664000175000017500000001671513643577416025442 0ustar zuulzuul00000000000000# 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) python-qinlingclient-5.0.1/qinlingclient/tests/unit/v1/test_function_worker.py0000664000175000017500000000301613643577416030074 0ustar zuulzuul00000000000000# 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]) python-qinlingclient-5.0.1/qinlingclient/tests/unit/v1/test_client.py0000664000175000017500000000206513643577416026137 0ustar zuulzuul00000000000000# 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()) python-qinlingclient-5.0.1/qinlingclient/tests/unit/v1/__init__.py0000664000175000017500000000000013643577416025344 0ustar zuulzuul00000000000000python-qinlingclient-5.0.1/qinlingclient/tests/unit/__init__.py0000664000175000017500000000000013643577416025016 0ustar zuulzuul00000000000000python-qinlingclient-5.0.1/qinlingclient/tests/__init__.py0000664000175000017500000000000013643577416024037 0ustar zuulzuul00000000000000python-qinlingclient-5.0.1/test-requirements.txt0000664000175000017500000000124413643577416022200 0ustar zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. hacking>=3.0,<3.1.0 # Apache-2.0 coverage!=4.4,>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD mock>=2.0.0 # BSD requests-mock>=1.5.2 # Apache-2.0 stestr>=2.0.0 # Apache-2.0 testscenarios>=0.4 # Apache-2.0/BSD testtools>=2.2.0 # MIT os-testr>=1.0.0 # Apache-2.0 oslotest>=3.2.0 # Apache-2.0 keystoneauth1>=3.4.0 # Apache-2.0 # doc build requirements openstackdocstheme>=1.18.1 # Apache-2.0 sphinx!=1.6.6,!=1.6.7,!=2.1.0,>=1.6.5 # BSD reno>=2.5.0 # Apache-2.0 python-qinlingclient-5.0.1/tox.ini0000664000175000017500000000366013643577416017256 0ustar zuulzuul00000000000000[tox] envlist = py37,pep8 minversion = 3.1.1 skipsdist = True ignore_basepython_conflict = True [testenv] basepython = python3 usedevelop = True whitelist_externals = bash find install_command = pip install {opts} {packages} setenv = VIRTUAL_ENV={envdir} passenv = http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY deps = -c{env:UPPER_CONSTRAINTS_FILE:https://opendev.org/openstack/requirements/raw/branch/master/upper-constraints.txt} -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = stestr run {posargs} [testenv:pep8] [testenv:venv] commands = {posargs} [testenv:functional] setenv = OS_TEST_PATH = ./qinlingclient/tests/functional [testenv:cover] setenv = PYTHON=coverage run --source qinlingclient --parallel-mode commands = stestr run {posargs} coverage combine coverage html -d cover coverage xml -o cover/coverage.xml coverage report [testenv:docs] whitelist_externals = rm deps = -r{toxinidir}/doc/requirements.txt commands = rm -rf doc/build sphinx-build -E -W -b html doc/source doc/build/html [testenv:debug] commands = find . -type f -name "*.pyc" -delete oslo_debug_helper -t qinlingclient/tests {posargs} [testenv:pyflakes] deps = -c{env:UPPER_CONSTRAINTS_FILE:https://opendev.org/openstack/requirements/raw/branch/master/upper-constraints.txt} flake8 commands = flake8 [testenv:releasenotes] deps = -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} -r{toxinidir}/doc/requirements.txt commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [flake8] show-source = true builtins = _ exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,tools,build,releasenotes [testenv:lower-constraints] deps = -c{toxinidir}/lower-constraints.txt -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt python-qinlingclient-5.0.1/.coveragerc0000664000175000017500000000014213643577416020054 0ustar zuulzuul00000000000000[run] source = qinlingclient omit = .tox/* qinlingclient/tests/* [report] ignore_errors = True python-qinlingclient-5.0.1/ChangeLog0000664000175000017500000001171013643577506017510 0ustar zuulzuul00000000000000CHANGES ======= 5.0.1 ----- * Cleanup py27 support * Update hacking for Python3 5.0.0 ----- * [ussuri][goal] Drop python 2.7 support and testing * Stop testing python2.7 * Switch to Ussuri jobs * Update master for stable/train 4.0.0 ----- * Improve function\_alias integration * Sync Sphinx requirement * Fix broken function version during webhook update 3.0.0 ----- * Release note for --function-alias support * Support function alias when creating job * Add Python 3 Train unit tests 2.2.0 ----- * Update the Launchpad to Storyboard * Add doc/requirements.txt to releasenotes tox environment * Update json module to jsonutils * Fix unit test failure * Fix the filter description for resource list * Replace git.openstack.org URLs with opendev.org URLs * OpenDev Migration Patch * Dropping the py35 testing * Update master for stable/stein * add python 3.7 unit test job * Change openstack-dev to openstack-discuss * Add Python 3.6 classifier to setup.cfg * add python 3.6 unit test job 2.1.0 ----- * Add release note for timeout support * Update min tox version to 2.0 * Don't quote {posargs} in tox.ini * Update the outdated URL in docstring * Update bug report url to storyboard * Support function name in most function operations * Function timeout support * Use templates for cover and lower-constraints * Allow to specify either container or object for swift function update * switch documentation job to new PTI * add python 3.6 unit test job * Fix doc generation * import zuul job settings from project-config * Fix minor typos * Delete qinlingclient.tests.unit.test\_client * Add unit tests for qinlingclient.v1.webhook * Add unit tests for qinlingclient.v1.job * Add unit tests for qinlingclient.v1.function\_worker * Add unit tests for qinlingclient.v1.function\_version * Add unit tests for qinlingclient.v1.function\_execution * Add unit tests for qinlingclient.v1.function\_alias * Add unit tests for qinlingclient.v1.function * Add unit tests for qinlingclient.v1.runtime * Add unit tests for qinlingclient.osc.v1.function\_alias * Add unit tests for qinlingclient.osc.v1.webhook * Add unit tests for qinlingclient.osc.v1.job * Add unit tests for qinlingclient.osc.v1.function\_worker * Add unit tests for qinlingclient.osc.v1.function\_version * Add unit tests for qinlingclient.osc.v1.function\_execution * Add unit tests for qinlingclient.osc.v1.function * Add unit tests for qinlingclient.osc.v1.runtime * Update reno for stable/rocky * Support function alias * handle required parameters not provided case in function creation 2.0.0 ----- * Support '--untrusted' param for runtime creation * Switch to stestr * Add CLI to get runtime pool * Remove --code-type for function creation CLI 1.1.0 ----- * Add release note for function version support in job CLI * Fix webhook operation * Support function version for job creation * Add release note for resource name support in CLI * Support function\_version for webhook operations * Add release notes link to README * Allow specifying name of related resource when creation * Release note for resource limitation CLI * fix tox python3 overrides * Support create function with customized cpu and memory\_size * Add commands to scale up/down function workers * Add docs job to tox * Support create exeuction with version number * Improve function creation CLI 1.0.0 ----- * Release note for function versioning CLI * Support function version * Fix typo: extention -> extension * Improve the support for Qinling Current/Future Versions * Updated from global requirements * add lower-constraints job * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * python-qinling doc improvement * Updated from global requirements * Fix the home-page url * fix the url in HACKING.rst * Fix tox cover config * setup.cfg: remove qinling console script 0.4.0 ----- * Specify md5 for package automatically * Limit package size to 50M * Support webhook in CLI * Support download function code package * Make sync as default execution model * Delete all executions for function * Improve help message * Support function detach and function worker * Get executions by admin user * Support function query filtering * Avoid tox\_install.sh for constraints support * Support function update command * Refine function create command * Remove setting of version/release from releasenotes * Support update function * Support optional runtime params * Function name is optional * Add filter support for resource query 0.3.0 ----- * Add support to get execution's log * Add 'description' output field for execution query CLI * Fix exception message 0.2.0 ----- * Support job update CLI * Make sync/async work for creating function execution * Fix the http exception handling 0.1.0 ----- * Implement get command * Support job CLI * Do not delete zip file after function creation * Add CLI support for runtime/function/execution * Function/Runtime list * Initial commit for python-qinlingclient * Added .gitreview python-qinlingclient-5.0.1/python_qinlingclient.egg-info/0000775000175000017500000000000013643577506023671 5ustar zuulzuul00000000000000python-qinlingclient-5.0.1/python_qinlingclient.egg-info/entry_points.txt0000664000175000017500000000461213643577506027172 0ustar zuulzuul00000000000000[openstack.cli.extension] function_engine = qinlingclient.osc.plugin [openstack.function_engine.v1] function_alias_create = qinlingclient.osc.v1.function_alias:Create function_alias_delete = qinlingclient.osc.v1.function_alias:Delete function_alias_list = qinlingclient.osc.v1.function_alias:List function_alias_show = qinlingclient.osc.v1.function_alias:Show function_alias_update = qinlingclient.osc.v1.function_alias:Update function_create = qinlingclient.osc.v1.function:Create function_delete = qinlingclient.osc.v1.function:Delete function_detach = qinlingclient.osc.v1.function:Detach function_download = qinlingclient.osc.v1.function:Download function_execution_create = qinlingclient.osc.v1.function_execution:Create function_execution_delete = qinlingclient.osc.v1.function_execution:Delete function_execution_list = qinlingclient.osc.v1.function_execution:List function_execution_log_show = qinlingclient.osc.v1.function_execution:LogShow function_execution_show = qinlingclient.osc.v1.function_execution:Show function_list = qinlingclient.osc.v1.function:List function_scaledown = qinlingclient.osc.v1.function:Scaledown function_scaleup = qinlingclient.osc.v1.function:Scaleup function_show = qinlingclient.osc.v1.function:Show function_update = qinlingclient.osc.v1.function:Update function_version_create = qinlingclient.osc.v1.function_version:Create function_version_delete = qinlingclient.osc.v1.function_version:Delete function_version_detach = qinlingclient.osc.v1.function_version:Detach function_version_list = qinlingclient.osc.v1.function_version:List function_version_show = qinlingclient.osc.v1.function_version:Show function_worker_list = qinlingclient.osc.v1.function_worker:List job_create = qinlingclient.osc.v1.job:Create job_delete = qinlingclient.osc.v1.job:Delete job_list = qinlingclient.osc.v1.job:List job_show = qinlingclient.osc.v1.job:Show job_update = qinlingclient.osc.v1.job:Update runtime_create = qinlingclient.osc.v1.runtime:Create runtime_delete = qinlingclient.osc.v1.runtime:Delete runtime_list = qinlingclient.osc.v1.runtime:List runtime_pool_show = qinlingclient.osc.v1.runtime:Pool runtime_show = qinlingclient.osc.v1.runtime:Show webhook_create = qinlingclient.osc.v1.webhook:Create webhook_delete = qinlingclient.osc.v1.webhook:Delete webhook_list = qinlingclient.osc.v1.webhook:List webhook_show = qinlingclient.osc.v1.webhook:Show webhook_update = qinlingclient.osc.v1.webhook:Update python-qinlingclient-5.0.1/python_qinlingclient.egg-info/pbr.json0000664000175000017500000000005613643577506025350 0ustar zuulzuul00000000000000{"git_version": "39a95b6", "is_release": true}python-qinlingclient-5.0.1/python_qinlingclient.egg-info/SOURCES.txt0000664000175000017500000001037113643577506025557 0ustar zuulzuul00000000000000.coveragerc .stestr.conf .zuul.yaml AUTHORS CONTRIBUTING.rst ChangeLog HACKING.rst LICENSE README.rst babel.cfg lower-constraints.txt requirements.txt run_tests.sh setup.cfg setup.py setup.sh test-requirements.txt tox.ini doc/requirements.txt doc/source/conf.py doc/source/index.rst doc/source/readme.rst doc/source/cli/index.rst doc/source/cli/osc/v1/qinling.rst doc/source/contributor/index.rst doc/source/install/index.rst python_qinlingclient.egg-info/PKG-INFO python_qinlingclient.egg-info/SOURCES.txt python_qinlingclient.egg-info/dependency_links.txt python_qinlingclient.egg-info/entry_points.txt python_qinlingclient.egg-info/not-zip-safe python_qinlingclient.egg-info/pbr.json python_qinlingclient.egg-info/requires.txt python_qinlingclient.egg-info/top_level.txt qinlingclient/__init__.py qinlingclient/client.py qinlingclient/i18n.py qinlingclient/utils.py qinlingclient/version.py qinlingclient/common/__init__.py qinlingclient/common/base.py qinlingclient/common/exceptions.py qinlingclient/common/http.py qinlingclient/osc/__init__.py qinlingclient/osc/plugin.py qinlingclient/osc/v1/__init__.py qinlingclient/osc/v1/base.py qinlingclient/osc/v1/function.py qinlingclient/osc/v1/function_alias.py qinlingclient/osc/v1/function_execution.py qinlingclient/osc/v1/function_version.py qinlingclient/osc/v1/function_worker.py qinlingclient/osc/v1/job.py qinlingclient/osc/v1/runtime.py qinlingclient/osc/v1/webhook.py qinlingclient/tests/__init__.py qinlingclient/tests/unit/__init__.py qinlingclient/tests/unit/base.py qinlingclient/tests/unit/fakes.py qinlingclient/tests/unit/osc/__init__.py qinlingclient/tests/unit/osc/v1/__init__.py qinlingclient/tests/unit/osc/v1/fakes.py qinlingclient/tests/unit/osc/v1/test_function.py qinlingclient/tests/unit/osc/v1/test_function_alias.py qinlingclient/tests/unit/osc/v1/test_function_execution.py qinlingclient/tests/unit/osc/v1/test_function_version.py qinlingclient/tests/unit/osc/v1/test_function_worker.py qinlingclient/tests/unit/osc/v1/test_job.py qinlingclient/tests/unit/osc/v1/test_runtime.py qinlingclient/tests/unit/osc/v1/test_webhook.py qinlingclient/tests/unit/v1/__init__.py qinlingclient/tests/unit/v1/test_client.py qinlingclient/tests/unit/v1/test_function.py qinlingclient/tests/unit/v1/test_function_alias.py qinlingclient/tests/unit/v1/test_function_execution.py qinlingclient/tests/unit/v1/test_function_version.py qinlingclient/tests/unit/v1/test_function_worker.py qinlingclient/tests/unit/v1/test_job.py qinlingclient/tests/unit/v1/test_runtime.py qinlingclient/tests/unit/v1/test_webhook.py qinlingclient/v1/__init__.py qinlingclient/v1/client.py qinlingclient/v1/function.py qinlingclient/v1/function_alias.py qinlingclient/v1/function_execution.py qinlingclient/v1/function_version.py qinlingclient/v1/function_worker.py qinlingclient/v1/job.py qinlingclient/v1/runtime.py qinlingclient/v1/versions.py qinlingclient/v1/webhook.py releasenotes/notes/.placeholder releasenotes/notes/add-support-function-alias-for-execution-and-webhook-8df54e2fe4a27779.yaml releasenotes/notes/deprecate-code-type-05211c5cf8cf5c51.yaml releasenotes/notes/drop-py-2-7-1fefd2071a107cc7.yaml releasenotes/notes/fix-webhook-update-version-4c79ac51af5656dd.yaml releasenotes/notes/function-aliasing-299f134f370d0681.yaml releasenotes/notes/function-timeout-f4c9d6d8ea2c7204.yaml releasenotes/notes/function-versioning-81881bc35bc3eb64.yaml releasenotes/notes/get-runtime-pool-d263e74d21b27091.yaml releasenotes/notes/support-function-alias-for-job-eb10994cac2c11e9.yaml releasenotes/notes/support-function-resource-limitation-f6f519999f5e23cd.yaml releasenotes/notes/support-function-version-for-job-7cb12ebb9fd64456.yaml releasenotes/notes/support-function-version-for-webhook-67beca9f1c78eb58.yaml releasenotes/notes/support-name-function-operation-1d32c4c2fc58bf26.yaml releasenotes/notes/support-resource-name-cd26d0edbd56bdc5.yaml releasenotes/notes/support-untrusted-runtime-72739ab479265d81.yaml releasenotes/source/conf.py releasenotes/source/index.rst releasenotes/source/rocky.rst releasenotes/source/stein.rst releasenotes/source/train.rst releasenotes/source/unreleased.rst releasenotes/source/_static/.placeholder releasenotes/source/_templates/.placeholder tools/cover.sh tools/install_venv.py tools/install_venv_common.py tools/qinling.bash_completion tools/with_venv.shpython-qinlingclient-5.0.1/python_qinlingclient.egg-info/PKG-INFO0000664000175000017500000000507013643577506024770 0ustar zuulzuul00000000000000Metadata-Version: 1.2 Name: python-qinlingclient Version: 5.0.1 Summary: python-qinlingclient Home-page: https://docs.openstack.org/qinling/latest/ Author: OpenStack Author-email: openstack-discuss@lists.openstack.org License: Apache License, Version 2.0 Description: ======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/python-qinlingclient.svg :target: https://governance.openstack.org/reference/tags/index.html .. 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 . Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Environment :: Console Classifier: Environment :: OpenStack Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Requires-Python: >=3.6 python-qinlingclient-5.0.1/python_qinlingclient.egg-info/dependency_links.txt0000664000175000017500000000000113643577506027737 0ustar zuulzuul00000000000000 python-qinlingclient-5.0.1/python_qinlingclient.egg-info/top_level.txt0000664000175000017500000000001613643577506026420 0ustar zuulzuul00000000000000qinlingclient python-qinlingclient-5.0.1/python_qinlingclient.egg-info/not-zip-safe0000664000175000017500000000000113643577506026117 0ustar zuulzuul00000000000000 python-qinlingclient-5.0.1/python_qinlingclient.egg-info/requires.txt0000664000175000017500000000043713643577506026275 0ustar zuulzuul00000000000000pbr!=2.1.0,>=2.0.0 PrettyTable<0.8,>=0.7.2 python-keystoneclient>=3.8.0 python-openstackclient>=3.12.0 iso8601>=0.1.11 six>=1.10.0 Babel!=2.4.0,>=2.3.4 requests>=2.14.2 PyYAML>=3.12 osc-lib>=1.8.0 oslo.utils>=3.33.0 oslo.log>=3.36.0 oslo.i18n>=3.15.3 oslo.serialization!=2.19.1,>=2.18.0 python-qinlingclient-5.0.1/lower-constraints.txt0000664000175000017500000000301013643577416022166 0ustar zuulzuul00000000000000alabaster==0.7.10 appdirs==1.3.0 asn1crypto==0.23.0 Babel==2.3.4 cffi==1.7.0 cliff==2.8.0 cmd2==0.8.0 coverage==4.0 cryptography==2.1 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 mock==2.0.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 openstackdocstheme==1.18.1 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.12 reno==2.5.0 requests==2.14.2 requests-mock==1.5.2 requestsexceptions==1.2.0 rfc3986==0.3.1 simplejson==3.5.1 six==1.10.0 snowballstemmer==1.2.1 Sphinx==1.6.5 sphinxcontrib-websupport==1.0.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 python-qinlingclient-5.0.1/CONTRIBUTING.rst0000664000175000017500000000123113643577416020374 0ustar zuulzuul00000000000000If 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 python-qinlingclient-5.0.1/tools/0000775000175000017500000000000013643577506017076 5ustar zuulzuul00000000000000python-qinlingclient-5.0.1/tools/qinling.bash_completion0000664000175000017500000000155113643577416023631 0ustar zuulzuul00000000000000_murano_opts="" # lazy init _murano_flags="" # lazy init _murano_opts_exp="" # lazy init _murano() { local cur prev kbc COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" if [ "x$_murano_opts" == "x" ] ; then kbc="$(murano bash-completion | sed -e "s/ -h / /")" _murano_opts="$(echo "$kbc" | sed -e "s/--[a-z0-9_-]*//g" -e "s/[ ][ ]*/ /g")" _murano_flags="$(echo " $kbc" | sed -e "s/ [^-][^-][a-z0-9_-]*//g" -e "s/[ ][ ]*/ /g")" _murano_opts_exp="$(echo "$_murano_opts" | sed -e "s/[ ]/|/g")" fi if [[ " ${COMP_WORDS[@]} " =~ " "($_murano_opts_exp)" " && "$prev" != "help" ]] ; then COMPREPLY=($(compgen -W "${_murano_flags}" -- "${cur}")) else COMPREPLY=($(compgen -W "${_murano_opts}" -- "${cur}")) fi return 0 } complete -o default -F _murano murano python-qinlingclient-5.0.1/tools/with_venv.sh0000775000175000017500000000012413643577416021443 0ustar zuulzuul00000000000000#!/bin/bash TOOLS=`dirname $0` VENV=$TOOLS/../.venv source $VENV/bin/activate && $@ python-qinlingclient-5.0.1/tools/install_venv.py0000664000175000017500000000504613643577416022161 0ustar zuulzuul00000000000000# Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # All Rights Reserved. # # Copyright 2010 OpenStack Foundation # Copyright 2013 IBM Corp. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Installation script for python-qinlingclient's development virtualenv """ from __future__ import print_function import os import sys from six.moves import configparser import install_venv_common as install_venv def print_help(project, venv, root): help = """ %(project)s development environment setup is complete. %(project)s development uses virtualenv to track and manage Python dependencies while in development and testing. To activate the %(project)s virtualenv for the extent of your current shell session you can run: $ source %(venv)s/bin/activate Or, if you prefer, you can run commands in the virtualenv on a case by case basis by running: $ %(root)s/tools/with_venv.sh """ print(help % dict(project=project, venv=venv, root=root)) def main(argv): root = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) if os.environ.get('tools_path'): root = os.environ['tools_path'] venv = os.path.join(root, '.venv') if os.environ.get('venv'): venv = os.environ['venv'] pip_requires = os.path.join(root, 'requirements.txt') test_requires = os.path.join(root, 'test-requirements.txt') py_version = "python%s.%s" % (sys.version_info[0], sys.version_info[1]) setup_cfg = configparser.ConfigParser() setup_cfg.read('setup.cfg') project = setup_cfg.get('metadata', 'name') install = install_venv.InstallVenv( root, venv, pip_requires, test_requires, py_version, project) options = install.parse_args(argv) install.check_python_version() install.check_dependencies() install.create_virtualenv(no_site_packages=options.no_site_packages) install.install_dependencies() install.post_process() print_help(project, venv, root) if __name__ == '__main__': sys.exit(main(sys.argv)) python-qinlingclient-5.0.1/tools/install_venv_common.py0000664000175000017500000001631413643577416023531 0ustar zuulzuul00000000000000# Copyright 2013 OpenStack, LLC # Copyright 2013 IBM Corp. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Provides methods needed by installation script for OpenStack development virtual environments. Synced in from openstack-common """ from __future__ import print_function import argparse import os import subprocess import sys class InstallVenv(object): def __init__(self, root, venv, requirements, test_requirements, py_version, project): self.root = root self.venv = venv self.requirements = requirements self.test_requirements = test_requirements self.py_version = py_version self.project = project def die(self, message, *args): print(message % args, file=sys.stderr) sys.exit(1) def check_python_version(self): if sys.version_info < (2, 6): self.die("Need Python Version >= 2.6") def run_command_with_code(self, cmd, redirect_output=True, check_exit_code=True): """Runs a command in an out-of-process shell. Returns the output of that command. Working directory is self.root. """ if redirect_output: stdout = subprocess.PIPE else: stdout = None proc = subprocess.Popen(cmd, cwd=self.root, stdout=stdout) output = proc.communicate()[0] if check_exit_code and proc.returncode != 0: self.die('Command "%s" failed.\n%s', ' '.join(cmd), output) return (output, proc.returncode) def run_command(self, cmd, redirect_output=True, check_exit_code=True): return self.run_command_with_code(cmd, redirect_output, check_exit_code)[0] def get_distro(self): if (os.path.exists('/etc/fedora-release') or os.path.exists('/etc/redhat-release')): return Fedora( self.root, self.venv, self.requirements, self.test_requirements, self.py_version, self.project) else: return Distro( self.root, self.venv, self.requirements, self.test_requirements, self.py_version, self.project) def check_dependencies(self): self.get_distro().install_virtualenv() def create_virtualenv(self, no_site_packages=True): """Creates the virtual environment and installs PIP. Creates the virtual environment and installs PIP only into the virtual environment. """ if not os.path.isdir(self.venv): print('Creating venv...'), if no_site_packages: self.run_command(['virtualenv', '-q', '--no-site-packages', self.venv]) else: self.run_command(['virtualenv', '-q', self.venv]) print('done.') else: print("venv already exists...") pass def pip_install(self, *args): self.run_command(['tools/with_venv.sh', 'pip', 'install', '--upgrade'] + list(args), redirect_output=False) def install_dependencies(self): print('Installing dependencies with pip (this can take a while)...') # First things first, make sure our venv has the latest pip and # distribute. # NOTE: we keep pip at version 1.1 since the most recent version causes # the .venv creation to fail. See: # https://bugs.launchpad.net/nova/+bug/1047120 self.pip_install('pip==1.1') self.pip_install('setuptools') self.pip_install('-r', self.requirements) self.pip_install('-r', self.test_requirements) def post_process(self): self.get_distro().post_process() def parse_args(self, argv): """Parses command-line arguments.""" parser = argparse.ArgumentParser() parser.add_argument('-n', '--no-site-packages', action='store_true', help="Do not inherit packages from global Python " "install") return parser.parse_args(argv[1:]) class Distro(InstallVenv): def check_cmd(self, cmd): return bool(self.run_command(['which', cmd], check_exit_code=False).strip()) def install_virtualenv(self): if self.check_cmd('virtualenv'): return if self.check_cmd('easy_install'): print('Installing virtualenv via easy_install...'), if self.run_command(['easy_install', 'virtualenv']): print('Succeeded') return else: print('Failed') self.die('ERROR: virtualenv not found.\n\n%s development' ' requires virtualenv, please install it using your' ' favorite package management tool' % self.project) def post_process(self): """Any distribution-specific post-processing gets done here. In particular, this is useful for applying patches to code inside the venv. """ pass class Fedora(Distro): """This covers all Fedora-based distributions. Includes: Fedora, RHEL, CentOS, Scientific Linux """ def check_pkg(self, pkg): return self.run_command_with_code(['rpm', '-q', pkg], check_exit_code=False)[1] == 0 def yum_install(self, pkg, **kwargs): print("Attempting to install '%s' via yum" % pkg) self.run_command(['sudo', 'yum', 'install', '-y', pkg], **kwargs) def apply_patch(self, originalfile, patchfile): self.run_command(['patch', originalfile, patchfile]) def install_virtualenv(self): if self.check_cmd('virtualenv'): return if not self.check_pkg('python-virtualenv'): self.yum_install('python-virtualenv', check_exit_code=False) super(Fedora, self).install_virtualenv() def post_process(self): """Workaround for a bug in eventlet. This currently affects RHEL6.1, but the fix can safely be applied to all RHEL and Fedora distributions. This can be removed when the fix is applied upstream. Nova: https://bugs.launchpad.net/nova/+bug/884915 Upstream: https://bitbucket.org/which_linden/eventlet/issue/89 """ # Install "patch" program if it's not there if not self.check_pkg('patch'): self.yum_install('patch') # Apply the eventlet patch self.apply_patch(os.path.join(self.venv, 'lib', self.py_version, 'site-packages', 'eventlet/green/subprocess.py'), 'contrib/redhat-eventlet.patch') python-qinlingclient-5.0.1/tools/cover.sh0000775000175000017500000000545513643577416020564 0ustar zuulzuul00000000000000#!/bin/bash # # Copyright 2016: Mirantis Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. ALLOWED_EXTRA_MISSING=4 show_diff () { head -1 $1 diff -U 0 $1 $2 | sed 1,2d } # Stash uncommitted changes, checkout master and save coverage report uncommitted=$(git status --porcelain | grep -v "^??") [[ -n $uncommitted ]] && git stash > /dev/null git checkout HEAD^ baseline_report=$(mktemp -t qinlingclient_coverageXXXXXXX) find . -type f -name "*.pyc" -delete stestr run "$*" coverage combine coverage html -d cover coverage xml -o cover/coverage.xml coverage report > $baseline_report baseline_missing=$(awk 'END { print $3 }' $baseline_report) # Checkout back and unstash uncommitted changes (if any) git checkout - [[ -n $uncommitted ]] && git stash pop > /dev/null # Generate and save coverage report current_report=$(mktemp -t qinlingclient_coverageXXXXXXX) find . -type f -name "*.pyc" -delete stestr run "$*" coverage combine coverage html -d cover coverage xml -o cover/coverage.xml coverage report > $current_report current_missing=$(awk 'END { print $3 }' $current_report) baseline_percentage=$(awk 'END { print $4 }' $baseline_report) current_percentage=$(awk 'END { print $4 }' $current_report) # Show coverage details allowed_missing=$((baseline_missing+ALLOWED_EXTRA_MISSING)) echo "Baseline report:" echo "$(cat ${baseline_report})" echo "Proposed change report:" echo "$(cat ${current_report})" echo "" echo "" echo "Allowed to introduce missing lines : ${ALLOWED_EXTRA_MISSING}" echo "Missing lines in master : ${baseline_missing}" echo "Missing lines in proposed change : ${current_missing}" echo "Current percentage : ${baseline_percentage}" echo "Proposed change percentage : ${current_percentage}" if [ $allowed_missing -gt $current_missing ]; then if [ $baseline_missing -lt $current_missing ]; then show_diff $baseline_report $current_report echo "I believe you can cover all your code with 100% coverage!" else echo "Thank you! You are awesome! Keep writing unit tests! :)" fi exit_code=0 else show_diff $baseline_report $current_report echo "Please write more unit tests, we should keep our test coverage :( " exit_code=1 fi rm $baseline_report $current_report exit $exit_code