├── .coveragerc ├── .gitignore ├── .gitreview ├── .stestr.conf ├── .zuul.yaml ├── CONTRIBUTING.rst ├── HACKING.rst ├── LICENSE ├── MANIFEST.in ├── README.rst ├── doc ├── requirements.txt └── source │ ├── cli │ ├── command-list.rst │ ├── command-objects │ │ ├── action.rst │ │ ├── host.rst │ │ ├── image.rst │ │ └── service.rst │ ├── index.rst │ └── man │ │ └── zun.rst │ ├── conf.py │ ├── contributor │ └── index.rst │ ├── index.rst │ ├── install │ └── index.rst │ └── user │ ├── index.rst │ └── python-api.rst ├── releasenotes ├── notes │ ├── .placeholder │ └── drop-py-2-7-c18f48ee28088c4a.yaml └── source │ ├── 2023.1.rst │ ├── 2023.2.rst │ ├── 2024.1.rst │ ├── 2024.2.rst │ ├── 2025.1.rst │ ├── _static │ └── .placeholder │ ├── _templates │ └── .placeholder │ ├── conf.py │ ├── index.rst │ ├── pike.rst │ ├── queens.rst │ ├── rocky.rst │ ├── stein.rst │ ├── train.rst │ ├── unreleased.rst │ ├── ussuri.rst │ ├── victoria.rst │ ├── wallaby.rst │ ├── xena.rst │ ├── yoga.rst │ └── zed.rst ├── requirements.txt ├── setup.cfg ├── setup.py ├── test-requirements.txt ├── tools ├── fast8.sh ├── flake8wrap.sh ├── gen-config ├── run_functional.sh ├── zun.bash_completion └── zun.zsh_completion ├── tox.ini └── zunclient ├── __init__.py ├── api_versions.py ├── client.py ├── common ├── __init__.py ├── apiclient │ ├── __init__.py │ ├── auth.py │ ├── base.py │ └── exceptions.py ├── base.py ├── cliutils.py ├── httpclient.py ├── template_format.py ├── template_utils.py ├── utils.py └── websocketclient │ ├── __init__.py │ ├── exceptions.py │ └── websocketclient.py ├── exceptions.py ├── i18n.py ├── osc ├── __init__.py ├── plugin.py └── v1 │ ├── __init__.py │ ├── availability_zones.py │ ├── capsules.py │ ├── containers.py │ ├── hosts.py │ ├── images.py │ ├── quota_classes.py │ ├── quotas.py │ ├── registries.py │ └── services.py ├── shell.py ├── tests ├── __init__.py ├── functional │ ├── __init__.py │ ├── base.py │ ├── hooks │ │ ├── __init__.py │ │ ├── gate_hook.sh │ │ └── post_test_hook.sh │ └── osc │ │ ├── __init__.py │ │ └── v1 │ │ ├── __init__.py │ │ ├── base.py │ │ └── test_container.py └── unit │ ├── __init__.py │ ├── base.py │ ├── common │ ├── __init__.py │ ├── test_httpclient.py │ └── test_utils.py │ ├── osc │ ├── __init__.py │ └── test_plugin.py │ ├── test_api_versions.py │ ├── test_client.py │ ├── test_shell.py │ ├── test_websocketclient.py │ ├── utils.py │ └── v1 │ ├── __init__.py │ ├── shell_test_base.py │ ├── test_availability_zones.py │ ├── test_availability_zones_shell.py │ ├── test_client.py │ ├── test_containers.py │ ├── test_containers_shell.py │ ├── test_images.py │ ├── test_images_shell.py │ ├── test_quotas.py │ ├── test_registries.py │ ├── test_services.py │ ├── test_services_shell.py │ ├── test_versions.py │ └── test_versions_shell.py ├── v1 ├── __init__.py ├── actions.py ├── actions_shell.py ├── availability_zones.py ├── availability_zones_shell.py ├── capsules.py ├── capsules_shell.py ├── client.py ├── containers.py ├── containers_shell.py ├── hosts.py ├── hosts_shell.py ├── images.py ├── images_shell.py ├── quota_classes.py ├── quota_classes_shell.py ├── quotas.py ├── quotas_shell.py ├── registries.py ├── registries_shell.py ├── services.py ├── services_shell.py ├── shell.py ├── versions.py └── versions_shell.py └── version.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | source = zunclient 4 | omit = zunclient/tests/* 5 | 6 | [report] 7 | ignore_errors = True 8 | exclude_lines = 9 | pass 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | 21 | # Installer logs 22 | pip-log.txt 23 | 24 | # Unit test / coverage reports 25 | .coverage 26 | .tox 27 | cover 28 | cover-master 29 | nosetests.xml 30 | .stestr/ 31 | 32 | # Translations 33 | *.mo 34 | 35 | # Mr Developer 36 | .mr.developer.cfg 37 | .project 38 | .pydevproject 39 | .idea 40 | 41 | # Complexity 42 | output/*.html 43 | output/*/index.html 44 | 45 | # Sphinx 46 | doc/build 47 | 48 | # pbr generates these 49 | AUTHORS 50 | ChangeLog 51 | 52 | # Editors 53 | *~ 54 | .*.swp 55 | *.DS_Store 56 | 57 | # Files created by releasenotes build 58 | releasenotes/build 59 | 60 | # Others 61 | test.conf 62 | -------------------------------------------------------------------------------- /.gitreview: -------------------------------------------------------------------------------- 1 | [gerrit] 2 | host=review.opendev.org 3 | port=29418 4 | project=openstack/python-zunclient.git 5 | -------------------------------------------------------------------------------- /.stestr.conf: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | test_path=${OS_TEST_PATH:-./zunclient/tests/unit} 3 | top_dir=./ 4 | -------------------------------------------------------------------------------- /.zuul.yaml: -------------------------------------------------------------------------------- 1 | - project: 2 | templates: 3 | - openstack-cover-jobs 4 | - openstack-python3-jobs 5 | - check-requirements 6 | - publish-openstack-docs-pti 7 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | If you would like to contribute to the development of OpenStack, you must 2 | follow the steps in this page: 3 | 4 | https://docs.openstack.org/infra/manual/developers.html 5 | 6 | If you already have a good understanding of how the system works and your 7 | OpenStack accounts are set up, you can skip to the development workflow 8 | section of this documentation to learn how changes to OpenStack should be 9 | submitted for review via the Gerrit tool: 10 | 11 | https://docs.openstack.org/infra/manual/developers.html#development-workflow 12 | 13 | Pull requests submitted through GitHub will be ignored. 14 | 15 | Bugs should be filed on Launchpad, not GitHub: 16 | 17 | https://bugs.launchpad.net/python-zunclient 18 | -------------------------------------------------------------------------------- /HACKING.rst: -------------------------------------------------------------------------------- 1 | python-zunclient Style Commandments 2 | =================================== 3 | 4 | Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/ 5 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS 2 | include ChangeLog 3 | exclude .gitignore 4 | exclude .gitreview 5 | 6 | global-exclude *.pyc 7 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ======================== 2 | Team and repository tags 3 | ======================== 4 | 5 | .. image:: https://governance.openstack.org/tc/badges/python-zunclient.svg 6 | :target: https://governance.openstack.org/tc/reference/tags/index.html 7 | 8 | .. Change things from this point on 9 | 10 | ============================================== 11 | Python bindings to the OpenStack Container API 12 | ============================================== 13 | 14 | .. image:: https://img.shields.io/pypi/v/python-zunclient.svg 15 | :target: https://pypi.org/project/python-zunclient/ 16 | :alt: Latest Version 17 | 18 | This is a client library for Zun built on the Zun API. 19 | It provides a Python API (the zunclient module) 20 | and a command-line tool (zun). 21 | 22 | * License: Apache License, Version 2.0 23 | * `PyPi`_ - package installation 24 | * `Online Documentation`_ 25 | * `Launchpad project`_ - release management 26 | * `Blueprints`_ - feature specifications 27 | * `Bugs`_ - issue tracking 28 | * `Source`_ 29 | * `How to Contribute`_ 30 | 31 | .. _PyPi: https://pypi.org/project/python-zunclient 32 | .. _Online Documentation: https://docs.openstack.org/python-zunclient/latest 33 | .. _Launchpad project: https://launchpad.net/python-zunclient 34 | .. _Blueprints: https://blueprints.launchpad.net/python-zunclient 35 | .. _Bugs: https://bugs.launchpad.net/python-zunclient 36 | .. _Source: https://opendev.org/openstack/python-zunclient 37 | .. _How to Contribute: https://docs.openstack.org/infra/manual/developers.html 38 | -------------------------------------------------------------------------------- /doc/requirements.txt: -------------------------------------------------------------------------------- 1 | # The order of packages is significant, because pip processes them in the order 2 | # of appearance. Changing the order has an impact on the overall integration 3 | # process, which may cause wedges in the gate later. 4 | openstackdocstheme>=2.2.0 # Apache-2.0 5 | sphinx>=2.0.0,!=2.1.0 # BSD 6 | reno>=3.1.0 # Apache-2.0 7 | -------------------------------------------------------------------------------- /doc/source/cli/command-list.rst: -------------------------------------------------------------------------------- 1 | .. _command-list: 2 | 3 | ============ 4 | Command List 5 | ============ 6 | 7 | .. toctree:: 8 | :glob: 9 | :maxdepth: 2 10 | 11 | command-objects/* 12 | -------------------------------------------------------------------------------- /doc/source/cli/command-objects/action.rst: -------------------------------------------------------------------------------- 1 | =================== 2 | appcontainer action 3 | =================== 4 | 5 | An **appcontainer action** specifies the action details for a container. 6 | 7 | appcontainer action list 8 | ------------------------ 9 | 10 | List actions on a container 11 | 12 | .. program:: appcontainer action list 13 | .. code:: bash 14 | 15 | openstack appcontainer action list [-h] 16 | [-f {csv,json,table,value,yaml}] 17 | [-c COLUMN] [--max-width ] 18 | [--fit-width] [--print-empty] 19 | [--noindent] 20 | [--quote {all,minimal,none,nonnumeric}] 21 | [--sort-column SORT_COLUMN] 22 | 23 | 24 | .. describe:: 25 | 26 | ID or name of the container to list actions. 27 | 28 | .. option:: -h, --help 29 | 30 | show this help message and exit 31 | 32 | .. option:: -f {csv,json,table,value,yaml}, 33 | --format {csv,json,table,value,yaml} 34 | 35 | the output format, defaults to table 36 | 37 | .. option:: -c COLUMN, --column COLUMN 38 | 39 | specify the column(s) to include, can be repeated 40 | 41 | .. option:: --sort-column SORT_COLUMN 42 | 43 | specify the column(s) to sort the data (columns 44 | specified first have a priority, non-existing columns 45 | are ignored), can be repeated 46 | 47 | .. option:: --max-width 48 | 49 | Maximum display width, <1 to disable. You can also use 50 | the CLIFF_MAX_TERM_WIDTH environment variable, but the 51 | parameter takes precedence. 52 | 53 | .. option:: --fit-width 54 | 55 | Fit the table to the display width. Implied if --max-width 56 | greater than 0. Set the environment variable 57 | CLIFF_FIT_WIDTH=1 to always enable 58 | 59 | .. option:: --print-empty 60 | 61 | Print empty table if there is no data to show. 62 | 63 | .. option:: --noindent 64 | 65 | whether to disable indenting the JSON 66 | 67 | .. option:: --quote {all,minimal,none,nonnumeric} 68 | 69 | when to include quotes, defaults to nonnumeric 70 | 71 | appcontainer action show 72 | ------------------------ 73 | 74 | Shows action 75 | 76 | .. program:: appcontainer action show 77 | .. code:: bash 78 | 79 | openstack appcontainer action show [-h] 80 | [-f {json,shell,table,value,yaml}] 81 | [-c COLUMN] [--max-width ] 82 | [--fit-width] [--print-empty] 83 | [--noindent] [--prefix PREFIX] 84 | 85 | 86 | .. describe:: 87 | 88 | ID or name of the container to show. 89 | 90 | .. describe:: 91 | 92 | request ID of action to describe. 93 | 94 | .. option:: -f {json,shell,table,value,yaml}, 95 | --format {json,shell,table,value,yaml} 96 | 97 | the output format, defaults to table 98 | 99 | .. option:: -c COLUMN, --column COLUMN 100 | 101 | specify the column(s) to include, can be repeated 102 | 103 | .. option:: --max-width 104 | 105 | Maximum display width, <1 to disable. You can also use 106 | the CLIFF_MAX_TERM_WIDTH environment variable, but the 107 | parameter takes precedence. 108 | 109 | .. option:: --fit-width 110 | 111 | Fit the table to the display width. Implied if --max-width 112 | greater than 0. Set the environment variable CLIFF_FIT_WIDTH 113 | =1 to always enable 114 | 115 | .. option:: --print-empty 116 | 117 | Print empty table if there is no data to show. 118 | 119 | .. option:: --noindent 120 | 121 | whether to disable indenting the JSON 122 | 123 | .. option:: --prefix PREFIX 124 | 125 | add a prefix to all variable names 126 | -------------------------------------------------------------------------------- /doc/source/cli/command-objects/host.rst: -------------------------------------------------------------------------------- 1 | ================= 2 | appcontainer host 3 | ================= 4 | 5 | An **appcontainer host** specifies the compute host for running containers. 6 | 7 | appcontainer host list 8 | ---------------------- 9 | 10 | List available hosts 11 | 12 | .. program:: appcontainer host list 13 | .. code:: bash 14 | 15 | openstack appcontainer host list [-h] [-f {csv,json,table,value,yaml}] 16 | [-c COLUMN] [--max-width ] 17 | [--fit-width] [--print-empty] 18 | [--noindent] 19 | [--quote {all,minimal,none,nonnumeric}] 20 | [--sort-column SORT_COLUMN] 21 | [--marker ] [--limit ] 22 | [--sort-key ] 23 | [--sort-dir ] 24 | 25 | .. option:: -h, --help 26 | 27 | show this help message and exit 28 | 29 | .. option:: --marker 30 | 31 | The last host UUID of the previous page; displays list 32 | of hosts after "marker". 33 | 34 | .. option:: --limit 35 | 36 | Maximum number of hosts to return 37 | 38 | .. option:: --sort-key 39 | 40 | Column to sort results by 41 | 42 | .. option:: --sort-dir 43 | 44 | Direction to sort. "asc" or "desc". 45 | 46 | .. option:: -f {csv,json,table,value,yaml}, 47 | --format {csv,json,table,value,yaml} 48 | 49 | the output format, defaults to table 50 | 51 | .. option:: -c COLUMN, --column COLUMN 52 | 53 | specify the column(s) to include, can be repeated 54 | 55 | .. option:: --sort-column SORT_COLUMN 56 | 57 | specify the column(s) to sort the data (columns 58 | specified first have a priority, non-existing columns 59 | are ignored), can be repeated 60 | 61 | .. option:: --max-width 62 | 63 | Maximum display width, <1 to disable. You can also use 64 | the CLIFF_MAX_TERM_WIDTH environment variable, but the 65 | parameter takes precedence. 66 | 67 | .. option:: --fit-width 68 | 69 | Fit the table to the display width. Implied if --max- 70 | width greater than 0. Set the environment variable 71 | CLIFF_FIT_WIDTH=1 to always enable 72 | 73 | .. option:: --print-empty 74 | 75 | Print empty table if there is no data to show. 76 | 77 | .. option:: --noindent 78 | 79 | whether to disable indenting the JSON 80 | 81 | .. option:: --quote {all,minimal,none,nonnumeric} 82 | 83 | when to include quotes, defaults to nonnumeric 84 | 85 | appcontainer host show 86 | ---------------------- 87 | 88 | Show a host 89 | 90 | .. program:: appcontainer host show 91 | .. code:: bash 92 | 93 | openstack appcontainer host show [-h] 94 | [-f {json,shell,table,value,yaml}] 95 | [-c COLUMN] [--max-width ] 96 | [--fit-width] [--print-empty] 97 | [--noindent] [--prefix PREFIX] 98 | 99 | 100 | .. describe:: 101 | 102 | ID or name of the host to show. 103 | 104 | .. option:: -h, --help 105 | 106 | show this help message and exit 107 | 108 | .. option:: -f {json,shell,table,value,yaml}, 109 | --format {json,shell,table,value,yaml} 110 | 111 | the output format, defaults to table 112 | 113 | .. option:: -c COLUMN, --column COLUMN 114 | 115 | specify the column(s) to include, can be repeated 116 | 117 | .. option:: --max-width 118 | 119 | Maximum display width, <1 to disable. You can also use 120 | the CLIFF_MAX_TERM_WIDTH environment variable, but the 121 | parameter takes precedence. 122 | 123 | .. option:: --fit-width 124 | 125 | Fit the table to the display width. Implied if --max- 126 | width greater than 0. Set the environment variable 127 | CLIFF_FIT_WIDTH=1 to always enable 128 | 129 | .. option:: --print-empty 130 | 131 | Print empty table if there is no data to show. 132 | 133 | .. option:: --noindent 134 | 135 | whether to disable indenting the JSON 136 | 137 | .. option:: --prefix PREFIX 138 | 139 | add a prefix to all variable names 140 | -------------------------------------------------------------------------------- /doc/source/cli/index.rst: -------------------------------------------------------------------------------- 1 | ==================== 2 | User Documentation 3 | ==================== 4 | 5 | .. toctree:: 6 | :maxdepth: 2 7 | 8 | Manual Page 9 | command-list 10 | -------------------------------------------------------------------------------- /doc/source/cli/man/zun.rst: -------------------------------------------------------------------------------- 1 | .. _manpage: 2 | 3 | ================ 4 | Zun CLI man page 5 | ================ 6 | 7 | 8 | SYNOPSIS 9 | ======== 10 | 11 | Zun operation use `zun` command, and also support use `openstack` command. 12 | 13 | :program:`zun` [options] [command-options] 14 | 15 | :program:`openstack` appcontainer [command-options] 16 | 17 | 18 | DESCRIPTION 19 | =========== 20 | 21 | The :program:`zun` command line utility interacts with OpenStack Containers 22 | Service (Zun). 23 | 24 | In order to use the CLI, you must provide your OpenStack username, password, 25 | project (historically called tenant), and auth endpoint. You can use 26 | configuration options `--os-username`, `--os-password`, `--os-tenant-name` or 27 | `--os-tenant-id`, and `--os-auth-url` or set corresponding environment 28 | variables:: 29 | 30 | export OS_USERNAME=user 31 | export OS_PASSWORD=pass 32 | export OS_PROJECT_NAME=myproject 33 | export OS_AUTH_URL=http://auth.example.com:5000/v3 34 | export OS_USER_DOMAIN_ID=default 35 | export OS_PROJECT_DOMAIN_ID=default 36 | 37 | OPTIONS 38 | ======= 39 | 40 | To get a list of available commands and options run:: 41 | 42 | zun help 43 | 44 | To get usage and options of a command:: 45 | 46 | zun help 47 | 48 | You can also use openstack command as follow. 49 | 50 | To get a list of available commands run:: 51 | 52 | openstack help appcontainer 53 | 54 | To get usage and options of a command:: 55 | 56 | openstack appcontainer --help 57 | 58 | 59 | 60 | EXAMPLES 61 | ======== 62 | 63 | List all the containers:: 64 | 65 | zun list 66 | 67 | Create new container:: 68 | 69 | zun run --name container01 IMAGE01 70 | 71 | Describe a specific container:: 72 | 73 | zun show container01 74 | 75 | You can also use openstack command as follow. 76 | 77 | List all the containers:: 78 | 79 | openstack appcontainer list 80 | 81 | Create new container:: 82 | 83 | openstack appcontainer run --name container01 IMAGE01 84 | 85 | Describe a specific container:: 86 | 87 | openstack appcontainer show container01 88 | -------------------------------------------------------------------------------- /doc/source/conf.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); 2 | # you may not use this file except in compliance with the License. 3 | # You may obtain a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, 9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 10 | # implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | 14 | import os 15 | import sys 16 | 17 | sys.path.insert(0, os.path.abspath('../..')) 18 | # -- General configuration ---------------------------------------------------- 19 | 20 | # Add any Sphinx extension module names here, as strings. They can be 21 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 22 | extensions = [ 23 | 'sphinx.ext.autodoc', 24 | #'sphinx.ext.intersphinx', 25 | 'openstackdocstheme', 26 | ] 27 | 28 | # openstackdocstheme options 29 | openstackdocs_repo_name = 'openstack/python-zunclient' 30 | openstackdocs_pdf_link = True 31 | openstackdocs_bug_project = 'python-zunclient' 32 | openstackdocs_bug_tag = 'doc' 33 | 34 | # autodoc generation is a bit aggressive and a nuisance when doing heavy 35 | # text edit cycles. 36 | # execute "export SPHINX_DEBUG=1" in your terminal to disable 37 | 38 | # The suffix of source filenames. 39 | source_suffix = '.rst' 40 | 41 | # The master toctree document. 42 | master_doc = 'index' 43 | 44 | # General information about the project. 45 | copyright = '2013, OpenStack Foundation' 46 | 47 | # If true, '()' will be appended to :func: etc. cross-reference text. 48 | add_function_parentheses = True 49 | 50 | # If true, the current module name will be prepended to all description 51 | # unit titles (such as .. function::). 52 | add_module_names = True 53 | 54 | # The name of the Pygments (syntax highlighting) style to use. 55 | pygments_style = 'native' 56 | 57 | # -- Options for HTML output -------------------------------------------------- 58 | 59 | # The theme to use for HTML and HTML Help pages. Major themes that come with 60 | # Sphinx are currently 'default' and 'sphinxdoc'. 61 | 62 | html_theme = 'openstackdocs' 63 | 64 | # Output file base name for HTML help builder. 65 | htmlhelp_basename = 'zunclientdoc' 66 | 67 | # Grouping the document tree into LaTeX files. List of tuples 68 | # (source start file, target name, title, author, documentclass 69 | # [howto/manual]). 70 | latex_documents = [ 71 | ('index', 72 | 'doc-python-zunclient.tex', 73 | 'Python Zun Client Documentation', 74 | 'Zun development team', 'manual'), 75 | ] 76 | 77 | # Disable usage of xindy https://bugzilla.redhat.com/show_bug.cgi?id=1643664 78 | latex_use_xindy = False 79 | 80 | latex_domain_indices = False 81 | 82 | latex_elements = { 83 | 'makeindex': '', 84 | 'printindex': '', 85 | 'preamble': r'\setcounter{tocdepth}{3}', 86 | 'extraclassoptions': 'openany', 87 | } 88 | -------------------------------------------------------------------------------- /doc/source/contributor/index.rst: -------------------------------------------------------------------------------- 1 | =================== 2 | Contributor's Guide 3 | =================== 4 | 5 | .. include:: ../../../CONTRIBUTING.rst 6 | -------------------------------------------------------------------------------- /doc/source/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to python-zunclient's documentation! 2 | ============================================ 3 | 4 | Contents 5 | -------- 6 | 7 | .. toctree:: 8 | :maxdepth: 2 9 | 10 | install/index 11 | contributor/index 12 | cli/index 13 | user/index 14 | 15 | .. only:: html 16 | 17 | Indices and tables 18 | ------------------ 19 | 20 | * :ref:`genindex` 21 | * :ref:`modindex` 22 | * :ref:`search` 23 | -------------------------------------------------------------------------------- /doc/source/install/index.rst: -------------------------------------------------------------------------------- 1 | ================== 2 | Installation Guide 3 | ================== 4 | 5 | At the command line:: 6 | 7 | $ pip install python-zunclient 8 | 9 | Or, if you have virtualenvwrapper installed:: 10 | 11 | $ mkvirtualenv python-zunclient 12 | $ pip install python-zunclient 13 | -------------------------------------------------------------------------------- /doc/source/user/index.rst: -------------------------------------------------------------------------------- 1 | Reference 2 | ========= 3 | 4 | .. toctree:: 5 | :maxdepth: 1 6 | 7 | python-api 8 | -------------------------------------------------------------------------------- /doc/source/user/python-api.rst: -------------------------------------------------------------------------------- 1 | ================================ 2 | The :mod:`zunclient` Python API 3 | ================================ 4 | 5 | .. module:: zunclient 6 | :synopsis: A client for the OpenStack Zun API. 7 | 8 | .. currentmodule:: zunclient 9 | 10 | Usage 11 | ----- 12 | 13 | First create a client instance with your credentials:: 14 | 15 | >>> from zunclient import client 16 | >>> zun = client.Client(VERSION, auth_url=AUTH_URL, username=USERNAME, 17 | ... password=PASSWORD, project_name=PROJECT_NAME, 18 | ... user_domain_name='default', 19 | ... project_domain_name='default') 20 | 21 | Here ``VERSION`` can be a string or ``zunclient.api_versions.APIVersion`` obj. 22 | If you prefer string value, you can use ``1`` or 23 | ``1.X`` (where X is a microversion). 24 | 25 | Alternatively, you can create a client instance using the keystoneauth 26 | session API:: 27 | 28 | >>> from keystoneauth1 import loading 29 | >>> from keystoneauth1 import session 30 | >>> from zunclient import client 31 | >>> loader = loading.get_plugin_loader('password') 32 | >>> auth = loader.load_from_options(auth_url=AUTH_URL, 33 | ... username=USERNAME, 34 | ... password=PASSWORD, 35 | ... project_name=PROJECT_NAME, 36 | ... user_domain_name='default', 37 | ... project_domain_name='default') 38 | >>> sess = session.Session(auth=auth) 39 | >>> zun = client.Client(VERSION, session=sess) 40 | 41 | If you have PROJECT_NAME instead of a PROJECT_ID, use the project_name 42 | parameter. Similarly, if your cloud uses keystone v3 and you have a DOMAIN_NAME 43 | or DOMAIN_ID, provide it as `user_domain_(name|id)` and if you are using a 44 | PROJECT_NAME also provide the domain information as `project_domain_(name|id)`. 45 | 46 | Then call methods on its managers:: 47 | 48 | >>> zun.containers.list() 49 | [] 50 | 51 | >>> zun.containers.run(name="my-container", image='nginx') 52 | 53 | -------------------------------------------------------------------------------- /releasenotes/notes/.placeholder: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/python-zunclient/eb6806a8682914171dd17a44c6b80e78f7c6b0bc/releasenotes/notes/.placeholder -------------------------------------------------------------------------------- /releasenotes/notes/drop-py-2-7-c18f48ee28088c4a.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | upgrade: 3 | - | 4 | Python 2.7 support has been dropped. Last release of python-zunclient 5 | to support py2.7 is OpenStack Train. The minimum version of Python now 6 | supported by python-zunclient is Python 3.6. 7 | -------------------------------------------------------------------------------- /releasenotes/source/2023.1.rst: -------------------------------------------------------------------------------- 1 | =========================== 2 | 2023.1 Series Release Notes 3 | =========================== 4 | 5 | .. release-notes:: 6 | :branch: unmaintained/2023.1 7 | -------------------------------------------------------------------------------- /releasenotes/source/2023.2.rst: -------------------------------------------------------------------------------- 1 | =========================== 2 | 2023.2 Series Release Notes 3 | =========================== 4 | 5 | .. release-notes:: 6 | :branch: stable/2023.2 7 | -------------------------------------------------------------------------------- /releasenotes/source/2024.1.rst: -------------------------------------------------------------------------------- 1 | =========================== 2 | 2024.1 Series Release Notes 3 | =========================== 4 | 5 | .. release-notes:: 6 | :branch: stable/2024.1 7 | -------------------------------------------------------------------------------- /releasenotes/source/2024.2.rst: -------------------------------------------------------------------------------- 1 | =========================== 2 | 2024.2 Series Release Notes 3 | =========================== 4 | 5 | .. release-notes:: 6 | :branch: stable/2024.2 7 | -------------------------------------------------------------------------------- /releasenotes/source/2025.1.rst: -------------------------------------------------------------------------------- 1 | =========================== 2 | 2025.1 Series Release Notes 3 | =========================== 4 | 5 | .. release-notes:: 6 | :branch: stable/2025.1 7 | -------------------------------------------------------------------------------- /releasenotes/source/_static/.placeholder: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/python-zunclient/eb6806a8682914171dd17a44c6b80e78f7c6b0bc/releasenotes/source/_static/.placeholder -------------------------------------------------------------------------------- /releasenotes/source/_templates/.placeholder: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/python-zunclient/eb6806a8682914171dd17a44c6b80e78f7c6b0bc/releasenotes/source/_templates/.placeholder -------------------------------------------------------------------------------- /releasenotes/source/index.rst: -------------------------------------------------------------------------------- 1 | ============================== 2 | python-zunclient Release Notes 3 | ============================== 4 | 5 | .. toctree:: 6 | :maxdepth: 1 7 | 8 | unreleased 9 | 2025.1 10 | 2024.2 11 | 2024.1 12 | 2023.2 13 | 2023.1 14 | zed 15 | yoga 16 | xena 17 | wallaby 18 | victoria 19 | ussuri 20 | train 21 | stein 22 | rocky 23 | queens 24 | pike 25 | -------------------------------------------------------------------------------- /releasenotes/source/pike.rst: -------------------------------------------------------------------------------- 1 | =================================== 2 | Pike Series Release Notes 3 | =================================== 4 | 5 | .. release-notes:: 6 | :branch: stable/pike 7 | -------------------------------------------------------------------------------- /releasenotes/source/queens.rst: -------------------------------------------------------------------------------- 1 | =================================== 2 | Queens Series Release Notes 3 | =================================== 4 | 5 | .. release-notes:: 6 | :branch: stable/queens 7 | -------------------------------------------------------------------------------- /releasenotes/source/rocky.rst: -------------------------------------------------------------------------------- 1 | ========================== 2 | Rocky Series Release Notes 3 | ========================== 4 | 5 | .. release-notes:: 6 | :branch: stable/rocky 7 | -------------------------------------------------------------------------------- /releasenotes/source/stein.rst: -------------------------------------------------------------------------------- 1 | =================================== 2 | Stein Series Release Notes 3 | =================================== 4 | 5 | .. release-notes:: 6 | :branch: stable/stein 7 | -------------------------------------------------------------------------------- /releasenotes/source/train.rst: -------------------------------------------------------------------------------- 1 | =================================== 2 | Train Series Release Notes 3 | =================================== 4 | 5 | .. release-notes:: 6 | :branch: stable/train 7 | -------------------------------------------------------------------------------- /releasenotes/source/unreleased.rst: -------------------------------------------------------------------------------- 1 | ============================== 2 | Current Series Release Notes 3 | ============================== 4 | 5 | .. release-notes:: 6 | -------------------------------------------------------------------------------- /releasenotes/source/ussuri.rst: -------------------------------------------------------------------------------- 1 | =========================== 2 | Ussuri Series Release Notes 3 | =========================== 4 | 5 | .. release-notes:: 6 | :branch: stable/ussuri 7 | -------------------------------------------------------------------------------- /releasenotes/source/victoria.rst: -------------------------------------------------------------------------------- 1 | ============================= 2 | Victoria Series Release Notes 3 | ============================= 4 | 5 | .. release-notes:: 6 | :branch: unmaintained/victoria 7 | -------------------------------------------------------------------------------- /releasenotes/source/wallaby.rst: -------------------------------------------------------------------------------- 1 | ============================ 2 | Wallaby Series Release Notes 3 | ============================ 4 | 5 | .. release-notes:: 6 | :branch: unmaintained/wallaby 7 | -------------------------------------------------------------------------------- /releasenotes/source/xena.rst: -------------------------------------------------------------------------------- 1 | ========================= 2 | Xena Series Release Notes 3 | ========================= 4 | 5 | .. release-notes:: 6 | :branch: unmaintained/xena 7 | -------------------------------------------------------------------------------- /releasenotes/source/yoga.rst: -------------------------------------------------------------------------------- 1 | ========================= 2 | Yoga Series Release Notes 3 | ========================= 4 | 5 | .. release-notes:: 6 | :branch: unmaintained/yoga 7 | -------------------------------------------------------------------------------- /releasenotes/source/zed.rst: -------------------------------------------------------------------------------- 1 | ======================== 2 | Zed Series Release Notes 3 | ======================== 4 | 5 | .. release-notes:: 6 | :branch: stable/zed 7 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # Requirements lower bounds listed here are our best effort to keep them up to 2 | # date but we do not test them so no guarantee of having them all correct. If 3 | # you find any incorrect lower bounds, let us know or propose a fix. 4 | 5 | # The order of packages is significant, because pip processes them in the order 6 | # of appearance. Changing the order has an impact on the overall integration 7 | # process, which may cause wedges in the gate later. 8 | 9 | pbr!=2.1.0,>=2.0.0 # Apache-2.0 10 | PrettyTable>=0.7.1 # BSD 11 | python-openstackclient>=3.12.0 # Apache-2.0 12 | keystoneauth1>=3.4.0 # Apache-2.0 13 | osc-lib>=1.8.0 # Apache-2.0 14 | oslo.i18n>=3.15.3 # Apache-2.0 15 | oslo.log>=3.36.0 # Apache-2.0 16 | oslo.utils>=3.33.0 # Apache-2.0 17 | websocket-client>=0.44.0 # LGPLv2+ 18 | docker>=2.4.2 # Apache-2.0 19 | PyYAML>=3.13 # MIT 20 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = python-zunclient 3 | summary = Client Library for Zun 4 | description_file = 5 | README.rst 6 | author = OpenStack 7 | author_email = openstack-discuss@lists.openstack.org 8 | home_page = https://docs.openstack.org/python-zunclient/latest/ 9 | python_requires = >=3.8 10 | classifier = 11 | Environment :: OpenStack 12 | Intended Audience :: Information Technology 13 | Intended Audience :: System Administrators 14 | License :: OSI Approved :: Apache Software License 15 | Operating System :: POSIX :: Linux 16 | Programming Language :: Python 17 | Programming Language :: Python :: Implementation :: CPython 18 | Programming Language :: Python :: 3 :: Only 19 | Programming Language :: Python :: 3 20 | Programming Language :: Python :: 3.8 21 | Programming Language :: Python :: 3.9 22 | Programming Language :: Python :: 3.10 23 | Programming Language :: Python :: 3.11 24 | 25 | [files] 26 | packages = 27 | zunclient 28 | 29 | [entry_points] 30 | console_scripts = 31 | zun = zunclient.shell:main 32 | 33 | openstack.cli.extension = 34 | container = zunclient.osc.plugin 35 | 36 | openstack.container.v1 = 37 | appcontainer_availability_zone_list = zunclient.osc.v1.availability_zones:ListAvailabilityZone 38 | appcontainer_service_list = zunclient.osc.v1.services:ListService 39 | appcontainer_service_delete = zunclient.osc.v1.services:DeleteService 40 | appcontainer_service_enable = zunclient.osc.v1.services:EnableService 41 | appcontainer_service_disable = zunclient.osc.v1.services:DisableService 42 | appcontainer_service_forcedown = zunclient.osc.v1.services:ForceDownService 43 | appcontainer_create = zunclient.osc.v1.containers:CreateContainer 44 | appcontainer_show = zunclient.osc.v1.containers:ShowContainer 45 | appcontainer_list = zunclient.osc.v1.containers:ListContainer 46 | appcontainer_delete = zunclient.osc.v1.containers:DeleteContainer 47 | appcontainer_restart = zunclient.osc.v1.containers:RestartContainer 48 | appcontainer_start = zunclient.osc.v1.containers:StartContainer 49 | appcontainer_pause = zunclient.osc.v1.containers:PauseContainer 50 | appcontainer_unpause = zunclient.osc.v1.containers:UnpauseContainer 51 | appcontainer_exec = zunclient.osc.v1.containers:ExecContainer 52 | appcontainer_logs = zunclient.osc.v1.containers:LogsContainer 53 | appcontainer_kill = zunclient.osc.v1.containers:KillContainer 54 | appcontainer_stop = zunclient.osc.v1.containers:StopContainer 55 | appcontainer_run = zunclient.osc.v1.containers:RunContainer 56 | appcontainer_top = zunclient.osc.v1.containers:TopContainer 57 | appcontainer_set = zunclient.osc.v1.containers:UpdateContainer 58 | appcontainer_attach = zunclient.osc.v1.containers:AttachContainer 59 | appcontainer_cp = zunclient.osc.v1.containers:CopyContainer 60 | appcontainer_stats = zunclient.osc.v1.containers:StatsContainer 61 | appcontainer_commit = zunclient.osc.v1.containers:CommitContainer 62 | appcontainer_add_security_group = zunclient.osc.v1.containers:AddSecurityGroup 63 | appcontainer_image_delete = zunclient.osc.v1.images:DeleteImage 64 | appcontainer_image_list = zunclient.osc.v1.images:ListImage 65 | appcontainer_image_pull = zunclient.osc.v1.images:PullImage 66 | appcontainer_host_list = zunclient.osc.v1.hosts:ListHost 67 | appcontainer_host_show = zunclient.osc.v1.hosts:ShowHost 68 | appcontainer_network_detach = zunclient.osc.v1.containers:NetworkDetach 69 | appcontainer_network_attach = zunclient.osc.v1.containers:NetworkAttach 70 | appcontainer_network_list = zunclient.osc.v1.containers:NetworkList 71 | appcontainer_image_search = zunclient.osc.v1.images:SearchImage 72 | appcontainer_remove_security_group = zunclient.osc.v1.containers:RemoveSecurityGroup 73 | appcontainer_image_show = zunclient.osc.v1.images:ShowImage 74 | appcontainer_rebuild = zunclient.osc.v1.containers:RebuildContainer 75 | appcontainer_action_list = zunclient.osc.v1.containers:ActionList 76 | appcontainer_action_show = zunclient.osc.v1.containers:ActionShow 77 | appcontainer_quota_get = zunclient.osc.v1.quotas:GetQuota 78 | appcontainer_quota_default = zunclient.osc.v1.quotas:GetDefaultQuota 79 | appcontainer_quota_delete = zunclient.osc.v1.quotas:DeleteQuota 80 | appcontainer_quota_update = zunclient.osc.v1.quotas:UpdateQuota 81 | appcontainer_quota_class_update = zunclient.osc.v1.quota_classes:UpdateQuotaClass 82 | appcontainer_quota_class_get = zunclient.osc.v1.quota_classes:GetQuotaClass 83 | appcontainer_registry_create = zunclient.osc.v1.registries:CreateRegistry 84 | appcontainer_registry_list = zunclient.osc.v1.registries:ListRegistry 85 | appcontainer_registry_show = zunclient.osc.v1.registries:ShowRegistry 86 | appcontainer_registry_update = zunclient.osc.v1.registries:UpdateRegistry 87 | appcontainer_registry_delete = zunclient.osc.v1.registries:DeleteRegistry 88 | appcontainer_add_floating_ip = zunclient.osc.v1.containers:AddFloatingIP 89 | appcontainer_remove_floating_ip = zunclient.osc.v1.containers:RemoveFloatingIP 90 | capsule_create = zunclient.osc.v1.capsules:CreateCapsule 91 | capsule_show = zunclient.osc.v1.capsules:ShowCapsule 92 | capsule_list = zunclient.osc.v1.capsules:ListCapsule 93 | capsule_delete = zunclient.osc.v1.capsules:DeleteCapsule 94 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | # implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | import setuptools 17 | 18 | setuptools.setup( 19 | setup_requires=['pbr>=2.0.0'], 20 | pbr=True) 21 | -------------------------------------------------------------------------------- /test-requirements.txt: -------------------------------------------------------------------------------- 1 | # The order of packages is significant, because pip processes them in the order 2 | # of appearance. Changing the order has an impact on the overall integration 3 | # process, which may cause wedges in the gate later. 4 | 5 | bandit>=1.1.0 # Apache-2.0 6 | coverage!=4.4,>=4.0 # Apache-2.0 7 | doc8>=0.6.0 # Apache-2.0 8 | ddt>=1.0.1 # MIT 9 | hacking>=3.0.1,<3.1.0 # Apache-2.0 10 | oslotest>=3.2.0 # Apache-2.0 11 | osprofiler>=1.4.0 # Apache-2.0 12 | stestr>=2.0.0 13 | python-subunit>=1.0.0 # Apache-2.0/BSD 14 | tempest>=17.1.0 # Apache-2.0 15 | testresources>=2.0.0 # Apache-2.0/BSD 16 | testscenarios>=0.4 # Apache-2.0/BSD 17 | testtools>=2.2.0 # MIT 18 | -------------------------------------------------------------------------------- /tools/fast8.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd $(dirname "$0")/.. 4 | CHANGED=$(git diff --name-only HEAD~1 | tr '\n' ' ') 5 | 6 | # Skip files that don't exist 7 | # (have been git rm'd) 8 | CHECK="" 9 | for FILE in $CHANGED; do 10 | if [ -f "$FILE" ]; then 11 | CHECK="$CHECK $FILE" 12 | fi 13 | done 14 | 15 | diff -u --from-file /dev/null $CHECK | flake8 --diff 16 | 17 | -------------------------------------------------------------------------------- /tools/flake8wrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # A simple wrapper around flake8 which makes it possible 4 | # to ask it to only verify files changed in the current 5 | # git HEAD patch. 6 | # 7 | # Intended to be invoked via tox: 8 | # 9 | # tox -epep8 -- -HEAD 10 | # 11 | 12 | if test "x$1" = "x-HEAD" ; then 13 | shift 14 | files=$(git diff --name-only HEAD~1 | tr '\n' ' ') 15 | echo "Running flake8 on ${files}" 16 | diff -u --from-file /dev/null ${files} | flake8 --max-complexity 10 --diff "$@" 17 | else 18 | echo "Running flake8 on all files" 19 | exec flake8 --max-complexity 10 "$@" 20 | fi 21 | -------------------------------------------------------------------------------- /tools/gen-config: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | oslo-config-generator --config-file=tools/zun-config-generator.conf 4 | -------------------------------------------------------------------------------- /tools/run_functional.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | FUNC_TEST_DIR=$(dirname $0)/../zunclient/tests/functional/ 4 | CONFIG_FILE=$FUNC_TEST_DIR/test.conf 5 | 6 | if [[ -n "$OS_AUTH_TOKEN" ]] && [[ -n "$ZUN_URL" ]]; then 7 | cat <$CONFIG_FILE 8 | [functional] 9 | api_version = 1 10 | auth_strategy=noauth 11 | os_auth_token=$OS_AUTH_TOKEN 12 | zun_url=$ZUN_URL 13 | END 14 | else 15 | cat <$CONFIG_FILE 16 | [functional] 17 | api_version = 1 18 | os_auth_url=$OS_AUTH_URL 19 | os_identity_api_version = $OS_IDENTITY_API_VERSION 20 | os_username=$OS_USERNAME 21 | os_password=$OS_PASSWORD 22 | os_project_name=$OS_PROJECT_NAME 23 | os_user_domain_id=$OS_USER_DOMAIN_ID 24 | os_project_domain_id=$OS_PROJECT_DOMAIN_ID 25 | os_service_type=container 26 | os_endpoint_type=public 27 | END 28 | fi 29 | tox -e functional -- --concurrency=1 30 | -------------------------------------------------------------------------------- /tools/zun.bash_completion: -------------------------------------------------------------------------------- 1 | _zun_opts="" # lazy init 2 | _zun_flags="" # lazy init 3 | _zun_opts_exp="" # lazy init 4 | _zun() 5 | { 6 | local cur prev nbc cflags 7 | COMPREPLY=() 8 | cur="${COMP_WORDS[COMP_CWORD]}" 9 | prev="${COMP_WORDS[COMP_CWORD-1]}" 10 | 11 | if [ "x$_zun_opts" == "x" ] ; then 12 | nbc="`zun bash-completion | sed -e "s/ *-h */ /" -e "s/ *-i */ /"`" 13 | _zun_opts="`echo "$nbc" | sed -e "s/--[a-z0-9_-]*//g" -e "s/ */ /g"`" 14 | _zun_flags="`echo " $nbc" | sed -e "s/ [^-][^-][a-z0-9_-]*//g" -e "s/ */ /g"`" 15 | _zun_opts_exp="`echo "$_zun_opts" | tr ' ' '|'`" 16 | fi 17 | 18 | if [[ " ${COMP_WORDS[@]} " =~ " "($_zun_opts_exp)" " && "$prev" != "help" ]] ; then 19 | COMPLETION_CACHE=~/.zunclient/*/*-cache 20 | cflags="$_zun_flags "$(cat $COMPLETION_CACHE 2> /dev/null | tr '\n' ' ') 21 | COMPREPLY=($(compgen -W "${cflags}" -- ${cur})) 22 | else 23 | COMPREPLY=($(compgen -W "${_zun_opts}" -- ${cur})) 24 | fi 25 | return 0 26 | } 27 | complete -F _zun zun 28 | -------------------------------------------------------------------------------- /tools/zun.zsh_completion: -------------------------------------------------------------------------------- 1 | #compdef zun 2 | 3 | local -a nbc _zun_opts _zun_flags _zun_opts_exp cur prev 4 | 5 | nbc=(${(ps: :)$(_call_program options "$service bash-completion" 2>/dev/null)}) 6 | _zun_opts=(${nbc:#-*}) 7 | _zun_flags=(${(M)nbc:#-*}) 8 | _zun_opt_exp=${${nbc:#-*}// /|} 9 | cur=$words[CURRENT] 10 | prev=$words[(( CURRENT - 1 ))] 11 | 12 | _checkcomp(){ 13 | for word in $words[@]; do 14 | if [[ -n ${_zun_opts[(r)$word]} ]]; then 15 | return 0 16 | fi 17 | done 18 | return 1 19 | } 20 | 21 | echo $_zun_opts[@] |grep --color zun 22 | if [[ "$prev" != "help" ]] && _checkcomp; then 23 | COMPLETION_CACHE=(~/.zunclient/*/*-cache) 24 | cflags=($_zun_flags[@] ${(ps: :)$(cat $COMPLETION_CACHE 2>/dev/null)}) 25 | compadd "$@" -d $cflags[@] 26 | else 27 | compadd "$@" -d $_zun_opts[@] 28 | fi 29 | 30 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | minversion = 3.1.1 3 | envlist = py3,pep8 4 | ignore_basepython_conflict = True 5 | 6 | [testenv] 7 | basepython = python3 8 | usedevelop = True 9 | install_command = pip install -U {opts} {packages} 10 | allowlist_externals = bash 11 | find 12 | rm 13 | setenv = 14 | VIRTUAL_ENV={envdir} 15 | 16 | deps = -r{toxinidir}/requirements.txt 17 | -r{toxinidir}/test-requirements.txt 18 | 19 | commands = 20 | find . -type f -name "*.py[c|o]" -delete 21 | stestr run {posargs} 22 | stestr slowest 23 | 24 | [testenv:bandit] 25 | deps = -r{toxinidir}/test-requirements.txt 26 | commands = bandit -r zunclient -x tests -n5 -ll 27 | 28 | [testenv:debug] 29 | commands = oslo_debug_helper -t zunclient/tests {posargs} 30 | 31 | [testenv:docs] 32 | deps = 33 | -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} 34 | -r{toxinidir}/doc/requirements.txt 35 | commands = 36 | sphinx-build -W -b html doc/source doc/build/html 37 | 38 | [testenv:pdf-docs] 39 | basepython = python3 40 | envdir = {toxworkdir}/docs 41 | deps = {[testenv:docs]deps} 42 | allowlist_externals = 43 | make 44 | commands = 45 | sphinx-build -W -b latex doc/source doc/build/pdf 46 | make -C doc/build/pdf 47 | 48 | [testenv:pep8] 49 | commands = 50 | flake8 51 | # Run security linter 52 | bandit -r zunclient -x tests -n5 -ll 53 | 54 | [testenv:releasenotes] 55 | deps = 56 | -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} 57 | -r{toxinidir}/doc/requirements.txt 58 | commands = 59 | sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html 60 | 61 | [testenv:venv] 62 | commands = {posargs} 63 | 64 | [testenv:cover] 65 | setenv = 66 | {[testenv]setenv} 67 | PYTHON=coverage run --source zunclient --parallel-mode 68 | commands = 69 | stestr run {posargs} 70 | coverage combine 71 | coverage html -d cover 72 | coverage xml -o cover/coverage.xml 73 | coverage report 74 | 75 | [flake8] 76 | # E123, E125 skipped as they are invalid PEP-8. 77 | 78 | show-source = True 79 | ignore = E123,E125,W503,W504 80 | builtins = _ 81 | exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build 82 | 83 | [hacking] 84 | import_exceptions = zunclient._i18n 85 | 86 | [testenv:functional] 87 | commands = 88 | find . -type f -name "*.py[c|o]" -delete 89 | stestr run {posargs} 90 | setenv = 91 | {[testenv]setenv} 92 | OS_TEST_PATH = ./zunclient/tests/functional/osc/v1 93 | # The OS_CACERT environment variable should be passed to the test 94 | # environments to specify a CA bundle file to use in verifying a 95 | # TLS (https) server certificate. 96 | passenv = OS_* 97 | -------------------------------------------------------------------------------- /zunclient/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 3 | # not use this file except in compliance with the License. You may obtain 4 | # a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 10 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 11 | # License for the specific language governing permissions and limitations 12 | # under the License. 13 | 14 | import pbr.version 15 | 16 | 17 | __version__ = pbr.version.VersionInfo( 18 | 'python-zunclient').version_string() 19 | -------------------------------------------------------------------------------- /zunclient/client.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 IBM Corp. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | import warnings 16 | 17 | from oslo_utils import importutils 18 | 19 | from zunclient import api_versions 20 | 21 | osprofiler_profiler = importutils.try_import("osprofiler.profiler") 22 | 23 | 24 | def _get_client_class_and_version(version): 25 | if not isinstance(version, api_versions.APIVersion): 26 | version = api_versions.get_api_version(version) 27 | else: 28 | api_versions.check_major_version(version) 29 | return version, importutils.import_class( 30 | 'zunclient.v%s.client.Client' % version.ver_major) 31 | 32 | 33 | def _check_arguments(kwargs, release, deprecated_name, right_name=None): 34 | """Process deprecation of arguments. 35 | 36 | Check presence of deprecated argument in kwargs, prints proper warning 37 | message, renames key to right one it needed. 38 | """ 39 | if deprecated_name in kwargs: 40 | if right_name: 41 | if right_name in kwargs: 42 | msg = ('The %(old)s argument is deprecated in %(release)s' 43 | 'and its use may result in errors in future releases.' 44 | 'As %(new)s is provided, the %(old)s argument will ' 45 | 'be ignored.') % {'old': deprecated_name, 46 | 'release': release, 47 | 'new': right_name} 48 | kwargs.pop(deprecated_name) 49 | else: 50 | msg = ('The %(old)s argument is deprecated in %(release)s ' 51 | 'and its use may result in errors in future releases. ' 52 | 'Use %(new)s instead.') % {'old': deprecated_name, 53 | 'release': release, 54 | 'new': right_name} 55 | kwargs[right_name] = kwargs.pop(deprecated_name) 56 | else: 57 | msg = ('The %(old)s argument is deprecated in %(release)s ' 58 | 'and its use may result in errors in future ' 59 | 'releases') % {'old': deprecated_name, 60 | 'release': release} 61 | # NOTE(kiennt): just ignore it 62 | kwargs.pop(deprecated_name) 63 | warnings.warn(msg) 64 | 65 | 66 | def Client(version='1', username=None, auth_url=None, **kwargs): 67 | """Initialize client objects based on given version""" 68 | _check_arguments(kwargs, 'Queens', 'api_key', right_name='password') 69 | # NOTE: OpenStack projects use 2 vars with one meaning: `endpoint_type` 70 | # and `interface`. `endpoint_type` is an old name which was used by 71 | # most OpenStack clients. Later it was replaced by `interface` in 72 | # keystone and later some other clients switched to new var name too. 73 | _check_arguments(kwargs, 'Queens', 'endpoint_type', 74 | right_name='interface') 75 | _check_arguments(kwargs, 'Queens', 'zun_url', 76 | right_name='endpoint_override') 77 | _check_arguments(kwargs, 'Queens', 'tenant_name', 78 | right_name='project_name') 79 | _check_arguments(kwargs, 'Queens', 'tenant_id', right_name='project_id') 80 | 81 | profile = kwargs.pop('profile', None) 82 | if osprofiler_profiler and profile: 83 | # Initialize the root of the future trace: the created trace ID 84 | # will be used as the very first parent to which all related 85 | # traces will be bound to. The given HMAC key must correspond to 86 | # the one set in zun-api zun.conf, otherwise the latter 87 | # will fail to check the request signature and will skip 88 | # initialization of osprofiler on the server side. 89 | osprofiler_profiler.init(profile) 90 | 91 | api_version, client_class = _get_client_class_and_version(version) 92 | if api_version.is_latest(): 93 | c = client_class(api_version=api_versions.APIVersion("1.1"), 94 | auth_url=auth_url, 95 | username=username, 96 | **kwargs) 97 | api_version = api_versions.discover_version(c, api_version) 98 | 99 | return client_class(api_version=api_version, 100 | auth_url=auth_url, 101 | username=username, 102 | **kwargs) 103 | -------------------------------------------------------------------------------- /zunclient/common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/python-zunclient/eb6806a8682914171dd17a44c6b80e78f7c6b0bc/zunclient/common/__init__.py -------------------------------------------------------------------------------- /zunclient/common/apiclient/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/python-zunclient/eb6806a8682914171dd17a44c6b80e78f7c6b0bc/zunclient/common/apiclient/__init__.py -------------------------------------------------------------------------------- /zunclient/common/apiclient/auth.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 OpenStack Foundation 2 | # Copyright 2013 Spanish National Research Council. 3 | # All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | # E0202: An attribute inherited from %s hide this method 18 | # pylint: disable=E0202 19 | 20 | import abc 21 | import argparse 22 | import os 23 | 24 | from zunclient.common.apiclient import exceptions 25 | 26 | 27 | _discovered_plugins = {} 28 | 29 | 30 | def load_auth_system_opts(parser): 31 | """Load options needed by the available auth-systems into a parser. 32 | 33 | This function will try to populate the parser with options from the 34 | available plugins. 35 | """ 36 | group = parser.add_argument_group("Common auth options") 37 | BaseAuthPlugin.add_common_opts(group) 38 | for name, auth_plugin in _discovered_plugins.items(): 39 | group = parser.add_argument_group( 40 | "Auth-system '%s' options" % name, 41 | conflict_handler="resolve") 42 | auth_plugin.add_opts(group) 43 | 44 | 45 | def load_plugin(auth_system): 46 | try: 47 | plugin_class = _discovered_plugins[auth_system] 48 | except KeyError: 49 | raise exceptions.AuthSystemNotFound(auth_system) 50 | return plugin_class(auth_system=auth_system) 51 | 52 | 53 | class BaseAuthPlugin(object, metaclass=abc.ABCMeta): 54 | """Base class for authentication plugins. 55 | 56 | An authentication plugin needs to override at least the authenticate 57 | method to be a valid plugin. 58 | """ 59 | 60 | auth_system = None 61 | opt_names = [] 62 | common_opt_names = [ 63 | "auth_system", 64 | "username", 65 | "password", 66 | "auth_url", 67 | ] 68 | 69 | def __init__(self, auth_system=None, **kwargs): 70 | self.auth_system = auth_system or self.auth_system 71 | self.opts = dict((name, kwargs.get(name)) 72 | for name in self.opt_names) 73 | 74 | @staticmethod 75 | def _parser_add_opt(parser, opt): 76 | """Add an option to parser in two variants. 77 | 78 | :param opt: option name (with underscores) 79 | """ 80 | dashed_opt = opt.replace("_", "-") 81 | env_var = "OS_%s" % opt.upper() 82 | arg_default = os.environ.get(env_var, "") 83 | arg_help = "Defaults to env[%s]." % env_var 84 | parser.add_argument( 85 | "--os-%s" % dashed_opt, 86 | metavar="<%s>" % dashed_opt, 87 | default=arg_default, 88 | help=arg_help) 89 | parser.add_argument( 90 | "--os_%s" % opt, 91 | metavar="<%s>" % dashed_opt, 92 | help=argparse.SUPPRESS) 93 | 94 | @classmethod 95 | def add_opts(cls, parser): 96 | """Populate the parser with the options for this plugin.""" 97 | for opt in cls.opt_names: 98 | # use `BaseAuthPlugin.common_opt_names` since it is never 99 | # changed in child classes 100 | if opt not in BaseAuthPlugin.common_opt_names: 101 | cls._parser_add_opt(parser, opt) 102 | 103 | @classmethod 104 | def add_common_opts(cls, parser): 105 | """Add options that are common for several plugins.""" 106 | for opt in cls.common_opt_names: 107 | cls._parser_add_opt(parser, opt) 108 | 109 | @staticmethod 110 | def get_opt(opt_name, args): 111 | """Return option name and value. 112 | 113 | :param opt_name: name of the option, e.g., "username" 114 | :param args: parsed arguments 115 | """ 116 | return (opt_name, getattr(args, "os_%s" % opt_name, None)) 117 | 118 | def parse_opts(self, args): 119 | """Parse the actual auth-system options if any. 120 | 121 | This method is expected to populate the attribute `self.opts` with a 122 | dict containing the options and values needed to make authentication. 123 | """ 124 | self.opts.update(dict(self.get_opt(opt_name, args) 125 | for opt_name in self.opt_names)) 126 | 127 | def authenticate(self, http_client): 128 | """Authenticate using plugin defined method. 129 | 130 | The method usually analyses `self.opts` and performs 131 | a request to authentication server. 132 | 133 | :param http_client: client object that needs authentication 134 | :type http_client: HTTPClient 135 | :raises: AuthorizationFailure 136 | """ 137 | self.sufficient_options() 138 | self._do_authenticate(http_client) 139 | 140 | @abc.abstractmethod 141 | def _do_authenticate(self, http_client): 142 | """Protected method for authentication.""" 143 | 144 | def sufficient_options(self): 145 | """Check if all required options are present. 146 | 147 | :raises: AuthPluginOptionsMissing 148 | """ 149 | missing = [opt 150 | for opt in self.opt_names 151 | if not self.opts.get(opt)] 152 | if missing: 153 | raise exceptions.AuthPluginOptionsMissing(missing) 154 | -------------------------------------------------------------------------------- /zunclient/common/apiclient/base.py: -------------------------------------------------------------------------------- 1 | # Copyright 2010 Jacob Kaplan-Moss 2 | # Copyright 2011 OpenStack Foundation 3 | # Copyright 2012 Grid Dynamics 4 | # Copyright 2013 OpenStack Foundation 5 | # All Rights Reserved. 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 8 | # not use this file except in compliance with the License. You may obtain 9 | # a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 16 | # License for the specific language governing permissions and limitations 17 | # under the License. 18 | 19 | """ 20 | Base utilities to build API operation managers and objects on top of. 21 | """ 22 | 23 | import copy 24 | 25 | 26 | class Resource(object): 27 | """Base class for OpenStack resources (tenant, user, etc.). 28 | 29 | This is pretty much just a bag for attributes. 30 | """ 31 | 32 | def __init__(self, manager, info, loaded=False): 33 | """Populate and bind to a manager. 34 | 35 | :param manager: BaseManager object 36 | :param info: dictionary representing resource attributes 37 | :param loaded: prevent lazy-loading if set to True 38 | """ 39 | self.manager = manager 40 | self._info = info 41 | self._add_details(info) 42 | self._loaded = loaded 43 | 44 | def __repr__(self): 45 | reprkeys = sorted(k 46 | for k in self.__dict__.keys() 47 | if k[0] != '_' and k != 'manager') 48 | info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys) 49 | return "<%s %s>" % (self.__class__.__name__, info) 50 | 51 | def _add_details(self, info): 52 | for (k, v) in info.items(): 53 | try: 54 | setattr(self, k, v) 55 | self._info[k] = v 56 | except AttributeError: 57 | # In this case we already defined the attribute on the class 58 | pass 59 | 60 | def __getattr__(self, k): 61 | if k not in self.__dict__: 62 | # NOTE(bcwaldon): disallow lazy-loading if already loaded once 63 | if not self.is_loaded(): 64 | self.get() 65 | return self.__getattr__(k) 66 | 67 | raise AttributeError(k) 68 | else: 69 | return self.__dict__[k] 70 | 71 | def get(self): 72 | """Support for lazy loading details. 73 | 74 | Some clients, such as novaclient have the option to lazy load the 75 | details, details which can be loaded with this function. 76 | """ 77 | # set_loaded() first ... so if we have to bail, we know we tried. 78 | self.set_loaded(True) 79 | if not hasattr(self.manager, 'get'): 80 | return 81 | 82 | new = self.manager.get(self.id) 83 | if new: 84 | self._add_details(new._info) 85 | self._add_details( 86 | {'x_request_id': self.manager.client.last_request_id}) 87 | 88 | def __eq__(self, other): 89 | if not isinstance(other, Resource): 90 | return NotImplemented 91 | # two resources of different types are not equal 92 | if not isinstance(other, self.__class__): 93 | return False 94 | if hasattr(self, 'id') and hasattr(other, 'id'): 95 | return self.id == other.id 96 | return self._info == other._info 97 | 98 | def is_loaded(self): 99 | return self._loaded 100 | 101 | def set_loaded(self, val): 102 | self._loaded = val 103 | 104 | def to_dict(self): 105 | return copy.deepcopy(self._info) 106 | -------------------------------------------------------------------------------- /zunclient/common/base.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2012 OpenStack LLC. 3 | # All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | """ 18 | Base utilities to build API operation managers and objects on top of. 19 | """ 20 | 21 | import copy 22 | 23 | from urllib import parse as urlparse 24 | 25 | from zunclient.common.apiclient import base 26 | 27 | 28 | def getid(obj): 29 | """Wrapper to get object's ID. 30 | 31 | Abstracts the common pattern of allowing both an object or an 32 | object's ID (UUID) as a parameter when dealing with relationships. 33 | """ 34 | return getattr(obj, 'id', obj) 35 | 36 | 37 | class Manager(object): 38 | """Provides CRUD operations with a particular API.""" 39 | resource_class = None 40 | 41 | def __init__(self, api): 42 | self.api = api 43 | 44 | @property 45 | def api_version(self): 46 | return self.api.api_version 47 | 48 | def _create(self, url, body): 49 | resp, body = self.api.json_request('POST', url, body=body) 50 | if body: 51 | return self.resource_class(self, body) 52 | 53 | def _format_body_data(self, body, response_key): 54 | if response_key: 55 | try: 56 | data = body[response_key] 57 | except KeyError: 58 | return [] 59 | else: 60 | data = body 61 | 62 | if not isinstance(data, list): 63 | data = [data] 64 | 65 | return data 66 | 67 | def _list_pagination(self, url, response_key=None, obj_class=None, 68 | limit=None): 69 | """Retrieve a list of items. 70 | 71 | The Zun API is configured to return a maximum number of 72 | items per request, (FIXME: see Zun's api.max_limit option). This 73 | iterates over the 'next' link (pagination) in the responses, 74 | to get the number of items specified by 'limit'. If 'limit' 75 | is None this function will continue pagination until there are 76 | no more values to be returned. 77 | 78 | :param url: a partial URL, e.g. '/nodes' 79 | :param response_key: the key to be looked up in response 80 | dictionary, e.g. 'nodes' 81 | :param obj_class: class for constructing the returned objects. 82 | :param limit: maximum number of items to return. If None returns 83 | everything. 84 | 85 | """ 86 | if obj_class is None: 87 | obj_class = self.resource_class 88 | 89 | if limit is not None: 90 | limit = int(limit) 91 | 92 | object_list = [] 93 | object_count = 0 94 | limit_reached = False 95 | while url: 96 | resp, body = self.api.json_request('GET', url) 97 | data = self._format_body_data(body, response_key) 98 | for obj in data: 99 | object_list.append(obj_class(self, obj, loaded=True)) 100 | object_count += 1 101 | if limit and object_count >= limit: 102 | # break the for loop 103 | limit_reached = True 104 | break 105 | 106 | # break the while loop and return 107 | if limit_reached: 108 | break 109 | 110 | url = body.get('next') 111 | if url: 112 | # NOTE(lucasagomes): We need to edit the URL to remove 113 | # the scheme and netloc 114 | url_parts = list(urlparse.urlparse(url)) 115 | url_parts[0] = url_parts[1] = '' 116 | url = urlparse.urlunparse(url_parts) 117 | 118 | return object_list 119 | 120 | def _list(self, url, response_key=None, obj_class=None, body=None, 121 | qparams=None): 122 | if qparams: 123 | if '?' in url: 124 | url = "%s&%s" % (url, urlparse.urlencode(qparams)) 125 | else: 126 | url = "%s?%s" % (url, urlparse.urlencode(qparams)) 127 | 128 | resp, body = self.api.json_request('GET', url) 129 | 130 | if obj_class is None: 131 | obj_class = self.resource_class 132 | 133 | data = self._format_body_data(body, response_key) 134 | return [obj_class(self, res, loaded=True) for res in data if res] 135 | 136 | def _update(self, url, body, method='PATCH', response_key=None): 137 | resp, body = self.api.json_request(method, url, body=body) 138 | # PATCH/PUT requests may not return a body 139 | if body: 140 | return self.resource_class(self, body) 141 | 142 | def _delete(self, url, qparams=None): 143 | if qparams: 144 | url = "%s?%s" % (url, urlparse.urlencode(qparams)) 145 | self.api.raw_request('DELETE', url) 146 | 147 | def _search(self, url, qparams=None, response_key=None, obj_class=None, 148 | body=None): 149 | if qparams: 150 | url = "%s?%s" % (url, urlparse.urlencode(qparams)) 151 | 152 | resp, body = self.api.json_request('GET', url, body=body) 153 | data = self._format_body_data(body, response_key) 154 | if obj_class is None: 155 | obj_class = self.resource_class 156 | return [obj_class(self, res, loaded=True) for res in data if res] 157 | 158 | 159 | class Resource(base.Resource): 160 | """Represents a particular instance of an object (tenant, user, etc). 161 | 162 | This is pretty much just a bag for attributes. 163 | """ 164 | 165 | def to_dict(self): 166 | return copy.deepcopy(self._info) 167 | -------------------------------------------------------------------------------- /zunclient/common/template_format.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Arm Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | import yaml 16 | 17 | from oslo_serialization import jsonutils 18 | 19 | 20 | if hasattr(yaml, 'CSafeDumper'): 21 | yaml_dumper_base = yaml.CSafeDumper 22 | else: 23 | yaml_dumper_base = yaml.SafeDumper 24 | 25 | 26 | # We create custom class to not overriden the default yaml behavior 27 | class yaml_loader(yaml.SafeLoader): 28 | pass 29 | 30 | 31 | class yaml_dumper(yaml_dumper_base): 32 | pass 33 | 34 | 35 | def _construct_yaml_str(self, node): 36 | # Override the default string handling function 37 | # to always return unicode objects 38 | return self.construct_scalar(node) 39 | 40 | 41 | yaml_loader.add_constructor(u'tag:yaml.org,2002:str', _construct_yaml_str) 42 | # Unquoted dates like 2013-05-23 in yaml files get loaded as objects of type 43 | # datetime.data which causes problems in API layer when being processed by 44 | # openstack.common.jsonutils. Therefore, make unicode string out of timestamps 45 | # until jsonutils can handle dates. 46 | yaml_loader.add_constructor(u'tag:yaml.org,2002:timestamp', 47 | _construct_yaml_str) 48 | 49 | 50 | def parse(tmpl_str): 51 | """Takes a string and returns a dict containing the parsed structure. 52 | 53 | This includes determination of whether the string is using the 54 | JSON or YAML format. 55 | """ 56 | # strip any whitespace before the check 57 | tmpl_str = tmpl_str.strip() 58 | if tmpl_str.startswith('{'): 59 | tpl = jsonutils.loads(tmpl_str) 60 | else: 61 | try: 62 | tpl = yaml.safe_load(tmpl_str) 63 | except yaml.YAMLError as yea: 64 | raise ValueError(yea) 65 | else: 66 | if tpl is None: 67 | tpl = {} 68 | 69 | return tpl 70 | -------------------------------------------------------------------------------- /zunclient/common/template_utils.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Arm Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | from oslo_serialization import jsonutils 16 | from urllib import parse 17 | from urllib import request 18 | 19 | from zunclient.common import template_format 20 | from zunclient.common import utils 21 | from zunclient import exceptions 22 | from zunclient.i18n import _ 23 | 24 | 25 | def get_template_contents(template_file=None, template_url=None, 26 | files=None): 27 | 28 | # Transform a bare file path to a file:// URL. 29 | if template_file: # nosec 30 | template_url = utils.normalise_file_path_to_url(template_file) 31 | tpl = request.urlopen(template_url).read() # nosec 32 | else: 33 | raise exceptions.CommandErrorException(_('Need to specify exactly ' 34 | 'one of %(arg1)s, %(arg2)s ' 35 | 'or %(arg3)s') % 36 | {'arg1': '--template-file', 37 | 'arg2': '--template-url'}) 38 | 39 | if not tpl: 40 | raise exceptions.CommandErrorException(_('Could not fetch ' 41 | 'template from %s') % 42 | template_url) 43 | 44 | try: 45 | if isinstance(tpl, bytes): 46 | tpl = tpl.decode('utf-8') 47 | template = template_format.parse(tpl) 48 | except ValueError as e: 49 | raise exceptions.CommandErrorException(_('Error parsing template ' 50 | '%(url)s %(error)s') % 51 | {'url': template_url, 52 | 'error': e}) 53 | return template 54 | 55 | 56 | def is_template(file_content): 57 | try: 58 | if isinstance(file_content, bytes): 59 | file_content = file_content.decode('utf-8') 60 | template_format.parse(file_content) 61 | except (ValueError, TypeError): 62 | return False 63 | return True 64 | 65 | 66 | def get_file_contents(from_data, files, base_url=None, 67 | ignore_if=None): 68 | 69 | if isinstance(from_data, dict): 70 | for key, value in from_data.items(): 71 | if ignore_if and ignore_if(key, value): 72 | continue 73 | 74 | if base_url and not base_url.endswith('/'): 75 | base_url = base_url + '/' 76 | 77 | str_url = parse.urljoin(base_url, value) 78 | if str_url not in files: 79 | file_content = utils.read_url_content(str_url) 80 | if is_template(file_content): 81 | template = get_template_contents( 82 | template_url=str_url, files=files)[1] 83 | file_content = jsonutils.dumps(template) 84 | files[str_url] = file_content 85 | # replace the data value with the normalised absolute URL 86 | from_data[key] = str_url 87 | -------------------------------------------------------------------------------- /zunclient/common/websocketclient/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/python-zunclient/eb6806a8682914171dd17a44c6b80e78f7c6b0bc/zunclient/common/websocketclient/__init__.py -------------------------------------------------------------------------------- /zunclient/common/websocketclient/exceptions.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 2 | # The Cloudscaling Group, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | # use this file except in compliance with the License. You may obtain a copy 6 | # of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | 17 | class ContainerWebSocketException(Exception): 18 | """base for all ContainerWebSocket interactive generated exceptions""" 19 | def __init__(self, wrapped=None, message=None): 20 | self.wrapped = wrapped 21 | if message: 22 | self.message = message 23 | if wrapped: 24 | formatted_string = "%s:%s" % (self.message, str(self.wrapped)) 25 | else: 26 | formatted_string = "%s" % self.message 27 | super(ContainerWebSocketException, self).__init__(formatted_string) 28 | 29 | 30 | class UserExit(ContainerWebSocketException): 31 | message = "User requested disconnect the container" 32 | 33 | 34 | class Disconnected(ContainerWebSocketException): 35 | message = "Remote host closed connection" 36 | 37 | 38 | class ConnectionFailed(ContainerWebSocketException): 39 | message = "Failed to connect to remote host" 40 | 41 | 42 | class InvalidWebSocketLink(ContainerWebSocketException): 43 | message = "Invalid websocket link when attach container" 44 | 45 | 46 | class ContainerFailtoStart(ContainerWebSocketException): 47 | message = "Container fail to start" 48 | 49 | 50 | class ContainerStateError(ContainerWebSocketException): 51 | message = "Container state is error, can not attach container" 52 | -------------------------------------------------------------------------------- /zunclient/exceptions.py: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 3 | # not use this file except in compliance with the License. You may obtain 4 | # a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 10 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 11 | # License for the specific language governing permissions and limitations 12 | # under the License. 13 | 14 | from zunclient.common.apiclient import exceptions 15 | from zunclient.common.apiclient.exceptions import * # noqa 16 | 17 | 18 | # NOTE(akurilin): This alias is left here since v.0.1.3 to support backwards 19 | # compatibility. 20 | InvalidEndpoint = exceptions.EndpointException 21 | CommunicationError = exceptions.ConnectionRefused 22 | HTTPBadRequest = exceptions.BadRequest 23 | HTTPInternalServerError = exceptions.InternalServerError 24 | HTTPNotFound = exceptions.NotFound 25 | HTTPServiceUnavailable = exceptions.ServiceUnavailable 26 | CommandErrorException = exceptions.CommandError 27 | 28 | 29 | class AmbiguousAuthSystem(exceptions.ClientException): 30 | """Could not obtain token and endpoint using provided credentials.""" 31 | pass 32 | 33 | 34 | # Alias for backwards compatibility 35 | AmbigiousAuthSystem = AmbiguousAuthSystem 36 | 37 | 38 | class InvalidAttribute(exceptions.ClientException): 39 | pass 40 | 41 | 42 | def from_response(response, message=None, traceback=None, method=None, 43 | url=None): 44 | """Return an HttpError instance based on response from httplib/requests.""" 45 | 46 | error_body = {} 47 | if message: 48 | error_body['message'] = message 49 | if traceback: 50 | error_body['details'] = traceback 51 | 52 | if hasattr(response, 'status') and not hasattr(response, 'status_code'): 53 | # NOTE(akurilin): These modifications around response object give 54 | # ability to get all necessary information in method `from_response` 55 | # from common code, which expecting response object from `requests` 56 | # library instead of object from `httplib/httplib2` library. 57 | response.status_code = response.status 58 | response.headers = { 59 | 'Content-Type': response.getheader('content-type', "")} 60 | 61 | if hasattr(response, 'status_code'): 62 | # NOTE(hongbin): This allows SessionClient to handle faultstring. 63 | response.json = lambda: {'error': error_body} 64 | 65 | if (response.headers.get('Content-Type', '').startswith('text/') and 66 | not hasattr(response, 'text')): 67 | # NOTE(clif_h): There seems to be a case in the 68 | # common.apiclient.exceptions module where if the 69 | # content-type of the response is text/* then it expects 70 | # the response to have a 'text' attribute, but that 71 | # doesn't always seem to necessarily be the case. 72 | # This is to work around that problem. 73 | response.text = '' 74 | 75 | return exceptions.from_response(response, method, url) 76 | -------------------------------------------------------------------------------- /zunclient/i18n.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | """oslo_i18n integration module for zunclient. 14 | 15 | See https://docs.openstack.org/oslo.i18n/latest/user/usage.html. 16 | 17 | """ 18 | 19 | import oslo_i18n 20 | 21 | 22 | _translators = oslo_i18n.TranslatorFactory(domain='zunclient') 23 | 24 | # The primary translation function using the well-known name "_" 25 | _ = _translators.primary 26 | -------------------------------------------------------------------------------- /zunclient/osc/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/python-zunclient/eb6806a8682914171dd17a44c6b80e78f7c6b0bc/zunclient/osc/__init__.py -------------------------------------------------------------------------------- /zunclient/osc/plugin.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | import argparse 14 | from oslo_log import log as logging 15 | 16 | from osc_lib import utils 17 | 18 | from zunclient import api_versions 19 | 20 | LOG = logging.getLogger(__name__) 21 | 22 | DEFAULT_CONTAINER_API_VERSION = api_versions.DEFAULT_API_VERSION 23 | API_VERSION_OPTION = "os_container_api_version" 24 | API_NAME = "container" 25 | CLIENT_CLASS = 'zunclient.v1.client.Client' 26 | LAST_KNOWN_API_VERSION = int(api_versions.MAX_API_VERSION.split('.')[1]) 27 | API_VERSIONS = { 28 | '1.%d' % i: CLIENT_CLASS 29 | for i in range(1, LAST_KNOWN_API_VERSION + 1) 30 | } 31 | API_VERSIONS['1'] = CLIENT_CLASS 32 | 33 | 34 | def make_client(instance): 35 | """Returns a zun service client""" 36 | requested_api_version = instance._api_version[API_NAME] 37 | 38 | zun_client = utils.get_client_class( 39 | API_NAME, 40 | requested_api_version, 41 | API_VERSIONS) 42 | LOG.debug("Instantiating zun client: {0}".format( 43 | zun_client)) 44 | 45 | api_version = api_versions.get_api_version(requested_api_version) 46 | if api_version.is_latest(): 47 | client = zun_client( 48 | region_name=instance._region_name, 49 | session=instance.session, 50 | service_type='container', 51 | api_version=api_versions.APIVersion("1.1"), 52 | ) 53 | api_version = api_versions.discover_version(client, api_version) 54 | 55 | client = zun_client( 56 | region_name=instance._region_name, 57 | session=instance.session, 58 | service_type='container', 59 | api_version=api_version, 60 | ) 61 | return client 62 | 63 | 64 | def build_option_parser(parser): 65 | """Hook to add global options""" 66 | parser.add_argument( 67 | '--os-container-api-version', 68 | metavar='', 69 | default=_get_environment_version(DEFAULT_CONTAINER_API_VERSION), 70 | action=ReplaceLatestVersion, 71 | choices=sorted( 72 | API_VERSIONS, 73 | key=lambda k: [int(x) for x in k.split('.')]) + ['1.latest'], 74 | help=("Container API version, default={0}" 75 | "(Env:OS_CONTAINER_API_VERSION)").format( 76 | DEFAULT_CONTAINER_API_VERSION)) 77 | return parser 78 | 79 | 80 | def _get_environment_version(default): 81 | env_value = utils.env('OS_CONTAINER_API_VERSION') or default 82 | latest = env_value == '1.latest' 83 | if latest: 84 | # NOTE(hongbin): '1.latest' means enabling negotiation of the 85 | # latest version between server and client but due to how OSC works 86 | # we cannot just add "1.latest" to the list of supported versions. 87 | # Use '1' in this case. 88 | env_value = '1' 89 | return env_value 90 | 91 | 92 | class ReplaceLatestVersion(argparse.Action): 93 | """Replaces `latest` keyword by last known version.""" 94 | 95 | def __call__(self, parser, namespace, values, option_string=None): 96 | latest = values == '1.latest' 97 | if latest: 98 | # NOTE(hongbin): '1.latest' means enabling negotiation of the 99 | # latest version between server and client but due to how OSC works 100 | # we cannot just add "1.latest" to the list of supported versions. 101 | # Use '1' in this case. 102 | values = '1' 103 | setattr(namespace, self.dest, values) 104 | -------------------------------------------------------------------------------- /zunclient/osc/v1/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/python-zunclient/eb6806a8682914171dd17a44c6b80e78f7c6b0bc/zunclient/osc/v1/__init__.py -------------------------------------------------------------------------------- /zunclient/osc/v1/availability_zones.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | from oslo_log import log as logging 14 | 15 | from osc_lib.command import command 16 | from osc_lib import utils 17 | 18 | 19 | def _get_client(obj, parsed_args): 20 | obj.log.debug("take_action(%s)" % parsed_args) 21 | return obj.app.client_manager.container 22 | 23 | 24 | class ListAvailabilityZone(command.Lister): 25 | """List availability zones""" 26 | 27 | log = logging.getLogger(__name__ + ".ListAvailabilityZones") 28 | 29 | def get_parser(self, prog_name): 30 | parser = super(ListAvailabilityZone, self).get_parser(prog_name) 31 | return parser 32 | 33 | def take_action(self, parsed_args): 34 | client = _get_client(self, parsed_args) 35 | zones = client.availability_zones.list() 36 | columns = ('availability_zone',) 37 | return (columns, (utils.get_item_properties(zone, columns) 38 | for zone in zones)) 39 | -------------------------------------------------------------------------------- /zunclient/osc/v1/capsules.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | from osc_lib.command import command 14 | from osc_lib import utils 15 | from oslo_log import log as logging 16 | 17 | from zunclient.common import template_utils 18 | from zunclient.common import utils as zun_utils 19 | from zunclient.i18n import _ 20 | 21 | 22 | def _capsule_columns(capsule): 23 | return capsule._info.keys() 24 | 25 | 26 | def _get_client(obj, parsed_args): 27 | obj.log.debug("take_action(%s)" % parsed_args) 28 | return obj.app.client_manager.container 29 | 30 | 31 | class CreateCapsule(command.ShowOne): 32 | """Create a capsule""" 33 | 34 | log = logging.getLogger(__name__ + ".CreateCapsule") 35 | 36 | def get_parser(self, prog_name): 37 | parser = super(CreateCapsule, self).get_parser(prog_name) 38 | parser.add_argument( 39 | '--file', 40 | metavar='', 41 | required=True, 42 | help='Path to the capsule template file.') 43 | return parser 44 | 45 | def take_action(self, parsed_args): 46 | client = _get_client(self, parsed_args) 47 | opts = {} 48 | opts['template'] = template_utils.get_template_contents( 49 | parsed_args.file) 50 | capsule = client.capsules.create(**opts) 51 | columns = _capsule_columns(capsule) 52 | return columns, utils.get_item_properties(capsule, columns) 53 | 54 | 55 | class ShowCapsule(command.ShowOne): 56 | """Show a capsule""" 57 | 58 | log = logging.getLogger(__name__ + ".ShowCapsule") 59 | 60 | def get_parser(self, prog_name): 61 | parser = super(ShowCapsule, self).get_parser(prog_name) 62 | parser.add_argument( 63 | 'capsule', 64 | metavar='', 65 | help='ID or name of the capsule to show.') 66 | return parser 67 | 68 | def take_action(self, parsed_args): 69 | client = _get_client(self, parsed_args) 70 | opts = {} 71 | opts['id'] = parsed_args.capsule 72 | opts = zun_utils.remove_null_parms(**opts) 73 | capsule = client.capsules.get(**opts) 74 | zun_utils.format_container_addresses(capsule) 75 | columns = _capsule_columns(capsule) 76 | return columns, utils.get_item_properties(capsule, columns) 77 | 78 | 79 | class ListCapsule(command.Lister): 80 | """List available capsules""" 81 | 82 | log = logging.getLogger(__name__ + ".ListCapsule") 83 | 84 | def get_parser(self, prog_name): 85 | parser = super(ListCapsule, self).get_parser(prog_name) 86 | parser.add_argument( 87 | '--all-projects', 88 | action="store_true", 89 | default=False, 90 | help='List capsules in all projects') 91 | parser.add_argument( 92 | '--marker', 93 | metavar='', 94 | help='The last capsule UUID of the previous page; ' 95 | 'displays list of capsules after "marker".') 96 | parser.add_argument( 97 | '--limit', 98 | metavar='', 99 | type=int, 100 | help='Maximum number of capsules to return') 101 | parser.add_argument( 102 | '--sort-key', 103 | metavar='', 104 | help='Column to sort results by') 105 | parser.add_argument( 106 | '--sort-dir', 107 | metavar='', 108 | choices=['desc', 'asc'], 109 | help='Direction to sort. "asc" or "desc".') 110 | return parser 111 | 112 | def take_action(self, parsed_args): 113 | client = _get_client(self, parsed_args) 114 | opts = {} 115 | opts['all_projects'] = parsed_args.all_projects 116 | opts['marker'] = parsed_args.marker 117 | opts['limit'] = parsed_args.limit 118 | opts['sort_key'] = parsed_args.sort_key 119 | opts['sort_dir'] = parsed_args.sort_dir 120 | opts = zun_utils.remove_null_parms(**opts) 121 | capsules = client.capsules.list(**opts) 122 | for capsule in capsules: 123 | zun_utils.format_container_addresses(capsule) 124 | columns = ('uuid', 'name', 'status', 'addresses') 125 | return (columns, (utils.get_item_properties(capsule, columns) 126 | for capsule in capsules)) 127 | 128 | 129 | class DeleteCapsule(command.Command): 130 | """Delete specified capsule(s)""" 131 | 132 | log = logging.getLogger(__name__ + ".DeleteCapsule") 133 | 134 | def get_parser(self, prog_name): 135 | parser = super(DeleteCapsule, self).get_parser(prog_name) 136 | parser.add_argument( 137 | 'capsule', 138 | metavar='', 139 | nargs='+', 140 | help='ID or name of the capsule(s) to delete.') 141 | return parser 142 | 143 | def take_action(self, parsed_args): 144 | client = _get_client(self, parsed_args) 145 | capsules = parsed_args.capsule 146 | for capsule in capsules: 147 | opts = {} 148 | opts['id'] = capsule 149 | try: 150 | client.capsules.delete(**opts) 151 | print(_('Request to delete capsule %s has been accepted.') 152 | % capsule) 153 | except Exception as e: 154 | print("Delete for capsule %(capsule)s failed: %(e)s" % 155 | {'capsule': capsule, 'e': e}) 156 | -------------------------------------------------------------------------------- /zunclient/osc/v1/hosts.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | from oslo_log import log as logging 14 | 15 | from osc_lib.command import command 16 | from osc_lib import utils 17 | 18 | from zunclient.common import utils as zun_utils 19 | 20 | 21 | def _host_columns(host): 22 | return host._info.keys() 23 | 24 | 25 | def _get_client(obj, parsed_args): 26 | obj.log.debug("take_action(%s)" % parsed_args) 27 | return obj.app.client_manager.container 28 | 29 | 30 | class ListHost(command.Lister): 31 | """List available hosts""" 32 | 33 | log = logging.getLogger(__name__ + ".ListHost") 34 | 35 | def get_parser(self, prog_name): 36 | parser = super(ListHost, self).get_parser(prog_name) 37 | parser.add_argument( 38 | '--marker', 39 | metavar='', 40 | default=None, 41 | help='The last host UUID of the previous page; ' 42 | 'displays list of hosts after "marker".') 43 | parser.add_argument( 44 | '--limit', 45 | metavar='', 46 | type=int, 47 | help='Maximum number of hosts to return') 48 | parser.add_argument( 49 | '--sort-key', 50 | metavar='', 51 | help='Column to sort results by') 52 | parser.add_argument( 53 | '--sort-dir', 54 | metavar='', 55 | choices=['desc', 'asc'], 56 | help='Direction to sort. "asc" or "desc".') 57 | return parser 58 | 59 | def take_action(self, parsed_args): 60 | client = _get_client(self, parsed_args) 61 | opts = {} 62 | opts['marker'] = parsed_args.marker 63 | opts['limit'] = parsed_args.limit 64 | opts['sort_key'] = parsed_args.sort_key 65 | opts['sort_dir'] = parsed_args.sort_dir 66 | opts = zun_utils.remove_null_parms(**opts) 67 | hosts = client.hosts.list(**opts) 68 | columns = ('uuid', 'hostname', 'mem_total', 'cpus', 'disk_total') 69 | return (columns, (utils.get_item_properties(host, columns) 70 | for host in hosts)) 71 | 72 | 73 | class ShowHost(command.ShowOne): 74 | """Show a host""" 75 | 76 | log = logging.getLogger(__name__ + ".ShowHost") 77 | 78 | def get_parser(self, prog_name): 79 | parser = super(ShowHost, self).get_parser(prog_name) 80 | parser.add_argument( 81 | 'host', 82 | metavar='', 83 | help='ID or name of the host to show.') 84 | return parser 85 | 86 | def take_action(self, parsed_args): 87 | client = _get_client(self, parsed_args) 88 | host = parsed_args.host 89 | host = client.hosts.get(host) 90 | columns = _host_columns(host) 91 | 92 | return columns, utils.get_item_properties(host, columns) 93 | -------------------------------------------------------------------------------- /zunclient/osc/v1/images.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | from oslo_log import log as logging 14 | 15 | from osc_lib.command import command 16 | from osc_lib import utils 17 | 18 | from zunclient.common import utils as zun_utils 19 | from zunclient.i18n import _ 20 | 21 | 22 | def _image_columns(image): 23 | return image._info.keys() 24 | 25 | 26 | def _get_client(obj, parsed_args): 27 | obj.log.debug("take_action(%s)" % parsed_args) 28 | return obj.app.client_manager.container 29 | 30 | 31 | class ListImage(command.Lister): 32 | """List available images""" 33 | 34 | log = logging.getLogger(__name__ + ".ListImage") 35 | 36 | def get_parser(self, prog_name): 37 | parser = super(ListImage, self).get_parser(prog_name) 38 | parser.add_argument( 39 | '--marker', 40 | metavar='', 41 | default=None, 42 | help='The last image UUID of the previous page; ' 43 | 'displays list of images after "marker".') 44 | parser.add_argument( 45 | '--limit', 46 | metavar='', 47 | type=int, 48 | help='Maximum number of images to return') 49 | parser.add_argument( 50 | '--sort-key', 51 | metavar='', 52 | help='Column to sort results by') 53 | parser.add_argument( 54 | '--sort-dir', 55 | metavar='', 56 | choices=['desc', 'asc'], 57 | help='Direction to sort. "asc" or "desc".') 58 | return parser 59 | 60 | def take_action(self, parsed_args): 61 | client = _get_client(self, parsed_args) 62 | opts = {} 63 | opts['marker'] = parsed_args.marker 64 | opts['limit'] = parsed_args.limit 65 | opts['sort_key'] = parsed_args.sort_key 66 | opts['sort_dir'] = parsed_args.sort_dir 67 | opts = zun_utils.remove_null_parms(**opts) 68 | images = client.images.list(**opts) 69 | columns = ('uuid', 'image_id', 'repo', 'tag', 'size') 70 | return (columns, (utils.get_item_properties(image, columns) 71 | for image in images)) 72 | 73 | 74 | class PullImage(command.ShowOne): 75 | """Pull specified image into a host""" 76 | 77 | log = logging.getLogger(__name__ + ".PullImage") 78 | 79 | def get_parser(self, prog_name): 80 | parser = super(PullImage, self).get_parser(prog_name) 81 | parser.add_argument( 82 | 'image', 83 | metavar='', 84 | help='Name of the image') 85 | parser.add_argument( 86 | 'host', 87 | metavar='', 88 | help='Name or UUID of the host') 89 | return parser 90 | 91 | def take_action(self, parsed_args): 92 | client = _get_client(self, parsed_args) 93 | opts = {} 94 | opts['repo'] = parsed_args.image 95 | opts['host'] = parsed_args.host 96 | image = client.images.create(**opts) 97 | columns = _image_columns(image) 98 | return columns, utils.get_item_properties(image, columns) 99 | 100 | 101 | class SearchImage(command.Lister): 102 | """Search specified image""" 103 | 104 | log = logging.getLogger(__name__ + ".SearchImage") 105 | 106 | def get_parser(self, prog_name): 107 | parser = super(SearchImage, self).get_parser(prog_name) 108 | parser.add_argument( 109 | '--image-driver', 110 | metavar='', 111 | help='Name of the image driver') 112 | parser.add_argument( 113 | 'image_name', 114 | metavar='', 115 | help='Name of the image') 116 | parser.add_argument( 117 | '--exact-match', 118 | default=False, 119 | action='store_true', 120 | help='exact match image name') 121 | return parser 122 | 123 | def take_action(self, parsed_args): 124 | client = _get_client(self, parsed_args) 125 | opts = {} 126 | opts['image_driver'] = parsed_args.image_driver 127 | opts['image'] = parsed_args.image_name 128 | opts['exact_match'] = parsed_args.exact_match 129 | opts = zun_utils.remove_null_parms(**opts) 130 | images = client.images.search_image(**opts) 131 | columns = ('ID', 'Name', 'Tags', 'Status', 'Size', 'Metadata') 132 | return (columns, (utils.get_item_properties(image, columns) 133 | for image in images)) 134 | 135 | 136 | class ShowImage(command.ShowOne): 137 | """Describe a specific image""" 138 | 139 | log = logging.getLogger(__name__ + ".ShowImage") 140 | 141 | def get_parser(self, prog_name): 142 | parser = super(ShowImage, self).get_parser(prog_name) 143 | parser.add_argument( 144 | 'uuid', 145 | metavar='', 146 | help='UUID of image to describe') 147 | return parser 148 | 149 | def take_action(self, parsed_args): 150 | client = _get_client(self, parsed_args) 151 | opts = {} 152 | opts['id'] = parsed_args.uuid 153 | image = client.images.get(**opts) 154 | columns = _image_columns(image) 155 | return columns, utils.get_item_properties(image, columns) 156 | 157 | 158 | class DeleteImage(command.Command): 159 | """Delete specified image from a host""" 160 | 161 | log = logging.getLogger(__name__ + ".DeleteImage") 162 | 163 | def get_parser(self, prog_name): 164 | parser = super(DeleteImage, self).get_parser(prog_name) 165 | parser.add_argument( 166 | 'uuid', 167 | metavar='', 168 | help='UUID of image to describe') 169 | return parser 170 | 171 | def take_action(self, parsed_args): 172 | client = _get_client(self, parsed_args) 173 | opts = {} 174 | opts['image_id'] = parsed_args.uuid 175 | try: 176 | client.images.delete(**opts) 177 | print(_('Request to delete image %s has been accepted.') 178 | % opts['image_id']) 179 | except Exception as e: 180 | print("Delete for image %(image)s failed: %(e)s" % 181 | {'image': opts['image_id'], 'e': e}) 182 | -------------------------------------------------------------------------------- /zunclient/osc/v1/quota_classes.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | from osc_lib.command import command 14 | from osc_lib import utils 15 | from oslo_log import log as logging 16 | 17 | 18 | def _quota_class_columns(quota_class): 19 | return quota_class._info.keys() 20 | 21 | 22 | def _get_client(obj, parsed_args): 23 | obj.log.debug("take_action(%s)" % parsed_args) 24 | return obj.app.client_manager.container 25 | 26 | 27 | class UpdateQuotaClass(command.ShowOne): 28 | """Update the quotas for a quota class""" 29 | 30 | log = logging.getLogger(__name__ + ".UpdateQuotaClass") 31 | 32 | def get_parser(self, prog_name): 33 | parser = super(UpdateQuotaClass, self).get_parser(prog_name) 34 | parser.add_argument( 35 | '--containers', 36 | metavar='', 37 | help='The number of containers allowed per project') 38 | parser.add_argument( 39 | '--memory', 40 | metavar='', 41 | help='The number of megabytes of container RAM ' 42 | 'allowed per project') 43 | parser.add_argument( 44 | '--cpu', 45 | metavar='', 46 | help='The number of container cores or vCPUs ' 47 | 'allowed per project') 48 | parser.add_argument( 49 | '--disk', 50 | metavar='', 51 | help='The number of gigabytes of container Disk ' 52 | 'allowed per project') 53 | parser.add_argument( 54 | 'quota_class_name', 55 | metavar='', 56 | help='The name of quota class') 57 | return parser 58 | 59 | def take_action(self, parsed_args): 60 | client = _get_client(self, parsed_args) 61 | opts = {} 62 | opts['containers'] = parsed_args.containers 63 | opts['memory'] = parsed_args.memory 64 | opts['cpu'] = parsed_args.cpu 65 | opts['disk'] = parsed_args.disk 66 | quota_class_name = parsed_args.quota_class_name 67 | quota_class = client.quota_classes.update( 68 | quota_class_name, **opts) 69 | columns = _quota_class_columns(quota_class) 70 | return columns, utils.get_item_properties(quota_class, columns) 71 | 72 | 73 | class GetQuotaClass(command.ShowOne): 74 | """List the quotas for a quota class""" 75 | 76 | log = logging.getLogger(__name__ + '.GetQuotaClass') 77 | 78 | def get_parser(self, prog_name): 79 | parser = super(GetQuotaClass, self).get_parser(prog_name) 80 | parser.add_argument( 81 | 'quota_class_name', 82 | metavar='', 83 | help='The name of quota class') 84 | return parser 85 | 86 | def take_action(self, parsed_args): 87 | client = _get_client(self, parsed_args) 88 | quota_class_name = parsed_args.quota_class_name 89 | quota_class = client.quota_classes.get(quota_class_name) 90 | columns = _quota_class_columns(quota_class) 91 | return columns, utils.get_item_properties(quota_class, columns) 92 | -------------------------------------------------------------------------------- /zunclient/osc/v1/quotas.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | from osc_lib.command import command 14 | from osc_lib import utils 15 | from oslo_log import log as logging 16 | 17 | from zunclient.i18n import _ 18 | 19 | 20 | def _quota_columns(quota): 21 | return quota._info.keys() 22 | 23 | 24 | def _get_client(obj, parsed_args): 25 | obj.log.debug("take_action(%s)" % parsed_args) 26 | return obj.app.client_manager.container 27 | 28 | 29 | class UpdateQuota(command.ShowOne): 30 | """Update the quotas of the project""" 31 | 32 | log = logging.getLogger(__name__ + ".UpdateQuota") 33 | 34 | def get_parser(self, prog_name): 35 | parser = super(UpdateQuota, self).get_parser(prog_name) 36 | parser.add_argument( 37 | '--containers', 38 | metavar='', 39 | help='The number of containers allowed per project') 40 | parser.add_argument( 41 | '--memory', 42 | metavar='', 43 | help='The number of megabytes of container RAM ' 44 | 'allowed per project') 45 | parser.add_argument( 46 | '--cpu', 47 | metavar='', 48 | help='The number of container cores or vCPUs ' 49 | 'allowed per project') 50 | parser.add_argument( 51 | '--disk', 52 | metavar='', 53 | help='The number of gigabytes of container Disk ' 54 | 'allowed per project') 55 | parser.add_argument( 56 | 'project_id', 57 | metavar='', 58 | help='The UUID of project in a multi-project cloud') 59 | return parser 60 | 61 | def take_action(self, parsed_args): 62 | client = _get_client(self, parsed_args) 63 | opts = {} 64 | opts['containers'] = parsed_args.containers 65 | opts['memory'] = parsed_args.memory 66 | opts['cpu'] = parsed_args.cpu 67 | opts['disk'] = parsed_args.disk 68 | quota = client.quotas.update(parsed_args.project_id, **opts) 69 | columns = _quota_columns(quota) 70 | return columns, utils.get_item_properties(quota, columns) 71 | 72 | 73 | class GetQuota(command.ShowOne): 74 | """Get quota of the project""" 75 | 76 | log = logging.getLogger(__name__ + '.GetQuota') 77 | 78 | def get_parser(self, prog_name): 79 | parser = super(GetQuota, self).get_parser(prog_name) 80 | parser.add_argument( 81 | '--usages', 82 | action='store_true', 83 | help='Whether show quota usage statistic or not') 84 | parser.add_argument( 85 | 'project_id', 86 | metavar='', 87 | help='The UUID of project in a multi-project cloud') 88 | return parser 89 | 90 | def take_action(self, parsed_args): 91 | client = _get_client(self, parsed_args) 92 | quota = client.quotas.get( 93 | parsed_args.project_id, 94 | usages=parsed_args.usages) 95 | columns = _quota_columns(quota) 96 | return columns, utils.get_item_properties(quota, columns) 97 | 98 | 99 | class GetDefaultQuota(command.ShowOne): 100 | """Get default quota of the project""" 101 | 102 | log = logging.getLogger(__name__ + '.GetDefaultQuota') 103 | 104 | def get_parser(self, prog_name): 105 | parser = super(GetDefaultQuota, self).get_parser(prog_name) 106 | parser.add_argument( 107 | 'project_id', 108 | metavar='', 109 | help='The UUID of project in a multi-project cloud') 110 | return parser 111 | 112 | def take_action(self, parsed_args): 113 | client = _get_client(self, parsed_args) 114 | default_quota = client.quotas.defaults(parsed_args.project_id) 115 | columns = _quota_columns(default_quota) 116 | return columns, utils.get_item_properties( 117 | default_quota, columns) 118 | 119 | 120 | class DeleteQuota(command.Command): 121 | """Delete quota of the project""" 122 | 123 | log = logging.getLogger(__name__ + '.DeleteQuota') 124 | 125 | def get_parser(self, prog_name): 126 | parser = super(DeleteQuota, self).get_parser(prog_name) 127 | parser.add_argument( 128 | 'project_id', 129 | metavar='', 130 | help='The UUID of project in a multi-project cloud') 131 | return parser 132 | 133 | def take_action(self, parsed_args): 134 | client = _get_client(self, parsed_args) 135 | try: 136 | client.quotas.delete(parsed_args.project_id) 137 | print(_('Request to delete quotas has been accepted.')) 138 | except Exception as e: 139 | print("Delete for quotas failed: %(e)s" % {'e': e}) 140 | -------------------------------------------------------------------------------- /zunclient/osc/v1/services.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | from oslo_log import log as logging 14 | 15 | from osc_lib.command import command 16 | from osc_lib import utils 17 | 18 | 19 | def _get_client(obj, parsed_args): 20 | obj.log.debug("take_action(%s)" % parsed_args) 21 | return obj.app.client_manager.container 22 | 23 | 24 | class ListService(command.Lister): 25 | """Print a list of zun services.""" 26 | 27 | log = logging.getLogger(__name__ + ".ListService") 28 | 29 | def get_parser(self, prog_name): 30 | parser = super(ListService, self).get_parser(prog_name) 31 | return parser 32 | 33 | def take_action(self, parsed_args): 34 | client = _get_client(self, parsed_args) 35 | services = client.services.list() 36 | columns = ('Id', 'Host', 'Binary', 'State', 'Disabled', 37 | 'Disabled Reason', 'Updated At', 38 | 'Availability Zone') 39 | return (columns, (utils.get_item_properties(service, columns) 40 | for service in services)) 41 | 42 | 43 | class DeleteService(command.Command): 44 | """Delete the Zun binaries/services.""" 45 | 46 | log = logging.getLogger(__name__ + ".DeleteService") 47 | 48 | def get_parser(self, prog_name): 49 | parser = super(DeleteService, self).get_parser(prog_name) 50 | parser.add_argument( 51 | 'host', 52 | metavar='', 53 | help='Name of host') 54 | parser.add_argument( 55 | 'binary', 56 | metavar='', 57 | help='Name of the binary to delete') 58 | return parser 59 | 60 | def take_action(self, parsed_args): 61 | client = _get_client(self, parsed_args) 62 | host = parsed_args.host 63 | binary = parsed_args.binary 64 | try: 65 | client.services.delete(host, binary) 66 | print("Request to delete binary %s on host %s has been accepted." % 67 | (binary, host)) 68 | except Exception as e: 69 | print("Delete for binary %s on host %s failed: %s" % 70 | (binary, host, e)) 71 | 72 | 73 | class EnableService(command.ShowOne): 74 | """Enable the Zun service.""" 75 | log = logging.getLogger(__name__ + ".EnableService") 76 | 77 | def get_parser(self, prog_name): 78 | parser = super(EnableService, self).get_parser(prog_name) 79 | parser.add_argument( 80 | 'host', 81 | metavar='', 82 | help='Name of host') 83 | parser.add_argument( 84 | 'binary', 85 | metavar='', 86 | help='Name of the binary to enable') 87 | return parser 88 | 89 | def take_action(self, parsed_args): 90 | client = _get_client(self, parsed_args) 91 | host = parsed_args.host 92 | binary = parsed_args.binary 93 | res = client.services.enable(host, binary) 94 | columns = ('Host', 'Binary', 'Disabled', 'Disabled Reason') 95 | return columns, (utils.get_dict_properties(res[1]['service'], 96 | columns)) 97 | 98 | 99 | class DisableService(command.ShowOne): 100 | """Disable the Zun service.""" 101 | log = logging.getLogger(__name__ + ".DisableService") 102 | 103 | def get_parser(self, prog_name): 104 | parser = super(DisableService, self).get_parser(prog_name) 105 | parser.add_argument( 106 | 'host', 107 | metavar='', 108 | help='Name of host') 109 | parser.add_argument( 110 | 'binary', 111 | metavar='', 112 | help='Name of the binary to disable') 113 | parser.add_argument( 114 | '--reason', 115 | metavar='', 116 | help='Reason for disabling service') 117 | return parser 118 | 119 | def take_action(self, parsed_args): 120 | client = _get_client(self, parsed_args) 121 | host = parsed_args.host 122 | binary = parsed_args.binary 123 | reason = parsed_args.reason 124 | res = client.services.disable(host, binary, reason) 125 | columns = ('Host', 'Binary', 'Disabled', 'Disabled Reason') 126 | return columns, (utils.get_dict_properties(res[1]['service'], 127 | columns)) 128 | 129 | 130 | class ForceDownService(command.ShowOne): 131 | """Force the Zun service to down or up.""" 132 | log = logging.getLogger(__name__ + ".ForceDownService") 133 | 134 | def get_parser(self, prog_name): 135 | parser = super(ForceDownService, self).get_parser(prog_name) 136 | parser.add_argument( 137 | 'host', 138 | metavar='', 139 | help='Name of host') 140 | parser.add_argument( 141 | 'binary', 142 | metavar='', 143 | help='Name of the binary to disable') 144 | parser.add_argument( 145 | '--unset', 146 | dest='force_down', 147 | help='Unset the force state down of service', 148 | action='store_false', 149 | default=True) 150 | return parser 151 | 152 | def take_action(self, parsed_args): 153 | client = _get_client(self, parsed_args) 154 | host = parsed_args.host 155 | binary = parsed_args.binary 156 | force_down = parsed_args.force_down 157 | res = client.services.force_down(host, binary, force_down) 158 | columns = ('Host', 'Binary', 'Forced_down') 159 | return columns, (utils.get_dict_properties(res[1]['service'], 160 | columns)) 161 | -------------------------------------------------------------------------------- /zunclient/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/python-zunclient/eb6806a8682914171dd17a44c6b80e78f7c6b0bc/zunclient/tests/__init__.py -------------------------------------------------------------------------------- /zunclient/tests/functional/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/python-zunclient/eb6806a8682914171dd17a44c6b80e78f7c6b0bc/zunclient/tests/functional/__init__.py -------------------------------------------------------------------------------- /zunclient/tests/functional/hooks/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/python-zunclient/eb6806a8682914171dd17a44c6b80e78f7c6b0bc/zunclient/tests/functional/hooks/__init__.py -------------------------------------------------------------------------------- /zunclient/tests/functional/hooks/gate_hook.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -x 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | # 15 | # This script is executed inside gate_hook function in devstack gate. 16 | 17 | 18 | # Keep all devstack settings here instead of project-config for easy 19 | # maintain if we want to change devstack config settings in future. 20 | 21 | driver=$1 22 | db=$2 23 | 24 | export DEVSTACK_LOCAL_CONFIG+=$'\n'"enable_plugin kuryr-libnetwork https://opendev.org/openstack/kuryr-libnetwork" 25 | export DEVSTACK_LOCAL_CONFIG+=$'\n'"enable_plugin devstack-plugin-container https://opendev.org/openstack/devstack-plugin-container" 26 | export DEVSTACK_LOCAL_CONFIG+=$'\n'"ZUN_USE_UWSGI=True" 27 | export DEVSTACK_LOCAL_CONFIG+=$'\n'"KURYR_CONFIG_DIR=/etc/kuryr-libnetwork" 28 | export DEVSTACK_GATE_TEMPEST=0 29 | 30 | if [ "$driver" = "docker" ]; then 31 | export DEVSTACK_LOCAL_CONFIG+=$'\n'"ZUN_DRIVER=docker" 32 | fi 33 | 34 | if [ "$db" = "etcd" ]; then 35 | export DEVSTACK_LOCAL_CONFIG+=$'\n'"ZUN_DB_TYPE=etcd" 36 | elif [ "$db" = "sql" ]; then 37 | export DEVSTACK_LOCAL_CONFIG+=$'\n'"ZUN_DB_TYPE=sql" 38 | fi 39 | 40 | $BASE/new/devstack-gate/devstack-vm-gate.sh 41 | gate_retval=$? 42 | 43 | # Copy over docker systemd unit journals. 44 | mkdir -p $WORKSPACE/logs 45 | sudo journalctl -o short-precise --unit docker | sudo tee $WORKSPACE/logs/docker.txt > /dev/null 46 | 47 | exit $gate_retval 48 | -------------------------------------------------------------------------------- /zunclient/tests/functional/hooks/post_test_hook.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -x 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | # This script is executed inside post_test_hook function in devstack gate. 16 | 17 | # Sleep some time until all services are starting 18 | sleep 5 19 | 20 | # Check if a function already exists 21 | function function_exists { 22 | declare -f -F $1 > /dev/null 23 | } 24 | 25 | if ! function_exists echo_summary; then 26 | function echo_summary { 27 | echo $@ 28 | } 29 | fi 30 | 31 | # Save trace setting 32 | XTRACE=$(set +o | grep xtrace) 33 | set -o xtrace 34 | 35 | echo_summary "Zunclient's post_test_hook.sh was called..." 36 | (set -o posix; set) 37 | 38 | # source it to make sure to get REQUIREMENTS_DIR 39 | source $BASE/new/devstack/stackrc 40 | source $BASE/new/devstack/accrc/admin/admin 41 | 42 | constraints="-c $REQUIREMENTS_DIR/upper-constraints.txt" 43 | sudo -H pip install $constraints -U -r requirements.txt -r test-requirements.txt 44 | 45 | echo "Running OSC commands test for Zun" 46 | 47 | export ZUNCLIENT_DIR="$BASE/new/python-zunclient" 48 | 49 | sudo chown -R $USER:stack $ZUNCLIENT_DIR 50 | 51 | # Go to the zunclient dir 52 | cd $ZUNCLIENT_DIR 53 | 54 | # Run tests 55 | set +e 56 | source $BASE/new/devstack/openrc admin admin 57 | sudo -E -H -u $USER ./tools/run_functional.sh 58 | 59 | EXIT_CODE=$? 60 | 61 | set -e 62 | 63 | $XTRACE 64 | exit $EXIT_CODE 65 | -------------------------------------------------------------------------------- /zunclient/tests/functional/osc/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/python-zunclient/eb6806a8682914171dd17a44c6b80e78f7c6b0bc/zunclient/tests/functional/osc/__init__.py -------------------------------------------------------------------------------- /zunclient/tests/functional/osc/v1/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/python-zunclient/eb6806a8682914171dd17a44c6b80e78f7c6b0bc/zunclient/tests/functional/osc/v1/__init__.py -------------------------------------------------------------------------------- /zunclient/tests/functional/osc/v1/base.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | from oslo_serialization import jsonutils 14 | 15 | from tempest.lib.common.utils import data_utils 16 | from tempest.lib import exceptions 17 | 18 | from zunclient.tests.functional import base 19 | 20 | 21 | class TestCase(base.FunctionalTestBase): 22 | 23 | def openstack(self, *args, **kwargs): 24 | return self._zun(cmd='openstack', *args, **kwargs) 25 | 26 | def get_opts(self, fields=None, output_format='json'): 27 | """Get options for OSC output fields format. 28 | 29 | :param List fields: List of fields to get 30 | :param String output_format: Select output format 31 | :return: String of formatted options 32 | """ 33 | if not fields: 34 | return ' -f {0}'.format(output_format) 35 | return ' -f {0} {1}'.format(output_format, 36 | ' '.join(['-c ' + it for it in fields])) 37 | 38 | def container_create(self, image='cirros', name=None, params=''): 39 | """Create container and add cleanup. 40 | 41 | :param String image: Image for a new container 42 | :param String name: Name for a new container 43 | :param String params: Additional args and kwargs 44 | :return: JSON object of created container 45 | """ 46 | if not name: 47 | name = data_utils.rand_name('container') 48 | 49 | opts = self.get_opts() 50 | output = self.openstack('appcontainer create {0}' 51 | ' --name {1} {2} {3}' 52 | .format(opts, name, image, params)) 53 | container = jsonutils.loads(output) 54 | 55 | if not output: 56 | self.fail('Container has not been created!') 57 | return container 58 | 59 | def container_run(self, image='cirros', name=None, params='sleep 100000'): 60 | """Run container and add cleanup. 61 | 62 | :param String image: Image for a new container 63 | :param String name: Name for a new container 64 | :param String params: Additional args and kwargs 65 | :return: JSON object of created container 66 | """ 67 | if not name: 68 | name = data_utils.rand_name('container') 69 | 70 | opts = self.get_opts() 71 | output = self.openstack('appcontainer run {0}' 72 | ' --name {1} {2} {3}' 73 | .format(opts, name, image, params)) 74 | container = jsonutils.loads(output) 75 | 76 | if not output: 77 | self.fail('Container has not run!') 78 | return container 79 | 80 | def container_delete(self, identifier, force=True, 81 | ignore_exceptions=False): 82 | """Try to delete container by name or UUID. 83 | 84 | :param String identifier: Name or UUID of the container 85 | :param Bool ignore_exceptions: Ignore exception (needed for cleanUp) 86 | :return: raw values output 87 | :raise: CommandFailed exception when command fails 88 | to delete a container 89 | """ 90 | arg = '--force' if force else '' 91 | try: 92 | return self.openstack('appcontainer delete {0} {1}' 93 | .format(arg, identifier)) 94 | except exceptions.CommandFailed: 95 | if not ignore_exceptions: 96 | raise 97 | 98 | def container_list(self, fields=None, params=''): 99 | """List Container. 100 | 101 | :param List fields: List of fields to show 102 | :param String params: Additional kwargs 103 | :return: list of JSON container objects 104 | """ 105 | opts = self.get_opts(fields=fields) 106 | output = self.openstack('appcontainer list {0} {1}' 107 | .format(opts, params)) 108 | return jsonutils.loads(output) 109 | 110 | def container_show(self, identifier, fields=None, params=''): 111 | """Show specified container. 112 | 113 | :param String identifier: Name or UUID of the container 114 | :param List fields: List of fields to show 115 | :param List params: Additional kwargs 116 | :return: JSON object of container 117 | """ 118 | opts = self.get_opts(fields) 119 | output = self.openstack('appcontainer show {0} {1} {2}' 120 | .format(opts, identifier, params)) 121 | return jsonutils.loads(output) 122 | 123 | def container_rename(self, identifier, name): 124 | """Rename specified container. 125 | 126 | :param String identifier: Name or UUID of the container 127 | :param String name: new name for the container 128 | """ 129 | self.openstack('appcontainer rename {0} {1}' 130 | .format(identifier, name)) 131 | 132 | def container_execute(self, identifier, command): 133 | """Execute in specified container. 134 | 135 | :param String identifier: Name or UUID of the container 136 | :param String command: command execute in the container 137 | """ 138 | return self.openstack('appcontainer exec {0} {1}' 139 | .format(identifier, command)) 140 | -------------------------------------------------------------------------------- /zunclient/tests/functional/osc/v1/test_container.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Mirantis, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | import ddt 16 | import time 17 | 18 | from zunclient.tests.functional.osc.v1 import base 19 | 20 | 21 | @ddt.ddt 22 | class ContainerTests(base.TestCase): 23 | """Functional tests for container commands.""" 24 | 25 | def setUp(self): 26 | super(ContainerTests, self).setUp() 27 | 28 | def test_list(self): 29 | """Check container list command. 30 | 31 | """ 32 | container = self.container_create(name='test_list') 33 | container_list = self.container_list() 34 | self.assertIn(container['name'], [x['name'] for x in 35 | container_list]) 36 | self.assertIn(container['uuid'], [x['uuid'] for x in 37 | container_list]) 38 | 39 | # Now delete the container and then see the list 40 | self.container_delete(container['name']) 41 | 42 | def test_create(self): 43 | """Check container create command. 44 | 45 | """ 46 | container_info = self.container_create(name='test_create') 47 | self.assertEqual(container_info['name'], 'test_create') 48 | self.assertEqual(container_info['image'], 'cirros') 49 | container_list = self.container_list() 50 | self.assertIn('test_create', [x['name'] for x in container_list]) 51 | self.container_delete(container_info['name']) 52 | 53 | def test_delete(self): 54 | """Check container delete command with name/UUID argument. 55 | 56 | Test steps: 57 | 1) Create container in setUp. 58 | 2) Delete container by name/UUID. 59 | 3) Check that container deleted successfully. 60 | """ 61 | container = self.container_create(name='test_delete') 62 | container_list = self.container_list() 63 | self.assertIn(container['name'], 64 | [x['name'] for x in container_list]) 65 | self.assertIn(container['uuid'], 66 | [x['uuid'] for x in container_list]) 67 | count = 0 68 | while count < 5: 69 | container = self.container_show(container['name']) 70 | if container['status'] == 'Created': 71 | break 72 | if container['status'] == 'Error': 73 | break 74 | time.sleep(2) 75 | count = count + 1 76 | self.container_delete(container['name']) 77 | # Wait for the container to be deleted 78 | count = 0 79 | while count < 5: 80 | container_list = self.container_list() 81 | if container['name'] not in [x['name'] for x in container_list]: 82 | break 83 | time.sleep(2) 84 | count = count + 1 85 | container_list = self.container_list() 86 | self.assertNotIn(container['name'], 87 | [x['name'] for x in container_list]) 88 | self.assertNotIn(container['uuid'], 89 | [x['uuid'] for x in container_list]) 90 | 91 | def test_show(self): 92 | """Check container show command with name and UUID arguments. 93 | 94 | Test steps: 95 | 1) Create container in setUp. 96 | 2) Show container calling it with name and UUID arguments. 97 | 3) Check name, uuid and image in container show output. 98 | """ 99 | container = self.container_create(name='test_show') 100 | self.container_show(container['name']) 101 | self.assertEqual(container['name'], container['name']) 102 | self.assertEqual(container['image'], container['image']) 103 | self.container_delete(container['name']) 104 | 105 | def test_execute(self): 106 | """Check container execute command with name and UUID arguments. 107 | 108 | Test steps: 109 | 1) Create container in setUp. 110 | 2) Execute command calling it with name and UUID arguments. 111 | 3) Check the container logs. 112 | """ 113 | container = self.container_run(name='test_execute') 114 | count = 0 115 | while count < 50: 116 | container = self.container_show(container['name']) 117 | if container['status'] == 'Running': 118 | break 119 | if container['status'] == 'Error': 120 | break 121 | time.sleep(2) 122 | count = count + 1 123 | command = "sh -c 'echo hello'" 124 | result = self.container_execute(container['name'], command) 125 | self.assertIn('hello', result) 126 | self.container_delete(container['name']) 127 | -------------------------------------------------------------------------------- /zunclient/tests/unit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/python-zunclient/eb6806a8682914171dd17a44c6b80e78f7c6b0bc/zunclient/tests/unit/__init__.py -------------------------------------------------------------------------------- /zunclient/tests/unit/base.py: -------------------------------------------------------------------------------- 1 | 2 | # Copyright 2010-2011 OpenStack Foundation 3 | # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | import os 18 | 19 | import fixtures 20 | import testtools 21 | 22 | _TRUE_VALUES = ('true', '1', 'yes') 23 | 24 | 25 | class TestCase(testtools.TestCase): 26 | 27 | """Test case base class for all unit tests.""" 28 | 29 | def setUp(self): 30 | """Run before each test method to initialize test environment.""" 31 | 32 | super(TestCase, self).setUp() 33 | test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0) 34 | try: 35 | test_timeout = int(test_timeout) 36 | except ValueError: 37 | # If timeout value is invalid do not set a timeout. 38 | test_timeout = 0 39 | if test_timeout > 0: 40 | self.useFixture(fixtures.Timeout(test_timeout, gentle=True)) 41 | 42 | self.useFixture(fixtures.NestedTempfile()) 43 | self.useFixture(fixtures.TempHomeDir()) 44 | 45 | if os.environ.get('OS_STDOUT_CAPTURE') in _TRUE_VALUES: 46 | stdout = self.useFixture(fixtures.StringStream('stdout')).stream 47 | self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout)) 48 | if os.environ.get('OS_STDERR_CAPTURE') in _TRUE_VALUES: 49 | stderr = self.useFixture(fixtures.StringStream('stderr')).stream 50 | self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr)) 51 | 52 | self.log_fixture = self.useFixture(fixtures.FakeLogger()) 53 | -------------------------------------------------------------------------------- /zunclient/tests/unit/common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/python-zunclient/eb6806a8682914171dd17a44c6b80e78f7c6b0bc/zunclient/tests/unit/common/__init__.py -------------------------------------------------------------------------------- /zunclient/tests/unit/osc/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/python-zunclient/eb6806a8682914171dd17a44c6b80e78f7c6b0bc/zunclient/tests/unit/osc/__init__.py -------------------------------------------------------------------------------- /zunclient/tests/unit/osc/test_plugin.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | from unittest import mock 14 | 15 | from zunclient import api_versions 16 | from zunclient.osc import plugin 17 | from zunclient.tests.unit import base 18 | 19 | 20 | class TestContainerPlugin(base.TestCase): 21 | 22 | @mock.patch("zunclient.api_versions.get_api_version") 23 | @mock.patch("zunclient.v1.client.Client") 24 | def test_make_client(self, p_client, mock_get_api_version): 25 | 26 | instance = mock.Mock() 27 | instance._api_version = {"container": '1'} 28 | instance._region_name = 'zun_region' 29 | instance.session = 'zun_session' 30 | mock_get_api_version.return_value = api_versions.APIVersion('1.2') 31 | 32 | plugin.make_client(instance) 33 | p_client.assert_called_with(region_name='zun_region', 34 | session='zun_session', 35 | service_type='container', 36 | api_version=api_versions.APIVersion('1.2')) 37 | -------------------------------------------------------------------------------- /zunclient/tests/unit/test_client.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 IBM Corp. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | from unittest import mock 16 | 17 | import testtools 18 | 19 | from zunclient import api_versions 20 | from zunclient import client 21 | from zunclient import exceptions 22 | 23 | 24 | class ClientTest(testtools.TestCase): 25 | 26 | @mock.patch('zunclient.api_versions.discover_version', 27 | return_value=api_versions.APIVersion('1.1')) 28 | @mock.patch('zunclient.v1.client.Client') 29 | def test_no_version_argument(self, mock_zun_client_v1, 30 | mock_discover_version): 31 | client.Client(auth_url='http://example/identity', 32 | username='admin') 33 | mock_zun_client_v1.assert_called_with( 34 | api_version=api_versions.APIVersion('1.1'), 35 | auth_url='http://example/identity', 36 | username='admin') 37 | 38 | @mock.patch('zunclient.api_versions.discover_version', 39 | return_value=api_versions.APIVersion('1.1')) 40 | @mock.patch('zunclient.v1.client.Client') 41 | def test_valid_version_argument(self, mock_zun_client_v1, 42 | mock_discover_version): 43 | client.Client(version='1', 44 | auth_url='http://example/identity', 45 | username='admin') 46 | mock_zun_client_v1.assert_called_with( 47 | api_version=api_versions.APIVersion('1.1'), 48 | auth_url='http://example/identity', 49 | username='admin') 50 | 51 | def test_invalid_version_argument(self): 52 | self.assertRaises( 53 | exceptions.UnsupportedVersion, 54 | client.Client, version='2') 55 | -------------------------------------------------------------------------------- /zunclient/tests/unit/test_websocketclient.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 OpenStack LLC. 2 | # All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | from unittest import mock 17 | 18 | import testtools 19 | 20 | from zunclient.common.websocketclient import websocketclient 21 | 22 | CONTAINER_ID = "0f96db5a-26dc-4550-b1a8-b110bd9247cb" 23 | ESCAPE_FLAG = "~" 24 | URL = "ws://localhost:2375/v1.17/containers/201e4e22c5b2/" \ 25 | "attach/ws?logs=0&stream=1&stdin=1&stdout=1&stderr=1" 26 | URL1 = "ws://10.10.10.10:2375/v1.17/containers/***********/" \ 27 | "attach/ws?logs=0&stream=1&stdin=1&stdout=1&stderr=1" 28 | WAIT_TIME = 0.5 29 | 30 | 31 | class WebSocketClientTest(testtools.TestCase): 32 | 33 | def test_websocketclient_variables(self): 34 | mock_client = mock.Mock() 35 | wsclient = websocketclient.WebSocketClient(zunclient=mock_client, 36 | url=URL, 37 | id=CONTAINER_ID, 38 | escape=ESCAPE_FLAG, 39 | close_wait=WAIT_TIME) 40 | self.assertEqual(wsclient.url, URL) 41 | self.assertEqual(wsclient.id, CONTAINER_ID) 42 | self.assertEqual(wsclient.escape, ESCAPE_FLAG) 43 | self.assertEqual(wsclient.close_wait, WAIT_TIME) 44 | -------------------------------------------------------------------------------- /zunclient/tests/unit/v1/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/python-zunclient/eb6806a8682914171dd17a44c6b80e78f7c6b0bc/zunclient/tests/unit/v1/__init__.py -------------------------------------------------------------------------------- /zunclient/tests/unit/v1/shell_test_base.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 NEC Corporation. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | import re 16 | from unittest import mock 17 | 18 | from testtools import matchers 19 | 20 | from zunclient import api_versions 21 | from zunclient.tests.unit import utils 22 | 23 | FAKE_ENV = {'OS_USERNAME': 'username', 24 | 'OS_PASSWORD': 'password', 25 | 'OS_PROJECT_NAME': 'project_name', 26 | 'OS_AUTH_URL': 'http://no.where/v2.0', 27 | 'BYPASS_URL': 'http://zun'} 28 | 29 | 30 | class TestCommandLineArgument(utils.TestCase): 31 | _unrecognized_arg_error = [ 32 | '.*?^usage: ', 33 | '.*?^error: unrecognized arguments:', 34 | ".*?^Try 'zun help ' for more information.", 35 | ] 36 | 37 | _mandatory_arg_error = [ 38 | '.*?^usage: ', 39 | '.*?^error: (the following arguments|argument)', 40 | ".*?^Try 'zun help ", 41 | ] 42 | 43 | _few_argument_error = [ 44 | '.*?^usage: zun ', 45 | '.*?^error: (the following arguments|too few arguments)', 46 | ".*?^Try 'zun help ", 47 | ] 48 | 49 | _invalid_value_error = [ 50 | '.*?^usage: ', 51 | '.*?^error: argument .*: invalid .* value:', 52 | ".*?^Try 'zun help ", 53 | ] 54 | 55 | _invalid_choice_error = [ 56 | '.*?^usage: ', 57 | '.*?^error: argument .*: invalid choice:', 58 | ".*?^Try 'zun help ", 59 | ] 60 | 61 | def setUp(self): 62 | super(TestCommandLineArgument, self).setUp() 63 | self.make_env(fake_env=FAKE_ENV) 64 | session_client = mock.patch( 65 | 'zunclient.common.httpclient.SessionClient') 66 | session_client.start() 67 | loader = mock.patch('keystoneauth1.loading.get_plugin_loader') 68 | loader.start() 69 | session = mock.patch('keystoneauth1.session.Session') 70 | session.start() 71 | discover = mock.patch('zunclient.api_versions.discover_version', 72 | return_value=api_versions.APIVersion('1.1')) 73 | discover.start() 74 | 75 | self.addCleanup(session_client.stop) 76 | self.addCleanup(loader.stop) 77 | self.addCleanup(session.stop) 78 | self.addCleanup(discover.stop) 79 | 80 | def _test_arg_success(self, command): 81 | stdout, stderr = self.shell(command) 82 | 83 | def _test_arg_failure(self, command, error_msg): 84 | stdout, stderr = self.shell(command, (2,)) 85 | for line in error_msg: 86 | self.assertThat((stdout + stderr), 87 | matchers.MatchesRegex(line, 88 | re.DOTALL | re.MULTILINE)) 89 | -------------------------------------------------------------------------------- /zunclient/tests/unit/v1/test_availability_zones.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | import testtools 14 | from testtools import matchers 15 | from zunclient.tests.unit import utils 16 | from zunclient.v1 import availability_zones as az 17 | 18 | AZ1 = {'availability_zone': 'fake-az-1'} 19 | 20 | AZ2 = {'availability_zone': 'fake-az-2'} 21 | 22 | fake_responses = { 23 | '/v1/availability_zones': 24 | { 25 | 'GET': ( 26 | {}, 27 | {'availability_zones': [AZ1, AZ2]}, 28 | ), 29 | }, 30 | } 31 | 32 | 33 | class AZManagerTest(testtools.TestCase): 34 | 35 | def setUp(self): 36 | super(AZManagerTest, self).setUp() 37 | self.api = utils.FakeAPI(fake_responses) 38 | self.mgr = az.AvailabilityZoneManager(self.api) 39 | 40 | def test_availability_zones_list(self): 41 | zones = self.mgr.list() 42 | expect = [ 43 | ('GET', '/v1/availability_zones', {}, None), 44 | ] 45 | self.assertEqual(expect, self.api.calls) 46 | self.assertThat(zones, matchers.HasLength(2)) 47 | -------------------------------------------------------------------------------- /zunclient/tests/unit/v1/test_availability_zones_shell.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | from unittest import mock 14 | 15 | from zunclient.tests.unit.v1 import shell_test_base 16 | 17 | 18 | class ShellTest(shell_test_base.TestCommandLineArgument): 19 | 20 | @mock.patch('zunclient.v1.availability_zones.AvailabilityZoneManager.list') 21 | def test_zun_service_list_success(self, mock_list): 22 | self._test_arg_success('availability-zone-list') 23 | self.assertTrue(mock_list.called) 24 | 25 | @mock.patch('zunclient.v1.availability_zones.AvailabilityZoneManager.list') 26 | def test_zun_service_list_failure(self, mock_list): 27 | self._test_arg_failure('availability-zone-list --wrong', 28 | self._unrecognized_arg_error) 29 | self.assertFalse(mock_list.called) 30 | -------------------------------------------------------------------------------- /zunclient/tests/unit/v1/test_images.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | import testtools 14 | from testtools import matchers 15 | 16 | from zunclient.tests.unit import utils 17 | from zunclient.v1 import images 18 | 19 | 20 | IMAGE1 = {'uuid': '092e2ed7-af11-4fa7-8ffa-c3ee9d5b451a', 21 | 'image_id': 'b8ff79200466', 22 | 'repo': 'fake-repo1', 23 | 'tag': 'latest', 24 | 'size': '1024', 25 | } 26 | IMAGE2 = {'uuid': '1996ba70-b074-454b-a8fc-0895ae26c7c6', 27 | 'image_id': '21c16b6787c6', 28 | 'repo': 'fake-repo2', 29 | 'tag': 'latest', 30 | 'size': '1024', 31 | } 32 | IMAGE3 = {'uuid': '1267gf34-34e4-tf4b-b84c-1345avf6c7c6', 33 | 'image_id': '24r5tt6y87c6', 34 | 'image': 'fake-name3', 35 | 'tag': 'latest', 36 | 'size': '1024', 37 | 'image_driver': 'fake-driver', 38 | } 39 | SEARCH_IMAGE = {'image': 'fake-name3', 40 | 'image_driver': 'fake-driver', 41 | } 42 | 43 | 44 | fake_responses = { 45 | '/v1/images/': 46 | { 47 | 'GET': ( 48 | {}, 49 | {'images': [IMAGE1, IMAGE2]}, 50 | ), 51 | }, 52 | '/v1/images/?limit=2': 53 | { 54 | 'GET': ( 55 | {}, 56 | {'images': [IMAGE1, IMAGE2]}, 57 | ), 58 | }, 59 | '/v1/images/?marker=%s' % IMAGE2['image_id']: 60 | { 61 | 'GET': ( 62 | {}, 63 | {'images': [IMAGE1, IMAGE2]}, 64 | ), 65 | }, 66 | '/v1/images/?limit=2&marker=%s' % IMAGE2['image_id']: 67 | { 68 | 'GET': ( 69 | {}, 70 | {'images': [IMAGE2, IMAGE1]}, 71 | ), 72 | }, 73 | '/v1/images/?sort_dir=asc': 74 | { 75 | 'GET': ( 76 | {}, 77 | {'images': [IMAGE1, IMAGE2]}, 78 | ), 79 | }, 80 | '/v1/images/?sort_key=image_id': 81 | { 82 | 'GET': ( 83 | {}, 84 | {'images': [IMAGE1, IMAGE2]}, 85 | ), 86 | }, 87 | '/v1/images/?sort_key=image_id&sort_dir=desc': 88 | { 89 | 'GET': ( 90 | {}, 91 | {'images': [IMAGE2, IMAGE1]}, 92 | ), 93 | }, 94 | '/v1/images/%s/search?image_driver=%s' % (IMAGE3['image'], 95 | IMAGE3['image_driver']): 96 | { 97 | 'GET': ( 98 | {}, 99 | {'images': [IMAGE3]}, 100 | ), 101 | }, 102 | } 103 | 104 | 105 | class ImageManagerTest(testtools.TestCase): 106 | 107 | def setUp(self): 108 | super(ImageManagerTest, self).setUp() 109 | self.api = utils.FakeAPI(fake_responses) 110 | self.mgr = images.ImageManager(self.api) 111 | 112 | def test_image_list(self): 113 | images = self.mgr.list() 114 | expect = [ 115 | ('GET', '/v1/images/', {}, None), 116 | ] 117 | self.assertEqual(expect, self.api.calls) 118 | self.assertThat(images, matchers.HasLength(2)) 119 | 120 | def _test_image_list_with_filters( 121 | self, limit=None, marker=None, 122 | sort_key=None, sort_dir=None, 123 | expect=[]): 124 | images_filter = self.mgr.list(limit=limit, marker=marker, 125 | sort_key=sort_key, 126 | sort_dir=sort_dir) 127 | self.assertEqual(expect, self.api.calls) 128 | self.assertThat(images_filter, matchers.HasLength(2)) 129 | 130 | def test_image_list_with_limit(self): 131 | expect = [ 132 | ('GET', '/v1/images/?limit=2', {}, None), 133 | ] 134 | self._test_image_list_with_filters( 135 | limit=2, 136 | expect=expect) 137 | 138 | def test_image_list_with_marker(self): 139 | expect = [ 140 | ('GET', '/v1/images/?marker=%s' % IMAGE2['image_id'], 141 | {}, None), 142 | ] 143 | self._test_image_list_with_filters( 144 | marker=IMAGE2['image_id'], 145 | expect=expect) 146 | 147 | def test_image_list_with_marker_limit(self): 148 | expect = [ 149 | ('GET', '/v1/images/?limit=2&marker=%s' % IMAGE2['image_id'], 150 | {}, None), 151 | ] 152 | self._test_image_list_with_filters( 153 | limit=2, marker=IMAGE2['image_id'], 154 | expect=expect) 155 | 156 | def test_image_list_with_sort_dir(self): 157 | expect = [ 158 | ('GET', '/v1/images/?sort_dir=asc', 159 | {}, None), 160 | ] 161 | self._test_image_list_with_filters( 162 | sort_dir='asc', 163 | expect=expect) 164 | 165 | def test_image_list_with_sort_key(self): 166 | expect = [ 167 | ('GET', '/v1/images/?sort_key=image_id', 168 | {}, None), 169 | ] 170 | self._test_image_list_with_filters( 171 | sort_key='image_id', 172 | expect=expect) 173 | 174 | def test_image_list_with_sort_key_dir(self): 175 | expect = [ 176 | ('GET', '/v1/images/?sort_key=image_id&sort_dir=desc', 177 | {}, None), 178 | ] 179 | self._test_image_list_with_filters( 180 | sort_key='image_id', sort_dir='desc', 181 | expect=expect) 182 | 183 | def test_image_search(self): 184 | images = self.mgr.search_image(**SEARCH_IMAGE) 185 | url = '/v1/images/%s/search?image_driver=%s' \ 186 | % (IMAGE3['image'], IMAGE3['image_driver']) 187 | expect = [ 188 | ('GET', url, {}, None), 189 | ] 190 | self.assertEqual(expect, self.api.calls) 191 | self.assertThat(images, matchers.HasLength(1)) 192 | -------------------------------------------------------------------------------- /zunclient/tests/unit/v1/test_images_shell.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 NEC Corporation. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | from unittest import mock 16 | 17 | from zunclient.tests.unit.v1 import shell_test_base 18 | 19 | 20 | class ShellTest(shell_test_base.TestCommandLineArgument): 21 | 22 | @mock.patch('zunclient.v1.images.ImageManager.list') 23 | def test_zun_image_list_success(self, mock_list): 24 | self._test_arg_success('image-list') 25 | self.assertTrue(mock_list.called) 26 | 27 | @mock.patch('zunclient.v1.images.ImageManager.list') 28 | def test_zun_image_list_failure(self, mock_list): 29 | self._test_arg_failure('image-list --wrong', 30 | self._unrecognized_arg_error) 31 | self.assertFalse(mock_list.called) 32 | 33 | @mock.patch('zunclient.v1.images.ImageManager.get') 34 | def test_zun_image_show_success(self, mock_get): 35 | self._test_arg_success('image-show 111') 36 | self.assertTrue(mock_get.called) 37 | 38 | @mock.patch('zunclient.v1.images.ImageManager.get') 39 | def test_zun_image_show_failure(self, mock_get): 40 | self._test_arg_failure('image-show --wrong 1111', 41 | self._unrecognized_arg_error) 42 | self.assertFalse(mock_get.called) 43 | 44 | @mock.patch('zunclient.v1.images.ImageManager.search_image') 45 | def test_zun_image_search_with_driver(self, mock_search_image): 46 | self._test_arg_success('image-search 111 --image_driver glance') 47 | self.assertTrue(mock_search_image.called) 48 | 49 | @mock.patch('zunclient.v1.images.ImageManager.search_image') 50 | def test_zun_image_search_default_driver(self, mock_search_image): 51 | self._test_arg_success('image-search 111') 52 | self.assertTrue(mock_search_image.called) 53 | 54 | @mock.patch('zunclient.v1.images.ImageManager.search_image') 55 | def test_zun_image_search_failure(self, mock_search_image): 56 | self._test_arg_failure('image-search --wrong 1111', 57 | self._unrecognized_arg_error) 58 | self.assertFalse(mock_search_image.called) 59 | -------------------------------------------------------------------------------- /zunclient/tests/unit/v1/test_quotas.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain a 3 | # copy of the License at 4 | # http://www.apache.org/licenses/LICENSE-2.0 5 | # 6 | # Unless required by applicable law or agreed to in writing, software 7 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 8 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 9 | # License for the specific language governing permissions and limitations 10 | # under the License. 11 | 12 | import testtools 13 | 14 | from zunclient.tests.unit import utils 15 | from zunclient.v1 import quotas 16 | 17 | 18 | DEFAULT_QUOTAS = { 19 | 'containers': '40', 20 | 'memory': '51200', 21 | 'cpu': '20', 22 | 'disk': '100' 23 | } 24 | 25 | MODIFIED_QUOTAS = { 26 | 'containers': '50', 27 | 'memory': '51200', 28 | 'cpu': '20', 29 | 'disk': '100' 30 | } 31 | 32 | MODIFIED_USAGE_QUOTAS = { 33 | 'containers': { 34 | 'limit': '50', 35 | 'in_use': '30' 36 | }, 37 | 'memory': {}, 38 | 'cpu': {}, 39 | 'disk': {} 40 | } 41 | 42 | fake_responses = { 43 | '/v1/quotas/test_project_id': 44 | { 45 | 'GET': ( 46 | {}, 47 | MODIFIED_QUOTAS 48 | ), 49 | 'PUT': ( 50 | {}, 51 | MODIFIED_QUOTAS 52 | ), 53 | 'DELETE': ( 54 | {}, 55 | None 56 | ) 57 | }, 58 | '/v1/quotas/test_project_id/defaults': 59 | { 60 | 'GET': ( 61 | {}, 62 | DEFAULT_QUOTAS 63 | ) 64 | }, 65 | '/v1/quotas/test_project_id?usages=True': 66 | { 67 | 'GET': ( 68 | {}, 69 | MODIFIED_USAGE_QUOTAS 70 | ) 71 | } 72 | } 73 | 74 | 75 | class QuotaManagerTest(testtools.TestCase): 76 | 77 | def setUp(self): 78 | super(QuotaManagerTest, self).setUp() 79 | self.api = utils.FakeAPI(fake_responses) 80 | self.mgr = quotas.QuotaManager(self.api) 81 | 82 | def test_quotas_get_defaults(self): 83 | quotas = self.mgr.defaults('test_project_id') 84 | expect = [ 85 | ('GET', '/v1/quotas/test_project_id/defaults', {}, None) 86 | ] 87 | self.assertEqual(expect, self.api.calls) 88 | self.assertEqual(quotas.containers, DEFAULT_QUOTAS['containers']) 89 | self.assertEqual(quotas.memory, DEFAULT_QUOTAS['memory']) 90 | self.assertEqual(quotas.cpu, DEFAULT_QUOTAS['cpu']) 91 | self.assertEqual(quotas.disk, DEFAULT_QUOTAS['disk']) 92 | -------------------------------------------------------------------------------- /zunclient/tests/unit/v1/test_services.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | import testtools 14 | from testtools import matchers 15 | 16 | from zunclient.tests.unit import utils 17 | from zunclient.v1 import services 18 | 19 | 20 | SERVICE1 = {'id': 123, 21 | 'host': 'fake-host1', 22 | 'binary': 'fake-bin1', 23 | 'state': 'up', 24 | 'availability_zone': 'nova', 25 | } 26 | SERVICE2 = {'id': 124, 27 | 'host': 'fake-host2', 28 | 'binary': 'fake-bin2', 29 | 'state': 'down', 30 | 'availability_zone': 'nova', 31 | } 32 | 33 | fake_responses = { 34 | '/v1/services': 35 | { 36 | 'GET': ( 37 | {}, 38 | {'services': [SERVICE1, SERVICE2]}, 39 | ), 40 | }, 41 | '/v1/services/?limit=2': 42 | { 43 | 'GET': ( 44 | {}, 45 | {'services': [SERVICE1, SERVICE2]}, 46 | ), 47 | }, 48 | '/v1/services/?marker=%s' % SERVICE2['id']: 49 | { 50 | 'GET': ( 51 | {}, 52 | {'services': [SERVICE1, SERVICE2]}, 53 | ), 54 | }, 55 | '/v1/services/?limit=2&marker=%s' % SERVICE2['id']: 56 | { 57 | 'GET': ( 58 | {}, 59 | {'services': [SERVICE2, SERVICE1]}, 60 | ), 61 | }, 62 | '/v1/services/?sort_dir=asc': 63 | { 64 | 'GET': ( 65 | {}, 66 | {'services': [SERVICE1, SERVICE2]}, 67 | ), 68 | }, 69 | '/v1/services/?sort_key=id': 70 | { 71 | 'GET': ( 72 | {}, 73 | {'services': [SERVICE1, SERVICE2]}, 74 | ), 75 | }, 76 | '/v1/services/?sort_key=id&sort_dir=desc': 77 | { 78 | 'GET': ( 79 | {}, 80 | {'services': [SERVICE2, SERVICE1]}, 81 | ), 82 | }, 83 | } 84 | 85 | 86 | class ServiceManagerTest(testtools.TestCase): 87 | 88 | def setUp(self): 89 | super(ServiceManagerTest, self).setUp() 90 | self.api = utils.FakeAPI(fake_responses) 91 | self.mgr = services.ServiceManager(self.api) 92 | 93 | def test_service_list(self): 94 | services = self.mgr.list() 95 | expect = [ 96 | ('GET', '/v1/services', {}, None), 97 | ] 98 | self.assertEqual(expect, self.api.calls) 99 | self.assertThat(services, matchers.HasLength(2)) 100 | 101 | def _test_service_list_with_filters( 102 | self, limit=None, marker=None, 103 | sort_key=None, sort_dir=None, 104 | expect=[]): 105 | services_filter = self.mgr.list(limit=limit, marker=marker, 106 | sort_key=sort_key, 107 | sort_dir=sort_dir) 108 | self.assertEqual(expect, self.api.calls) 109 | self.assertThat(services_filter, matchers.HasLength(2)) 110 | 111 | def test_service_list_with_limit(self): 112 | expect = [ 113 | ('GET', '/v1/services/?limit=2', {}, None), 114 | ] 115 | self._test_service_list_with_filters( 116 | limit=2, 117 | expect=expect) 118 | 119 | def test_service_list_with_marker(self): 120 | expect = [ 121 | ('GET', '/v1/services/?marker=%s' % SERVICE2['id'], 122 | {}, None), 123 | ] 124 | self._test_service_list_with_filters( 125 | marker=SERVICE2['id'], 126 | expect=expect) 127 | 128 | def test_service_list_with_marker_limit(self): 129 | expect = [ 130 | ('GET', '/v1/services/?limit=2&marker=%s' % SERVICE2['id'], 131 | {}, None), 132 | ] 133 | self._test_service_list_with_filters( 134 | limit=2, marker=SERVICE2['id'], 135 | expect=expect) 136 | 137 | def test_service_list_with_sort_dir(self): 138 | expect = [ 139 | ('GET', '/v1/services/?sort_dir=asc', 140 | {}, None), 141 | ] 142 | self._test_service_list_with_filters( 143 | sort_dir='asc', 144 | expect=expect) 145 | 146 | def test_service_list_with_sort_key(self): 147 | expect = [ 148 | ('GET', '/v1/services/?sort_key=id', 149 | {}, None), 150 | ] 151 | self._test_service_list_with_filters( 152 | sort_key='id', 153 | expect=expect) 154 | 155 | def test_service_list_with_sort_key_dir(self): 156 | expect = [ 157 | ('GET', '/v1/services/?sort_key=id&sort_dir=desc', 158 | {}, None), 159 | ] 160 | self._test_service_list_with_filters( 161 | sort_key='id', sort_dir='desc', 162 | expect=expect) 163 | -------------------------------------------------------------------------------- /zunclient/tests/unit/v1/test_services_shell.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 NEC Corporation. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | from unittest import mock 16 | 17 | from zunclient.tests.unit.v1 import shell_test_base 18 | 19 | 20 | class ShellTest(shell_test_base.TestCommandLineArgument): 21 | 22 | @mock.patch('zunclient.v1.services.ServiceManager.list') 23 | def test_zun_service_list_success(self, mock_list): 24 | self._test_arg_success('service-list') 25 | self.assertTrue(mock_list.called) 26 | 27 | @mock.patch('zunclient.v1.services.ServiceManager.list') 28 | def test_zun_service_list_failure(self, mock_list): 29 | self._test_arg_failure('service-list --wrong', 30 | self._unrecognized_arg_error) 31 | self.assertFalse(mock_list.called) 32 | -------------------------------------------------------------------------------- /zunclient/tests/unit/v1/test_versions.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | import testtools 14 | from testtools import matchers 15 | 16 | from zunclient.tests.unit import utils 17 | from zunclient.v1 import versions 18 | 19 | 20 | VERSION1 = {'status': 'CURRENT', 21 | 'min_version': '1.1', 22 | 'max_version': '1.12', 23 | 'id': 'v1', 24 | } 25 | 26 | fake_responses = { 27 | 'https://example.com': 28 | { 29 | 'GET': ( 30 | {}, 31 | {'versions': [VERSION1]}, 32 | ), 33 | }, 34 | } 35 | 36 | 37 | class VersionManagerTest(testtools.TestCase): 38 | 39 | def test_version_list(self): 40 | self._test_version_list('https://example.com') 41 | self._test_version_list('https://example.com/v1') 42 | self._test_version_list('https://example.com/v1/') 43 | 44 | def _test_version_list(self, endpoint): 45 | api = utils.FakeAPI(fake_responses, endpoint=endpoint) 46 | mgr = versions.VersionManager(api) 47 | api_versions = mgr.list() 48 | expect = [ 49 | ('GET', 'https://example.com', {}, None), 50 | ] 51 | self.assertEqual(expect, api.calls) 52 | self.assertThat(api_versions, matchers.HasLength(1)) 53 | -------------------------------------------------------------------------------- /zunclient/tests/unit/v1/test_versions_shell.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 NEC Corporation. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | from unittest import mock 16 | 17 | from zunclient.tests.unit.v1 import shell_test_base 18 | 19 | 20 | class ShellTest(shell_test_base.TestCommandLineArgument): 21 | 22 | @mock.patch('zunclient.v1.versions.VersionManager.list') 23 | def test_zun_version_list_success(self, mock_list): 24 | self._test_arg_success('version-list') 25 | self.assertTrue(mock_list.called) 26 | 27 | @mock.patch('zunclient.v1.versions.VersionManager.list') 28 | def test_zun_version_list_failure(self, mock_list): 29 | self._test_arg_failure('version-list --wrong', 30 | self._unrecognized_arg_error) 31 | self.assertFalse(mock_list.called) 32 | -------------------------------------------------------------------------------- /zunclient/v1/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/python-zunclient/eb6806a8682914171dd17a44c6b80e78f7c6b0bc/zunclient/v1/__init__.py -------------------------------------------------------------------------------- /zunclient/v1/actions.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | from zunclient.common import base 14 | 15 | 16 | class Action(base.Resource): 17 | def __repr__(self): 18 | return "" % self._info 19 | 20 | 21 | class ActionManager(base.Manager): 22 | resource_class = Action 23 | 24 | @staticmethod 25 | def _path(container, request_id=None): 26 | 27 | if request_id: 28 | return '/v1/containers/%s/container_actions/%s' % (container, 29 | request_id) 30 | else: 31 | return '/v1/containers/%s/container_actions' % container 32 | 33 | def list(self, container): 34 | """Retrieve a list of actions. 35 | 36 | :returns: A list of actions. 37 | 38 | """ 39 | 40 | return self._list(self._path(container), "containerActions") 41 | 42 | def get(self, container, request_id): 43 | try: 44 | return self._list(self._path(container, request_id))[0] 45 | except IndexError: 46 | return None 47 | -------------------------------------------------------------------------------- /zunclient/v1/actions_shell.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | from zunclient.common import cliutils as utils 14 | from zunclient.common import utils as zun_utils 15 | 16 | 17 | def _show_action(action): 18 | utils.print_dict(action._info) 19 | 20 | 21 | @utils.arg('container', 22 | metavar='', 23 | help='ID or name of a container.') 24 | def do_action_list(cs, args): 25 | """Print a list of actions done on a container.""" 26 | container = args.container 27 | actions = cs.actions.list(container) 28 | columns = ('user_id', 'container_uuid', 'request_id', 'action', 29 | 'message', 'start_time') 30 | utils.print_list(actions, columns, 31 | {'versions': zun_utils.print_list_field('versions')}, 32 | sortby_index=None) 33 | 34 | 35 | @utils.arg('container', 36 | metavar='', 37 | help='ID or name of the container whose actions are showed.') 38 | @utils.arg('request_id', 39 | metavar='', 40 | help='request ID of action to describe.') 41 | def do_action_show(cs, args): 42 | """Describe a specific action.""" 43 | action = cs.actions.get(args.container, args.request_id) 44 | _show_action(action) 45 | -------------------------------------------------------------------------------- /zunclient/v1/availability_zones.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 NEC Corporation. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | from zunclient.common import base 16 | 17 | 18 | class AvailabilityZone(base.Resource): 19 | def __repr__(self): 20 | return "" % self._info 21 | 22 | 23 | class AvailabilityZoneManager(base.Manager): 24 | resource_class = AvailabilityZone 25 | 26 | @staticmethod 27 | def _path(): 28 | return '/v1/availability_zones' 29 | 30 | def list(self, **kwargs): 31 | return self._list(self._path(), "availability_zones", qparams=kwargs) 32 | -------------------------------------------------------------------------------- /zunclient/v1/availability_zones_shell.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 NEC Corporation. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | from zunclient.common import utils as zun_utils 16 | 17 | 18 | def do_availability_zone_list(cs, args): 19 | """Print a list of availability zones.""" 20 | zones = cs.availability_zones.list() 21 | zun_utils.list_availability_zones(zones) 22 | -------------------------------------------------------------------------------- /zunclient/v1/capsules.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Arm Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | from zunclient.common import base 16 | from zunclient.common import utils 17 | from zunclient import exceptions 18 | 19 | 20 | CREATION_ATTRIBUTES = ['template'] 21 | 22 | 23 | class Capsule(base.Resource): 24 | def __repr__(self): 25 | return "" % self._info 26 | 27 | 28 | class CapsuleManager(base.Manager): 29 | resource_class = Capsule 30 | 31 | @staticmethod 32 | def _path(id=None): 33 | 34 | if id: 35 | return '/capsules/%s' % id 36 | else: 37 | return '/capsules/' 38 | 39 | def get(self, id): 40 | try: 41 | return self._list(self._path(id))[0] 42 | except IndexError: 43 | return None 44 | 45 | def create(self, **kwargs): 46 | new = {} 47 | for (key, value) in kwargs.items(): 48 | if key in CREATION_ATTRIBUTES: 49 | new[key] = value 50 | else: 51 | raise exceptions.InvalidAttribute( 52 | "Key must be in %s" % ','.join(CREATION_ATTRIBUTES)) 53 | return self._create(self._path(), new) 54 | 55 | def list(self, marker=None, limit=None, sort_key=None, 56 | sort_dir=None, all_projects=False): 57 | """Retrieve a list of capsules. 58 | 59 | :param all_projects: Optional, list containers in all projects 60 | 61 | :param marker: Optional, the UUID of a containers, eg the last 62 | containers from a previous result set. Return 63 | the next result set. 64 | :param limit: The maximum number of results to return per 65 | request, if: 66 | 67 | 1) limit > 0, the maximum number of containers to return. 68 | 2) limit param is NOT specified (None), the number of items 69 | returned respect the maximum imposed by the ZUN API 70 | (see Zun's api.max_limit option). 71 | 72 | :param sort_key: Optional, field used for sorting. 73 | 74 | :param sort_dir: Optional, direction of sorting, either 'asc' (the 75 | default) or 'desc'. 76 | 77 | :returns: A list of containers. 78 | 79 | """ 80 | if limit is not None: 81 | limit = int(limit) 82 | 83 | filters = utils.common_filters(marker, limit, sort_key, 84 | sort_dir, all_projects) 85 | 86 | path = '' 87 | if filters: 88 | path += '?' + '&'.join(filters) 89 | 90 | if limit is None: 91 | return self._list(self._path(path), 92 | "capsules") 93 | else: 94 | return self._list_pagination(self._path(path), 95 | "capsules", 96 | limit=limit) 97 | 98 | def delete(self, id): 99 | return self._delete(self._path(id)) 100 | 101 | def describe(self, id): 102 | try: 103 | return self._list(self._path(id))[0] 104 | except IndexError: 105 | return None 106 | -------------------------------------------------------------------------------- /zunclient/v1/capsules_shell.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Arm Limited. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | import yaml 16 | 17 | from oslo_serialization import jsonutils 18 | 19 | from zunclient.common import cliutils as utils 20 | from zunclient.common import template_utils 21 | from zunclient.common import utils as zun_utils 22 | from zunclient.i18n import _ 23 | 24 | 25 | def _show_capsule(capsule): 26 | zun_utils.format_container_addresses(capsule) 27 | utils.print_dict(capsule._info) 28 | 29 | 30 | @utils.arg('-f', '--template-file', metavar='', 31 | required=True, help=_('Path to the template.')) 32 | def do_capsule_create(cs, args): 33 | """Create a capsule.""" 34 | opts = {} 35 | if args.template_file: 36 | template = template_utils.get_template_contents( 37 | args.template_file) 38 | opts['template'] = template 39 | cs.capsules.create(**opts) 40 | print("Request to create capsule has been accepted.") 41 | 42 | 43 | @utils.arg('--all-projects', 44 | action="store_true", 45 | default=False, 46 | help='List containers in all projects') 47 | @utils.arg('--marker', 48 | metavar='', 49 | default=None, 50 | help='The last container UUID of the previous page; ' 51 | 'displays list of containers after "marker".') 52 | @utils.arg('--limit', 53 | metavar='', 54 | type=int, 55 | help='Maximum number of containers to return') 56 | @utils.arg('--sort-key', 57 | metavar='', 58 | help='Column to sort results by') 59 | @utils.arg('--sort-dir', 60 | metavar='', 61 | choices=['desc', 'asc'], 62 | help='Direction to sort. "asc" or "desc".') 63 | def do_capsule_list(cs, args): 64 | """Print a list of available capsules.""" 65 | opts = {} 66 | opts['all_projects'] = args.all_projects 67 | opts['marker'] = args.marker 68 | opts['limit'] = args.limit 69 | opts['sort_key'] = args.sort_key 70 | opts['sort_dir'] = args.sort_dir 71 | opts = zun_utils.remove_null_parms(**opts) 72 | capsules = cs.capsules.list(**opts) 73 | zun_utils.list_capsules(capsules) 74 | 75 | 76 | @utils.arg('capsules', 77 | metavar='', 78 | nargs='+', 79 | help='ID or name of the (capsule)s to delete.') 80 | def do_capsule_delete(cs, args): 81 | """Delete specified capsules.""" 82 | for capsule in args.capsules: 83 | try: 84 | cs.capsules.delete(capsule) 85 | print("Request to delete capsule %s has been accepted." % 86 | capsule) 87 | except Exception as e: 88 | print("Delete for capsule %(capsule)s failed: %(e)s" % 89 | {'capsule': capsule, 'e': e}) 90 | 91 | 92 | @utils.arg('capsule', 93 | metavar='', 94 | help='ID or name of the capsule to show.') 95 | @utils.arg('-f', '--format', 96 | metavar='', 97 | action='store', 98 | choices=['json', 'yaml', 'table'], 99 | default='table', 100 | help='Print representation of the capsule. ' 101 | 'The choices of the output format is json,table,yaml. ' 102 | 'Defaults to table. ') 103 | def do_capsule_describe(cs, args): 104 | """Show details of a capsule.""" 105 | capsule = cs.capsules.describe(args.capsule) 106 | if args.format == 'json': 107 | print(jsonutils.dumps(capsule._info, indent=4, sort_keys=True)) 108 | elif args.format == 'yaml': 109 | print(yaml.safe_dump(capsule._info, default_flow_style=False)) 110 | elif args.format == 'table': 111 | _show_capsule(capsule) 112 | -------------------------------------------------------------------------------- /zunclient/v1/hosts.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | from zunclient.common import base 14 | from zunclient.common import utils 15 | 16 | 17 | class Host(base.Resource): 18 | def __repr__(self): 19 | return "" % self._info 20 | 21 | 22 | class HostManager(base.Manager): 23 | resource_class = Host 24 | 25 | @staticmethod 26 | def _path(id=None): 27 | 28 | if id: 29 | return '/v1/hosts/%s' % id 30 | else: 31 | return '/v1/hosts/' 32 | 33 | def list(self, marker=None, limit=None, sort_key=None, 34 | sort_dir=None): 35 | """Retrieve a list of hosts. 36 | 37 | :param marker: Optional, the UUID of an host, eg the last 38 | host from a previous result set. Return 39 | the next result set. 40 | :param limit: The maximum number of results to return per 41 | request, if: 42 | 43 | 1) limit > 0, the maximum number of hosts to return. 44 | 2) limit param is NOT specified (None), the number of items 45 | returned respect the maximum imposed by the Zun api 46 | 47 | :param sort_key: Optional, field used for sorting. 48 | 49 | :param sort_dir: Optional, direction of sorting, either 'asc' (the 50 | default) or 'desc'. 51 | 52 | :returns: A list of hosts. 53 | 54 | """ 55 | if limit is not None: 56 | limit = int(limit) 57 | 58 | filters = utils.common_filters(marker, limit, sort_key, sort_dir) 59 | 60 | path = '' 61 | if filters: 62 | path += '?' + '&'.join(filters) 63 | 64 | if limit is None: 65 | return self._list(self._path(path), 66 | "hosts") 67 | else: 68 | return self._list_pagination(self._path(path), 69 | "hosts", 70 | limit=limit) 71 | 72 | def get(self, id): 73 | try: 74 | return self._list(self._path(id))[0] 75 | except IndexError: 76 | return None 77 | -------------------------------------------------------------------------------- /zunclient/v1/hosts_shell.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | import yaml 14 | 15 | from oslo_serialization import jsonutils 16 | 17 | from zunclient.common import cliutils as utils 18 | from zunclient.common import utils as zun_utils 19 | 20 | 21 | @utils.arg('--marker', 22 | metavar='', 23 | default=None, 24 | help='The last host UUID of the previous page; ' 25 | 'displays list of hosts after "marker".') 26 | @utils.arg('--limit', 27 | metavar='', 28 | type=int, 29 | help='Maximum number of hosts to return') 30 | @utils.arg('--sort-key', 31 | metavar='', 32 | help='Column to sort results by') 33 | @utils.arg('--sort-dir', 34 | metavar='', 35 | choices=['desc', 'asc'], 36 | help='Direction to sort. "asc" or "desc".') 37 | def do_host_list(cs, args): 38 | """Print a list of available host.""" 39 | opts = {} 40 | opts['marker'] = args.marker 41 | opts['limit'] = args.limit 42 | opts['sort_key'] = args.sort_key 43 | opts['sort_dir'] = args.sort_dir 44 | opts = zun_utils.remove_null_parms(**opts) 45 | hosts = cs.hosts.list(**opts) 46 | columns = ('uuid', 'hostname', 'mem_total', 'cpus', 'disk_total') 47 | utils.print_list(hosts, columns, 48 | {'versions': zun_utils.print_list_field('versions')}, 49 | sortby_index=None) 50 | 51 | 52 | @utils.arg('host', 53 | metavar='', 54 | help='ID or name of the host to show.') 55 | @utils.arg('-f', '--format', 56 | metavar='', 57 | action='store', 58 | choices=['json', 'yaml', 'table'], 59 | default='table', 60 | help='Print representation of the host.' 61 | 'The choices of the output format is json,table,yaml.' 62 | 'Defaults to table.') 63 | def do_host_show(cs, args): 64 | """Show details of a host.""" 65 | host = cs.hosts.get(args.host) 66 | if args.format == 'json': 67 | print(jsonutils.dumps(host._info, indent=4, sort_keys=True)) 68 | elif args.format == 'yaml': 69 | print(yaml.safe_dump(host._info, default_flow_style=False)) 70 | elif args.format == 'table': 71 | utils.print_dict(host._info) 72 | -------------------------------------------------------------------------------- /zunclient/v1/images.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | from zunclient.common import base 14 | from zunclient.common import utils 15 | from zunclient import exceptions 16 | 17 | 18 | PULL_ATTRIBUTES = ['repo', 'host'] 19 | IMAGE_SEARCH_ATTRIBUTES = ['image', 'image_driver', 'exact_match'] 20 | 21 | 22 | class Image(base.Resource): 23 | def __repr__(self): 24 | return "" % self._info 25 | 26 | 27 | class ImageManager(base.Manager): 28 | resource_class = Image 29 | 30 | @staticmethod 31 | def _path(id=None): 32 | 33 | if id: 34 | return '/v1/images/%s' % id 35 | else: 36 | return '/v1/images/' 37 | 38 | def list(self, marker=None, limit=None, sort_key=None, 39 | sort_dir=None): 40 | """Retrieve a list of images. 41 | 42 | :param marker: Optional, the UUID of an image, eg the last 43 | image from a previous result set. Return 44 | the next result set. 45 | :param limit: The maximum number of results to return per 46 | request, if: 47 | 48 | 1) limit > 0, the maximum number of images to return. 49 | 2) limit param is NOT specified (None), the number of items 50 | returned respect the maximum imposed by the Zun api 51 | 52 | :param sort_key: Optional, field used for sorting. 53 | 54 | :param sort_dir: Optional, direction of sorting, either 'asc' (the 55 | default) or 'desc'. 56 | 57 | :returns: A list of images. 58 | 59 | """ 60 | if limit is not None: 61 | limit = int(limit) 62 | 63 | filters = utils.common_filters(marker, limit, sort_key, sort_dir) 64 | 65 | path = '' 66 | if filters: 67 | path += '?' + '&'.join(filters) 68 | 69 | if limit is None: 70 | return self._list(self._path(path), 71 | "images") 72 | else: 73 | return self._list_pagination(self._path(path), 74 | "images", 75 | limit=limit) 76 | 77 | def get(self, id): 78 | try: 79 | return self._list(self._path(id))[0] 80 | except IndexError: 81 | return None 82 | 83 | def create(self, **kwargs): 84 | new = {} 85 | for (key, value) in kwargs.items(): 86 | if key in PULL_ATTRIBUTES: 87 | new[key] = value 88 | else: 89 | raise exceptions.InvalidAttribute( 90 | "Key must be in %s" % ','.join(PULL_ATTRIBUTES)) 91 | return self._create(self._path(), new) 92 | 93 | def delete(self, image_id): 94 | """Delete an image 95 | 96 | :params image_id: uuid of the image. 97 | """ 98 | return self._delete(self._path(image_id)) 99 | 100 | def search_image(self, image, **kwargs): 101 | """Retrieves list of images based on image name and image_driver name 102 | 103 | :returns: A list of images based on the search query 104 | i.e., image_name & image_driver 105 | 106 | """ 107 | image_query = {} 108 | for (key, value) in kwargs.items(): 109 | if key in IMAGE_SEARCH_ATTRIBUTES: 110 | image_query[key] = value 111 | else: 112 | raise exceptions.InvalidAttribute( 113 | "Key must be in %s" % ','.join(IMAGE_SEARCH_ATTRIBUTES)) 114 | return self._search(self._path(image) + '/search', image_query) 115 | -------------------------------------------------------------------------------- /zunclient/v1/images_shell.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | from zunclient.common import cliutils as utils 14 | from zunclient.common import utils as zun_utils 15 | 16 | 17 | def _show_image(image): 18 | utils.print_dict(image._info) 19 | 20 | 21 | @utils.arg('image', 22 | metavar='', 23 | help='Name of the image') 24 | @utils.arg('host', 25 | metavar='', 26 | help='Name or UUID of the host') 27 | def do_pull(cs, args): 28 | """Pull an image into a host.""" 29 | opts = {} 30 | opts['repo'] = args.image 31 | opts['host'] = args.host 32 | _show_image(cs.images.create(**opts)) 33 | 34 | 35 | @utils.arg('--marker', 36 | metavar='', 37 | default=None, 38 | help='The last image UUID of the previous page; ' 39 | 'displays list of images after "marker".') 40 | @utils.arg('--limit', 41 | metavar='', 42 | type=int, 43 | help='Maximum number of images to return') 44 | @utils.arg('--sort-key', 45 | metavar='', 46 | help='Column to sort results by') 47 | @utils.arg('--sort-dir', 48 | metavar='', 49 | choices=['desc', 'asc'], 50 | help='Direction to sort. "asc" or "desc".') 51 | def do_image_list(cs, args): 52 | """Print a list of available images.""" 53 | opts = {} 54 | opts['marker'] = args.marker 55 | opts['limit'] = args.limit 56 | opts['sort_key'] = args.sort_key 57 | opts['sort_dir'] = args.sort_dir 58 | opts = zun_utils.remove_null_parms(**opts) 59 | images = cs.images.list(**opts) 60 | columns = ('uuid', 'image_id', 'repo', 'tag', 'size') 61 | utils.print_list(images, columns, 62 | {'versions': zun_utils.print_list_field('versions')}, 63 | sortby_index=None) 64 | 65 | 66 | @utils.arg('id', metavar='', help='UUID of image to describe.') 67 | def do_image_show(cs, args): 68 | """Describe a specific image.""" 69 | image = cs.images.get(args.id) 70 | _show_image(image) 71 | 72 | 73 | @utils.arg('id', 74 | metavar='', 75 | help='UUID of image to delete') 76 | def do_image_delete(cs, args): 77 | """Delete a specified image.""" 78 | opts = {} 79 | opts['image_id'] = args.id 80 | cs.images.delete(**opts) 81 | 82 | 83 | @utils.arg('image', 84 | metavar='', 85 | help='Name of the image') 86 | @utils.arg('--image_driver', 87 | metavar='', 88 | choices=['glance', 'docker'], 89 | default='docker', 90 | help='Name of the image driver (glance, docker)') 91 | @utils.arg('--exact-match', 92 | default=False, 93 | action='store_true', 94 | help='exact match image name') 95 | def do_image_search(cs, args): 96 | """Print list of available images from repository based on user query.""" 97 | opts = {} 98 | opts['image_driver'] = args.image_driver 99 | opts['exact_match'] = args.exact_match 100 | images = cs.images.search_image(args.image, **opts) 101 | columns = ('ID', 'Name', 'Tags', 'Status', 'Size', 'Metadata') 102 | utils.print_list(images, columns, 103 | {'versions': zun_utils.print_list_field('versions')}, 104 | sortby_index=None) 105 | -------------------------------------------------------------------------------- /zunclient/v1/quota_classes.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | from zunclient.common import base 14 | 15 | 16 | class QuotaClass(base.Resource): 17 | def __repr__(self): 18 | return "" % self._info 19 | 20 | 21 | class QuotaClassManager(base.Manager): 22 | resource_class = QuotaClass 23 | 24 | @staticmethod 25 | def _path(quota_class_name): 26 | return '/v1/quota_classes/{}' . format(quota_class_name) 27 | 28 | def get(self, quota_class_name): 29 | return self._list(self._path(quota_class_name))[0] 30 | 31 | def update(self, quota_class_name, containers=None, 32 | memory=None, cpu=None, disk=None): 33 | resources = {} 34 | if cpu is not None: 35 | resources['cpu'] = cpu 36 | if memory is not None: 37 | resources['memory'] = memory 38 | if containers is not None: 39 | resources['containers'] = containers 40 | if disk is not None: 41 | resources['disk'] = disk 42 | return self._update(self._path(quota_class_name), 43 | resources, method='PUT') 44 | -------------------------------------------------------------------------------- /zunclient/v1/quota_classes_shell.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | from zunclient.common import cliutils as utils 14 | 15 | 16 | @utils.arg( 17 | '--containers', 18 | metavar='', 19 | type=int, 20 | help='The number of containers allowed per project') 21 | @utils.arg( 22 | '--cpu', 23 | metavar='', 24 | type=int, 25 | help='The number of container cores or vCPUs allowed per project') 26 | @utils.arg( 27 | '--memory', 28 | metavar='', 29 | type=int, 30 | help='The number of megabytes of container RAM allowed per project') 31 | @utils.arg( 32 | '--disk', 33 | metavar='', 34 | type=int, 35 | help='The number of gigabytes of container Disk allowed per project') 36 | @utils.arg( 37 | 'quota_class_name', 38 | metavar='', 39 | help='The name of quota class') 40 | def do_quota_class_update(cs, args): 41 | """Print an updated quotas for a quota class""" 42 | utils.print_dict(cs.quota_classes.update( 43 | args.quota_class_name, 44 | containers=args.containers, 45 | memory=args.memory, 46 | cpu=args.cpu, 47 | disk=args.disk)._info) 48 | 49 | 50 | @utils.arg( 51 | 'quota_class_name', 52 | metavar='', 53 | help='The name of quota class') 54 | def do_quota_class_get(cs, args): 55 | """Print a quotas for a quota class""" 56 | utils.print_dict(cs.quota_classes.get(args.quota_class_name)._info) 57 | -------------------------------------------------------------------------------- /zunclient/v1/quotas.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | from zunclient.common import base 14 | 15 | 16 | class Quota(base.Resource): 17 | def __repr__(self): 18 | return "" % self._info 19 | 20 | 21 | class QuotaManager(base.Manager): 22 | resource_class = Quota 23 | 24 | @staticmethod 25 | def _path(project_id): 26 | if project_id is not None: 27 | return '/v1/quotas/{}'.format(project_id) 28 | return '/v1/quotas' 29 | 30 | def get(self, project_id, **kwargs): 31 | if not kwargs.get('usages'): 32 | kwargs = {} 33 | return self._list(self._path(project_id), qparams=kwargs)[0] 34 | 35 | def update(self, project_id, containers=None, 36 | memory=None, cpu=None, disk=None): 37 | resources = {} 38 | if cpu is not None: 39 | resources['cpu'] = cpu 40 | if memory is not None: 41 | resources['memory'] = memory 42 | if containers is not None: 43 | resources['containers'] = containers 44 | if disk is not None: 45 | resources['disk'] = disk 46 | return self._update(self._path(project_id), resources, method='PUT') 47 | 48 | def defaults(self, project_id): 49 | return self._list(self._path(project_id) + '/defaults')[0] 50 | 51 | def delete(self, project_id): 52 | return self._delete(self._path(project_id)) 53 | -------------------------------------------------------------------------------- /zunclient/v1/quotas_shell.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | from zunclient.common import cliutils as utils 14 | 15 | 16 | @utils.arg( 17 | '--containers', 18 | metavar='', 19 | type=int, 20 | help='The number of containers allowed per project') 21 | @utils.arg( 22 | '--cpu', 23 | metavar='', 24 | type=int, 25 | help='The number of container cores or vCPUs allowed per project') 26 | @utils.arg( 27 | '--memory', 28 | metavar='', 29 | type=int, 30 | help='The number of megabytes of container RAM allowed per project') 31 | @utils.arg( 32 | '--disk', 33 | metavar='', 34 | type=int, 35 | help='The number of gigabytes of container Disk allowed per project') 36 | @utils.arg( 37 | 'project_id', 38 | metavar='', 39 | help='The UUID of project in a multi-project cloud') 40 | def do_quota_update(cs, args): 41 | """Print an updated quotas for a project""" 42 | utils.print_dict(cs.quotas.update(args.project_id, 43 | containers=args.containers, 44 | memory=args.memory, 45 | cpu=args.cpu, 46 | disk=args.disk)._info) 47 | 48 | 49 | @utils.arg( 50 | '--usages', 51 | default=False, 52 | action='store_true', 53 | help='Whether show quota usage statistic or not') 54 | @utils.arg( 55 | 'project_id', 56 | metavar='', 57 | help='The UUID of project in a multi-project cloud') 58 | def do_quota_get(cs, args): 59 | """Print a quotas for a project with usages (optional)""" 60 | if args.usages: 61 | utils.print_dict( 62 | cs.quotas.get(args.project_id, usages=args.usages)._info, 63 | value_fields=('limit', 'in_use')) 64 | else: 65 | utils.print_dict( 66 | cs.quotas.get(args.project_id, usages=args.usages)._info) 67 | 68 | 69 | @utils.arg( 70 | 'project_id', 71 | metavar='', 72 | help='The UUID of project in a multi-project cloud') 73 | def do_quota_defaults(cs, args): 74 | """Print a default quotas for a project""" 75 | utils.print_dict(cs.quotas.defaults(args.project_id)._info) 76 | 77 | 78 | @utils.arg( 79 | 'project_id', 80 | metavar='', 81 | help='The UUID of project in a multi-project cloud') 82 | def do_quota_delete(cs, args): 83 | """Delete quotas for a project""" 84 | cs.quotas.delete(args.project_id) 85 | -------------------------------------------------------------------------------- /zunclient/v1/registries.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | from zunclient.common import base 14 | from zunclient.common import utils 15 | from zunclient import exceptions 16 | 17 | 18 | CREATION_ATTRIBUTES = ['name', 'domain', 'username', 'password'] 19 | 20 | 21 | class Registry(base.Resource): 22 | def __repr__(self): 23 | return "" % self._info 24 | 25 | 26 | class RegistryManager(base.Manager): 27 | resource_class = Registry 28 | 29 | @staticmethod 30 | def _path(id=None): 31 | 32 | if id: 33 | return '/v1/registries/%s' % id 34 | else: 35 | return '/v1/registries' 36 | 37 | def list(self, marker=None, limit=None, sort_key=None, 38 | sort_dir=None, all_projects=False, **kwargs): 39 | """Retrieve a list of registries. 40 | 41 | :param all_projects: Optional, list registries in all projects 42 | 43 | :param marker: Optional, the UUID of a registries, eg the last 44 | registries from a previous result set. Return 45 | the next result set. 46 | :param limit: The maximum number of results to return per 47 | request, if: 48 | 49 | 1) limit > 0, the maximum number of registries to return. 50 | 2) limit param is NOT specified (None), the number of items 51 | returned respect the maximum imposed by the ZUN API 52 | (see Zun's api.max_limit option). 53 | 54 | :param sort_key: Optional, field used for sorting. 55 | 56 | :param sort_dir: Optional, direction of sorting, either 'asc' (the 57 | default) or 'desc'. 58 | 59 | :returns: A list of registries. 60 | 61 | """ 62 | if limit is not None: 63 | limit = int(limit) 64 | 65 | filters = utils.common_filters(marker, limit, sort_key, 66 | sort_dir, all_projects) 67 | path = '' 68 | if filters: 69 | path += '?' + '&'.join(filters) 70 | 71 | if limit is None: 72 | return self._list(self._path(path), 73 | "registries", qparams=kwargs) 74 | else: 75 | return self._list_pagination(self._path(path), 76 | "registries", 77 | limit=limit) 78 | 79 | def get(self, id, **kwargs): 80 | try: 81 | return self._list(self._path(id), 82 | qparams=kwargs)[0] 83 | except IndexError: 84 | return None 85 | 86 | def create(self, **kwargs): 87 | new = {'registry': {}} 88 | for (key, value) in kwargs.items(): 89 | if key in CREATION_ATTRIBUTES: 90 | new['registry'][key] = value 91 | else: 92 | raise exceptions.InvalidAttribute( 93 | "Key must be in %s" % ','.join(CREATION_ATTRIBUTES)) 94 | return self._create(self._path(), new) 95 | 96 | def delete(self, id, **kwargs): 97 | return self._delete(self._path(id), 98 | qparams=kwargs) 99 | 100 | def update(self, id, **patch): 101 | kwargs = {'registry': patch} 102 | return self._update(self._path(id), kwargs) 103 | -------------------------------------------------------------------------------- /zunclient/v1/services.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | from urllib import parse 14 | 15 | from zunclient.common import base 16 | from zunclient.common import utils 17 | 18 | 19 | class Service(base.Resource): 20 | def __repr__(self): 21 | return "" % self._info 22 | 23 | 24 | class ServiceManager(base.Manager): 25 | resource_class = Service 26 | 27 | @staticmethod 28 | def _path(id=None): 29 | return '/v1/services/%s' % id if id else '/v1/services' 30 | 31 | def list(self, marker=None, limit=None, sort_key=None, 32 | sort_dir=None): 33 | """Retrieve list of zun services. 34 | 35 | :param marker: Optional, the ID of a zun service, eg the last 36 | services from a previous result set. Return 37 | the next result set. 38 | :param limit: The maximum number of results to return per 39 | request, if: 40 | 41 | 1) limit > 0, the maximum number of services to return. 42 | 2) limit param is NOT specified (None), the number of items 43 | returned respect the maximum imposed by the Zun API 44 | (see Zun's api.max_limit option). 45 | 46 | :param sort_key: Optional, field used for sorting. 47 | 48 | :param sort_dir: Optional, direction of sorting, either 'asc' (the 49 | default) or 'desc'. 50 | 51 | :returns: A list of services. 52 | """ 53 | 54 | if limit is not None: 55 | limit = int(limit) 56 | 57 | filters = utils.common_filters(marker, limit, sort_key, sort_dir) 58 | 59 | path = '' 60 | if filters: 61 | path += '?' + '&'.join(filters) 62 | 63 | if limit is None: 64 | return self._list(self._path(path), "services") 65 | else: 66 | return self._list_pagination(self._path(path), "services", 67 | limit=limit) 68 | 69 | def delete(self, host, binary): 70 | """Delete a service.""" 71 | return self._delete(self._path(), 72 | qparams={'host': host, 73 | 'binary': binary}) 74 | 75 | def _action(self, action, method='PUT', qparams=None, **kwargs): 76 | if qparams: 77 | action = "%s?%s" % (action, 78 | parse.urlencode(qparams)) 79 | kwargs.setdefault('headers', {}) 80 | kwargs['headers'].setdefault('Content-Length', '0') 81 | resp, body = self.api.json_request(method, 82 | self._path() + action, 83 | **kwargs) 84 | return resp, body 85 | 86 | def _update_body(self, host, binary, disabled_reason=None, 87 | force_down=None): 88 | body = {"host": host, 89 | "binary": binary} 90 | if disabled_reason is not None: 91 | body["disabled_reason"] = disabled_reason 92 | if force_down is not None: 93 | body["forced_down"] = force_down 94 | return body 95 | 96 | def enable(self, host, binary): 97 | """Enable the service specified by hostname and binary.""" 98 | body = self._update_body(host, binary) 99 | return self._action("/enable", qparams=body) 100 | 101 | def disable(self, host, binary, reason=None): 102 | """Disable the service specified by hostname and binary.""" 103 | body = self._update_body(host, binary, reason) 104 | return self._action("/disable", qparams=body) 105 | 106 | def force_down(self, host, binary, force_down=None): 107 | """Force service state to down specified by hostname and binary.""" 108 | body = self._update_body(host, binary, force_down=force_down) 109 | return self._action("/force_down", qparams=body) 110 | -------------------------------------------------------------------------------- /zunclient/v1/services_shell.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 NEC Corporation. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | 16 | from zunclient.common import cliutils as utils 17 | from zunclient.common import utils as zun_utils 18 | 19 | 20 | def do_service_list(cs, args): 21 | """Print a list of zun services.""" 22 | services = cs.services.list() 23 | columns = ('Id', 'Host', 'Binary', 'State', 'Disabled', 24 | 'Disabled Reason', 'Updated At', 25 | 'Availability Zone') 26 | utils.print_list(services, columns, 27 | {'versions': zun_utils.print_list_field('versions')}) 28 | 29 | 30 | @utils.arg('host', 31 | metavar='', 32 | help='Name of host.') 33 | @utils.arg('binary', 34 | metavar='', 35 | help='Name of the binary to delete.') 36 | def do_service_delete(cs, args): 37 | """Delete the Zun binaries/services.""" 38 | try: 39 | cs.services.delete(args.host, args.binary) 40 | print("Request to delete binary %s on host %s has been accepted." % 41 | (args.binary, args.host)) 42 | except Exception as e: 43 | print("Delete for binary %(binary)s on host %(host)s failed: %(e)s" % 44 | {'binary': args.binary, 'host': args.host, 'e': e}) 45 | 46 | 47 | @utils.arg('host', metavar='', help='Name of host.') 48 | @utils.arg('binary', metavar='', help='Service binary.') 49 | def do_service_enable(cs, args): 50 | """Enable the Zun service.""" 51 | res = cs.services.enable(args.host, args.binary) 52 | utils.print_dict(res[1]['service']) 53 | 54 | 55 | @utils.arg('host', metavar='', help='Name of host.') 56 | @utils.arg('binary', metavar='', help='Service binary.') 57 | @utils.arg( 58 | '--reason', 59 | metavar='', 60 | help='Reason for disabling service.') 61 | def do_service_disable(cs, args): 62 | """Disable the Zun service.""" 63 | res = cs.services.disable(args.host, args.binary, args.reason) 64 | utils.print_dict(res[1]['service']) 65 | 66 | 67 | @utils.arg('host', metavar='', help='Name of host.') 68 | @utils.arg('binary', metavar='', help='Service binary.') 69 | @utils.arg( 70 | '--unset', 71 | dest='force_down', 72 | help="Unset the force state down of service.", 73 | action='store_false', 74 | default=True) 75 | def do_service_force_down(cs, args): 76 | """Force Zun service to down or unset the force state.""" 77 | res = cs.services.force_down(args.host, args.binary, args.force_down) 78 | utils.print_dict(res[1]['service']) 79 | -------------------------------------------------------------------------------- /zunclient/v1/shell.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 2 | # The Cloudscaling Group, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | # use this file except in compliance with the License. You may obtain a copy 6 | # of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | from zunclient.v1 import actions_shell 17 | from zunclient.v1 import availability_zones_shell 18 | from zunclient.v1 import capsules_shell 19 | from zunclient.v1 import containers_shell 20 | from zunclient.v1 import hosts_shell 21 | from zunclient.v1 import images_shell 22 | from zunclient.v1 import quota_classes_shell 23 | from zunclient.v1 import quotas_shell 24 | from zunclient.v1 import registries_shell 25 | from zunclient.v1 import services_shell 26 | from zunclient.v1 import versions_shell 27 | 28 | COMMAND_MODULES = [ 29 | availability_zones_shell, 30 | containers_shell, 31 | images_shell, 32 | services_shell, 33 | hosts_shell, 34 | versions_shell, 35 | capsules_shell, 36 | actions_shell, 37 | quotas_shell, 38 | quota_classes_shell, 39 | registries_shell, 40 | ] 41 | -------------------------------------------------------------------------------- /zunclient/v1/versions.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | import urllib 14 | 15 | from zunclient.common import base 16 | 17 | 18 | class Version(base.Resource): 19 | def __repr__(self): 20 | return "" 21 | 22 | 23 | class VersionManager(base.Manager): 24 | resource_class = Version 25 | 26 | def list(self): 27 | endpoint = self.api.get_endpoint() 28 | url = urllib.parse.urlparse(endpoint) 29 | # NOTE(hongbin): endpoint URL has at least 2 formats: 30 | # 1. the classic (legacy) endpoint: 31 | # http://{host}:{optional_port}/v1/ 32 | # 2. under wsgi: 33 | # http://{host}:{optional_port}/container/v1 34 | if url.path.endswith("v1") or "/v1/" in url.path: 35 | # this way should handle all 2 possible formats 36 | path = url.path[:url.path.rfind("/v1")] 37 | version_url = '%s://%s%s' % (url.scheme, url.netloc, path) 38 | else: 39 | # NOTE(hongbin): probably, it is one of the next cases: 40 | # * https://container.example.com/ 41 | # * https://example.com/container 42 | # leave as is without cropping. 43 | version_url = endpoint 44 | 45 | return self._list(version_url, "versions") 46 | 47 | def get_current(self): 48 | for version in self.list(): 49 | if version.status == "CURRENT": 50 | return version 51 | return None 52 | -------------------------------------------------------------------------------- /zunclient/v1/versions_shell.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | from zunclient import api_versions 14 | from zunclient.common import cliutils as utils 15 | 16 | 17 | def do_version_list(cs, args): 18 | """List all API versions.""" 19 | print("Client supported API versions:") 20 | print("Minimum version %(v)s" % 21 | {'v': api_versions.MIN_API_VERSION}) 22 | print("Maximum version %(v)s" % 23 | {'v': api_versions.MAX_API_VERSION}) 24 | 25 | print("\nServer supported API versions:") 26 | result = cs.versions.list() 27 | columns = ["Id", "Status", "Min Version", "Max Version"] 28 | utils.print_list(result, columns) 29 | -------------------------------------------------------------------------------- /zunclient/version.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 2 | # The Cloudscaling Group, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | # use this file except in compliance with the License. You may obtain a copy 6 | # of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | from pbr import version 17 | 18 | version_info = version.VersionInfo('python-zunclient') 19 | --------------------------------------------------------------------------------