python-zunclient-4.0.0/0000775000175000017500000000000013643163533015076 5ustar zuulzuul00000000000000python-zunclient-4.0.0/python_zunclient.egg-info/0000775000175000017500000000000013643163533022204 5ustar zuulzuul00000000000000python-zunclient-4.0.0/python_zunclient.egg-info/top_level.txt0000664000175000017500000000001213643163532024726 0ustar zuulzuul00000000000000zunclient python-zunclient-4.0.0/python_zunclient.egg-info/SOURCES.txt0000664000175000017500000001140213643163533024066 0ustar zuulzuul00000000000000.coveragerc .stestr.conf .testr.conf .zuul.yaml AUTHORS CONTRIBUTING.rst ChangeLog HACKING.rst LICENSE MANIFEST.in README.rst babel.cfg lower-constraints.txt requirements.txt setup.cfg setup.py test-requirements.txt tox.ini doc/requirements.txt doc/source/conf.py doc/source/index.rst doc/source/cli/command-list.rst doc/source/cli/index.rst doc/source/cli/command-objects/action.rst doc/source/cli/command-objects/host.rst doc/source/cli/command-objects/image.rst doc/source/cli/command-objects/service.rst doc/source/cli/man/zun.rst doc/source/contributor/index.rst doc/source/install/index.rst doc/source/user/index.rst doc/source/user/python-api.rst playbooks/zunclient-devstack-docker-sql/post.yaml playbooks/zunclient-devstack-docker-sql/run.yaml python_zunclient.egg-info/PKG-INFO python_zunclient.egg-info/SOURCES.txt python_zunclient.egg-info/dependency_links.txt python_zunclient.egg-info/entry_points.txt python_zunclient.egg-info/not-zip-safe python_zunclient.egg-info/pbr.json python_zunclient.egg-info/requires.txt python_zunclient.egg-info/top_level.txt releasenotes/notes/.placeholder releasenotes/notes/drop-py-2-7-c18f48ee28088c4a.yaml releasenotes/source/conf.py releasenotes/source/index.rst releasenotes/source/pike.rst releasenotes/source/queens.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/fast8.sh tools/flake8wrap.sh tools/gen-config tools/run_functional.sh tools/zun.bash_completion tools/zun.zsh_completion zunclient/__init__.py zunclient/api_versions.py zunclient/client.py zunclient/exceptions.py zunclient/i18n.py zunclient/shell.py zunclient/version.py zunclient/common/__init__.py zunclient/common/base.py zunclient/common/cliutils.py zunclient/common/httpclient.py zunclient/common/template_format.py zunclient/common/template_utils.py zunclient/common/utils.py zunclient/common/apiclient/__init__.py zunclient/common/apiclient/auth.py zunclient/common/apiclient/base.py zunclient/common/apiclient/exceptions.py zunclient/common/websocketclient/__init__.py zunclient/common/websocketclient/exceptions.py zunclient/common/websocketclient/websocketclient.py zunclient/osc/__init__.py zunclient/osc/plugin.py zunclient/osc/v1/__init__.py zunclient/osc/v1/availability_zones.py zunclient/osc/v1/capsules.py zunclient/osc/v1/containers.py zunclient/osc/v1/hosts.py zunclient/osc/v1/images.py zunclient/osc/v1/quota_classes.py zunclient/osc/v1/quotas.py zunclient/osc/v1/registries.py zunclient/osc/v1/services.py zunclient/tests/__init__.py zunclient/tests/functional/__init__.py zunclient/tests/functional/base.py zunclient/tests/functional/hooks/__init__.py zunclient/tests/functional/hooks/gate_hook.sh zunclient/tests/functional/hooks/post_test_hook.sh zunclient/tests/functional/osc/__init__.py zunclient/tests/functional/osc/v1/__init__.py zunclient/tests/functional/osc/v1/base.py zunclient/tests/functional/osc/v1/test_container.py zunclient/tests/unit/__init__.py zunclient/tests/unit/base.py zunclient/tests/unit/test_api_versions.py zunclient/tests/unit/test_client.py zunclient/tests/unit/test_shell.py zunclient/tests/unit/test_websocketclient.py zunclient/tests/unit/utils.py zunclient/tests/unit/common/__init__.py zunclient/tests/unit/common/test_httpclient.py zunclient/tests/unit/common/test_utils.py zunclient/tests/unit/osc/__init__.py zunclient/tests/unit/osc/test_plugin.py zunclient/tests/unit/v1/__init__.py zunclient/tests/unit/v1/shell_test_base.py zunclient/tests/unit/v1/test_availability_zones.py zunclient/tests/unit/v1/test_availability_zones_shell.py zunclient/tests/unit/v1/test_client.py zunclient/tests/unit/v1/test_containers.py zunclient/tests/unit/v1/test_containers_shell.py zunclient/tests/unit/v1/test_images.py zunclient/tests/unit/v1/test_images_shell.py zunclient/tests/unit/v1/test_quotas.py zunclient/tests/unit/v1/test_registries.py zunclient/tests/unit/v1/test_services.py zunclient/tests/unit/v1/test_services_shell.py zunclient/tests/unit/v1/test_versions.py zunclient/tests/unit/v1/test_versions_shell.py zunclient/v1/__init__.py zunclient/v1/actions.py zunclient/v1/actions_shell.py zunclient/v1/availability_zones.py zunclient/v1/availability_zones_shell.py zunclient/v1/capsules.py zunclient/v1/capsules_shell.py zunclient/v1/client.py zunclient/v1/containers.py zunclient/v1/containers_shell.py zunclient/v1/hosts.py zunclient/v1/hosts_shell.py zunclient/v1/images.py zunclient/v1/images_shell.py zunclient/v1/quota_classes.py zunclient/v1/quota_classes_shell.py zunclient/v1/quotas.py zunclient/v1/quotas_shell.py zunclient/v1/registries.py zunclient/v1/registries_shell.py zunclient/v1/services.py zunclient/v1/services_shell.py zunclient/v1/shell.py zunclient/v1/versions.py zunclient/v1/versions_shell.pypython-zunclient-4.0.0/python_zunclient.egg-info/not-zip-safe0000664000175000017500000000000113643163532024431 0ustar zuulzuul00000000000000 python-zunclient-4.0.0/python_zunclient.egg-info/PKG-INFO0000664000175000017500000000477713643163532023317 0ustar zuulzuul00000000000000Metadata-Version: 1.2 Name: python-zunclient Version: 4.0.0 Summary: Client Library for Zun Home-page: https://docs.openstack.org/python-zunclient/latest/ Author: OpenStack Author-email: openstack-discuss@lists.openstack.org License: UNKNOWN Description: ======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/python-zunclient.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on ============================================== Python bindings to the OpenStack Container API ============================================== .. image:: https://img.shields.io/pypi/v/python-zunclient.svg :target: https://pypi.org/project/python-zunclient/ :alt: Latest Version This is a client library for Zun built on the Zun API. It provides a Python API (the zunclient module) and a command-line tool (zun). * License: Apache License, Version 2.0 * `PyPi`_ - package installation * `Online Documentation`_ * `Launchpad project`_ - release management * `Blueprints`_ - feature specifications * `Bugs`_ - issue tracking * `Source`_ * `How to Contribute`_ .. _PyPi: https://pypi.org/project/python-zunclient .. _Online Documentation: https://docs.openstack.org/python-zunclient/latest .. _Launchpad project: https://launchpad.net/python-zunclient .. _Blueprints: https://blueprints.launchpad.net/python-zunclient .. _Bugs: https://bugs.launchpad.net/python-zunclient .. _Source: https://opendev.org/openstack/python-zunclient .. _How to Contribute: https://docs.openstack.org/infra/manual/developers.html Platform: UNKNOWN Classifier: Environment :: OpenStack 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 :: Implementation :: CPython Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Requires-Python: >=3.6 python-zunclient-4.0.0/python_zunclient.egg-info/entry_points.txt0000664000175000017500000000753513643163532025513 0ustar zuulzuul00000000000000[console_scripts] zun = zunclient.shell:main [openstack.cli.extension] container = zunclient.osc.plugin [openstack.container.v1] appcontainer_action_list = zunclient.osc.v1.containers:ActionList appcontainer_action_show = zunclient.osc.v1.containers:ActionShow appcontainer_add_floating_ip = zunclient.osc.v1.containers:AddFloatingIP appcontainer_add_security_group = zunclient.osc.v1.containers:AddSecurityGroup appcontainer_attach = zunclient.osc.v1.containers:AttachContainer appcontainer_availability_zone_list = zunclient.osc.v1.availability_zones:ListAvailabilityZone appcontainer_commit = zunclient.osc.v1.containers:CommitContainer appcontainer_cp = zunclient.osc.v1.containers:CopyContainer appcontainer_create = zunclient.osc.v1.containers:CreateContainer appcontainer_delete = zunclient.osc.v1.containers:DeleteContainer appcontainer_exec = zunclient.osc.v1.containers:ExecContainer appcontainer_host_list = zunclient.osc.v1.hosts:ListHost appcontainer_host_show = zunclient.osc.v1.hosts:ShowHost appcontainer_image_delete = zunclient.osc.v1.images:DeleteImage appcontainer_image_list = zunclient.osc.v1.images:ListImage appcontainer_image_pull = zunclient.osc.v1.images:PullImage appcontainer_image_search = zunclient.osc.v1.images:SearchImage appcontainer_image_show = zunclient.osc.v1.images:ShowImage appcontainer_kill = zunclient.osc.v1.containers:KillContainer appcontainer_list = zunclient.osc.v1.containers:ListContainer appcontainer_logs = zunclient.osc.v1.containers:LogsContainer appcontainer_network_attach = zunclient.osc.v1.containers:NetworkAttach appcontainer_network_detach = zunclient.osc.v1.containers:NetworkDetach appcontainer_network_list = zunclient.osc.v1.containers:NetworkList appcontainer_pause = zunclient.osc.v1.containers:PauseContainer appcontainer_quota_class_get = zunclient.osc.v1.quota_classes:GetQuotaClass appcontainer_quota_class_update = zunclient.osc.v1.quota_classes:UpdateQuotaClass appcontainer_quota_default = zunclient.osc.v1.quotas:GetDefaultQuota appcontainer_quota_delete = zunclient.osc.v1.quotas:DeleteQuota appcontainer_quota_get = zunclient.osc.v1.quotas:GetQuota appcontainer_quota_update = zunclient.osc.v1.quotas:UpdateQuota appcontainer_rebuild = zunclient.osc.v1.containers:RebuildContainer appcontainer_registry_create = zunclient.osc.v1.registries:CreateRegistry appcontainer_registry_delete = zunclient.osc.v1.registries:DeleteRegistry appcontainer_registry_list = zunclient.osc.v1.registries:ListRegistry appcontainer_registry_show = zunclient.osc.v1.registries:ShowRegistry appcontainer_registry_update = zunclient.osc.v1.registries:UpdateRegistry appcontainer_remove_floating_ip = zunclient.osc.v1.containers:RemoveFloatingIP appcontainer_remove_security_group = zunclient.osc.v1.containers:RemoveSecurityGroup appcontainer_restart = zunclient.osc.v1.containers:RestartContainer appcontainer_run = zunclient.osc.v1.containers:RunContainer appcontainer_service_delete = zunclient.osc.v1.services:DeleteService appcontainer_service_disable = zunclient.osc.v1.services:DisableService appcontainer_service_enable = zunclient.osc.v1.services:EnableService appcontainer_service_forcedown = zunclient.osc.v1.services:ForceDownService appcontainer_service_list = zunclient.osc.v1.services:ListService appcontainer_set = zunclient.osc.v1.containers:UpdateContainer appcontainer_show = zunclient.osc.v1.containers:ShowContainer appcontainer_start = zunclient.osc.v1.containers:StartContainer appcontainer_stats = zunclient.osc.v1.containers:StatsContainer appcontainer_stop = zunclient.osc.v1.containers:StopContainer appcontainer_top = zunclient.osc.v1.containers:TopContainer appcontainer_unpause = zunclient.osc.v1.containers:UnpauseContainer capsule_create = zunclient.osc.v1.capsules:CreateCapsule capsule_delete = zunclient.osc.v1.capsules:DeleteCapsule capsule_list = zunclient.osc.v1.capsules:ListCapsule capsule_show = zunclient.osc.v1.capsules:ShowCapsule python-zunclient-4.0.0/python_zunclient.egg-info/dependency_links.txt0000664000175000017500000000000113643163532026251 0ustar zuulzuul00000000000000 python-zunclient-4.0.0/python_zunclient.egg-info/requires.txt0000664000175000017500000000033013643163532024577 0ustar zuulzuul00000000000000pbr!=2.1.0,>=2.0.0 PrettyTable<0.8,>=0.7.1 python-openstackclient>=3.12.0 keystoneauth1>=3.4.0 osc-lib>=1.8.0 oslo.i18n>=3.15.3 oslo.log>=3.36.0 oslo.utils>=3.33.0 websocket-client>=0.44.0 docker>=2.4.2 PyYAML>=3.12 python-zunclient-4.0.0/python_zunclient.egg-info/pbr.json0000664000175000017500000000005613643163532023662 0ustar zuulzuul00000000000000{"git_version": "e920390", "is_release": true}python-zunclient-4.0.0/.zuul.yaml0000664000175000017500000000136613643163457017052 0ustar zuulzuul00000000000000- job: name: zunclient-devstack-docker-sql parent: legacy-dsvm-base run: playbooks/zunclient-devstack-docker-sql/run.yaml post-run: playbooks/zunclient-devstack-docker-sql/post.yaml timeout: 4200 required-projects: - openstack/devstack - openstack/devstack-gate - openstack/devstack-plugin-container - openstack/kuryr-libnetwork - openstack/python-zunclient - openstack/zun - openstack/zun-tempest-plugin - project: templates: - openstack-cover-jobs - openstack-lower-constraints-jobs - openstack-python3-ussuri-jobs - check-requirements - publish-openstack-docs-pti check: jobs: - zunclient-devstack-docker-sql: voting: false python-zunclient-4.0.0/.testr.conf0000664000175000017500000000054513643163457017175 0ustar zuulzuul00000000000000[DEFAULT] test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \ OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \ OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \ ${PYTHON:-python} -m subunit.run discover -t ./ ${OS_TEST_PATH:-./zunclient/tests/unit} $LISTOPT $IDOPTION test_id_option=--load-list $IDFILE test_list_option=--list python-zunclient-4.0.0/requirements.txt0000664000175000017500000000105113643163457020364 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.1 # BSD python-openstackclient>=3.12.0 # Apache-2.0 keystoneauth1>=3.4.0 # Apache-2.0 osc-lib>=1.8.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 oslo.log>=3.36.0 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 websocket-client>=0.44.0 # LGPLv2+ docker>=2.4.2 # Apache-2.0 PyYAML>=3.12 # MIT python-zunclient-4.0.0/setup.py0000664000175000017500000000127113643163457016616 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-zunclient-4.0.0/tools/0000775000175000017500000000000013643163533016236 5ustar zuulzuul00000000000000python-zunclient-4.0.0/tools/zun.bash_completion0000664000175000017500000000203313643163457022145 0ustar zuulzuul00000000000000_zun_opts="" # lazy init _zun_flags="" # lazy init _zun_opts_exp="" # lazy init _zun() { local cur prev nbc cflags COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" if [ "x$_zun_opts" == "x" ] ; then nbc="`zun bash-completion | sed -e "s/ *-h */ /" -e "s/ *-i */ /"`" _zun_opts="`echo "$nbc" | sed -e "s/--[a-z0-9_-]*//g" -e "s/ */ /g"`" _zun_flags="`echo " $nbc" | sed -e "s/ [^-][^-][a-z0-9_-]*//g" -e "s/ */ /g"`" _zun_opts_exp="`echo "$_zun_opts" | tr ' ' '|'`" fi if [[ " ${COMP_WORDS[@]} " =~ " "($_zun_opts_exp)" " && "$prev" != "help" ]] ; then COMPLETION_CACHE=~/.zunclient/*/*-cache cflags="$_zun_flags "$(cat $COMPLETION_CACHE 2> /dev/null | tr '\n' ' ') COMPREPLY=($(compgen -W "${cflags}" -- ${cur})) else COMPREPLY=($(compgen -W "${_zun_opts}" -- ${cur})) fi return 0 } complete -F _zun zun python-zunclient-4.0.0/tools/flake8wrap.sh0000775000175000017500000000100313643163457020640 0ustar zuulzuul00000000000000#!/bin/sh # # A simple wrapper around flake8 which makes it possible # to ask it to only verify files changed in the current # git HEAD patch. # # Intended to be invoked via tox: # # tox -epep8 -- -HEAD # if test "x$1" = "x-HEAD" ; then shift files=$(git diff --name-only HEAD~1 | tr '\n' ' ') echo "Running flake8 on ${files}" diff -u --from-file /dev/null ${files} | flake8 --max-complexity 10 --diff "$@" else echo "Running flake8 on all files" exec flake8 --max-complexity 10 "$@" fi python-zunclient-4.0.0/tools/zun.zsh_completion0000664000175000017500000000131313643163457022034 0ustar zuulzuul00000000000000#compdef zun local -a nbc _zun_opts _zun_flags _zun_opts_exp cur prev nbc=(${(ps: :)$(_call_program options "$service bash-completion" 2>/dev/null)}) _zun_opts=(${nbc:#-*}) _zun_flags=(${(M)nbc:#-*}) _zun_opt_exp=${${nbc:#-*}// /|} cur=$words[CURRENT] prev=$words[(( CURRENT - 1 ))] _checkcomp(){ for word in $words[@]; do if [[ -n ${_zun_opts[(r)$word]} ]]; then return 0 fi done return 1 } echo $_zun_opts[@] |grep --color zun if [[ "$prev" != "help" ]] && _checkcomp; then COMPLETION_CACHE=(~/.zunclient/*/*-cache) cflags=($_zun_flags[@] ${(ps: :)$(cat $COMPLETION_CACHE 2>/dev/null)}) compadd "$@" -d $cflags[@] else compadd "$@" -d $_zun_opts[@] fi python-zunclient-4.0.0/tools/run_functional.sh0000775000175000017500000000125313643163457021631 0ustar zuulzuul00000000000000#!/bin/bash FUNC_TEST_DIR=$(dirname $0)/../zunclient/tests/functional/ CONFIG_FILE=$FUNC_TEST_DIR/test.conf if [[ -n "$OS_AUTH_TOKEN" ]] && [[ -n "$ZUN_URL" ]]; then cat <$CONFIG_FILE [functional] api_version = 1 auth_strategy=noauth os_auth_token=$OS_AUTH_TOKEN zun_url=$ZUN_URL END else cat <$CONFIG_FILE [functional] api_version = 1 os_auth_url=$OS_AUTH_URL os_identity_api_version = $OS_IDENTITY_API_VERSION os_username=$OS_USERNAME os_password=$OS_PASSWORD os_project_name=$OS_PROJECT_NAME os_user_domain_id=$OS_USER_DOMAIN_ID os_project_domain_id=$OS_PROJECT_DOMAIN_ID os_service_type=container os_endpoint_type=public END fi tox -e functional -- --concurrency=1 python-zunclient-4.0.0/tools/fast8.sh0000664000175000017500000000045413643163457017627 0ustar zuulzuul00000000000000#!/bin/bash cd $(dirname "$0")/.. CHANGED=$(git diff --name-only HEAD~1 | tr '\n' ' ') # Skip files that don't exist # (have been git rm'd) CHECK="" for FILE in $CHANGED; do if [ -f "$FILE" ]; then CHECK="$CHECK $FILE" fi done diff -u --from-file /dev/null $CHECK | flake8 --diff python-zunclient-4.0.0/tools/gen-config0000775000175000017500000000011713643163457020204 0ustar zuulzuul00000000000000#!/bin/sh oslo-config-generator --config-file=tools/zun-config-generator.conf python-zunclient-4.0.0/babel.cfg0000664000175000017500000000002113643163457016622 0ustar zuulzuul00000000000000[python: **.py] python-zunclient-4.0.0/LICENSE0000664000175000017500000002363713643163457016123 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-zunclient-4.0.0/.coveragerc0000664000175000017500000000017013643163457017222 0ustar zuulzuul00000000000000[run] branch = True source = zunclient omit = zunclient/tests/* [report] ignore_errors = True exclude_lines = pass python-zunclient-4.0.0/HACKING.rst0000664000175000017500000000023213643163457016676 0ustar zuulzuul00000000000000python-zunclient Style Commandments =================================== Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/ python-zunclient-4.0.0/MANIFEST.in0000664000175000017500000000013613643163457016641 0ustar zuulzuul00000000000000include AUTHORS include ChangeLog exclude .gitignore exclude .gitreview global-exclude *.pyc python-zunclient-4.0.0/PKG-INFO0000664000175000017500000000477713643163533016212 0ustar zuulzuul00000000000000Metadata-Version: 1.2 Name: python-zunclient Version: 4.0.0 Summary: Client Library for Zun Home-page: https://docs.openstack.org/python-zunclient/latest/ Author: OpenStack Author-email: openstack-discuss@lists.openstack.org License: UNKNOWN Description: ======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/python-zunclient.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on ============================================== Python bindings to the OpenStack Container API ============================================== .. image:: https://img.shields.io/pypi/v/python-zunclient.svg :target: https://pypi.org/project/python-zunclient/ :alt: Latest Version This is a client library for Zun built on the Zun API. It provides a Python API (the zunclient module) and a command-line tool (zun). * License: Apache License, Version 2.0 * `PyPi`_ - package installation * `Online Documentation`_ * `Launchpad project`_ - release management * `Blueprints`_ - feature specifications * `Bugs`_ - issue tracking * `Source`_ * `How to Contribute`_ .. _PyPi: https://pypi.org/project/python-zunclient .. _Online Documentation: https://docs.openstack.org/python-zunclient/latest .. _Launchpad project: https://launchpad.net/python-zunclient .. _Blueprints: https://blueprints.launchpad.net/python-zunclient .. _Bugs: https://bugs.launchpad.net/python-zunclient .. _Source: https://opendev.org/openstack/python-zunclient .. _How to Contribute: https://docs.openstack.org/infra/manual/developers.html Platform: UNKNOWN Classifier: Environment :: OpenStack 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 :: Implementation :: CPython Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Requires-Python: >=3.6 python-zunclient-4.0.0/releasenotes/0000775000175000017500000000000013643163533017567 5ustar zuulzuul00000000000000python-zunclient-4.0.0/releasenotes/source/0000775000175000017500000000000013643163533021067 5ustar zuulzuul00000000000000python-zunclient-4.0.0/releasenotes/source/conf.py0000664000175000017500000002036413643163457022400 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. # Glance 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 = [ 'oslosphinx', 'reno.sphinxext', ] # 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. copyright = u'2016, OpenStack Foundation' # 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 = 'GlanceReleaseNotesdoc' # -- Options for LaTeX output --------------------------------------------- # 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', 'GlanceReleaseNotes.tex', u'Glance Release Notes Documentation', u'Glance 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', 'glancereleasenotes', u'Glance Release Notes Documentation', [u'Glance 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', 'GlanceReleaseNotes', u'Glance Release Notes Documentation', u'Glance Developers', 'GlanceReleaseNotes', '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-zunclient-4.0.0/releasenotes/source/train.rst0000664000175000017500000000022113643163457022736 0ustar zuulzuul00000000000000=================================== Train Series Release Notes =================================== .. release-notes:: :branch: stable/train python-zunclient-4.0.0/releasenotes/source/rocky.rst0000664000175000017500000000017613643163457022761 0ustar zuulzuul00000000000000========================== Rocky Series Release Notes ========================== .. release-notes:: :branch: stable/rocky python-zunclient-4.0.0/releasenotes/source/index.rst0000664000175000017500000000026713643163457022742 0ustar zuulzuul00000000000000============================== python-zunclient Release Notes ============================== .. toctree:: :maxdepth: 1 unreleased train stein rocky queens pike python-zunclient-4.0.0/releasenotes/source/_static/0000775000175000017500000000000013643163533022515 5ustar zuulzuul00000000000000python-zunclient-4.0.0/releasenotes/source/_static/.placeholder0000664000175000017500000000000013643163457024773 0ustar zuulzuul00000000000000python-zunclient-4.0.0/releasenotes/source/pike.rst0000664000175000017500000000021713643163457022556 0ustar zuulzuul00000000000000=================================== Pike Series Release Notes =================================== .. release-notes:: :branch: stable/pike python-zunclient-4.0.0/releasenotes/source/queens.rst0000664000175000017500000000022313643163457023123 0ustar zuulzuul00000000000000=================================== Queens Series Release Notes =================================== .. release-notes:: :branch: stable/queens python-zunclient-4.0.0/releasenotes/source/stein.rst0000664000175000017500000000022113643163457022743 0ustar zuulzuul00000000000000=================================== Stein Series Release Notes =================================== .. release-notes:: :branch: stable/stein python-zunclient-4.0.0/releasenotes/source/_templates/0000775000175000017500000000000013643163533023224 5ustar zuulzuul00000000000000python-zunclient-4.0.0/releasenotes/source/_templates/.placeholder0000664000175000017500000000000013643163457025502 0ustar zuulzuul00000000000000python-zunclient-4.0.0/releasenotes/source/unreleased.rst0000664000175000017500000000016013643163457023752 0ustar zuulzuul00000000000000============================== Current Series Release Notes ============================== .. release-notes:: python-zunclient-4.0.0/releasenotes/notes/0000775000175000017500000000000013643163533020717 5ustar zuulzuul00000000000000python-zunclient-4.0.0/releasenotes/notes/drop-py-2-7-c18f48ee28088c4a.yaml0000664000175000017500000000033113643163457025731 0ustar zuulzuul00000000000000--- upgrade: - | Python 2.7 support has been dropped. Last release of python-zunclient to support py2.7 is OpenStack Train. The minimum version of Python now supported by python-zunclient is Python 3.6. python-zunclient-4.0.0/releasenotes/notes/.placeholder0000664000175000017500000000000013643163457023175 0ustar zuulzuul00000000000000python-zunclient-4.0.0/lower-constraints.txt0000664000175000017500000000412313643163457021341 0ustar zuulzuul00000000000000amqp==2.1.1 appdirs==1.4.3 asn1crypto==0.24.0 Babel==2.5.3 bandit==1.4.0 cachetools==2.0.0 certifi==2018.1.18 cffi==1.11.5 chardet==3.0.4 cliff==2.11.0 cmd2==0.8.2 contextlib2==0.4.0 coverage==4.0 cryptography==2.2.1 ddt==1.0.1 debtcollector==1.19.0 decorator==4.2.1 deprecation==2.0 doc8==0.6.0 docker==2.4.2 docker-pycreds==0.2.2 docutils==0.11 dogpile.cache==0.6.5 eventlet==0.18.2 extras==1.0.0 fasteners==0.7.0 fixtures==3.0.0 flake8==2.5.5 future==0.16.0 futurist==1.2.0 gitdb==0.6.4 GitPython==1.0.1 greenlet==0.4.10 hacking==0.12.0 idna==2.6 iso8601==0.1.12 Jinja2==2.10 jmespath==0.9.3 jsonpatch==1.21 jsonpointer==2.0 jsonschema==2.6.0 keystoneauth1==3.4.0 kombu==4.0.0 linecache2==1.0.0 MarkupSafe==1.0 mccabe==0.2.1 mock==2.0.0 monotonic==1.4 mox3==0.20.0 msgpack==0.5.6 msgpack-python==0.4.0 munch==2.2.0 netaddr==0.7.19 netifaces==0.10.6 openstacksdk==0.12.0 os-client-config==1.29.0 os-service-types==1.2.0 os-testr==1.0.0 osc-lib==1.8.0 oslo.concurrency==3.26.0 oslo.config==5.2.0 oslo.context==2.20.0 oslo.i18n==3.15.3 oslo.log==3.36.0 oslo.messaging==5.29.0 oslo.middleware==3.31.0 oslo.serialization==2.25.0 oslo.service==1.24.0 oslo.utils==3.33.0 oslotest==3.2.0 osprofiler==1.4.0 packaging==17.1 paramiko==2.0.0 Paste==2.0.2 PasteDeploy==1.5.0 pbr==2.0.0 pep8==1.5.7 pika==0.10.0 pika-pool==0.1.3 positional==1.2.1 prettytable==0.7.1 pyasn1==0.1.8 pycparser==2.18 pyflakes==0.8.1 pyinotify==0.9.6 pyOpenSSL==17.5.0 pyparsing==2.2.0 pyperclip==1.6.0 python-cinderclient==3.5.0 python-dateutil==2.7.0 python-glanceclient==2.9.1 python-keystoneclient==3.15.0 python-mimeparse==1.6.0 python-novaclient==10.1.0 python-openstackclient==3.12.0 python-subunit==1.0.0 pytz==2018.3 PyYAML==3.12 repoze.lru==0.7 requests==2.18.4 requestsexceptions==1.4.0 restructuredtext-lint==1.1.1 rfc3986==1.1.0 Routes==2.3.1 simplejson==3.13.2 six==1.11.0 smmap==0.9.0 statsd==3.2.1 stestr==2.0.0 stevedore==1.28.0 tempest==17.1.0 tenacity==3.2.1 testresources==2.0.0 testscenarios==0.4 testtools==2.2.0 traceback2==1.4.0 unittest2==1.1.0 urllib3==1.22 vine==1.1.4 warlock==1.3.0 WebOb==1.7.1 websocket-client==0.44.0 wrapt==1.10.11 python-zunclient-4.0.0/playbooks/0000775000175000017500000000000013643163533017101 5ustar zuulzuul00000000000000python-zunclient-4.0.0/playbooks/zunclient-devstack-docker-sql/0000775000175000017500000000000013643163533024760 5ustar zuulzuul00000000000000python-zunclient-4.0.0/playbooks/zunclient-devstack-docker-sql/post.yaml0000664000175000017500000000063313643163457026640 0ustar zuulzuul00000000000000- hosts: primary tasks: - name: Copy files from {{ ansible_user_dir }}/workspace/ on node synchronize: src: '{{ ansible_user_dir }}/workspace/' dest: '{{ zuul.executor.log_root }}' mode: pull copy_links: true verify_host: true rsync_opts: - --include=/logs/** - --include=*/ - --exclude=* - --prune-empty-dirs python-zunclient-4.0.0/playbooks/zunclient-devstack-docker-sql/run.yaml0000664000175000017500000000466113643163457026464 0ustar zuulzuul00000000000000- hosts: all name: Job zunclient-devstack-docker-sql tasks: - name: Ensure workspace directory file: path: '{{ ansible_user_dir }}/workspace' state: directory - shell: cmd: | set -e set -x cat > clonemap.yaml << EOF clonemap: - name: openstack/devstack-gate dest: devstack-gate EOF /usr/zuul-env/bin/zuul-cloner -m clonemap.yaml --cache-dir /opt/git \ https://opendev.org \ openstack/devstack-gate executable: /bin/bash chdir: '{{ ansible_user_dir }}/workspace' environment: '{{ zuul | zuul_legacy_vars }}' - shell: cmd: | set -e set -x cat << 'EOF' >>"/tmp/dg-local.conf" [[local|localrc]] enable_plugin zun https://opendev.org/openstack/zun USE_PYTHON3=True EOF executable: /bin/bash chdir: '{{ ansible_user_dir }}/workspace' environment: '{{ zuul | zuul_legacy_vars }}' - shell: cmd: | set -e set -x export PYTHONUNBUFFERED=true export DEVSTACK_GATE_TEMPEST=0 export DEVSTACK_GATE_NEUTRON=1 # Enable tempest for tempest plugin export ENABLED_SERVICES=tempest export PROJECTS="openstack/zun $PROJECTS" export PROJECTS="openstack/python-zunclient $PROJECTS" export PROJECTS="openstack/kuryr-libnetwork $PROJECTS" export PROJECTS="openstack/devstack-plugin-container $PROJECTS" export PROJECTS="openstack/zun-tempest-plugin $PROJECTS" # Keep localrc to be able to set some vars in post_test_hook export KEEP_LOCALRC=1 function gate_hook { cd /opt/stack/new/python-zunclient/ ./zunclient/tests/functional/hooks/gate_hook.sh docker sql } export -f gate_hook function post_test_hook { source $BASE/new/devstack/accrc/admin/admin cd /opt/stack/new/python-zunclient/ ./zunclient/tests/functional/hooks/post_test_hook.sh docker } export -f post_test_hook cp devstack-gate/devstack-vm-gate-wrap.sh ./safe-devstack-vm-gate-wrap.sh ./safe-devstack-vm-gate-wrap.sh executable: /bin/bash chdir: '{{ ansible_user_dir }}/workspace' environment: '{{ zuul | zuul_legacy_vars }}' python-zunclient-4.0.0/setup.cfg0000664000175000017500000001200313643163533016713 0ustar zuulzuul00000000000000[metadata] name = python-zunclient summary = Client Library for Zun description-file = README.rst author = OpenStack author-email = openstack-discuss@lists.openstack.org home-page = https://docs.openstack.org/python-zunclient/latest/ python-requires = >=3.6 classifier = Environment :: OpenStack Intended Audience :: Information Technology Intended Audience :: System Administrators License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: 3 :: Only Programming Language :: Python :: 3 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 [files] packages = zunclient [entry_points] console_scripts = zun = zunclient.shell:main openstack.cli.extension = container = zunclient.osc.plugin openstack.container.v1 = appcontainer_availability_zone_list = zunclient.osc.v1.availability_zones:ListAvailabilityZone appcontainer_service_list = zunclient.osc.v1.services:ListService appcontainer_service_delete = zunclient.osc.v1.services:DeleteService appcontainer_service_enable = zunclient.osc.v1.services:EnableService appcontainer_service_disable = zunclient.osc.v1.services:DisableService appcontainer_service_forcedown = zunclient.osc.v1.services:ForceDownService appcontainer_create = zunclient.osc.v1.containers:CreateContainer appcontainer_show = zunclient.osc.v1.containers:ShowContainer appcontainer_list = zunclient.osc.v1.containers:ListContainer appcontainer_delete = zunclient.osc.v1.containers:DeleteContainer appcontainer_restart = zunclient.osc.v1.containers:RestartContainer appcontainer_start = zunclient.osc.v1.containers:StartContainer appcontainer_pause = zunclient.osc.v1.containers:PauseContainer appcontainer_unpause = zunclient.osc.v1.containers:UnpauseContainer appcontainer_exec = zunclient.osc.v1.containers:ExecContainer appcontainer_logs = zunclient.osc.v1.containers:LogsContainer appcontainer_kill = zunclient.osc.v1.containers:KillContainer appcontainer_stop = zunclient.osc.v1.containers:StopContainer appcontainer_run = zunclient.osc.v1.containers:RunContainer appcontainer_top = zunclient.osc.v1.containers:TopContainer appcontainer_set = zunclient.osc.v1.containers:UpdateContainer appcontainer_attach = zunclient.osc.v1.containers:AttachContainer appcontainer_cp = zunclient.osc.v1.containers:CopyContainer appcontainer_stats = zunclient.osc.v1.containers:StatsContainer appcontainer_commit = zunclient.osc.v1.containers:CommitContainer appcontainer_add_security_group = zunclient.osc.v1.containers:AddSecurityGroup appcontainer_image_delete = zunclient.osc.v1.images:DeleteImage appcontainer_image_list = zunclient.osc.v1.images:ListImage appcontainer_image_pull = zunclient.osc.v1.images:PullImage appcontainer_host_list = zunclient.osc.v1.hosts:ListHost appcontainer_host_show = zunclient.osc.v1.hosts:ShowHost appcontainer_network_detach = zunclient.osc.v1.containers:NetworkDetach appcontainer_network_attach = zunclient.osc.v1.containers:NetworkAttach appcontainer_network_list = zunclient.osc.v1.containers:NetworkList appcontainer_image_search = zunclient.osc.v1.images:SearchImage appcontainer_remove_security_group = zunclient.osc.v1.containers:RemoveSecurityGroup appcontainer_image_show = zunclient.osc.v1.images:ShowImage appcontainer_rebuild = zunclient.osc.v1.containers:RebuildContainer appcontainer_action_list = zunclient.osc.v1.containers:ActionList appcontainer_action_show = zunclient.osc.v1.containers:ActionShow appcontainer_quota_get = zunclient.osc.v1.quotas:GetQuota appcontainer_quota_default = zunclient.osc.v1.quotas:GetDefaultQuota appcontainer_quota_delete = zunclient.osc.v1.quotas:DeleteQuota appcontainer_quota_update = zunclient.osc.v1.quotas:UpdateQuota appcontainer_quota_class_update = zunclient.osc.v1.quota_classes:UpdateQuotaClass appcontainer_quota_class_get = zunclient.osc.v1.quota_classes:GetQuotaClass appcontainer_registry_create = zunclient.osc.v1.registries:CreateRegistry appcontainer_registry_list = zunclient.osc.v1.registries:ListRegistry appcontainer_registry_show = zunclient.osc.v1.registries:ShowRegistry appcontainer_registry_update = zunclient.osc.v1.registries:UpdateRegistry appcontainer_registry_delete = zunclient.osc.v1.registries:DeleteRegistry appcontainer_add_floating_ip = zunclient.osc.v1.containers:AddFloatingIP appcontainer_remove_floating_ip = zunclient.osc.v1.containers:RemoveFloatingIP capsule_create = zunclient.osc.v1.capsules:CreateCapsule capsule_show = zunclient.osc.v1.capsules:ShowCapsule capsule_list = zunclient.osc.v1.capsules:ListCapsule capsule_delete = zunclient.osc.v1.capsules:DeleteCapsule [compile_catalog] directory = zunclient/locale domain = zunclient [update_catalog] domain = zunclient output_dir = zunclient/locale input_file = zunclient/locale/zunclient.pot [extract_messages] keywords = _ gettext ngettext l_ lazy_gettext mapping_file = babel.cfg output_file = zunclient/locale/zunclient.pot [egg_info] tag_build = tag_date = 0 python-zunclient-4.0.0/AUTHORS0000664000175000017500000000573113643163532016153 0ustar zuulzuul0000000000000000129207 98k <18552437190@163.com> AleptNmarata Andreas Jaeger Bharath Thiruveedula Bin Zhou Cao Xuan Hoang Charles Short Corey Bryant Deepak Doug Hellmann Eli Qiao Eric Fried Feng Shengqin Ghanshyam Mann Guoqiang Ding Hangdong Zhang Hongbin LU Hongbin Lu Hongbin Lu Ian Wienand James E. Blair Jeremy Liu JiWei Kevin Zhao Kevin Zhao Kien Nguyen Kien Nguyen Lajos Katona Lei Li M V P Nitesh Madhuri Kumari Madhuri Kumari Mehdi Abaakouk Michael Lekkas Nam Nguyen Hoai Namrata Sitlani Nguyen Hai Nicolas Haller Nilesh Chandekar OpenStack Release Bot Pavlo Shchelokovskyy Pradeep Kumar Singh Ranler Cao Sharat Sharma ShunliZhou Tony Xu Tovin Seven Tuan Do Anh Vu Cong Tuan WangChangyu Xianghui Zeng Yuanbin.Chen Zhenguo Niu ZhongShengping avnish bhavani bhavani.cr caishan cao.yuan chengyang chenke chenlx chenpengzi <1523688226@qq.com> cooldharma06 deepak_mourya haobing1 huang.zhiping jacky06 kangyufei miaohb namrata pengdake <19921207pq@gmail.com> pengyuesheng prameswar qingszhao rajat29 ricolin shubham.git wanghui wangzhh weikeyou xxj zhangjl python-zunclient-4.0.0/test-requirements.txt0000664000175000017500000000111113643163457021336 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. bandit>=1.1.0 # Apache-2.0 coverage!=4.4,>=4.0 # Apache-2.0 doc8>=0.6.0 # Apache-2.0 ddt>=1.0.1 # MIT hacking>=3.0,<3.1.0 # Apache-2.0 oslotest>=3.2.0 # Apache-2.0 osprofiler>=1.4.0 # Apache-2.0 stestr>=2.0.0 python-subunit>=1.0.0 # Apache-2.0/BSD tempest>=17.1.0 # Apache-2.0 testresources>=2.0.0 # Apache-2.0/BSD testscenarios>=0.4 # Apache-2.0/BSD testtools>=2.2.0 # MIT python-zunclient-4.0.0/.stestr.conf0000664000175000017500000000010713643163457017352 0ustar zuulzuul00000000000000[DEFAULT] test_path=${OS_TEST_PATH:-./zunclient/tests/unit} top_dir=./ python-zunclient-4.0.0/README.rst0000664000175000017500000000254413643163457016577 0ustar zuulzuul00000000000000======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/python-zunclient.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on ============================================== Python bindings to the OpenStack Container API ============================================== .. image:: https://img.shields.io/pypi/v/python-zunclient.svg :target: https://pypi.org/project/python-zunclient/ :alt: Latest Version This is a client library for Zun built on the Zun API. It provides a Python API (the zunclient module) and a command-line tool (zun). * License: Apache License, Version 2.0 * `PyPi`_ - package installation * `Online Documentation`_ * `Launchpad project`_ - release management * `Blueprints`_ - feature specifications * `Bugs`_ - issue tracking * `Source`_ * `How to Contribute`_ .. _PyPi: https://pypi.org/project/python-zunclient .. _Online Documentation: https://docs.openstack.org/python-zunclient/latest .. _Launchpad project: https://launchpad.net/python-zunclient .. _Blueprints: https://blueprints.launchpad.net/python-zunclient .. _Bugs: https://bugs.launchpad.net/python-zunclient .. _Source: https://opendev.org/openstack/python-zunclient .. _How to Contribute: https://docs.openstack.org/infra/manual/developers.html python-zunclient-4.0.0/ChangeLog0000664000175000017500000004132013643163532016647 0ustar zuulzuul00000000000000CHANGES ======= 4.0.0 ----- * Add support for entrypoint * Cleanup py27 support * Use unittest.mock instead of third party mock * Update to hacking 3.0 * Add support for capsule in OSC plugin * Add support for adding/removing floating ip * Update hacking for Python3 * Add support for requested host * Bump API version to 1.38 * Fix:modify comment for registry\_update * Drop python 2.7 support and testing * PDF documentation build 3.6.0 ----- * Fix python-openstackclient plugin doc build * Switch to Ussuri jobs * Send binary to websocket proxy * Fix a python3 issue in websocketclient * Make job zunclient-devstack-docker-sql as non-voting * Update master for stable/train 3.5.0 ----- * Bump api version to 1.37 3.4.0 ----- * Bump api\_version from 1.35 to 1.36 * Bump api\_version to 1.35 * Add Python 3 Train unit tests * Bump the openstackdocstheme extension to 1.20 * Blacklist sphinx 2.1.0 (autodoc bug) * Add Python 3 Train unit tests * Switch to the new canonical constraints URL on master * Sync Sphinx requirement * Replace git.openstack.org URLs with opendev.org URLs * OpenDev Migration Patch * Fix command parsing on legacy api version * Dropping the py35 testing * Update API version to 1.32 * Add '--registry' option on running container * Replace openstack.org git:// URLs with https:// * Update master for stable/stein 3.3.0 ----- * Update json module to jsonutils * add python 3.7 unit test job * Fix repository&tag when commit a container * Handle missing 'type' key on 'mounts' * Fix error message on mounts argument * Don't print extra output on waiting * Add support for registry API * Wait for the container to delete * Bump the api version to 1.29 * Fix a classname for GetDefaultQuota * Wait for the container to rebuild * Wait for the container to create * Fix KeyError when error\_body = body\_json['errors'][0] * Allow setting quota of other tenants * Several fixes to quota\_class osc module * Support cert and key file of mutual authentication for client * Change openstack-dev to openstack-discuss * Add Python 3.6 classifier to setup.cfg * Set HTTP header "Origin" when using websocket * Allow wss as websocket protocol 3.2.1 ----- * Fix setup.cfg Get and Update QuotaClass * Refactor the getid method base.py * Add documentation on the python API * Handle the case that a generic api version * Fix help message for action-list * Remove unused config for devstack job 3.2.0 ----- * Commit functional tests to stestr * Bump oslo.concurrency to 3.26.0 * Add job for checking the test coverage * Fix the 'tox -e cover' command * Add quotas and quota classes * Handle different endpoint format * Use template for lower constraints jobs 3.1.0 ----- * Disable devstack gate tempest in zunclient job * Update min tox version to 2.0 * Support API version discovery * Remove an empty line in .zuul.yaml * Encode injected file data in containers module 3.0.0 ----- * Encode injected file data (client-side) * Encode/Decode data when copying files * Don't change pyyaml behavior * Doc: Add appcontainer service enable and appcontainer service disable * Support file injection in CLI * add python 3.6 unit test job * switch documentation job to new PTI * Remove image 'host' parameter when delete image * import zuul job settings from project-config * Doc: Add appcontainer service list, appcontainer service delete and appcontainer service forcedown * Introduce an option to expose container ports * Do not send 'auto\_heal' if not specified * Doc: Add appcontainer image delete, appcontainer image search and appcontainer image pull * Fix container top command process 'NoneType' error * Add fixed\_ip to network\_attach * Doc: Add appcontainer image list and appcontainer image show * Handle StopIteration for Py3.7 * Delete incorrect comments * Doc: Add appcontainer host list and appcontainer host show * Deprecate security groups related commands * Support health check for Docker containers * Give extended privileges to the container * Support to update auto\_heal of container * Modify ca certificate parameter in the client * Update reno for stable/rocky * Doc: Add appcontainer action list and appcontainer action show 2.1.0 ----- * Change container execute top return * Change 'command' type from string to list * Remove testrepository * Add Cli support action list and show operation * Re-introduce gate\_hook.sh in zunclient 2.0.0 ----- * Fix container top command process 'NoneType' error * Introduce 'fixed\_ips' to network\_list * Support detach neutron port from container * Remove unused 'detail' parameter * Add image search default use driver 'docker' * Connect to websocket proxy for exec * Remove option --force in capsule delete * fix tox python3 overrides 1.4.0 ----- * Fix some formatting issues in help messages * Support listing availability zones * Switch to using stestr * Enhance network-attach command * Add 'host' parameter to image API * Change service args metavar hostname to host * Change container stats description information * Change container create image help message * Fix logger invocation in DeleteImage * Change host list display value * Enhance zun update command * Check restart and auto\_remove are setted for per container * Trivial: Update pypi url to new url * Introduce filters on listing containers * Heal non existent containers in docker * rebuild a container with a different image * Rename \`appcontainer update\` to \`appcontainer set\` * Add cli documents file * Updated from global requirements * add lower-constraints job 1.3.0 ----- * Add oslo.log into requirements.txt * Add network-list to OSC * Add image-delete to python-zunclient * Add --availability-zone option to container * Display availability zone for service * Minor changes in docs * zun rebuild on local node * Add network-list to zunclient 1.2.1 ----- * Updated from global requirements * Add image delete to zunclient OSC 1.2.0 ----- * Updated from global requirements * Remove the 'auto' network mode from help text * Avoid empty values in 'nets' options * Avoid empty values in 'mounts' options * Add assertions for container arguments * Updated from global requirements * Add image show for openstackclient * Move Capsule from Experimental API to v1 * Print image uuid instead of the whole dict * Updated from global requirements * Don't display 'Created At' for service * Changing logging to oslo\_log * Update python-zunclient README.rst's url links * Revert "Support the option volume\_binds of docker run" * Fix error about request validate for image\_search * Fix image\_search request * Update zunclient about image\_search * Zuul: Remove project name * add select.POLLNVAL event when poll register * Remove unavailable exception catch when get\_subcommand\_parser * Limit the amount of disk of container * Update reno for stable/queens 1.1.0 ----- * Updated from global requirements * Format container addresses about openstackclient * Supprt version discovery * Updated from global requirements * Updated from global requirements * Adds ZSH completion * Update the homepage url * Add cli for remove security group * Rename '--all-tenants' to '--all-projects' * Modify the Capsule creation opts field * Rename option '--rm' to '--auto-remove' 1.0.0 ----- * Updated from global requirements * Update new documentation PTI jobs * Add support for image show * Support the option volume\_binds of docker run * Rephrase the helptext of runtime parameter * Updated from global requirements * Add size option to "--mount" when run container * Updated add-security-group help text * Add image-search support * Revise the implementation in container exec\_resize * Remove the debug code in websocketclient * Support stop and delete container * Updated from global requirements * Updated from global requirements * Wait for container deletion in tests * Include zun-tempest-plugin as required project * Zuul: add file extension to playbook path * Remove the runtime constraint * Add argument 'mounts' to container * Generate stestr.conf * Cleanup the containers at zunclient funtional tests 0.5.0 ----- * Migrate to Zuulv3 * Enhance the README page * Consolidate API version in client * add capsule describe * Updated from global requirements * TrivialFix: the format of the annotation * Bring hostname option back * Updated from global requirements * Add attach network CLI * Add support for \`runtime\` parameter * Updated from global requirements * 'module' object has no attribute 'WebSocketBadStatusException' * Add capsule method in zunclient * Bump the api version to 1.7 * Show container network * Bump the api version to 1.6 * Updated from global requirements * [Refactor code] Update client v1 * [Refactor code] Backward compatibility * [Refactor code] Update shell * Updated from global requirements * [Refactor code] Support dynamic load Client module * Add detach network CLI side * Fix the default service type as container * Reserve links info when showing host info * Remove null parms form image\_list and host\_list * Align the style with Docker on commit * Remove unused CLI options * Fixed the incorrect metaver in OSC * Add host show CLI * Add CLI support for add\_security\_group * Add host list cli * Showing contain info after updating by osc command * Reserve links info when showing image * Add <> for metavar info * Fixed description in README.rst * Update reno for stable/pike 0.4.0 ----- * Updated from global requirements * Update the documentation link for doc migration * Autoremove container added to zun run client * Add warning-is-error in setup.cfg * [doc-migration] Move documents to their respective folders * Fixed the api version issue on OSC plugin * Add the parameter interactive in ExecContainer * Add uts for parse\_command 0.3.0 ----- * Remove deprecated parameter command in RunContainer * Re-enable osc tempest tests * Updated from global requirements * Rename parameter '--nets' to '--net' * Fix the typo that missing blank between words * Enhance api version support in CLI * Support all\_tenants in show and delete * Fixed wrap from taking negative values * Support api micro version in OSC * Add network options to zunclient * Reserve links info when showing a container by osc command * Updated from global requirements * switch to openstackdocstheme * Remove unused code from zunclient/common/apiclient * Updated from global requirements * Don't run zun server tempest tests in gate * Add security groups to container create/run * Upgrade from docker-py to docker * Make --profile load from environment variables * Add scheduler hints for zunclient * Updated from global requirements * Return image ID in container commit * OSC: return columns instead of using print\_dict * Zunclient should escape special character * Set concurrency to 1 for OSC tempest tests * Updated from global requirements * OSC support service api * Fix spelling mistake in osc client * Fix some spelling mistakes * Client support for service force-down * Add image-list and pull to osc * Revert "Add scheduler hint for zunclient" * Implement container snapshot * Add scheduler hint for zunclient * Updated from global requirements * Compile stats on server side * Improve style of zun pull * Client support for service-enable/disable * Replace assertRaisesRegexp with assertRaisesRegex * Make client work with websocket proxy * Remove the code that add uuid to websocket header * Replace the magic number with const * Client support for display snapshot of zun stats * Skip run OSC tests on unit tests * Updated from global requirements * Updated from global requirements * Client support for service delete * Revert file mode from 0755 to 0644 * Support interactive mode for exec command * Optimize the link address * Fix confusing error message on interactive run * Introduce API micro version * Add container uuid to the websocket connection header 0.2.0 ----- * Combine tty and stdin\_open in server side * Updated from global requirements * Rename 'reboot' to 'restart' * Reuse the existing zunclient in websocketclient * The result of "zun list" and "openstack appcontainer list" are different * Fix the os\_identity\_api\_version parameter * Revise the docstring of DeleteContainer * Combine the -i and -t option at run/create * Needn't check the RUNNING state in container\_create * Fix the description of functions * Make "command" parameter as positional * Support to kill multi-containers percommand by osc * List command in osc supports --all-tenants * Module has no attributes check\_container\_status * Consolidate websocket attach code * Correct the print in class CopyContainer * Add a functional test for rename command * Remove log translations * Fix typo in updatecontainer's help message * Add support for OSProfiler in client * Fix gate failure about container delete in functional test * Get "zun cp" command to work * Return the exit code of the executed command * Updated from global requirements * Removes utf-8 encoding and remove support for py34 * Setup coverage job in gate * Support the command "zun get-archive" and "zun put-archive" * Add --all-tenants option into zun list * Fixed an incorrect name of method call * Right websocket attach for interactive mode * Container logs is not good user experience * [Fix gate]Update test requirement * Fix typo about the helpinfo of zun top 0.1.0 ----- * Improve error message parsing * Add support for interactive mode for "run" command * Revise the functional env in tox.ini * Update in OSC needs \_remove\_null\_params * \_remove\_null\_params in OSC needs be synchronized * Add OSC Plugin for openstack appcontainer update * Handle various permutation of query params * Add more container related test * Update requirements * Use "--format" instead of "--json" when showing a container * Add image\_driver option to create and run * Generalize the usage of \_remove\_null\_params * Added stdout&stderr for openstack appcontainer logs * Add OSC commands functional test * Support the command "zun top" * Don't pass parameter to API when the parameters is null * Add support for attach detach and resize * Added support for stdout & stderr for logs in zunclient * Clean imports in code * Add post\_test\_hook.sh * Changed Optional to Positional arg in zun pull * Allow zun exec to take arbitrary number of arguments * Remove support for py33 * Update to match latest global-requirements * Add support for interactive mode in shell when zun run and create * "Zun reboot" supports the parameter "--timeout" * "Zun stop" supports the parameter "--timeout" * Remove white space between print () * Add support for restart policy in CLIS when create/run * Add OSC Plugin for openstack appcontainer rename * Add container-update command * Add OSC Plugin for openstack appcontainer run * Add OSC Plugin for openstack appcontainer stop * Support rename a container * Add OSC Plugin for openstack appcontainer kill * Add OSC Plugin for openstack appcontainer logs * Add OSC Plugin for openstack appcontainer exec * Corrected wrong env variable in --zun-api-version * Add support for killing multiple containers * Move out the "-i" and "--image" flags from shell * Add OSC Plugin for openstack appcontainer unpause * Add OSC Plugin for openstack appcontainer pause * Add OSC Plugin for openstack appcontainer start * zun exec syntax changed similar to docker * Add OSC Plugin for openstack appcontainer reboot * Move unit test cases under unit folder * Add OSC Plugin for openstack appcontainer delete * Add OSC Plugin for openstack appcontainer list * zun run URL changed to /v1/containers?run=true * Rename 'format\_labels' to 'format\_args' in OSC * Remove option --hostname on container creation * Change the help message for memory input * Add OSC Plugin for openstack appcontainer create * Rename method 'format\_labels' to 'format\_args' * Add OSC Plugin for openstack appcontainer show * --expose option removed as it is not supported * Added unit tests for zunclient/v1/containers.py * Fixed the '\_' is not defined error * Initial commit for openstack-client support in python-zunclient * Add auto-complete function for zun's commands * added run command support at client side * Add image command support in zunclient * Compatibility issue with environment variable * Add addresses column to containers * Add --image-pull-policy option to zun create command * Delete python bytecode file * Add docs and releasenotes testenv in tox * Adding files to .gitignore * Add coverage configuration * Add Python 3.5 classifier and venv * Add more details to service-list command * Add commandline for Zun kill * Added more properties to Container * Parse the provided container environment * Enable release notes translation * Changed the link to homepage * Create shortcut for frequently used options in command * Client support for --force option * Change HTTP method of \_action from PUT to POST * Cleanup tox.ini: Remove obsolete constraints * Enable Code Coverage * Changed the CLI style * Added containers command * Add base files and service-list command * Initial commit for zunclient * Added .gitreview python-zunclient-4.0.0/tox.ini0000664000175000017500000000533213643163457016421 0ustar zuulzuul00000000000000[tox] minversion = 3.1.1 envlist = py37,pep8 skipsdist = True ignore_basepython_conflict = True [testenv] basepython = python3 usedevelop = True install_command = pip install -U {opts} {packages} whitelist_externals = bash find rm setenv = VIRTUAL_ENV={envdir} deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = find . -type f -name "*.py[c|o]" -delete stestr run {posargs} stestr slowest [testenv:bandit] deps = -r{toxinidir}/test-requirements.txt commands = bandit -r zunclient -x tests -n5 -ll [testenv:pypy] deps = setuptools<3.2 -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt [testenv:debug] commands = oslo_debug_helper -t zunclient/tests {posargs} [testenv:debug-py34] basepython = python3.4 commands = oslo_debug_helper -t zunclient/tests {posargs} [testenv:docs] deps = -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt commands = sphinx-build -W -b html doc/source doc/build/html [testenv:pdf-docs] basepython = python3 envdir = {toxworkdir}/docs deps = {[testenv:docs]deps} whitelist_externals = make commands = sphinx-build -W -b latex doc/source doc/build/pdf make -C doc/build/pdf [testenv:pep8] commands = flake8 # Run security linter bandit -r zunclient -x tests -n5 -ll [testenv:releasenotes] deps = -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [testenv:venv] commands = {posargs} [testenv:cover] setenv = {[testenv]setenv} PYTHON=coverage run --source zunclient --parallel-mode commands = stestr run {posargs} coverage combine coverage html -d cover coverage xml -o cover/coverage.xml coverage report [flake8] # E123, E125 skipped as they are invalid PEP-8. show-source = True ignore = E123,E125,W503,W504 builtins = _ exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build [hacking] import_exceptions = zunclient._i18n [testenv:functional] commands = find . -type f -name "*.py[c|o]" -delete stestr run {posargs} setenv = {[testenv]setenv} OS_TEST_PATH = ./zunclient/tests/functional/osc/v1 # The OS_CACERT environment variable should be passed to the test # environments to specify a CA bundle file to use in verifying a # TLS (https) server certificate. passenv = OS_* [testenv:lower-constraints] deps = -c{toxinidir}/lower-constraints.txt -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt python-zunclient-4.0.0/zunclient/0000775000175000017500000000000013643163533017111 5ustar zuulzuul00000000000000python-zunclient-4.0.0/zunclient/shell.py0000664000175000017500000007573313643163457020616 0ustar zuulzuul00000000000000# Copyright 2014 # The Cloudscaling Group, 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. ### # This code is taken from python-novaclient. Goal is minimal modification. ### """ Command-line interface to the OpenStack Zun API. """ from __future__ import print_function import argparse import getpass import logging import os import sys from oslo_utils import encodeutils from oslo_utils import importutils from oslo_utils import strutils import six from zunclient import api_versions from zunclient import client as base_client from zunclient.common.apiclient import auth from zunclient.common import cliutils from zunclient import exceptions as exc from zunclient.i18n import _ from zunclient.v1 import shell as shell_v1 from zunclient import version profiler = importutils.try_import("osprofiler.profiler") HAS_KEYRING = False all_errors = ValueError try: import keyring HAS_KEYRING = True try: if isinstance(keyring.get_keyring(), keyring.backend.GnomeKeyring): import gnomekeyring all_errors = (ValueError, gnomekeyring.IOError, gnomekeyring.NoKeyringDaemonError) except Exception: pass except ImportError: pass DEFAULT_API_VERSION = api_versions.DEFAULT_API_VERSION DEFAULT_ENDPOINT_TYPE = 'publicURL' DEFAULT_SERVICE_TYPE = 'container' logger = logging.getLogger(__name__) def positive_non_zero_float(text): if text is None: return None try: value = float(text) except ValueError: msg = "%s must be a float" % text raise argparse.ArgumentTypeError(msg) if value <= 0: msg = "%s must be greater than 0" % text raise argparse.ArgumentTypeError(msg) return value class SecretsHelper(object): def __init__(self, args, client): self.args = args self.client = client self.key = None def _validate_string(self, text): if text is None or len(text) == 0: return False return True def _make_key(self): if self.key is not None: return self.key keys = [ self.client.auth_url, self.client.projectid, self.client.user, self.client.region_name, self.client.endpoint_type, self.client.service_type, self.client.service_name, self.client.volume_service_name, ] for (index, key) in enumerate(keys): if key is None: keys[index] = '?' else: keys[index] = str(keys[index]) self.key = "/".join(keys) return self.key def _prompt_password(self, verify=True): pw = None if hasattr(sys.stdin, 'isatty') and sys.stdin.isatty(): # Check for Ctl-D try: while True: pw1 = getpass.getpass('OS Password: ') if verify: pw2 = getpass.getpass('Please verify: ') else: pw2 = pw1 if pw1 == pw2 and self._validate_string(pw1): pw = pw1 break except EOFError: pass return pw def save(self, auth_token, management_url, tenant_id): if not HAS_KEYRING or not self.args.os_cache: return if (auth_token == self.auth_token and management_url == self.management_url): # Nothing changed.... return if not all([management_url, auth_token, tenant_id]): raise ValueError("Unable to save empty management url/auth token") value = "|".join([str(auth_token), str(management_url), str(tenant_id)]) keyring.set_password("zunclient_auth", self._make_key(), value) @property def password(self): if self._validate_string(self.args.os_password): return self.args.os_password verify_pass = ( strutils.bool_from_string(cliutils.env("OS_VERIFY_PASSWORD")) ) return self._prompt_password(verify_pass) @property def management_url(self): if not HAS_KEYRING or not self.args.os_cache: return None management_url = None try: block = keyring.get_password('zunclient_auth', self._make_key()) if block: _token, management_url, _tenant_id = block.split('|', 2) except all_errors: pass return management_url @property def auth_token(self): # Now is where it gets complicated since we # want to look into the keyring module, if it # exists and see if anything was provided in that # file that we can use. if not HAS_KEYRING or not self.args.os_cache: return None token = None try: block = keyring.get_password('zunclient_auth', self._make_key()) if block: token, _management_url, _tenant_id = block.split('|', 2) except all_errors: pass return token @property def tenant_id(self): if not HAS_KEYRING or not self.args.os_cache: return None tenant_id = None try: block = keyring.get_password('zunclient_auth', self._make_key()) if block: _token, _management_url, tenant_id = block.split('|', 2) except all_errors: pass return tenant_id class ZunClientArgumentParser(argparse.ArgumentParser): def __init__(self, *args, **kwargs): super(ZunClientArgumentParser, self).__init__(*args, **kwargs) def error(self, message): """error(message: string) Prints a usage message incorporating the message to stderr and exits. """ self.print_usage(sys.stderr) # FIXME(lzyeval): if changes occur in argparse.ArgParser._check_value choose_from = ' (choose from' progparts = self.prog.partition(' ') self.exit(2, "error: %(errmsg)s\nTry '%(mainp)s help %(subp)s'" " for more information.\n" % {'errmsg': message.split(choose_from)[0], 'mainp': progparts[0], 'subp': progparts[2]}) class OpenStackZunShell(object): def get_base_parser(self): parser = ZunClientArgumentParser( prog='zun', description=__doc__.strip(), epilog='See "zun help COMMAND" ' 'for help on a specific command.', add_help=False, formatter_class=OpenStackHelpFormatter, ) # Global arguments parser.add_argument('-h', '--help', action='store_true', help=argparse.SUPPRESS) parser.add_argument('--version', action='version', version=version.version_info.version_string()) parser.add_argument('--debug', default=False, action='store_true', help="Print debugging output.") parser.add_argument('--os-cache', default=strutils.bool_from_string( cliutils.env('OS_CACHE', default=False)), action='store_true', help="Use the auth token cache. Defaults to False " "if env[OS_CACHE] is not set.") parser.add_argument('--os-region-name', metavar='', default=os.environ.get('OS_REGION_NAME'), help='Region name. Default=env[OS_REGION_NAME].') # TODO(mattf) - add get_timings support to Client # parser.add_argument('--timings', # default=False, # action='store_true', # help="Print call timing info") # TODO(mattf) - use timeout # parser.add_argument('--timeout', # default=600, # metavar='', # type=positive_non_zero_float, # help="Set HTTP call timeout (in seconds)") parser.add_argument('--os-project-id', metavar='', default=cliutils.env('OS_PROJECT_ID', default=None), help='Defaults to env[OS_PROJECT_ID].') parser.add_argument('--os-project-name', metavar='', default=cliutils.env('OS_PROJECT_NAME', default=None), help='Defaults to env[OS_PROJECT_NAME].') parser.add_argument('--os-user-domain-id', metavar='', default=cliutils.env('OS_USER_DOMAIN_ID'), help='Defaults to env[OS_USER_DOMAIN_ID].') parser.add_argument('--os-user-domain-name', metavar='', default=cliutils.env('OS_USER_DOMAIN_NAME'), help='Defaults to env[OS_USER_DOMAIN_NAME].') parser.add_argument('--os-project-domain-id', metavar='', default=cliutils.env('OS_PROJECT_DOMAIN_ID'), help='Defaults to env[OS_PROJECT_DOMAIN_ID].') parser.add_argument('--os-project-domain-name', metavar='', default=cliutils.env('OS_PROJECT_DOMAIN_NAME'), help='Defaults to env[OS_PROJECT_DOMAIN_NAME].') parser.add_argument('--service-type', metavar='', help='Defaults to container for all ' 'actions.') parser.add_argument('--service_type', help=argparse.SUPPRESS) parser.add_argument('--endpoint-type', metavar='', default=cliutils.env( 'OS_ENDPOINT_TYPE', default=DEFAULT_ENDPOINT_TYPE), help='Defaults to env[OS_ENDPOINT_TYPE] or ' + DEFAULT_ENDPOINT_TYPE + '.') # NOTE(dtroyer): We can't add --endpoint_type here due to argparse # thinking usage-list --end is ambiguous; but it # works fine with only --endpoint-type present # Go figure. I'm leaving this here for doc purposes. # parser.add_argument('--endpoint_type', # help=argparse.SUPPRESS) parser.add_argument('--zun-api-version', metavar='', default=cliutils.env( 'ZUN_API_VERSION', default=DEFAULT_API_VERSION), help='Accepts X, X.Y (where X is major, Y is minor' ' part) or "X.latest", defaults to' ' env[ZUN_API_VERSION].') parser.add_argument('--zun_api_version', help=argparse.SUPPRESS) parser.add_argument('--os-cacert', metavar='', default=cliutils.env('OS_CACERT', default=None), help='Specify a CA bundle file to use in ' 'verifying a TLS (https) server certificate. ' 'Defaults to env[OS_CACERT].') parser.add_argument('--os-cert', metavar='', default=cliutils.env('OS_CERT', default=None), help='Specify a client certificate file (for ' 'client auth). ' 'Defaults to env[OS_CERT].') parser.add_argument('--os-key', metavar='', default=cliutils.env('OS_KEY', default=None), help='Specify a client certificate key file (for ' 'client auth). ' 'Defaults to env[OS_KEY].') parser.add_argument('--bypass-url', metavar='', default=cliutils.env('BYPASS_URL', default=None), dest='bypass_url', help="Use this API endpoint instead of the " "Service Catalog.") parser.add_argument('--bypass_url', help=argparse.SUPPRESS) parser.add_argument('--insecure', default=cliutils.env('ZUNCLIENT_INSECURE', default=False), action='store_true', help="Do not verify https connections") if profiler: parser.add_argument('--profile', metavar='HMAC_KEY', default=cliutils.env('OS_PROFILE', default=None), help='HMAC key to use for encrypting context ' 'data for performance profiling of ' 'operation. This key should be the ' 'value of the HMAC key configured for ' 'the OSprofiler middleware in zun; it ' 'is specified in the Zun configuration ' 'file at "/etc/zun/zun.conf". Without ' 'the key, profiling functions will not ' 'be triggered even if OSprofiler is ' 'enabled on the server side.') # The auth-system-plugins might require some extra options auth.load_auth_system_opts(parser) return parser def get_subcommand_parser(self, version, do_help=False): parser = self.get_base_parser() self.subcommands = {} subparsers = parser.add_subparsers(metavar='') actions_modules = shell_v1.COMMAND_MODULES for action_modules in actions_modules: self._find_actions(subparsers, action_modules, version, do_help) self._find_actions(subparsers, self, version, do_help) self._add_bash_completion_subparser(subparsers) return parser def _add_bash_completion_subparser(self, subparsers): subparser = ( subparsers.add_parser('bash_completion', add_help=False, formatter_class=OpenStackHelpFormatter) ) self.subcommands['bash_completion'] = subparser subparser.set_defaults(func=self.do_bash_completion) def _find_actions(self, subparsers, actions_module, version, do_help): msg = _(" (Supported by API versions '%(start)s' - '%(end)s')") for attr in (a for a in dir(actions_module) if a.startswith('do_')): # I prefer to be hyphen-separated instead of underscores. command = attr[3:].replace('_', '-') callback = getattr(actions_module, attr) desc = callback.__doc__ or '' if hasattr(callback, "versioned"): subs = api_versions.get_substitutions(callback) if do_help: desc += msg % {'start': subs[0].start_version.get_string(), 'end': subs[-1].end_version.get_string()} else: for versioned_method in subs: if version.matches(versioned_method.start_version, versioned_method.end_version): callback = versioned_method.func break else: continue action_help = desc.strip() exclusive_args = getattr(callback, 'exclusive_args', {}) arguments = getattr(callback, 'arguments', []) subparser = ( subparsers.add_parser(command, help=action_help, description=desc, add_help=False, formatter_class=OpenStackHelpFormatter) ) subparser.add_argument('-h', '--help', action='help', help=argparse.SUPPRESS,) self.subcommands[command] = subparser self._add_subparser_args(subparser, arguments, version, do_help, msg) self._add_subparser_exclusive_args(subparser, exclusive_args, version, do_help, msg) subparser.set_defaults(func=callback) def _add_subparser_exclusive_args(self, subparser, exclusive_args, version, do_help, msg): for group_name, arguments in exclusive_args.items(): if group_name == '__required__': continue required = exclusive_args['__required__'][group_name] exclusive_group = subparser.add_mutually_exclusive_group( required=required) self._add_subparser_args(exclusive_group, arguments, version, do_help, msg) def _add_subparser_args(self, subparser, arguments, version, do_help, msg): for (args, kwargs) in arguments: start_version = kwargs.get("start_version", None) if start_version: start_version = api_versions.APIVersion(start_version) end_version = kwargs.get("end_version", None) if end_version: end_version = api_versions.APIVersion(end_version) else: end_version = api_versions.APIVersion( "%s.latest" % start_version.ver_major) if do_help: kwargs["help"] = kwargs.get("help", "") + (msg % { "start": start_version.get_string(), "end": end_version.get_string()}) else: if not version.matches(start_version, end_version): continue kw = kwargs.copy() kw.pop("start_version", None) kw.pop("end_version", None) subparser.add_argument(*args, **kwargs) def setup_debugging(self, debug): if debug: streamformat = "%(levelname)s (%(module)s:%(lineno)d) %(message)s" # Set up the root logger to debug so that the submodules can # print debug messages logging.basicConfig(level=logging.DEBUG, format=streamformat) else: streamformat = "%(levelname)s %(message)s" logging.basicConfig(level=logging.CRITICAL, format=streamformat) def main(self, argv): # NOTE(Christoph Jansen): With Python 3.4 argv somehow becomes a Map. # This hack fixes it. argv = list(argv) # Parse args once to find version and debug settings parser = self.get_base_parser() (options, args) = parser.parse_known_args(argv) self.setup_debugging(options.debug) api_version = api_versions.get_api_version(options.zun_api_version) # NOTE(dtroyer): Hackery to handle --endpoint_type due to argparse # thinking usage-list --end is ambiguous; but it # works fine with only --endpoint-type present # Go figure. if '--endpoint_type' in argv: spot = argv.index('--endpoint_type') argv[spot] = '--endpoint-type' do_help = "help" in args subcommand_parser = self.get_subcommand_parser( api_version, do_help=do_help) self.parser = subcommand_parser if options.help or not argv: subcommand_parser.print_help() return 0 args = subcommand_parser.parse_args(argv) # Short-circuit and deal with help right away. # NOTE(jamespage): args.func is not guaranteed with python >= 3.4 if not hasattr(args, 'func') or args.func == self.do_help: self.do_help(args) return 0 elif args.func == self.do_bash_completion: self.do_bash_completion(args) return 0 (os_username, os_project_name, os_project_id, os_user_domain_id, os_user_domain_name, os_project_domain_id, os_project_domain_name, os_auth_url, os_auth_system, endpoint_type, service_type, bypass_url, insecure, os_cacert, os_cert, os_key) = ( (args.os_username, args.os_project_name, args.os_project_id, args.os_user_domain_id, args.os_user_domain_name, args.os_project_domain_id, args.os_project_domain_name, args.os_auth_url, args.os_auth_system, args.endpoint_type, args.service_type, args.bypass_url, args.insecure, args.os_cacert, args.os_cert, args.os_key) ) if os_auth_system and os_auth_system != "keystone": auth_plugin = auth.load_plugin(os_auth_system) else: auth_plugin = None # Fetched and set later as needed os_password = None if not endpoint_type: endpoint_type = DEFAULT_ENDPOINT_TYPE if not service_type: service_type = DEFAULT_SERVICE_TYPE # NA - there is only one service this CLI accesses # service_type = utils.get_service_type(args.func) or service_type # FIXME(usrleon): Here should be restrict for project id same as # for os_username or os_password but for compatibility it is not. if not cliutils.isunauthenticated(args.func): if auth_plugin: auth_plugin.parse_opts(args) if not auth_plugin or not auth_plugin.opts: if not os_username: raise exc.CommandError("You must provide a username " "via either --os-username or " "env[OS_USERNAME]") if not os_project_name and not os_project_id: raise exc.CommandError("You must provide a project name " "or project id via --os-project-name, " "--os-project-id, env[OS_PROJECT_NAME] " "or env[OS_PROJECT_ID]") if not os_auth_url: if os_auth_system and os_auth_system != 'keystone': os_auth_url = auth_plugin.get_auth_url() if not os_auth_url: raise exc.CommandError("You must provide an auth url " "via either --os-auth-url or " "env[OS_AUTH_URL] or specify an " "auth_system which defines a " "default url with --os-auth-system " "or env[OS_AUTH_SYSTEM]") # NOTE: The Zun client authenticates when you create it. So instead of # creating here and authenticating later, which is what the # novaclient does, we just create the client later. # Now check for the password/token of which pieces of the # identifying keyring key can come from the underlying client if not cliutils.isunauthenticated(args.func): # NA - Client can't be used with SecretsHelper if (auth_plugin and auth_plugin.opts and "os_password" not in auth_plugin.opts): use_pw = False else: use_pw = True if use_pw: # Auth using token must have failed or not happened # at all, so now switch to password mode and save # the token when its gotten... using our keyring # saver os_password = args.os_password if not os_password: raise exc.CommandError( 'Expecting a password provided via either ' '--os-password, env[OS_PASSWORD], or ' 'prompted response') client = base_client if not do_help: if api_version.is_latest(): # This client is just used to discover api version. # Version API needn't microversion, so we just pass # version 1.1 at here. self.cs = client.Client( version=api_versions.APIVersion("1.1"), username=os_username, password=os_password, project_id=os_project_id, project_name=os_project_name, user_domain_id=os_user_domain_id, user_domain_name=os_user_domain_name, project_domain_id=os_project_domain_id, project_domain_name=os_project_domain_name, auth_url=os_auth_url, service_type=service_type, region_name=args.os_region_name, endpoint_override=bypass_url, interface=endpoint_type, insecure=insecure, cacert=os_cacert) api_version = api_versions.discover_version(self.cs, api_version) min_version = api_versions.APIVersion(api_versions.MIN_API_VERSION) max_version = api_versions.APIVersion(api_versions.MAX_API_VERSION) if not api_version.matches(min_version, max_version): raise exc.CommandError( _("The specified version isn't supported by " "client. The valid version range is '%(min)s' " "to '%(max)s'") % { "min": min_version.get_string(), "max": max_version.get_string()} ) kwargs = {} if profiler: kwargs["profile"] = args.profile self.cs = client.Client(version=api_version, username=os_username, password=os_password, project_id=os_project_id, project_name=os_project_name, user_domain_id=os_user_domain_id, user_domain_name=os_user_domain_name, project_domain_id=os_project_domain_id, project_domain_name=os_project_domain_name, auth_url=os_auth_url, service_type=service_type, region_name=args.os_region_name, endpoint_override=bypass_url, interface=endpoint_type, insecure=insecure, cacert=os_cacert, cert=os_cert, key=os_key, **kwargs) args.func(self.cs, args) if profiler and args.profile: trace_id = profiler.get().get_base_id() print("To display trace use the command:\n\n" " osprofiler trace show --html %s " % trace_id) def _dump_timings(self, timings): class Tyme(object): def __init__(self, url, seconds): self.url = url self.seconds = seconds results = [Tyme(url, end - start) for url, start, end in timings] total = 0.0 for tyme in results: total += tyme.seconds results.append(Tyme("Total", total)) cliutils.print_list(results, ["url", "seconds"], sortby_index=None) def do_bash_completion(self, _args): """Prints arguments for bash-completion. Prints all of the commands and options to stdout so that the zun.bash_completion script doesn't have to hard code them. """ commands = set() options = set() for sc_str, sc in self.subcommands.items(): commands.add(sc_str) for option in sc._optionals._option_string_actions.keys(): options.add(option) commands.remove('bash-completion') commands.remove('bash_completion') print(' '.join(commands | options)) @cliutils.arg('command', metavar='', nargs='?', help='Display help for .') def do_help(self, args): """Display help about this program or one of its subcommands.""" # NOTE(jamespage): args.command is not guaranteed with python >= 3.4 command = getattr(args, 'command', '') if command: if args.command in self.subcommands: self.subcommands[args.command].print_help() else: raise exc.CommandError("'%s' is not a valid subcommand" % args.command) else: self.parser.print_help() # I'm picky about my shell help. class OpenStackHelpFormatter(argparse.HelpFormatter): def start_section(self, heading): # Title-case the headings heading = '%s%s' % (heading[0].upper(), heading[1:]) super(OpenStackHelpFormatter, self).start_section(heading) def main(): try: return OpenStackZunShell().main( map(encodeutils.safe_decode, sys.argv[1:])) except Exception as e: logger.debug(e, exc_info=1) print("ERROR: %s" % encodeutils.safe_encode(six.text_type(e)), file=sys.stderr) sys.exit(1) if __name__ == "__main__": sys.exit(main()) python-zunclient-4.0.0/zunclient/exceptions.py0000664000175000017500000000560713643163457021661 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. from zunclient.common.apiclient import exceptions from zunclient.common.apiclient.exceptions import * # noqa # NOTE(akurilin): This alias is left here since v.0.1.3 to support backwards # compatibility. InvalidEndpoint = exceptions.EndpointException CommunicationError = exceptions.ConnectionRefused HTTPBadRequest = exceptions.BadRequest HTTPInternalServerError = exceptions.InternalServerError HTTPNotFound = exceptions.NotFound HTTPServiceUnavailable = exceptions.ServiceUnavailable CommandErrorException = exceptions.CommandError class AmbiguousAuthSystem(exceptions.ClientException): """Could not obtain token and endpoint using provided credentials.""" pass # Alias for backwards compatibility AmbigiousAuthSystem = AmbiguousAuthSystem class InvalidAttribute(exceptions.ClientException): pass def from_response(response, message=None, traceback=None, method=None, url=None): """Return an HttpError instance based on response from httplib/requests.""" error_body = {} if message: error_body['message'] = message if traceback: error_body['details'] = traceback if hasattr(response, 'status') and not hasattr(response, 'status_code'): # NOTE(akurilin): These modifications around response object give # ability to get all necessary information in method `from_response` # from common code, which expecting response object from `requests` # library instead of object from `httplib/httplib2` library. response.status_code = response.status response.headers = { 'Content-Type': response.getheader('content-type', "")} if hasattr(response, 'status_code'): # NOTE(hongbin): This allows SessionClient to handle faultstring. response.json = lambda: {'error': error_body} if (response.headers.get('Content-Type', '').startswith('text/') and not hasattr(response, 'text')): # NOTE(clif_h): There seems to be a case in the # common.apiclient.exceptions module where if the # content-type of the response is text/* then it expects # the response to have a 'text' attribute, but that # doesn't always seem to necessarily be the case. # This is to work around that problem. response.text = '' return exceptions.from_response(response, method, url) python-zunclient-4.0.0/zunclient/i18n.py0000664000175000017500000000150613643163457020251 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 for zunclient. See https://docs.openstack.org/oslo.i18n/latest/user/usage.html. """ import oslo_i18n _translators = oslo_i18n.TranslatorFactory(domain='zunclient') # The primary translation function using the well-known name "_" _ = _translators.primary python-zunclient-4.0.0/zunclient/client.py0000664000175000017500000001103713643163457020750 0ustar zuulzuul00000000000000# Copyright (c) 2015 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. import warnings from oslo_utils import importutils from zunclient import api_versions osprofiler_profiler = importutils.try_import("osprofiler.profiler") def _get_client_class_and_version(version): if not isinstance(version, api_versions.APIVersion): version = api_versions.get_api_version(version) else: api_versions.check_major_version(version) return version, importutils.import_class( 'zunclient.v%s.client.Client' % version.ver_major) def _check_arguments(kwargs, release, deprecated_name, right_name=None): """Process deprecation of arguments. Check presence of deprecated argument in kwargs, prints proper warning message, renames key to right one it needed. """ if deprecated_name in kwargs: if right_name: if right_name in kwargs: msg = ('The %(old)s argument is deprecated in %(release)s' 'and its use may result in errors in future releases.' 'As %(new)s is provided, the %(old)s argument will ' 'be ignored.') % {'old': deprecated_name, 'release': release, 'new': right_name} kwargs.pop(deprecated_name) else: msg = ('The %(old)s argument is deprecated in %(release)s ' 'and its use may result in errors in future releases. ' 'Use %(new)s instead.') % {'old': deprecated_name, 'release': release, 'new': right_name} kwargs[right_name] = kwargs.pop(deprecated_name) else: msg = ('The %(old)s argument is deprecated in %(release)s ' 'and its use may result in errors in future ' 'releases') % {'old': deprecated_name, 'release': release} # NOTE(kiennt): just ignore it kwargs.pop(deprecated_name) warnings.warn(msg) def Client(version='1', username=None, auth_url=None, **kwargs): """Initialize client objects based on given version""" _check_arguments(kwargs, 'Queens', 'api_key', right_name='password') # NOTE: OpenStack projects use 2 vars with one meaning: `endpoint_type` # and `interface`. `endpoint_type` is an old name which was used by # most OpenStack clients. Later it was replaced by `interface` in # keystone and later some other clients switched to new var name too. _check_arguments(kwargs, 'Queens', 'endpoint_type', right_name='interface') _check_arguments(kwargs, 'Queens', 'zun_url', right_name='endpoint_override') _check_arguments(kwargs, 'Queens', 'tenant_name', right_name='project_name') _check_arguments(kwargs, 'Queens', 'tenant_id', right_name='project_id') profile = kwargs.pop('profile', None) if osprofiler_profiler and profile: # Initialize the root of the future trace: the created trace ID # will be used as the very first parent to which all related # traces will be bound to. The given HMAC key must correspond to # the one set in zun-api zun.conf, otherwise the latter # will fail to check the request signature and will skip # initialization of osprofiler on the server side. osprofiler_profiler.init(profile) api_version, client_class = _get_client_class_and_version(version) if api_version.is_latest(): c = client_class(api_version=api_versions.APIVersion("1.1"), auth_url=auth_url, username=username, **kwargs) api_version = api_versions.discover_version(c, api_version) return client_class(api_version=api_version, auth_url=auth_url, username=username, **kwargs) python-zunclient-4.0.0/zunclient/version.py0000664000175000017500000000123713643163457021160 0ustar zuulzuul00000000000000# Copyright 2014 # The Cloudscaling Group, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy # of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from pbr import version version_info = version.VersionInfo('python-zunclient') python-zunclient-4.0.0/zunclient/v1/0000775000175000017500000000000013643163533017437 5ustar zuulzuul00000000000000python-zunclient-4.0.0/zunclient/v1/shell.py0000664000175000017500000000242013643163457021123 0ustar zuulzuul00000000000000# Copyright 2014 # The Cloudscaling Group, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy # of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from zunclient.v1 import actions_shell from zunclient.v1 import availability_zones_shell from zunclient.v1 import capsules_shell from zunclient.v1 import containers_shell from zunclient.v1 import hosts_shell from zunclient.v1 import images_shell from zunclient.v1 import quota_classes_shell from zunclient.v1 import quotas_shell from zunclient.v1 import registries_shell from zunclient.v1 import services_shell from zunclient.v1 import versions_shell COMMAND_MODULES = [ availability_zones_shell, containers_shell, images_shell, services_shell, hosts_shell, versions_shell, capsules_shell, actions_shell, quotas_shell, quota_classes_shell, registries_shell, ] python-zunclient-4.0.0/zunclient/v1/capsules_shell.py0000664000175000017500000000745113643163457023033 0ustar zuulzuul00000000000000# Copyright 2017 Arm 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 yaml from oslo_serialization import jsonutils from zunclient.common import cliutils as utils from zunclient.common import template_utils from zunclient.common import utils as zun_utils from zunclient.i18n import _ def _show_capsule(capsule): zun_utils.format_container_addresses(capsule) utils.print_dict(capsule._info) @utils.arg('-f', '--template-file', metavar='', required=True, help=_('Path to the template.')) def do_capsule_create(cs, args): """Create a capsule.""" opts = {} if args.template_file: template = template_utils.get_template_contents( args.template_file) opts['template'] = template cs.capsules.create(**opts) print("Request to create capsule has been accepted.") @utils.arg('--all-projects', action="store_true", default=False, help='List containers in all projects') @utils.arg('--marker', metavar='', default=None, help='The last container UUID of the previous page; ' 'displays list of containers after "marker".') @utils.arg('--limit', metavar='', type=int, help='Maximum number of containers to return') @utils.arg('--sort-key', metavar='', help='Column to sort results by') @utils.arg('--sort-dir', metavar='', choices=['desc', 'asc'], help='Direction to sort. "asc" or "desc".') def do_capsule_list(cs, args): """Print a list of available capsules.""" opts = {} opts['all_projects'] = args.all_projects opts['marker'] = args.marker opts['limit'] = args.limit opts['sort_key'] = args.sort_key opts['sort_dir'] = args.sort_dir opts = zun_utils.remove_null_parms(**opts) capsules = cs.capsules.list(**opts) zun_utils.list_capsules(capsules) @utils.arg('capsules', metavar='', nargs='+', help='ID or name of the (capsule)s to delete.') def do_capsule_delete(cs, args): """Delete specified capsules.""" for capsule in args.capsules: try: cs.capsules.delete(capsule) print("Request to delete capsule %s has been accepted." % capsule) except Exception as e: print("Delete for capsule %(capsule)s failed: %(e)s" % {'capsule': capsule, 'e': e}) @utils.arg('capsule', metavar='', help='ID or name of the capsule to show.') @utils.arg('-f', '--format', metavar='', action='store', choices=['json', 'yaml', 'table'], default='table', help='Print representation of the capsule. ' 'The choices of the output format is json,table,yaml. ' 'Defaults to table. ') def do_capsule_describe(cs, args): """Show details of a capsule.""" capsule = cs.capsules.describe(args.capsule) if args.format == 'json': print(jsonutils.dumps(capsule._info, indent=4, sort_keys=True)) elif args.format == 'yaml': print(yaml.safe_dump(capsule._info, default_flow_style=False)) elif args.format == 'table': _show_capsule(capsule) python-zunclient-4.0.0/zunclient/v1/capsules.py0000664000175000017500000000641013643163457021636 0ustar zuulzuul00000000000000# Copyright 2017 Arm 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 zunclient.common import base from zunclient.common import utils from zunclient import exceptions CREATION_ATTRIBUTES = ['template'] class Capsule(base.Resource): def __repr__(self): return "" % self._info class CapsuleManager(base.Manager): resource_class = Capsule @staticmethod def _path(id=None): if id: return '/capsules/%s' % id else: return '/capsules/' def get(self, id): try: return self._list(self._path(id))[0] except IndexError: return None def create(self, **kwargs): new = {} for (key, value) in kwargs.items(): if key in CREATION_ATTRIBUTES: new[key] = value else: raise exceptions.InvalidAttribute( "Key must be in %s" % ','.join(CREATION_ATTRIBUTES)) return self._create(self._path(), new) def list(self, marker=None, limit=None, sort_key=None, sort_dir=None, all_projects=False): """Retrieve a list of capsules. :param all_projects: Optional, list containers in all projects :param marker: Optional, the UUID of a containers, eg the last containers from a previous result set. Return the next result set. :param limit: The maximum number of results to return per request, if: 1) limit > 0, the maximum number of containers to return. 2) limit param is NOT specified (None), the number of items returned respect the maximum imposed by the ZUN API (see Zun's api.max_limit option). :param sort_key: Optional, field used for sorting. :param sort_dir: Optional, direction of sorting, either 'asc' (the default) or 'desc'. :returns: A list of containers. """ if limit is not None: limit = int(limit) filters = utils.common_filters(marker, limit, sort_key, sort_dir, all_projects) path = '' if filters: path += '?' + '&'.join(filters) if limit is None: return self._list(self._path(path), "capsules") else: return self._list_pagination(self._path(path), "capsules", limit=limit) def delete(self, id): return self._delete(self._path(id)) def describe(self, id): try: return self._list(self._path(id))[0] except IndexError: return None python-zunclient-4.0.0/zunclient/v1/quotas_shell.py0000664000175000017500000000525413643163457022527 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. from zunclient.common import cliutils as utils @utils.arg( '--containers', metavar='', type=int, help='The number of containers allowed per project') @utils.arg( '--cpu', metavar='', type=int, help='The number of container cores or vCPUs allowed per project') @utils.arg( '--memory', metavar='', type=int, help='The number of megabytes of container RAM allowed per project') @utils.arg( '--disk', metavar='', type=int, help='The number of gigabytes of container Disk allowed per project') @utils.arg( 'project_id', metavar='', help='The UUID of project in a multi-project cloud') def do_quota_update(cs, args): """Print an updated quotas for a project""" utils.print_dict(cs.quotas.update(args.project_id, containers=args.containers, memory=args.memory, cpu=args.cpu, disk=args.disk)._info) @utils.arg( '--usages', default=False, action='store_true', help='Whether show quota usage statistic or not') @utils.arg( 'project_id', metavar='', help='The UUID of project in a multi-project cloud') def do_quota_get(cs, args): """Print a quotas for a project with usages (optional)""" if args.usages: utils.print_dict( cs.quotas.get(args.project_id, usages=args.usages)._info, value_fields=('limit', 'in_use')) else: utils.print_dict( cs.quotas.get(args.project_id, usages=args.usages)._info) @utils.arg( 'project_id', metavar='', help='The UUID of project in a multi-project cloud') def do_quota_defaults(cs, args): """Print a default quotas for a project""" utils.print_dict(cs.quotas.defaults(args.project_id)._info) @utils.arg( 'project_id', metavar='', help='The UUID of project in a multi-project cloud') def do_quota_delete(cs, args): """Delete quotas for a project""" cs.quotas.delete(args.project_id) python-zunclient-4.0.0/zunclient/v1/client.py0000664000175000017500000001451413643163457021301 0ustar zuulzuul00000000000000# Copyright 2014 # The Cloudscaling Group, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy # of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from keystoneauth1 import loading from keystoneauth1 import session as ksa_session from zunclient.common import httpclient from zunclient.v1 import actions from zunclient.v1 import availability_zones as az from zunclient.v1 import capsules from zunclient.v1 import containers from zunclient.v1 import hosts from zunclient.v1 import images from zunclient.v1 import quota_classes from zunclient.v1 import quotas from zunclient.v1 import registries from zunclient.v1 import services from zunclient.v1 import versions class Client(object): """Top-level object to access the OpenStack Container API.""" def __init__(self, api_version=None, auth_token=None, auth_type='password', auth_url=None, endpoint_override=None, interface='public', insecure=False, password=None, project_domain_id=None, project_domain_name=None, project_id=None, project_name=None, region_name=None, service_name=None, service_type='container', session=None, user_domain_id=None, user_domain_name=None, username=None, cacert=None, cert=None, key=None, **kwargs): """Initialization of Client object. :param api_version: Container API version :type api_version: zunclient.api_version.APIVersion :param str auth_token: Auth token :param str auth_url: Auth URL :param str auth_type: Auth Type :param str endpoint_override: Bypass URL :param str interface: Interface :param str insecure: Allow insecure :param str password: User password :param str project_domain_id: ID of project domain :param str project_domain_name: Nam of project domain :param str project_id: Project/Tenant ID :param str project_name: Project/Tenant Name :param str region_name: Region Name :param str service_name: Service Name :param str service_type: Service Type :param str session: Session :param str user_domain_id: ID of user domain :param str user_id: User ID :param str username: Username :param str cacert: CA certificate """ if endpoint_override and auth_token: auth_type = 'admin_token' session = None loader_kwargs = { 'token': auth_token, 'endpoint': endpoint_override } elif auth_token and not session: auth_type = 'token' loader_kwargs = { 'token': auth_token, 'auth_url': auth_url, 'project_domain_id': project_domain_id, 'project_domain_name': project_domain_name, 'project_id': project_id, 'project_name': project_name, 'user_domain_id': user_domain_id, 'user_domain_name': user_domain_name } else: loader_kwargs = { 'auth_url': auth_url, 'password': password, 'project_domain_id': project_domain_id, 'project_domain_name': project_domain_name, 'project_id': project_id, 'project_name': project_name, 'user_domain_id': user_domain_id, 'user_domain_name': user_domain_name, 'username': username, } # Backwards compatibility for people not passing in Session if session is None: loader = loading.get_plugin_loader(auth_type) # This should be able to handle v2 and v3 Keystone Auth auth_plugin = loader.load_from_options(**loader_kwargs) if cert and key: cert = cert, key session = ksa_session.Session(auth=auth_plugin, verify=(cacert or not insecure), cert=cert) client_kwargs = {} if not endpoint_override: try: # Trigger an auth error so that we can throw the exception # we always have session.get_endpoint( service_name=service_name, service_type=service_type, interface=interface, region_name=region_name ) except Exception: raise RuntimeError('Not authorized') else: client_kwargs = {'endpoint_override': endpoint_override} self.http_client = httpclient.SessionClient(service_type=service_type, service_name=service_name, interface=interface, region_name=region_name, session=session, api_version=api_version, **client_kwargs) self.containers = containers.ContainerManager(self.http_client) self.images = images.ImageManager(self.http_client) self.services = services.ServiceManager(self.http_client) self.hosts = hosts.HostManager(self.http_client) self.versions = versions.VersionManager(self.http_client) self.capsules = capsules.CapsuleManager(self.http_client) self.availability_zones = az.AvailabilityZoneManager(self.http_client) self.actions = actions.ActionManager(self.http_client) self.quotas = quotas.QuotaManager(self.http_client) self.quota_classes = quota_classes.QuotaClassManager(self.http_client) self.registries = registries.RegistryManager(self.http_client) @property def api_version(self): return self.http_client.api_version python-zunclient-4.0.0/zunclient/v1/services_shell.py0000664000175000017500000000546313643163457023040 0ustar zuulzuul00000000000000# Copyright 2015 NEC Corporation. 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. from zunclient.common import cliutils as utils from zunclient.common import utils as zun_utils def do_service_list(cs, args): """Print a list of zun services.""" services = cs.services.list() columns = ('Id', 'Host', 'Binary', 'State', 'Disabled', 'Disabled Reason', 'Updated At', 'Availability Zone') utils.print_list(services, columns, {'versions': zun_utils.print_list_field('versions')}) @utils.arg('host', metavar='', help='Name of host.') @utils.arg('binary', metavar='', help='Name of the binary to delete.') def do_service_delete(cs, args): """Delete the Zun binaries/services.""" try: cs.services.delete(args.host, args.binary) print("Request to delete binary %s on host %s has been accepted." % (args.binary, args.host)) except Exception as e: print("Delete for binary %(binary)s on host %(host)s failed: %(e)s" % {'binary': args.binary, 'host': args.host, 'e': e}) @utils.arg('host', metavar='', help='Name of host.') @utils.arg('binary', metavar='', help='Service binary.') def do_service_enable(cs, args): """Enable the Zun service.""" res = cs.services.enable(args.host, args.binary) utils.print_dict(res[1]['service']) @utils.arg('host', metavar='', help='Name of host.') @utils.arg('binary', metavar='', help='Service binary.') @utils.arg( '--reason', metavar='', help='Reason for disabling service.') def do_service_disable(cs, args): """Disable the Zun service.""" res = cs.services.disable(args.host, args.binary, args.reason) utils.print_dict(res[1]['service']) @utils.arg('host', metavar='', help='Name of host.') @utils.arg('binary', metavar='', help='Service binary.') @utils.arg( '--unset', dest='force_down', help="Unset the force state down of service.", action='store_false', default=True) def do_service_force_down(cs, args): """Force Zun service to down or unset the force state.""" res = cs.services.force_down(args.host, args.binary, args.force_down) utils.print_dict(res[1]['service']) python-zunclient-4.0.0/zunclient/v1/images.py0000664000175000017500000000717313643163457021273 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. from zunclient.common import base from zunclient.common import utils from zunclient import exceptions PULL_ATTRIBUTES = ['repo', 'host'] IMAGE_SEARCH_ATTRIBUTES = ['image', 'image_driver', 'exact_match'] class Image(base.Resource): def __repr__(self): return "" % self._info class ImageManager(base.Manager): resource_class = Image @staticmethod def _path(id=None): if id: return '/v1/images/%s' % id else: return '/v1/images/' def list(self, marker=None, limit=None, sort_key=None, sort_dir=None): """Retrieve a list of images. :param marker: Optional, the UUID of an image, eg the last image from a previous result set. Return the next result set. :param limit: The maximum number of results to return per request, if: 1) limit > 0, the maximum number of images to return. 2) limit param is NOT specified (None), the number of items returned respect the maximum imposed by the Zun api :param sort_key: Optional, field used for sorting. :param sort_dir: Optional, direction of sorting, either 'asc' (the default) or 'desc'. :returns: A list of images. """ if limit is not None: limit = int(limit) filters = utils.common_filters(marker, limit, sort_key, sort_dir) path = '' if filters: path += '?' + '&'.join(filters) if limit is None: return self._list(self._path(path), "images") else: return self._list_pagination(self._path(path), "images", limit=limit) def get(self, id): try: return self._list(self._path(id))[0] except IndexError: return None def create(self, **kwargs): new = {} for (key, value) in kwargs.items(): if key in PULL_ATTRIBUTES: new[key] = value else: raise exceptions.InvalidAttribute( "Key must be in %s" % ','.join(PULL_ATTRIBUTES)) return self._create(self._path(), new) def delete(self, image_id): """Delete an image :params image_id: uuid of the image. """ return self._delete(self._path(image_id)) def search_image(self, image, **kwargs): """Retrieves list of images based on image name and image_driver name :returns: A list of images based on the search query i.e., image_name & image_driver """ image_query = {} for (key, value) in kwargs.items(): if key in IMAGE_SEARCH_ATTRIBUTES: image_query[key] = value else: raise exceptions.InvalidAttribute( "Key must be in %s" % ','.join(IMAGE_SEARCH_ATTRIBUTES)) return self._search(self._path(image) + '/search', image_query) python-zunclient-4.0.0/zunclient/v1/availability_zones_shell.py0000664000175000017500000000152613643163457025101 0ustar zuulzuul00000000000000# Copyright 2015 NEC Corporation. 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. from zunclient.common import utils as zun_utils def do_availability_zone_list(cs, args): """Print a list of availability zones.""" zones = cs.availability_zones.list() zun_utils.list_availability_zones(zones) python-zunclient-4.0.0/zunclient/v1/images_shell.py0000664000175000017500000000667013643163457022463 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. from zunclient.common import cliutils as utils from zunclient.common import utils as zun_utils def _show_image(image): utils.print_dict(image._info) @utils.arg('image', metavar='', help='Name of the image') @utils.arg('host', metavar='', help='Name or UUID of the host') def do_pull(cs, args): """Pull an image into a host.""" opts = {} opts['repo'] = args.image opts['host'] = args.host _show_image(cs.images.create(**opts)) @utils.arg('--marker', metavar='', default=None, help='The last image UUID of the previous page; ' 'displays list of images after "marker".') @utils.arg('--limit', metavar='', type=int, help='Maximum number of images to return') @utils.arg('--sort-key', metavar='', help='Column to sort results by') @utils.arg('--sort-dir', metavar='', choices=['desc', 'asc'], help='Direction to sort. "asc" or "desc".') def do_image_list(cs, args): """Print a list of available images.""" opts = {} opts['marker'] = args.marker opts['limit'] = args.limit opts['sort_key'] = args.sort_key opts['sort_dir'] = args.sort_dir opts = zun_utils.remove_null_parms(**opts) images = cs.images.list(**opts) columns = ('uuid', 'image_id', 'repo', 'tag', 'size') utils.print_list(images, columns, {'versions': zun_utils.print_list_field('versions')}, sortby_index=None) @utils.arg('id', metavar='', help='UUID of image to describe.') def do_image_show(cs, args): """Describe a specific image.""" image = cs.images.get(args.id) _show_image(image) @utils.arg('id', metavar='', help='UUID of image to delete') def do_image_delete(cs, args): """Delete a specified image.""" opts = {} opts['image_id'] = args.id cs.images.delete(**opts) @utils.arg('image', metavar='', help='Name of the image') @utils.arg('--image_driver', metavar='', choices=['glance', 'docker'], default='docker', help='Name of the image driver (glance, docker)') @utils.arg('--exact-match', default=False, action='store_true', help='exact match image name') def do_image_search(cs, args): """Print list of available images from repository based on user query.""" opts = {} opts['image_driver'] = args.image_driver opts['exact_match'] = args.exact_match images = cs.images.search_image(args.image, **opts) columns = ('ID', 'Name', 'Tags', 'Status', 'Size', 'Metadata') utils.print_list(images, columns, {'versions': zun_utils.print_list_field('versions')}, sortby_index=None) python-zunclient-4.0.0/zunclient/v1/versions_shell.py0000664000175000017500000000212513643163457023055 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. from zunclient import api_versions from zunclient.common import cliutils as utils def do_version_list(cs, args): """List all API versions.""" print("Client supported API versions:") print("Minimum version %(v)s" % {'v': api_versions.MIN_API_VERSION}) print("Maximum version %(v)s" % {'v': api_versions.MAX_API_VERSION}) print("\nServer supported API versions:") result = cs.versions.list() columns = ["Id", "Status", "Min Version", "Max Version"] utils.print_list(result, columns) python-zunclient-4.0.0/zunclient/v1/containers_shell.py0000664000175000017500000011617013643163457023360 0ustar zuulzuul00000000000000# Copyright 2015 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import argparse from contextlib import closing import io import os import tarfile import time import yaml from oslo_serialization import jsonutils from zunclient.common import cliutils as utils from zunclient.common import utils as zun_utils from zunclient.common.websocketclient import exceptions from zunclient.common.websocketclient import websocketclient from zunclient import exceptions as exc RENAME_DEPRECATION_MESSAGE = ( 'WARNING: Rename container command deprecated and will be removed ' 'in a future release.\nUse Update container command to avoid ' 'seeing this message.') SG_DEPRECATION_MESSAGE = ( 'WARNING: Security group related commands deprecated and will be removed ' 'in a future release.\nUse Neutron commands to manage security groups ' 'instead.') def _show_container(container): zun_utils.format_container_addresses(container) utils.print_dict(container._info) @utils.exclusive_arg( 'restart_auto_remove', '--auto-remove', required=False, action='store_true', help='Automatically remove the container when it exits') @utils.exclusive_arg( 'restart_auto_remove', '--restart', required=False, metavar='', help='Restart policy to apply when a container exits' '(no, on-failure[:max-retry], always, unless-stopped)') @utils.exclusive_arg( 'secgroup_expose_port', '--security-group', metavar='', action='append', default=[], help='The name of security group for the container. ' 'May be used multiple times.') @utils.exclusive_arg( 'secgroup_expose_port', '-p', '--expose-port', action='append', default=[], metavar='', help='Expose container port(s) to outside (format: [/])') @utils.arg('-n', '--name', metavar='', help='name of the container') @utils.arg('--cpu', metavar='', help='The number of virtual cpus.') @utils.arg('-m', '--memory', metavar='', help='The container memory size in MiB') @utils.arg('-e', '--environment', metavar='', action='append', default=[], help='The environment variables') @utils.arg('--workdir', metavar='', help='The working directory for commands to run in') @utils.arg('--label', metavar='', action='append', default=[], help='Adds a map of labels to a container. ' 'May be used multiple times.') @utils.arg('--image-pull-policy', dest='image_pull_policy', metavar='', choices=['never', 'always', 'ifnotpresent'], help='The policy which determines if the image should ' 'be pulled prior to starting the container. ' 'It can have following values: ' '"ifnotpresent": only pull the image if it does not ' 'already exist on the node. ' '"always": Always pull the image from repository.' '"never": never pull the image') @utils.arg('image', metavar='', help='name or ID or repo of the image ' '(e.g. cirros:latest)') @utils.arg('-i', '--interactive', dest='interactive', action='store_true', default=False, help='Keep STDIN open even if not attached, allocate a pseudo-TTY') @utils.arg('--image-driver', metavar='', help='The image driver to use to pull container image. ' 'It can have following values: ' '"docker": pull the image from Docker Hub. ' '"glance": pull the image from Glance. ') @utils.arg('command', metavar='', nargs=argparse.REMAINDER, help='Send command to the container') @utils.arg('--hint', action='append', default=[], metavar='', help='The key-value pair(s) for scheduler to select host. ' 'The format of this parameter is "key=value[,key=value]". ' 'May be used multiple times.') @utils.arg('--net', action='append', default=[], metavar='', help='Create network enpoints for the container. ' 'network: attach container to the specified neturon networks. ' 'port: attach container to the neutron port with this UUID. ' 'v4-fixed-ip: IPv4 fixed address for container. ' 'v6-fixed-ip: IPv6 fixed address for container.') @utils.arg('--mount', action='append', default=[], metavar='', help='A dictionary to configure volumes mounted inside the ' 'container.') @utils.arg('--runtime', metavar='', help='The runtime to use for this container. ' 'It can have value "runc" or any other custom runtime.') @utils.arg('--hostname', metavar='', default=None, help='Container host name') @utils.arg('--disk', metavar='', type=int, default=None, help='The disk size in GiB for per container.') @utils.arg('--availability-zone', metavar='', default=None, help='The availability zone of the container.') @utils.arg('--auto-heal', action='store_true', default=False, help='The flag of healing non-existent container in docker.') @utils.arg('--privileged', dest='privileged', action='store_true', default=False, help='Give extended privileges to this container') @utils.arg('--healthcheck', action='append', default=[], metavar='', help='Specify a test cmd to perform to check that the container' 'is healthy. ' 'cmd: Command to run to check health. ' 'interval: Time between running the check (s|m|h)' ' (default 0s). ' 'retries: Consecutive failures needed to report unhealthy. ' 'timeout: Maximum time to allow one check to run (s|m|h)' ' (default 0s).') @utils.arg('--registry', metavar='', help='The container image registry ID or name') @utils.arg('--host', metavar='', help='Requested host to create containers. Admin only by default.' '(Supported by API versions 1.39 or above)') @utils.arg('--entrypoint', metavar='', help='The entrypoint which overwrites the default ENTRYPOINT ' 'of the image. (Supported by API versions 1.40 or above)') def do_create(cs, args): """Create a container.""" opts = {} opts['name'] = args.name opts['image'] = args.image opts['memory'] = args.memory opts['cpu'] = args.cpu opts['environment'] = zun_utils.format_args(args.environment) opts['auto_remove'] = args.auto_remove opts['workdir'] = args.workdir opts['labels'] = zun_utils.format_args(args.label) opts['image_pull_policy'] = args.image_pull_policy opts['image_driver'] = args.image_driver opts['hints'] = zun_utils.format_args(args.hint) opts['nets'] = zun_utils.parse_nets(args.net) opts['mounts'] = zun_utils.parse_mounts(args.mount) opts['runtime'] = args.runtime opts['hostname'] = args.hostname opts['disk'] = args.disk opts['availability_zone'] = args.availability_zone opts['command'] = args.command opts['registry'] = args.registry opts['host'] = args.host opts['entrypoint'] = zun_utils.parse_entrypoint(args.entrypoint) if args.healthcheck: opts['healthcheck'] = zun_utils.parse_health(args.healthcheck) if args.auto_heal: opts['auto_heal'] = args.auto_heal if args.security_group: opts['security_groups'] = args.security_group if args.expose_port: opts['exposed_ports'] = zun_utils.parse_exposed_ports(args.expose_port) if args.restart: opts['restart_policy'] = zun_utils.check_restart_policy(args.restart) if args.interactive: opts['interactive'] = True if args.privileged: opts['privileged'] = True opts = zun_utils.remove_null_parms(**opts) _show_container(cs.containers.create(**opts)) @utils.arg('--all-projects', action="store_true", default=False, help='List containers in all projects') @utils.arg('--marker', metavar='', default=None, help='The last container UUID of the previous page; ' 'displays list of containers after "marker".') @utils.arg('--limit', metavar='', type=int, help='Maximum number of containers to return') @utils.arg('--sort-key', metavar='', help='Column to sort results by') @utils.arg('--sort-dir', metavar='', choices=['desc', 'asc'], help='Direction to sort. "asc" or "desc".') @utils.arg('--name', metavar='', help='List containers according to their name.') @utils.arg('--image', metavar='', help='List containers according to their image.') @utils.arg('--project-id', metavar='', help='List containers according to their Project_id') @utils.arg('--user-id', metavar='', help='List containers according to their user_id') @utils.arg('--task-state', metavar='', help='List containers according to their task-state') @utils.arg('--status', metavar='', help='List containers according to their status') @utils.arg('--memory', metavar='', help='List containers according to their memory size in MiB') @utils.arg('--host', metavar='', help='List containers according to their hostname') @utils.arg('--auto-remove', metavar='', help='List containers according to whether they are ' 'auto-removed on exiting') def do_list(cs, args): """Print a list of available containers.""" opts = {} opts['all_projects'] = args.all_projects opts['marker'] = args.marker opts['limit'] = args.limit opts['sort_key'] = args.sort_key opts['sort_dir'] = args.sort_dir opts['image'] = args.image opts['name'] = args.name opts['project_id'] = args.project_id opts['user_id'] = args.user_id opts['host'] = args.host opts['task_state'] = args.task_state opts['memory'] = args.memory opts['auto_remove'] = args.auto_remove opts['status'] = args.status opts = zun_utils.remove_null_parms(**opts) containers = cs.containers.list(**opts) zun_utils.list_containers(containers) @utils.arg('containers', metavar='', nargs='+', help='ID or name of the (container)s to delete.') @utils.arg('-f', '--force', action='store_true', help='Force delete the container.') @utils.arg('-s', '--stop', action='store_true', help='Stop the running container first before delete.') @utils.arg('--all-projects', action="store_true", default=False, help='Delete container(s) in all projects by name.') def do_delete(cs, args): """Delete specified containers.""" for container in args.containers: opts = {} opts['id'] = container opts['force'] = args.force opts['stop'] = args.stop opts['all_projects'] = args.all_projects opts = zun_utils.remove_null_parms(**opts) try: cs.containers.delete(**opts) print("Request to delete container %s has been accepted." % container) except Exception as e: print("Delete for container %(container)s failed: %(e)s" % {'container': container, 'e': e}) @utils.arg('container', metavar='', help='ID or name of the container to show.') @utils.arg('-f', '--format', metavar='', action='store', choices=['json', 'yaml', 'table'], default='table', help='Print representation of the container.' 'The choices of the output format is json,table,yaml.' 'Defaults to table.') @utils.arg('--all-projects', action="store_true", default=False, help='Show container(s) in all projects by name.') def do_show(cs, args): """Show details of a container.""" opts = {} opts['id'] = args.container opts['all_projects'] = args.all_projects opts = zun_utils.remove_null_parms(**opts) container = cs.containers.get(**opts) if args.format == 'json': print(jsonutils.dumps(container._info, indent=4, sort_keys=True)) elif args.format == 'yaml': print(yaml.safe_dump(container._info, default_flow_style=False)) elif args.format == 'table': _show_container(container) @utils.arg('containers', metavar='', nargs='+', help='ID of the (container)s to rebuild.') @utils.arg('--image', metavar='', help='The image for specified container to update.') @utils.arg('--image-driver', metavar='', help='The image driver to use to pull container image. ' 'It can have following values: ' '"docker": pull the image from Docker Hub. ' '"glance": pull the image from Glance. ' 'The default value is source container\'s image driver ') def do_rebuild(cs, args): """Rebuild specified containers.""" for container in args.containers: opts = {} opts['id'] = container if args.image: opts['image'] = args.image if args.image_driver: opts['image_driver'] = args.image_driver try: cs.containers.rebuild(**opts) print("Request to rebuild container %s has been accepted." % container) except Exception as e: print("Rebuild for container %(container)s failed: %(e)s" % {'container': container, 'e': e}) @utils.arg('containers', metavar='', nargs='+', help='ID or name of the (container)s to restart.') @utils.arg('-t', '--timeout', metavar='', default=10, help='Seconds to wait for stop before restarting (container)s') def do_restart(cs, args): """Restart specified containers.""" for container in args.containers: try: cs.containers.restart(container, args.timeout) print("Request to restart container %s has been accepted." % container) except Exception as e: print("Restart for container %(container)s failed: %(e)s" % {'container': container, 'e': e}) @utils.arg('containers', metavar='', nargs='+', help='ID or name of the (container)s to stop.') @utils.arg('-t', '--timeout', metavar='', default=10, help='Seconds to wait for stop before killing (container)s') def do_stop(cs, args): """Stop specified containers.""" for container in args.containers: try: cs.containers.stop(container, args.timeout) print("Request to stop container %s has been accepted." % container) except Exception as e: print("Stop for container %(container)s failed: %(e)s" % {'container': container, 'e': e}) @utils.arg('containers', metavar='', nargs='+', help='ID of the (container)s to start.') def do_start(cs, args): """Start specified containers.""" for container in args.containers: try: cs.containers.start(container) print("Request to start container %s has been accepted." % container) except Exception as e: print("Start for container %(container)s failed: %(e)s" % {'container': container, 'e': e}) @utils.arg('containers', metavar='', nargs='+', help='ID or name of the (container)s to pause.') def do_pause(cs, args): """Pause specified containers.""" for container in args.containers: try: cs.containers.pause(container) print("Request to pause container %s has been accepted." % container) except Exception as e: print("Pause for container %(container)s failed: %(e)s" % {'container': container, 'e': e}) @utils.arg('containers', metavar='', nargs='+', help='ID or name of the (container)s to unpause.') def do_unpause(cs, args): """Unpause specified containers.""" for container in args.containers: try: cs.containers.unpause(container) print("Request to unpause container %s has been accepted." % container) except Exception as e: print("Unpause for container %(container)s failed: %(e)s" % {'container': container, 'e': e}) @utils.arg('container', metavar='', help='ID or name of the container to get logs for.') @utils.arg('--stdout', action='store_true', help='Only stdout logs of container.') @utils.arg('--stderr', action='store_true', help='Only stderr logs of container.') @utils.arg('--since', metavar='', default=None, help='Show logs since a given datetime or integer ' 'epoch (in seconds).') @utils.arg('-t', '--timestamps', dest='timestamps', action='store_true', default=False, help='Show timestamps.') @utils.arg('--tail', metavar='', default='all', help='Number of lines to show from the end of the logs.') def do_logs(cs, args): """Get logs of a container.""" opts = {} opts['id'] = args.container opts['stdout'] = args.stdout opts['stderr'] = args.stderr opts['since'] = args.since opts['timestamps'] = args.timestamps opts['tail'] = args.tail opts = zun_utils.remove_null_parms(**opts) logs = cs.containers.logs(**opts) print(logs) @utils.arg('container', metavar='', help='ID or name of the container to execute command in.') @utils.arg('command', metavar='', nargs=argparse.REMAINDER, help='The command to execute in a container') @utils.arg('-i', '--interactive', dest='interactive', action='store_true', default=False, help='Keep STDIN open and allocate a pseudo-TTY for interactive') def do_exec(cs, args): """Execute command in a running container.""" opts = {} opts['command'] = zun_utils.parse_command(args.command) if args.interactive: opts['interactive'] = True opts['run'] = False response = cs.containers.execute(args.container, **opts) if args.interactive: exec_id = response['exec_id'] url = response['proxy_url'] websocketclient.do_exec(cs, url, args.container, exec_id, "~", 0.5) else: output = response['output'] exit_code = response['exit_code'] print(output) return exit_code @utils.arg('containers', metavar='', nargs='+', help='ID or name of the (container)s to kill signal to.') @utils.arg('-s', '--signal', metavar='', default=None, help='The signal to kill') def do_kill(cs, args): """Kill one or more running container(s).""" for container in args.containers: opts = {} opts['id'] = container opts['signal'] = args.signal opts = zun_utils.remove_null_parms(**opts) try: cs.containers.kill(**opts) print( "Request to kill signal to container %s has been accepted." % container) except Exception as e: print( "kill signal for container %(container)s failed: %(e)s" % {'container': container, 'e': e}) @utils.exclusive_arg( 'restart_auto_remove', '--auto-remove', required=False, action='store_true', help='Automatically remove the container when it exits') @utils.exclusive_arg( 'restart_auto_remove', '--restart', required=False, metavar='', help='Restart policy to apply when a container exits' '(no, on-failure[:max-retry], always, unless-stopped)') @utils.exclusive_arg( 'secgroup_expose_port', '--security-group', metavar='', action='append', default=[], help='The name of security group for the container. ' 'May be used multiple times.') @utils.exclusive_arg( 'secgroup_expose_port', '-p', '--expose-port', action='append', default=[], metavar='', help='Expose container port(s) to outside (format: [/])') @utils.arg('-n', '--name', metavar='', help='name of the container') @utils.arg('--cpu', metavar='', help='The number of virtual cpus.') @utils.arg('-m', '--memory', metavar='', help='The container memory size in MiB') @utils.arg('-e', '--environment', metavar='', action='append', default=[], help='The environment variables') @utils.arg('--workdir', metavar='', help='The working directory for commands to run in') @utils.arg('--label', metavar='', action='append', default=[], help='Adds a map of labels to a container. ' 'May be used multiple times.') @utils.arg('--image-pull-policy', dest='image_pull_policy', metavar='', choices=['never', 'always', 'ifnotpresent'], help='The policy which determines if the image should ' 'be pulled prior to starting the container. ' 'It can have following values: ' '"ifnotpresent": only pull the image if it does not ' 'already exist on the node. ' '"always": Always pull the image from repository.' '"never": never pull the image') @utils.arg('image', metavar='', help='name or ID of the image') @utils.arg('-i', '--interactive', dest='interactive', action='store_true', default=False, help='Keep STDIN open even if not attached, allocate a pseudo-TTY') @utils.arg('--image-driver', metavar='', help='The image driver to use to pull container image. ' 'It can have following values: ' '"docker": pull the image from Docker Hub. ' '"glance": pull the image from Glance. ') @utils.arg('command', metavar='', nargs=argparse.REMAINDER, help='Send command to the container') @utils.arg('--hint', action='append', default=[], metavar='', help='The key-value pair(s) for scheduler to select host. ' 'The format of this parameter is "key=value[,key=value]". ' 'May be used multiple times.') @utils.arg('--net', action='append', default=[], metavar='', help='Create network enpoints for the container. ' 'network: attach container to the specified neutron networks. ' 'port: attach container to the neutron port with this UUID. ' 'v4-fixed-ip: IPv4 fixed address for container. ' 'v6-fixed-ip: IPv6 fixed address for container.') @utils.arg('--mount', action='append', default=[], metavar='', help='A dictionary to configure volumes mounted inside the ' 'container.') @utils.arg('--runtime', metavar='', help='The runtime to use for this container. ' 'It can have value "runc" or any other custom runtime.') @utils.arg('--hostname', metavar='', default=None, help='Container hostname') @utils.arg('--disk', metavar='', type=int, default=None, help='The disk size in GiB for per container.') @utils.arg('--availability-zone', metavar='', default=None, help='The availability zone of the container.') @utils.arg('--auto-heal', action='store_true', default=False, help='The flag of healing non-existent container in docker.') @utils.arg('--privileged', dest='privileged', action='store_true', default=False, help='Give extended privileges to this container') @utils.arg('--healthcheck', action='append', default=[], metavar='', help='Specify a test cmd to perform to check that the container' 'is healthy. ' 'cmd: Command to run to check health. ' 'interval: Time between running the check (s|m|h)' ' (default 0s). ' 'retries: Consecutive failures needed to report unhealthy. ' 'timeout: Maximum time to allow one check to run (s|m|h)' ' (default 0s).') @utils.arg('--registry', metavar='', help='The container image registry ID or name') @utils.arg('--host', metavar='', help='Requested host to run containers. Admin only by default.' '(Supported by API versions 1.39 or above)') @utils.arg('--entrypoint', metavar='', help='The entrypoint which overwrites the default ENTRYPOINT ' 'of the image. (Supported by API versions 1.40 or above)') def do_run(cs, args): """Run a command in a new container.""" opts = {} opts['name'] = args.name opts['image'] = args.image opts['memory'] = args.memory opts['cpu'] = args.cpu opts['environment'] = zun_utils.format_args(args.environment) opts['workdir'] = args.workdir opts['auto_remove'] = args.auto_remove opts['labels'] = zun_utils.format_args(args.label) opts['image_pull_policy'] = args.image_pull_policy opts['image_driver'] = args.image_driver opts['hints'] = zun_utils.format_args(args.hint) opts['nets'] = zun_utils.parse_nets(args.net) opts['mounts'] = zun_utils.parse_mounts(args.mount) opts['runtime'] = args.runtime opts['hostname'] = args.hostname opts['disk'] = args.disk opts['availability_zone'] = args.availability_zone opts['command'] = args.command opts['registry'] = args.registry opts['host'] = args.host opts['entrypoint'] = zun_utils.parse_entrypoint(args.entrypoint) if args.healthcheck: opts['healthcheck'] = zun_utils.parse_health(args.healthcheck) if args.auto_heal: opts['auto_heal'] = args.auto_heal if args.security_group: opts['security_groups'] = args.security_group if args.expose_port: opts['exposed_ports'] = zun_utils.parse_exposed_ports(args.expose_port) if args.restart: opts['restart_policy'] = zun_utils.check_restart_policy(args.restart) if args.interactive: opts['interactive'] = True if args.privileged: opts['privileged'] = True opts = zun_utils.remove_null_parms(**opts) container = cs.containers.run(**opts) _show_container(container) container_uuid = getattr(container, 'uuid', None) if args.interactive: ready_for_attach = False while True: container = cs.containers.get(container_uuid) if zun_utils.check_container_status(container, 'Running'): ready_for_attach = True break if zun_utils.check_container_status(container, 'Error'): raise exceptions.ContainerStateError(container_uuid) print("Waiting for container start") time.sleep(1) if ready_for_attach is True: response = cs.containers.attach(container_uuid) websocketclient.do_attach(cs, response, container_uuid, "~", 0.5) else: raise exceptions.InvalidWebSocketLink(container_uuid) @utils.arg('container', metavar='', help="ID or name of the container to update.") @utils.arg('--cpu', metavar='', help='The number of virtual cpus.') @utils.arg('-m', '--memory', metavar='', help='The container memory size in MiB') @utils.arg('--name', metavar='', help='The new name for the container') @utils.exclusive_arg( 'auto_heal_value', '--auto-heal', required=False, action='store_true', help='Automatic recovery the status of contaier') @utils.exclusive_arg( 'auto_heal_value', '--no-auto-heal', required=False, action='store_true', help='Needless recovery the status of contaier') def do_update(cs, args): """Update one or more attributes of the container.""" opts = {} opts['memory'] = args.memory opts['cpu'] = args.cpu opts['name'] = args.name if 'auto_heal' in args and args.auto_heal: opts['auto_heal'] = True if 'no_auto_heal' in args and args.no_auto_heal: opts['auto_heal'] = False opts = zun_utils.remove_null_parms(**opts) if not opts: raise exc.CommandError("You must update at least one property") container = cs.containers.update(args.container, **opts) _show_container(container) @utils.deprecated(RENAME_DEPRECATION_MESSAGE) @utils.arg('container', metavar='', help='ID or name of the container to rename.') @utils.arg('name', metavar='', help='The new name for the container') def do_rename(cs, args): """Rename a container.""" cs.containers.rename(args.container, args.name) @utils.arg('container', metavar='', help='ID or name of the container to be attached to.') def do_attach(cs, args): """Attach to a running container.""" response = cs.containers.attach(args.container) websocketclient.do_attach(cs, response, args.container, "~", 0.5) @utils.arg('container', metavar='', help='ID or name of the container to display processes.') @utils.arg('--pid', metavar='', action='append', default=[], help='The args of the ps id.') def do_top(cs, args): """Display the running processes inside the container.""" if args.pid: # List container single ps id top result output = cs.containers.top(args.container, ' '.join(args.pid)) else: # List container all processes top result output = cs.containers.top(args.container) for titles in output['Titles']: print("%-20s") % titles, if output['Processes']: for process in output['Processes']: print("") for info in process: print("%-20s") % info, else: print("") @utils.arg('source', metavar='', help='The source should be copied to the container or localhost. ' 'The format of this parameter is [container:]src_path.') @utils.arg('destination', metavar='', help='The directory destination where save the source. ' 'The format of this parameter is [container:]dest_path.') def do_cp(cs, args): """Copy files/tars between a container and the local filesystem.""" if ':' in args.source: source_parts = args.source.split(':', 1) container_id = source_parts[0] container_path = source_parts[1] opts = {} opts['id'] = container_id opts['path'] = container_path res = cs.containers.get_archive(**opts) dest_path = args.destination tardata = io.BytesIO(res['data']) with closing(tarfile.open(fileobj=tardata)) as tar: tar.extractall(dest_path) elif ':' in args.destination: dest_parts = args.destination.split(':', 1) container_id = dest_parts[0] container_path = dest_parts[1] filename = os.path.split(args.source)[1] opts = {} opts['id'] = container_id opts['path'] = container_path tardata = io.BytesIO() with closing(tarfile.open(fileobj=tardata, mode='w')) as tar: tar.add(args.source, arcname=filename) opts['data'] = tardata.getvalue() cs.containers.put_archive(**opts) else: print("Please check the parameters for zun copy!") print("Usage:") print("zun cp container:src_path dest_path|-") print("zun cp src_path|- container:dest_path") @utils.arg('container', metavar='', help='ID or name of the container to display stats.') def do_stats(cs, args): """Display stats of the container.""" stats_info = cs.containers.stats(args.container) utils.print_dict(stats_info) @utils.arg('container', metavar='', help='ID or name of the container to commit.') @utils.arg('repository', metavar='[:]', help='The repository and tag of the image.') def do_commit(cs, args): """Create a new image from a container's changes.""" opts = zun_utils.check_commit_container_args(args) opts = zun_utils.remove_null_parms(**opts) try: image = cs.containers.commit(args.container, **opts) print("Request to commit container %s has been accepted. " "The image is %s." % (args.container, image['uuid'])) except Exception as e: print("Commit for container %(container)s failed: %(e)s" % {'container': args.container, 'e': e}) @utils.deprecated(SG_DEPRECATION_MESSAGE) @utils.arg('container', metavar='', help='ID or name of the container to add security group.') @utils.arg('security_group', metavar='', help='Security group ID or name for specified container.') def do_add_security_group(cs, args): """Add security group for specified container.""" opts = {} opts['id'] = args.container opts['security_group'] = args.security_group opts = zun_utils.remove_null_parms(**opts) try: cs.containers.add_security_group(**opts) print("Request to add security group for container %s " "has been accepted." % args.container) except Exception as e: print("Add security group for container %(container)s " "failed: %(e)s" % {'container': args.container, 'e': e}) @utils.exclusive_arg( 'detach_network_port', '--network', metavar='', help='The neutron network that container will detach from.') @utils.exclusive_arg( 'detach_network_port', '--port', metavar='', help='The neutron port that container will detach from.') @utils.arg('container', metavar='', help='ID or name of the container to detach the network.') def do_network_detach(cs, args): """Detach a network from the container.""" opts = {} opts['container'] = args.container opts['network'] = args.network opts['port'] = args.port opts = zun_utils.remove_null_parms(**opts) try: cs.containers.network_detach(**opts) print("Request to detach network from container %s " "has been accepted." % args.container) except Exception as e: print("Detach network from container %(container)s " "failed: %(e)s" % {'container': args.container, 'e': e}) @utils.arg('container', metavar='', help='ID or name of the container to attach network.') @utils.arg('--network', metavar='', help='The neutron network that container will attach to.') @utils.arg('--port', metavar='', help='The neutron port that container will attach to.') @utils.arg('--fixed-ip', metavar='', help='The fixed-ip that container will attach to.') def do_network_attach(cs, args): """Attach a network to the container.""" opts = {} opts['container'] = args.container opts['network'] = args.network opts['port'] = args.port opts['fixed_ip'] = args.fixed_ip opts = zun_utils.remove_null_parms(**opts) try: cs.containers.network_attach(**opts) print("Request to attach network to container %s " "has been accepted." % args.container) except Exception as e: print("Attach network to container %(container)s " "failed: %(e)s" % {'container': args.container, 'e': e}) @utils.arg('container', metavar='', help='ID or name of the container to display network info.') def do_network_list(cs, args): """List networks on a container""" opts = {} opts['container'] = args.container opts = zun_utils.remove_null_parms(**opts) networks = cs.containers.network_list(**opts) zun_utils.list_container_networks(networks) @utils.deprecated(SG_DEPRECATION_MESSAGE) @utils.arg('container', metavar='', help='ID or name of the container to remove security group.') @utils.arg('security_group', metavar='', help='The security group to remove from specified container.') def do_remove_security_group(cs, args): """Remove security group for specified container.""" opts = {} opts['id'] = args.container opts['security_group'] = args.security_group opts = zun_utils.remove_null_parms(**opts) try: cs.containers.remove_security_group(**opts) print("Request to remove security group for container %s " "has been accepted." % args.container) except Exception as e: print("Remove security group for container %(container)s " "failed: %(e)s" % {'container': args.container, 'e': e}) python-zunclient-4.0.0/zunclient/v1/availability_zones.py0000664000175000017500000000203413643163457023705 0ustar zuulzuul00000000000000# Copyright 2014 NEC Corporation. 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. from zunclient.common import base class AvailabilityZone(base.Resource): def __repr__(self): return "" % self._info class AvailabilityZoneManager(base.Manager): resource_class = AvailabilityZone @staticmethod def _path(): return '/v1/availability_zones' def list(self, **kwargs): return self._list(self._path(), "availability_zones", qparams=kwargs) python-zunclient-4.0.0/zunclient/v1/containers.py0000664000175000017500000002300713643163457022165 0ustar zuulzuul00000000000000# Copyright 2014 NEC Corporation. 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. from six.moves.urllib import parse from zunclient import api_versions from zunclient.common import base from zunclient.common import utils from zunclient import exceptions CREATION_ATTRIBUTES = ['name', 'image', 'command', 'cpu', 'memory', 'environment', 'workdir', 'labels', 'image_pull_policy', 'restart_policy', 'interactive', 'image_driver', 'security_groups', 'hints', 'nets', 'auto_remove', 'runtime', 'hostname', 'mounts', 'disk', 'availability_zone', 'auto_heal', 'privileged', 'exposed_ports', 'healthcheck', 'registry', 'tty', 'host', 'entrypoint'] class Container(base.Resource): def __repr__(self): return "" % self._info class ContainerManager(base.Manager): resource_class = Container @staticmethod def _path(id=None): if id: return '/v1/containers/%s' % id else: return '/v1/containers' def list(self, marker=None, limit=None, sort_key=None, sort_dir=None, all_projects=False, **kwargs): """Retrieve a list of containers. :param all_projects: Optional, list containers in all projects :param marker: Optional, the UUID of a containers, eg the last containers from a previous result set. Return the next result set. :param limit: The maximum number of results to return per request, if: 1) limit > 0, the maximum number of containers to return. 2) limit param is NOT specified (None), the number of items returned respect the maximum imposed by the ZUN API (see Zun's api.max_limit option). :param sort_key: Optional, field used for sorting. :param sort_dir: Optional, direction of sorting, either 'asc' (the default) or 'desc'. :returns: A list of containers. """ if limit is not None: limit = int(limit) filters = utils.common_filters(marker, limit, sort_key, sort_dir, all_projects) path = '' if filters: path += '?' + '&'.join(filters) if limit is None: return self._list(self._path(path), "containers", qparams=kwargs) else: return self._list_pagination(self._path(path), "containers", limit=limit) def get(self, id, **kwargs): try: return self._list(self._path(id), qparams=kwargs)[0] except IndexError: return None def create(self, **kwargs): self._process_command(kwargs) self._process_mounts(kwargs) self._process_tty(kwargs) new = {} for (key, value) in kwargs.items(): if key in CREATION_ATTRIBUTES: new[key] = value else: raise exceptions.InvalidAttribute( "Key must be in %s" % ','.join(CREATION_ATTRIBUTES)) return self._create(self._path(), new) def _process_command(self, kwargs): cmd_microversion = api_versions.APIVersion("1.20") if self.api_version < cmd_microversion: command = kwargs.pop('command', None) if command: kwargs['command'] = utils.parse_command(command) def _process_mounts(self, kwargs): mounts = kwargs.get('mounts', None) if mounts: for mount in mounts: if mount.get('type') == 'bind': mount['source'] = utils.encode_file_data(mount['source']) def _process_tty(self, kwargs): tty_microversion = api_versions.APIVersion("1.36") if self.api_version >= tty_microversion: if 'interactive' in kwargs and 'tty' not in kwargs: kwargs['tty'] = kwargs['interactive'] def delete(self, id, **kwargs): return self._delete(self._path(id), qparams=kwargs) def _action(self, id, action, method='POST', qparams=None, **kwargs): if qparams: action = "%s?%s" % (action, parse.urlencode(qparams)) kwargs.setdefault('headers', {}) kwargs['headers'].setdefault('Content-Length', '0') resp, body = self.api.json_request(method, self._path(id) + action, **kwargs) return resp, body def start(self, id): return self._action(id, '/start') def stop(self, id, timeout): return self._action(id, '/stop', qparams={'timeout': timeout}) def rebuild(self, id, **kwargs): return self._action(id, '/rebuild', qparams=kwargs) def restart(self, id, timeout): return self._action(id, '/reboot', qparams={'timeout': timeout}) def pause(self, id): return self._action(id, '/pause') def unpause(self, id): return self._action(id, '/unpause') def logs(self, id, **kwargs): if kwargs['stdout'] is False and kwargs['stderr'] is False: kwargs['stdout'] = True kwargs['stderr'] = True return self._action(id, '/logs', method='GET', qparams=kwargs)[1] def execute(self, id, **kwargs): return self._action(id, '/execute', qparams=kwargs)[1] def execute_resize(self, id, exec_id, width, height): self._action(id, '/execute_resize', qparams={'exec_id': exec_id, 'w': width, 'h': height})[1] def kill(self, id, signal=None): return self._action(id, '/kill', qparams={'signal': signal})[1] def run(self, **kwargs): self._process_command(kwargs) self._process_mounts(kwargs) self._process_tty(kwargs) if not set(kwargs).issubset(CREATION_ATTRIBUTES): raise exceptions.InvalidAttribute( "Key must be in %s" % ','.join(CREATION_ATTRIBUTES)) else: return self._create(self._path() + '?run=true', kwargs) def rename(self, id, name): return self._action(id, '/rename', qparams={'name': name}) def update(self, id, **patch): return self._update(self._path(id), patch) def attach(self, id): return self._action(id, '/attach', method='GET')[1] def resize(self, id, width, height): return self._action(id, '/resize', qparams={'w': width, 'h': height})[1] def top(self, id, ps_args=None): return self._action(id, '/top', method='GET', qparams={'ps_args': ps_args})[1] def get_archive(self, id, path): res = self._action(id, '/get_archive', method='GET', qparams={'path': path})[1] # API version 1.25 or later will return Base64-encoded data if self.api_version >= api_versions.APIVersion("1.25"): res['data'] = utils.decode_file_data(res['data']) else: res['data'] = res['data'].encode() return res def put_archive(self, id, path, data): # API version 1.25 or later will expect Base64-encoded data if self.api_version >= api_versions.APIVersion("1.25"): data = utils.encode_file_data(data) return self._action(id, '/put_archive', qparams={'path': path}, body={'data': data}) def stats(self, id): return self._action(id, '/stats', method='GET')[1] def commit(self, id, repository, tag=None): if tag is not None: return self._action(id, '/commit', qparams={ 'repository': repository, 'tag': tag})[1] else: return self._action(id, '/commit', qparams={ 'repository': repository})[1] def add_security_group(self, id, security_group): return self._action(id, '/add_security_group', qparams={'name': security_group}) def network_detach(self, container, **kwargs): return self._action(container, '/network_detach', qparams=kwargs) def network_attach(self, container, **kwargs): return self._action(container, '/network_attach', qparams=kwargs) def network_list(self, container): return self._list(self._path(container) + '/network_list', "networks") def remove_security_group(self, id, security_group): return self._action(id, '/remove_security_group', qparams={'name': security_group}) python-zunclient-4.0.0/zunclient/v1/registries.py0000664000175000017500000000664613643163457022212 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. from zunclient.common import base from zunclient.common import utils from zunclient import exceptions CREATION_ATTRIBUTES = ['name', 'domain', 'username', 'password'] class Registry(base.Resource): def __repr__(self): return "" % self._info class RegistryManager(base.Manager): resource_class = Registry @staticmethod def _path(id=None): if id: return '/v1/registries/%s' % id else: return '/v1/registries' def list(self, marker=None, limit=None, sort_key=None, sort_dir=None, all_projects=False, **kwargs): """Retrieve a list of registries. :param all_projects: Optional, list registries in all projects :param marker: Optional, the UUID of a registries, eg the last registries from a previous result set. Return the next result set. :param limit: The maximum number of results to return per request, if: 1) limit > 0, the maximum number of registries to return. 2) limit param is NOT specified (None), the number of items returned respect the maximum imposed by the ZUN API (see Zun's api.max_limit option). :param sort_key: Optional, field used for sorting. :param sort_dir: Optional, direction of sorting, either 'asc' (the default) or 'desc'. :returns: A list of registries. """ if limit is not None: limit = int(limit) filters = utils.common_filters(marker, limit, sort_key, sort_dir, all_projects) path = '' if filters: path += '?' + '&'.join(filters) if limit is None: return self._list(self._path(path), "registries", qparams=kwargs) else: return self._list_pagination(self._path(path), "registries", limit=limit) def get(self, id, **kwargs): try: return self._list(self._path(id), qparams=kwargs)[0] except IndexError: return None def create(self, **kwargs): new = {'registry': {}} for (key, value) in kwargs.items(): if key in CREATION_ATTRIBUTES: new['registry'][key] = value else: raise exceptions.InvalidAttribute( "Key must be in %s" % ','.join(CREATION_ATTRIBUTES)) return self._create(self._path(), new) def delete(self, id, **kwargs): return self._delete(self._path(id), qparams=kwargs) def update(self, id, **patch): kwargs = {'registry': patch} return self._update(self._path(id), kwargs) python-zunclient-4.0.0/zunclient/v1/services.py0000664000175000017500000000773113643163457021651 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. from six.moves.urllib import parse from zunclient.common import base from zunclient.common import utils class Service(base.Resource): def __repr__(self): return "" % self._info class ServiceManager(base.Manager): resource_class = Service @staticmethod def _path(id=None): return '/v1/services/%s' % id if id else '/v1/services' def list(self, marker=None, limit=None, sort_key=None, sort_dir=None): """Retrieve list of zun services. :param marker: Optional, the ID of a zun service, eg the last services from a previous result set. Return the next result set. :param limit: The maximum number of results to return per request, if: 1) limit > 0, the maximum number of services to return. 2) limit param is NOT specified (None), the number of items returned respect the maximum imposed by the Zun API (see Zun's api.max_limit option). :param sort_key: Optional, field used for sorting. :param sort_dir: Optional, direction of sorting, either 'asc' (the default) or 'desc'. :returns: A list of services. """ if limit is not None: limit = int(limit) filters = utils.common_filters(marker, limit, sort_key, sort_dir) path = '' if filters: path += '?' + '&'.join(filters) if limit is None: return self._list(self._path(path), "services") else: return self._list_pagination(self._path(path), "services", limit=limit) def delete(self, host, binary): """Delete a service.""" return self._delete(self._path(), qparams={'host': host, 'binary': binary}) def _action(self, action, method='PUT', qparams=None, **kwargs): if qparams: action = "%s?%s" % (action, parse.urlencode(qparams)) kwargs.setdefault('headers', {}) kwargs['headers'].setdefault('Content-Length', '0') resp, body = self.api.json_request(method, self._path() + action, **kwargs) return resp, body def _update_body(self, host, binary, disabled_reason=None, force_down=None): body = {"host": host, "binary": binary} if disabled_reason is not None: body["disabled_reason"] = disabled_reason if force_down is not None: body["forced_down"] = force_down return body def enable(self, host, binary): """Enable the service specified by hostname and binary.""" body = self._update_body(host, binary) return self._action("/enable", qparams=body) def disable(self, host, binary, reason=None): """Disable the service specified by hostname and binary.""" body = self._update_body(host, binary, reason) return self._action("/disable", qparams=body) def force_down(self, host, binary, force_down=None): """Force service state to down specified by hostname and binary.""" body = self._update_body(host, binary, force_down=force_down) return self._action("/force_down", qparams=body) python-zunclient-4.0.0/zunclient/v1/hosts.py0000664000175000017500000000456713643163457021172 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. from zunclient.common import base from zunclient.common import utils class Host(base.Resource): def __repr__(self): return "" % self._info class HostManager(base.Manager): resource_class = Host @staticmethod def _path(id=None): if id: return '/v1/hosts/%s' % id else: return '/v1/hosts/' def list(self, marker=None, limit=None, sort_key=None, sort_dir=None): """Retrieve a list of hosts. :param marker: Optional, the UUID of an host, eg the last host from a previous result set. Return the next result set. :param limit: The maximum number of results to return per request, if: 1) limit > 0, the maximum number of hosts to return. 2) limit param is NOT specified (None), the number of items returned respect the maximum imposed by the Zun api :param sort_key: Optional, field used for sorting. :param sort_dir: Optional, direction of sorting, either 'asc' (the default) or 'desc'. :returns: A list of hosts. """ if limit is not None: limit = int(limit) filters = utils.common_filters(marker, limit, sort_key, sort_dir) path = '' if filters: path += '?' + '&'.join(filters) if limit is None: return self._list(self._path(path), "hosts") else: return self._list_pagination(self._path(path), "hosts", limit=limit) def get(self, id): try: return self._list(self._path(id))[0] except IndexError: return None python-zunclient-4.0.0/zunclient/v1/versions.py0000664000175000017500000000347113643163457021673 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. from six.moves import urllib from zunclient.common import base class Version(base.Resource): def __repr__(self): return "" class VersionManager(base.Manager): resource_class = Version def list(self): endpoint = self.api.get_endpoint() url = urllib.parse.urlparse(endpoint) # NOTE(hongbin): endpoint URL has at least 2 formats: # 1. the classic (legacy) endpoint: # http://{host}:{optional_port}/v1/ # 2. under wsgi: # http://{host}:{optional_port}/container/v1 if url.path.endswith("v1") or "/v1/" in url.path: # this way should handle all 2 possible formats path = url.path[:url.path.rfind("/v1")] version_url = '%s://%s%s' % (url.scheme, url.netloc, path) else: # NOTE(hongbin): probably, it is one of the next cases: # * https://container.example.com/ # * https://example.com/container # leave as is without cropping. version_url = endpoint return self._list(version_url, "versions") def get_current(self): for version in self.list(): if version.status == "CURRENT": return version return None python-zunclient-4.0.0/zunclient/v1/quotas.py0000664000175000017500000000336113643163457021335 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. from zunclient.common import base class Quota(base.Resource): def __repr__(self): return "" % self._info class QuotaManager(base.Manager): resource_class = Quota @staticmethod def _path(project_id): if project_id is not None: return '/v1/quotas/{}'.format(project_id) return '/v1/quotas' def get(self, project_id, **kwargs): if not kwargs.get('usages'): kwargs = {} return self._list(self._path(project_id), qparams=kwargs)[0] def update(self, project_id, containers=None, memory=None, cpu=None, disk=None): resources = {} if cpu is not None: resources['cpu'] = cpu if memory is not None: resources['memory'] = memory if containers is not None: resources['containers'] = containers if disk is not None: resources['disk'] = disk return self._update(self._path(project_id), resources, method='PUT') def defaults(self, project_id): return self._list(self._path(project_id) + '/defaults')[0] def delete(self, project_id): return self._delete(self._path(project_id)) python-zunclient-4.0.0/zunclient/v1/registries_shell.py0000664000175000017500000001406713643163457023375 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. import yaml from oslo_serialization import jsonutils from zunclient.common import cliutils as utils from zunclient.common import utils as zun_utils from zunclient import exceptions as exc def _show_registry(registry): utils.print_dict(registry._info['registry']) @utils.arg('--name', metavar='', help='The name of the registry.') @utils.arg('--username', metavar='', help='The username to login to the registry.') @utils.arg('--password', metavar='', help='The password to login to the registry.') @utils.arg('--domain', metavar='', required=True, help='The domain of the registry.') def do_registry_create(cs, args): """Create a registry.""" opts = {} opts['name'] = args.name opts['domain'] = args.domain opts['username'] = args.username opts['password'] = args.password opts = zun_utils.remove_null_parms(**opts) _show_registry(cs.registries.create(**opts)) @utils.arg('--all-projects', action="store_true", default=False, help='List registries in all projects') @utils.arg('--marker', metavar='', default=None, help='The last registry UUID of the previous page; ' 'displays list of registries after "marker".') @utils.arg('--limit', metavar='', type=int, help='Maximum number of registries to return') @utils.arg('--sort-key', metavar='', help='Column to sort results by') @utils.arg('--sort-dir', metavar='', choices=['desc', 'asc'], help='Direction to sort. "asc" or "desc".') @utils.arg('--name', metavar='', help='List registries according to their name.') @utils.arg('--domain', metavar='', help='List registries according to their domain.') @utils.arg('--project-id', metavar='', help='List registries according to their Project_id') @utils.arg('--user-id', metavar='', help='List registries according to their user_id') @utils.arg('--username', metavar='', help='List registries according to their username') def do_registry_list(cs, args): """Print a list of available registries.""" opts = {} opts['all_projects'] = args.all_projects opts['marker'] = args.marker opts['limit'] = args.limit opts['sort_key'] = args.sort_key opts['sort_dir'] = args.sort_dir opts['domain'] = args.domain opts['name'] = args.name opts['project_id'] = args.project_id opts['user_id'] = args.user_id opts['username'] = args.username opts = zun_utils.remove_null_parms(**opts) registries = cs.registries.list(**opts) columns = ('uuid', 'name', 'domain', 'username', 'password') utils.print_list(registries, columns, sortby_index=None) @utils.arg('registries', metavar='', nargs='+', help='ID or name of the (registry)s to delete.') def do_registry_delete(cs, args): """Delete specified registries.""" for registry in args.registries: opts = {} opts['id'] = registry opts = zun_utils.remove_null_parms(**opts) try: cs.registries.delete(**opts) print("Request to delete registry %s has been accepted." % registry) except Exception as e: print("Delete for registry %(registry)s failed: %(e)s" % {'registry': registry, 'e': e}) @utils.arg('registry', metavar='', help='ID or name of the registry to show.') @utils.arg('-f', '--format', metavar='', action='store', choices=['json', 'yaml', 'table'], default='table', help='Print representation of the container.' 'The choices of the output format is json,table,yaml.' 'Defaults to table.') def do_registry_show(cs, args): """Show details of a registry.""" opts = {} opts['id'] = args.registry opts = zun_utils.remove_null_parms(**opts) registry = cs.registries.get(**opts) if args.format == 'json': print(jsonutils.dumps(registry._info, indent=4, sort_keys=True)) elif args.format == 'yaml': print(yaml.safe_dump(registry._info, default_flow_style=False)) elif args.format == 'table': _show_registry(registry) @utils.arg('registry', metavar='', help="ID or name of the registry to update.") @utils.arg('--username', metavar='', help='The new username for the registry.') @utils.arg('--password', metavar='', help='The new password for the registry.') @utils.arg('--domain', metavar='', help='The new domain for the registry.') @utils.arg('--name', metavar='', help='The new name for the registry') def do_registry_update(cs, args): """Update one or more attributes of the registry.""" opts = {} opts['username'] = args.username opts['password'] = args.password opts['domain'] = args.domain opts['name'] = args.name opts = zun_utils.remove_null_parms(**opts) if not opts: raise exc.CommandError("You must update at least one property") registry = cs.registries.update(args.registry, **opts) _show_registry(registry) python-zunclient-4.0.0/zunclient/v1/actions_shell.py0000664000175000017500000000317013643163457022646 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. from zunclient.common import cliutils as utils from zunclient.common import utils as zun_utils def _show_action(action): utils.print_dict(action._info) @utils.arg('container', metavar='', help='ID or name of a container.') def do_action_list(cs, args): """Print a list of actions done on a container.""" container = args.container actions = cs.actions.list(container) columns = ('user_id', 'container_uuid', 'request_id', 'action', 'message', 'start_time') utils.print_list(actions, columns, {'versions': zun_utils.print_list_field('versions')}, sortby_index=None) @utils.arg('container', metavar='', help='ID or name of the container whose actions are showed.') @utils.arg('request_id', metavar='', help='request ID of action to describe.') def do_action_show(cs, args): """Describe a specific action.""" action = cs.actions.get(args.container, args.request_id) _show_action(action) python-zunclient-4.0.0/zunclient/v1/quota_classes_shell.py0000664000175000017500000000341513643163457024056 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. from zunclient.common import cliutils as utils @utils.arg( '--containers', metavar='', type=int, help='The number of containers allowed per project') @utils.arg( '--cpu', metavar='', type=int, help='The number of container cores or vCPUs allowed per project') @utils.arg( '--memory', metavar='', type=int, help='The number of megabytes of container RAM allowed per project') @utils.arg( '--disk', metavar='', type=int, help='The number of gigabytes of container Disk allowed per project') @utils.arg( 'quota_class_name', metavar='', help='The name of quota class') def do_quota_class_update(cs, args): """Print an updated quotas for a quota class""" utils.print_dict(cs.quota_classes.update( args.quota_class_name, containers=args.containers, memory=args.memory, cpu=args.cpu, disk=args.disk)._info) @utils.arg( 'quota_class_name', metavar='', help='The name of quota class') def do_quota_class_get(cs, args): """Print a quotas for a quota class""" utils.print_dict(cs.quota_classes.get(args.quota_class_name)._info) python-zunclient-4.0.0/zunclient/v1/__init__.py0000664000175000017500000000000013643163457021543 0ustar zuulzuul00000000000000python-zunclient-4.0.0/zunclient/v1/quota_classes.py0000664000175000017500000000276513643163457022676 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. from zunclient.common import base class QuotaClass(base.Resource): def __repr__(self): return "" % self._info class QuotaClassManager(base.Manager): resource_class = QuotaClass @staticmethod def _path(quota_class_name): return '/v1/quota_classes/{}' . format(quota_class_name) def get(self, quota_class_name): return self._list(self._path(quota_class_name))[0] def update(self, quota_class_name, containers=None, memory=None, cpu=None, disk=None): resources = {} if cpu is not None: resources['cpu'] = cpu if memory is not None: resources['memory'] = memory if containers is not None: resources['containers'] = containers if disk is not None: resources['disk'] = disk return self._update(self._path(quota_class_name), resources, method='PUT') python-zunclient-4.0.0/zunclient/v1/actions.py0000664000175000017500000000265613643163457021467 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. from zunclient.common import base class Action(base.Resource): def __repr__(self): return "" % self._info class ActionManager(base.Manager): resource_class = Action @staticmethod def _path(container, request_id=None): if request_id: return '/v1/containers/%s/container_actions/%s' % (container, request_id) else: return '/v1/containers/%s/container_actions' % container def list(self, container): """Retrieve a list of actions. :returns: A list of actions. """ return self._list(self._path(container), "containerActions") def get(self, container, request_id): try: return self._list(self._path(container, request_id))[0] except IndexError: return None python-zunclient-4.0.0/zunclient/v1/hosts_shell.py0000664000175000017500000000506613643163457022354 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. import yaml from oslo_serialization import jsonutils from zunclient.common import cliutils as utils from zunclient.common import utils as zun_utils @utils.arg('--marker', metavar='', default=None, help='The last host UUID of the previous page; ' 'displays list of hosts after "marker".') @utils.arg('--limit', metavar='', type=int, help='Maximum number of hosts to return') @utils.arg('--sort-key', metavar='', help='Column to sort results by') @utils.arg('--sort-dir', metavar='', choices=['desc', 'asc'], help='Direction to sort. "asc" or "desc".') def do_host_list(cs, args): """Print a list of available host.""" opts = {} opts['marker'] = args.marker opts['limit'] = args.limit opts['sort_key'] = args.sort_key opts['sort_dir'] = args.sort_dir opts = zun_utils.remove_null_parms(**opts) hosts = cs.hosts.list(**opts) columns = ('uuid', 'hostname', 'mem_total', 'cpus', 'disk_total') utils.print_list(hosts, columns, {'versions': zun_utils.print_list_field('versions')}, sortby_index=None) @utils.arg('host', metavar='', help='ID or name of the host to show.') @utils.arg('-f', '--format', metavar='', action='store', choices=['json', 'yaml', 'table'], default='table', help='Print representation of the host.' 'The choices of the output format is json,table,yaml.' 'Defaults to table.') def do_host_show(cs, args): """Show details of a host.""" host = cs.hosts.get(args.host) if args.format == 'json': print(jsonutils.dumps(host._info, indent=4, sort_keys=True)) elif args.format == 'yaml': print(yaml.safe_dump(host._info, default_flow_style=False)) elif args.format == 'table': utils.print_dict(host._info) python-zunclient-4.0.0/zunclient/common/0000775000175000017500000000000013643163533020401 5ustar zuulzuul00000000000000python-zunclient-4.0.0/zunclient/common/httpclient.py0000664000175000017500000003742013643163457023144 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 os from oslo_log import log as logging from oslo_serialization import jsonutils import socket import ssl from keystoneauth1 import adapter from oslo_utils import importutils import six import six.moves.urllib.parse as urlparse from zunclient import api_versions from zunclient import exceptions osprofiler_web = importutils.try_import("osprofiler.web") LOG = logging.getLogger(__name__) USER_AGENT = 'python-zunclient' CHUNKSIZE = 1024 * 64 # 64kB API_VERSION = '/v1' DEFAULT_API_VERSION = '1.latest' def _extract_error_json(body): """Return error_message from the HTTP response body.""" error_json = {} try: body_json = jsonutils.loads(body) if 'error_message' in body_json: raw_msg = body_json['error_message'] error_json = jsonutils.loads(raw_msg) elif 'error' in body_json: error_body = body_json['error'] error_json = {'faultstring': error_body['title'], 'debuginfo': error_body['message']} elif 'errors' in body_json: error_body = body_json['errors'][0] error_json = {'faultstring': error_body['title']} if 'detail' in error_body: error_json['debuginfo'] = error_body['detail'] elif 'description' in error_body: error_json['debuginfo'] = error_body['description'] except ValueError: return {} return error_json class HTTPClient(object): def __init__(self, endpoint, api_version=DEFAULT_API_VERSION, **kwargs): self.endpoint = endpoint self.auth_token = kwargs.get('token') self.auth_ref = kwargs.get('auth_ref') self.api_version = api_version or api_versions.APIVersion() self.connection_params = self.get_connection_params(endpoint, **kwargs) @staticmethod def get_connection_params(endpoint, **kwargs): parts = urlparse.urlparse(endpoint) # trim API version and trailing slash from endpoint path = parts.path path = path.rstrip('/').rstrip(API_VERSION) _args = (parts.hostname, parts.port, path) _kwargs = {'timeout': (float(kwargs.get('timeout')) if kwargs.get('timeout') else 600)} if parts.scheme == 'https': _class = VerifiedHTTPSConnection _kwargs['ca_file'] = kwargs.get('ca_file', None) _kwargs['cert_file'] = kwargs.get('cert_file', None) _kwargs['key_file'] = kwargs.get('key_file', None) _kwargs['insecure'] = kwargs.get('insecure', False) elif parts.scheme == 'http': _class = six.moves.http_client.HTTPConnection else: msg = 'Unsupported scheme: %s' % parts.scheme raise exceptions.EndpointException(msg) return (_class, _args, _kwargs) def get_connection(self): _class = self.connection_params[0] try: return _class(*self.connection_params[1][0:2], **self.connection_params[2]) except six.moves.http_client.InvalidURL: raise exceptions.EndpointException() def log_curl_request(self, method, url, kwargs): curl = ['curl -i -X %s' % method] for (key, value) in kwargs['headers'].items(): header = '-H \'%s: %s\'' % (key, value) curl.append(header) conn_params_fmt = [ ('key_file', '--key %s'), ('cert_file', '--cert %s'), ('ca_file', '--cacert %s'), ] for (key, fmt) in conn_params_fmt: value = self.connection_params[2].get(key) if value: curl.append(fmt % value) if self.connection_params[2].get('insecure'): curl.append('-k') if 'body' in kwargs: curl.append('-d \'%s\'' % kwargs['body']) curl.append('%s/%s' % (self.endpoint, url.lstrip(API_VERSION))) LOG.debug(' '.join(curl)) @staticmethod def log_http_response(resp, body=None): status = (resp.version / 10.0, resp.status, resp.reason) dump = ['\nHTTP/%.1f %s %s' % status] dump.extend(['%s: %s' % (k, v) for k, v in resp.getheaders()]) dump.append('') if body: dump.extend([body, '']) LOG.debug('\n'.join(dump)) def _make_connection_url(self, url): (_class, _args, _kwargs) = self.connection_params base_url = _args[2] return '%s/%s' % (base_url, url.lstrip('/')) def _http_request(self, url, method, **kwargs): """Send an http request with the specified characteristics. Wrapper around httplib.HTTP(S)Connection.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) api_versions.update_headers(kwargs["headers"], self.api_version) if self.auth_token: kwargs['headers'].setdefault('X-Auth-Token', self.auth_token) self.log_curl_request(method, url, kwargs) conn = self.get_connection() try: conn_url = self._make_connection_url(url) conn.request(method, conn_url, **kwargs) resp = conn.getresponse() except socket.gaierror as e: message = ("Error finding address for %(url)s: %(e)s" % dict(url=url, e=e)) raise exceptions.EndpointNotFound(message) except (socket.error, socket.timeout) as e: endpoint = self.endpoint message = ("Error communicating with %(endpoint)s %(e)s" % dict(endpoint=endpoint, e=e)) raise exceptions.ConnectionRefused(message) body_iter = ResponseBodyIterator(resp) # Read body into string if it isn't obviously image data body_str = None if resp.getheader('content-type', None) != 'application/octet-stream': # decoding byte to string is necessary for Python 3.4 compatibility # this issues has not been found with Python 3.4 unit tests # because the test creates a fake http response of type str # the if statement satisfies test (str) and real (bytes) behavior body_list = [ chunk.decode("utf-8") if isinstance(chunk, bytes) else chunk for chunk in body_iter ] body_str = ''.join(body_list) self.log_http_response(resp, body_str) body_iter = six.StringIO(body_str) else: self.log_http_response(resp) if 400 <= resp.status < 600: LOG.warning("Request returned failure status.") error_json = _extract_error_json(body_str) raise exceptions.from_response( resp, error_json.get('faultstring'), error_json.get('debuginfo'), method, url) elif resp.status in (301, 302, 305): # Redirected. Reissue the request to the new location. return self._http_request(resp['location'], method, **kwargs) elif resp.status == 300: raise exceptions.from_response(resp, method=method, url=url) return resp, body_iter def json_request(self, method, url, **kwargs): kwargs.setdefault('headers', {}) kwargs['headers'].setdefault('Content-Type', 'application/json') kwargs['headers'].setdefault('Accept', 'application/json') if 'body' in kwargs: kwargs['body'] = jsonutils.dumps(kwargs['body']) resp, body_iter = self._http_request(url, method, **kwargs) content_type = resp.getheader('content-type', None) if resp.status == 204 or resp.status == 205 or content_type is None: return resp, list() if 'application/json' in content_type: body = ''.join([chunk for chunk in body_iter]) try: body = jsonutils.loads(body) except ValueError: LOG.error('Could not decode response body as JSON') else: body = None return resp, body def raw_request(self, method, url, **kwargs): kwargs.setdefault('headers', {}) kwargs['headers'].setdefault('Content-Type', 'application/octet-stream') return self._http_request(url, method, **kwargs) class VerifiedHTTPSConnection(six.moves.http_client.HTTPSConnection): """httplib-compatibile connection using client-side SSL authentication :see http://code.activestate.com/recipes/ 577548-https-httplib-client-connection-with-certificate-v/ """ def __init__(self, host, port, key_file=None, cert_file=None, ca_file=None, timeout=None, insecure=False): six.moves.http_client.HTTPSConnection.__init__(self, host, port, key_file=key_file, cert_file=cert_file) self.key_file = key_file self.cert_file = cert_file if ca_file is not None: self.ca_file = ca_file else: self.ca_file = self.get_system_ca_file() self.timeout = timeout self.insecure = insecure def connect(self): """Connect to a host on a given (SSL) port. If ca_file is pointing somewhere, use it to check Server Certificate. Redefined/copied and extended from httplib.py:1105 (Python 2.6.x). This is needed to pass cert_reqs=ssl.CERT_REQUIRED as parameter to ssl.wrap_socket(), which forces SSL to check server certificate against our client certificate. """ sock = socket.create_connection((self.host, self.port), self.timeout) if self._tunnel_host: self.sock = sock self._tunnel() if self.insecure is True: kwargs = {'cert_reqs': ssl.CERT_NONE} else: kwargs = {'cert_reqs': ssl.CERT_REQUIRED, 'ca_certs': self.ca_file} if self.cert_file: kwargs['certfile'] = self.cert_file if self.key_file: kwargs['keyfile'] = self.key_file self.sock = ssl.wrap_socket(sock, **kwargs) @staticmethod def get_system_ca_file(): """Return path to system default CA file.""" # Standard CA file locations for Debian/Ubuntu, RedHat/Fedora, # Suse, FreeBSD/OpenBSD ca_path = ['/etc/ssl/certs/ca-certificates.crt', '/etc/pki/tls/certs/ca-bundle.crt', '/etc/ssl/ca-bundle.pem', '/etc/ssl/cert.pem'] for ca in ca_path: if os.path.exists(ca): return ca return None class SessionClient(adapter.LegacyJsonAdapter): """HTTP client based on Keystone client session.""" def __init__(self, user_agent=USER_AGENT, logger=LOG, api_version=DEFAULT_API_VERSION, *args, **kwargs): self.user_agent = USER_AGENT self.api_version = api_version or api_versions.APIVersion() super(SessionClient, self).__init__(*args, **kwargs) def _http_request(self, url, method, **kwargs): if url.startswith(API_VERSION): url = url[len(API_VERSION):] kwargs.setdefault('user_agent', self.user_agent) kwargs.setdefault('auth', self.auth) kwargs.setdefault('endpoint_override', self.endpoint_override) # 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', self.user_agent) api_versions.update_headers(kwargs["headers"], self.api_version) # NOTE(kevinz): osprofiler_web.get_trace_id_headers does not add any # headers in case if osprofiler is not initialized. if osprofiler_web: kwargs['headers'].update(osprofiler_web.get_trace_id_headers()) endpoint_filter = kwargs.setdefault('endpoint_filter', {}) endpoint_filter.setdefault('interface', self.interface) endpoint_filter.setdefault('service_type', self.service_type) endpoint_filter.setdefault('region_name', self.region_name) resp = self.session.request(url, method, raise_exc=False, **kwargs) if 400 <= resp.status_code < 600: error_json = _extract_error_json(resp.content) raise exceptions.from_response( resp, error_json.get('faultstring'), error_json.get('debuginfo'), method, url) elif resp.status_code in (301, 302, 305): # Redirected. Reissue the request to the new location. location = resp.headers.get('location') resp = self._http_request(location, method, **kwargs) elif resp.status_code == 300: raise exceptions.from_response(resp, method=method, url=url) return resp def json_request(self, method, url, **kwargs): kwargs.setdefault('headers', {}) kwargs['headers'].setdefault('Content-Type', 'application/json') kwargs['headers'].setdefault('Accept', 'application/json') if 'body' in kwargs: kwargs['data'] = jsonutils.dumps(kwargs.pop('body')) resp = self._http_request(url, method, **kwargs) body = resp.content content_type = resp.headers.get('content-type', None) status = resp.status_code if status == 204 or status == 205 or content_type is None: return resp, list() if 'application/json' in content_type: try: body = resp.json() except ValueError: LOG.error('Could not decode response body as JSON') else: body = None return resp, body def raw_request(self, method, url, **kwargs): kwargs.setdefault('headers', {}) kwargs['headers'].setdefault('Content-Type', 'application/octet-stream') return self._http_request(url, method, **kwargs) class ResponseBodyIterator(object): """A class that acts as an iterator over an HTTP response.""" def __init__(self, resp): self.resp = resp def __iter__(self): while True: try: yield self.next() except StopIteration: return def next(self): chunk = self.resp.read(CHUNKSIZE) if chunk: return chunk else: raise StopIteration() def _construct_http_client(*args, **kwargs): session = kwargs.pop('session', None) auth = kwargs.pop('auth', None) if session: service_type = kwargs.pop('service_type', 'container') interface = kwargs.pop('endpoint_type', None) region_name = kwargs.pop('region_name', None) return SessionClient(session=session, auth=auth, interface=interface, service_type=service_type, region_name=region_name, service_name=None, user_agent='python-zunclient') else: return HTTPClient(*args, **kwargs) python-zunclient-4.0.0/zunclient/common/template_utils.py0000664000175000017500000000651013643163457024015 0ustar zuulzuul00000000000000# Copyright 2017 Arm 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 import six from six.moves.urllib import parse from six.moves.urllib import request from zunclient.common import template_format from zunclient.common import utils from zunclient import exceptions from zunclient.i18n import _ def get_template_contents(template_file=None, template_url=None, files=None): # Transform a bare file path to a file:// URL. if template_file: # nosec template_url = utils.normalise_file_path_to_url(template_file) tpl = request.urlopen(template_url).read() else: raise exceptions.CommandErrorException(_('Need to specify exactly ' 'one of %(arg1)s, %(arg2)s ' 'or %(arg3)s') % {'arg1': '--template-file', 'arg2': '--template-url'}) if not tpl: raise exceptions.CommandErrorException(_('Could not fetch ' 'template from %s') % template_url) try: if isinstance(tpl, six.binary_type): tpl = tpl.decode('utf-8') template = template_format.parse(tpl) except ValueError as e: raise exceptions.CommandErrorException(_('Error parsing template ' '%(url)s %(error)s') % {'url': template_url, 'error': e}) return template def is_template(file_content): try: if isinstance(file_content, six.binary_type): file_content = file_content.decode('utf-8') template_format.parse(file_content) except (ValueError, TypeError): return False return True def get_file_contents(from_data, files, base_url=None, ignore_if=None): if isinstance(from_data, dict): for key, value in from_data.items(): if ignore_if and ignore_if(key, value): continue if base_url and not base_url.endswith('/'): base_url = base_url + '/' str_url = parse.urljoin(base_url, value) if str_url not in files: file_content = utils.read_url_content(str_url) if is_template(file_content): template = get_template_contents( template_url=str_url, files=files)[1] file_content = jsonutils.dumps(template) files[str_url] = file_content # replace the data value with the normalised absolute URL from_data[key] = str_url python-zunclient-4.0.0/zunclient/common/base.py0000664000175000017500000001227613643163457021702 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 copy import six.moves.urllib.parse as urlparse from zunclient.common.apiclient import base def getid(obj): """Wrapper to get object's ID. Abstracts the common pattern of allowing both an object or an object's ID (UUID) as a parameter when dealing with relationships. """ return getattr(obj, 'id', obj) class Manager(object): """Provides CRUD operations with a particular API.""" resource_class = None def __init__(self, api): self.api = api @property def api_version(self): return self.api.api_version def _create(self, url, body): resp, body = self.api.json_request('POST', url, body=body) if body: return self.resource_class(self, body) def _format_body_data(self, body, response_key): if response_key: try: data = body[response_key] except KeyError: return [] else: data = body if not isinstance(data, list): data = [data] return data def _list_pagination(self, url, response_key=None, obj_class=None, limit=None): """Retrieve a list of items. The Zun API is configured to return a maximum number of items per request, (FIXME: see Zun's api.max_limit option). This iterates over the 'next' link (pagination) in the responses, to get the number of items specified by 'limit'. If 'limit' is None this function will continue pagination until there are no more values to be returned. :param url: a partial URL, e.g. '/nodes' :param response_key: the key to be looked up in response dictionary, e.g. 'nodes' :param obj_class: class for constructing the returned objects. :param limit: maximum number of items to return. If None returns everything. """ if obj_class is None: obj_class = self.resource_class if limit is not None: limit = int(limit) object_list = [] object_count = 0 limit_reached = False while url: resp, body = self.api.json_request('GET', url) data = self._format_body_data(body, response_key) for obj in data: object_list.append(obj_class(self, obj, loaded=True)) object_count += 1 if limit and object_count >= limit: # break the for loop limit_reached = True break # break the while loop and return if limit_reached: break url = body.get('next') if url: # NOTE(lucasagomes): We need to edit the URL to remove # the scheme and netloc url_parts = list(urlparse.urlparse(url)) url_parts[0] = url_parts[1] = '' url = urlparse.urlunparse(url_parts) return object_list def _list(self, url, response_key=None, obj_class=None, body=None, qparams=None): if qparams: url = "%s?%s" % (url, urlparse.urlencode(qparams)) resp, body = self.api.json_request('GET', url) if obj_class is None: obj_class = self.resource_class data = self._format_body_data(body, response_key) return [obj_class(self, res, loaded=True) for res in data if res] def _update(self, url, body, method='PATCH', response_key=None): resp, body = self.api.json_request(method, url, body=body) # PATCH/PUT requests may not return a body if body: return self.resource_class(self, body) def _delete(self, url, qparams=None): if qparams: url = "%s?%s" % (url, urlparse.urlencode(qparams)) self.api.raw_request('DELETE', url) def _search(self, url, qparams=None, response_key=None, obj_class=None, body=None): if qparams: url = "%s?%s" % (url, urlparse.urlencode(qparams)) resp, body = self.api.json_request('GET', url, body=body) data = self._format_body_data(body, response_key) if obj_class is None: obj_class = self.resource_class return [obj_class(self, res, loaded=True) for res in data if res] class Resource(base.Resource): """Represents a particular instance of an object (tenant, user, etc). This is pretty much just a bag for attributes. """ def to_dict(self): return copy.deepcopy(self._info) python-zunclient-4.0.0/zunclient/common/template_format.py0000664000175000017500000000412713643163457024147 0ustar zuulzuul00000000000000# Copyright 2017 Arm 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 yaml from oslo_serialization import jsonutils if hasattr(yaml, 'CSafeDumper'): yaml_dumper_base = yaml.CSafeDumper else: yaml_dumper_base = yaml.SafeDumper # We create custom class to not overriden the default yaml behavior class yaml_loader(yaml.SafeLoader): pass class yaml_dumper(yaml_dumper_base): pass def _construct_yaml_str(self, node): # Override the default string handling function # to always return unicode objects return self.construct_scalar(node) yaml_loader.add_constructor(u'tag:yaml.org,2002:str', _construct_yaml_str) # Unquoted dates like 2013-05-23 in yaml files get loaded as objects of type # datetime.data which causes problems in API layer when being processed by # openstack.common.jsonutils. Therefore, make unicode string out of timestamps # until jsonutils can handle dates. yaml_loader.add_constructor(u'tag:yaml.org,2002:timestamp', _construct_yaml_str) def parse(tmpl_str): """Takes a string and returns a dict containing the parsed structure. This includes determination of whether the string is using the JSON or YAML format. """ # strip any whitespace before the check tmpl_str = tmpl_str.strip() if tmpl_str.startswith('{'): tpl = jsonutils.loads(tmpl_str) else: try: tpl = yaml.safe_load(tmpl_str) except yaml.YAMLError as yea: raise ValueError(yea) else: if tpl is None: tpl = {} return tpl python-zunclient-4.0.0/zunclient/common/cliutils.py0000664000175000017500000002414613643163457022617 0ustar zuulzuul00000000000000# Copyright 2012 Red Hat, 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. # W0603: Using the global statement # W0621: Redefining name %s from outer scope # pylint: disable=W0603,W0621 from __future__ import print_function import collections import getpass import inspect import os import sys import textwrap import decorator from oslo_utils import encodeutils from oslo_utils import strutils import prettytable import six from six import moves from zunclient.i18n import _ class MissingArgs(Exception): """Supplied arguments are not sufficient for calling a function.""" def __init__(self, missing): self.missing = missing msg = _("Missing arguments: %s") % ", ".join(missing) super(MissingArgs, self).__init__(msg) def validate_args(fn, *args, **kwargs): """Check that the supplied args are sufficient for calling a function. >>> validate_args(lambda a: None) Traceback (most recent call last): ... MissingArgs: Missing argument(s): a >>> validate_args(lambda a, b, c, d: None, 0, c=1) Traceback (most recent call last): ... MissingArgs: Missing argument(s): b, d :param fn: the function to check :param arg: the positional arguments supplied :param kwargs: the keyword arguments supplied """ argspec = inspect.getargspec(fn) num_defaults = len(argspec.defaults or []) required_args = argspec.args[:len(argspec.args) - num_defaults] def isbound(method): return getattr(method, '__self__', None) is not None if isbound(fn): required_args.pop(0) missing = [arg for arg in required_args if arg not in kwargs] missing = missing[len(args):] if missing: raise MissingArgs(missing) def arg(*args, **kwargs): """Decorator for CLI args. Example: >>> @arg("name", help="Name of the new entity") ... def entity_create(args): ... pass """ def _decorator(func): add_arg(func, *args, **kwargs) return func return _decorator def exclusive_arg(group_name, *args, **kwargs): """Decorator for CLI mutually exclusive args.""" def _decorator(func): required = kwargs.pop('required', None) add_exclusive_arg(func, group_name, required, *args, **kwargs) return func return _decorator def env(*args, **kwargs): """Returns the first environment variable set. If all are empty, defaults to '' or keyword arg `default`. """ for arg in args: value = os.environ.get(arg) if value: return value return kwargs.get('default', '') def add_arg(func, *args, **kwargs): """Bind CLI arguments to a shell.py `do_foo` function.""" if not hasattr(func, 'arguments'): func.arguments = [] # NOTE(sirp): avoid dups that can occur when the module is shared across # tests. if (args, kwargs) not in func.arguments: # Because of the semantics of decorator composition if we just append # to the options list positional options will appear to be backwards. func.arguments.insert(0, (args, kwargs)) def add_exclusive_arg(func, group_name, required, *args, **kwargs): """Bind CLI mutally exclusive arguments to a shell.py `do_foo` function.""" if not hasattr(func, 'exclusive_args'): func.exclusive_args = collections.defaultdict(list) # Default required to False func.exclusive_args['__required__'] = collections.defaultdict(bool) # NOTE(sirp): avoid dups that can occur when the module is shared across # tests. if (args, kwargs) not in func.exclusive_args[group_name]: # Because of the semantics of decorator composition if we just append # to the options list positional options will appear to be backwards. func.exclusive_args[group_name].insert(0, (args, kwargs)) if required is not None: func.exclusive_args['__required__'][group_name] = required def unauthenticated(func): """Adds 'unauthenticated' attribute to decorated function. Usage: >>> @unauthenticated ... def mymethod(f): ... pass """ func.unauthenticated = True return func def isunauthenticated(func): """Checks if the function does not require authentication. Mark such functions with the `@unauthenticated` decorator. :returns: bool """ return getattr(func, 'unauthenticated', False) def print_list(objs, fields, formatters=None, sortby_index=0, mixed_case_fields=None, field_labels=None): """Print a list or objects as a table, one row per object. :param objs: iterable of :class:`Resource` :param fields: attributes that correspond to columns, in order :param formatters: `dict` of callables for field formatting :param sortby_index: index of the field for sorting table rows :param mixed_case_fields: fields corresponding to object attributes that have mixed case names (e.g., 'serverId') :param field_labels: Labels to use in the heading of the table, default to fields. """ formatters = formatters or {} mixed_case_fields = mixed_case_fields or [] field_labels = field_labels or fields if len(field_labels) != len(fields): raise ValueError(_("Field labels list %(labels)s has different number " "of elements than fields list %(fields)s"), {'labels': field_labels, 'fields': fields}) if sortby_index is None: kwargs = {} else: kwargs = {'sortby': field_labels[sortby_index]} pt = prettytable.PrettyTable(field_labels) pt.align = 'l' for o in objs: row = [] for field in fields: if field in formatters: row.append(formatters[field](o)) else: if field in mixed_case_fields: field_name = field.replace(' ', '_') else: field_name = field.lower().replace(' ', '_') data = getattr(o, field_name, '') row.append(data) pt.add_row(row) if six.PY3: print(encodeutils.safe_encode(pt.get_string(**kwargs)).decode()) else: print(encodeutils.safe_encode(pt.get_string(**kwargs))) def keys_and_vals_to_strs(dictionary): """Recursively convert a dictionary's keys and values to strings. :param dictionary: dictionary whose keys/vals are to be converted to strs """ def to_str(k_or_v): if isinstance(k_or_v, dict): return keys_and_vals_to_strs(k_or_v) elif isinstance(k_or_v, six.text_type): return str(k_or_v) else: return k_or_v return dict((to_str(k), to_str(v)) for k, v in dictionary.items()) def print_dict(dct, dict_property="Property", wrap=0, value_fields=None): """Print a `dict` as a table of two columns. :param dct: `dict` to print :param dict_property: name of the first column :param wrap: wrapping for the second column :param value_fields: attributes that correspond to columns, in order """ pt = prettytable.PrettyTable([dict_property, 'Value']) if value_fields: pt = prettytable.PrettyTable([dict_property] + list(value_fields)) pt.align = 'l' for k, v in dct.items(): # convert dict to str to check length if isinstance(v, dict) and not value_fields: v = six.text_type(keys_and_vals_to_strs(v)) if wrap > 0: v = textwrap.fill(six.text_type(v), wrap) elif wrap < 0: raise ValueError(_("Wrap argument should be a positive integer")) # if value has a newline, add in multiple rows # e.g. fault with stacktrace if v and isinstance(v, six.string_types) and r'\n' in v: lines = v.strip().split(r'\n') col1 = k for line in lines: pt.add_row([col1, line]) col1 = '' elif isinstance(v, dict): vals = [v[field] for field in v if field in value_fields] pt.add_row([k] + vals) elif isinstance(v, list): val = str([str(i) for i in v]) pt.add_row([k, val]) else: pt.add_row([k, v]) if six.PY3: print(encodeutils.safe_encode(pt.get_string()).decode()) else: print(encodeutils.safe_encode(pt.get_string())) def get_password(max_password_prompts=3): """Read password from TTY.""" verify = strutils.bool_from_string(env("OS_VERIFY_PASSWORD")) pw = None if hasattr(sys.stdin, "isatty") and sys.stdin.isatty(): # Check for Ctrl-D try: for __ in moves.range(max_password_prompts): pw1 = getpass.getpass("OS Password: ") if verify: pw2 = getpass.getpass("Please verify: ") else: pw2 = pw1 if pw1 == pw2 and pw1: pw = pw1 break except EOFError: pass return pw def service_type(stype): """Adds 'service_type' attribute to decorated function. Usage: .. code-block:: python @service_type('volume') def mymethod(f): ... """ def inner(f): f.service_type = stype return f return inner def get_service_type(f): """Retrieves service type from function.""" return getattr(f, 'service_type', None) def pretty_choice_list(l): return ', '.join("'%s'" % i for i in l) def exit(msg=''): if msg: print(msg, file=sys.stderr) sys.exit(1) def deprecated(message): @decorator.decorator def wrapper(func, *args, **kwargs): print(message) return func(*args, **kwargs) return wrapper python-zunclient-4.0.0/zunclient/common/websocketclient/0000775000175000017500000000000013643163533023566 5ustar zuulzuul00000000000000python-zunclient-4.0.0/zunclient/common/websocketclient/exceptions.py0000664000175000017500000000330713643163457026331 0ustar zuulzuul00000000000000# Copyright 2014 # The Cloudscaling Group, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy # of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. class ContainerWebSocketException(Exception): """base for all ContainerWebSocket interactive generated exceptions""" def __init__(self, wrapped=None, message=None): self.wrapped = wrapped if message: self.message = message if wrapped: formatted_string = "%s:%s" % (self.message, str(self.wrapped)) else: formatted_string = "%s" % self.message super(ContainerWebSocketException, self).__init__(formatted_string) class UserExit(ContainerWebSocketException): message = "User requested disconnect the container" class Disconnected(ContainerWebSocketException): message = "Remote host closed connection" class ConnectionFailed(ContainerWebSocketException): message = "Failed to connect to remote host" class InvalidWebSocketLink(ContainerWebSocketException): message = "Invalid websocket link when attach container" class ContainerFailtoStart(ContainerWebSocketException): message = "Container fail to start" class ContainerStateError(ContainerWebSocketException): message = "Container state is error, can not attach container" python-zunclient-4.0.0/zunclient/common/websocketclient/__init__.py0000664000175000017500000000000013643163457025672 0ustar zuulzuul00000000000000python-zunclient-4.0.0/zunclient/common/websocketclient/websocketclient.py0000664000175000017500000002754513643163457027347 0ustar zuulzuul00000000000000# Copyright 2010 Jacob Kaplan-Moss # Copyright 2011 OpenStack Foundation # Copyright 2012 Grid Dynamics # Copyright 2013 OpenStack Foundation # 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 errno import fcntl import os from oslo_log import log as logging import select import signal import six import six.moves.urllib.parse as urlparse import socket import struct import sys import termios import time import tty import websocket from zunclient.common.apiclient import exceptions as acexceptions from zunclient.common.websocketclient import exceptions LOG = logging.getLogger(__name__) DEFAULT_API_VERSION = '1' DEFAULT_ENDPOINT_TYPE = 'publicURL' DEFAULT_SERVICE_TYPE = 'container' class BaseClient(object): def __init__(self, zunclient, url, id, escape='~', close_wait=0.5): self.url = url self.id = id self.escape = escape self.close_wait = close_wait self.cs = zunclient def connect(self): raise NotImplementedError() def fileno(self): raise NotImplementedError() def send(self, data): raise NotImplementedError() def recv(self): raise NotImplementedError() def tty_resize(self, height, width): """Resize the tty session Get the client and send the tty size data to zun api server The environment variables need to get when implement sending operation. """ raise NotImplementedError() def start_loop(self): self.poll = select.poll() self.poll.register(sys.stdin, select.POLLIN | select.POLLHUP | select.POLLPRI | select.POLLNVAL) self.poll.register(self.fileno(), select.POLLIN | select.POLLHUP | select.POLLPRI | select.POLLNVAL) self.start_of_line = False self.read_escape = False with WINCHHandler(self): try: self.setup_tty() self.run_forever() except socket.error as e: raise exceptions.ConnectionFailed(e) except websocket.WebSocketConnectionClosedException as e: raise exceptions.Disconnected(e) finally: self.restore_tty() def run_forever(self): LOG.debug('starting main loop in client') self.quit = False quitting = False when = None while True: try: for fd, event in self.poll.poll(500): if fd == self.fileno(): self.handle_socket(event) elif fd == sys.stdin.fileno(): self.handle_stdin(event) except select.error as e: # POSIX signals interrupt select() no = e.errno if six.PY3 else e[0] if no == errno.EINTR: continue else: raise e if self.quit and not quitting: LOG.debug('entering close_wait') quitting = True when = time.time() + self.close_wait if quitting and time.time() > when: LOG.debug('quitting') break def setup_tty(self): if os.isatty(sys.stdin.fileno()): LOG.debug('putting tty into raw mode') self.old_settings = termios.tcgetattr(sys.stdin) tty.setraw(sys.stdin) def restore_tty(self): if os.isatty(sys.stdin.fileno()): LOG.debug('restoring tty configuration') termios.tcsetattr(sys.stdin, termios.TCSADRAIN, self.old_settings) def handle_stdin(self, event): if event in (select.POLLHUP, select.POLLNVAL): LOG.debug('event %d on stdin', event) LOG.debug('eof on stdin') self.poll.unregister(sys.stdin) self.quit = True data = os.read(sys.stdin.fileno(), 1024) if not data: return if self.start_of_line and data == self.escape: self.read_escape = True return if self.read_escape and data == '.': LOG.debug('exit by local escape code') raise exceptions.UserExit() elif self.read_escape: self.read_escape = False self.send(self.escape) self.send(data) if data == '\r': self.start_of_line = True else: self.start_of_line = False def handle_socket(self, event): if event in (select.POLLHUP, select.POLLNVAL): self.poll.unregister(self.fileno()) self.quit = True data = self.recv() if not data: self.poll.unregister(self.fileno()) self.quit = True return if isinstance(data, bytes): data = data.decode("utf-8") sys.stdout.write(data) sys.stdout.flush() def handle_resize(self): """send the POST to resize the tty session size in container. Resize the container's PTY. If `size` is not None, it must be a tuple of (height,width), otherwise it will be determined by the size of the current TTY. """ size = self.tty_size(sys.stdout) if size is not None: rows, cols = size try: self.tty_resize(height=rows, width=cols) except IOError: # Container already exited pass except acexceptions.BadRequest: pass def tty_size(self, fd): """Get the tty size Return a tuple (rows,cols) representing the size of the TTY `fd`. The provided file descriptor should be the stdout stream of the TTY. If the TTY size cannot be determined, returns None. """ if not os.isatty(fd.fileno()): return None try: dims = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, 'hhhh')) except Exception: try: dims = (os.environ['LINES'], os.environ['COLUMNS']) except Exception: return None return dims class WebSocketClient(BaseClient): def __init__(self, zunclient, url, id, escape='~', close_wait=0.5): super(WebSocketClient, self).__init__( zunclient, url, id, escape, close_wait) def connect(self): url = self.url LOG.debug('connecting to: %s', url) try: self.ws = websocket.create_connection( url, skip_utf8_validation=True, origin=self._compute_origin_header(url), subprotocols=["binary", "base64"]) print('connected to %s, press Enter to continue' % self.id) print('type %s. to disconnect' % self.escape) except socket.error as e: raise exceptions.ConnectionFailed(e) except websocket.WebSocketConnectionClosedException as e: raise exceptions.ConnectionFailed(e) except websocket.WebSocketBadStatusException as e: raise exceptions.ConnectionFailed(e) def _compute_origin_header(self, url): origin = urlparse.urlparse(url) if origin.scheme == 'wss': return "https://%s:%s" % (origin.hostname, origin.port) else: return "http://%s:%s" % (origin.hostname, origin.port) def fileno(self): return self.ws.fileno() def send(self, data): self.ws.send_binary(data) def recv(self): return self.ws.recv() class AttachClient(WebSocketClient): def tty_resize(self, height, width): """Resize the tty session Get the client and send the tty size data to zun api server The environment variables need to get when implement sending operation. """ height = str(height) width = str(width) self.cs.containers.resize(self.id, width, height) class ExecClient(WebSocketClient): def __init__(self, zunclient, url, exec_id, id, escape='~', close_wait=0.5): super(ExecClient, self).__init__(zunclient, url, id, escape, close_wait) self.exec_id = exec_id def tty_resize(self, height, width): """Resize the tty session Get the client and send the tty size data to zun api server The environment variables need to get when implement sending operation. """ height = str(height) width = str(width) self.cs.containers.execute_resize(self.id, self.exec_id, width, height) class WINCHHandler(object): """WINCH Signal handler WINCH Signal handler to keep the PTY correctly sized. """ def __init__(self, client): """Initialize a new WINCH handler for the given PTY. Initializing a handler has no immediate side-effects. The `start()` method must be invoked for the signals to be trapped. """ self.client = client self.original_handler = None def __enter__(self): """Enter Invoked on entering a `with` block. """ self.start() return self def __exit__(self, *_): """Exit Invoked on exiting a `with` block. """ self.stop() def start(self): """Start Start trapping WINCH signals and resizing the PTY. This method saves the previous WINCH handler so it can be restored on `stop()`. """ def handle(signum, frame): if signum == signal.SIGWINCH: LOG.debug("Send command to resize the tty session") self.client.handle_resize() self.original_handler = signal.signal(signal.SIGWINCH, handle) def stop(self): """stop Stop trapping WINCH signals and restore the previous WINCH handler. """ if self.original_handler is not None: signal.signal(signal.SIGWINCH, self.original_handler) def do_attach(zunclient, url, container_id, escape, close_wait): if url.startswith("ws://") or url.startswith("wss://"): try: wscls = AttachClient(zunclient=zunclient, url=url, id=container_id, escape=escape, close_wait=close_wait) wscls.connect() wscls.handle_resize() wscls.start_loop() except exceptions.ContainerWebSocketException as e: print("%(e)s:%(container)s" % {'e': e, 'container': container_id}) else: raise exceptions.InvalidWebSocketLink(container_id) def do_exec(zunclient, url, container_id, exec_id, escape, close_wait): if url.startswith("ws://") or url.startswith("wss://"): try: wscls = ExecClient(zunclient=zunclient, url=url, exec_id=exec_id, id=container_id, escape=escape, close_wait=close_wait) wscls.connect() wscls.handle_resize() wscls.start_loop() except exceptions.ContainerWebSocketException as e: print("%(e)s:%(container)s" % {'e': e, 'container': container_id}) else: raise exceptions.InvalidWebSocketLink(container_id) python-zunclient-4.0.0/zunclient/common/utils.py0000664000175000017500000003054213643163457022124 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 base64 import binascii import os import re import shlex from oslo_serialization import jsonutils from oslo_utils import netutils import six from six.moves.urllib import parse from six.moves.urllib import request from zunclient.common.apiclient import exceptions as apiexec from zunclient.common import cliutils as utils from zunclient import exceptions as exc from zunclient.i18n import _ VALID_UNITS = ( K, M, G, ) = ( 1024, 1024 * 1024, 1024 * 1024 * 1024, ) def common_filters(marker=None, limit=None, sort_key=None, sort_dir=None, all_projects=False): """Generate common filters for any list request. :param all_projects: list containers in all projects or not :param marker: entity ID from which to start returning entities. :param limit: maximum number of entities to return. :param sort_key: field to use for sorting. :param sort_dir: direction of sorting: 'asc' or 'desc'. :returns: list of string filters. """ filters = [] if all_projects is True: filters.append('all_projects=1') if isinstance(limit, int): filters.append('limit=%s' % limit) if marker is not None: filters.append('marker=%s' % marker) if sort_key is not None: filters.append('sort_key=%s' % sort_key) if sort_dir is not None: filters.append('sort_dir=%s' % sort_dir) return filters def split_and_deserialize(string): """Split and try to JSON deserialize a string. Gets a string with the KEY=VALUE format, split it (using '=' as the separator) and try to JSON deserialize the VALUE. :returns: A tuple of (key, value). """ try: key, value = string.split("=", 1) except ValueError: raise exc.CommandError(_('Attributes must be a list of ' 'PATH=VALUE not "%s"') % string) try: value = jsonutils.loads(value) except ValueError: pass return (key, value) def args_array_to_patch(attributes): patch = [] for attr in attributes: path, value = split_and_deserialize(attr) patch.append({path: value}) return patch def format_args(args, parse_comma=True): '''Reformat a list of key-value arguments into a dict. Convert arguments into format expected by the API. ''' if not args: return {} if parse_comma: # expect multiple invocations of --label (or other arguments) but fall # back to either , or ; delimited if only one --label is specified if len(args) == 1: args = args[0].replace(';', ',').split(',') fmt_args = {} for arg in args: try: (k, v) = arg.split(('='), 1) except ValueError: raise exc.CommandError(_('arguments must be a list of KEY=VALUE ' 'not %s') % arg) if k not in fmt_args: fmt_args[k] = v else: if not isinstance(fmt_args[k], list): fmt_args[k] = [fmt_args[k]] fmt_args[k].append(v) return fmt_args def print_list_field(field): return lambda obj: ', '.join(getattr(obj, field)) def check_restart_policy(policy): if ":" in policy: name, count = policy.split(":") restart_policy = {"Name": name, "MaximumRetryCount": count} else: restart_policy = {"Name": policy, "MaximumRetryCount": '0'} return restart_policy def check_commit_container_args(commit_args): opts = {} if commit_args.repository is not None: if ':' in commit_args.repository: args_list = commit_args.repository.rsplit(':') opts['repository'] = args_list[0] opts['tag'] = args_list[1] else: opts['repository'] = commit_args.repository return opts def remove_null_parms(**kwargs): new = {} for (key, value) in kwargs.items(): if value is not None: new[key] = value return new def check_container_status(container, status): if getattr(container, 'status', None) == status: return True else: return False def format_container_addresses(container): addresses = getattr(container, 'addresses', {}) output = [] networks = [] try: for address_name, address_list in addresses.items(): for a in address_list: output.append(a['addr']) networks.append(address_name) except Exception: pass setattr(container, 'addresses', ', '.join(output)) setattr(container, 'networks', ', '.join(networks)) container._info['addresses'] = ', '.join(output) container._info['networks'] = ', '.join(networks) def list_containers(containers): for c in containers: format_container_addresses(c) columns = ('uuid', 'name', 'image', 'status', 'task_state', 'addresses', 'ports') utils.print_list(containers, columns, {'versions': print_list_field('versions')}, sortby_index=None) def list_availability_zones(zones): columns = ('availability_zone',) utils.print_list(zones, columns, {'versions': print_list_field('versions')}, sortby_index=None) def parse_command(command): output = [] if command: if isinstance(command, six.string_types): command = [command] for c in command: c = '"' + c + '"' output.append(c) return " ".join(output) def parse_entrypoint(entrypoint): return shlex.split(entrypoint) def parse_mounts(mounts): err_msg = ("Invalid mounts argument '%s'. mounts arguments must be of " "the form --mount source=,destination=, " "or use --mount size=,destination= to create " "a new volume and mount to the container, " "or use --mount type=bind,source=,destination= " "to inject file into a path in the container.") parsed_mounts = [] for mount in mounts: keys = ["source", "destination", "size", "type"] mount_info = {} for mnt in mount.split(","): try: k, v = mnt.split("=", 1) k = k.strip() v = v.strip() except ValueError: raise apiexec.CommandError(err_msg % mnt) if k in keys: if mount_info.get(k): raise apiexec.CommandError(err_msg % mnt) mount_info[k] = v else: raise apiexec.CommandError(err_msg % mnt) if not mount_info.get('destination'): raise apiexec.CommandError(err_msg % mount) if not mount_info.get('source') and not mount_info.get('size'): raise apiexec.CommandError(err_msg % mount) type = mount_info.get('type', 'volume') if type not in ('volume', 'bind'): mnt = "type=%s" % type raise apiexec.CommandError(err_msg % mnt) if type == 'bind': # TODO(hongbin): handle the case that 'source' is a directory filename = mount_info.pop('source') with open(filename, 'rb') as file: mount_info['source'] = file.read() parsed_mounts.append(mount_info) return parsed_mounts def parse_nets(ns): err_msg = ("Invalid nets argument '%s'. nets arguments must be of " "the form --nets , " "with only one of network, or port specified.") nets = [] for net_str in ns: keys = ["network", "port", "v4-fixed-ip", "v6-fixed-ip"] net_info = {} for kv_str in net_str.split(","): try: k, v = kv_str.split("=", 1) k = k.strip() v = v.strip() except ValueError: raise apiexec.CommandError(err_msg % net_str) if k in keys: if net_info.get(k): raise apiexec.CommandError(err_msg % net_str) net_info[k] = v else: raise apiexec.CommandError(err_msg % net_str) if net_info.get('v4-fixed-ip') and not netutils.is_valid_ipv4( net_info['v4-fixed-ip']): raise apiexec.CommandError("Invalid ipv4 address.") if net_info.get('v6-fixed-ip') and not netutils.is_valid_ipv6( net_info['v6-fixed-ip']): raise apiexec.CommandError("Invalid ipv6 address.") if bool(net_info.get('network')) == bool(net_info.get('port')): raise apiexec.CommandError(err_msg % net_str) nets.append(net_info) return nets def parse_health(hc_str): err_msg = ("Invalid healthcheck argument '%s'. healthcheck arguments" " must be of the form --healthcheck , and the unit " "of time is s(seconds), m(minutes), h(hours).") % hc_str keys = ["cmd", "interval", "retries", "timeout"] health_info = {} for kv_str in hc_str[0].split(","): try: k, v = kv_str.split("=", 1) k = k.strip() v = v.strip() except ValueError: raise apiexec.CommandError(err_msg) if k in keys: if health_info.get(k): raise apiexec.CommandError(err_msg) elif k in ['interval', 'timeout']: health_info[k] = _convert_healthcheck_para(v, err_msg) elif k == "retries": health_info[k] = int(v) else: health_info[k] = v else: raise apiexec.CommandError(err_msg) return health_info def _convert_healthcheck_para(time, err_msg): int_pattern = r'^\d+$' time_pattern = r'^\d+(s|m|h)$' ret = 0 if re.match(int_pattern, time): ret = int(time) elif re.match(time_pattern, time): if time.endswith('s'): ret = int(time.split('s')[0]) elif time.endswith('m'): ret = int(time.split('m')[0]) * 60 elif time.endswith('h'): ret = int(time.split('h')[0]) * 3600 else: raise apiexec.CommandError(err_msg) return ret def parse_exposed_ports(ports): return {p: {} for p in ports} def normalise_file_path_to_url(path): if parse.urlparse(path).scheme: return path path = os.path.abspath(path) return parse.urljoin('file:', request.pathname2url(path)) def base_url_for_url(url): parsed = parse.urlparse(url) parsed_dir = os.path.dirname(parsed.path) return parse.urljoin(url, parsed_dir) def list_capsules(capsules): for c in capsules: format_container_addresses(c) columns = ('uuid', 'name', 'status', 'addresses') utils.print_list(capsules, columns, {'versions': print_list_field('versions')}, sortby_index=None) def format_fixed_ips(fixed_ips): if fixed_ips is None: return None return ",".join([fip['ip_address'] for fip in fixed_ips]) def format_network_fixed_ips(network): return format_fixed_ips(network.fixed_ips) def list_container_networks(networks): columns = ('net_id', 'port_id', 'fixed_ips') utils.print_list(networks, columns, {'fixed_ips': format_network_fixed_ips}, sortby_index=None) def encode_file_data(data): if six.PY3 and isinstance(data, str): data = data.encode('utf-8') return base64.b64encode(data).decode('utf-8') def decode_file_data(data): # Py3 raises binascii.Error instead of TypeError as in Py27 try: return base64.b64decode(data) except (TypeError, binascii.Error): raise exc.CommandError(_('Invalid Base 64 file data.')) python-zunclient-4.0.0/zunclient/common/apiclient/0000775000175000017500000000000013643163533022351 5ustar zuulzuul00000000000000python-zunclient-4.0.0/zunclient/common/apiclient/exceptions.py0000664000175000017500000003072513643163457025120 0ustar zuulzuul00000000000000# Copyright 2010 Jacob Kaplan-Moss # Copyright 2011 Nebula, Inc. # Copyright 2013 Alessio Ababilov # Copyright 2013 OpenStack Foundation # 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. """ Exception definitions. """ import inspect import sys import six from zunclient.i18n import _ class VersionNotFoundForAPIMethod(Exception): msg_fmt = "API version '%(vers)s' is not supported on '%(method)s' method." def __init__(self, version, method): self.version = version self.method = method def __str__(self): return self.msg_fmt % {"vers": self.version, "method": self.method} class ClientException(Exception): """The base exception class for all exceptions this library raises.""" pass class UnsupportedVersion(ClientException): """User is trying to use an unsupported version of the API.""" pass class CommandError(ClientException): """Error in CLI tool.""" pass class AuthorizationFailure(ClientException): """Cannot authorize API client.""" pass class ConnectionError(ClientException): """Cannot connect to API service.""" pass class ConnectionRefused(ConnectionError): """Connection refused while trying to connect to API service.""" pass class AuthPluginOptionsMissing(AuthorizationFailure): """Auth plugin misses some options.""" def __init__(self, opt_names): super(AuthPluginOptionsMissing, self).__init__( _("Authentication failed. Missing options: %s") % ", ".join(opt_names)) self.opt_names = opt_names class AuthSystemNotFound(AuthorizationFailure): """User has specified an AuthSystem that is not installed.""" def __init__(self, auth_system): super(AuthSystemNotFound, self).__init__( _("AuthSystemNotFound: %r") % auth_system) self.auth_system = auth_system class NoUniqueMatch(ClientException): """Multiple entities found instead of one.""" pass class EndpointException(ClientException): """Something is rotten in Service Catalog.""" pass class EndpointNotFound(EndpointException): """Could not find requested endpoint in Service Catalog.""" pass class AmbiguousEndpoints(EndpointException): """Found more than one matching endpoint in Service Catalog.""" def __init__(self, endpoints=None): super(AmbiguousEndpoints, self).__init__( _("AmbiguousEndpoints: %r") % endpoints) self.endpoints = endpoints class HttpError(ClientException): """The base exception class for all HTTP exceptions.""" http_status = 0 message = _("HTTP Error") def __init__(self, message=None, details=None, response=None, request_id=None, url=None, method=None, http_status=None): self.http_status = http_status or self.http_status self.message = message or self.message self.details = details self.request_id = request_id self.response = response self.url = url self.method = method formatted_string = "%s (HTTP %s)" % (self.message, self.http_status) if request_id: formatted_string += " (Request-ID: %s)" % request_id super(HttpError, self).__init__(formatted_string) class HTTPRedirection(HttpError): """HTTP Redirection.""" message = _("HTTP Redirection") class HTTPClientError(HttpError): """Client-side HTTP error. Exception for cases in which the client seems to have erred. """ message = _("HTTP Client Error") class HttpServerError(HttpError): """Server-side HTTP error. Exception for cases in which the server is aware that it has erred or is incapable of performing the request. """ message = _("HTTP Server Error") class MultipleChoices(HTTPRedirection): """HTTP 300 - Multiple Choices. Indicates multiple options for the resource that the client may follow. """ http_status = 300 message = _("Multiple Choices") class BadRequest(HTTPClientError): """HTTP 400 - Bad Request. The request cannot be fulfilled due to bad syntax. """ http_status = 400 message = _("Bad Request") class Unauthorized(HTTPClientError): """HTTP 401 - Unauthorized. Similar to 403 Forbidden, but specifically for use when authentication is required and has failed or has not yet been provided. """ http_status = 401 message = _("Unauthorized") class PaymentRequired(HTTPClientError): """HTTP 402 - Payment Required. Reserved for future use. """ http_status = 402 message = _("Payment Required") class Forbidden(HTTPClientError): """HTTP 403 - Forbidden. The request was a valid request, but the server is refusing to respond to it. """ http_status = 403 message = _("Forbidden") class NotFound(HTTPClientError): """HTTP 404 - Not Found. The requested resource could not be found but may be available again in the future. """ http_status = 404 message = _("Not Found") class MethodNotAllowed(HTTPClientError): """HTTP 405 - Method Not Allowed. A request was made of a resource using a request method not supported by that resource. """ http_status = 405 message = _("Method Not Allowed") class NotAcceptable(HTTPClientError): """HTTP 406 - Not Acceptable. The requested resource is only capable of generating content not acceptable according to the Accept headers sent in the request. """ http_status = 406 message = _("Not Acceptable") class ProxyAuthenticationRequired(HTTPClientError): """HTTP 407 - Proxy Authentication Required. The client must first authenticate itself with the proxy. """ http_status = 407 message = _("Proxy Authentication Required") class RequestTimeout(HTTPClientError): """HTTP 408 - Request Timeout. The server timed out waiting for the request. """ http_status = 408 message = _("Request Timeout") class Conflict(HTTPClientError): """HTTP 409 - Conflict. Indicates that the request could not be processed because of conflict in the request, such as an edit conflict. """ http_status = 409 message = _("Conflict") class Gone(HTTPClientError): """HTTP 410 - Gone. Indicates that the resource requested is no longer available and will not be available again. """ http_status = 410 message = _("Gone") class LengthRequired(HTTPClientError): """HTTP 411 - Length Required. The request did not specify the length of its content, which is required by the requested resource. """ http_status = 411 message = _("Length Required") class PreconditionFailed(HTTPClientError): """HTTP 412 - Precondition Failed. The server does not meet one of the preconditions that the requester put on the request. """ http_status = 412 message = _("Precondition Failed") class RequestEntityTooLarge(HTTPClientError): """HTTP 413 - Request Entity Too Large. The request is larger than the server is willing or able to process. """ http_status = 413 message = _("Request Entity Too Large") def __init__(self, *args, **kwargs): try: self.retry_after = int(kwargs.pop('retry_after')) except (KeyError, ValueError): self.retry_after = 0 super(RequestEntityTooLarge, self).__init__(*args, **kwargs) class RequestUriTooLong(HTTPClientError): """HTTP 414 - Request-URI Too Long. The URI provided was too long for the server to process. """ http_status = 414 message = _("Request-URI Too Long") class UnsupportedMediaType(HTTPClientError): """HTTP 415 - Unsupported Media Type. The request entity has a media type which the server or resource does not support. """ http_status = 415 message = _("Unsupported Media Type") class RequestedRangeNotSatisfiable(HTTPClientError): """HTTP 416 - Requested Range Not Satisfiable. The client has asked for a portion of the file, but the server cannot supply that portion. """ http_status = 416 message = _("Requested Range Not Satisfiable") class ExpectationFailed(HTTPClientError): """HTTP 417 - Expectation Failed. The server cannot meet the requirements of the Expect request-header field. """ http_status = 417 message = _("Expectation Failed") class UnprocessableEntity(HTTPClientError): """HTTP 422 - Unprocessable Entity. The request was well-formed but was unable to be followed due to semantic errors. """ http_status = 422 message = _("Unprocessable Entity") class InternalServerError(HttpServerError): """HTTP 500 - Internal Server Error. A generic error message, given when no more specific message is suitable. """ http_status = 500 message = _("Internal Server Error") # NotImplemented is a python keyword. class HttpNotImplemented(HttpServerError): """HTTP 501 - Not Implemented. The server either does not recognize the request method, or it lacks the ability to fulfill the request. """ http_status = 501 message = _("Not Implemented") class BadGateway(HttpServerError): """HTTP 502 - Bad Gateway. The server was acting as a gateway or proxy and received an invalid response from the upstream server. """ http_status = 502 message = _("Bad Gateway") class ServiceUnavailable(HttpServerError): """HTTP 503 - Service Unavailable. The server is currently unavailable. """ http_status = 503 message = _("Service Unavailable") class GatewayTimeout(HttpServerError): """HTTP 504 - Gateway Timeout. The server was acting as a gateway or proxy and did not receive a timely response from the upstream server. """ http_status = 504 message = _("Gateway Timeout") class HttpVersionNotSupported(HttpServerError): """HTTP 505 - HttpVersion Not Supported. The server does not support the HTTP protocol version used in the request. """ http_status = 505 message = _("HTTP Version Not Supported") # _code_map contains all the classes that have http_status attribute. _code_map = dict( (getattr(obj, 'http_status', None), obj) for name, obj in vars(sys.modules[__name__]).items() if inspect.isclass(obj) and getattr(obj, 'http_status', False) ) def from_response(response, method, url): """Returns an instance of :class:`HttpError` or subclass based on response. :param response: instance of `requests.Response` class :param method: HTTP method used for request :param url: URL used for request """ req_id = response.headers.get("x-openstack-request-id") # NOTE(hdd) true for older versions of nova and cinder if not req_id: req_id = response.headers.get("x-compute-request-id") kwargs = { "http_status": response.status_code, "response": response, "method": method, "url": url, "request_id": req_id, } if "retry-after" in response.headers: kwargs["retry_after"] = response.headers["retry-after"] content_type = response.headers.get("Content-Type", "") if content_type.startswith("application/json"): try: body = response.json() except ValueError: pass else: if isinstance(body, dict): error = body.get(list(body)[0]) if isinstance(error, dict): kwargs["message"] = (error.get("message") or error.get("faultstring")) kwargs["details"] = (error.get("details") or six.text_type(body)) elif content_type.startswith("text/"): kwargs["details"] = getattr(response, 'text', '') try: cls = _code_map[response.status_code] except KeyError: if 500 <= response.status_code < 600: cls = HttpServerError elif 400 <= response.status_code < 500: cls = HTTPClientError else: cls = HttpError return cls(**kwargs) python-zunclient-4.0.0/zunclient/common/apiclient/base.py0000664000175000017500000000657013643163457023652 0ustar zuulzuul00000000000000# Copyright 2010 Jacob Kaplan-Moss # Copyright 2011 OpenStack Foundation # Copyright 2012 Grid Dynamics # Copyright 2013 OpenStack Foundation # 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 copy class Resource(object): """Base class for OpenStack resources (tenant, user, etc.). This is pretty much just a bag for attributes. """ def __init__(self, manager, info, loaded=False): """Populate and bind to a manager. :param manager: BaseManager object :param info: dictionary representing resource attributes :param loaded: prevent lazy-loading if set to True """ self.manager = manager self._info = info self._add_details(info) self._loaded = loaded 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 _add_details(self, info): for (k, v) in info.items(): try: setattr(self, k, v) self._info[k] = v except AttributeError: # In this case we already defined the attribute on the class pass 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 get(self): """Support for lazy loading details. Some clients, such as novaclient have the option to lazy load the details, details which can be loaded with this function. """ # 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) self._add_details( {'x_request_id': self.manager.client.last_request_id}) def __eq__(self, other): if not isinstance(other, Resource): return NotImplemented # two resources of different types are not equal if not isinstance(other, self.__class__): return False if hasattr(self, 'id') and hasattr(other, 'id'): return self.id == other.id return self._info == other._info 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-zunclient-4.0.0/zunclient/common/apiclient/auth.py0000664000175000017500000001165613643163457023702 0ustar zuulzuul00000000000000# Copyright 2013 OpenStack Foundation # Copyright 2013 Spanish National Research Council. # 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. # E0202: An attribute inherited from %s hide this method # pylint: disable=E0202 import abc import argparse import os import six from zunclient.common.apiclient import exceptions _discovered_plugins = {} def load_auth_system_opts(parser): """Load options needed by the available auth-systems into a parser. This function will try to populate the parser with options from the available plugins. """ group = parser.add_argument_group("Common auth options") BaseAuthPlugin.add_common_opts(group) for name, auth_plugin in _discovered_plugins.items(): group = parser.add_argument_group( "Auth-system '%s' options" % name, conflict_handler="resolve") auth_plugin.add_opts(group) def load_plugin(auth_system): try: plugin_class = _discovered_plugins[auth_system] except KeyError: raise exceptions.AuthSystemNotFound(auth_system) return plugin_class(auth_system=auth_system) @six.add_metaclass(abc.ABCMeta) class BaseAuthPlugin(object): """Base class for authentication plugins. An authentication plugin needs to override at least the authenticate method to be a valid plugin. """ auth_system = None opt_names = [] common_opt_names = [ "auth_system", "username", "password", "auth_url", ] def __init__(self, auth_system=None, **kwargs): self.auth_system = auth_system or self.auth_system self.opts = dict((name, kwargs.get(name)) for name in self.opt_names) @staticmethod def _parser_add_opt(parser, opt): """Add an option to parser in two variants. :param opt: option name (with underscores) """ dashed_opt = opt.replace("_", "-") env_var = "OS_%s" % opt.upper() arg_default = os.environ.get(env_var, "") arg_help = "Defaults to env[%s]." % env_var parser.add_argument( "--os-%s" % dashed_opt, metavar="<%s>" % dashed_opt, default=arg_default, help=arg_help) parser.add_argument( "--os_%s" % opt, metavar="<%s>" % dashed_opt, help=argparse.SUPPRESS) @classmethod def add_opts(cls, parser): """Populate the parser with the options for this plugin.""" for opt in cls.opt_names: # use `BaseAuthPlugin.common_opt_names` since it is never # changed in child classes if opt not in BaseAuthPlugin.common_opt_names: cls._parser_add_opt(parser, opt) @classmethod def add_common_opts(cls, parser): """Add options that are common for several plugins.""" for opt in cls.common_opt_names: cls._parser_add_opt(parser, opt) @staticmethod def get_opt(opt_name, args): """Return option name and value. :param opt_name: name of the option, e.g., "username" :param args: parsed arguments """ return (opt_name, getattr(args, "os_%s" % opt_name, None)) def parse_opts(self, args): """Parse the actual auth-system options if any. This method is expected to populate the attribute `self.opts` with a dict containing the options and values needed to make authentication. """ self.opts.update(dict(self.get_opt(opt_name, args) for opt_name in self.opt_names)) def authenticate(self, http_client): """Authenticate using plugin defined method. The method usually analyses `self.opts` and performs a request to authentication server. :param http_client: client object that needs authentication :type http_client: HTTPClient :raises: AuthorizationFailure """ self.sufficient_options() self._do_authenticate(http_client) @abc.abstractmethod def _do_authenticate(self, http_client): """Protected method for authentication.""" def sufficient_options(self): """Check if all required options are present. :raises: AuthPluginOptionsMissing """ missing = [opt for opt in self.opt_names if not self.opts.get(opt)] if missing: raise exceptions.AuthPluginOptionsMissing(missing) python-zunclient-4.0.0/zunclient/common/apiclient/__init__.py0000664000175000017500000000000013643163457024455 0ustar zuulzuul00000000000000python-zunclient-4.0.0/zunclient/common/__init__.py0000664000175000017500000000000013643163457022505 0ustar zuulzuul00000000000000python-zunclient-4.0.0/zunclient/tests/0000775000175000017500000000000013643163533020253 5ustar zuulzuul00000000000000python-zunclient-4.0.0/zunclient/tests/unit/0000775000175000017500000000000013643163533021232 5ustar zuulzuul00000000000000python-zunclient-4.0.0/zunclient/tests/unit/test_shell.py0000664000175000017500000003161313643163457023763 0ustar zuulzuul00000000000000# Copyright 2015 NEC Corporation. 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 from unittest import mock import fixtures from keystoneauth1 import fixture import six from testtools import matchers from zunclient import api_versions from zunclient import exceptions import zunclient.shell from zunclient.tests.unit import utils FAKE_ENV = {'OS_USERNAME': 'username', 'OS_PASSWORD': 'password', 'OS_PROJECT_NAME': 'project_name', 'OS_AUTH_URL': 'http://no.where/v2.0'} FAKE_ENV2 = {'OS_USER_ID': 'user_id', 'OS_PASSWORD': 'password', 'OS_PROJECT_ID': 'project_id', 'OS_AUTH_URL': 'http://no.where/v2.0'} FAKE_ENV3 = {'OS_USERNAME': 'username', 'OS_PASSWORD': 'password', 'OS_PROJECT_ID': 'project_id', 'OS_AUTH_URL': 'http://no.where/v2.0'} FAKE_ENV4 = {'OS_USERNAME': 'username', 'OS_PASSWORD': 'password', 'OS_PROJECT_ID': 'project_id', 'OS_USER_DOMAIN_NAME': 'Default', 'OS_PROJECT_DOMAIN_NAME': 'Default', 'OS_AUTH_URL': 'http://no.where/v3'} def _create_ver_list(versions): return {'versions': {'values': versions}} class ParserTest(utils.TestCase): def setUp(self): super(ParserTest, self).setUp() self.parser = zunclient.shell.ZunClientArgumentParser() def test_ambiguous_option(self): self.parser.add_argument('--tic') self.parser.add_argument('--tac') try: self.parser.parse_args(['--t']) except SystemExit as err: self.assertEqual(2, err.code) else: self.fail('SystemExit not raised') class ShellTest(utils.TestCase): AUTH_URL = utils.FAKE_ENV['OS_AUTH_URL'] _msg_no_tenant_project = ("You must provide a project name or project id" " via --os-project-name, --os-project-id," " env[OS_PROJECT_NAME] or env[OS_PROJECT_ID]") def setUp(self): super(ShellTest, self).setUp() self.nc_util = mock.patch( 'zunclient.common.cliutils.isunauthenticated').start() self.nc_util.return_value = False self.discover_version = mock.patch( 'zunclient.api_versions.discover_version').start() self.discover_version.return_value = api_versions.APIVersion('1.1') def test_help_unknown_command(self): self.assertRaises(exceptions.CommandError, self.shell, 'help foofoo') def test_help(self): required = [ r'.*?^usage: ', r'.*?^\s+stop\s+Stop specified container.', r'.*?^See "zun help COMMAND" for help on a specific command', ] stdout, stderr = self.shell('help') for r in required: self.assertThat((stdout + stderr), matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE)) def test_help_on_subcommand(self): required = [ r'.*?^usage: zun create', r'.*?^Create a container.', r'.*?^Optional arguments:', ] stdout, stderr = self.shell('help create') for r in required: self.assertThat((stdout + stderr), matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE)) def test_help_no_options(self): required = [ r'.*?^usage: ', r'.*?^\s+stop\s+Stop specified container.', r'.*?^See "zun help COMMAND" for help on a specific command', ] stdout, stderr = self.shell('') for r in required: self.assertThat((stdout + stderr), matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE)) def test_bash_completion(self): stdout, stderr = self.shell('bash-completion') # just check we have some output required = [ r'.*--format', r'.*help', r'.*show', r'.*--name'] for r in required: self.assertThat((stdout + stderr), matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE)) def test_no_username(self): required = ('You must provide a username via either' ' --os-username or env[OS_USERNAME]') self.make_env(exclude='OS_USERNAME') try: self.shell('service-list') except exceptions.CommandError as message: self.assertEqual(required, message.args[0]) else: self.fail('CommandError not raised') def test_no_user_id(self): required = ('You must provide a username via' ' either --os-username or env[OS_USERNAME]') self.make_env(exclude='OS_USER_ID', fake_env=FAKE_ENV2) try: self.shell('service-list') except exceptions.CommandError as message: self.assertEqual(required, message.args[0]) else: self.fail('CommandError not raised') def test_no_project_name(self): required = self._msg_no_tenant_project self.make_env(exclude='OS_PROJECT_NAME') try: self.shell('service-list') except exceptions.CommandError as message: self.assertEqual(required, message.args[0]) else: self.fail('CommandError not raised') def test_no_project_id(self): required = self._msg_no_tenant_project self.make_env(exclude='OS_PROJECT_ID', fake_env=FAKE_ENV3) try: self.shell('service-list') except exceptions.CommandError as message: self.assertEqual(required, message.args[0]) else: self.fail('CommandError not raised') def test_no_auth_url(self): required = ('You must provide an auth url' ' via either --os-auth-url or env[OS_AUTH_URL] or' ' specify an auth_system which defines a default url' ' with --os-auth-system or env[OS_AUTH_SYSTEM]',) self.make_env(exclude='OS_AUTH_URL') try: self.shell('service-list') except exceptions.CommandError as message: self.assertEqual(required, message.args) else: self.fail('CommandError not raised') @mock.patch('zunclient.client.Client') def test_service_type(self, mock_client): self.make_env() self.shell('service-list') _, client_kwargs = mock_client.call_args_list[0] self.assertEqual('container', client_kwargs['service_type']) @mock.patch('zunclient.client.Client') def test_container_format_env(self, mock_client): self.make_env() self.shell('create --environment key=value test') _, create_args = mock_client.return_value.containers.create.call_args self.assertEqual({'key': 'value'}, create_args['environment']) @mock.patch('zunclient.client.Client') def test_insecure(self, mock_client): self.make_env() self.shell('--insecure service-list') _, session_kwargs = mock_client.call_args_list[0] self.assertEqual(True, session_kwargs['insecure']) @mock.patch('sys.stdin', side_effect=mock.MagicMock) @mock.patch('getpass.getpass', side_effect=EOFError) def test_no_password(self, mock_getpass, mock_stdin): required = ('Expecting a password provided' ' via either --os-password, env[OS_PASSWORD],' ' or prompted response',) self.make_env(exclude='OS_PASSWORD') try: self.shell('service-list') except exceptions.CommandError as message: self.assertEqual(required, message.args) else: self.fail('CommandError not raised') @mock.patch('sys.argv', ['zun']) @mock.patch('sys.stdout', six.StringIO()) @mock.patch('sys.stderr', six.StringIO()) def test_main_noargs(self): # Ensure that main works with no command-line arguments try: zunclient.shell.main() except SystemExit: self.fail('Unexpected SystemExit') # We expect the normal usage as a result self.assertIn('Command-line interface to the OpenStack Zun API', sys.stdout.getvalue()) @mock.patch('zunclient.client.Client') def _test_main_region(self, command, expected_region_name, mock_client): self.shell(command) mock_client.assert_called_once_with( username='username', password='password', interface='publicURL', project_id=None, project_name='project_name', auth_url=self.AUTH_URL, service_type='container', region_name=expected_region_name, project_domain_id='', project_domain_name='', user_domain_id='', user_domain_name='', profile=None, endpoint_override=None, insecure=False, cacert=None, cert=None, key=None, version=api_versions.APIVersion('1.29')) def test_main_option_region(self): self.make_env() self._test_main_region( '--zun-api-version 1.29 ' '--os-region-name=myregion service-list', 'myregion') def test_main_env_region(self): fake_env = dict(utils.FAKE_ENV, OS_REGION_NAME='myregion') self.make_env(fake_env=fake_env) self._test_main_region( '--zun-api-version 1.29 ' 'service-list', 'myregion') def test_main_no_region(self): self.make_env() self._test_main_region( '--zun-api-version 1.29 ' 'service-list', None) @mock.patch('zunclient.client.Client') def test_main_endpoint_public(self, mock_client): self.make_env() self.shell( '--zun-api-version 1.29 ' '--endpoint-type publicURL service-list') mock_client.assert_called_once_with( username='username', password='password', interface='publicURL', project_id=None, project_name='project_name', auth_url=self.AUTH_URL, service_type='container', region_name=None, project_domain_id='', project_domain_name='', user_domain_id='', user_domain_name='', profile=None, endpoint_override=None, insecure=False, cacert=None, cert=None, key=None, version=api_versions.APIVersion('1.29')) @mock.patch('zunclient.client.Client') def test_main_endpoint_internal(self, mock_client): self.make_env() self.shell( '--zun-api-version 1.29 ' '--endpoint-type internalURL service-list') mock_client.assert_called_once_with( username='username', password='password', interface='internalURL', project_id=None, project_name='project_name', auth_url=self.AUTH_URL, service_type='container', region_name=None, project_domain_id='', project_domain_name='', user_domain_id='', user_domain_name='', profile=None, endpoint_override=None, insecure=False, cacert=None, cert=None, key=None, version=api_versions.APIVersion('1.29')) class ShellTestKeystoneV3(ShellTest): AUTH_URL = 'http://no.where/v3' def make_env(self, exclude=None, fake_env=FAKE_ENV): if 'OS_AUTH_URL' in fake_env: fake_env.update({'OS_AUTH_URL': self.AUTH_URL}) env = dict((k, v) for k, v in fake_env.items() if k != exclude) self.useFixture(fixtures.MonkeyPatch('os.environ', env)) def register_keystone_discovery_fixture(self, mreq): v3_url = "http://no.where/v3" v3_version = fixture.V3Discovery(v3_url) mreq.register_uri( 'GET', v3_url, json=_create_ver_list([v3_version]), status_code=200) @mock.patch('zunclient.client.Client') def test_main_endpoint_public(self, mock_client): self.make_env(fake_env=FAKE_ENV4) self.shell( '--zun-api-version 1.29 ' '--endpoint-type publicURL service-list') mock_client.assert_called_once_with( username='username', password='password', interface='publicURL', project_id='project_id', project_name=None, auth_url=self.AUTH_URL, service_type='container', region_name=None, project_domain_id='', project_domain_name='Default', user_domain_id='', user_domain_name='Default', endpoint_override=None, insecure=False, profile=None, cacert=None, cert=None, key=None, version=api_versions.APIVersion('1.29')) python-zunclient-4.0.0/zunclient/tests/unit/test_api_versions.py0000664000175000017500000003037313643163457025357 0ustar zuulzuul00000000000000# Copyright 2015 Mirantis # 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. from unittest import mock from zunclient import api_versions from zunclient import exceptions from zunclient.tests.unit import utils from zunclient.v1 import versions class APIVersionTestCase(utils.TestCase): def test_valid_version_strings(self): def _test_string(version, exp_major, exp_minor): v = api_versions.APIVersion(version) self.assertEqual(v.ver_major, exp_major) self.assertEqual(v.ver_minor, exp_minor) _test_string("1.1", 1, 1) _test_string("2.10", 2, 10) _test_string("5.234", 5, 234) _test_string("12.5", 12, 5) _test_string("2.0", 2, 0) _test_string("2.200", 2, 200) def test_null_version(self): v = api_versions.APIVersion() self.assertTrue(v.is_null()) def test_invalid_version_strings(self): self.assertRaises(exceptions.UnsupportedVersion, api_versions.APIVersion, "2") self.assertRaises(exceptions.UnsupportedVersion, api_versions.APIVersion, "200") self.assertRaises(exceptions.UnsupportedVersion, api_versions.APIVersion, "2.1.4") self.assertRaises(exceptions.UnsupportedVersion, api_versions.APIVersion, "200.23.66.3") self.assertRaises(exceptions.UnsupportedVersion, api_versions.APIVersion, "5 .3") self.assertRaises(exceptions.UnsupportedVersion, api_versions.APIVersion, "5. 3") self.assertRaises(exceptions.UnsupportedVersion, api_versions.APIVersion, "5.03") self.assertRaises(exceptions.UnsupportedVersion, api_versions.APIVersion, "02.1") self.assertRaises(exceptions.UnsupportedVersion, api_versions.APIVersion, "2.001") self.assertRaises(exceptions.UnsupportedVersion, api_versions.APIVersion, "") self.assertRaises(exceptions.UnsupportedVersion, api_versions.APIVersion, " 2.1") self.assertRaises(exceptions.UnsupportedVersion, api_versions.APIVersion, "2.1 ") def test_version_comparisons(self): v1 = api_versions.APIVersion("2.0") v2 = api_versions.APIVersion("2.5") v3 = api_versions.APIVersion("5.23") v4 = api_versions.APIVersion("2.0") v_null = api_versions.APIVersion() self.assertTrue(v1 < v2) self.assertTrue(v3 > v2) self.assertTrue(v1 != v2) self.assertTrue(v1 == v4) self.assertTrue(v1 != v_null) self.assertTrue(v_null == v_null) self.assertRaises(TypeError, v1.__le__, "2.1") def test_version_matches(self): v1 = api_versions.APIVersion("2.0") v2 = api_versions.APIVersion("2.5") v3 = api_versions.APIVersion("2.45") v4 = api_versions.APIVersion("3.3") v5 = api_versions.APIVersion("3.23") v6 = api_versions.APIVersion("2.0") v7 = api_versions.APIVersion("3.3") v8 = api_versions.APIVersion("4.0") v_null = api_versions.APIVersion() self.assertTrue(v2.matches(v1, v3)) self.assertTrue(v2.matches(v1, v_null)) self.assertTrue(v1.matches(v6, v2)) self.assertTrue(v4.matches(v2, v7)) self.assertTrue(v4.matches(v_null, v7)) self.assertTrue(v4.matches(v_null, v8)) self.assertFalse(v1.matches(v2, v3)) self.assertFalse(v5.matches(v2, v4)) self.assertFalse(v2.matches(v3, v1)) self.assertRaises(ValueError, v_null.matches, v1, v3) def test_get_string(self): v1_string = "3.23" v1 = api_versions.APIVersion(v1_string) self.assertEqual(v1_string, v1.get_string()) self.assertRaises(ValueError, api_versions.APIVersion().get_string) class UpdateHeadersTestCase(utils.TestCase): def test_api_version_is_null(self): headers = {} api_versions.update_headers(headers, api_versions.APIVersion()) self.assertEqual({}, headers) def test_api_version_is_major(self): headers = {} api_versions.update_headers(headers, api_versions.APIVersion("7.0")) self.assertEqual({}, headers) def test_api_version_is_not_null(self): api_version = api_versions.APIVersion("2.3") headers = {} api_versions.update_headers(headers, api_version) self.assertEqual( {"OpenStack-API-Version": "container %s" % api_version.get_string()}, headers) class GetAPIVersionTestCase(utils.TestCase): def test_get_available_client_versions(self): output = api_versions.get_available_major_versions() self.assertNotEqual([], output) def test_wrong_format(self): self.assertRaises(exceptions.UnsupportedVersion, api_versions.get_api_version, "something_wrong") @mock.patch("zunclient.api_versions.APIVersion") def test_only_major_part_is_presented(self, mock_apiversion): version = 7 self.assertEqual(mock_apiversion.return_value, api_versions.get_api_version(version)) mock_apiversion.assert_called_once_with("%s.latest" % str(version)) @mock.patch("zunclient.api_versions.APIVersion") def test_major_and_minor_parts_is_presented(self, mock_apiversion): version = "2.7" self.assertEqual(mock_apiversion.return_value, api_versions.get_api_version(version)) mock_apiversion.assert_called_once_with(version) class WrapsTestCase(utils.TestCase): def _get_obj_with_vers(self, vers): return mock.MagicMock(api_version=api_versions.APIVersion(vers)) def _side_effect_of_vers_method(self, *args, **kwargs): m = mock.MagicMock(start_version=args[1], end_version=args[2]) m.name = args[0] return m @mock.patch("zunclient.api_versions._get_function_name") @mock.patch("zunclient.api_versions.VersionedMethod") def test_end_version_is_none(self, mock_versioned_method, mock_name): func_name = "foo" mock_name.return_value = func_name mock_versioned_method.side_effect = self._side_effect_of_vers_method @api_versions.wraps("2.2") def foo(*args, **kwargs): pass foo(self._get_obj_with_vers("2.4")) mock_versioned_method.assert_called_once_with( func_name, api_versions.APIVersion("2.2"), api_versions.APIVersion("2.latest"), mock.ANY) @mock.patch("zunclient.api_versions._get_function_name") @mock.patch("zunclient.api_versions.VersionedMethod") def test_start_and_end_version_are_presented(self, mock_versioned_method, mock_name): func_name = "foo" mock_name.return_value = func_name mock_versioned_method.side_effect = self._side_effect_of_vers_method @api_versions.wraps("2.2", "2.6") def foo(*args, **kwargs): pass foo(self._get_obj_with_vers("2.4")) mock_versioned_method.assert_called_once_with( func_name, api_versions.APIVersion("2.2"), api_versions.APIVersion("2.6"), mock.ANY) @mock.patch("zunclient.api_versions._get_function_name") @mock.patch("zunclient.api_versions.VersionedMethod") def test_api_version_doesnt_match(self, mock_versioned_method, mock_name): func_name = "foo" mock_name.return_value = func_name mock_versioned_method.side_effect = self._side_effect_of_vers_method @api_versions.wraps("2.2", "2.6") def foo(*args, **kwargs): pass self.assertRaises(exceptions.VersionNotFoundForAPIMethod, foo, self._get_obj_with_vers("2.1")) mock_versioned_method.assert_called_once_with( func_name, api_versions.APIVersion("2.2"), api_versions.APIVersion("2.6"), mock.ANY) def test_define_method_is_actually_called(self): checker = mock.MagicMock() @api_versions.wraps("2.2", "2.6") def some_func(*args, **kwargs): checker(*args, **kwargs) obj = self._get_obj_with_vers("2.4") some_args = ("arg_1", "arg_2") some_kwargs = {"key1": "value1", "key2": "value2"} some_func(obj, *some_args, **some_kwargs) checker.assert_called_once_with(*((obj,) + some_args), **some_kwargs) class DiscoverVersionTestCase(utils.TestCase): def setUp(self): super(DiscoverVersionTestCase, self).setUp() self.orig_max = api_versions.MAX_API_VERSION self.orig_min = api_versions.MIN_API_VERSION self.addCleanup(self._clear_fake_version) def _clear_fake_version(self): api_versions.MAX_API_VERSION = self.orig_max api_versions.MIN_API_VERSION = self.orig_min def test_server_is_too_new(self): fake_client = mock.MagicMock() fake_client.versions.get_current.return_value = mock.MagicMock( max_version="1.7", min_version="1.4") api_versions.MAX_API_VERSION = "1.3" api_versions.MIN_API_VERSION = "1.1" self.assertRaises(exceptions.UnsupportedVersion, api_versions.discover_version, fake_client, api_versions.APIVersion('1.latest')) def test_server_is_too_old(self): fake_client = mock.MagicMock() fake_client.versions.get_current.return_value = mock.MagicMock( max_version="1.7", min_version="1.4") api_versions.MAX_API_VERSION = "1.10" api_versions.MIN_API_VERSION = "1.9" self.assertRaises(exceptions.UnsupportedVersion, api_versions.discover_version, fake_client, api_versions.APIVersion('1.latest')) def test_server_end_version_is_the_latest_one(self): fake_client = mock.MagicMock() fake_client.versions.get_current.return_value = mock.MagicMock( max_version="1.7", min_version="1.4") api_versions.MAX_API_VERSION = "1.11" api_versions.MIN_API_VERSION = "1.1" self.assertEqual( "1.7", api_versions.discover_version( fake_client, api_versions.APIVersion('1.latest')).get_string()) def test_client_end_version_is_the_latest_one(self): fake_client = mock.MagicMock() fake_client.versions.get_current.return_value = mock.MagicMock( max_version="1.16", min_version="1.4") api_versions.MAX_API_VERSION = "1.11" api_versions.MIN_API_VERSION = "1.1" self.assertEqual( "1.11", api_versions.discover_version( fake_client, api_versions.APIVersion('1.latest')).get_string()) def test_server_without_microversion(self): fake_client = mock.MagicMock() fake_client.versions.get_current.return_value = mock.MagicMock( max_version='', min_version='') api_versions.MAX_API_VERSION = "1.11" api_versions.MIN_API_VERSION = "1.1" self.assertEqual( "1.1", api_versions.discover_version( fake_client, api_versions.APIVersion('1.latest')).get_string()) def test_server_without_microversion_and_no_version_field(self): fake_client = mock.MagicMock() fake_client.versions.get_current.return_value = versions.Version( None, {}) api_versions.MAX_API_VERSION = "1.11" api_versions.MIN_API_VERSION = "1.1" self.assertEqual( "1.1", api_versions.discover_version( fake_client, api_versions.APIVersion('1.latest')).get_string()) python-zunclient-4.0.0/zunclient/tests/unit/base.py0000664000175000017500000000354713643163457022534 0ustar zuulzuul00000000000000 # Copyright 2010-2011 OpenStack Foundation # 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 os import fixtures import testtools _TRUE_VALUES = ('true', '1', 'yes') class TestCase(testtools.TestCase): """Test case base class for all unit tests.""" def setUp(self): """Run before each test method to initialize test environment.""" super(TestCase, self).setUp() test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0) try: test_timeout = int(test_timeout) except ValueError: # If timeout value is invalid do not set a timeout. test_timeout = 0 if test_timeout > 0: self.useFixture(fixtures.Timeout(test_timeout, gentle=True)) self.useFixture(fixtures.NestedTempfile()) self.useFixture(fixtures.TempHomeDir()) if os.environ.get('OS_STDOUT_CAPTURE') in _TRUE_VALUES: stdout = self.useFixture(fixtures.StringStream('stdout')).stream self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout)) if os.environ.get('OS_STDERR_CAPTURE') in _TRUE_VALUES: stderr = self.useFixture(fixtures.StringStream('stderr')).stream self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr)) self.log_fixture = self.useFixture(fixtures.FakeLogger()) python-zunclient-4.0.0/zunclient/tests/unit/v1/0000775000175000017500000000000013643163533021560 5ustar zuulzuul00000000000000python-zunclient-4.0.0/zunclient/tests/unit/v1/test_versions.py0000664000175000017500000000311013643163457025041 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. import testtools from testtools import matchers from zunclient.tests.unit import utils from zunclient.v1 import versions VERSION1 = {'status': 'CURRENT', 'min_version': '1.1', 'max_version': '1.12', 'id': 'v1', } fake_responses = { 'https://example.com': { 'GET': ( {}, {'versions': [VERSION1]}, ), }, } class VersionManagerTest(testtools.TestCase): def test_version_list(self): self._test_version_list('https://example.com') self._test_version_list('https://example.com/v1') self._test_version_list('https://example.com/v1/') def _test_version_list(self, endpoint): api = utils.FakeAPI(fake_responses, endpoint=endpoint) mgr = versions.VersionManager(api) api_versions = mgr.list() expect = [ ('GET', 'https://example.com', {}, None), ] self.assertEqual(expect, api.calls) self.assertThat(api_versions, matchers.HasLength(1)) python-zunclient-4.0.0/zunclient/tests/unit/v1/test_containers.py0000664000175000017500000005660613643163457025360 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. import copy from six.moves.urllib import parse import testtools from testtools import matchers from zunclient.common import utils as zun_utils from zunclient import exceptions from zunclient.tests.unit import utils from zunclient.v1 import containers CONTAINER1 = {'id': '1234', 'uuid': '36e527e4-6d03-4eda-9443-904424043741', 'name': 'test1', 'image_pull_policy': 'never', 'image': 'cirros', 'command': 'sh -c "echo hello"', 'cpu': '1', 'memory': '256', 'environment': {'hostname': 'zunsystem'}, 'workdir': '/', 'labels': {'label1': 'foo'}, 'hints': {'hint1': 'bar'}, 'restart_policy': 'no', 'security_groups': ['test'], 'auto_remove': True, 'runtime': 'runc', 'hostname': 'testhost', 'disk': '20', 'auto_heal': False, 'privileged': False, 'healthcheck': {} } CONTAINER2 = {'id': '1235', 'uuid': 'c7f9da0f-581b-4586-8d0d-a6c894822165', 'name': 'test2', 'image_pull_policy': 'ifnotpresent', 'image': 'cirros', 'command': 'sleep 100000000', 'cpu': '1', 'memory': '256', 'environment': {'hostname': 'zunsystem'}, 'workdir': '/', 'labels': {'label2': 'foo'}, 'hints': {'hint2': 'bar'}, 'restart_policy': 'on-failure:5', 'security_groups': ['test'], 'auto_remove': False, 'runtime': 'runc', 'hostname': 'testhost', 'auto_heal': False, 'privileged': True, 'healthcheck': {} } NETWORK1 = {'net_id': '99e90853-e1fd-4c57-a116-9e335deaa592', 'port_id': '83f39a10-45c8-4463-a274-5a7cda3e6c97', 'fixed_ips': [{ 'ip_address': '10.0.0.7', 'version': 4, 'subnet_id': '5899aa85-c98f-4d1d-bc8f-99fed7bde5b9'}] } CREATE_CONTAINER1 = copy.deepcopy(CONTAINER1) del CREATE_CONTAINER1['id'] del CREATE_CONTAINER1['uuid'] force_delete1 = False force_delete2 = True all_projects = True signal = "SIGTERM" name = "new-name" timeout = 10 tty_height = "56" tty_width = "121" path = "/tmp/test.txt" data = "/tmp/test.tar" repo = "repo-test" tag = "tag-test" security_group = "testsg" fake_responses = { '/v1/containers': { 'GET': ( {}, {'containers': [CONTAINER1, CONTAINER2]}, ), 'POST': ( {}, CREATE_CONTAINER1, ), }, '/v1/containers/?limit=2': { 'GET': ( {}, {'containers': [CONTAINER1, CONTAINER2]}, ), }, '/v1/containers/?marker=%s' % CONTAINER2['uuid']: { 'GET': ( {}, {'containers': [CONTAINER1, CONTAINER2]}, ), }, '/v1/containers/?limit=2&marker=%s' % CONTAINER2['uuid']: { 'GET': ( {}, {'containers': [CONTAINER1, CONTAINER2]}, ), }, '/v1/containers/?sort_dir=asc': { 'GET': ( {}, {'containers': [CONTAINER1, CONTAINER2]}, ), }, '/v1/containers/?sort_key=uuid': { 'GET': ( {}, {'containers': [CONTAINER1, CONTAINER2]}, ), }, '/v1/containers/?sort_key=uuid&sort_dir=desc': { 'GET': ( {}, {'containers': [CONTAINER1, CONTAINER2]}, ), }, '/v1/containers/%s' % CONTAINER1['id']: { 'GET': ( {}, CONTAINER1 ), }, '/v1/containers/%s' % CONTAINER1['name']: { 'GET': ( {}, CONTAINER1 ), }, '/v1/containers/%s/start' % CONTAINER1['id']: { 'POST': ( {}, None, ), }, '/v1/containers/%s?force=%s' % (CONTAINER1['id'], force_delete1): { 'DELETE': ( {}, None, ), }, '/v1/containers/%s?force=%s' % (CONTAINER1['id'], force_delete2): { 'DELETE': ( {}, None, ), }, '/v1/containers/%s?all_projects=%s' % (CONTAINER1['id'], all_projects): { 'DELETE': ( {}, None, ), }, '/v1/containers/%s/stop?timeout=10' % CONTAINER1['id']: { 'POST': ( {}, None, ), }, '/v1/containers/%s/reboot?timeout=10' % CONTAINER1['id']: { 'POST': ( {}, None, ), }, '/v1/containers/%s/pause' % CONTAINER1['id']: { 'POST': ( {}, None, ), }, '/v1/containers/%s/unpause' % CONTAINER1['id']: { 'POST': ( {}, None, ), }, '/v1/containers/%s/logs?%s' % (CONTAINER1['id'], parse.urlencode({'stdout': True, 'stderr': True, 'timestamps': False, 'tail': 'all', 'since': None})): { 'GET': ( {}, None, ), }, '/v1/containers/%s/execute?%s' % (CONTAINER1['id'], parse.urlencode({'command': CONTAINER1['command']})): { 'POST': ( {}, None, ), }, '/v1/containers/%s/kill?%s' % (CONTAINER1['id'], parse.urlencode({'signal': signal})): { 'POST': ( {}, None, ), }, '/v1/containers?run=true': { 'POST': ( {}, CREATE_CONTAINER1, ), }, '/v1/containers/%s/rename?%s' % (CONTAINER1['id'], parse.urlencode({'name': name})): { 'POST': ( {}, None, ), }, '/v1/containers/%s/attach' % CONTAINER1['id']: { 'GET': ( {}, None, ), }, '/v1/containers/%s/resize?w=%s&h=%s' % (CONTAINER1['id'], tty_width, tty_height): { 'POST': ( {}, None, ), }, '/v1/containers/%s/resize?h=%s&w=%s' % (CONTAINER1['id'], tty_height, tty_width): { 'POST': ( {}, None, ), }, '/v1/containers/%s/top?ps_args=None' % (CONTAINER1['id']): { 'GET': ( {}, None, ), }, '/v1/containers/%s/get_archive?%s' % (CONTAINER1['id'], parse.urlencode({'path': path})): { 'GET': ( {}, {'data': data}, ), }, '/v1/containers/%s/put_archive?%s' % (CONTAINER1['id'], parse.urlencode({'path': path})): { 'POST': ( {}, None, ), }, '/v1/containers/%s/stats?%s' % (CONTAINER1['id'], parse.urlencode({'decode': False, 'stream': False})): { 'GET': ( {}, None, ), }, '/v1/containers/%s/commit?%s' % (CONTAINER1['id'], parse.urlencode({'repository': repo, 'tag': tag})): { 'POST': ( {}, None, ), }, '/v1/containers/%s/add_security_group?%s' % (CONTAINER1['id'], parse.urlencode({'name': security_group})): { 'POST': ( {}, None, ), }, '/v1/containers/%s/network_detach?%s' % (CONTAINER1['id'], parse.urlencode({'network': 'neutron_network'})): { 'POST': ( {}, None, ), }, '/v1/containers/%s/network_attach?%s' % (CONTAINER1['id'], parse.urlencode({'network': 'neutron_network'})): { 'POST': ( {}, None, ), }, '/v1/containers/%s/network_list' % (CONTAINER1['id']): { 'GET': ( {}, {'networks': NETWORK1}, ), }, '/v1/containers/%s/remove_security_group?%s' % (CONTAINER1['id'], parse.urlencode({'name': security_group})): { 'POST': ( {}, None, ), }, '/v1/containers/%s/rebuild?image=cirros' % (CONTAINER1['id']): { 'POST': ( {}, None, ), }, } class ContainerManagerTest(testtools.TestCase): def setUp(self): super(ContainerManagerTest, self).setUp() self.api = utils.FakeAPI(fake_responses) self.mgr = containers.ContainerManager(self.api) def test_container_create(self): containers = self.mgr.create(**CREATE_CONTAINER1) expect = [ ('POST', '/v1/containers', {}, CREATE_CONTAINER1) ] self.assertEqual(expect, self.api.calls) self.assertTrue(containers) def test_container_create_fail(self): create_container_fail = copy.deepcopy(CREATE_CONTAINER1) create_container_fail["wrong_key"] = "wrong" self.assertRaisesRegex(exceptions.InvalidAttribute, ("Key must be in %s" % ','.join(containers.CREATION_ATTRIBUTES)), self.mgr.create, **create_container_fail) self.assertEqual([], self.api.calls) def test_containers_list(self): containers = self.mgr.list() expect = [ ('GET', '/v1/containers', {}, None), ] self.assertEqual(expect, self.api.calls) self.assertThat(containers, matchers.HasLength(2)) def _test_containers_list_with_filters(self, limit=None, marker=None, sort_key=None, sort_dir=None, expect=[]): containers_filter = self.mgr.list(limit=limit, marker=marker, sort_key=sort_key, sort_dir=sort_dir) self.assertEqual(expect, self.api.calls) self.assertThat(containers_filter, matchers.HasLength(2)) def test_containers_list_with_limit(self): expect = [ ('GET', '/v1/containers/?limit=2', {}, None), ] self._test_containers_list_with_filters( limit=2, expect=expect) def test_containers_list_with_marker(self): expect = [ ('GET', '/v1/containers/?marker=%s' % CONTAINER2['uuid'], {}, None), ] self._test_containers_list_with_filters( marker=CONTAINER2['uuid'], expect=expect) def test_containers_list_with_marker_limit(self): expect = [ ('GET', '/v1/containers/?limit=2&marker=%s' % CONTAINER2['uuid'], {}, None), ] self._test_containers_list_with_filters( limit=2, marker=CONTAINER2['uuid'], expect=expect) def test_coontainer_list_with_sort_dir(self): expect = [ ('GET', '/v1/containers/?sort_dir=asc', {}, None), ] self._test_containers_list_with_filters( sort_dir='asc', expect=expect) def test_container_list_with_sort_key(self): expect = [ ('GET', '/v1/containers/?sort_key=uuid', {}, None), ] self._test_containers_list_with_filters( sort_key='uuid', expect=expect) def test_container_list_with_sort_key_dir(self): expect = [ ('GET', '/v1/containers/?sort_key=uuid&sort_dir=desc', {}, None), ] self._test_containers_list_with_filters( sort_key='uuid', sort_dir='desc', expect=expect) def test_container_show_by_id(self): container = self.mgr.get(CONTAINER1['id']) expect = [ ('GET', '/v1/containers/%s' % CONTAINER1['id'], {}, None) ] self.assertEqual(expect, self.api.calls) self.assertEqual(CONTAINER1['name'], container.name) self.assertEqual(CONTAINER1['uuid'], container.uuid) def test_container_show_by_name(self): container = self.mgr.get(CONTAINER1['name']) expect = [ ('GET', '/v1/containers/%s' % CONTAINER1['name'], {}, None) ] self.assertEqual(expect, self.api.calls) self.assertEqual(CONTAINER1['name'], container.name) self.assertEqual(CONTAINER1['uuid'], container.uuid) def test_containers_start(self): containers = self.mgr.start(CONTAINER1['id']) expect = [ ('POST', '/v1/containers/%s/start' % CONTAINER1['id'], {'Content-Length': '0'}, None) ] self.assertEqual(expect, self.api.calls) self.assertTrue(containers) def test_containers_delete(self): containers = self.mgr.delete(CONTAINER1['id'], force=force_delete1) expect = [ ('DELETE', '/v1/containers/%s?force=%s' % (CONTAINER1['id'], force_delete1), {}, None) ] self.assertEqual(expect, self.api.calls) self.assertIsNone(containers) def test_containers_delete_with_force(self): containers = self.mgr.delete(CONTAINER1['id'], force=force_delete2) expect = [ ('DELETE', '/v1/containers/%s?force=%s' % (CONTAINER1['id'], force_delete2), {}, None) ] self.assertEqual(expect, self.api.calls) self.assertIsNone(containers) def test_containers_delete_with_all_projects(self): containers = self.mgr.delete(CONTAINER1['id'], all_projects=all_projects) expect = [ ('DELETE', '/v1/containers/%s?all_projects=%s' % (CONTAINER1['id'], all_projects), {}, None) ] self.assertEqual(expect, self.api.calls) self.assertIsNone(containers) def test_containers_stop(self): containers = self.mgr.stop(CONTAINER1['id'], timeout) expect = [ ('POST', '/v1/containers/%s/stop?timeout=10' % CONTAINER1['id'], {'Content-Length': '0'}, None) ] self.assertEqual(expect, self.api.calls) self.assertTrue(containers) def test_containers_restart(self): containers = self.mgr.restart(CONTAINER1['id'], timeout) expect = [ ('POST', '/v1/containers/%s/reboot?timeout=10' % CONTAINER1['id'], {'Content-Length': '0'}, None) ] self.assertEqual(expect, self.api.calls) self.assertTrue(containers) def test_containers_pause(self): containers = self.mgr.pause(CONTAINER1['id']) expect = [ ('POST', '/v1/containers/%s/pause' % CONTAINER1['id'], {'Content-Length': '0'}, None) ] self.assertEqual(expect, self.api.calls) self.assertTrue(containers) def test_containers_unpause(self): containers = self.mgr.unpause(CONTAINER1['id']) expect = [ ('POST', '/v1/containers/%s/unpause' % CONTAINER1['id'], {'Content-Length': '0'}, None) ] self.assertEqual(expect, self.api.calls) self.assertTrue(containers) def test_containers_logs(self): containers = self.mgr.logs(CONTAINER1['id'], stdout=True, stderr=True, timestamps=False, tail='all', since=None) expect = [ ('GET', '/v1/containers/%s/logs?%s' % (CONTAINER1['id'], parse.urlencode({'stdout': True, 'stderr': True, 'timestamps': False, 'tail': 'all', 'since': None})), {'Content-Length': '0'}, None) ] self.assertEqual(expect, self.api.calls) self.assertIsNone(containers) def test_containers_execute(self): containers = self.mgr.execute(CONTAINER1['id'], command=CONTAINER1['command']) expect = [ ('POST', '/v1/containers/%s/execute?%s' % (CONTAINER1['id'], parse.urlencode({'command': CONTAINER1['command']})), {'Content-Length': '0'}, None) ] self.assertEqual(expect, self.api.calls) self.assertIsNone(containers) def test_containers_kill(self): containers = self.mgr.kill(CONTAINER1['id'], signal) expect = [ ('POST', '/v1/containers/%s/kill?%s' % (CONTAINER1['id'], parse.urlencode({'signal': signal})), {'Content-Length': '0'}, None) ] self.assertEqual(expect, self.api.calls) self.assertIsNone(containers) def test_container_run(self): containers = self.mgr.run(**CREATE_CONTAINER1) expect = [ ('POST', '/v1/containers?run=true', {}, CREATE_CONTAINER1) ] self.assertEqual(expect, self.api.calls) self.assertTrue(containers) def test_container_run_fail(self): run_container_fail = copy.deepcopy(CREATE_CONTAINER1) run_container_fail["wrong_key"] = "wrong" self.assertRaisesRegex(exceptions.InvalidAttribute, ("Key must be in %s" % ','.join(containers.CREATION_ATTRIBUTES)), self.mgr.run, **run_container_fail) self.assertEqual([], self.api.calls) def test_containers_rename(self): containers = self.mgr.rename(CONTAINER1['id'], name) expect = [ ('POST', '/v1/containers/%s/rename?%s' % (CONTAINER1['id'], parse.urlencode({'name': name})), {'Content-Length': '0'}, None) ] self.assertEqual(expect, self.api.calls) self.assertTrue(containers) def test_containers_attach(self): containers = self.mgr.attach(CONTAINER1['id']) expect = [ ('GET', '/v1/containers/%s/attach' % CONTAINER1['id'], {'Content-Length': '0'}, None) ] self.assertEqual(expect, self.api.calls) self.assertIsNone(containers) def test_containers_resize(self): containers = self.mgr.resize(CONTAINER1['id'], tty_width, tty_height) expects = [] expects.append([ ('POST', '/v1/containers/%s/resize?w=%s&h=%s' % (CONTAINER1['id'], tty_width, tty_height), {'Content-Length': '0'}, None) ]) expects.append([ ('POST', '/v1/containers/%s/resize?h=%s&w=%s' % (CONTAINER1['id'], tty_height, tty_width), {'Content-Length': '0'}, None) ]) self.assertTrue(self.api.calls in expects) self.assertIsNone(containers) def test_containers_top(self): containers = self.mgr.top(CONTAINER1['id']) expect = [ ('GET', '/v1/containers/%s/top?ps_args=None' % CONTAINER1['id'], {'Content-Length': '0'}, None) ] self.assertEqual(expect, self.api.calls) self.assertIsNone(containers) def test_containers_get_archive(self): response = self.mgr.get_archive(CONTAINER1['id'], path) expect = [ ('GET', '/v1/containers/%s/get_archive?%s' % (CONTAINER1['id'], parse.urlencode({'path': path})), {'Content-Length': '0'}, None) ] self.assertEqual(expect, self.api.calls) self.assertEqual(zun_utils.decode_file_data(data), response['data']) def test_containers_put_archive(self): response = self.mgr.put_archive(CONTAINER1['id'], path, data) expect = [ ('POST', '/v1/containers/%s/put_archive?%s' % (CONTAINER1['id'], parse.urlencode({'path': path})), {'Content-Length': '0'}, {'data': zun_utils.encode_file_data(data)}) ] self.assertEqual(expect, self.api.calls) self.assertTrue(response) def test_containers_commit(self): containers = self.mgr.commit(CONTAINER1['id'], repo, tag) expect = [ ('POST', '/v1/containers/%s/commit?%s' % (CONTAINER1['id'], parse.urlencode({'repository': repo, 'tag': tag})), {'Content-Length': '0'}, None) ] self.assertEqual(expect, self.api.calls) self.assertIsNone(containers) def test_containers_add_security_group(self): containers = self.mgr.add_security_group( CONTAINER1['id'], security_group) expect = [ ('POST', '/v1/containers/%s/add_security_group?%s' % (CONTAINER1['id'], parse.urlencode({'name': security_group})), {'Content-Length': '0'}, None) ] self.assertEqual(expect, self.api.calls) self.assertTrue(containers) def test_containers_network_detach(self): containers = self.mgr.network_detach( CONTAINER1['id'], network='neutron_network') expect = [ ('POST', '/v1/containers/%s/network_detach?%s' % (CONTAINER1['id'], parse.urlencode({'network': 'neutron_network'})), {'Content-Length': '0'}, None) ] self.assertEqual(expect, self.api.calls) self.assertTrue(containers) def test_containers_network_attach(self): containers = self.mgr.network_attach( CONTAINER1['id'], network='neutron_network') expect = [ ('POST', '/v1/containers/%s/network_attach?%s' % (CONTAINER1['id'], parse.urlencode({'network': 'neutron_network'})), {'Content-Length': '0'}, None) ] self.assertEqual(expect, self.api.calls) self.assertTrue(containers) def test_containers_network_list(self): networks = self.mgr.network_list(CONTAINER1['id']) expect = [ ('GET', '/v1/containers/%s/network_list' % (CONTAINER1['id']), {}, None) ] self.assertEqual(expect, self.api.calls) self.assertTrue(networks) def test_containers_remove_security_group(self): containers = self.mgr.remove_security_group( CONTAINER1['id'], security_group) expect = [ ('POST', '/v1/containers/%s/remove_security_group?%s' % (CONTAINER1['id'], parse.urlencode({'name': security_group})), {'Content-Length': '0'}, None) ] self.assertEqual(expect, self.api.calls) self.assertTrue(containers) def test_containers_rebuild(self): opts = {'image': 'cirros'} self.mgr.rebuild(CONTAINER1['id'], **opts) expect = [ ('POST', '/v1/containers/%s/rebuild?image=cirros' % (CONTAINER1['id']), {'Content-Length': '0'}, None) ] self.assertEqual(expect, self.api.calls) python-zunclient-4.0.0/zunclient/tests/unit/v1/shell_test_base.py0000664000175000017500000000557713643163457025315 0ustar zuulzuul00000000000000# Copyright 2015 NEC Corporation. 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 from unittest import mock from testtools import matchers from zunclient import api_versions from zunclient.tests.unit import utils FAKE_ENV = {'OS_USERNAME': 'username', 'OS_PASSWORD': 'password', 'OS_PROJECT_NAME': 'project_name', 'OS_AUTH_URL': 'http://no.where/v2.0', 'BYPASS_URL': 'http://zun'} class TestCommandLineArgument(utils.TestCase): _unrecognized_arg_error = [ '.*?^usage: ', '.*?^error: unrecognized arguments:', ".*?^Try 'zun help ' for more information.", ] _mandatory_arg_error = [ '.*?^usage: ', '.*?^error: (the following arguments|argument)', ".*?^Try 'zun help ", ] _few_argument_error = [ '.*?^usage: zun ', '.*?^error: (the following arguments|too few arguments)', ".*?^Try 'zun help ", ] _invalid_value_error = [ '.*?^usage: ', '.*?^error: argument .*: invalid .* value:', ".*?^Try 'zun help ", ] _invalid_choice_error = [ '.*?^usage: ', '.*?^error: argument .*: invalid choice:', ".*?^Try 'zun help ", ] def setUp(self): super(TestCommandLineArgument, self).setUp() self.make_env(fake_env=FAKE_ENV) session_client = mock.patch( 'zunclient.common.httpclient.SessionClient') session_client.start() loader = mock.patch('keystoneauth1.loading.get_plugin_loader') loader.start() session = mock.patch('keystoneauth1.session.Session') session.start() discover = mock.patch('zunclient.api_versions.discover_version', return_value=api_versions.APIVersion('1.1')) discover.start() self.addCleanup(session_client.stop) self.addCleanup(loader.stop) self.addCleanup(session.stop) self.addCleanup(discover.stop) def _test_arg_success(self, command): stdout, stderr = self.shell(command) def _test_arg_failure(self, command, error_msg): stdout, stderr = self.shell(command, (2,)) for line in error_msg: self.assertThat((stdout + stderr), matchers.MatchesRegex(line, re.DOTALL | re.MULTILINE)) python-zunclient-4.0.0/zunclient/tests/unit/v1/test_images.py0000664000175000017500000001255213643163457024450 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. import testtools from testtools import matchers from zunclient.tests.unit import utils from zunclient.v1 import images IMAGE1 = {'uuid': '092e2ed7-af11-4fa7-8ffa-c3ee9d5b451a', 'image_id': 'b8ff79200466', 'repo': 'fake-repo1', 'tag': 'latest', 'size': '1024', } IMAGE2 = {'uuid': '1996ba70-b074-454b-a8fc-0895ae26c7c6', 'image_id': '21c16b6787c6', 'repo': 'fake-repo2', 'tag': 'latest', 'size': '1024', } IMAGE3 = {'uuid': '1267gf34-34e4-tf4b-b84c-1345avf6c7c6', 'image_id': '24r5tt6y87c6', 'image': 'fake-name3', 'tag': 'latest', 'size': '1024', 'image_driver': 'fake-driver', } SEARCH_IMAGE = {'image': 'fake-name3', 'image_driver': 'fake-driver', } fake_responses = { '/v1/images/': { 'GET': ( {}, {'images': [IMAGE1, IMAGE2]}, ), }, '/v1/images/?limit=2': { 'GET': ( {}, {'images': [IMAGE1, IMAGE2]}, ), }, '/v1/images/?marker=%s' % IMAGE2['image_id']: { 'GET': ( {}, {'images': [IMAGE1, IMAGE2]}, ), }, '/v1/images/?limit=2&marker=%s' % IMAGE2['image_id']: { 'GET': ( {}, {'images': [IMAGE2, IMAGE1]}, ), }, '/v1/images/?sort_dir=asc': { 'GET': ( {}, {'images': [IMAGE1, IMAGE2]}, ), }, '/v1/images/?sort_key=image_id': { 'GET': ( {}, {'images': [IMAGE1, IMAGE2]}, ), }, '/v1/images/?sort_key=image_id&sort_dir=desc': { 'GET': ( {}, {'images': [IMAGE2, IMAGE1]}, ), }, '/v1/images/%s/search?image_driver=%s' % (IMAGE3['image'], IMAGE3['image_driver']): { 'GET': ( {}, {'images': [IMAGE3]}, ), }, } class ImageManagerTest(testtools.TestCase): def setUp(self): super(ImageManagerTest, self).setUp() self.api = utils.FakeAPI(fake_responses) self.mgr = images.ImageManager(self.api) def test_image_list(self): images = self.mgr.list() expect = [ ('GET', '/v1/images/', {}, None), ] self.assertEqual(expect, self.api.calls) self.assertThat(images, matchers.HasLength(2)) def _test_image_list_with_filters( self, limit=None, marker=None, sort_key=None, sort_dir=None, expect=[]): images_filter = self.mgr.list(limit=limit, marker=marker, sort_key=sort_key, sort_dir=sort_dir) self.assertEqual(expect, self.api.calls) self.assertThat(images_filter, matchers.HasLength(2)) def test_image_list_with_limit(self): expect = [ ('GET', '/v1/images/?limit=2', {}, None), ] self._test_image_list_with_filters( limit=2, expect=expect) def test_image_list_with_marker(self): expect = [ ('GET', '/v1/images/?marker=%s' % IMAGE2['image_id'], {}, None), ] self._test_image_list_with_filters( marker=IMAGE2['image_id'], expect=expect) def test_image_list_with_marker_limit(self): expect = [ ('GET', '/v1/images/?limit=2&marker=%s' % IMAGE2['image_id'], {}, None), ] self._test_image_list_with_filters( limit=2, marker=IMAGE2['image_id'], expect=expect) def test_image_list_with_sort_dir(self): expect = [ ('GET', '/v1/images/?sort_dir=asc', {}, None), ] self._test_image_list_with_filters( sort_dir='asc', expect=expect) def test_image_list_with_sort_key(self): expect = [ ('GET', '/v1/images/?sort_key=image_id', {}, None), ] self._test_image_list_with_filters( sort_key='image_id', expect=expect) def test_image_list_with_sort_key_dir(self): expect = [ ('GET', '/v1/images/?sort_key=image_id&sort_dir=desc', {}, None), ] self._test_image_list_with_filters( sort_key='image_id', sort_dir='desc', expect=expect) def test_image_search(self): images = self.mgr.search_image(**SEARCH_IMAGE) url = '/v1/images/%s/search?image_driver=%s' \ % (IMAGE3['image'], IMAGE3['image_driver']) expect = [ ('GET', url, {}, None), ] self.assertEqual(expect, self.api.calls) self.assertThat(images, matchers.HasLength(1)) python-zunclient-4.0.0/zunclient/tests/unit/v1/test_quotas.py0000664000175000017500000000434113643163457024514 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. import testtools from zunclient.tests.unit import utils from zunclient.v1 import quotas DEFAULT_QUOTAS = { 'containers': '40', 'memory': '51200', 'cpu': '20', 'disk': '100' } MODIFIED_QUOTAS = { 'containers': '50', 'memory': '51200', 'cpu': '20', 'disk': '100' } MODIFIED_USAGE_QUOTAS = { 'containers': { 'limit': '50', 'in_use': '30' }, 'memory': {}, 'cpu': {}, 'disk': {} } fake_responses = { '/v1/quotas/test_project_id': { 'GET': ( {}, MODIFIED_QUOTAS ), 'PUT': ( {}, MODIFIED_QUOTAS ), 'DELETE': ( {}, None ) }, '/v1/quotas/test_project_id/defaults': { 'GET': ( {}, DEFAULT_QUOTAS ) }, '/v1/quotas/test_project_id?usages=True': { 'GET': ( {}, MODIFIED_USAGE_QUOTAS ) } } class QuotaManagerTest(testtools.TestCase): def setUp(self): super(QuotaManagerTest, self).setUp() self.api = utils.FakeAPI(fake_responses) self.mgr = quotas.QuotaManager(self.api) def test_quotas_get_defaults(self): quotas = self.mgr.defaults('test_project_id') expect = [ ('GET', '/v1/quotas/test_project_id/defaults', {}, None) ] self.assertEqual(expect, self.api.calls) self.assertEqual(quotas.containers, DEFAULT_QUOTAS['containers']) self.assertEqual(quotas.memory, DEFAULT_QUOTAS['memory']) self.assertEqual(quotas.cpu, DEFAULT_QUOTAS['cpu']) self.assertEqual(quotas.disk, DEFAULT_QUOTAS['disk']) python-zunclient-4.0.0/zunclient/tests/unit/v1/test_registries.py0000664000175000017500000001621013643163457025356 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. import copy import testtools from testtools import matchers from zunclient import exceptions from zunclient.tests.unit import utils from zunclient.v1 import registries REGISTRY1 = {'uuid': 'cb9d9848-6c12-411b-a915-6eadc7bd2871', 'name': 'test1', 'domain': 'testdomain.io', 'username': 'testuser1', 'password': 'testpassword1', } REGISTRY2 = {'uuid': 'c2d923ef-e9b5-4e6b-a8aa-7c54a715b370', 'name': 'test2', 'domain': 'otherdomain.io', 'username': 'testuser2', 'password': 'testpassword2', } CREATE_REGISTRY1 = copy.deepcopy(REGISTRY1) del CREATE_REGISTRY1['uuid'] UPDATE_REGISTRY1 = copy.deepcopy(REGISTRY1) del UPDATE_REGISTRY1['uuid'] UPDATE_REGISTRY1['name'] = 'newname' fake_responses = { '/v1/registries': { 'GET': ( {}, {'registries': [REGISTRY1, REGISTRY2]}, ), 'POST': ( {}, {'registry': CREATE_REGISTRY1}, ), }, '/v1/registries/?limit=2': { 'GET': ( {}, {'registries': [REGISTRY1, REGISTRY2]}, ), }, '/v1/registries/?marker=%s' % REGISTRY2['uuid']: { 'GET': ( {}, {'registries': [REGISTRY1, REGISTRY2]}, ), }, '/v1/registries/?limit=2&marker=%s' % REGISTRY2['uuid']: { 'GET': ( {}, {'registries': [REGISTRY1, REGISTRY2]}, ), }, '/v1/registries/?sort_dir=asc': { 'GET': ( {}, {'registries': [REGISTRY1, REGISTRY2]}, ), }, '/v1/registries/?sort_key=uuid': { 'GET': ( {}, {'registries': [REGISTRY1, REGISTRY2]}, ), }, '/v1/registries/?sort_key=uuid&sort_dir=desc': { 'GET': ( {}, {'registries': [REGISTRY1, REGISTRY2]}, ), }, '/v1/registries/%s' % REGISTRY1['uuid']: { 'GET': ( {}, {'registry': REGISTRY1} ), 'DELETE': ( {}, None, ), 'PATCH': ( {}, {'registry': UPDATE_REGISTRY1}, ), }, } class RegistryManagerTest(testtools.TestCase): def setUp(self): super(RegistryManagerTest, self).setUp() self.api = utils.FakeAPI(fake_responses) self.mgr = registries.RegistryManager(self.api) def test_registry_create(self): registries = self.mgr.create(**CREATE_REGISTRY1) expect = [ ('POST', '/v1/registries', {}, {'registry': CREATE_REGISTRY1}) ] self.assertEqual(expect, self.api.calls) self.assertTrue(registries) def test_registry_create_fail(self): create_registry_fail = copy.deepcopy(CREATE_REGISTRY1) create_registry_fail["wrong_key"] = "wrong" self.assertRaisesRegex(exceptions.InvalidAttribute, ("Key must be in %s" % ','.join(registries.CREATION_ATTRIBUTES)), self.mgr.create, **create_registry_fail) self.assertEqual([], self.api.calls) def test_registries_list(self): registries = self.mgr.list() expect = [ ('GET', '/v1/registries', {}, None), ] self.assertEqual(expect, self.api.calls) self.assertThat(registries, matchers.HasLength(2)) def _test_registries_list_with_filters(self, limit=None, marker=None, sort_key=None, sort_dir=None, expect=[]): registries_filter = self.mgr.list(limit=limit, marker=marker, sort_key=sort_key, sort_dir=sort_dir) self.assertEqual(expect, self.api.calls) self.assertThat(registries_filter, matchers.HasLength(2)) def test_registries_list_with_limit(self): expect = [ ('GET', '/v1/registries/?limit=2', {}, None), ] self._test_registries_list_with_filters( limit=2, expect=expect) def test_registries_list_with_marker(self): expect = [ ('GET', '/v1/registries/?marker=%s' % REGISTRY2['uuid'], {}, None), ] self._test_registries_list_with_filters( marker=REGISTRY2['uuid'], expect=expect) def test_registries_list_with_marker_limit(self): expect = [ ('GET', '/v1/registries/?limit=2&marker=%s' % REGISTRY2['uuid'], {}, None), ] self._test_registries_list_with_filters( limit=2, marker=REGISTRY2['uuid'], expect=expect) def test_coontainer_list_with_sort_dir(self): expect = [ ('GET', '/v1/registries/?sort_dir=asc', {}, None), ] self._test_registries_list_with_filters( sort_dir='asc', expect=expect) def test_registry_list_with_sort_key(self): expect = [ ('GET', '/v1/registries/?sort_key=uuid', {}, None), ] self._test_registries_list_with_filters( sort_key='uuid', expect=expect) def test_registry_list_with_sort_key_dir(self): expect = [ ('GET', '/v1/registries/?sort_key=uuid&sort_dir=desc', {}, None), ] self._test_registries_list_with_filters( sort_key='uuid', sort_dir='desc', expect=expect) def test_registry_show(self): registry = self.mgr.get(REGISTRY1['uuid']) expect = [ ('GET', '/v1/registries/%s' % REGISTRY1['uuid'], {}, None) ] self.assertEqual(expect, self.api.calls) self.assertEqual(REGISTRY1['name'], registry._info['registry']['name']) self.assertEqual(REGISTRY1['uuid'], registry._info['registry']['uuid']) def test_registry_update(self): registry = self.mgr.update(REGISTRY1['uuid'], **UPDATE_REGISTRY1) expect = [ ('PATCH', '/v1/registries/%s' % REGISTRY1['uuid'], {}, {'registry': UPDATE_REGISTRY1}) ] self.assertEqual(expect, self.api.calls) self.assertEqual(UPDATE_REGISTRY1['name'], registry._info['registry']['name']) def test_registries_delete(self): registries = self.mgr.delete(REGISTRY1['uuid']) expect = [ ('DELETE', '/v1/registries/%s' % (REGISTRY1['uuid']), {}, None) ] self.assertEqual(expect, self.api.calls) self.assertIsNone(registries) python-zunclient-4.0.0/zunclient/tests/unit/v1/test_images_shell.py0000664000175000017500000000471213643163457025636 0ustar zuulzuul00000000000000# Copyright 2015 NEC Corporation. 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. from unittest import mock from zunclient.tests.unit.v1 import shell_test_base class ShellTest(shell_test_base.TestCommandLineArgument): @mock.patch('zunclient.v1.images.ImageManager.list') def test_zun_image_list_success(self, mock_list): self._test_arg_success('image-list') self.assertTrue(mock_list.called) @mock.patch('zunclient.v1.images.ImageManager.list') def test_zun_image_list_failure(self, mock_list): self._test_arg_failure('image-list --wrong', self._unrecognized_arg_error) self.assertFalse(mock_list.called) @mock.patch('zunclient.v1.images.ImageManager.get') def test_zun_image_show_success(self, mock_get): self._test_arg_success('image-show 111') self.assertTrue(mock_get.called) @mock.patch('zunclient.v1.images.ImageManager.get') def test_zun_image_show_failure(self, mock_get): self._test_arg_failure('image-show --wrong 1111', self._unrecognized_arg_error) self.assertFalse(mock_get.called) @mock.patch('zunclient.v1.images.ImageManager.search_image') def test_zun_image_search_with_driver(self, mock_search_image): self._test_arg_success('image-search 111 --image_driver glance') self.assertTrue(mock_search_image.called) @mock.patch('zunclient.v1.images.ImageManager.search_image') def test_zun_image_search_default_driver(self, mock_search_image): self._test_arg_success('image-search 111') self.assertTrue(mock_search_image.called) @mock.patch('zunclient.v1.images.ImageManager.search_image') def test_zun_image_search_failure(self, mock_search_image): self._test_arg_failure('image-search --wrong 1111', self._unrecognized_arg_error) self.assertFalse(mock_search_image.called) python-zunclient-4.0.0/zunclient/tests/unit/v1/test_services_shell.py0000664000175000017500000000234613643163457026215 0ustar zuulzuul00000000000000# Copyright 2015 NEC Corporation. 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. from unittest import mock from zunclient.tests.unit.v1 import shell_test_base class ShellTest(shell_test_base.TestCommandLineArgument): @mock.patch('zunclient.v1.services.ServiceManager.list') def test_zun_service_list_success(self, mock_list): self._test_arg_success('service-list') self.assertTrue(mock_list.called) @mock.patch('zunclient.v1.services.ServiceManager.list') def test_zun_service_list_failure(self, mock_list): self._test_arg_failure('service-list --wrong', self._unrecognized_arg_error) self.assertFalse(mock_list.called) python-zunclient-4.0.0/zunclient/tests/unit/v1/test_client.py0000664000175000017500000001471413643163457024463 0ustar zuulzuul00000000000000# Copyright (c) 2015 Thales Services SAS # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from unittest import mock import testtools from zunclient.v1 import client class ClientTest(testtools.TestCase): @mock.patch('zunclient.common.httpclient.SessionClient') @mock.patch('keystoneauth1.session.Session') def test_init_with_session(self, mock_session, http_client): session = mock.Mock() client.Client(session=session) mock_session.assert_not_called() http_client.assert_called_once_with( interface='public', region_name=None, service_name=None, service_type='container', session=session, api_version=None) @mock.patch('zunclient.common.httpclient.SessionClient') @mock.patch('keystoneauth1.token_endpoint.Token') @mock.patch('keystoneauth1.session.Session') def test_init_with_token_and_url( self, mock_session, mock_token, http_client): mock_auth_plugin = mock.Mock() mock_token.return_value = mock_auth_plugin session = mock.Mock() mock_session.return_value = session client.Client(auth_token='mytoken', endpoint_override='http://myurl/') mock_session.assert_called_once_with( auth=mock_auth_plugin, cert=None, verify=True) http_client.assert_called_once_with( endpoint_override='http://myurl/', interface='public', region_name=None, service_name=None, service_type='container', session=session, api_version=None) @mock.patch('zunclient.common.httpclient.SessionClient') @mock.patch('keystoneauth1.loading.get_plugin_loader') @mock.patch('keystoneauth1.session.Session') def test_init_with_token( self, mock_session, mock_loader, http_client): mock_plugin = mock.Mock() mock_loader.return_value = mock_plugin client.Client(auth_token='mytoken', auth_url='authurl') mock_loader.assert_called_once_with('token') mock_plugin.load_from_options.assert_called_once_with( auth_url='authurl', project_id=None, project_name=None, project_domain_id=None, project_domain_name=None, user_domain_id=None, user_domain_name=None, token='mytoken') http_client.assert_called_once_with( interface='public', region_name=None, service_name=None, service_type='container', session=mock.ANY, api_version=None) @mock.patch('zunclient.common.httpclient.SessionClient') @mock.patch('keystoneauth1.loading.get_plugin_loader') @mock.patch('keystoneauth1.session.Session') def test_init_with_user( self, mock_session, mock_loader, http_client): mock_plugin = mock.Mock() mock_loader.return_value = mock_plugin client.Client(username='myuser', auth_url='authurl') mock_loader.assert_called_once_with('password') mock_plugin.load_from_options.assert_called_once_with( auth_url='authurl', username='myuser', password=None, project_domain_id=None, project_domain_name=None, user_domain_id=None, user_domain_name=None, project_id=None, project_name=None) http_client.assert_called_once_with( interface='public', region_name=None, service_name=None, service_type='container', session=mock.ANY, api_version=None) @mock.patch('zunclient.common.httpclient.SessionClient') @mock.patch('keystoneauth1.loading.get_plugin_loader') @mock.patch('keystoneauth1.session.Session') def test_init_unauthorized( self, mock_session, mock_loader, http_client): mock_plugin = mock.Mock() mock_loader.return_value = mock_plugin mock_session_obj = mock.Mock() mock_session.return_value = mock_session_obj mock_session_obj.get_endpoint.side_effect = Exception() self.assertRaises( RuntimeError, client.Client, username='myuser', auth_url='authurl') mock_loader.assert_called_once_with('password') mock_plugin.load_from_options.assert_called_once_with( auth_url='authurl', username='myuser', password=None, project_domain_id=None, project_domain_name=None, user_domain_id=None, user_domain_name=None, project_id=None, project_name=None) http_client.assert_not_called() @mock.patch('zunclient.common.httpclient.SessionClient') @mock.patch('keystoneauth1.session.Session') def test_init_with_endpoint_override(self, mock_session, http_client): session = mock.Mock() client.Client(session=session, endpoint_override='zunurl') mock_session.assert_not_called() http_client.assert_called_once_with( interface='public', region_name=None, service_name=None, service_type='container', session=session, endpoint_override='zunurl', api_version=None) @mock.patch('zunclient.common.httpclient.SessionClient') @mock.patch('keystoneauth1.session.Session') def test_init_with_zun_url_and_endpoint_override(self, mock_session, http_client): session = mock.Mock() client.Client(session=session, zun_url='zunurl', endpoint_override='zunurl') mock_session.assert_not_called() http_client.assert_called_once_with( interface='public', region_name=None, service_name=None, service_type='container', session=session, endpoint_override='zunurl', api_version=None) python-zunclient-4.0.0/zunclient/tests/unit/v1/test_availability_zones_shell.py0000664000175000017500000000234613643163457030262 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. from unittest import mock from zunclient.tests.unit.v1 import shell_test_base class ShellTest(shell_test_base.TestCommandLineArgument): @mock.patch('zunclient.v1.availability_zones.AvailabilityZoneManager.list') def test_zun_service_list_success(self, mock_list): self._test_arg_success('availability-zone-list') self.assertTrue(mock_list.called) @mock.patch('zunclient.v1.availability_zones.AvailabilityZoneManager.list') def test_zun_service_list_failure(self, mock_list): self._test_arg_failure('availability-zone-list --wrong', self._unrecognized_arg_error) self.assertFalse(mock_list.called) python-zunclient-4.0.0/zunclient/tests/unit/v1/test_services.py0000664000175000017500000001073013643163457025022 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. import testtools from testtools import matchers from zunclient.tests.unit import utils from zunclient.v1 import services SERVICE1 = {'id': 123, 'host': 'fake-host1', 'binary': 'fake-bin1', 'state': 'up', 'availability_zone': 'nova', } SERVICE2 = {'id': 124, 'host': 'fake-host2', 'binary': 'fake-bin2', 'state': 'down', 'availability_zone': 'nova', } fake_responses = { '/v1/services': { 'GET': ( {}, {'services': [SERVICE1, SERVICE2]}, ), }, '/v1/services/?limit=2': { 'GET': ( {}, {'services': [SERVICE1, SERVICE2]}, ), }, '/v1/services/?marker=%s' % SERVICE2['id']: { 'GET': ( {}, {'services': [SERVICE1, SERVICE2]}, ), }, '/v1/services/?limit=2&marker=%s' % SERVICE2['id']: { 'GET': ( {}, {'services': [SERVICE2, SERVICE1]}, ), }, '/v1/services/?sort_dir=asc': { 'GET': ( {}, {'services': [SERVICE1, SERVICE2]}, ), }, '/v1/services/?sort_key=id': { 'GET': ( {}, {'services': [SERVICE1, SERVICE2]}, ), }, '/v1/services/?sort_key=id&sort_dir=desc': { 'GET': ( {}, {'services': [SERVICE2, SERVICE1]}, ), }, } class ServiceManagerTest(testtools.TestCase): def setUp(self): super(ServiceManagerTest, self).setUp() self.api = utils.FakeAPI(fake_responses) self.mgr = services.ServiceManager(self.api) def test_service_list(self): services = self.mgr.list() expect = [ ('GET', '/v1/services', {}, None), ] self.assertEqual(expect, self.api.calls) self.assertThat(services, matchers.HasLength(2)) def _test_service_list_with_filters( self, limit=None, marker=None, sort_key=None, sort_dir=None, expect=[]): services_filter = self.mgr.list(limit=limit, marker=marker, sort_key=sort_key, sort_dir=sort_dir) self.assertEqual(expect, self.api.calls) self.assertThat(services_filter, matchers.HasLength(2)) def test_service_list_with_limit(self): expect = [ ('GET', '/v1/services/?limit=2', {}, None), ] self._test_service_list_with_filters( limit=2, expect=expect) def test_service_list_with_marker(self): expect = [ ('GET', '/v1/services/?marker=%s' % SERVICE2['id'], {}, None), ] self._test_service_list_with_filters( marker=SERVICE2['id'], expect=expect) def test_service_list_with_marker_limit(self): expect = [ ('GET', '/v1/services/?limit=2&marker=%s' % SERVICE2['id'], {}, None), ] self._test_service_list_with_filters( limit=2, marker=SERVICE2['id'], expect=expect) def test_service_list_with_sort_dir(self): expect = [ ('GET', '/v1/services/?sort_dir=asc', {}, None), ] self._test_service_list_with_filters( sort_dir='asc', expect=expect) def test_service_list_with_sort_key(self): expect = [ ('GET', '/v1/services/?sort_key=id', {}, None), ] self._test_service_list_with_filters( sort_key='id', expect=expect) def test_service_list_with_sort_key_dir(self): expect = [ ('GET', '/v1/services/?sort_key=id&sort_dir=desc', {}, None), ] self._test_service_list_with_filters( sort_key='id', sort_dir='desc', expect=expect) python-zunclient-4.0.0/zunclient/tests/unit/v1/test_versions_shell.py0000664000175000017500000000234613643163457026242 0ustar zuulzuul00000000000000# Copyright 2015 NEC Corporation. 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. from unittest import mock from zunclient.tests.unit.v1 import shell_test_base class ShellTest(shell_test_base.TestCommandLineArgument): @mock.patch('zunclient.v1.versions.VersionManager.list') def test_zun_version_list_success(self, mock_list): self._test_arg_success('version-list') self.assertTrue(mock_list.called) @mock.patch('zunclient.v1.versions.VersionManager.list') def test_zun_version_list_failure(self, mock_list): self._test_arg_failure('version-list --wrong', self._unrecognized_arg_error) self.assertFalse(mock_list.called) python-zunclient-4.0.0/zunclient/tests/unit/v1/test_availability_zones.py0000664000175000017500000000262613643163457027074 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. import testtools from testtools import matchers from zunclient.tests.unit import utils from zunclient.v1 import availability_zones as az AZ1 = {'availability_zone': 'fake-az-1'} AZ2 = {'availability_zone': 'fake-az-2'} fake_responses = { '/v1/availability_zones': { 'GET': ( {}, {'availability_zones': [AZ1, AZ2]}, ), }, } class AZManagerTest(testtools.TestCase): def setUp(self): super(AZManagerTest, self).setUp() self.api = utils.FakeAPI(fake_responses) self.mgr = az.AvailabilityZoneManager(self.api) def test_availability_zones_list(self): zones = self.mgr.list() expect = [ ('GET', '/v1/availability_zones', {}, None), ] self.assertEqual(expect, self.api.calls) self.assertThat(zones, matchers.HasLength(2)) python-zunclient-4.0.0/zunclient/tests/unit/v1/__init__.py0000664000175000017500000000000013643163457023664 0ustar zuulzuul00000000000000python-zunclient-4.0.0/zunclient/tests/unit/v1/test_containers_shell.py0000664000175000017500000002572613643163457026546 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. from unittest import mock from zunclient.common.apiclient import exceptions as apiexec from zunclient.common import utils as zun_utils from zunclient.common.websocketclient import exceptions from zunclient.tests.unit.v1 import shell_test_base from zunclient.v1 import containers_shell def _get_container_args(**kwargs): default_args = { 'auto_remove': False, 'environment': {}, 'hints': {}, 'labels': {}, 'mounts': [], 'nets': [], 'command': [], 'entrypoint': [], } default_args.update(kwargs) return default_args class ShellTest(shell_test_base.TestCommandLineArgument): @mock.patch('zunclient.v1.containers_shell._show_container') @mock.patch('zunclient.v1.containers.ContainerManager.create') def test_zun_container_create_success(self, mock_create, mock_show_container): mock_create.return_value = 'container' self._test_arg_success('create x') mock_show_container.assert_called_once_with('container') @mock.patch('zunclient.common.utils.list_containers') @mock.patch('zunclient.v1.containers.ContainerManager.list') def test_zun_container_list_success(self, mock_list, mock_list_containers): mock_list.return_value = ['container'] self._test_arg_success('list') mock_list_containers.assert_called_once_with(['container']) @mock.patch('zunclient.v1.containers_shell._show_container') @mock.patch('zunclient.v1.containers.ContainerManager.get') def test_zun_container_show_success(self, mock_get, mock_show_container): mock_get.return_value = 'container' self._test_arg_success('show x') mock_show_container.assert_called_once_with('container') @mock.patch('zunclient.v1.containers.ContainerManager.list') def test_zun_container_command_failure(self, mock_list): self._test_arg_failure('list --wrong', self._unrecognized_arg_error) self.assertFalse(mock_list.called) @mock.patch('zunclient.common.cliutils.print_dict') def test_show_container(self, mock_print_dict): fake_container = mock.MagicMock() fake_container._info = {} fake_container.addresses = {'private': [{'addr': '10.0.0.1'}]} containers_shell._show_container(fake_container) mock_print_dict.assert_called_once_with({'networks': 'private', 'addresses': '10.0.0.1'}) @mock.patch('zunclient.common.cliutils.print_list') def test_list_container(self, mock_print_list): fake_container = mock.MagicMock() fake_container._info = {} fake_container.addresses = {'private': [{'addr': '10.0.0.1'}]} zun_utils.list_containers([fake_container]) self.assertTrue(mock_print_list.called) self.assertEqual(fake_container.addresses, '10.0.0.1') @mock.patch('zunclient.v1.containers_shell._show_container') @mock.patch('zunclient.v1.containers.ContainerManager.create') def test_zun_container_create_success_with_pull_policy( self, mock_create, mock_show_container): mock_create.return_value = 'container-never' self._test_arg_success( 'create --image-pull-policy never x') mock_show_container.assert_called_with('container-never') mock_create.assert_called_with( **_get_container_args(image='x', image_pull_policy='never')) mock_create.return_value = 'container-always' self._test_arg_success( 'create --image-pull-policy always x') mock_show_container.assert_called_with('container-always') mock_create.assert_called_with( **_get_container_args(image='x', image_pull_policy='always')) mock_create.return_value = 'container-ifnotpresent' self._test_arg_success( 'create --image-pull-policy ifnotpresent x') mock_show_container.assert_called_with('container-ifnotpresent') mock_create.assert_called_with( **_get_container_args(image='x', image_pull_policy='ifnotpresent')) @mock.patch('zunclient.v1.containers_shell._show_container') @mock.patch('zunclient.v1.containers.ContainerManager.create') def test_zun_container_create_success_without_pull_policy( self, mock_create, mock_show_container): mock_create.return_value = 'container' self._test_arg_success('create x') mock_show_container.assert_called_once_with('container') mock_create.assert_called_with(**_get_container_args(image='x')) @mock.patch('zunclient.v1.containers.ContainerManager.create') def test_zun_container_create_failure_with_wrong_pull_policy( self, mock_create): self._test_arg_failure( 'create --image-pull-policy wrong x ', self._invalid_choice_error) self.assertFalse(mock_create.called) mock_create.assert_not_called() @mock.patch('zunclient.v1.containers_shell._show_container') @mock.patch('zunclient.v1.containers.ContainerManager.run') def test_zun_container_run_success_with_pull_policy( self, mock_run, mock_show_container): mock_run.return_value = 'container-never' self._test_arg_success( 'run --image-pull-policy never x') mock_show_container.assert_called_with('container-never') mock_run.assert_called_with( **_get_container_args(image='x', image_pull_policy='never')) mock_run.return_value = 'container-always' self._test_arg_success( 'run --image-pull-policy always x ') mock_show_container.assert_called_with('container-always') mock_run.assert_called_with( **_get_container_args(image='x', image_pull_policy='always')) mock_run.return_value = 'container-ifnotpresent' self._test_arg_success( 'run --image-pull-policy ifnotpresent x') mock_show_container.assert_called_with('container-ifnotpresent') mock_run.assert_called_with( **_get_container_args(image='x', image_pull_policy='ifnotpresent')) @mock.patch('zunclient.v1.containers_shell._show_container') @mock.patch('zunclient.v1.containers.ContainerManager.run') def test_zun_container_run_success_without_pull_policy( self, mock_run, mock_show_container): mock_run.return_value = 'container' self._test_arg_success('run x') mock_show_container.assert_called_once_with('container') mock_run.assert_called_with(**_get_container_args(image='x')) @mock.patch('zunclient.v1.containers.ContainerManager.run') def test_zun_container_run_failure_with_wrong_pull_policy( self, mock_run): self._test_arg_failure( 'run --image-pull-policy wrong x', self._invalid_choice_error) self.assertFalse(mock_run.called) mock_run.assert_not_called() @mock.patch('zunclient.v1.containers.ContainerManager.get') @mock.patch('zunclient.v1.containers_shell._show_container') @mock.patch('zunclient.v1.containers.ContainerManager.run') def test_zun_container_run_interactive(self, mock_run, mock_show_container, mock_get_container): fake_container = mock.MagicMock() fake_container.uuid = 'fake_uuid' mock_run.return_value = fake_container fake_container.status = 'Error' mock_get_container.return_value = fake_container self.assertRaises(exceptions.ContainerStateError, self.shell, 'run -i x ') @mock.patch('zunclient.v1.containers_shell._show_container') @mock.patch('zunclient.v1.containers.ContainerManager.run') def test_zun_container_run_success_with_runtime( self, mock_run, mock_show_container): mock_run.return_value = 'container' self._test_arg_success('run --runtime runc x') mock_show_container.assert_called_once_with('container') mock_run.assert_called_with( **_get_container_args(image='x', runtime='runc')) @mock.patch('zunclient.v1.containers_shell._show_container') @mock.patch('zunclient.v1.containers.ContainerManager.run') def test_zun_container_run_success_with_availability_zone( self, mock_run, mock_show_container): mock_run.return_value = 'container' self._test_arg_success('run --availability-zone nova x') mock_show_container.assert_called_once_with('container') @mock.patch('zunclient.v1.containers_shell._show_container') @mock.patch('zunclient.v1.containers.ContainerManager.run') def test_zun_container_run_with_mount( self, mock_run, mock_show_container): mock_run.return_value = 'container' self._test_arg_success( 'run --mount source=s,destination=d x') mock_show_container.assert_called_once_with('container') mounts = [{'source': 's', 'destination': 'd'}] mock_run.assert_called_with( **_get_container_args(image='x', mounts=mounts)) def test_zun_container_run_with_mount_invalid_format(self): self.assertRaisesRegex( apiexec.CommandError, 'Invalid mounts argument', self.shell, 'run --mount source,destination=d x') def test_zun_container_run_with_mount_missed_key(self): self.assertRaisesRegex( apiexec.CommandError, 'Invalid mounts argument', self.shell, 'run --mount source=s x') def test_zun_container_run_with_mount_duplicated_key(self): self.assertRaisesRegex( apiexec.CommandError, 'Invalid mounts argument', self.shell, 'run --mount source=s,source=s,destination=d x') def test_zun_container_run_with_mount_invalid_key(self): self.assertRaisesRegex( apiexec.CommandError, 'Invalid mounts argument', self.shell, 'run --mount invalid=s,destination=d x') @mock.patch('zunclient.v1.containers_shell._show_container') @mock.patch('zunclient.v1.containers.ContainerManager.run') def test_zun_container_run_success_with_hostname( self, mock_run, mock_show): mock_run.return_value = 'container' self._test_arg_success('run --hostname testhost x') mock_show.assert_called_once_with('container') mock_run.assert_called_with( **_get_container_args(image='x', hostname='testhost')) python-zunclient-4.0.0/zunclient/tests/unit/common/0000775000175000017500000000000013643163533022522 5ustar zuulzuul00000000000000python-zunclient-4.0.0/zunclient/tests/unit/common/test_utils.py0000664000175000017500000002151413643163457025303 0ustar zuulzuul00000000000000# # Copyright 2013 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 collections import six from zunclient.common import cliutils from zunclient.common import utils from zunclient import exceptions as exc from zunclient.tests.unit import utils as test_utils class CommonFiltersTest(test_utils.BaseTestCase): def test_limit(self): result = utils.common_filters(limit=42) self.assertEqual(['limit=42'], result) def test_limit_0(self): result = utils.common_filters(limit=0) self.assertEqual(['limit=0'], result) def test_limit_negative_number(self): result = utils.common_filters(limit=-2) self.assertEqual(['limit=-2'], result) def test_other(self): for key in ('marker', 'sort_key', 'sort_dir'): result = utils.common_filters(**{key: 'test'}) self.assertEqual(['%s=test' % key], result) class SplitAndDeserializeTest(test_utils.BaseTestCase): def test_split_and_deserialize(self): ret = utils.split_and_deserialize('str=foo') self.assertEqual(('str', 'foo'), ret) ret = utils.split_and_deserialize('int=1') self.assertEqual(('int', 1), ret) ret = utils.split_and_deserialize('bool=false') self.assertEqual(('bool', False), ret) ret = utils.split_and_deserialize('list=[1, "foo", 2]') self.assertEqual(('list', [1, "foo", 2]), ret) ret = utils.split_and_deserialize('dict={"foo": 1}') self.assertEqual(('dict', {"foo": 1}), ret) ret = utils.split_and_deserialize('str_int="1"') self.assertEqual(('str_int', "1"), ret) def test_split_and_deserialize_fail(self): self.assertRaises(exc.CommandError, utils.split_and_deserialize, 'foo:bar') class ArgsArrayToPatchTest(test_utils.BaseTestCase): def test_args_array_to_patch(self): my_args = { 'attributes': ['str=foo', 'int=1', 'bool=true', 'list=[1, 2, 3]', 'dict={"foo": "bar"}'], } patch = utils.args_array_to_patch(my_args['attributes']) self.assertEqual([{'str': 'foo'}, {'int': 1}, {'bool': True}, {'list': [1, 2, 3]}, {'dict': {"foo": "bar"}}], patch) class FormatArgsTest(test_utils.BaseTestCase): def test_format_args_none(self): self.assertEqual({}, utils.format_args(None)) def test_format_args(self): li = utils.format_args([ 'K1=V1,K2=V2,' 'K3=V3,K4=V4,' 'K5=V5']) self.assertEqual({'K1': 'V1', 'K2': 'V2', 'K3': 'V3', 'K4': 'V4', 'K5': 'V5' }, li) def test_format_args_semicolon(self): li = utils.format_args([ 'K1=V1;K2=V2;' 'K3=V3;K4=V4;' 'K5=V5']) self.assertEqual({'K1': 'V1', 'K2': 'V2', 'K3': 'V3', 'K4': 'V4', 'K5': 'V5' }, li) def test_format_args_mix_commas_semicolon(self): li = utils.format_args([ 'K1=V1,K2=V2,' 'K3=V3;K4=V4,' 'K5=V5']) self.assertEqual({'K1': 'V1', 'K2': 'V2', 'K3': 'V3', 'K4': 'V4', 'K5': 'V5' }, li) def test_format_args_split(self): li = utils.format_args([ 'K1=V1,' 'K2=V22222222222222222222222222222' '222222222222222222222222222,' 'K3=3.3.3.3']) self.assertEqual({'K1': 'V1', 'K2': 'V22222222222222222222222222222' '222222222222222222222222222', 'K3': '3.3.3.3'}, li) def test_format_args_multiple(self): li = utils.format_args([ 'K1=V1', 'K2=V22222222222222222222222222222' '222222222222222222222222222', 'K3=3.3.3.3']) self.assertEqual({'K1': 'V1', 'K2': 'V22222222222222222222222222222' '222222222222222222222222222', 'K3': '3.3.3.3'}, li) def test_format_args_multiple_colon_values(self): li = utils.format_args([ 'K1=V1', 'K2=V2,V22,V222,V2222', 'K3=3.3.3.3']) self.assertEqual({'K1': 'V1', 'K2': 'V2,V22,V222,V2222', 'K3': '3.3.3.3'}, li) def test_format_args_parse_comma_false(self): li = utils.format_args( ['K1=V1,K2=2.2.2.2,K=V'], parse_comma=False) self.assertEqual({'K1': 'V1,K2=2.2.2.2,K=V'}, li) def test_format_args_multiple_values_per_args(self): li = utils.format_args([ 'K1=V1', 'K1=V2']) self.assertIn('K1', li) self.assertIn('V1', li['K1']) self.assertIn('V2', li['K1']) def test_format_args_bad_arg(self): args = ['K1=V1,K22.2.2.2'] ex = self.assertRaises(exc.CommandError, utils.format_args, args) self.assertEqual('arguments must be a list of KEY=VALUE ' 'not K22.2.2.2', str(ex)) def test_format_multiple_bad_args(self): args = ['K1=V1', 'K22.2.2.2'] ex = self.assertRaises(exc.CommandError, utils.format_args, args) self.assertEqual('arguments must be a list of KEY=VALUE ' 'not K22.2.2.2', str(ex)) class CliUtilsTest(test_utils.BaseTestCase): def test_keys_and_vals_to_strs(self): dict_in = {six.u('a'): six.u('1'), six.u('b'): {six.u('x'): 1, 'y': six.u('2'), six.u('z'): six.u('3')}, 'c': 7} dict_exp = collections.OrderedDict([ ('a', '1'), ('b', collections.OrderedDict([ ('x', 1), ('y', '2'), ('z', '3')])), ('c', 7)]) dict_out = cliutils.keys_and_vals_to_strs(dict_in) dict_act = collections.OrderedDict([ ('a', dict_out['a']), ('b', collections.OrderedDict(sorted(dict_out['b'].items()))), ('c', dict_out['c'])]) self.assertEqual(six.text_type(dict_exp), six.text_type(dict_act)) class ParseNetsTest(test_utils.BaseTestCase): def test_no_nets(self): nets = [] result = utils.parse_nets(nets) self.assertEqual([], result) def test_nets_with_network(self): nets = [' network = 1234567 , v4-fixed-ip = 172.17.0.3 '] result = utils.parse_nets(nets) self.assertEqual([{'network': '1234567', 'v4-fixed-ip': '172.17.0.3'}], result) def test_nets_with_port(self): nets = ['port=1234567, v6-fixed-ip=2001:db8::2'] result = utils.parse_nets(nets) self.assertEqual([{'port': '1234567', 'v6-fixed-ip': '2001:db8::2'}], result) def test_nets_with_only_ip(self): nets = ['v4-fixed-ip = 172.17.0.3'] self.assertRaises(exc.CommandError, utils.parse_nets, nets) def test_nets_with_both_network_port(self): nets = ['port=1234567, network=2345678, v4-fixed-ip=172.17.0.3'] self.assertRaises(exc.CommandError, utils.parse_nets, nets) def test_nets_with_invalid_ip(self): nets = ['network=1234567, v4-fixed-ip=23.555.567,789'] self.assertRaises(exc.CommandError, utils.parse_nets, nets) class ParseCommandTest(test_utils.BaseTestCase): def test_no_command(self): command = [] result = utils.parse_command(command) self.assertEqual('', result) def test_command_ls(self): command = ['ls', '-al'] result = utils.parse_command(command) self.assertEqual('"ls" "-al"', result) def test_command_echo_hello(self): command = ['sh', '-c', 'echo hello'] result = utils.parse_command(command) self.assertEqual('"sh" "-c" "echo hello"', result) python-zunclient-4.0.0/zunclient/tests/unit/common/test_httpclient.py0000664000175000017500000003126113643163457026321 0ustar zuulzuul00000000000000# Copyright 2015 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. from unittest import mock from oslo_serialization import jsonutils import six from zunclient import api_versions from zunclient.common.apiclient import exceptions from zunclient.common import httpclient as http from zunclient import exceptions as exc from zunclient.tests.unit import utils def _get_error_body(faultstring=None, debuginfo=None): error_body = { 'faultstring': faultstring, 'debuginfo': debuginfo } raw_error_body = jsonutils.dumps(error_body) body = {'error_message': raw_error_body} raw_body = jsonutils.dumps(body) return raw_body HTTP_CLASS = six.moves.http_client.HTTPConnection HTTPS_CLASS = http.VerifiedHTTPSConnection DEFAULT_TIMEOUT = 600 class HttpClientTest(utils.BaseTestCase): def test_url_generation_trailing_slash_in_base(self): client = http.HTTPClient('http://localhost/') url = client._make_connection_url('/v1/resources') self.assertEqual('/v1/resources', url) def test_url_generation_without_trailing_slash_in_base(self): client = http.HTTPClient('http://localhost') url = client._make_connection_url('/v1/resources') self.assertEqual('/v1/resources', url) def test_url_generation_prefix_slash_in_path(self): client = http.HTTPClient('http://localhost/') url = client._make_connection_url('/v1/resources') self.assertEqual('/v1/resources', url) def test_url_generation_without_prefix_slash_in_path(self): client = http.HTTPClient('http://localhost') url = client._make_connection_url('v1/resources') self.assertEqual('/v1/resources', url) def test_server_exception_empty_body(self): error_body = _get_error_body() fake_resp = utils.FakeResponse({'content-type': 'application/json'}, six.StringIO(error_body), version=1, status=500) client = http.HTTPClient( 'http://localhost/', api_version=api_versions.APIVersion('1.latest')) client.get_connection = ( lambda *a, **kw: utils.FakeConnection(fake_resp)) error = self.assertRaises(exc.InternalServerError, client.json_request, 'GET', '/v1/resources') self.assertEqual('Internal Server Error (HTTP 500)', str(error)) def test_server_exception_msg_only(self): error_msg = 'test error msg' error_body = _get_error_body(error_msg) fake_resp = utils.FakeResponse({'content-type': 'application/json'}, six.StringIO(error_body), version=1, status=500) client = http.HTTPClient( 'http://localhost/', api_version=api_versions.APIVersion('1.latest')) client.get_connection = ( lambda *a, **kw: utils.FakeConnection(fake_resp)) error = self.assertRaises(exc.InternalServerError, client.json_request, 'GET', '/v1/resources') self.assertEqual(error_msg + ' (HTTP 500)', str(error)) def test_server_exception_msg_and_traceback(self): error_msg = 'another test error' error_trace = ("\"Traceback (most recent call last):\\n\\n " "File \\\"/usr/local/lib/python2.7/...") error_body = _get_error_body(error_msg, error_trace) fake_resp = utils.FakeResponse({'content-type': 'application/json'}, six.StringIO(error_body), version=1, status=500) client = http.HTTPClient( 'http://localhost/', api_version=api_versions.APIVersion('1.latest')) client.get_connection = ( lambda *a, **kw: utils.FakeConnection(fake_resp)) error = self.assertRaises(exc.InternalServerError, client.json_request, 'GET', '/v1/resources') self.assertEqual( '%(error)s (HTTP 500)\n%(trace)s' % {'error': error_msg, 'trace': error_trace}, "%(error)s\n%(details)s" % {'error': str(error), 'details': str(error.details)}) def test_get_connection_params(self): endpoint = 'http://zun-host:6385' expected = (HTTP_CLASS, ('zun-host', 6385, ''), {'timeout': DEFAULT_TIMEOUT}) params = http.HTTPClient.get_connection_params(endpoint) self.assertEqual(expected, params) def test_get_connection_params_with_trailing_slash(self): endpoint = 'http://zun-host:6385/' expected = (HTTP_CLASS, ('zun-host', 6385, ''), {'timeout': DEFAULT_TIMEOUT}) params = http.HTTPClient.get_connection_params(endpoint) self.assertEqual(expected, params) def test_get_connection_params_with_ssl(self): endpoint = 'https://zun-host:6385' expected = (HTTPS_CLASS, ('zun-host', 6385, ''), { 'timeout': DEFAULT_TIMEOUT, 'ca_file': None, 'cert_file': None, 'key_file': None, 'insecure': False, }) params = http.HTTPClient.get_connection_params(endpoint) self.assertEqual(expected, params) def test_get_connection_params_with_ssl_params(self): endpoint = 'https://zun-host:6385' ssl_args = { 'ca_file': '/path/to/ca_file', 'cert_file': '/path/to/cert_file', 'key_file': '/path/to/key_file', 'insecure': True, } expected_kwargs = {'timeout': DEFAULT_TIMEOUT} expected_kwargs.update(ssl_args) expected = (HTTPS_CLASS, ('zun-host', 6385, ''), expected_kwargs) params = http.HTTPClient.get_connection_params(endpoint, **ssl_args) self.assertEqual(expected, params) def test_get_connection_params_with_timeout(self): endpoint = 'http://zun-host:6385' expected = (HTTP_CLASS, ('zun-host', 6385, ''), {'timeout': 300.0}) params = http.HTTPClient.get_connection_params(endpoint, timeout=300) self.assertEqual(expected, params) def test_get_connection_params_with_version(self): endpoint = 'http://zun-host:6385/v1' expected = (HTTP_CLASS, ('zun-host', 6385, ''), {'timeout': DEFAULT_TIMEOUT}) params = http.HTTPClient.get_connection_params(endpoint) self.assertEqual(expected, params) def test_get_connection_params_with_version_trailing_slash(self): endpoint = 'http://zun-host:6385/v1/' expected = (HTTP_CLASS, ('zun-host', 6385, ''), {'timeout': DEFAULT_TIMEOUT}) params = http.HTTPClient.get_connection_params(endpoint) self.assertEqual(expected, params) def test_get_connection_params_with_subpath(self): endpoint = 'http://zun-host:6385/zun' expected = (HTTP_CLASS, ('zun-host', 6385, '/zun'), {'timeout': DEFAULT_TIMEOUT}) params = http.HTTPClient.get_connection_params(endpoint) self.assertEqual(expected, params) def test_get_connection_params_with_subpath_trailing_slash(self): endpoint = 'http://zun-host:6385/zun/' expected = (HTTP_CLASS, ('zun-host', 6385, '/zun'), {'timeout': DEFAULT_TIMEOUT}) params = http.HTTPClient.get_connection_params(endpoint) self.assertEqual(expected, params) def test_get_connection_params_with_subpath_version(self): endpoint = 'http://zun-host:6385/zun/v1' expected = (HTTP_CLASS, ('zun-host', 6385, '/zun'), {'timeout': DEFAULT_TIMEOUT}) params = http.HTTPClient.get_connection_params(endpoint) self.assertEqual(expected, params) def test_get_connection_params_with_subpath_version_trailing_slash(self): endpoint = 'http://zun-host:6385/zun/v1/' expected = (HTTP_CLASS, ('zun-host', 6385, '/zun'), {'timeout': DEFAULT_TIMEOUT}) params = http.HTTPClient.get_connection_params(endpoint) self.assertEqual(expected, params) def test_401_unauthorized_exception(self): error_body = _get_error_body() fake_resp = utils.FakeResponse({'content-type': 'text/plain'}, six.StringIO(error_body), version=1, status=401) client = http.HTTPClient( 'http://localhost/', api_version=api_versions.APIVersion('1.latest')) client.get_connection = (lambda *a, **kw: utils.FakeConnection(fake_resp)) self.assertRaises(exc.Unauthorized, client.json_request, 'GET', '/v1/resources') class SessionClientTest(utils.BaseTestCase): def test_server_exception_msg_and_traceback(self): error_msg = 'another test error' error_trace = ("\"Traceback (most recent call last):\\n\\n " "File \\\"/usr/local/lib/python2.7/...") error_body = _get_error_body(error_msg, error_trace) fake_session = utils.FakeSession({'Content-Type': 'application/json'}, error_body, 500) client = http.SessionClient( api_version=api_versions.APIVersion('1.latest'), session=fake_session) error = self.assertRaises(exc.InternalServerError, client.json_request, 'GET', '/v1/resources') self.assertEqual( '%(error)s (HTTP 500)\n%(trace)s' % {'error': error_msg, 'trace': error_trace}, "%(error)s\n%(details)s" % {'error': str(error), 'details': str(error.details)}) def test_server_exception_empty_body(self): error_body = _get_error_body() fake_session = utils.FakeSession({'Content-Type': 'application/json'}, error_body, 500) client = http.SessionClient( api_version=api_versions.APIVersion('1.latest'), session=fake_session) error = self.assertRaises(exc.InternalServerError, client.json_request, 'GET', '/v1/resources') self.assertEqual('Internal Server Error (HTTP 500)', str(error)) def test_bypass_url(self): fake_response = utils.FakeSessionResponse( {}, content="", status_code=201) fake_session = mock.MagicMock() fake_session.request.side_effect = [fake_response] client = http.SessionClient( api_version=api_versions.APIVersion('1.latest'), session=fake_session, endpoint_override='http://zun') client.json_request('GET', '/v1/services') self.assertEqual( fake_session.request.call_args[1]['endpoint_override'], 'http://zun' ) def test_exception(self): fake_response = utils.FakeSessionResponse( {}, content="", status_code=504) fake_session = mock.MagicMock() fake_session.request.side_effect = [fake_response] client = http.SessionClient( api_version=api_versions.APIVersion('1.latest'), session=fake_session, endpoint_override='http://zun') self.assertRaises(exceptions.GatewayTimeout, client.json_request, 'GET', '/v1/resources') python-zunclient-4.0.0/zunclient/tests/unit/common/__init__.py0000664000175000017500000000000013643163457024626 0ustar zuulzuul00000000000000python-zunclient-4.0.0/zunclient/tests/unit/test_websocketclient.py0000664000175000017500000000333213643163457026036 0ustar zuulzuul00000000000000# Copyright 2015 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. from unittest import mock import testtools from zunclient.common.websocketclient import websocketclient CONTAINER_ID = "0f96db5a-26dc-4550-b1a8-b110bd9247cb" ESCAPE_FLAG = "~" URL = "ws://localhost:2375/v1.17/containers/201e4e22c5b2/" \ "attach/ws?logs=0&stream=1&stdin=1&stdout=1&stderr=1" URL1 = "ws://10.10.10.10:2375/v1.17/containers/***********/" \ "attach/ws?logs=0&stream=1&stdin=1&stdout=1&stderr=1" WAIT_TIME = 0.5 class WebSocketClientTest(testtools.TestCase): def test_websocketclient_variables(self): mock_client = mock.Mock() wsclient = websocketclient.WebSocketClient(zunclient=mock_client, url=URL, id=CONTAINER_ID, escape=ESCAPE_FLAG, close_wait=WAIT_TIME) self.assertEqual(wsclient.url, URL) self.assertEqual(wsclient.id, CONTAINER_ID) self.assertEqual(wsclient.escape, ESCAPE_FLAG) self.assertEqual(wsclient.close_wait, WAIT_TIME) python-zunclient-4.0.0/zunclient/tests/unit/test_client.py0000664000175000017500000000404213643163457024126 0ustar zuulzuul00000000000000# Copyright (c) 2015 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. from unittest import mock import testtools from zunclient import api_versions from zunclient import client from zunclient import exceptions class ClientTest(testtools.TestCase): @mock.patch('zunclient.api_versions.discover_version', return_value=api_versions.APIVersion('1.1')) @mock.patch('zunclient.v1.client.Client') def test_no_version_argument(self, mock_zun_client_v1, mock_discover_version): client.Client(auth_url='http://example/identity', username='admin') mock_zun_client_v1.assert_called_with( api_version=api_versions.APIVersion('1.1'), auth_url='http://example/identity', username='admin') @mock.patch('zunclient.api_versions.discover_version', return_value=api_versions.APIVersion('1.1')) @mock.patch('zunclient.v1.client.Client') def test_valid_version_argument(self, mock_zun_client_v1, mock_discover_version): client.Client(version='1', auth_url='http://example/identity', username='admin') mock_zun_client_v1.assert_called_with( api_version=api_versions.APIVersion('1.1'), auth_url='http://example/identity', username='admin') def test_invalid_version_argument(self): self.assertRaises( exceptions.UnsupportedVersion, client.Client, version='2') python-zunclient-4.0.0/zunclient/tests/unit/utils.py0000664000175000017500000001353113643163457022754 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 datetime import os import sys import fixtures import six import testtools from zunclient import api_versions from zunclient.common import httpclient as http from zunclient import shell FAKE_ENV = {'OS_USERNAME': 'username', 'OS_PASSWORD': 'password', 'OS_PROJECT_NAME': 'project_name', 'OS_AUTH_URL': 'http://no.where/v2.0'} class BaseTestCase(testtools.TestCase): def setUp(self): super(BaseTestCase, self).setUp() self.useFixture(fixtures.FakeLogger()) class FakeAPI(object): def __init__(self, responses, endpoint=None): self.responses = responses self.calls = [] self.api_version = api_versions.APIVersion( api_versions.MAX_API_VERSION) self.endpoint = endpoint def _request(self, method, url, headers=None, body=None): call = (method, url, headers or {}, body) self.calls.append(call) return self.responses[url][method] def raw_request(self, *args, **kwargs): response = self._request(*args, **kwargs) body_iter = http.ResponseBodyIterator(six.StringIO(response[1])) return FakeResponse(response[0]), body_iter def json_request(self, *args, **kwargs): response = self._request(*args, **kwargs) return FakeResponse(response[0]), response[1] def get_endpoint(self, *args, **kwargs): return self.endpoint or '/v1' class FakeConnection(object): def __init__(self, response=None): self._response = response self._last_request = None def request(self, method, conn_url, **kwargs): self._last_request = (method, conn_url, kwargs) def setresponse(self, response): self._response = response def getresponse(self): return self._response class FakeResponse(object): def __init__(self, headers, body=None, version=None, status=None, reason=None): """Fake object to help testing. :param headers: dict representing HTTP response headers :param body: file-like object """ self.headers = headers self.body = body self.version = version self.status = status self.reason = reason def getheaders(self): return copy.deepcopy(self.headers).items() def getheader(self, key, default): return self.headers.get(key, default) def read(self, amt): return self.body.read(amt) class FakeServiceCatalog(object): def url_for(self, endpoint_type, service_type, attr=None, filter_value=None): if attr == 'region' and filter_value: return 'http://regionhost:6385/v1/f14b41234' else: return 'http://localhost:6385/v1/f14b41234' class FakeKeystone(object): service_catalog = FakeServiceCatalog() timestamp = datetime.datetime.utcnow() + datetime.timedelta(days=5) def __init__(self, auth_token): self.auth_token = auth_token self.auth_ref = { 'token': {'expires': FakeKeystone.timestamp.strftime( '%Y-%m-%dT%H:%M:%S.%f'), 'id': 'd1a541311782870742235'} } class TestCase(testtools.TestCase): TEST_REQUEST_BASE = { 'verify': True, } def setUp(self): super(TestCase, self).setUp() if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or os.environ.get('OS_STDOUT_CAPTURE') == '1'): stdout = self.useFixture(fixtures.StringStream('stdout')).stream self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout)) if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or os.environ.get('OS_STDERR_CAPTURE') == '1'): stderr = self.useFixture(fixtures.StringStream('stderr')).stream self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr)) def make_env(self, exclude=None, fake_env=FAKE_ENV): env = dict((k, v) for k, v in fake_env.items() if k != exclude) self.useFixture(fixtures.MonkeyPatch('os.environ', env)) def shell(self, argstr, exitcodes=(0,)): orig = sys.stdout orig_stderr = sys.stderr try: sys.stdout = six.StringIO() sys.stderr = six.StringIO() _shell = shell.OpenStackZunShell() _shell.main(argstr.split()) except SystemExit: exc_type, exc_value, exc_traceback = sys.exc_info() self.assertIn(exc_value.code, exitcodes) finally: stdout = sys.stdout.getvalue() sys.stdout.close() sys.stdout = orig stderr = sys.stderr.getvalue() sys.stderr.close() sys.stderr = orig_stderr return (stdout, stderr) class FakeSessionResponse(object): def __init__(self, headers, content=None, status_code=None): self.headers = headers self.content = content self.status_code = status_code class FakeSession(object): def __init__(self, headers, content=None, status_code=None): self.headers = headers self.content = content self.status_code = status_code def request(self, url, method, **kwargs): return FakeSessionResponse(self.headers, self.content, self.status_code) python-zunclient-4.0.0/zunclient/tests/unit/osc/0000775000175000017500000000000013643163533022016 5ustar zuulzuul00000000000000python-zunclient-4.0.0/zunclient/tests/unit/osc/test_plugin.py0000664000175000017500000000267013643163457024737 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. from unittest import mock from zunclient import api_versions from zunclient.osc import plugin from zunclient.tests.unit import base class TestContainerPlugin(base.TestCase): @mock.patch("zunclient.api_versions.get_api_version") @mock.patch("zunclient.v1.client.Client") def test_make_client(self, p_client, mock_get_api_version): instance = mock.Mock() instance._api_version = {"container": '1'} instance._region_name = 'zun_region' instance.session = 'zun_session' mock_get_api_version.return_value = api_versions.APIVersion('1.2') plugin.make_client(instance) p_client.assert_called_with(region_name='zun_region', session='zun_session', service_type='container', api_version=api_versions.APIVersion('1.2')) python-zunclient-4.0.0/zunclient/tests/unit/osc/__init__.py0000664000175000017500000000000013643163457024122 0ustar zuulzuul00000000000000python-zunclient-4.0.0/zunclient/tests/unit/__init__.py0000664000175000017500000000000013643163457023336 0ustar zuulzuul00000000000000python-zunclient-4.0.0/zunclient/tests/__init__.py0000664000175000017500000000000013643163457022357 0ustar zuulzuul00000000000000python-zunclient-4.0.0/zunclient/tests/functional/0000775000175000017500000000000013643163533022415 5ustar zuulzuul00000000000000python-zunclient-4.0.0/zunclient/tests/functional/hooks/0000775000175000017500000000000013643163533023540 5ustar zuulzuul00000000000000python-zunclient-4.0.0/zunclient/tests/functional/hooks/gate_hook.sh0000775000175000017500000000326713643163457026054 0ustar zuulzuul00000000000000#!/bin/bash -x # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # # This script is executed inside gate_hook function in devstack gate. # Keep all devstack settings here instead of project-config for easy # maintain if we want to change devstack config settings in future. driver=$1 db=$2 export DEVSTACK_LOCAL_CONFIG+=$'\n'"enable_plugin kuryr-libnetwork https://opendev.org/openstack/kuryr-libnetwork" export DEVSTACK_LOCAL_CONFIG+=$'\n'"enable_plugin devstack-plugin-container https://opendev.org/openstack/devstack-plugin-container" export DEVSTACK_LOCAL_CONFIG+=$'\n'"ZUN_USE_UWSGI=True" export DEVSTACK_LOCAL_CONFIG+=$'\n'"KURYR_CONFIG_DIR=/etc/kuryr-libnetwork" export DEVSTACK_GATE_TEMPEST=0 if [ "$driver" = "docker" ]; then export DEVSTACK_LOCAL_CONFIG+=$'\n'"ZUN_DRIVER=docker" fi if [ "$db" = "etcd" ]; then export DEVSTACK_LOCAL_CONFIG+=$'\n'"ZUN_DB_TYPE=etcd" elif [ "$db" = "sql" ]; then export DEVSTACK_LOCAL_CONFIG+=$'\n'"ZUN_DB_TYPE=sql" fi $BASE/new/devstack-gate/devstack-vm-gate.sh gate_retval=$? # Copy over docker systemd unit journals. mkdir -p $WORKSPACE/logs sudo journalctl -o short-precise --unit docker | sudo tee $WORKSPACE/logs/docker.txt > /dev/null exit $gate_retval python-zunclient-4.0.0/zunclient/tests/functional/hooks/post_test_hook.sh0000775000175000017500000000315313643163457027152 0ustar zuulzuul00000000000000#!/bin/bash -x # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # This script is executed inside post_test_hook function in devstack gate. # Sleep some time until all services are starting sleep 5 # Check if a function already exists function function_exists { declare -f -F $1 > /dev/null } if ! function_exists echo_summary; then function echo_summary { echo $@ } fi # Save trace setting XTRACE=$(set +o | grep xtrace) set -o xtrace echo_summary "Zunclient's post_test_hook.sh was called..." (set -o posix; set) # source it to make sure to get REQUIREMENTS_DIR source $BASE/new/devstack/stackrc source $BASE/new/devstack/accrc/admin/admin constraints="-c $REQUIREMENTS_DIR/upper-constraints.txt" sudo -H pip install $constraints -U -r requirements.txt -r test-requirements.txt echo "Running OSC commands test for Zun" export ZUNCLIENT_DIR="$BASE/new/python-zunclient" sudo chown -R $USER:stack $ZUNCLIENT_DIR # Go to the zunclient dir cd $ZUNCLIENT_DIR # Run tests set +e source $BASE/new/devstack/openrc admin admin sudo -E -H -u $USER ./tools/run_functional.sh EXIT_CODE=$? set -e $XTRACE exit $EXIT_CODE python-zunclient-4.0.0/zunclient/tests/functional/hooks/__init__.py0000664000175000017500000000000013643163457025644 0ustar zuulzuul00000000000000python-zunclient-4.0.0/zunclient/tests/functional/base.py0000664000175000017500000001722713643163457023717 0ustar zuulzuul00000000000000# Copyright (c) 2016 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import os import six import six.moves.configparser as config_parser from tempest.lib.cli import base DEFAULT_CONFIG_FILE = os.path.join(os.path.dirname(__file__), 'test.conf') class FunctionalTestBase(base.ClientTestBase): """Container base class, calls to zunclient.""" def setUp(self): super(FunctionalTestBase, self).setUp() self.client = self._get_clients() def _get_clients(self): # NOTE(aarefiev): {toxinidir} is a current working directory, so # the tox env path is {toxinidir}/.tox cli_dir = os.path.join(os.path.abspath('.'), '.tox/functional/bin') config = self._get_config() if config.get('os_auth_url'): client = base.CLIClient(cli_dir=cli_dir, username=config['os_username'], password=config['os_password'], tenant_name=config['os_project_name'], uri=config['os_auth_url']) for keystone_object in 'user', 'project': domain_attr = 'os_%s_domain_id' % keystone_object if config.get(domain_attr): setattr(self, domain_attr, config[domain_attr]) else: self.zun_url = config['zun_url'] self.os_auth_token = config['os_auth_token'] client = base.CLIClient(cli_dir=cli_dir, zun_url=self.zun_url, os_auth_token=self.os_auth_token) return client def _get_config(self): config_file = os.environ.get('ZUNCLIENT_TEST_CONFIG', DEFAULT_CONFIG_FILE) # SafeConfigParser was deprecated in Python 3.2 if six.PY3: config = config_parser.ConfigParser() else: config = config_parser.SafeConfigParser() if not config.read(config_file): self.skipTest('Skipping, no test config found @ %s' % config_file) try: auth_strategy = config.get('functional', 'auth_strategy') except config_parser.NoOptionError: auth_strategy = 'keystone' if auth_strategy not in ['keystone', 'noauth']: raise self.fail( 'Invalid auth type specified: %s in functional must be ' 'one of: [keystone, noauth]' % auth_strategy) conf_settings = [] keystone_v3_conf_settings = [] if auth_strategy == 'keystone': conf_settings += ['os_auth_url', 'os_username', 'os_password', 'os_project_name', 'os_identity_api_version'] keystone_v3_conf_settings += ['os_user_domain_id', 'os_project_domain_id'] else: conf_settings += ['os_auth_token', 'zun_url'] cli_flags = {} missing = [] for c in conf_settings + keystone_v3_conf_settings: try: cli_flags[c] = config.get('functional', c) except config_parser.NoOptionError: # NOTE(vdrok): Here we ignore the absence of KS v3 options as # v2 may be used. Keystone client will do the actual check of # the parameters' correctness. if c not in keystone_v3_conf_settings: missing.append(c) if missing: self.fail('Missing required setting in test.conf (%(conf)s) for ' 'auth_strategy=%(auth)s: %(missing)s' % {'conf': config_file, 'auth': auth_strategy, 'missing': ','.join(missing)}) return cli_flags def _cmd_no_auth(self, cmd, action, flags='', params=''): """Execute given command with noauth attributes. :param cmd: command to be executed :type cmd: string :param action: command on cli to run :type action: string :param flags: optional cli flags to use :type flags: string :param params: optional positional args to use :type params: string """ flags = ('--os_auth_token %(token)s --zun_url %(url)s %(flags)s' % {'token': self.os_auth_token, 'url': self.zun_url, 'flags': flags}) return base.execute(cmd, action, flags, params, cli_dir=self.client.cli_dir) def _zun(self, action, cmd='zun', flags='', params='', merge_stderr=False): """Execute Zun command for the given action. :param action: the cli command to run using Zun :type action: string :param cmd: the base of cli command to run :type action: string :param flags: any optional cli flags to use :type flags: string :param params: any optional positional args to use :type params: string :param merge_stderr: whether to merge stderr into the result :type merge_stderr: bool """ if cmd == 'openstack': config = self._get_config() id_api_version = config['os_identity_api_version'] flags += ' --os-identity-api-version {0}'.format(id_api_version) else: flags += ' --os-endpoint-type publicURL' if hasattr(self, 'os_auth_token'): return self._cmd_no_auth(cmd, action, flags, params) else: for keystone_object in 'user', 'project': domain_attr = 'os_%s_domain_id' % keystone_object if hasattr(self, domain_attr): flags += ' --os-%(ks_obj)s-domain-id %(value)s' % { 'ks_obj': keystone_object, 'value': getattr(self, domain_attr) } return self.client.cmd_with_auth( cmd, action, flags, params, merge_stderr=merge_stderr) def zun(self, action, flags='', params='', parse=True): """Return parsed list of dicts with basic item info. :param action: the cli command to run using Container :type action: string :param flags: any optional cli flags to use :type flags: string :param params: any optional positional args to use :type params: string :param parse: return parsed list or raw output :type parse: bool """ output = self._zun(action=action, flags=flags, params=params) return self.parser.listing(output) if parse else output def get_table_headers(self, action, flags='', params=''): output = self._zun(action=action, flags=flags, params=params) table = self.parser.table(output) return table['headers'] def assertTableHeaders(self, field_names, table_headers): """Assert that field_names and table_headers are equal. :param field_names: field names from the output table of the cmd :param table_headers: table headers output from cmd """ self.assertEqual(sorted(field_names), sorted(table_headers)) def list_containers(self, params=''): return self.zun('container-list', params=params) python-zunclient-4.0.0/zunclient/tests/functional/osc/0000775000175000017500000000000013643163533023201 5ustar zuulzuul00000000000000python-zunclient-4.0.0/zunclient/tests/functional/osc/v1/0000775000175000017500000000000013643163533023527 5ustar zuulzuul00000000000000python-zunclient-4.0.0/zunclient/tests/functional/osc/v1/base.py0000664000175000017500000001226013643163457025021 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. from oslo_serialization import jsonutils from tempest.lib.common.utils import data_utils from tempest.lib import exceptions from zunclient.tests.functional import base class TestCase(base.FunctionalTestBase): def openstack(self, *args, **kwargs): return self._zun(cmd='openstack', *args, **kwargs) def get_opts(self, fields=None, output_format='json'): """Get options for OSC output fields format. :param List fields: List of fields to get :param String output_format: Select output format :return: String of formatted options """ if not fields: return ' -f {0}'.format(output_format) return ' -f {0} {1}'.format(output_format, ' '.join(['-c ' + it for it in fields])) def container_create(self, image='cirros', name=None, params=''): """Create container and add cleanup. :param String image: Image for a new container :param String name: Name for a new container :param String params: Additional args and kwargs :return: JSON object of created container """ if not name: name = data_utils.rand_name('container') opts = self.get_opts() output = self.openstack('appcontainer create {0}' ' --name {1} {2} {3}' .format(opts, name, image, params)) container = jsonutils.loads(output) if not output: self.fail('Container has not been created!') return container def container_run(self, image='cirros', name=None, params='sleep 100000'): """Run container and add cleanup. :param String image: Image for a new container :param String name: Name for a new container :param String params: Additional args and kwargs :return: JSON object of created container """ if not name: name = data_utils.rand_name('container') opts = self.get_opts() output = self.openstack('appcontainer run {0}' ' --name {1} {2} {3}' .format(opts, name, image, params)) container = jsonutils.loads(output) if not output: self.fail('Container has not run!') return container def container_delete(self, identifier, force=True, ignore_exceptions=False): """Try to delete container by name or UUID. :param String identifier: Name or UUID of the container :param Bool ignore_exceptions: Ignore exception (needed for cleanUp) :return: raw values output :raise: CommandFailed exception when command fails to delete a container """ arg = '--force' if force else '' try: return self.openstack('appcontainer delete {0} {1}' .format(arg, identifier)) except exceptions.CommandFailed: if not ignore_exceptions: raise def container_list(self, fields=None, params=''): """List Container. :param List fields: List of fields to show :param String params: Additional kwargs :return: list of JSON container objects """ opts = self.get_opts(fields=fields) output = self.openstack('appcontainer list {0} {1}' .format(opts, params)) return jsonutils.loads(output) def container_show(self, identifier, fields=None, params=''): """Show specified container. :param String identifier: Name or UUID of the container :param List fields: List of fields to show :param List params: Additional kwargs :return: JSON object of container """ opts = self.get_opts(fields) output = self.openstack('appcontainer show {0} {1} {2}' .format(opts, identifier, params)) return jsonutils.loads(output) def container_rename(self, identifier, name): """Rename specified container. :param String identifier: Name or UUID of the container :param String name: new name for the container """ self.openstack('appcontainer rename {0} {1}' .format(identifier, name)) def container_execute(self, identifier, command): """Execute in specified container. :param String identifier: Name or UUID of the container :param String command: command execute in the container """ return self.openstack('appcontainer exec {0} {1}' .format(identifier, command)) python-zunclient-4.0.0/zunclient/tests/functional/osc/v1/test_container.py0000664000175000017500000001112513643163457027127 0ustar zuulzuul00000000000000# Copyright (c) 2016 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import ddt import time from zunclient.tests.functional.osc.v1 import base @ddt.ddt class ContainerTests(base.TestCase): """Functional tests for container commands.""" def setUp(self): super(ContainerTests, self).setUp() def test_list(self): """Check container list command. """ container = self.container_create(name='test_list') container_list = self.container_list() self.assertIn(container['name'], [x['name'] for x in container_list]) self.assertIn(container['uuid'], [x['uuid'] for x in container_list]) # Now delete the container and then see the list self.container_delete(container['name']) def test_create(self): """Check container create command. """ container_info = self.container_create(name='test_create') self.assertEqual(container_info['name'], 'test_create') self.assertEqual(container_info['image'], 'cirros') container_list = self.container_list() self.assertIn('test_create', [x['name'] for x in container_list]) self.container_delete(container_info['name']) def test_delete(self): """Check container delete command with name/UUID argument. Test steps: 1) Create container in setUp. 2) Delete container by name/UUID. 3) Check that container deleted successfully. """ container = self.container_create(name='test_delete') container_list = self.container_list() self.assertIn(container['name'], [x['name'] for x in container_list]) self.assertIn(container['uuid'], [x['uuid'] for x in container_list]) count = 0 while count < 5: container = self.container_show(container['name']) if container['status'] == 'Created': break if container['status'] == 'Error': break time.sleep(2) count = count + 1 self.container_delete(container['name']) # Wait for the container to be deleted count = 0 while count < 5: container_list = self.container_list() if container['name'] not in [x['name'] for x in container_list]: break time.sleep(2) count = count + 1 container_list = self.container_list() self.assertNotIn(container['name'], [x['name'] for x in container_list]) self.assertNotIn(container['uuid'], [x['uuid'] for x in container_list]) def test_show(self): """Check container show command with name and UUID arguments. Test steps: 1) Create container in setUp. 2) Show container calling it with name and UUID arguments. 3) Check name, uuid and image in container show output. """ container = self.container_create(name='test_show') self.container_show(container['name']) self.assertEqual(container['name'], container['name']) self.assertEqual(container['image'], container['image']) self.container_delete(container['name']) def test_execute(self): """Check container execute command with name and UUID arguments. Test steps: 1) Create container in setUp. 2) Execute command calling it with name and UUID arguments. 3) Check the container logs. """ container = self.container_run(name='test_execute') count = 0 while count < 50: container = self.container_show(container['name']) if container['status'] == 'Running': break if container['status'] == 'Error': break time.sleep(2) count = count + 1 command = "sh -c 'echo hello'" result = self.container_execute(container['name'], command) self.assertIn('hello', result) self.container_delete(container['name']) python-zunclient-4.0.0/zunclient/tests/functional/osc/v1/__init__.py0000664000175000017500000000000013643163457025633 0ustar zuulzuul00000000000000python-zunclient-4.0.0/zunclient/tests/functional/osc/__init__.py0000664000175000017500000000000013643163457025305 0ustar zuulzuul00000000000000python-zunclient-4.0.0/zunclient/tests/functional/__init__.py0000664000175000017500000000000013643163457024521 0ustar zuulzuul00000000000000python-zunclient-4.0.0/zunclient/osc/0000775000175000017500000000000013643163533017675 5ustar zuulzuul00000000000000python-zunclient-4.0.0/zunclient/osc/v1/0000775000175000017500000000000013643163533020223 5ustar zuulzuul00000000000000python-zunclient-4.0.0/zunclient/osc/v1/capsules.py0000664000175000017500000001233313643163457022423 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. from osc_lib.command import command from osc_lib import utils from oslo_log import log as logging from zunclient.common import template_utils from zunclient.common import utils as zun_utils from zunclient.i18n import _ def _capsule_columns(capsule): return capsule._info.keys() def _get_client(obj, parsed_args): obj.log.debug("take_action(%s)" % parsed_args) return obj.app.client_manager.container class CreateCapsule(command.ShowOne): """Create a capsule""" log = logging.getLogger(__name__ + ".CreateCapsule") def get_parser(self, prog_name): parser = super(CreateCapsule, self).get_parser(prog_name) parser.add_argument( '--file', metavar='', required=True, help='Path to the capsule template file.') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) opts = {} opts['template'] = template_utils.get_template_contents( parsed_args.file) capsule = client.capsules.create(**opts) columns = _capsule_columns(capsule) return columns, utils.get_item_properties(capsule, columns) class ShowCapsule(command.ShowOne): """Show a capsule""" log = logging.getLogger(__name__ + ".ShowCapsule") def get_parser(self, prog_name): parser = super(ShowCapsule, self).get_parser(prog_name) parser.add_argument( 'capsule', metavar='', help='ID or name of the capsule to show.') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) opts = {} opts['id'] = parsed_args.capsule opts = zun_utils.remove_null_parms(**opts) capsule = client.capsules.get(**opts) zun_utils.format_container_addresses(capsule) columns = _capsule_columns(capsule) return columns, utils.get_item_properties(capsule, columns) class ListCapsule(command.Lister): """List available capsules""" log = logging.getLogger(__name__ + ".ListCapsule") def get_parser(self, prog_name): parser = super(ListCapsule, self).get_parser(prog_name) parser.add_argument( '--all-projects', action="store_true", default=False, help='List capsules in all projects') parser.add_argument( '--marker', metavar='', help='The last capsule UUID of the previous page; ' 'displays list of capsules after "marker".') parser.add_argument( '--limit', metavar='', type=int, help='Maximum number of capsules to return') parser.add_argument( '--sort-key', metavar='', help='Column to sort results by') parser.add_argument( '--sort-dir', metavar='', choices=['desc', 'asc'], help='Direction to sort. "asc" or "desc".') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) opts = {} opts['all_projects'] = parsed_args.all_projects opts['marker'] = parsed_args.marker opts['limit'] = parsed_args.limit opts['sort_key'] = parsed_args.sort_key opts['sort_dir'] = parsed_args.sort_dir opts = zun_utils.remove_null_parms(**opts) capsules = client.capsules.list(**opts) for capsule in capsules: zun_utils.format_container_addresses(capsule) columns = ('uuid', 'name', 'status', 'addresses') return (columns, (utils.get_item_properties(capsule, columns) for capsule in capsules)) class DeleteCapsule(command.Command): """Delete specified capsule(s)""" log = logging.getLogger(__name__ + ".DeleteCapsule") def get_parser(self, prog_name): parser = super(DeleteCapsule, self).get_parser(prog_name) parser.add_argument( 'capsule', metavar='', nargs='+', help='ID or name of the capsule(s) to delete.') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) capsules = parsed_args.capsule for capsule in capsules: opts = {} opts['id'] = capsule try: client.capsules.delete(**opts) print(_('Request to delete capsule %s has been accepted.') % capsule) except Exception as e: print("Delete for capsule %(capsule)s failed: %(e)s" % {'capsule': capsule, 'e': e}) python-zunclient-4.0.0/zunclient/osc/v1/images.py0000664000175000017500000001357413643163457022061 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. from oslo_log import log as logging from osc_lib.command import command from osc_lib import utils from zunclient.common import utils as zun_utils from zunclient.i18n import _ def _image_columns(image): return image._info.keys() def _get_client(obj, parsed_args): obj.log.debug("take_action(%s)" % parsed_args) return obj.app.client_manager.container class ListImage(command.Lister): """List available images""" log = logging.getLogger(__name__ + ".ListImage") def get_parser(self, prog_name): parser = super(ListImage, self).get_parser(prog_name) parser.add_argument( '--marker', metavar='', default=None, help='The last image UUID of the previous page; ' 'displays list of images after "marker".') parser.add_argument( '--limit', metavar='', type=int, help='Maximum number of images to return') parser.add_argument( '--sort-key', metavar='', help='Column to sort results by') parser.add_argument( '--sort-dir', metavar='', choices=['desc', 'asc'], help='Direction to sort. "asc" or "desc".') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) opts = {} opts['marker'] = parsed_args.marker opts['limit'] = parsed_args.limit opts['sort_key'] = parsed_args.sort_key opts['sort_dir'] = parsed_args.sort_dir opts = zun_utils.remove_null_parms(**opts) images = client.images.list(**opts) columns = ('uuid', 'image_id', 'repo', 'tag', 'size') return (columns, (utils.get_item_properties(image, columns) for image in images)) class PullImage(command.ShowOne): """Pull specified image into a host""" log = logging.getLogger(__name__ + ".PullImage") def get_parser(self, prog_name): parser = super(PullImage, self).get_parser(prog_name) parser.add_argument( 'image', metavar='', help='Name of the image') parser.add_argument( 'host', metavar='', help='Name or UUID of the host') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) opts = {} opts['repo'] = parsed_args.image opts['host'] = parsed_args.host image = client.images.create(**opts) columns = _image_columns(image) return columns, utils.get_item_properties(image, columns) class SearchImage(command.Lister): """Search specified image""" log = logging.getLogger(__name__ + ".SearchImage") def get_parser(self, prog_name): parser = super(SearchImage, self).get_parser(prog_name) parser.add_argument( '--image-driver', metavar='', help='Name of the image driver') parser.add_argument( 'image_name', metavar='', help='Name of the image') parser.add_argument( '--exact-match', default=False, action='store_true', help='exact match image name') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) opts = {} opts['image_driver'] = parsed_args.image_driver opts['image'] = parsed_args.image_name opts['exact_match'] = parsed_args.exact_match opts = zun_utils.remove_null_parms(**opts) images = client.images.search_image(**opts) columns = ('ID', 'Name', 'Tags', 'Status', 'Size', 'Metadata') return (columns, (utils.get_item_properties(image, columns) for image in images)) class ShowImage(command.ShowOne): """Describe a specific image""" log = logging.getLogger(__name__ + ".ShowImage") def get_parser(self, prog_name): parser = super(ShowImage, self).get_parser(prog_name) parser.add_argument( 'uuid', metavar='', help='UUID of image to describe') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) opts = {} opts['id'] = parsed_args.uuid image = client.images.get(**opts) columns = _image_columns(image) return columns, utils.get_item_properties(image, columns) class DeleteImage(command.Command): """Delete specified image from a host""" log = logging.getLogger(__name__ + ".DeleteImage") def get_parser(self, prog_name): parser = super(DeleteImage, self).get_parser(prog_name) parser.add_argument( 'uuid', metavar='', help='UUID of image to describe') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) opts = {} opts['image_id'] = parsed_args.uuid try: client.images.delete(**opts) print(_('Request to delete image %s has been accepted.') % opts['image_id']) except Exception as e: print("Delete for image %(image)s failed: %(e)s" % {'image': opts['image_id'], 'e': e}) python-zunclient-4.0.0/zunclient/osc/v1/availability_zones.py0000664000175000017500000000247713643163457024504 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. from oslo_log import log as logging from osc_lib.command import command from osc_lib import utils def _get_client(obj, parsed_args): obj.log.debug("take_action(%s)" % parsed_args) return obj.app.client_manager.container class ListAvailabilityZone(command.Lister): """List availability zones""" log = logging.getLogger(__name__ + ".ListAvailabilityZones") def get_parser(self, prog_name): parser = super(ListAvailabilityZone, self).get_parser(prog_name) return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) zones = client.availability_zones.list() columns = ('availability_zone',) return (columns, (utils.get_item_properties(zone, columns) for zone in zones)) python-zunclient-4.0.0/zunclient/osc/v1/containers.py0000664000175000017500000017203413643163457022756 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. import argparse from contextlib import closing import io import os from oslo_log import log as logging import tarfile import time from osc_lib.command import command from osc_lib import utils from zunclient.common import utils as zun_utils from zunclient.common.websocketclient import exceptions from zunclient.common.websocketclient import websocketclient from zunclient import exceptions as exc from zunclient.i18n import _ def _container_columns(container): return container._info.keys() def _get_client(obj, parsed_args): obj.log.debug("take_action(%s)" % parsed_args) return obj.app.client_manager.container def _action_columns(action): return action._info.keys() class CreateContainer(command.ShowOne): """Create a container""" log = logging.getLogger(__name__ + ".CreateContainer") def get_parser(self, prog_name): parser = super(CreateContainer, self).get_parser(prog_name) parser.add_argument( '--name', metavar='', help='name of the container') parser.add_argument( 'image', metavar='', help='name or ID or repo of the image (e.g. cirros:latest)') parser.add_argument( '--cpu', metavar='', help='The number of virtual cpus.') parser.add_argument( '--memory', metavar='', help='The container memory size in MiB') parser.add_argument( '--environment', metavar='', action='append', default=[], help='The environment variables') parser.add_argument( '--workdir', metavar='', help='The working directory for commands to run in') parser.add_argument( '--label', metavar='', action='append', default=[], help='Adds a map of labels to a container. ' 'May be used multiple times.') parser.add_argument( '--image-pull-policy', dest='image_pull_policy', metavar='', choices=['never', 'always', 'ifnotpresent'], help='The policy which determines if the image should ' 'be pulled prior to starting the container. ' 'It can have following values: ' '"ifnotpresent": only pull the image if it does not ' 'already exist on the node. ' '"always": Always pull the image from repository.' '"never": never pull the image') restart_auto_remove_args = parser.add_mutually_exclusive_group() restart_auto_remove_args.add_argument( '--restart', metavar='', help='Restart policy to apply when a container exits' '(no, on-failure[:max-retry], always, unless-stopped)') restart_auto_remove_args.add_argument( '--auto-remove', dest='auto_remove', action='store_true', default=False, help='Automatically remove the container when it exits') parser.add_argument( '--image-driver', metavar='', help='The image driver to use to pull container image. ' 'It can have following values: ' '"docker": pull the image from Docker Hub. ' '"glance": pull the image from Glance. ') parser.add_argument( '--interactive', dest='interactive', action='store_true', default=False, help='Keep STDIN open even if not attached, allocate a pseudo-TTY') secgroup_expose_port_args = parser.add_mutually_exclusive_group() secgroup_expose_port_args.add_argument( '--security-group', metavar='', action='append', default=[], help='The name of security group for the container. ' 'May be used multiple times.') secgroup_expose_port_args.add_argument( '--expose-port', action='append', default=[], metavar='', help='Expose container port(s) to outside (format: ' '[/]).') parser.add_argument( 'command', metavar='', nargs=argparse.REMAINDER, help='Send command to the container') parser.add_argument( '--hint', metavar='', action='append', default=[], help='The key-value pair(s) for scheduler to select host. ' 'The format of this parameter is "key=value[,key=value]". ' 'May be used multiple times.') parser.add_argument( '--net', metavar='', action='append', default=[], help='Create network enpoints for the container. ' 'network: attach container to the specified neutron networks.' ' port: attach container to the neutron port with this UUID. ' 'v4-fixed-ip: IPv4 fixed address for container. ' 'v6-fixed-ip: IPv6 fixed address for container.') parser.add_argument( '--mount', action='append', default=[], metavar='', help='A dictionary to configure volumes mounted inside the ' 'container.') parser.add_argument( '--runtime', metavar='', help='The runtime to use for this container. ' 'It can have value "runc" or any other custom runtime.') parser.add_argument( '--hostname', metavar='', help='Container host name') parser.add_argument( '--disk', metavar='', type=int, default=None, help='The disk size in GiB for per container.') parser.add_argument( '--availability-zone', metavar='', default=None, help='The availability zone of the container.') parser.add_argument( '--auto-heal', dest='auto_heal', action='store_true', default=False, help='The flag of healing non-existent container in docker') parser.add_argument( '--privileged', dest='privileged', action='store_true', default=False, help='Give extended privileges to this container') parser.add_argument( '--healthcheck', action='append', default=[], metavar='', help='Specify a test cmd to perform to check that the container' 'is healthy. ' 'cmd: Command to run to check health. ' 'interval: Time between running the check (``s|m|h``)' ' (default 0s). ' 'retries: Consecutive failures needed to report unhealthy.' 'timeout: Maximum time to allow one check to run (``s|m|h``)' ' (default 0s).') parser.add_argument( '--wait', action='store_true', help='Wait for create to complete') parser.add_argument( '--registry', metavar='', help='The container image registry ID or name.') parser.add_argument( '--host', metavar='', help='Requested host to create containers. Admin only by ' 'default. (supported by --os-container-api-version 1.39 ' 'or above') parser.add_argument( '--entrypoint', metavar='', help='The entrypoint which overwrites the default ENTRYPOINT ' 'of the image.') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) opts = {} opts['name'] = parsed_args.name opts['image'] = parsed_args.image opts['memory'] = parsed_args.memory opts['cpu'] = parsed_args.cpu opts['environment'] = zun_utils.format_args(parsed_args.environment) opts['workdir'] = parsed_args.workdir opts['labels'] = zun_utils.format_args(parsed_args.label) opts['image_pull_policy'] = parsed_args.image_pull_policy opts['image_driver'] = parsed_args.image_driver opts['auto_remove'] = parsed_args.auto_remove opts['command'] = parsed_args.command opts['registry'] = parsed_args.registry opts['host'] = parsed_args.host opts['entrypoint'] = zun_utils.parse_entrypoint(parsed_args.entrypoint) if parsed_args.security_group: opts['security_groups'] = parsed_args.security_group if parsed_args.expose_port: opts['exposed_ports'] = zun_utils.parse_exposed_ports( parsed_args.expose_port) if parsed_args.restart: opts['restart_policy'] = \ zun_utils.check_restart_policy(parsed_args.restart) if parsed_args.interactive: opts['interactive'] = True if parsed_args.privileged: opts['privileged'] = True opts['hints'] = zun_utils.format_args(parsed_args.hint) opts['nets'] = zun_utils.parse_nets(parsed_args.net) opts['mounts'] = zun_utils.parse_mounts(parsed_args.mount) opts['runtime'] = parsed_args.runtime opts['hostname'] = parsed_args.hostname opts['disk'] = parsed_args.disk opts['availability_zone'] = parsed_args.availability_zone if parsed_args.auto_heal: opts['auto_heal'] = parsed_args.auto_heal if parsed_args.healthcheck: opts['healthcheck'] = \ zun_utils.parse_health(parsed_args.healthcheck) opts = zun_utils.remove_null_parms(**opts) container = client.containers.create(**opts) if parsed_args.wait: container_uuid = getattr(container, 'uuid', None) if utils.wait_for_status( client.containers.get, container_uuid, success_status=['created'], ): container = client.containers.get(container_uuid) else: print('Failed to create container.\n') raise SystemExit columns = _container_columns(container) return columns, utils.get_item_properties(container, columns) class ShowContainer(command.ShowOne): """Show a container""" log = logging.getLogger(__name__ + ".ShowContainer") def get_parser(self, prog_name): parser = super(ShowContainer, self).get_parser(prog_name) parser.add_argument( 'container', metavar='', help='ID or name of the container to show.') parser.add_argument( '--all-projects', action="store_true", default=False, help='Show container(s) in all projects by name.') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) opts = {} opts['id'] = parsed_args.container opts['all_projects'] = parsed_args.all_projects opts = zun_utils.remove_null_parms(**opts) container = client.containers.get(**opts) zun_utils.format_container_addresses(container) columns = _container_columns(container) return columns, utils.get_item_properties(container, columns) class ListContainer(command.Lister): """List available containers""" log = logging.getLogger(__name__ + ".ListContainers") def get_parser(self, prog_name): parser = super(ListContainer, self).get_parser(prog_name) parser.add_argument( '--all-projects', action="store_true", default=False, help='List containers in all projects') parser.add_argument( '--marker', metavar='', help='The last container UUID of the previous page; ' 'displays list of containers after "marker".') parser.add_argument( '--limit', metavar='', type=int, help='Maximum number of containers to return') parser.add_argument( '--sort-key', metavar='', help='Column to sort results by') parser.add_argument( '--sort-dir', metavar='', choices=['desc', 'asc'], help='Direction to sort. "asc" or "desc".') parser.add_argument( '--name', metavar='', help='List containers according to their name.') parser.add_argument( '--image', metavar='', help='List containers according to their image.') parser.add_argument( '--project-id', metavar='', help='List containers according to their project_id') parser.add_argument( '--user-id', metavar='', help='List containers according to their user_id') parser.add_argument( '--task-state', metavar='', help='List containers according to their task-state') parser.add_argument( '--status', metavar='', help='List containers according to their Status') parser.add_argument( '--memory', metavar='', help='List containers according to their memory size in MiB') parser.add_argument( '--host', metavar='', help='List containers according to their hostname') parser.add_argument( '--auto-remove', metavar='', help='List containers whether they are auto-removed on exiting') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) opts = {} opts['all_projects'] = parsed_args.all_projects opts['marker'] = parsed_args.marker opts['limit'] = parsed_args.limit opts['sort_key'] = parsed_args.sort_key opts['sort_dir'] = parsed_args.sort_dir opts['image'] = parsed_args.image opts['name'] = parsed_args.name opts['project_id'] = parsed_args.project_id opts['user_id'] = parsed_args.user_id opts['host'] = parsed_args.host opts['task_state'] = parsed_args.task_state opts['memory'] = parsed_args.memory opts['auto_remove'] = parsed_args.auto_remove opts['status'] = parsed_args.status opts = zun_utils.remove_null_parms(**opts) containers = client.containers.list(**opts) for c in containers: zun_utils.format_container_addresses(c) columns = ('uuid', 'name', 'image', 'status', 'task_state', 'addresses', 'ports') return (columns, (utils.get_item_properties(container, columns) for container in containers)) class DeleteContainer(command.Command): """Delete specified container(s)""" log = logging.getLogger(__name__ + ".Deletecontainer") def get_parser(self, prog_name): parser = super(DeleteContainer, self).get_parser(prog_name) parser.add_argument( 'container', metavar='', nargs='+', help='ID or name of the container(s) to delete.') parser.add_argument( '--force', action='store_true', help='Force delete the container.') parser.add_argument( '--stop', action='store_true', help='Stop the running container first before delete.') parser.add_argument( '--all-projects', action="store_true", default=False, help='Delete container(s) in all projects by name.') parser.add_argument( '--wait', action='store_true', help='Wait for create to complete') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) containers = parsed_args.container for container in containers: opts = {} opts['id'] = container opts['force'] = parsed_args.force opts['stop'] = parsed_args.stop opts['all_projects'] = parsed_args.all_projects opts = zun_utils.remove_null_parms(**opts) try: client.containers.delete(**opts) print(_('Request to delete container %s has been accepted.') % container) if parsed_args.wait: if utils.wait_for_delete( client.containers, container, timeout=30 ): print("Delete for container %(container)s success." % {'container': container}) else: print("Delete for container %(container)s failed." % {'container': container}) raise SystemExit except Exception as e: print("Delete for container %(container)s failed: %(e)s" % {'container': container, 'e': e}) class RestartContainer(command.Command): """Restart specified container""" log = logging.getLogger(__name__ + ".RestartContainer") def get_parser(self, prog_name): parser = super(RestartContainer, self).get_parser(prog_name) parser.add_argument( 'container', metavar='', nargs='+', help='ID or name of the (container)s to restart.') parser.add_argument( '--timeout', metavar='', default=10, help='Seconds to wait for stop before restarting (container)s') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) containers = parsed_args.container for container in containers: try: client.containers.restart(container, parsed_args.timeout) print(_('Request to restart container %s has been accepted') % container) except Exception as e: print("Restart for container %(container)s failed: %(e)s" % {'container': container, 'e': e}) class StartContainer(command.Command): """Start specified container""" log = logging.getLogger(__name__ + ".StartContainer") def get_parser(self, prog_name): parser = super(StartContainer, self).get_parser(prog_name) parser.add_argument( 'container', metavar='', nargs='+', help='ID or name of the (container)s to start.') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) containers = parsed_args.container for container in containers: try: client.containers.start(container) print(_('Request to start container %s has been accepted') % container) except Exception as e: print("Start for container %(container)s failed: %(e)s" % {'container': container, 'e': e}) class PauseContainer(command.Command): """Pause specified container""" log = logging.getLogger(__name__ + ".PauseContainer") def get_parser(self, prog_name): parser = super(PauseContainer, self).get_parser(prog_name) parser.add_argument( 'container', metavar='', nargs='+', help='ID or name of the (container)s to pause.') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) containers = parsed_args.container for container in containers: try: client.containers.pause(container) print(_('Request to pause container %s has been accepted') % container) except Exception as e: print("Pause for container %(container)s failed: %(e)s" % {'container': container, 'e': e}) class UnpauseContainer(command.Command): """unpause specified container""" log = logging.getLogger(__name__ + ".UnpauseContainer") def get_parser(self, prog_name): parser = super(UnpauseContainer, self).get_parser(prog_name) parser.add_argument( 'container', metavar='', nargs='+', help='ID or name of the (container)s to unpause.') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) containers = parsed_args.container for container in containers: try: client.containers.unpause(container) print(_('Request to unpause container %s has been accepted') % container) except Exception as e: print("unpause for container %(container)s failed: %(e)s" % {'container': container, 'e': e}) class ExecContainer(command.Command): """Execute command in a running container""" log = logging.getLogger(__name__ + ".ExecContainer") def get_parser(self, prog_name): parser = super(ExecContainer, self).get_parser(prog_name) parser.add_argument( 'container', metavar='', help='ID or name of the container to execute command in.') parser.add_argument( 'command', metavar='', nargs=argparse.REMAINDER, help='The command to execute.') parser.add_argument( '--interactive', dest='interactive', action='store_true', default=False, help='Keep STDIN open and allocate a pseudo-TTY for interactive') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) container = parsed_args.container opts = {} opts['command'] = zun_utils.parse_command(parsed_args.command) if parsed_args.interactive: opts['interactive'] = True opts['run'] = False response = client.containers.execute(container, **opts) if parsed_args.interactive: exec_id = response['exec_id'] url = response['proxy_url'] websocketclient.do_exec(client, url, container, exec_id, "~", 0.5) else: output = response['output'] exit_code = response['exit_code'] print(output) return exit_code class LogsContainer(command.Command): """Get logs of a container""" log = logging.getLogger(__name__ + ".LogsContainer") def get_parser(self, prog_name): parser = super(LogsContainer, self).get_parser(prog_name) parser.add_argument( 'container', metavar='', help='ID or name of the container to get logs for.') parser.add_argument( '--stdout', action='store_true', help='Only stdout logs of container.') parser.add_argument( '--stderr', action='store_true', help='Only stderr logs of container.') parser.add_argument( '--since', metavar='', default=None, help='Show logs since a given datetime or integer ' 'epoch (in seconds).') parser.add_argument( '--timestamps', dest='timestamps', action='store_true', default=False, help='Show timestamps.') parser.add_argument( '--tail', metavar='', default='all', help='Number of lines to show from the end of the logs.') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) opts = {} opts['id'] = parsed_args.container opts['stdout'] = parsed_args.stdout opts['stderr'] = parsed_args.stderr opts['since'] = parsed_args.since opts['timestamps'] = parsed_args.timestamps opts['tail'] = parsed_args.tail opts = zun_utils.remove_null_parms(**opts) logs = client.containers.logs(**opts) print(logs) class KillContainer(command.Command): """Kill one or more running container(s)""" log = logging.getLogger(__name__ + ".KillContainers") def get_parser(self, prog_name): parser = super(KillContainer, self).get_parser(prog_name) parser.add_argument( 'containers', metavar='', nargs='+', help='ID or name of the (container)s to kill.') parser.add_argument( '--signal', metavar='', default=None, help='The signal to kill') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) for container in parsed_args.containers: opts = {} opts['id'] = container opts['signal'] = parsed_args.signal opts = zun_utils.remove_null_parms(**opts) try: client.containers.kill(**opts) print(_('Request to send kill signal to container %s has ' 'been accepted') % container) except Exception as e: print("kill signal for container %(container)s failed: %(e)s" % {'container': container, 'e': e}) class StopContainer(command.Command): """Stop specified containers""" log = logging.getLogger(__name__ + ".StopContainer") def get_parser(self, prog_name): parser = super(StopContainer, self).get_parser(prog_name) parser.add_argument( 'container', metavar='', nargs='+', help='ID or name of the (container)s to stop.') parser.add_argument( '--timeout', metavar='', default=10, help='Seconds to wait for stop before killing (container)s') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) containers = parsed_args.container for container in containers: try: client.containers.stop(container, parsed_args.timeout) print(_('Request to stop container %s has been accepted.') % container) except Exception as e: print("Stop for container %(container)s failed: %(e)s" % {'container': container, 'e': e}) class RunContainer(command.ShowOne): """Create and run a new container""" log = logging.getLogger(__name__ + ".RunContainer") def get_parser(self, prog_name): parser = super(RunContainer, self).get_parser(prog_name) parser.add_argument( '--name', metavar='', help='name of the container') parser.add_argument( 'image', metavar='', help='name or ID of the image') parser.add_argument( '--cpu', metavar='', help='The number of virtual cpus.') parser.add_argument( '--memory', metavar='', help='The container memory size in MiB') parser.add_argument( '--environment', metavar='', action='append', default=[], help='The environment variables') parser.add_argument( '--workdir', metavar='', help='The working directory for commands to run in') parser.add_argument( '--label', metavar='', action='append', default=[], help='Adds a map of labels to a container. ' 'May be used multiple times.') parser.add_argument( '--image-pull-policy', dest='image_pull_policy', metavar='', choices=['never', 'always', 'ifnotpresent'], help='The policy which determines if the image should ' 'be pulled prior to starting the container. ' 'It can have following values: ' '"ifnotpresent": only pull the image if it does not ' 'already exist on the node. ' '"always": Always pull the image from repository.' '"never": never pull the image') restart_auto_remove_args = parser.add_mutually_exclusive_group() restart_auto_remove_args.add_argument( '--restart', metavar='', help='Restart policy to apply when a container exits' '(no, on-failure[:max-retry], always, unless-stopped)') restart_auto_remove_args.add_argument( '--auto-remove', dest='auto_remove', action='store_true', default=False, help='Automatically remove the container when it exits') parser.add_argument( '--image-driver', metavar='', help='The image driver to use to pull container image. ' 'It can have following values: ' '"docker": pull the image from Docker Hub. ' '"glance": pull the image from Glance. ') parser.add_argument( '--interactive', dest='interactive', action='store_true', default=False, help='Keep STDIN open even if not attached, allocate a pseudo-TTY') secgroup_expose_port_args = parser.add_mutually_exclusive_group() secgroup_expose_port_args.add_argument( '--security-group', metavar='', action='append', default=[], help='The name of security group for the container. ' 'May be used multiple times.') secgroup_expose_port_args.add_argument( '--expose-port', action='append', default=[], metavar='', help='Expose container port(s) to outside (format: ' '[/]).') parser.add_argument( 'command', metavar='', nargs=argparse.REMAINDER, help='Send command to the container') parser.add_argument( '--hint', metavar='', action='append', default=[], help='The key-value pair(s) for scheduler to select host. ' 'The format of this parameter is "key=value[,key=value]". ' 'May be used multiple times.') parser.add_argument( '--net', metavar='', action='append', default=[], help='Create network enpoints for the container. ' 'network: attach container to the specified neutron networks.' ' port: attach container to the neutron port with this UUID. ' 'v4-fixed-ip: IPv4 fixed address for container. ' 'v6-fixed-ip: IPv6 fixed address for container.') parser.add_argument( '--mount', action='append', default=[], metavar='', help='A dictionary to configure volumes mounted inside the ' 'container.') parser.add_argument( '--runtime', metavar='', help='The runtime to use for this container. ' 'It can have value "runc" or any other custom runtime.') parser.add_argument( '--hostname', metavar='', help='Container host name') parser.add_argument( '--disk', metavar='', type=int, default=None, help='The disk size in GiB for per container.') parser.add_argument( '--availability-zone', metavar='', default=None, help='The availability zone of the container.') parser.add_argument( '--auto-heal', dest='auto_heal', action='store_true', default=False, help='The flag of healing non-existent container in docker') parser.add_argument( '--privileged', dest='privileged', action='store_true', default=False, help='Give extended privileges to this container') parser.add_argument( '--healthcheck', action='append', default=[], metavar='', help='Specify a test cmd to perform to check that the container' 'is healthy. ' 'cmd: Command to run to check health. ' 'interval: Time between running the check (``s|m|h``)' ' (default 0s). ' 'retries: Consecutive failures needed to report unhealthy.' 'timeout: Maximum time to allow one check to run (``s|m|h``)' ' (default 0s).') parser.add_argument( '--wait', action='store_true', help='Wait for run to complete') parser.add_argument( '--registry', metavar='', help='The container image registry ID or name.') parser.add_argument( '--host', metavar='', help='Requested host to run containers. Admin only by ' 'default. (supported by --os-container-api-version 1.39 ' 'or above') parser.add_argument( '--entrypoint', metavar='', help='The entrypoint which overwrites the default ENTRYPOINT ' 'of the image.') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) opts = {} opts['name'] = parsed_args.name opts['image'] = parsed_args.image opts['memory'] = parsed_args.memory opts['cpu'] = parsed_args.cpu opts['environment'] = zun_utils.format_args(parsed_args.environment) opts['workdir'] = parsed_args.workdir opts['labels'] = zun_utils.format_args(parsed_args.label) opts['image_pull_policy'] = parsed_args.image_pull_policy opts['image_driver'] = parsed_args.image_driver opts['auto_remove'] = parsed_args.auto_remove opts['command'] = parsed_args.command opts['registry'] = parsed_args.registry opts['host'] = parsed_args.host opts['entrypoint'] = zun_utils.parse_entrypoint(parsed_args.entrypoint) if parsed_args.security_group: opts['security_groups'] = parsed_args.security_group if parsed_args.expose_port: opts['exposed_ports'] = zun_utils.parse_exposed_ports( parsed_args.expose_port) if parsed_args.restart: opts['restart_policy'] = \ zun_utils.check_restart_policy(parsed_args.restart) if parsed_args.interactive: opts['interactive'] = True if parsed_args.privileged: opts['privileged'] = True opts['hints'] = zun_utils.format_args(parsed_args.hint) opts['nets'] = zun_utils.parse_nets(parsed_args.net) opts['mounts'] = zun_utils.parse_mounts(parsed_args.mount) opts['runtime'] = parsed_args.runtime opts['hostname'] = parsed_args.hostname opts['disk'] = parsed_args.disk opts['availability_zone'] = parsed_args.availability_zone if parsed_args.auto_heal: opts['auto_heal'] = parsed_args.auto_heal if parsed_args.healthcheck: opts['healthcheck'] = \ zun_utils.parse_health(parsed_args.healthcheck) opts = zun_utils.remove_null_parms(**opts) container = client.containers.run(**opts) columns = _container_columns(container) container_uuid = getattr(container, 'uuid', None) if parsed_args.wait: if utils.wait_for_status( client.containers.get, container_uuid, success_status=['running'], ): container = client.containers.get(container_uuid) else: print('Failed to run container.\n') raise SystemExit if parsed_args.interactive: ready_for_attach = False while True: container = client.containers.get(container_uuid) if zun_utils.check_container_status(container, 'Running'): ready_for_attach = True break if zun_utils.check_container_status(container, 'Error'): break print("Waiting for container start") time.sleep(1) if ready_for_attach is True: response = client.containers.attach(container_uuid) websocketclient.do_attach(client, response, container_uuid, "~", 0.5) else: raise exceptions.InvalidWebSocketLink(container_uuid) return columns, utils.get_item_properties(container, columns) class TopContainer(command.Command): """Display the running processes inside the container""" log = logging.getLogger(__name__ + ".TopContainer") def get_parser(self, prog_name): parser = super(TopContainer, self).get_parser(prog_name) parser.add_argument( 'container', metavar='', help='ID or name of the container to display processes.') parser.add_argument( '--pid', metavar='', action='append', default=[], help='The args of the ps id.') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) if parsed_args.pid: # List container single ps id top result output = client.containers.top(parsed_args.container, ' '.join(parsed_args.pid)) else: # List container all processes top result output = client.containers.top(parsed_args.container) for titles in output['Titles']: print("%-20s") % titles, if output['Processes']: for process in output['Processes']: print("") for info in process: print("%-20s") % info, else: print("") class UpdateContainer(command.ShowOne): """Update one or more attributes of the container""" log = logging.getLogger(__name__ + ".UpdateContainer") def get_parser(self, prog_name): parser = super(UpdateContainer, self).get_parser(prog_name) parser.add_argument( 'container', metavar='', help="ID or name of the container to update.") parser.add_argument( '--cpu', metavar='', help='The number of virtual cpus.') parser.add_argument( '--memory', metavar='', help='The container memory size in MiB') parser.add_argument( '--name', metavar='', help='The new name of container to update') auto_heal_value = parser.add_mutually_exclusive_group() auto_heal_value.add_argument( '--auto-heal', required=False, action='store_true', help='Automatic recovery the status of contaier') auto_heal_value.add_argument( '--no-auto-heal', required=False, action='store_true', help='Needless recovery the status of contaier') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) container = parsed_args.container opts = {} opts['memory'] = parsed_args.memory opts['cpu'] = parsed_args.cpu opts['name'] = parsed_args.name if 'auto_heal' in parsed_args and parsed_args.auto_heal: opts['auto_heal'] = True if 'no_auto_heal' in parsed_args and parsed_args.no_auto_heal: opts['auto_heal'] = False opts = zun_utils.remove_null_parms(**opts) if not opts: raise exc.CommandError("You must update at least one property") container = client.containers.update(container, **opts) columns = _container_columns(container) return columns, utils.get_item_properties(container, columns) class AttachContainer(command.Command): """Attach to a running container""" log = logging.getLogger(__name__ + ".AttachContainer") def get_parser(self, prog_name): parser = super(AttachContainer, self).get_parser(prog_name) parser.add_argument( 'container', metavar='', help='ID or name of the container to be attached to.') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) response = client.containers.attach(parsed_args.container) websocketclient.do_attach(client, response, parsed_args.container, "~", 0.5) class CopyContainer(command.Command): """Copy files/tars between a container and the local filesystem.""" log = logging.getLogger(__name__ + ".CopyContainer") def get_parser(self, prog_name): parser = super(CopyContainer, self).get_parser(prog_name) parser.add_argument( 'source', metavar='', help='The source should be copied to the container or localhost. ' 'The format of this parameter is [container:]src_path.') parser.add_argument( 'destination', metavar='', help='The directory destination where save the source. ' 'The format of this parameter is [container:]dest_path.') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) if ':' in parsed_args.source: source_parts = parsed_args.source.split(':', 1) container_id = source_parts[0] container_path = source_parts[1] opts = {} opts['id'] = container_id opts['path'] = container_path res = client.containers.get_archive(**opts) dest_path = parsed_args.destination tardata = io.BytesIO(res['data']) with closing(tarfile.open(fileobj=tardata)) as tar: tar.extractall(dest_path) elif ':' in parsed_args.destination: dest_parts = parsed_args.destination.split(':', 1) container_id = dest_parts[0] container_path = dest_parts[1] filename = os.path.split(parsed_args.source)[1] opts = {} opts['id'] = container_id opts['path'] = container_path tardata = io.BytesIO() with closing(tarfile.open(fileobj=tardata, mode='w')) as tar: tar.add(parsed_args.source, arcname=filename) opts['data'] = tardata.getvalue() client.containers.put_archive(**opts) else: print("Please check the parameters for zun copy!") print("Usage:") print("openstack appcontainer cp container:src_path dest_path|-") print("openstack appcontainer cp src_path|- container:dest_path") class StatsContainer(command.ShowOne): """Display stats of the container.""" log = logging.getLogger(__name__ + ".StatsContainer") def get_parser(self, prog_name): parser = super(StatsContainer, self).get_parser(prog_name) parser.add_argument( 'container', metavar='', help='ID or name of the (container)s to display stats.') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) container = parsed_args.container stats_info = client.containers.stats(container) return stats_info.keys(), stats_info.values() class CommitContainer(command.Command): """Create a new image from a container's changes""" log = logging.getLogger(__name__ + ".CommitContainer") def get_parser(self, prog_name): parser = super(CommitContainer, self).get_parser(prog_name) parser.add_argument( 'container', metavar='', help='ID or name of the (container)s to commit.') parser.add_argument( 'repository', metavar='[:]', help='Repository and tag of the new image.') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) container = parsed_args.container opts = zun_utils.check_commit_container_args(parsed_args) opts = zun_utils.remove_null_parms(**opts) try: image = client.containers.commit(container, **opts) print("Request to commit container %s has been accepted. " "The image is %s." % (container, image['uuid'])) except Exception as e: print("commit container %(container)s failed: %(e)s" % {'container': container, 'e': e}) class AddSecurityGroup(command.Command): """Add security group for specified container.""" log = logging.getLogger(__name__ + ".AddSecurityGroup") def get_parser(self, prog_name): parser = super(AddSecurityGroup, self).get_parser(prog_name) parser.add_argument( 'container', metavar='', help='ID or name of the container to add security group.') parser.add_argument( 'security_group', metavar='', help='Security group ID or name for specified container. ') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) opts = {} opts['id'] = parsed_args.container opts['security_group'] = parsed_args.security_group opts = zun_utils.remove_null_parms(**opts) try: # TODO(hongbin): add_security_group is removed starting from # API version 1.15. Use Neutron APIs to add security groups # to container's ports instead. client.containers.add_security_group(**opts) print("Request to add security group for container %s " "has been accepted." % parsed_args.container) except Exception as e: print("Add security group for container %(container)s failed: " "%(e)s" % {'container': parsed_args.container, 'e': e}) class RemoveSecurityGroup(command.Command): """Remove security group for specified container.""" log = logging.getLogger(__name__ + ".RemoveSecurityGroup") def get_parser(self, prog_name): parser = super(RemoveSecurityGroup, self).get_parser(prog_name) parser.add_argument( 'container', metavar='', help='ID or name of the container to remove security group.') parser.add_argument( 'security_group', metavar='', help='The security group to remove from specified container. ') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) opts = {} opts['id'] = parsed_args.container opts['security_group'] = parsed_args.security_group opts = zun_utils.remove_null_parms(**opts) try: # TODO(hongbin): remove_security_group is removed starting from # API version 1.15. Use Neutron APIs to remove security groups # from container's ports instead. client.containers.remove_security_group(**opts) print("Request to remove security group from container %s " "has been accepted." % parsed_args.container) except Exception as e: print("Remove security group from container %(container)s failed: " "%(e)s" % {'container': parsed_args.container, 'e': e}) class NetworkDetach(command.Command): """Detach neutron network from specified container.""" log = logging.getLogger(__name__ + ".NetworkDetach") def get_parser(self, prog_name): parser = super(NetworkDetach, self).get_parser(prog_name) parser.add_argument( 'container', metavar='', help='ID or name of the container to detach network.') network_port_args = parser.add_mutually_exclusive_group() network_port_args.add_argument( '--network', metavar='', help='The network for specified container to detach.') network_port_args.add_argument( '--port', metavar='', help='The port for specified container to detach.') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) opts = {} opts['container'] = parsed_args.container opts['network'] = parsed_args.network opts['port'] = parsed_args.port opts = zun_utils.remove_null_parms(**opts) try: client.containers.network_detach(**opts) print("Request to detach network for container %s " "has been accepted." % parsed_args.container) except Exception as e: print("Detach network for container %(container)s failed: " "%(e)s" % {'container': parsed_args.container, 'e': e}) class NetworkAttach(command.Command): """Attach neutron network to specified container.""" log = logging.getLogger(__name__ + ".NetworkAttach") def get_parser(self, prog_name): parser = super(NetworkAttach, self).get_parser(prog_name) parser.add_argument( 'container', metavar='', help='ID or name of the container to attach network.') parser.add_argument( '--network', metavar='', help='The network for specified container to attach.') parser.add_argument( '--port', metavar='', help='The port for specified container to attach.') parser.add_argument( '--fixed-ip', metavar='', help='The fixed-ip that container will attach to.') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) opts = {} opts['container'] = parsed_args.container opts['network'] = parsed_args.network opts['port'] = parsed_args.port opts['fixed_ip'] = parsed_args.fixed_ip opts = zun_utils.remove_null_parms(**opts) try: client.containers.network_attach(**opts) print("Request to attach network to container %s " "has been accepted." % parsed_args.container) except Exception as e: print("Attach network to container %(container)s failed: " "%(e)s" % {'container': parsed_args.container, 'e': e}) class RebuildContainer(command.Command): """Rebuild one or more running container(s)""" log = logging.getLogger(__name__ + ".RebuildContainer") def get_parser(self, prog_name): parser = super(RebuildContainer, self).get_parser(prog_name) parser.add_argument( 'containers', metavar='', nargs='+', help='ID or name of the (container)s to rebuild.') parser.add_argument( '--image', metavar='', help='The image for specified container to update.') parser.add_argument( '--image-driver', metavar='', help='The image driver to use to update container image. ' 'It can have following values: ' '"docker": update the image from Docker Hub. ' '"glance": update the image from Glance. ' 'The default value is source container\'s image driver ') parser.add_argument( '--wait', action='store_true', help='Wait for rebuild to complete') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) for container in parsed_args.containers: opts = {} opts['id'] = container if parsed_args.image: opts['image'] = parsed_args.image if parsed_args.image_driver: opts['image_driver'] = parsed_args.image_driver try: client.containers.rebuild(**opts) print(_('Request to rebuild container %s has ' 'been accepted') % container) if parsed_args.wait: if utils.wait_for_status( client.containers.get, container, success_status=['created', 'running'], ): print("rebuild container %(container)s success." % {'container': container}) else: print("rebuild container %(container)s failed." % {'container': container}) raise SystemExit except Exception as e: print("rebuild container %(container)s failed: %(e)s" % {'container': container, 'e': e}) class NetworkList(command.Lister): """List networks on a container""" log = logging.getLogger(__name__ + ".ListNetworks") def get_parser(self, prog_name): parser = super(NetworkList, self).get_parser(prog_name) parser.add_argument( 'container', metavar='', help='ID or name of the container to list networks.' ) return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) opts = {} opts['container'] = parsed_args.container opts = zun_utils.remove_null_parms(**opts) networks = client.containers.network_list(**opts) columns = ('net_id', 'port_id', 'fixed_ips') return (columns, (utils.get_item_properties( network, columns, formatters={ 'fixed_ips': zun_utils.format_fixed_ips}) for network in networks)) class ActionList(command.Lister): """List actions on a container""" log = logging.getLogger(__name__ + ".ListActions") def get_parser(self, prog_name): parser = super(ActionList, self).get_parser(prog_name) parser.add_argument( 'container', metavar='', help='ID or name of the container to list actions.' ) return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) container = parsed_args.container actions = client.actions.list(container) columns = ('user_id', 'container_uuid', 'request_id', 'action', 'message', 'start_time') return (columns, (utils.get_item_properties(action, columns) for action in actions)) class ActionShow(command.ShowOne): """Show a action""" log = logging.getLogger(__name__ + ".ShowAction") def get_parser(self, prog_name): parser = super(ActionShow, self).get_parser(prog_name) parser.add_argument( 'container', metavar='', help='ID or name of the container to show.') parser.add_argument( 'request_id', metavar='', help='request ID of action to describe.') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) container = parsed_args.container request_id = parsed_args.request_id action = client.actions.get(container, request_id) columns = _action_columns(action) return columns, utils.get_item_properties(action, columns) class AddFloatingIP(command.Command): """Add floating IP address to container""" log = logging.getLogger(__name__ + ".AddFloatingIP") def get_parser(self, prog_name): parser = super(AddFloatingIP, self).get_parser(prog_name) parser.add_argument( 'container', metavar='', help='ID or name of the container to receive the floating ' 'IP address.' ) parser.add_argument( "ip_address", metavar="", help="Floating IP address to assign to the first available " "container port (IP only)" ) parser.add_argument( "--fixed-ip-address", metavar="", help="Fixed IP address to associate with this floating IP " "address. The first container port containing the fixed " "IP address will be used" ) return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) opts = {} opts['id'] = parsed_args.container container = client.containers.get(**opts) network_client = self.app.client_manager.network attrs = {} obj = network_client.find_ip( parsed_args.ip_address, ignore_missing=False, ) ports = list(network_client.ports(device_id=container.uuid)) # If the fixed IP address was specified, we need to find the # corresponding port. if parsed_args.fixed_ip_address: fip_address = parsed_args.fixed_ip_address attrs['fixed_ip_address'] = fip_address for port in ports: for ip in port.fixed_ips: if ip['ip_address'] == fip_address: attrs['port_id'] = port.id break else: continue break if 'port_id' not in attrs: print(_('No port found for fixed IP address %s.') % fip_address) raise SystemExit network_client.update_ip(obj, **attrs) else: # It's possible that one or more ports are not connected to a # router and thus could fail association with a floating IP. # Try each port until one succeeds. If none succeed, re-raise the # last exception. error = None for port in ports: attrs['port_id'] = port.id try: network_client.update_ip(obj, **attrs) except Exception as e: error = e continue else: error = None break if error: raise error class RemoveFloatingIP(command.Command): """Remove floating IP address from container""" log = logging.getLogger(__name__ + ".RemoveFloatingIP") def get_parser(self, prog_name): parser = super(RemoveFloatingIP, self).get_parser(prog_name) parser.add_argument( "ip_address", metavar="", help=_("Floating IP address to remove from container (IP only)"), ) return parser def take_action(self, parsed_args): network_client = self.app.client_manager.network attrs = {} obj = network_client.find_ip( parsed_args.ip_address, ignore_missing=False, ) attrs['port_id'] = None network_client.update_ip(obj, **attrs) python-zunclient-4.0.0/zunclient/osc/v1/registries.py0000664000175000017500000002020413643163457022760 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. from osc_lib.command import command from osc_lib import utils from oslo_log import log as logging import six from zunclient.common import utils as zun_utils from zunclient import exceptions as exc from zunclient.i18n import _ def _get_client(obj, parsed_args): obj.log.debug("take_action(%s)" % parsed_args) return obj.app.client_manager.container class CreateRegistry(command.ShowOne): """Create a registry""" log = logging.getLogger(__name__ + ".CreateRegistry") def get_parser(self, prog_name): parser = super(CreateRegistry, self).get_parser(prog_name) parser.add_argument( '--name', metavar='', help='The name of the registry.') parser.add_argument( '--username', metavar='', help='The username to login to the registry.') parser.add_argument( '--password', metavar='', help='The password to login to the registry.') parser.add_argument( '--domain', metavar='', required=True, help='The domain of the registry.') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) opts = {} opts['name'] = parsed_args.name opts['domain'] = parsed_args.domain opts['username'] = parsed_args.username opts['password'] = parsed_args.password opts = zun_utils.remove_null_parms(**opts) registry = client.registries.create(**opts) return zip(*sorted(six.iteritems(registry._info['registry']))) class ShowRegistry(command.ShowOne): """Show a registry""" log = logging.getLogger(__name__ + ".ShowRegistry") def get_parser(self, prog_name): parser = super(ShowRegistry, self).get_parser(prog_name) parser.add_argument( 'registry', metavar='', help='ID or name of the registry to show.') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) opts = {} opts['id'] = parsed_args.registry opts = zun_utils.remove_null_parms(**opts) registry = client.registries.get(**opts) return zip(*sorted(six.iteritems(registry._info['registry']))) class ListRegistry(command.Lister): """List available registries""" log = logging.getLogger(__name__ + ".ListRegistrys") def get_parser(self, prog_name): parser = super(ListRegistry, self).get_parser(prog_name) parser.add_argument( '--all-projects', action="store_true", default=False, help='List registries in all projects') parser.add_argument( '--marker', metavar='', help='The last registry UUID of the previous page; ' 'displays list of registries after "marker".') parser.add_argument( '--limit', metavar='', type=int, help='Maximum number of registries to return') parser.add_argument( '--sort-key', metavar='', help='Column to sort results by') parser.add_argument( '--sort-dir', metavar='', choices=['desc', 'asc'], help='Direction to sort. "asc" or "desc".') parser.add_argument( '--name', metavar='', help='List registries according to their name.') parser.add_argument( '--domain', metavar='', help='List registries according to their domain.') parser.add_argument( '--project-id', metavar='', help='List registries according to their project_id') parser.add_argument( '--user-id', metavar='', help='List registries according to their user_id') parser.add_argument( '--username', metavar='', help='List registries according to their username') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) opts = {} opts['all_projects'] = parsed_args.all_projects opts['marker'] = parsed_args.marker opts['limit'] = parsed_args.limit opts['sort_key'] = parsed_args.sort_key opts['sort_dir'] = parsed_args.sort_dir opts['domain'] = parsed_args.domain opts['name'] = parsed_args.name opts['project_id'] = parsed_args.project_id opts['user_id'] = parsed_args.user_id opts['username'] = parsed_args.username opts = zun_utils.remove_null_parms(**opts) registries = client.registries.list(**opts) columns = ('uuid', 'name', 'domain', 'username', 'password') return (columns, (utils.get_item_properties(registry, columns) for registry in registries)) class DeleteRegistry(command.Command): """Delete specified registry(s)""" log = logging.getLogger(__name__ + ".Deleteregistry") def get_parser(self, prog_name): parser = super(DeleteRegistry, self).get_parser(prog_name) parser.add_argument( 'registry', metavar='', nargs='+', help='ID or name of the registry(s) to delete.') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) registries = parsed_args.registry for registry in registries: opts = {} opts['id'] = registry opts = zun_utils.remove_null_parms(**opts) try: client.registries.delete(**opts) print(_('Request to delete registry %s has been accepted.') % registry) except Exception as e: print("Delete for registry %(registry)s failed: %(e)s" % {'registry': registry, 'e': e}) class UpdateRegistry(command.ShowOne): """Update one or more attributes of the registry""" log = logging.getLogger(__name__ + ".UpdateRegistry") def get_parser(self, prog_name): parser = super(UpdateRegistry, self).get_parser(prog_name) parser.add_argument( 'registry', metavar='', help="ID or name of the registry to update.") parser.add_argument( '--username', metavar='', help='The new username of registry to update.') parser.add_argument( '--password', metavar='', help='The new password of registry to update.') parser.add_argument( '--name', metavar='', help='The new name of registry to update.') parser.add_argument( '--domain', metavar='', help='The new domain of registry to update.') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) registry = parsed_args.registry opts = {} opts['username'] = parsed_args.username opts['password'] = parsed_args.password opts['domain'] = parsed_args.domain opts['name'] = parsed_args.name opts = zun_utils.remove_null_parms(**opts) if not opts: raise exc.CommandError("You must update at least one property") registry = client.registries.update(registry, **opts) return zip(*sorted(six.iteritems(registry._info['registry']))) python-zunclient-4.0.0/zunclient/osc/v1/services.py0000664000175000017500000001273013643163457022430 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. from oslo_log import log as logging from osc_lib.command import command from osc_lib import utils def _get_client(obj, parsed_args): obj.log.debug("take_action(%s)" % parsed_args) return obj.app.client_manager.container class ListService(command.Lister): """Print a list of zun services.""" log = logging.getLogger(__name__ + ".ListService") def get_parser(self, prog_name): parser = super(ListService, self).get_parser(prog_name) return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) services = client.services.list() columns = ('Id', 'Host', 'Binary', 'State', 'Disabled', 'Disabled Reason', 'Updated At', 'Availability Zone') return (columns, (utils.get_item_properties(service, columns) for service in services)) class DeleteService(command.Command): """Delete the Zun binaries/services.""" log = logging.getLogger(__name__ + ".DeleteService") def get_parser(self, prog_name): parser = super(DeleteService, self).get_parser(prog_name) parser.add_argument( 'host', metavar='', help='Name of host') parser.add_argument( 'binary', metavar='', help='Name of the binary to delete') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) host = parsed_args.host binary = parsed_args.binary try: client.services.delete(host, binary) print("Request to delete binary %s on host %s has been accepted." % (binary, host)) except Exception as e: print("Delete for binary %s on host %s failed: %s" % (binary, host, e)) class EnableService(command.ShowOne): """Enable the Zun service.""" log = logging.getLogger(__name__ + ".EnableService") def get_parser(self, prog_name): parser = super(EnableService, self).get_parser(prog_name) parser.add_argument( 'host', metavar='', help='Name of host') parser.add_argument( 'binary', metavar='', help='Name of the binary to enable') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) host = parsed_args.host binary = parsed_args.binary res = client.services.enable(host, binary) columns = ('Host', 'Binary', 'Disabled', 'Disabled Reason') return columns, (utils.get_dict_properties(res[1]['service'], columns)) class DisableService(command.ShowOne): """Disable the Zun service.""" log = logging.getLogger(__name__ + ".DisableService") def get_parser(self, prog_name): parser = super(DisableService, self).get_parser(prog_name) parser.add_argument( 'host', metavar='', help='Name of host') parser.add_argument( 'binary', metavar='', help='Name of the binary to disable') parser.add_argument( '--reason', metavar='', help='Reason for disabling service') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) host = parsed_args.host binary = parsed_args.binary reason = parsed_args.reason res = client.services.disable(host, binary, reason) columns = ('Host', 'Binary', 'Disabled', 'Disabled Reason') return columns, (utils.get_dict_properties(res[1]['service'], columns)) class ForceDownService(command.ShowOne): """Force the Zun service to down or up.""" log = logging.getLogger(__name__ + ".ForceDownService") def get_parser(self, prog_name): parser = super(ForceDownService, self).get_parser(prog_name) parser.add_argument( 'host', metavar='', help='Name of host') parser.add_argument( 'binary', metavar='', help='Name of the binary to disable') parser.add_argument( '--unset', dest='force_down', help='Unset the force state down of service', action='store_false', default=True) return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) host = parsed_args.host binary = parsed_args.binary force_down = parsed_args.force_down res = client.services.force_down(host, binary, force_down) columns = ('Host', 'Binary', 'Forced_down') return columns, (utils.get_dict_properties(res[1]['service'], columns)) python-zunclient-4.0.0/zunclient/osc/v1/hosts.py0000664000175000017500000000571113643163457021746 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. from oslo_log import log as logging from osc_lib.command import command from osc_lib import utils from zunclient.common import utils as zun_utils def _host_columns(host): return host._info.keys() def _get_client(obj, parsed_args): obj.log.debug("take_action(%s)" % parsed_args) return obj.app.client_manager.container class ListHost(command.Lister): """List available hosts""" log = logging.getLogger(__name__ + ".ListHost") def get_parser(self, prog_name): parser = super(ListHost, self).get_parser(prog_name) parser.add_argument( '--marker', metavar='', default=None, help='The last host UUID of the previous page; ' 'displays list of hosts after "marker".') parser.add_argument( '--limit', metavar='', type=int, help='Maximum number of hosts to return') parser.add_argument( '--sort-key', metavar='', help='Column to sort results by') parser.add_argument( '--sort-dir', metavar='', choices=['desc', 'asc'], help='Direction to sort. "asc" or "desc".') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) opts = {} opts['marker'] = parsed_args.marker opts['limit'] = parsed_args.limit opts['sort_key'] = parsed_args.sort_key opts['sort_dir'] = parsed_args.sort_dir opts = zun_utils.remove_null_parms(**opts) hosts = client.hosts.list(**opts) columns = ('uuid', 'hostname', 'mem_total', 'cpus', 'disk_total') return (columns, (utils.get_item_properties(host, columns) for host in hosts)) class ShowHost(command.ShowOne): """Show a host""" log = logging.getLogger(__name__ + ".ShowHost") def get_parser(self, prog_name): parser = super(ShowHost, self).get_parser(prog_name) parser.add_argument( 'host', metavar='', help='ID or name of the host to show.') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) host = parsed_args.host host = client.hosts.get(host) columns = _host_columns(host) return columns, utils.get_item_properties(host, columns) python-zunclient-4.0.0/zunclient/osc/v1/quotas.py0000664000175000017500000001121513643163457022116 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. from osc_lib.command import command from osc_lib import utils from oslo_log import log as logging from zunclient.i18n import _ def _quota_columns(quota): return quota._info.keys() def _get_client(obj, parsed_args): obj.log.debug("take_action(%s)" % parsed_args) return obj.app.client_manager.container class UpdateQuota(command.ShowOne): """Update the quotas of the project""" log = logging.getLogger(__name__ + ".UpdateQuota") def get_parser(self, prog_name): parser = super(UpdateQuota, self).get_parser(prog_name) parser.add_argument( '--containers', metavar='', help='The number of containers allowed per project') parser.add_argument( '--memory', metavar='', help='The number of megabytes of container RAM ' 'allowed per project') parser.add_argument( '--cpu', metavar='', help='The number of container cores or vCPUs ' 'allowed per project') parser.add_argument( '--disk', metavar='', help='The number of gigabytes of container Disk ' 'allowed per project') parser.add_argument( 'project_id', metavar='', help='The UUID of project in a multi-project cloud') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) opts = {} opts['containers'] = parsed_args.containers opts['memory'] = parsed_args.memory opts['cpu'] = parsed_args.cpu opts['disk'] = parsed_args.disk quota = client.quotas.update(parsed_args.project_id, **opts) columns = _quota_columns(quota) return columns, utils.get_item_properties(quota, columns) class GetQuota(command.ShowOne): """Get quota of the project""" log = logging.getLogger(__name__ + '.GetQuota') def get_parser(self, prog_name): parser = super(GetQuota, self).get_parser(prog_name) parser.add_argument( '--usages', action='store_true', help='Whether show quota usage statistic or not') parser.add_argument( 'project_id', metavar='', help='The UUID of project in a multi-project cloud') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) quota = client.quotas.get( parsed_args.project_id, usages=parsed_args.usages) columns = _quota_columns(quota) return columns, utils.get_item_properties(quota, columns) class GetDefaultQuota(command.ShowOne): """Get default quota of the project""" log = logging.getLogger(__name__ + '.GetDefaultQuota') def get_parser(self, prog_name): parser = super(GetDefaultQuota, self).get_parser(prog_name) parser.add_argument( 'project_id', metavar='', help='The UUID of project in a multi-project cloud') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) default_quota = client.quotas.defaults(parsed_args.project_id) columns = _quota_columns(default_quota) return columns, utils.get_item_properties( default_quota, columns) class DeleteQuota(command.Command): """Delete quota of the project""" log = logging.getLogger(__name__ + '.DeleteQuota') def get_parser(self, prog_name): parser = super(DeleteQuota, self).get_parser(prog_name) parser.add_argument( 'project_id', metavar='', help='The UUID of project in a multi-project cloud') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) try: client.quotas.delete(parsed_args.project_id) print(_('Request to delete quotas has been accepted.')) except Exception as e: print("Delete for quotas failed: %(e)s" % {'e': e}) python-zunclient-4.0.0/zunclient/osc/v1/__init__.py0000664000175000017500000000000013643163457022327 0ustar zuulzuul00000000000000python-zunclient-4.0.0/zunclient/osc/v1/quota_classes.py0000664000175000017500000000631613643163457023456 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. from osc_lib.command import command from osc_lib import utils from oslo_log import log as logging def _quota_class_columns(quota_class): return quota_class._info.keys() def _get_client(obj, parsed_args): obj.log.debug("take_action(%s)" % parsed_args) return obj.app.client_manager.container class UpdateQuotaClass(command.ShowOne): """Update the quotas for a quota class""" log = logging.getLogger(__name__ + ".UpdateQuotaClass") def get_parser(self, prog_name): parser = super(UpdateQuotaClass, self).get_parser(prog_name) parser.add_argument( '--containers', metavar='', help='The number of containers allowed per project') parser.add_argument( '--memory', metavar='', help='The number of megabytes of container RAM ' 'allowed per project') parser.add_argument( '--cpu', metavar='', help='The number of container cores or vCPUs ' 'allowed per project') parser.add_argument( '--disk', metavar='', help='The number of gigabytes of container Disk ' 'allowed per project') parser.add_argument( 'quota_class_name', metavar='', help='The name of quota class') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) opts = {} opts['containers'] = parsed_args.containers opts['memory'] = parsed_args.memory opts['cpu'] = parsed_args.cpu opts['disk'] = parsed_args.disk quota_class_name = parsed_args.quota_class_name quota_class = client.quota_classes.update( quota_class_name, **opts) columns = _quota_class_columns(quota_class) return columns, utils.get_item_properties(quota_class, columns) class GetQuotaClass(command.ShowOne): """List the quotas for a quota class""" log = logging.getLogger(__name__ + '.GetQuotaClass') def get_parser(self, prog_name): parser = super(GetQuotaClass, self).get_parser(prog_name) parser.add_argument( 'quota_class_name', metavar='', help='The name of quota class') return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) quota_class_name = parsed_args.quota_class_name quota_class = client.quota_classes.get(quota_class_name) columns = _quota_class_columns(quota_class) return columns, utils.get_item_properties(quota_class, columns) python-zunclient-4.0.0/zunclient/osc/plugin.py0000664000175000017500000000706413643163457021561 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. import argparse from oslo_log import log as logging from osc_lib import utils from zunclient import api_versions LOG = logging.getLogger(__name__) DEFAULT_CONTAINER_API_VERSION = api_versions.DEFAULT_API_VERSION API_VERSION_OPTION = "os_container_api_version" API_NAME = "container" CLIENT_CLASS = 'zunclient.v1.client.Client' LAST_KNOWN_API_VERSION = int(api_versions.MAX_API_VERSION.split('.')[1]) API_VERSIONS = { '1.%d' % i: CLIENT_CLASS for i in range(1, LAST_KNOWN_API_VERSION + 1) } API_VERSIONS['1'] = CLIENT_CLASS def make_client(instance): """Returns a zun service client""" requested_api_version = instance._api_version[API_NAME] zun_client = utils.get_client_class( API_NAME, requested_api_version, API_VERSIONS) LOG.debug("Instantiating zun client: {0}".format( zun_client)) api_version = api_versions.get_api_version(requested_api_version) if api_version.is_latest(): client = zun_client( region_name=instance._region_name, session=instance.session, service_type='container', api_version=api_versions.APIVersion("1.1"), ) api_version = api_versions.discover_version(client, api_version) client = zun_client( region_name=instance._region_name, session=instance.session, service_type='container', api_version=api_version, ) return client def build_option_parser(parser): """Hook to add global options""" parser.add_argument( '--os-container-api-version', metavar='', default=_get_environment_version(DEFAULT_CONTAINER_API_VERSION), action=ReplaceLatestVersion, choices=sorted( API_VERSIONS, key=lambda k: [int(x) for x in k.split('.')]) + ['1.latest'], help=("Container API version, default={0}" "(Env:OS_CONTAINER_API_VERSION)").format( DEFAULT_CONTAINER_API_VERSION)) return parser def _get_environment_version(default): env_value = utils.env('OS_CONTAINER_API_VERSION') or default latest = env_value == '1.latest' if latest: # NOTE(hongbin): '1.latest' means enabling negotiation of the # latest version between server and client but due to how OSC works # we cannot just add "1.latest" to the list of supported versions. # Use '1' in this case. env_value = '1' return env_value class ReplaceLatestVersion(argparse.Action): """Replaces `latest` keyword by last known version.""" def __call__(self, parser, namespace, values, option_string=None): latest = values == '1.latest' if latest: # NOTE(hongbin): '1.latest' means enabling negotiation of the # latest version between server and client but due to how OSC works # we cannot just add "1.latest" to the list of supported versions. # Use '1' in this case. values = '1' setattr(namespace, self.dest, values) python-zunclient-4.0.0/zunclient/osc/__init__.py0000664000175000017500000000000013643163457022001 0ustar zuulzuul00000000000000python-zunclient-4.0.0/zunclient/__init__.py0000664000175000017500000000121013643163457021221 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. import pbr.version __version__ = pbr.version.VersionInfo( 'python-zunclient').version_string() python-zunclient-4.0.0/zunclient/api_versions.py0000664000175000017500000003362413643163457022201 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. import functools import logging import os import pkgutil import re import traceback from oslo_utils import strutils from zunclient import exceptions from zunclient.i18n import _ LOG = logging.getLogger(__name__) if not LOG.handlers: LOG.addHandler(logging.StreamHandler()) HEADER_NAME = "OpenStack-API-Version" SERVICE_TYPE = "container" MIN_API_VERSION = '1.1' MAX_API_VERSION = '1.40' DEFAULT_API_VERSION = '1.latest' _SUBSTITUTIONS = {} _type_error_msg = _("'%(other)s' should be an instance of '%(cls)s'") class APIVersion(object): """This class represents an API Version Request. This class provides convenience methods for manipulation and comparison of version numbers that we need to do to implement microversions. """ def __init__(self, version_str=None): """Create an API version object. :param version_str: String representation of APIVersionRequest. Correct format is 'X.Y', where 'X' and 'Y' are int values. None value should be used to create Null APIVersionRequest, which is equal to 0.0 """ self.ver_major = 0 self.ver_minor = 0 if version_str is not None: match = re.match(r"^([1-9]\d*)\.([1-9]\d*|0|latest)$", version_str) if match: self.ver_major = int(match.group(1)) if match.group(2) == "latest": # NOTE(andreykurilin): Infinity allows to easily determine # latest version and doesn't require any additional checks # in comparison methods. self.ver_minor = float("inf") else: self.ver_minor = int(match.group(2)) else: msg = _("Invalid format of client version '%s'. " "Expected format 'X.Y', where X is a major part and Y " "is a minor part of version.") % version_str raise exceptions.UnsupportedVersion(msg) def __str__(self): """Debug/Logging representation of object.""" if self.is_latest(): return "Latest API Version Major: %s" % self.ver_major return ("API Version Major: %s, Minor: %s" % (self.ver_major, self.ver_minor)) def __repr__(self): if self.is_null(): return "" else: return "" % self.get_string() def is_null(self): return self.ver_major == 0 and self.ver_minor == 0 def is_latest(self): return self.ver_minor == float("inf") def __lt__(self, other): if not isinstance(other, APIVersion): raise TypeError(_type_error_msg % {"other": other, "cls": self.__class__}) return ((self.ver_major, self.ver_minor) < (other.ver_major, other.ver_minor)) def __eq__(self, other): if not isinstance(other, APIVersion): raise TypeError(_type_error_msg % {"other": other, "cls": self.__class__}) return ((self.ver_major, self.ver_minor) == (other.ver_major, other.ver_minor)) def __gt__(self, other): if not isinstance(other, APIVersion): raise TypeError(_type_error_msg % {"other": other, "cls": self.__class__}) return ((self.ver_major, self.ver_minor) > (other.ver_major, other.ver_minor)) def __le__(self, other): return self < other or self == other def __ne__(self, other): return not self.__eq__(other) def __ge__(self, other): return self > other or self == other def matches(self, min_version, max_version): """Matches the version object. Returns whether the version object represents a version greater than or equal to the minimum version and less than or equal to the maximum version. :param min_version: Minimum acceptable version. :param max_version: Maximum acceptable version. :returns: boolean If min_version is null then there is no minimum limit. If max_version is null then there is no maximum limit. If self is null then raise ValueError """ if self.is_null(): raise ValueError(_("Null APIVersion doesn't support 'matches'.")) if max_version.is_null() and min_version.is_null(): return True elif max_version.is_null(): return min_version <= self elif min_version.is_null(): return self <= max_version else: return min_version <= self <= max_version def get_string(self): """Version string representation. Converts object to string representation which if used to create an APIVersion object results in the same version. """ if self.is_null(): raise ValueError( _("Null APIVersion cannot be converted to string.")) elif self.is_latest(): return "%s.%s" % (self.ver_major, "latest") return "%s.%s" % (self.ver_major, self.ver_minor) class VersionedMethod(object): def __init__(self, name, start_version, end_version, func): """Versioning information for a single method :param name: Name of the method :param start_version: Minimum acceptable version :param end_version: Maximum acceptable_version :param func: Method to call Minimum and maximums are inclusive """ self.name = name self.start_version = start_version self.end_version = end_version self.func = func def __str__(self): return ("Version Method %s: min: %s, max: %s" % (self.name, self.start_version, self.end_version)) def __repr__(self): return "" % self.name def get_available_major_versions(): # NOTE(andreykurilin): available clients version should not be # hardcoded, so let's discover them. matcher = re.compile(r"v[0-9]*$") submodules = pkgutil.iter_modules([os.path.dirname(__file__)]) available_versions = [name[1:] for loader, name, ispkg in submodules if matcher.search(name)] return available_versions def check_major_version(api_version): """Checks major part of ``APIVersion`` obj is supported. :raises exceptions.UnsupportedVersion: if major part is not supported """ available_versions = get_available_major_versions() if (not api_version.is_null() and str(api_version.ver_major) not in available_versions): if len(available_versions) == 1: msg = _("Invalid client version '%(version)s'. " "Major part should be '%(major)s'") % { "version": api_version.get_string(), "major": available_versions[0]} else: msg = _("Invalid client version '%(version)s'. " "Major part must be one of: '%(major)s'") % { "version": api_version.get_string(), "major": ", ".join(available_versions)} raise exceptions.UnsupportedVersion(msg) def get_api_version(version_string): """Returns checked APIVersion object""" version_string = str(version_string) if strutils.is_int_like(version_string): version_string = "%s.latest" % version_string api_version = APIVersion(version_string) check_major_version(api_version) return api_version def _get_server_version_range(client): version = client.versions.get_current() if not hasattr(version, 'max_version') or not version.max_version: return APIVersion(), APIVersion() return APIVersion(version.min_version), APIVersion(version.max_version) def discover_version(client, requested_version): server_start_version, server_end_version = _get_server_version_range( client) if (not requested_version.is_latest() and requested_version != APIVersion('1.1')): if server_start_version.is_null() and server_end_version.is_null(): raise exceptions.UnsupportedVersion( _("Server doesn't support microversions")) if not requested_version.matches(server_start_version, server_end_version): raise exceptions.UnsupportedVersion( _("The specified version isn't supported by server. The valid " "version range is '%(min)s' to '%(max)s'") % { "min": server_start_version.get_string(), "max": server_end_version.get_string()}) return requested_version min_version = APIVersion(MIN_API_VERSION) max_version = APIVersion(MAX_API_VERSION) if server_start_version.is_null() and server_end_version.is_null(): return APIVersion("1.1") elif min_version > server_end_version: raise exceptions.UnsupportedVersion( _("Server version is too old. The client valid version range is " "'%(client_min)s' to '%(client_max)s'. The server valid version " "range is '%(server_min)s' to '%(server_max)s'.") % { 'client_min': min_version.get_string(), 'client_max': max_version.get_string(), 'server_min': server_start_version.get_string(), 'server_max': server_end_version.get_string()}) elif max_version < server_start_version: raise exceptions.UnsupportedVersion( _("Server version is too new. The client valid version range is " "'%(client_min)s' to '%(client_max)s'. The server valid version " "range is '%(server_min)s' to '%(server_max)s'.") % { 'client_min': min_version.get_string(), 'client_max': max_version.get_string(), 'server_min': server_start_version.get_string(), 'server_max': server_end_version.get_string()}) elif max_version <= server_end_version: return max_version elif server_end_version < max_version: return server_end_version def update_headers(headers, api_version): """Set microversion headers if api_version is not null""" if not api_version.is_null() and api_version.ver_minor != 0: version_string = api_version.get_string() headers[HEADER_NAME] = '%s %s' % (SERVICE_TYPE, version_string) def _add_substitution(versioned_method): _SUBSTITUTIONS.setdefault(versioned_method.name, []) _SUBSTITUTIONS[versioned_method.name].append(versioned_method) def _get_function_name(func): # NOTE(andreykurilin): Based on the facts: # - Python 2 does not have __qualname__ property as Python 3 has; # - we cannot use im_class here, since we need to obtain name of # function in `wraps` decorator during class initialization # ("im_class" property does not exist at that moment) # we need to write own logic to obtain the full function name which # include module name, owner name(optional) and just function name. filename, _lineno, _name, line = traceback.extract_stack()[-4] module, _file_extension = os.path.splitext(filename) module = module.replace("/", ".") if module.endswith(func.__module__): return "%s.[%s].%s" % (func.__module__, line, func.__name__) else: return "%s.%s" % (func.__module__, func.__name__) def get_substitutions(func_name, api_version=None): if hasattr(func_name, "__id__"): func_name = func_name.__id__ substitutions = _SUBSTITUTIONS.get(func_name, []) if api_version and not api_version.is_null(): return [m for m in substitutions if api_version.matches(m.start_version, m.end_version)] return sorted(substitutions, key=lambda m: m.start_version) def wraps(start_version, end_version=None): start_version = APIVersion(start_version) if end_version: end_version = APIVersion(end_version) else: end_version = APIVersion("%s.latest" % start_version.ver_major) def decor(func): func.versioned = True name = _get_function_name(func) versioned_method = VersionedMethod(name, start_version, end_version, func) _add_substitution(versioned_method) @functools.wraps(func) def substitution(obj, *args, **kwargs): methods = get_substitutions(name, obj.api_version) if not methods: raise exceptions.VersionNotFoundForAPIMethod( obj.api_version.get_string(), name) return methods[-1].func(obj, *args, **kwargs) # Let's share "arguments" with original method and substitution to # allow put cliutils.arg and wraps decorators in any order if not hasattr(func, 'arguments'): func.arguments = [] substitution.arguments = func.arguments # NOTE(andreykurilin): The way to obtain function's name in Python 2 # bases on traceback(see _get_function_name for details). Since the # right versioned method method is used in several places, one object # can have different names. Let's generate name of function one time # and use __id__ property in all other places. substitution.__id__ = name return substitution return decor python-zunclient-4.0.0/CONTRIBUTING.rst0000664000175000017500000000122613643163457017545 0ustar zuulzuul00000000000000If you would like to contribute to the development of OpenStack, you must follow the steps in this page: https://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: https://docs.openstack.org/infra/manual/developers.html#development-workflow Pull requests submitted through GitHub will be ignored. Bugs should be filed on Launchpad, not GitHub: https://bugs.launchpad.net/python-zunclient python-zunclient-4.0.0/doc/0000775000175000017500000000000013643163533015643 5ustar zuulzuul00000000000000python-zunclient-4.0.0/doc/requirements.txt0000664000175000017500000000050013643163457021127 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. openstackdocstheme>=1.20.0 # Apache-2.0 sphinx!=1.6.6,!=1.6.7,!=2.1.0,>=1.6.2 # BSD reno>=2.5.0 # Apache-2.0 python-zunclient-4.0.0/doc/source/0000775000175000017500000000000013643163533017143 5ustar zuulzuul00000000000000python-zunclient-4.0.0/doc/source/conf.py0000775000175000017500000000512413643163457020454 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. 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', #'sphinx.ext.intersphinx', 'openstackdocstheme', ] # openstackdocstheme options repository_name = 'openstack/python-zunclient' bug_project = 'python-zunclient' bug_tag = 'doc' # 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. copyright = u'2013, 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 = 'openstackdocs' # Output file base name for HTML help builder. htmlhelp_basename = 'zunclientdoc' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass # [howto/manual]). latex_documents = [ ('index', 'doc-python-zunclient.tex', u'Python Zun Client Documentation', u'Zun development team', 'manual'), ] # Disable usage of xindy https://bugzilla.redhat.com/show_bug.cgi?id=1643664 latex_use_xindy = False latex_domain_indices = False latex_elements = { 'makeindex': '', 'printindex': '', 'preamble': r'\setcounter{tocdepth}{3}', 'extraclassoptions': 'openany', } python-zunclient-4.0.0/doc/source/index.rst0000664000175000017500000000051013643163457021005 0ustar zuulzuul00000000000000Welcome to python-zunclient's documentation! ============================================ Contents -------- .. toctree:: :maxdepth: 2 install/index contributor/index cli/index user/index .. only:: html Indices and tables ------------------ * :ref:`genindex` * :ref:`modindex` * :ref:`search` python-zunclient-4.0.0/doc/source/install/0000775000175000017500000000000013643163533020611 5ustar zuulzuul00000000000000python-zunclient-4.0.0/doc/source/install/index.rst0000664000175000017500000000035313643163457022460 0ustar zuulzuul00000000000000================== Installation Guide ================== At the command line:: $ pip install python-zunclient Or, if you have virtualenvwrapper installed:: $ mkvirtualenv python-zunclient $ pip install python-zunclient python-zunclient-4.0.0/doc/source/contributor/0000775000175000017500000000000013643163533021515 5ustar zuulzuul00000000000000python-zunclient-4.0.0/doc/source/contributor/index.rst0000664000175000017500000000014413643163457023362 0ustar zuulzuul00000000000000=================== Contributor's Guide =================== .. include:: ../../../CONTRIBUTING.rst python-zunclient-4.0.0/doc/source/user/0000775000175000017500000000000013643163533020121 5ustar zuulzuul00000000000000python-zunclient-4.0.0/doc/source/user/index.rst0000664000175000017500000000010113643163457021757 0ustar zuulzuul00000000000000Reference ========= .. toctree:: :maxdepth: 1 python-api python-zunclient-4.0.0/doc/source/user/python-api.rst0000664000175000017500000000365313643163457022757 0ustar zuulzuul00000000000000================================ The :mod:`zunclient` Python API ================================ .. module:: zunclient :synopsis: A client for the OpenStack Zun API. .. currentmodule:: zunclient Usage ----- First create a client instance with your credentials:: >>> from zunclient import client >>> zun = client.Client(VERSION, auth_url=AUTH_URL, username=USERNAME, ... password=PASSWORD, project_name=PROJECT_NAME, ... user_domain_name='default', ... project_domain_name='default') Here ``VERSION`` can be a string or ``zunclient.api_versions.APIVersion`` obj. If you prefer string value, you can use ``1`` or ``1.X`` (where X is a microversion). Alternatively, you can create a client instance using the keystoneauth session API:: >>> from keystoneauth1 import loading >>> from keystoneauth1 import session >>> from zunclient import client >>> loader = loading.get_plugin_loader('password') >>> auth = loader.load_from_options(auth_url=AUTH_URL, ... username=USERNAME, ... password=PASSWORD, ... project_name=PROJECT_NAME, ... user_domain_name='default', ... project_domain_name='default') >>> sess = session.Session(auth=auth) >>> zun = client.Client(VERSION, session=sess) If you have PROJECT_NAME instead of a PROJECT_ID, use the project_name parameter. Similarly, if your cloud uses keystone v3 and you have a DOMAIN_NAME or DOMAIN_ID, provide it as `user_domain_(name|id)` and if you are using a PROJECT_NAME also provide the domain information as `project_domain_(name|id)`. Then call methods on its managers:: >>> zun.containers.list() [] >>> zun.containers.run(name="my-container", image='nginx') python-zunclient-4.0.0/doc/source/cli/0000775000175000017500000000000013643163533017712 5ustar zuulzuul00000000000000python-zunclient-4.0.0/doc/source/cli/index.rst0000664000175000017500000000020613643163457021556 0ustar zuulzuul00000000000000==================== User Documentation ==================== .. toctree:: :maxdepth: 2 Manual Page command-list python-zunclient-4.0.0/doc/source/cli/command-list.rst0000664000175000017500000000017013643163457023036 0ustar zuulzuul00000000000000.. _command-list: ============ Command List ============ .. toctree:: :glob: :maxdepth: 2 command-objects/* python-zunclient-4.0.0/doc/source/cli/command-objects/0000775000175000017500000000000013643163533022757 5ustar zuulzuul00000000000000python-zunclient-4.0.0/doc/source/cli/command-objects/action.rst0000664000175000017500000000652013643163457024776 0ustar zuulzuul00000000000000=================== appcontainer action =================== An **appcontainer action** specifies the action details for a container. appcontainer action list ------------------------ List actions on a container .. program:: appcontainer action list .. code:: bash openstack appcontainer action list [-h] [-f {csv,json,table,value,yaml}] [-c COLUMN] [--max-width ] [--fit-width] [--print-empty] [--noindent] [--quote {all,minimal,none,nonnumeric}] [--sort-column SORT_COLUMN] .. describe:: ID or name of the container to list actions. .. option:: -h, --help show this help message and exit .. option:: -f {csv,json,table,value,yaml}, --format {csv,json,table,value,yaml} the output format, defaults to table .. option:: -c COLUMN, --column COLUMN specify the column(s) to include, can be repeated .. option:: --sort-column SORT_COLUMN specify the column(s) to sort the data (columns specified first have a priority, non-existing columns are ignored), can be repeated .. option:: --max-width Maximum display width, <1 to disable. You can also use the CLIFF_MAX_TERM_WIDTH environment variable, but the parameter takes precedence. .. option:: --fit-width Fit the table to the display width. Implied if --max-width greater than 0. Set the environment variable CLIFF_FIT_WIDTH=1 to always enable .. option:: --print-empty Print empty table if there is no data to show. .. option:: --noindent whether to disable indenting the JSON .. option:: --quote {all,minimal,none,nonnumeric} when to include quotes, defaults to nonnumeric appcontainer action show ------------------------ Shows action .. program:: appcontainer action show .. code:: bash openstack appcontainer action show [-h] [-f {json,shell,table,value,yaml}] [-c COLUMN] [--max-width ] [--fit-width] [--print-empty] [--noindent] [--prefix PREFIX] .. describe:: ID or name of the container to show. .. describe:: request ID of action to describe. .. option:: -f {json,shell,table,value,yaml}, --format {json,shell,table,value,yaml} the output format, defaults to table .. option:: -c COLUMN, --column COLUMN specify the column(s) to include, can be repeated .. option:: --max-width Maximum display width, <1 to disable. You can also use the CLIFF_MAX_TERM_WIDTH environment variable, but the parameter takes precedence. .. option:: --fit-width Fit the table to the display width. Implied if --max-width greater than 0. Set the environment variable CLIFF_FIT_WIDTH =1 to always enable .. option:: --print-empty Print empty table if there is no data to show. .. option:: --noindent whether to disable indenting the JSON .. option:: --prefix PREFIX add a prefix to all variable names python-zunclient-4.0.0/doc/source/cli/command-objects/host.rst0000664000175000017500000000715513643163457024503 0ustar zuulzuul00000000000000================= appcontainer host ================= An **appcontainer host** specifies the compute host for running containers. appcontainer host list ---------------------- List available hosts .. program:: appcontainer host list .. code:: bash openstack appcontainer host list [-h] [-f {csv,json,table,value,yaml}] [-c COLUMN] [--max-width ] [--fit-width] [--print-empty] [--noindent] [--quote {all,minimal,none,nonnumeric}] [--sort-column SORT_COLUMN] [--marker ] [--limit ] [--sort-key ] [--sort-dir ] .. option:: -h, --help show this help message and exit .. option:: --marker The last host UUID of the previous page; displays list of hosts after "marker". .. option:: --limit Maximum number of hosts to return .. option:: --sort-key Column to sort results by .. option:: --sort-dir Direction to sort. "asc" or "desc". .. option:: -f {csv,json,table,value,yaml}, --format {csv,json,table,value,yaml} the output format, defaults to table .. option:: -c COLUMN, --column COLUMN specify the column(s) to include, can be repeated .. option:: --sort-column SORT_COLUMN specify the column(s) to sort the data (columns specified first have a priority, non-existing columns are ignored), can be repeated .. option:: --max-width Maximum display width, <1 to disable. You can also use the CLIFF_MAX_TERM_WIDTH environment variable, but the parameter takes precedence. .. option:: --fit-width Fit the table to the display width. Implied if --max- width greater than 0. Set the environment variable CLIFF_FIT_WIDTH=1 to always enable .. option:: --print-empty Print empty table if there is no data to show. .. option:: --noindent whether to disable indenting the JSON .. option:: --quote {all,minimal,none,nonnumeric} when to include quotes, defaults to nonnumeric appcontainer host show ---------------------- Show a host .. program:: appcontainer host show .. code:: bash openstack appcontainer host show [-h] [-f {json,shell,table,value,yaml}] [-c COLUMN] [--max-width ] [--fit-width] [--print-empty] [--noindent] [--prefix PREFIX] .. describe:: ID or name of the host to show. .. option:: -h, --help show this help message and exit .. option:: -f {json,shell,table,value,yaml}, --format {json,shell,table,value,yaml} the output format, defaults to table .. option:: -c COLUMN, --column COLUMN specify the column(s) to include, can be repeated .. option:: --max-width Maximum display width, <1 to disable. You can also use the CLIFF_MAX_TERM_WIDTH environment variable, but the parameter takes precedence. .. option:: --fit-width Fit the table to the display width. Implied if --max- width greater than 0. Set the environment variable CLIFF_FIT_WIDTH=1 to always enable .. option:: --print-empty Print empty table if there is no data to show. .. option:: --noindent whether to disable indenting the JSON .. option:: --prefix PREFIX add a prefix to all variable names python-zunclient-4.0.0/doc/source/cli/command-objects/image.rst0000664000175000017500000001673113643163457024610 0ustar zuulzuul00000000000000================== appcontainer image ================== An **appcontainer image** specifies the image for a host. appcontainer image list ----------------------- List available images .. program:: appcontainer image list .. code:: bash openstack appcontainer image list [-h] [-f {csv,json,table,value,yaml}] [-c COLUMN] [--max-width ] [--fit-width] [--print-empty] [--noindent] [--quote {all,minimal,none,nonnumeric}] [--sort-column SORT_COLUMN] [--marker ] [--limit ] [--sort-key ] [--sort-dir ] .. option:: -h, --help show this help message and exit .. option:: --marker The last host UUID of the previous page; displays list of hosts after "marker". .. option:: --limit Maximum number of hosts to return .. option:: --sort-key Column to sort results by .. option:: --sort-dir Direction to sort. "asc" or "desc". .. option:: -f {csv,json,table,value,yaml}, --format {csv,json,table,value,yaml} the output format, defaults to table .. option:: -c COLUMN, --column COLUMN specify the column(s) to include, can be repeated .. option:: --sort-column SORT_COLUMN specify the column(s) to sort the data (columns specified first have a priority, non-existing columns are ignored), can be repeated .. option:: --max-width Maximum display width, <1 to disable. You can also use the CLIFF_MAX_TERM_WIDTH environment variable, but the parameter takes precedence. .. option:: --fit-width Fit the table to the display width. Implied if --max- width greater than 0. Set the environment variable CLIFF_FIT_WIDTH=1 to always enable .. option:: --print-empty Print empty table if there is no data to show. .. option:: --noindent whether to disable indenting the JSON .. option:: --quote {all,minimal,none,nonnumeric} when to include quotes, defaults to nonnumeric appcontainer image show ----------------------- Describe a specific image .. program:: appcontainer image show .. code:: bash openstack appcontainer image show [-h] [-f {json,shell,table,value,yaml}] [-c COLUMN] [--max-width ] [--fit-width] [--print-empty] [--noindent] [--prefix PREFIX] .. describe:: UUID of image to describe .. option:: -h, --help show this help message and exit .. option:: -f {json,shell,table,value,yaml}, --format {json,shell,table,value,yaml} the output format, defaults to table .. option:: -c COLUMN, --column COLUMN specify the column(s) to include, can be repeated .. option:: --max-width Maximum display width, <1 to disable. You can also use the CLIFF_MAX_TERM_WIDTH environment variable, but the parameter takes precedence. .. option:: --fit-width Fit the table to the display width. Implied if --max- width greater than 0. Set the environment variable CLIFF_FIT_WIDTH=1 to always enable .. option:: --print-empty Print empty table if there is no data to show. .. option:: --noindent whether to disable indenting the JSON .. option:: --prefix PREFIX add a prefix to all variable names appcontainer image delete ------------------------- Delete specified image from a host .. program:: appcontainer image delete .. code:: bash openstack appcontainer image delete [-h] .. describe:: UUID of image to describe .. describe:: Name or UUID of the host .. option:: -h, --help show this help message and exit appcontainer image pull ----------------------- Pull specified image into a host .. program:: appcontainer image pull .. code:: bash openstack appcontainer image pull [-h] [-f {json,shell,table,value,yaml}] [-c COLUMN] [--max-width ] [--fit-width] [--print-empty] [--noindent] [--prefix PREFIX] .. describe:: Name of the image .. describe:: Name or UUID of the host .. option:: -h, --help show this help message and exit .. option:: -f {json,shell,table,value,yaml}, --format {json,shell,table,value,yaml} the output format, defaults to table .. option:: -c COLUMN, --column COLUMN specify the column(s) to include, can be repeated .. option:: --max-width Maximum display width, <1 to disable. You can also use the CLIFF_MAX_TERM_WIDTH environment variable, but the parameter takes precedence. .. option:: --fit-width Fit the table to the display width. Implied if --max- width greater than 0. Set the environment variable CLIFF_FIT_WIDTH=1 to always enable .. option:: --print-empty Print empty table if there is no data to show. .. option:: --noindent whether to disable indenting the JSON .. option:: --prefix PREFIX add a prefix to all variable names appcontainer image search ------------------------- Search the image repository for images .. program:: appcontainer image search .. code:: bash openstack appcontainer image search [-h] [-f {csv,json,table,value,yaml}] [-c COLUMN] [--max-width ] [--fit-width] [--print-empty] [--noindent] [--quote {all,minimal,none,nonnumeric}] [--sort-column SORT_COLUMN] [--image-driver ] [--exact-match] .. describe:: Name of the image .. option:: -h, --help show this help message and exit .. option:: --image-driver Name of the image driver .. option:: --exact-match exact match image name .. option:: -f {csv,json,table,value,yaml}, --format {csv,json,table,value,yaml} the output format, defaults to table .. option:: -c COLUMN, --column COLUMN specify the column(s) to include, can be repeated .. option:: --sort-column SORT_COLUMN specify the column(s) to sort the data (columns specified first have a priority, non-existing columns are ignored), can be repeated .. option:: --max-width Maximum display width, <1 to disable. You can also use the CLIFF_MAX_TERM_WIDTH environment variable, but the parameter takes precedence. .. option:: --fit-width Fit the table to the display width. Implied if --max- width greater than 0. Set the environment variable CLIFF_FIT_WIDTH=1 to always enable .. option:: --print-empty Print empty table if there is no data to show. .. option:: --noindent whether to disable indenting the JSON .. option:: --quote {all,minimal,none,nonnumeric} when to include quotes, defaults to nonnumeric python-zunclient-4.0.0/doc/source/cli/command-objects/service.rst0000664000175000017500000001567113643163457025170 0ustar zuulzuul00000000000000==================== appcontainer service ==================== An **appcontainer service** specifies the zun services. appcontainer service list ------------------------- Print a list of zun services .. program:: appcontainer service list .. code:: bash openstack appcontainer service list [-h] [-f {csv,json,table,value,yaml}] [-c COLUMN] [--max-width ] [--fit-width] [--print-empty] [--noindent] [--quote {all,minimal,none,nonnumeric}] [--sort-column SORT_COLUMN] .. option:: -h, --help show this help message and exit .. option:: -f {csv,json,table,value,yaml}, --format {csv,json,table,value,yaml} the output format, defaults to table .. option:: -c COLUMN, --column COLUMN specify the column(s) to include, can be repeated .. option:: --sort-column SORT_COLUMN specify the column(s) to sort the data (columns specified first have a priority, non-existing columns are ignored), can be repeated .. option:: --max-width Maximum display width, <1 to disable. You can also use the CLIFF_MAX_TERM_WIDTH environment variable, but the parameter takes precedence. .. option:: --fit-width Fit the table to the display width. Implied if --max- width greater than 0. Set the environment variable CLIFF_FIT_WIDTH=1 to always enable .. option:: --print-empty Print empty table if there is no data to show. .. option:: --noindent whether to disable indenting the JSON .. option:: --quote {all,minimal,none,nonnumeric} when to include quotes, defaults to nonnumeric appcontainer service delete --------------------------- Delete the Zun binaries/services. .. program:: appcontainer service delete .. code:: bash openstack appcontainer service delete [-h] .. describe:: Name of host .. describe:: Name of the binary to delete .. option:: -h, --help show this help message and exit appcontainer service forcedown ------------------------------ Force the Zun service to down or up. .. program:: appcontainer service forcedown .. code:: bash openstack appcontainer service forcedown [-h] [-f {json,shell,table,value,yaml}] [-c COLUMN] [--max-width ] [--fit-width] [--print-empty] [--noindent] [--prefix PREFIX] [--unset] .. describe:: Name of host .. describe:: Name of the binary to forcedown .. option:: -h, --help show this help message and exit .. option:: --unset Unset the force state down of service .. option:: -f {json,shell,table,value,yaml}, --format {json,shell,table,value,yaml} the output format, defaults to table .. option:: -c COLUMN, --column COLUMN specify the column(s) to include, can be repeated .. option:: --max-width Maximum display width, <1 to disable. You can also use the CLIFF_MAX_TERM_WIDTH environment variable, but the parameter takes precedence. .. option:: --fit-width Fit the table to the display width. Implied if --max- width greater than 0. Set the environment variable CLIFF_FIT_WIDTH=1 to always enable .. option:: --print-empty Print empty table if there is no data to show. .. option:: --noindent whether to disable indenting the JSON .. option:: --prefix PREFIX add a prefix to all variable names appcontainer service enable --------------------------- Enable the Zun service. .. program:: appcontainer service enable .. code:: bash openstack appcontainer service enable [-h] [-f {json,shell,table,value,yaml}] [-c COLUMN] [--max-width ] [--fit-width] [--print-empty] [--noindent] [--prefix PREFIX] .. describe:: Name of host .. describe:: Name of the binary to enable .. option:: -h, --help show this help message and exit .. option:: -f {json,shell,table,value,yaml}, --format {json,shell,table,value,yaml} the output format, defaults to table .. option:: -c COLUMN, --column COLUMN specify the column(s) to include, can be repeated .. option:: --max-width Maximum display width, <1 to disable. You can also use the CLIFF_MAX_TERM_WIDTH environment variable, but the parameter takes precedence. .. option:: --fit-width Fit the table to the display width. Implied if --max- width greater than 0. Set the environment variable CLIFF_FIT_WIDTH=1 to always enable .. option:: --print-empty Print empty table if there is no data to show. .. option:: --noindent whether to disable indenting the JSON appcontainer service disable ---------------------------- Disable the Zun service. .. program:: appcontainer service disable .. code:: bash openstack appcontainer service disable [-h] [-f {json,shell,table,value,yaml}] [-c COLUMN] [--max-width ] [--fit-width] [--print-empty] [--noindent] [--prefix PREFIX] [--reason ] .. describe:: Name of host .. describe:: Name of the binary to disable .. option:: -h, --help show this help message and exit .. option:: --reason Reason for disabling service .. option:: -f {json,shell,table,value,yaml}, --format {json,shell,table,value,yaml} the output format, defaults to table .. option:: -c COLUMN, --column COLUMN specify the column(s) to include, can be repeated .. option:: --max-width Maximum display width, <1 to disable. You can also use the CLIFF_MAX_TERM_WIDTH environment variable, but the parameter takes precedence. .. option:: --fit-width Fit the table to the display width. Implied if --max- width greater than 0. Set the environment variable CLIFF_FIT_WIDTH=1 to always enable .. option:: --print-empty Print empty table if there is no data to show. .. option:: --noindent whether to disable indenting the JSON .. option:: --prefix PREFIX add a prefix to all variable names python-zunclient-4.0.0/doc/source/cli/man/0000775000175000017500000000000013643163533020465 5ustar zuulzuul00000000000000python-zunclient-4.0.0/doc/source/cli/man/zun.rst0000664000175000017500000000331713643163457022044 0ustar zuulzuul00000000000000.. _manpage: ================ Zun CLI man page ================ SYNOPSIS ======== Zun operation use `zun` command, and also support use `openstack` command. :program:`zun` [options] [command-options] :program:`openstack` appcontainer [command-options] DESCRIPTION =========== The :program:`zun` command line utility interacts with OpenStack Containers Service (Zun). In order to use the CLI, you must provide your OpenStack username, password, project (historically called tenant), and auth endpoint. You can use configuration options `--os-username`, `--os-password`, `--os-tenant-name` or `--os-tenant-id`, and `--os-auth-url` or set corresponding environment variables:: export OS_USERNAME=user export OS_PASSWORD=pass export OS_PROJECT_NAME=myproject export OS_AUTH_URL=http://auth.example.com:5000/v3 export OS_USER_DOMAIN_ID=default export OS_PROJECT_DOMAIN_ID=default OPTIONS ======= To get a list of available commands and options run:: zun help To get usage and options of a command:: zun help You can also use openstack command as follow. To get a list of available commands run:: openstack help appcontainer To get usage and options of a command:: openstack appcontainer --help EXAMPLES ======== List all the containers:: zun list Create new container:: zun run --name container01 IMAGE01 Describe a specific container:: zun show container01 You can also use openstack command as follow. List all the containers:: openstack appcontainer list Create new container:: openstack appcontainer run --name container01 IMAGE01 Describe a specific container:: openstack appcontainer show container01