├── .coveragerc ├── .gitignore ├── .gitreview ├── .mailmap ├── .stestr.conf ├── .zuul.yaml ├── CONTRIBUTING.rst ├── HACKING.rst ├── LICENSE ├── README.rst ├── bindep.txt ├── brick_cinderclient_ext ├── __init__.py ├── brick_utils.py ├── client.py ├── exceptions.py ├── tests │ ├── __init__.py │ ├── functional │ │ ├── __init__.py │ │ └── test_brick_client.py │ └── unit │ │ ├── __init__.py │ │ ├── test_brick_client.py │ │ ├── test_brick_utils.py │ │ ├── test_cli.py │ │ └── test_volume_actions.py ├── version.py └── volume_actions.py ├── doc ├── requirements.txt └── source │ ├── conf.py │ ├── contributor │ └── contributing.rst │ ├── index.rst │ ├── installation.rst │ ├── readme.rst │ └── usage.rst ├── releasenotes ├── notes │ ├── add_get_connector_nic_attr-fd429b22d02f8621.yaml │ ├── add_local_attach_nic_attr-923f867bd565dc36.yaml │ ├── drop-py2-5a229fe56b86cbe9.yaml │ ├── drop-python-3-6-and-3-7-f8afbd8e0d0a98e0.yaml │ ├── local-attach-feature-474283267873f091.yaml │ ├── require-root-for-attach-detach-commands-c3a63f6c5213e28c.yaml │ ├── start-using-reno-819e161ed41a130f.yaml │ └── windows-support-c12b7b922aa43272.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 │ ├── newton.rst │ ├── ocata.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 └── tox.ini /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | source = brick-python-cinderclient-ext 4 | omit = brick-python-cinderclient-ext/openstack/* 5 | 6 | [report] 7 | ignore_errors = True 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | /.* 3 | 4 | # C extensions 5 | *.so 6 | 7 | # Things to include 8 | !.gitignore 9 | !.zuul.yaml 10 | 11 | # Packages 12 | *.egg 13 | *.egg-info 14 | dist 15 | build 16 | .eggs 17 | eggs 18 | parts 19 | bin 20 | var 21 | sdist 22 | develop-eggs 23 | .installed.cfg 24 | lib 25 | lib64 26 | 27 | # Installer logs 28 | pip-log.txt 29 | 30 | # Unit test / coverage reports 31 | .coverage 32 | .tox 33 | nosetests.xml 34 | .stestr 35 | .venv 36 | cover 37 | 38 | # Translations 39 | *.mo 40 | 41 | # Mr Developer 42 | .mr.developer.cfg 43 | .project 44 | .pydevproject 45 | 46 | # Complexity 47 | output/*.html 48 | output/*/index.html 49 | 50 | # Sphinx 51 | doc/build 52 | 53 | # Release notes 54 | releasenotes/build/ 55 | 56 | # pbr generates these 57 | AUTHORS 58 | ChangeLog 59 | 60 | # Editors 61 | *~ 62 | .*.swp 63 | .*sw? 64 | -------------------------------------------------------------------------------- /.gitreview: -------------------------------------------------------------------------------- 1 | [gerrit] 2 | host=review.opendev.org 3 | port=29418 4 | project=openstack/python-brick-cinderclient-ext.git 5 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | # Format is: 2 | # 3 | # 4 | -------------------------------------------------------------------------------- /.stestr.conf: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | test_path=${OS_TEST_PATH:-./brick_cinderclient_ext/tests/unit} 3 | top_dir=./ 4 | -------------------------------------------------------------------------------- /.zuul.yaml: -------------------------------------------------------------------------------- 1 | - job: 2 | name: brick-cinderclient-dsvm-functional-base 3 | abstract: true 4 | parent: devstack-tox-functional 5 | description: | 6 | devstack-based functional tests for python-brick-cinderclient-ext 7 | required-projects: 8 | - openstack/python-cinderclient 9 | timeout: 4200 10 | vars: 11 | devstack_localrc: 12 | LIBS_FROM_GIT: python-cinderclient 13 | devstack_services: 14 | # turn off ceilometer 15 | ceilometer-acentral: false 16 | ceilometer-acompute: false 17 | ceilometer-alarm-evaluator: false 18 | ceilometer-alarm-notifier: false 19 | ceilometer-anotification: false 20 | ceilometer-api: false 21 | ceilometer-collector: false 22 | # turn off swift 23 | s-account: false 24 | s-container: false 25 | s-object: false 26 | s-proxy: false 27 | # turn off glance 28 | g-api: false 29 | g-reg: false 30 | # turn off nova 31 | n-api: false 32 | n-api-meta: false 33 | n-cauth: false 34 | n-cond: false 35 | n-cpu: false 36 | n-novnc: false 37 | n-obj: false 38 | n-sch: false 39 | placement-api: false 40 | # turn off misc 41 | horizon: false 42 | tempest: false 43 | c-bak: false 44 | # Hardcode brick-cinderclient path so the job can be run on other repo patches 45 | zuul_work_dir: src/opendev.org/openstack/python-brick-cinderclient-ext 46 | 47 | - job: 48 | name: brick-cinderclient-dsvm-functional-py310 49 | parent: brick-cinderclient-dsvm-functional-base 50 | # Python 3.10 is the default on Ubuntu 22.04 (Jammy) 51 | nodeset: openstack-single-node-jammy 52 | vars: 53 | python_version: 3.10 54 | tox_envlist: functional-py310 55 | 56 | - job: 57 | name: brick-cinderclient-dsvm-functional-py312 58 | parent: brick-cinderclient-dsvm-functional-base 59 | vars: 60 | python_version: 3.12 61 | tox_envlist: functional-py312 62 | 63 | - project: 64 | templates: 65 | - check-requirements 66 | - openstack-python3-jobs 67 | - publish-openstack-docs-pti 68 | - release-notes-jobs-python3 69 | check: 70 | jobs: 71 | - brick-cinderclient-dsvm-functional-py310 72 | - brick-cinderclient-dsvm-functional-py312 73 | gate: 74 | jobs: 75 | - brick-cinderclient-dsvm-functional-py310 76 | - brick-cinderclient-dsvm-functional-py312 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | The source repository for this project can be found at: 2 | 3 | https://opendev.org/openstack/python-brick-cinderclient-ext 4 | 5 | Pull requests submitted through GitHub are not monitored. 6 | 7 | To start contributing to OpenStack, follow the steps in the contribution guide 8 | to set up and use Gerrit: 9 | 10 | https://docs.openstack.org/contributors/code-and-documentation/quick-start.html 11 | 12 | Bugs should be filed on Launchpad: 13 | 14 | https://bugs.launchpad.net/python-cinderclient 15 | 16 | .. note:: 17 | When creating a bug, please tag it with 'brick-cinderclient-ext'. 18 | To create a tag, select the 'Extra options' after filling in the 19 | bug description and type **brick-cinderclient-ext** in the 'Tags' 20 | text box. 21 | 22 | For more specific information about contributing to this repository, see the 23 | python-brick-cinderclient-ext contributor guide: 24 | 25 | https://docs.openstack.org/python-brick-cinderclient-ext/latest/contributor/contributing.html 26 | -------------------------------------------------------------------------------- /HACKING.rst: -------------------------------------------------------------------------------- 1 | brick-python-cinderclient-ext Style Commandments 2 | ================================================ 3 | 4 | Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/ 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ======================== 2 | Team and repository tags 3 | ======================== 4 | 5 | .. image:: https://governance.openstack.org/tc/badges/python-brick-cinderclient-ext.svg 6 | :target: https://governance.openstack.org/tc/reference/tags/index.html 7 | 8 | .. Change things from this point on 9 | 10 | ============================= 11 | python-brick-cinderclient-ext 12 | ============================= 13 | 14 | OpenStack Cinder Brick client for local volume attachement 15 | 16 | Features 17 | -------- 18 | 19 | * Get volume connector information 20 | 21 | 22 | Dependencies 23 | ------------ 24 | 25 | Requires dependency:: 26 | 27 | * python-cinderclient 28 | 29 | Optional dependencies based on Cinder driver's protocol:: 30 | 31 | * open-iscsi, udev - for volume attachment via iSCSI 32 | 33 | NOTE (e0ne): current version is tested on Linux and Windows hosts 34 | 35 | For any other information, refer to the parent projects, Cinder and 36 | python-cinderclient:: 37 | 38 | * https://opendev.org/openstack/cinder 39 | * https://opendev.org/openstack/python-cinderclient 40 | 41 | * License: Apache License, Version 2.0 42 | * Documentation: https://docs.openstack.org/python-brick-cinderclient-ext/latest/ 43 | * Source: https://opendev.org/openstack/python-brick-cinderclient-ext 44 | * Bugs: https://bugs.launchpad.net/python-cinderclient 45 | * Release notes: https://docs.openstack.org/releasenotes/python-brick-cinderclient-ext 46 | -------------------------------------------------------------------------------- /bindep.txt: -------------------------------------------------------------------------------- 1 | # This is a cross-platform list tracking distribution packages needed for 2 | # install and tests 3 | # see http://docs.openstack.org/infra/bindep/ for additional information. 4 | 5 | open-iscsi [platform:dpkg] 6 | iscsi-initiator-utils [platform:rpm] 7 | udev [platform:dpkg platform:rpm] 8 | -------------------------------------------------------------------------------- /brick_cinderclient_ext/__init__.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 | 14 | """Command-line interface to the os-brick.""" 15 | 16 | import json 17 | import socket 18 | 19 | from cinderclient import utils 20 | import pbr.version 21 | 22 | from brick_cinderclient_ext import brick_utils 23 | from brick_cinderclient_ext import client as brick_client 24 | 25 | 26 | __version__ = pbr.version.VersionInfo( 27 | 'python-brick-cinderclient-ext').version_string() 28 | 29 | 30 | VOLUME_ID_HELP_MESSAGE = 'Name or other Identifier for existing volume' 31 | MULTIPATH_HELP_MESSAGE = ('Set True if connector wants to use multipath.' 32 | 'Default value is False.') 33 | ENFORCE_MULTIPATH_HELP_MESSAGE = ( 34 | 'If enforce_multipath=True is specified too, an exception is thrown when ' 35 | 'multipathd is not running. Otherwise, it falls back to multipath=False ' 36 | 'and only the first path shown up is used.') 37 | NETWORK_INTERFACE_HELP_MESSAGE = ('Use a specific network interface to ' 38 | 'determine IP address.') 39 | LOCAL_ATTACH_NIC_HELP_MESSAGE = ('Use a specific network interface for ' 40 | 'connector during attach operation.') 41 | 42 | 43 | @utils.arg('--multipath', 44 | metavar='', 45 | default=False, 46 | help=MULTIPATH_HELP_MESSAGE) 47 | @utils.arg('--enforce_multipath', 48 | metavar='', 49 | default=False, 50 | help=ENFORCE_MULTIPATH_HELP_MESSAGE) 51 | @utils.arg('--nic', 52 | metavar='', 53 | default=None, 54 | help=NETWORK_INTERFACE_HELP_MESSAGE) 55 | @brick_utils.require_root 56 | def do_get_connector(client, args): 57 | """Get the connection properties for all protocols.""" 58 | brickclient = brick_client.Client(client) 59 | connector = brickclient.get_connector(args.multipath, 60 | args.enforce_multipath, 61 | args.nic) 62 | utils.print_dict(connector) 63 | 64 | 65 | @utils.arg('identifier', 66 | metavar='', 67 | help=VOLUME_ID_HELP_MESSAGE) 68 | @utils.arg('--hostname', 69 | metavar='', 70 | default=socket.gethostname(), 71 | help='hostname') 72 | @utils.arg('--mountpoint', 73 | metavar='', 74 | default=None, 75 | help='mountpoint') 76 | @utils.arg('--mode', 77 | metavar='', 78 | default='rw', 79 | help='mode') 80 | @utils.arg('--multipath', 81 | metavar='', 82 | default=False, 83 | help=MULTIPATH_HELP_MESSAGE) 84 | @utils.arg('--enforce_multipath', 85 | metavar='', 86 | default=False, 87 | help=ENFORCE_MULTIPATH_HELP_MESSAGE) 88 | @utils.arg('--nic', 89 | metavar='', 90 | default=None, 91 | help=LOCAL_ATTACH_NIC_HELP_MESSAGE) 92 | @brick_utils.require_root 93 | def do_local_attach(client, args): 94 | hostname = args.hostname 95 | volume = args.identifier 96 | brickclient = brick_client.Client(client) 97 | device_info = brickclient.attach(volume, 98 | hostname, 99 | args.mountpoint, 100 | args.mode, 101 | args.multipath, 102 | args.enforce_multipath, 103 | args.nic) 104 | utils.print_dict(device_info) 105 | 106 | 107 | @utils.arg('identifier', 108 | metavar='', 109 | help=VOLUME_ID_HELP_MESSAGE) 110 | @utils.arg('--attachment_uuid', 111 | metavar='', 112 | default=None, 113 | help='The uuid of the volume attachment.') 114 | @utils.arg('--multipath', 115 | metavar='', 116 | default=False, 117 | help=MULTIPATH_HELP_MESSAGE) 118 | @utils.arg('--enforce_multipath', 119 | metavar='', 120 | default=False, 121 | help=ENFORCE_MULTIPATH_HELP_MESSAGE) 122 | @utils.arg('--device_info', 123 | metavar='', 124 | default=None, 125 | help='The device_info is returned from connect_volume.') 126 | @brick_utils.require_root 127 | def do_local_detach(client, args): 128 | volume = args.identifier 129 | brickclient = brick_client.Client(client) 130 | device_info = None 131 | if args.device_info: 132 | device_info = json.loads(args.device_info) 133 | 134 | brickclient.detach(volume, args.attachment_uuid, args.multipath, 135 | args.enforce_multipath, device_info) 136 | 137 | 138 | @utils.arg('identifier', 139 | metavar='', 140 | help=VOLUME_ID_HELP_MESSAGE) 141 | @utils.arg('--multipath', 142 | metavar='', 143 | default=False, 144 | help=MULTIPATH_HELP_MESSAGE) 145 | @brick_utils.require_root 146 | def do_get_volume_paths(client, args): 147 | """Get volume paths for a volume.""" 148 | volume = args.identifier 149 | brickclient = brick_client.Client(client) 150 | 151 | paths = brickclient.get_volume_paths(volume, args.multipath) 152 | if paths: 153 | print('\n'.join(paths)) 154 | 155 | 156 | @utils.arg('--multipath', 157 | metavar='', 158 | default=False, 159 | help=MULTIPATH_HELP_MESSAGE) 160 | @utils.arg('--protocol', 161 | metavar='', 162 | default='ISCSI', 163 | help='Connection protocol. ISCSI, FIBRE_CHANNEL, etc.') 164 | @brick_utils.require_root 165 | def do_get_all_volume_paths(client, args): 166 | """Get all volume paths for a protocol.""" 167 | brickclient = brick_client.Client(client) 168 | 169 | paths = brickclient.get_all_volume_paths(args.protocol, args.multipath) 170 | if paths: 171 | print('\n'.join(paths)) 172 | 173 | 174 | manager_class = brick_client.Client 175 | name = 'brick_local_volume_management' 176 | -------------------------------------------------------------------------------- /brick_cinderclient_ext/brick_utils.py: -------------------------------------------------------------------------------- 1 | # Copyright 2011-2014 OpenStack Foundation 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 | import netifaces 17 | import os 18 | import socket 19 | 20 | from brick_cinderclient_ext import exceptions as exc 21 | from cinderclient import exceptions 22 | from oslo_concurrency import processutils 23 | 24 | 25 | def get_my_ip(): 26 | try: 27 | csock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 28 | csock.connect(('8.8.8.8', 80)) 29 | (addr, port) = csock.getsockname() 30 | csock.close() 31 | return addr 32 | except socket.error: 33 | return None 34 | 35 | 36 | def get_ip(nic_name=None): 37 | """Getting ip address by network interface name. 38 | 39 | :param nic_name: interface name 40 | :returns: ip address from interface or default ip 41 | """ 42 | # TODO(mdovgal): add ipv6 support 43 | if not nic_name: 44 | return get_my_ip() 45 | 46 | existing_ifaces = netifaces.interfaces() 47 | if nic_name not in existing_ifaces: 48 | raise exc.NicNotFound(iface=nic_name) 49 | 50 | # get necessary iface information 51 | iface = netifaces.ifaddresses(nic_name) 52 | try: 53 | return iface[netifaces.AF_INET][0]['addr'] 54 | except KeyError: 55 | raise exc.IncorrectNic(iface=nic_name) 56 | 57 | 58 | def get_root_helper(): 59 | # NOTE (e0ne): We don't use rootwrap now 60 | return 'sudo' 61 | 62 | 63 | def require_root(f): 64 | def wrapper(*args, **kwargs): 65 | if hasattr(os, 'getuid') and os.getuid() != 0: 66 | raise exceptions.CommandError( 67 | "This command requires root permissions.") 68 | return f(*args, **kwargs) 69 | return wrapper 70 | 71 | 72 | def safe_execute(cmd): 73 | try: 74 | processutils.execute(*cmd, root_helper=get_root_helper(), 75 | run_as_root=True) 76 | except processutils.ProcessExecutionError as e: 77 | print('Command "{0}" execution returned {1} exit code:'.format( 78 | e.cmd, e.exit_code)) 79 | print('Stderr: {0}'.format(e.stderr)) 80 | print('Stdout: {0}'.format(e.stdout)) 81 | -------------------------------------------------------------------------------- /brick_cinderclient_ext/client.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 cinderclient 14 | from cinderclient import api_versions 15 | from cinderclient import exceptions 16 | from os_brick.initiator import connector 17 | from oslo_utils import uuidutils 18 | from pbr import version as pbr_version 19 | 20 | from brick_cinderclient_ext import brick_utils 21 | from brick_cinderclient_ext import volume_actions as actions 22 | 23 | 24 | class Client(object): 25 | """Python client for os-brick 26 | 27 | Version history: 28 | 29 | 1.0.0 - Initial version 30 | 1.1.0 - Query volume paths implementation 31 | 1.2.0 - Add --nic attribute to get-connector 32 | 1.3.0 - Added new v3 attach/detach workflow support 33 | 34 | """ 35 | 36 | version = '1.3.0' 37 | 38 | # Use the legacy attach/detach workflow? 39 | _use_legacy_attach = True 40 | 41 | def __init__(self, volumes_client=None): 42 | self.volumes_client = volumes_client 43 | 44 | # Test to see if we have a version of the cinderclient 45 | # that can do the new volume attach/detach API 46 | version_want = pbr_version.SemanticVersion(major=2) 47 | current_version = cinderclient.version_info.semantic_version() 48 | if (self.volumes_client and current_version >= version_want): 49 | # We have a recent enough client to test the microversion we need. 50 | required_version = api_versions.APIVersion("3.44") 51 | if self.volumes_client.api_version.matches(required_version): 52 | # we can use the new attach/detach API 53 | self._use_legacy_attach = False 54 | 55 | def _brick_get_connector(self, protocol, driver=None, 56 | execute=None, 57 | use_multipath=False, 58 | device_scan_attempts=3, 59 | *args, **kwargs): 60 | """Wrapper to get a brick connector object. 61 | 62 | This automatically populates the required protocol as well 63 | as the root_helper needed to execute commands. 64 | """ 65 | return connector.InitiatorConnector.factory( 66 | protocol, 67 | brick_utils.get_root_helper(), 68 | driver=driver, 69 | execute=execute, 70 | use_multipath=use_multipath, 71 | device_scan_attempts=device_scan_attempts, 72 | *args, **kwargs) 73 | 74 | def get_connector(self, multipath=False, enforce_multipath=False, 75 | nic=None): 76 | conn_prop = connector.get_connector_properties( 77 | brick_utils.get_root_helper(), 78 | brick_utils.get_ip(nic), 79 | multipath=multipath, 80 | enforce_multipath=(enforce_multipath), 81 | execute=None) 82 | return conn_prop 83 | 84 | def attach(self, volume_id, hostname, mountpoint=None, mode='rw', 85 | multipath=False, enforce_multipath=False, nic=None): 86 | """Main entry point for trying to attach a volume. 87 | 88 | If the cinderclient has a recent version that can do the new attach 89 | workflow, lets try that. Otherwise we revert to the older attach 90 | workflow. 91 | """ 92 | if self._use_legacy_attach: 93 | return self._legacy_attach(volume_id, hostname, 94 | mountpoint=mountpoint, 95 | mode=mode, multipath=multipath, 96 | enforce_multipath=enforce_multipath, 97 | nic=nic) 98 | else: 99 | return self._attach(volume_id, hostname, 100 | mountpoint=mountpoint, 101 | mode=mode, multipath=multipath, 102 | enforce_multipath=enforce_multipath, 103 | nic=nic) 104 | 105 | def _legacy_attach(self, volume_id, hostname, mountpoint=None, mode='rw', 106 | multipath=False, enforce_multipath=False, nic=None): 107 | """The original/legacy attach workflow.""" 108 | # Reserve volume before attachment 109 | with actions.Reserve(self.volumes_client, volume_id) as cmd: 110 | cmd.reserve() 111 | 112 | with actions.InitializeConnection( 113 | self.volumes_client, volume_id) as cmd: 114 | connection = cmd.initialize(self, multipath, enforce_multipath, 115 | nic) 116 | 117 | with actions.VerifyProtocol(self.volumes_client, volume_id) as cmd: 118 | cmd.verify(connection['driver_volume_type']) 119 | 120 | with actions.ConnectVolume(self.volumes_client, volume_id) as cmd: 121 | brick_connector = self._brick_get_connector( 122 | connection['driver_volume_type'], do_local_attach=True) 123 | device_info = cmd.connect(brick_connector, 124 | connection['data'], 125 | mountpoint, mode, hostname) 126 | return device_info 127 | 128 | def _attach(self, volume_id, hostname, mountpoint=None, mode='rw', 129 | multipath=False, enforce_multipath=False, nic=None): 130 | """Attempt to use the v3 API for attach workflow. 131 | 132 | If the cinder API microversion is good enough, we will use the new 133 | attach workflow, otherwise we resort back to the old workflow. 134 | """ 135 | # We can use the new attach/detach workflow 136 | connector_properties = self.get_connector( 137 | multipath=multipath, 138 | enforce_multipath=enforce_multipath, 139 | nic=nic 140 | ) 141 | 142 | instance_id = uuidutils.generate_uuid() 143 | 144 | info = self.volumes_client.attachments.create( 145 | volume_id, connector_properties, instance_id) 146 | 147 | connection = info['connection_info'] 148 | with actions.VerifyProtocol(self.volumes_client, volume_id) as cmd: 149 | cmd.verify(connection['driver_volume_type']) 150 | 151 | brick_connector = self._brick_get_connector( 152 | connection['driver_volume_type'], 153 | do_local_attach=True, 154 | use_multipath=multipath, 155 | ) 156 | device_info = brick_connector.connect_volume(connection) 157 | # MV 3.44 requires this step to move the volume to 'in-use'. 158 | self.volumes_client.attachments.complete( 159 | info['connection_info']['attachment_id']) 160 | return device_info 161 | 162 | def detach(self, volume_id, attachment_uuid=None, multipath=False, 163 | enforce_multipath=False, device_info=None, nic=None): 164 | 165 | if self._use_legacy_attach: 166 | self._legacy_detach(volume_id, 167 | attachment_uuid=attachment_uuid, 168 | multipath=multipath, 169 | enforce_multipath=enforce_multipath, 170 | device_info=device_info, nic=nic) 171 | else: 172 | self._detach(volume_id, 173 | attachment_uuid=attachment_uuid, 174 | multipath=multipath, 175 | enforce_multipath=enforce_multipath, 176 | device_info=device_info, nic=nic) 177 | 178 | def _legacy_detach(self, volume_id, attachment_uuid=None, multipath=False, 179 | enforce_multipath=False, device_info=None, nic=None): 180 | """The original/legacy detach workflow.""" 181 | with actions.BeginDetach(self.volumes_client, volume_id) as cmd: 182 | cmd.reserve() 183 | 184 | with actions.InitializeConnectionForDetach( 185 | self.volumes_client, volume_id) as cmd: 186 | connection = cmd.initialize(self, multipath, enforce_multipath, 187 | nic) 188 | 189 | brick_connector = self._brick_get_connector( 190 | connection['driver_volume_type'], 191 | do_local_attach=True, 192 | use_multipath=multipath, 193 | ) 194 | 195 | with actions.DisconnectVolume(self.volumes_client, volume_id) as cmd: 196 | cmd.disconnect(brick_connector, connection['data'], device_info) 197 | 198 | with actions.DetachVolume(self.volumes_client, volume_id) as cmd: 199 | cmd.detach(self, attachment_uuid, multipath, enforce_multipath) 200 | 201 | def _detach(self, volume_id, attachment_uuid=None, multipath=False, 202 | enforce_multipath=False, device_info=None, nic=None): 203 | if not attachment_uuid: 204 | # We need the specific attachment uuid to know which one to detach. 205 | # if None was passed in we can only work if there is one and only 206 | # one attachment for the volume. 207 | # Get the list of attachments for the volume. 208 | search_opts = {'volume_id': volume_id} 209 | attachments = self.volumes_client.attachments.list( 210 | search_opts=search_opts) 211 | 212 | if len(attachments) == 0: 213 | raise exceptions.NoAttachmentsFound(volume_id=volume_id) 214 | if len(attachments) == 1: 215 | attachment_uuid = attachments[0].id 216 | else: 217 | # We have more than 1 attachment and we don't know which to use 218 | raise exceptions.NeedAttachmentUUID(volume_id=volume_id) 219 | 220 | attachment = self.volumes_client.attachments.show(attachment_uuid) 221 | 222 | brick_connector = self._brick_get_connector( 223 | attachment.connection_info['driver_volume_type'], 224 | do_local_attach=True, 225 | use_multipath=multipath, 226 | ) 227 | 228 | with actions.DisconnectVolume(self.volumes_client, volume_id) as cmd: 229 | cmd.disconnect(brick_connector, 230 | attachment.connection_info, 231 | device_info) 232 | 233 | self.volumes_client.attachments.delete(attachment_uuid) 234 | 235 | def get_volume_paths(self, volume_id, use_multipath=False): 236 | """Gets volume paths on the system for a specific volume.""" 237 | conn_props = self.get_connector(multipath=use_multipath) 238 | vols = self.volumes_client.volumes.list() 239 | vol_in_use = False 240 | vol_found = False 241 | for vol in vols: 242 | if (volume_id == vol.id or volume_id == vol.name): 243 | vol_found = True 244 | if vol.status == "in-use": 245 | vol_in_use = True 246 | # Make sure the volume ID is used and not the name 247 | volume_id = vol.id 248 | break 249 | 250 | if not vol_found: 251 | msg = "No volume with a name or ID of '%s' exists." % volume_id 252 | raise exceptions.CommandError(msg) 253 | 254 | paths = [] 255 | if vol_in_use: 256 | conn_info = self.volumes_client.volumes.initialize_connection( 257 | volume_id, conn_props) 258 | protocol = conn_info['driver_volume_type'] 259 | conn = self._brick_get_connector(protocol, 260 | use_multipath=use_multipath) 261 | paths = conn.get_volume_paths(conn_info['data']) 262 | 263 | return paths 264 | 265 | def get_all_volume_paths(self, protocol, use_multipath=False): 266 | """Gets all volume paths on the system for a given protocol.""" 267 | conn = self._brick_get_connector(protocol, use_multipath=use_multipath) 268 | paths = conn.get_all_available_volumes() 269 | 270 | return paths 271 | -------------------------------------------------------------------------------- /brick_cinderclient_ext/exceptions.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Mirantis.Inc 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 | """ 17 | Exception definitions. 18 | """ 19 | from cinderclient._i18n import _ 20 | 21 | 22 | class BrickInterfaceException(Exception): 23 | """Base exception for brick-cinderclient.""" 24 | message = _("An unknown exception occurred.") 25 | 26 | def __init__(self, message=None, **kwargs): 27 | if message: 28 | self.message = message 29 | self.message = self.message % kwargs 30 | 31 | def __str__(self): 32 | return self.message 33 | 34 | 35 | class NicNotFound(BrickInterfaceException): 36 | message = _("Could not find network interface %(iface)s.") 37 | 38 | 39 | class IncorrectNic(BrickInterfaceException): 40 | # TODO(mdovgal): change message after adding ipv6 support 41 | message = _("Network interface %(iface)s has not ipv4 address assigned.") 42 | 43 | 44 | class NoAttachmentsFound(BrickInterfaceException): 45 | message = _("There were no attachments found for %(volume_id)s") 46 | 47 | 48 | class NeedAttachmentUUID(BrickInterfaceException): 49 | message = _("Volume %(volume_id)s has more than one attachment. " 50 | "Please pass in the attachment_uuid you wish to detach.") 51 | -------------------------------------------------------------------------------- /brick_cinderclient_ext/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/python-brick-cinderclient-ext/3997894711e22cd163e0fb21c8ae655cc16fb84d/brick_cinderclient_ext/tests/__init__.py -------------------------------------------------------------------------------- /brick_cinderclient_ext/tests/functional/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/python-brick-cinderclient-ext/3997894711e22cd163e0fb21c8ae655cc16fb84d/brick_cinderclient_ext/tests/functional/__init__.py -------------------------------------------------------------------------------- /brick_cinderclient_ext/tests/functional/test_brick_client.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 configparser 14 | import os 15 | 16 | from cinderclient import client as c_client 17 | from oslotest import base 18 | 19 | 20 | from brick_cinderclient_ext import client 21 | 22 | 23 | _CREDS_FILE = 'functional_creds.conf' 24 | 25 | 26 | def credentials(): 27 | """Retrieves credentials to run functional tests 28 | 29 | Credentials are either read from the environment or from a config file 30 | ('functional_creds.conf'). Environment variables override those from the 31 | config file. 32 | 33 | The 'functional_creds.conf' file is the clean and new way to use (by 34 | default tox 2.0 does not pass environment variables). 35 | """ 36 | 37 | username = os.environ.get('OS_USERNAME') 38 | password = os.environ.get('OS_PASSWORD') 39 | tenant_name = os.environ.get('OS_TENANT_NAME') 40 | auth_url = os.environ.get('OS_AUTH_URL') 41 | 42 | config = configparser.RawConfigParser() 43 | if config.read(_CREDS_FILE): 44 | username = username or config.get('admin', 'user') 45 | password = password or config.get('admin', 'pass') 46 | tenant_name = tenant_name or config.get('admin', 'tenant_name') 47 | auth_url = auth_url or config.get('auth', 'uri') 48 | 49 | return { 50 | 'username': username, 51 | 'password': password, 52 | 'tenant_name': tenant_name, 53 | 'uri': auth_url 54 | } 55 | 56 | 57 | class BrickClientTests(base.BaseTestCase): 58 | def setUp(self): 59 | super(BrickClientTests, self).setUp() 60 | creds = credentials() 61 | self.cinder_client = c_client.Client(3, 62 | creds['username'], 63 | creds['password'], 64 | creds['tenant_name'], 65 | creds['uri']) 66 | 67 | self.client = client.Client(self.cinder_client) 68 | 69 | def test_get_connector(self): 70 | connector = self.client.get_connector() 71 | for prop in ['ip', 'host', 'multipath', 'platform', 'os_type']: 72 | self.assertIn(prop, connector) 73 | -------------------------------------------------------------------------------- /brick_cinderclient_ext/tests/unit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/python-brick-cinderclient-ext/3997894711e22cd163e0fb21c8ae655cc16fb84d/brick_cinderclient_ext/tests/unit/__init__.py -------------------------------------------------------------------------------- /brick_cinderclient_ext/tests/unit/test_brick_client.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 cinderclient import api_versions 16 | from oslotest import base 17 | from pbr import version as pbr_version 18 | 19 | from brick_cinderclient_ext import client 20 | 21 | 22 | class TestBrickClient(base.BaseTestCase): 23 | def setUp(self): 24 | super(TestBrickClient, self).setUp() 25 | self.volume_id = '3d96b134-75bd-492b-8372-330455cae38f' 26 | self.hostname = 'hostname' 27 | self.client = client.Client() 28 | 29 | def _init_fake_cinderclient(self, protocol): 30 | # Init fake cinderclient 31 | self.mock_vc = mock.Mock() 32 | self.mock_vc.version_info = mock.Mock() 33 | conn_data = {'key': 'value'} 34 | connection = {'driver_volume_type': protocol, 'data': conn_data} 35 | self.mock_vc.volumes.initialize_connection.return_value = connection 36 | mock_vol = mock.Mock() 37 | mock_vol.id = self.volume_id 38 | mock_vol.name = 'fake-vol' 39 | mock_vol.status = 'in-use' 40 | self.mock_vc.volumes.list.return_value = [mock_vol] 41 | self.client.volumes_client = self.mock_vc 42 | return connection 43 | 44 | def _init_fake_os_brick(self, mock_conn_prop): 45 | # Init fakes for os-brick 46 | conn_props = mock.Mock() 47 | mock_conn_prop.return_value = conn_props 48 | mock_connector = mock.Mock() 49 | mock_connect = mock.Mock() 50 | mock_connector.return_value = mock_connect 51 | self.client._brick_get_connector = mock_connector 52 | mock_connect.connect_volume = mock.Mock() 53 | 54 | return conn_props, mock_connect 55 | 56 | @mock.patch('brick_cinderclient_ext.brick_utils.get_my_ip') 57 | @mock.patch('brick_cinderclient_ext.brick_utils.get_root_helper') 58 | @mock.patch('os_brick.initiator.connector.get_connector_properties') 59 | def test_get_connector(self, mock_connector, mock_root_helper, 60 | mock_my_ip): 61 | mock_root_helper.return_value = 'root-helper' 62 | mock_my_ip.return_value = '1.0.0.0' 63 | 64 | self.client.get_connector() 65 | mock_connector.assert_called_with('root-helper', '1.0.0.0', 66 | enforce_multipath=False, 67 | multipath=False, 68 | execute=None) 69 | 70 | @mock.patch('brick_cinderclient_ext.brick_utils.get_my_ip') 71 | @mock.patch('brick_cinderclient_ext.brick_utils.get_root_helper') 72 | @mock.patch('os_brick.initiator.connector.get_connector_properties') 73 | def test_get_connector_with_multipath(self, mock_connector, 74 | mock_root_helper, mock_my_ip): 75 | mock_root_helper.return_value = 'root-helper' 76 | mock_my_ip.return_value = '1.0.0.0' 77 | 78 | self.client.get_connector(True, True) 79 | mock_connector.assert_called_with('root-helper', '1.0.0.0', 80 | enforce_multipath=True, 81 | multipath=True, 82 | execute=None) 83 | 84 | def test_client_use_new_attach_no_volumes_client(self): 85 | brick_client = client.Client(None) 86 | self.assertTrue(brick_client._use_legacy_attach) 87 | 88 | @mock.patch('cinderclient.version_info.semantic_version') 89 | def test_client_use_new_attach_v1_cinderclient(self, 90 | mock_semantic_version): 91 | self._init_fake_cinderclient('iscsi') 92 | mock_semantic_version.return_value = pbr_version.SemanticVersion( 93 | major=1, minor=0) 94 | self.client.volumes_client.version_info.semantic_version 95 | brick_client = client.Client(self.client.volumes_client) 96 | self.assertTrue(brick_client._use_legacy_attach) 97 | 98 | @mock.patch('cinderclient.version_info.semantic_version') 99 | def test_client_use_new_attach_v2_cinderclient_3_0(self, 100 | mock_semantic_version): 101 | self._init_fake_cinderclient('iscsi') 102 | mock_semantic_version.return_value = pbr_version.SemanticVersion( 103 | major=2, minor=0) 104 | self.client.volumes_client.version_info.semantic_version 105 | current_api_version = api_versions.APIVersion("3.0") 106 | self.client.volumes_client.api_version = current_api_version 107 | brick_client = client.Client(self.client.volumes_client) 108 | 109 | self.assertTrue(brick_client._use_legacy_attach) 110 | 111 | @mock.patch('cinderclient.version_info.semantic_version') 112 | def test_client_use_new_attach_v2_cinderclient_3_44(self, 113 | mock_semantic_version): 114 | self._init_fake_cinderclient('iscsi') 115 | mock_semantic_version.return_value = pbr_version.SemanticVersion( 116 | major=2, minor=0) 117 | self.client.volumes_client.version_info.semantic_version 118 | current_api_version = api_versions.APIVersion("3.44") 119 | self.client.volumes_client.api_version = current_api_version 120 | brick_client = client.Client(self.client.volumes_client) 121 | 122 | self.assertFalse(brick_client._use_legacy_attach) 123 | 124 | @mock.patch('os_brick.initiator.connector.get_connector_properties') 125 | def test_attach_iscsi(self, mock_conn_prop): 126 | connection = self._init_fake_cinderclient('iscsi') 127 | conn_props, mock_connect = self._init_fake_os_brick(mock_conn_prop) 128 | 129 | self.client.attach(self.volume_id, self.hostname) 130 | self.mock_vc.volumes.initialize_connection.assert_called_with( 131 | self.volume_id, conn_props) 132 | mock_connect.connect_volume.assert_called_with(connection['data']) 133 | 134 | @mock.patch('os_brick.initiator.connector.get_connector_properties') 135 | def test_detach_iscsi(self, mock_conn_prop): 136 | connection = self._init_fake_cinderclient('iscsi') 137 | conn_props, m_connect = self._init_fake_os_brick(mock_conn_prop) 138 | 139 | self.client.detach(self.volume_id) 140 | self.mock_vc.volumes.initialize_connection.assert_called_with( 141 | self.volume_id, conn_props) 142 | m_connect.disconnect_volume.assert_called_with(connection['data'], {}) 143 | 144 | @mock.patch('os_brick.initiator.connector.get_connector_properties') 145 | def test_get_volume_paths(self, mock_conn_prop): 146 | connection = self._init_fake_cinderclient('iscsi') 147 | conn_props, m_connect = self._init_fake_os_brick(mock_conn_prop) 148 | self.client.get_volume_paths(self.volume_id, use_multipath=False) 149 | self.mock_vc.volumes.initialize_connection.assert_called_with( 150 | self.volume_id, conn_props) 151 | self.client._brick_get_connector.assert_called_with( 152 | connection['driver_volume_type'], use_multipath=False) 153 | m_connect.get_volume_paths.assert_called_with(connection['data']) 154 | 155 | @mock.patch('os_brick.initiator.connector.get_connector_properties') 156 | def test_get_all_volume_paths(self, mock_conn_prop): 157 | protocol = 'iscsi' 158 | conn_props, m_connect = self._init_fake_os_brick(mock_conn_prop) 159 | self.client.get_all_volume_paths(protocol, use_multipath=False) 160 | self.client._brick_get_connector.assert_called_with( 161 | protocol, use_multipath=False) 162 | m_connect.get_all_available_volumes.assert_called_with() 163 | -------------------------------------------------------------------------------- /brick_cinderclient_ext/tests/unit/test_brick_utils.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Mirantis.Inc 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 | from unittest import mock 17 | 18 | import ddt 19 | 20 | import netifaces 21 | from oslotest import base 22 | 23 | from brick_cinderclient_ext import brick_utils 24 | from brick_cinderclient_ext import exceptions as exc 25 | 26 | 27 | @ddt.ddt 28 | class TestBrickUtils(base.BaseTestCase): 29 | @mock.patch('netifaces.ifaddresses', 30 | return_value={netifaces.AF_INET: [{'addr': '127.0.0.1'}]}) 31 | @mock.patch('netifaces.interfaces', return_value=['eth1']) 32 | @mock.patch('brick_cinderclient_ext.brick_utils.get_my_ip', 33 | return_value='1.0.0.0') 34 | @ddt.data((None, '1.0.0.0'), 35 | ('eth1', '127.0.0.1')) 36 | @ddt.unpack 37 | def test_get_ip(self, nic, expected, 38 | _fake_my_ip, _fake_interfaces, 39 | _fake_ifaddresses): 40 | """Test getting ip address using interface name. 41 | 42 | Test cases: 43 | 1. Getting address from existing interface; 44 | 2. Getting default value when network-iface param is missing; 45 | """ 46 | self.assertEqual(expected, brick_utils.get_ip(nic)) 47 | 48 | @mock.patch('netifaces.interfaces', return_value=[]) 49 | def test_get_ip_failed_interface(self, _fake_interfaces): 50 | """Test getting ip from nonexistent interface.""" 51 | nic = 'fake_nic' 52 | self.assertRaises(exc.NicNotFound, brick_utils.get_ip, 53 | nic) 54 | 55 | @mock.patch('netifaces.ifaddresses', return_value={}) 56 | @mock.patch('netifaces.interfaces', return_value=['without_addr']) 57 | def test_get_ip_non_addr_in_iface(self, _fake_interfaces, 58 | _fake_ifaddresses): 59 | """Test getting ip using nic that doesn't have ipv4 address.""" 60 | nic = 'without_addr' 61 | self.assertRaises(exc.IncorrectNic, brick_utils.get_ip, nic) 62 | -------------------------------------------------------------------------------- /brick_cinderclient_ext/tests/unit/test_cli.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 cinderclient import exceptions 16 | from oslotest import base 17 | 18 | import brick_cinderclient_ext 19 | 20 | 21 | class TestBrickClientCLI(base.BaseTestCase): 22 | def setUp(self): 23 | super(TestBrickClientCLI, self).setUp() 24 | 25 | @mock.patch('os.getuid') 26 | def test_get_connector_non_root(self, mock_getuid): 27 | mock_getuid.return_value = 1 28 | self.assertRaises(exceptions.CommandError, 29 | brick_cinderclient_ext.do_get_connector, None, None) 30 | -------------------------------------------------------------------------------- /brick_cinderclient_ext/tests/unit/test_volume_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 unittest import mock 14 | 15 | import ddt 16 | import netifaces 17 | 18 | from cinderclient import exceptions as cinder_exceptions 19 | from os_brick import exception 20 | from oslotest import base 21 | 22 | from brick_cinderclient_ext import volume_actions 23 | 24 | 25 | @ddt.ddt 26 | class TestVolumeActions(base.BaseTestCase): 27 | def setUp(self): 28 | super(TestVolumeActions, self).setUp() 29 | self.volume_id = '3d96b134-75bd-492b-8372-330455cae38f' 30 | self.brick_client = mock.Mock() 31 | self.v_client = mock.Mock() 32 | self.command_args = [self.v_client, self.volume_id] 33 | 34 | def test_reserve(self): 35 | with volume_actions.Reserve(*self.command_args) as cmd: 36 | cmd.reserve() 37 | 38 | self.v_client.volumes.reserve.assert_called_once_with(self.volume_id) 39 | 40 | def test_reserve_failed(self): 41 | self.v_client.volumes.reserve.side_effect = ( 42 | cinder_exceptions.BadRequest(400)) 43 | try: 44 | with volume_actions.Reserve(*self.command_args) as cmd: 45 | cmd.reserve() 46 | except cinder_exceptions.BadRequest: 47 | self.v_client.volumes.unreserve.assert_called_once_with( 48 | self.volume_id) 49 | 50 | self.v_client.volumes.reserve.assert_called_once_with(self.volume_id) 51 | 52 | @mock.patch('netifaces.ifaddresses', 53 | return_value={netifaces.AF_INET: [{'addr': '127.0.0.1'}]}) 54 | @mock.patch('netifaces.interfaces', return_value=['eth1']) 55 | @mock.patch('brick_cinderclient_ext.brick_utils.get_my_ip', 56 | return_value='1.0.0.0') 57 | @ddt.data((None, {'ip': '1.0.0.0'}), 58 | ('eth1', {'ip': '127.0.0.1'})) 59 | @ddt.unpack 60 | def test_initialize_connection(self, _nic, _conn_prop, 61 | _fake_my_ip, _fake_interfaces, 62 | _fake_ifaddresses): 63 | """Test calling initialize_connection with different input params. 64 | 65 | Contains next initialize connection test cases: 66 | 1. Without any additional parameters in request; 67 | 2. Using --nic as a parameter; 68 | TODO (mdovgal): add other test cases; 69 | """ 70 | self.brick_client.get_connector.return_value = _conn_prop 71 | with volume_actions.InitializeConnection(*self.command_args) as cmd: 72 | cmd.initialize(self.brick_client, False, False, _nic) 73 | 74 | self.brick_client.get_connector.assert_called_once_with(False, False, 75 | _nic) 76 | self.v_client.volumes.initialize_connection.assert_called_once_with( 77 | self.volume_id, _conn_prop) 78 | 79 | @ddt.data('iscsi', 'iSCSI', 'ISCSI', 'rbd', 'RBD') 80 | def test_verify_protocol(self, protocol): 81 | with volume_actions.VerifyProtocol(*self.command_args) as cmd: 82 | # NOTE(e0ne): veryfy that no exception is rased 83 | cmd.verify(protocol) 84 | 85 | def test_verify_protocol_failed(self): 86 | try: 87 | with volume_actions.VerifyProtocol(*self.command_args) as cmd: 88 | cmd.verify('protocol') 89 | except exception.ProtocolNotSupported: 90 | self.v_client.volumes.unreserve.assert_called_once_with( 91 | self.volume_id) 92 | 93 | def test_connect_volume(self): 94 | connector = mock.Mock() 95 | connector.connect_volume.return_value = {'device': 'info'} 96 | with volume_actions.ConnectVolume(*self.command_args) as cmd: 97 | cmd.connect(connector, 98 | 'connection_data', 'mountpoint', 'mode', 'hostname') 99 | 100 | connector.connect_volume.assert_called_once_with('connection_data') 101 | self.v_client.volumes.attach.assert_called_once_with( 102 | self.volume_id, 103 | instance_uuid=None, mountpoint='mountpoint', mode='mode', 104 | host_name='hostname') 105 | 106 | @ddt.data((None, {}), ('connection_data', 'connection_data')) 107 | @ddt.unpack 108 | def test_disconnect_no_device_info(self, command_arg, connector_arg): 109 | connector = mock.Mock() 110 | with volume_actions.DisconnectVolume(*self.command_args) as cmd: 111 | cmd.disconnect(connector, 'connection_data', command_arg) 112 | 113 | connector.disconnect_volume.assert_called_once_with('connection_data', 114 | connector_arg) 115 | 116 | def test_detach(self): 117 | brick_client = mock.Mock() 118 | brick_client.get_connector.return_value = 'connector' 119 | with volume_actions.DetachVolume(*self.command_args) as cmd: 120 | cmd.detach(brick_client, 'attachment_uuid', 121 | 'multipath', 'enforce_multipath') 122 | 123 | brick_client.get_connector.assert_called_once_with('multipath', 124 | 'enforce_multipath') 125 | self.v_client.volumes.terminate_connection.assert_called_once_with( 126 | self.volume_id, 'connector') 127 | self.v_client.volumes.detach.assert_called_once_with( 128 | self.volume_id, 'attachment_uuid') 129 | -------------------------------------------------------------------------------- /brick_cinderclient_ext/version.py: -------------------------------------------------------------------------------- 1 | # 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 | import pbr.version 17 | 18 | 19 | version_info = pbr.version.VersionInfo('python-brick-cinderclient-ext') 20 | __version__ = version_info.version_string() 21 | -------------------------------------------------------------------------------- /brick_cinderclient_ext/volume_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 os_brick import exception 14 | from os_brick import initiator 15 | 16 | 17 | class VolumeAction(object): 18 | def __init__(self, volumes_client, volume_id): 19 | self.volumes_client = volumes_client 20 | self.volume_id = volume_id 21 | 22 | def __enter__(self): 23 | return self 24 | 25 | def __exit__(self, type, value, traceback): 26 | if traceback: 27 | self.volumes_client.volumes.unreserve(self.volume_id) 28 | return False 29 | return True 30 | 31 | 32 | class Reserve(VolumeAction): 33 | def reserve(self): 34 | self.volumes_client.volumes.reserve(self.volume_id) 35 | 36 | 37 | class InitializeConnection(VolumeAction): 38 | def initialize(self, brick_client, multipath, enforce_multipath, nic): 39 | conn_prop = brick_client.get_connector(multipath, enforce_multipath, 40 | nic) 41 | return self.volumes_client.volumes.initialize_connection( 42 | self.volume_id, conn_prop) 43 | 44 | 45 | class VerifyProtocol(VolumeAction): 46 | # NOTE(e0ne): Only iSCSI and RBD based drivers are supported. NFS doesn't 47 | # work. Drivers with other protocols are not tested yet. 48 | SUPPORTED_PROCOTOLS = [ 49 | initiator.ISCSI, 50 | initiator.RBD, 51 | initiator.FIBRE_CHANNEL 52 | ] 53 | 54 | def verify(self, protocol): 55 | protocol = protocol.upper() 56 | 57 | # NOTE(e0ne): iSCSI drivers works without issues, RBD and NFS don't 58 | # work. Drivers with other protocols are not tested yet. 59 | if protocol not in VerifyProtocol.SUPPORTED_PROCOTOLS: 60 | raise exception.ProtocolNotSupported(protocol=protocol) 61 | 62 | 63 | class ConnectVolume(VolumeAction): 64 | def connect(self, brick_connector, connection_data, 65 | mountpoint, mode, hostname): 66 | device_info = brick_connector.connect_volume(connection_data) 67 | 68 | # NOTE(flaper87): mountpoint doesn't seem to be used at all, we should 69 | # perhaps consider removing it from the connect args. Unsure whether 70 | # `path` exists for all the different drivers so, using the value of 71 | # `mountpoint` as the default. 72 | mountpoint = device_info.get('path', mountpoint) 73 | 74 | self.volumes_client.volumes.attach(self.volume_id, instance_uuid=None, 75 | mountpoint=mountpoint, 76 | mode=mode, 77 | host_name=hostname) 78 | return device_info 79 | 80 | 81 | class VolumeDetachAction(VolumeAction): 82 | def __exit__(self, type, value, traceback): 83 | if traceback: 84 | self.volumes_client.volumes.roll_detaching(self.volume_id) 85 | return False 86 | return True 87 | 88 | 89 | class BeginDetach(VolumeDetachAction): 90 | def reserve(self): 91 | self.volumes_client.volumes.begin_detaching(self.volume_id) 92 | 93 | 94 | class InitializeConnectionForDetach(InitializeConnection, VolumeDetachAction): 95 | pass 96 | 97 | 98 | class DisconnectVolume(VolumeDetachAction): 99 | def disconnect(self, brick_connector, connection_data, device_info): 100 | device_info = device_info or {} 101 | 102 | brick_connector.disconnect_volume(connection_data, device_info) 103 | 104 | 105 | class DetachVolume(VolumeDetachAction): 106 | def detach(self, brick_client, 107 | attachment_uuid, multipath, enforce_multipath): 108 | conn_prop = brick_client.get_connector(multipath, enforce_multipath) 109 | 110 | self.volumes_client.volumes.terminate_connection(self.volume_id, 111 | conn_prop) 112 | self.volumes_client.volumes.detach(self.volume_id, attachment_uuid) 113 | -------------------------------------------------------------------------------- /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 | sphinx>=3.5.1 # BSD 5 | openstackdocstheme>=2.2.7 # Apache-2.0 6 | reno>=3.2.0 # Apache-2.0 7 | -------------------------------------------------------------------------------- /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 | 'openstackdocstheme', 25 | 'reno.sphinxext', 26 | ] 27 | 28 | # autodoc generation is a bit aggressive and a nuisance when doing heavy 29 | # text edit cycles. 30 | # execute "export SPHINX_DEBUG=1" in your terminal to disable 31 | 32 | # The suffix of source filenames. 33 | source_suffix = '.rst' 34 | 35 | # The master toctree document. 36 | master_doc = 'index' 37 | 38 | # General information about the project. 39 | project = 'brick-python-cinderclient-ext' 40 | copyright = '2013, OpenStack Foundation' 41 | 42 | # If true, '()' will be appended to :func: etc. cross-reference text. 43 | add_function_parentheses = True 44 | 45 | # If true, the current module name will be prepended to all description 46 | # unit titles (such as .. function::). 47 | add_module_names = True 48 | 49 | # The name of the Pygments (syntax highlighting) style to use. 50 | pygments_style = 'native' 51 | 52 | # -- Options for HTML output -------------------------------------------------- 53 | 54 | # The theme to use for HTML and HTML Help pages. Major themes that come with 55 | # Sphinx are currently 'default' and 'sphinxdoc'. 56 | # html_theme_path = ["."] 57 | # html_theme = '_theme' 58 | # html_static_path = ['static'] 59 | html_theme = 'openstackdocs' 60 | 61 | # Output file base name for HTML help builder. 62 | htmlhelp_basename = '%sdoc' % project 63 | 64 | # Grouping the document tree into LaTeX files. List of tuples 65 | # (source start file, target name, title, author, documentclass 66 | # [howto/manual]). 67 | latex_documents = [ 68 | ('index', 69 | '%s.tex' % project, 70 | '%s Documentation' % project, 71 | 'OpenStack Foundation', 'manual'), 72 | ] 73 | 74 | # Example configuration for intersphinx: refer to the Python standard library. 75 | #intersphinx_mapping = {'http://docs.python.org/': None} 76 | 77 | # -- Options for openstackdocstheme ------------------------------------------- 78 | openstackdocs_repo_name = 'openstack/python-brick-cinderclient-ext' 79 | openstackdocs_bug_project = 'python-brick-cinderclient-ext' 80 | openstackdocs_bug_tag = '' 81 | -------------------------------------------------------------------------------- /doc/source/contributor/contributing.rst: -------------------------------------------------------------------------------- 1 | ============================ 2 | So You Want to Contribute... 3 | ============================ 4 | 5 | For general information on contributing to OpenStack, please check out the 6 | `contributor guide `_ to get started. 7 | It covers all the basics that are common to all OpenStack projects: the 8 | accounts you need, the basics of interacting with our Gerrit review system, how 9 | we communicate as a community, etc. 10 | 11 | The python-brick-cinderclient-ext is maintained by the OpenStack Cinder 12 | project. To understand our development process and how you can contribute to 13 | it, please look at the Cinder project's general contributor's page: 14 | http://docs.openstack.org/cinder/latest/contributor/contributing.html 15 | -------------------------------------------------------------------------------- /doc/source/index.rst: -------------------------------------------------------------------------------- 1 | .. python-brick-cinderclient-ext documentation master file, created by 2 | sphinx-quickstart on Tue Jul 9 22:26:36 2013. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to python-brick-cinderclient-ext's documentation! 7 | ========================================================= 8 | 9 | Contents: 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | readme 15 | installation 16 | usage 17 | contributor/contributing 18 | 19 | Indices and tables 20 | ================== 21 | 22 | * :ref:`genindex` 23 | * :ref:`modindex` 24 | * :ref:`search` 25 | 26 | -------------------------------------------------------------------------------- /doc/source/installation.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Installation 3 | ============ 4 | 5 | At the command line:: 6 | 7 | $ pip install python-brick-cinderclient-ext 8 | 9 | Or, if you have virtualenvwrapper installed:: 10 | 11 | $ mkvirtualenv python-brick-cinderclient-ext 12 | $ pip install python-brick-cinderclient-ext 13 | -------------------------------------------------------------------------------- /doc/source/readme.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../../README.rst 2 | -------------------------------------------------------------------------------- /doc/source/usage.rst: -------------------------------------------------------------------------------- 1 | ===== 2 | Usage 3 | ===== 4 | 5 | To use python-brick-cinderclient-ext in a project:: 6 | 7 | import brick_cinderclient_ext 8 | from cinderclient import client 9 | 10 | c = client.Client(2, extensions=[brick_cinderclient_ext]) 11 | print(c.brick_client_ext.get_connector()) 12 | -------------------------------------------------------------------------------- /releasenotes/notes/add_get_connector_nic_attr-fd429b22d02f8621.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - Added attribute --nic to get-connector command. 4 | -------------------------------------------------------------------------------- /releasenotes/notes/add_local_attach_nic_attr-923f867bd565dc36.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - Added attribute --nic to local-attach command. 4 | -------------------------------------------------------------------------------- /releasenotes/notes/drop-py2-5a229fe56b86cbe9.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | upgrade: 3 | - | 4 | Python 2.7 support has been dropped. Beginning with 5 | python-brick-cinderclient-ext release 1.0.0, the minimum version 6 | of Python supported is Python 3.6. 7 | -------------------------------------------------------------------------------- /releasenotes/notes/drop-python-3-6-and-3-7-f8afbd8e0d0a98e0.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | upgrade: 3 | - | 4 | Python 3.6 & 3.7 support has been dropped. The minimum version of Python now 5 | supported is Python 3.8. 6 | 7 | -------------------------------------------------------------------------------- /releasenotes/notes/local-attach-feature-474283267873f091.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - Local attach/detach features implemented 4 | Introduced new Python and CLI APIs to attach Cinder volumes to any host 5 | including baremetal instances and containers. 6 | Current implementation supports only iSCSI and RBD protocols and it was 7 | tested only on Linux hosts. 8 | -------------------------------------------------------------------------------- /releasenotes/notes/require-root-for-attach-detach-commands-c3a63f6c5213e28c.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - Require root permissions for local-attach and local-detach CLI operations 4 | -------------------------------------------------------------------------------- /releasenotes/notes/start-using-reno-819e161ed41a130f.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | other: 3 | - Start using reno to manage release notes. 4 | 5 | -------------------------------------------------------------------------------- /releasenotes/notes/windows-support-c12b7b922aa43272.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | Added Windows support. 5 | -------------------------------------------------------------------------------- /releasenotes/source/2023.1.rst: -------------------------------------------------------------------------------- 1 | =========================== 2 | 2023.1 Series Release Notes 3 | =========================== 4 | 5 | .. release-notes:: 6 | :branch: 2023.1-eom 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-brick-cinderclient-ext/3997894711e22cd163e0fb21c8ae655cc16fb84d/releasenotes/source/_static/.placeholder -------------------------------------------------------------------------------- /releasenotes/source/_templates/.placeholder: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/python-brick-cinderclient-ext/3997894711e22cd163e0fb21c8ae655cc16fb84d/releasenotes/source/_templates/.placeholder -------------------------------------------------------------------------------- /releasenotes/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain 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, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 11 | # implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Cinder Release Notes documentation build configuration file, created by 16 | # sphinx-quickstart on Tue Nov 4 17:02:44 2015. 17 | # 18 | # This file is execfile()d with the current directory set to its 19 | # containing dir. 20 | # 21 | # Note that not all possible configuration values are present in this 22 | # autogenerated file. 23 | # 24 | # All configuration values have a default; values that are commented out 25 | # serve to show the default. 26 | 27 | # If extensions (or modules to document with autodoc) are in another directory, 28 | # add these directories to sys.path here. If the directory is relative to the 29 | # documentation root, use os.path.abspath to make it absolute, like shown here. 30 | # sys.path.insert(0, os.path.abspath('.')) 31 | 32 | # -- General configuration ------------------------------------------------ 33 | 34 | # If your documentation needs a minimal Sphinx version, state it here. 35 | # needs_sphinx = '1.0' 36 | 37 | # Add any Sphinx extension module names here, as strings. They can be 38 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 39 | # ones. 40 | extensions = [ 41 | 'openstackdocstheme', 42 | 'reno.sphinxext', 43 | ] 44 | 45 | # Add any paths that contain templates here, relative to this directory. 46 | templates_path = ['_templates'] 47 | 48 | # The suffix of source filenames. 49 | source_suffix = '.rst' 50 | 51 | # The encoding of source files. 52 | # source_encoding = 'utf-8-sig' 53 | 54 | # The master toctree document. 55 | master_doc = 'index' 56 | 57 | # General information about the project. 58 | project = 'python-brick-cinderclient-ext Release Notes' 59 | copyright = '2016, Cinder Developers' 60 | 61 | # Release notes are unversioned and do not need version and release set 62 | version = '' 63 | release = '' 64 | 65 | # The language for content autogenerated by Sphinx. Refer to documentation 66 | # for a list of supported languages. 67 | # language = None 68 | 69 | # There are two options for replacing |today|: either, you set today to some 70 | # non-false value, then it is used: 71 | # today = '' 72 | # Else, today_fmt is used as the format for a strftime call. 73 | # today_fmt = '%B %d, %Y' 74 | 75 | # List of patterns, relative to source directory, that match files and 76 | # directories to ignore when looking for source files. 77 | exclude_patterns = [] 78 | 79 | # The reST default role (used for this markup: `text`) to use for all 80 | # documents. 81 | # default_role = None 82 | 83 | # If true, '()' will be appended to :func: etc. cross-reference text. 84 | # add_function_parentheses = True 85 | 86 | # If true, the current module name will be prepended to all description 87 | # unit titles (such as .. function::). 88 | # add_module_names = True 89 | 90 | # If true, sectionauthor and moduleauthor directives will be shown in the 91 | # output. They are ignored by default. 92 | # show_authors = False 93 | 94 | # The name of the Pygments (syntax highlighting) style to use. 95 | pygments_style = 'native' 96 | 97 | # A list of ignored prefixes for module index sorting. 98 | # modindex_common_prefix = [] 99 | 100 | # If true, keep warnings as "system message" paragraphs in the built documents. 101 | # keep_warnings = False 102 | 103 | 104 | # -- Options for HTML output ---------------------------------------------- 105 | 106 | # The theme to use for HTML and HTML Help pages. See the documentation for 107 | # a list of builtin themes. 108 | html_theme = 'openstackdocs' 109 | 110 | # Theme options are theme-specific and customize the look and feel of a theme 111 | # further. For a list of options available for each theme, see the 112 | # documentation. 113 | # html_theme_options = {} 114 | 115 | # Add any paths that contain custom themes here, relative to this directory. 116 | # html_theme_path = [] 117 | 118 | # The name for this set of Sphinx documents. If None, it defaults to 119 | # " v documentation". 120 | # html_title = None 121 | 122 | # A shorter title for the navigation bar. Default is the same as html_title. 123 | # html_short_title = None 124 | 125 | # The name of an image file (relative to this directory) to place at the top 126 | # of the sidebar. 127 | # html_logo = None 128 | 129 | # The name of an image file (within the static path) to use as favicon of the 130 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 131 | # pixels large. 132 | # html_favicon = None 133 | 134 | # Add any paths that contain custom static files (such as style sheets) here, 135 | # relative to this directory. They are copied after the builtin static files, 136 | # so a file named "default.css" will overwrite the builtin "default.css". 137 | html_static_path = ['_static'] 138 | 139 | # Add any extra paths that contain custom files (such as robots.txt or 140 | # .htaccess) here, relative to this directory. These files are copied 141 | # directly to the root of the documentation. 142 | # html_extra_path = [] 143 | 144 | # If true, SmartyPants will be used to convert quotes and dashes to 145 | # typographically correct entities. 146 | # html_use_smartypants = True 147 | 148 | # Custom sidebar templates, maps document names to template names. 149 | # html_sidebars = {} 150 | 151 | # Additional templates that should be rendered to pages, maps page names to 152 | # template names. 153 | # html_additional_pages = {} 154 | 155 | # If false, no module index is generated. 156 | # html_domain_indices = True 157 | 158 | # If false, no index is generated. 159 | # html_use_index = True 160 | 161 | # If true, the index is split into individual pages for each letter. 162 | # html_split_index = False 163 | 164 | # If true, links to the reST sources are added to the pages. 165 | # html_show_sourcelink = True 166 | 167 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 168 | # html_show_sphinx = True 169 | 170 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 171 | # html_show_copyright = True 172 | 173 | # If true, an OpenSearch description file will be output, and all pages will 174 | # contain a tag referring to it. The value of this option must be the 175 | # base URL from which the finished HTML is served. 176 | # html_use_opensearch = '' 177 | 178 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 179 | # html_file_suffix = None 180 | 181 | # Output file base name for HTML help builder. 182 | htmlhelp_basename = 'BrickCinderclientExtNotesdoc' 183 | 184 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 185 | # using the given strftime format. 186 | # html_last_updated_fmt = '%b %d, %Y' 187 | 188 | # -- Options for LaTeX output --------------------------------------------- 189 | 190 | latex_elements = { 191 | # The paper size ('letterpaper' or 'a4paper'). 192 | # 'papersize': 'letterpaper', 193 | 194 | # The font size ('10pt', '11pt' or '12pt'). 195 | # 'pointsize': '10pt', 196 | 197 | # Additional stuff for the LaTeX preamble. 198 | # 'preamble': '', 199 | } 200 | 201 | # Grouping the document tree into LaTeX files. List of tuples 202 | # (source start file, target name, title, 203 | # author, documentclass [howto, manual, or own class]). 204 | latex_documents = [ 205 | ('index', 'BrickCinderclientExtReleaseNotes.tex', 206 | 'os-brick Cinderclient Extension Release Notes Documentation', 207 | 'Cinder Developers', 'manual'), 208 | ] 209 | 210 | # The name of an image file (relative to this directory) to place at the top of 211 | # the title page. 212 | # latex_logo = None 213 | 214 | # For "manual" documents, if this is true, then toplevel headings are parts, 215 | # not chapters. 216 | # latex_use_parts = False 217 | 218 | # If true, show page references after internal links. 219 | # latex_show_pagerefs = False 220 | 221 | # If true, show URL addresses after external links. 222 | # latex_show_urls = False 223 | 224 | # Documents to append as an appendix to all manuals. 225 | # latex_appendices = [] 226 | 227 | # If false, no module index is generated. 228 | # latex_domain_indices = True 229 | 230 | 231 | # -- Options for manual page output --------------------------------------- 232 | 233 | # One entry per manual page. List of tuples 234 | # (source start file, name, description, authors, manual section). 235 | man_pages = [ 236 | ('index', 'osbrickreleasenotes', 237 | 'OS-Brick Release Notes Documentation', 238 | ['Cinder Developers'], 1) 239 | ] 240 | 241 | # If true, show URL addresses after external links. 242 | # man_show_urls = False 243 | 244 | 245 | # -- Options for Texinfo output ------------------------------------------- 246 | 247 | # Grouping the document tree into Texinfo files. List of tuples 248 | # (source start file, target name, title, author, 249 | # dir menu entry, description, category) 250 | texinfo_documents = [ 251 | ('index', 'BrickCinderclientExtReleaseNotes', 252 | 'os-brick Cinderclient Extension Release Notes Documentation', 253 | 'Cinder Developers', 'BrickCinderclientExtReleaseNotes', 254 | 'python-cinderclient extension for local storage management.', 255 | 'Miscellaneous'), 256 | ] 257 | 258 | # Documents to append as an appendix to all manuals. 259 | # texinfo_appendices = [] 260 | 261 | # If false, no module index is generated. 262 | # texinfo_domain_indices = True 263 | 264 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 265 | # texinfo_show_urls = 'footnote' 266 | 267 | # If true, do not generate a @detailmenu in the "Top" node's menu. 268 | # texinfo_no_detailmenu = False 269 | 270 | # -- Options for Internationalization output ------------------------------ 271 | locale_dirs = ['locale/'] 272 | 273 | # -- Options for openstackdocstheme ------------------------------------------- 274 | openstackdocs_repo_name = 'openstack/python-brick-cinderclient-ext' 275 | openstackdocs_bug_project = 'python-brick-cinderclient-ext' 276 | openstackdocs_bug_tag = '' 277 | openstackdocs_auto_name = False 278 | -------------------------------------------------------------------------------- /releasenotes/source/index.rst: -------------------------------------------------------------------------------- 1 | =========================================== 2 | python-brick-cinderclient-ext 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 | ocata 26 | newton 27 | -------------------------------------------------------------------------------- /releasenotes/source/newton.rst: -------------------------------------------------------------------------------- 1 | ============================= 2 | Newton Series Release Notes 3 | ============================= 4 | 5 | .. release-notes:: 6 | :branch: origin/stable/newton 7 | -------------------------------------------------------------------------------- /releasenotes/source/ocata.rst: -------------------------------------------------------------------------------- 1 | =================================== 2 | Ocata Series Release Notes 3 | =================================== 4 | 5 | .. release-notes:: 6 | :branch: origin/stable/ocata 7 | -------------------------------------------------------------------------------- /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: victoria-eom 7 | -------------------------------------------------------------------------------- /releasenotes/source/wallaby.rst: -------------------------------------------------------------------------------- 1 | ============================ 2 | Wallaby Series Release Notes 3 | ============================ 4 | 5 | .. release-notes:: 6 | :branch: wallaby-eom 7 | -------------------------------------------------------------------------------- /releasenotes/source/xena.rst: -------------------------------------------------------------------------------- 1 | ========================= 2 | Xena Series Release Notes 3 | ========================= 4 | 5 | .. release-notes:: 6 | :branch: xena-eom 7 | -------------------------------------------------------------------------------- /releasenotes/source/yoga.rst: -------------------------------------------------------------------------------- 1 | ========================= 2 | Yoga Series Release Notes 3 | ========================= 4 | 5 | .. release-notes:: 6 | :branch: yoga-eom 7 | -------------------------------------------------------------------------------- /releasenotes/source/zed.rst: -------------------------------------------------------------------------------- 1 | ======================== 2 | Zed Series Release Notes 3 | ======================== 4 | 5 | .. release-notes:: 6 | :branch: zed-eom 7 | -------------------------------------------------------------------------------- /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 | pbr>=5.5.1 # Apache-2.0 6 | 7 | python-cinderclient>=9.0.0 # Apache-2.0 8 | os-brick>=6.0.0 # Apache-2.0 9 | oslo.concurrency>=5.0.0 # Apache-2.0 10 | netifaces>=0.11.0 11 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = python-brick-cinderclient-ext 3 | description = python-cinderclient extension for local storage management 4 | long_description = file: README.rst 5 | author = OpenStack 6 | author_email = openstack-discuss@lists.openstack.org 7 | url = http://docs.openstack.org/cinder/latest/ 8 | python_requires = >=3.9 9 | classifiers = 10 | Environment :: OpenStack 11 | Intended Audience :: Information Technology 12 | Intended Audience :: System Administrators 13 | License :: OSI Approved :: Apache Software License 14 | Operating System :: POSIX :: Linux 15 | Programming Language :: Python 16 | Programming Language :: Python :: 3 :: Only 17 | Programming Language :: Python :: Implementation :: CPython 18 | Programming Language :: Python :: 3 19 | Programming Language :: Python :: 3.9 20 | Programming Language :: Python :: 3.10 21 | Programming Language :: Python :: 3.11 22 | Programming Language :: Python :: 3.12 23 | 24 | [files] 25 | packages = 26 | brick_cinderclient_ext 27 | brick_python_cinderclient_ext 28 | -------------------------------------------------------------------------------- /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 | hacking>=7.0.0,<7.1.0 # Apache-2.0 6 | coverage>=5.5 # Apache-2.0 7 | ddt>=1.4.1 # MIT 8 | oslotest>=4.4.1 # Apache-2.0 9 | testscenarios>=0.5.0 # Apache-2.0/BSD 10 | testtools>=2.4.0 # MIT 11 | stestr>=3.1.0 # Apache-2.0 12 | doc8>=0.8.1 # Apache-2.0 13 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | minversion = 3.18.0 3 | skipsdist = True 4 | # python runtimes: https://governance.openstack.org/tc/reference/project-testing-interface.html#tested-runtimes 5 | envlist = py3,pep8 6 | # this allows tox to infer the base python from the environment name 7 | # and override any basepython configured in this file 8 | ignore_basepython_conflict=true 9 | 10 | [testenv] 11 | basepython = python3 12 | usedevelop = True 13 | setenv = VIRTUAL_ENV={envdir} 14 | OS_STDOUT_CAPTURE=1 15 | OS_STDERR_CAPTURE=1 16 | OS_TEST_TIMEOUT=60 17 | OS_TEST_PATH=./brick_cinderclient_ext/tests/unit 18 | passenv = 19 | *_proxy 20 | *_PROXY 21 | 22 | deps = 23 | -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} 24 | -r{toxinidir}/test-requirements.txt 25 | -r{toxinidir}/requirements.txt 26 | commands = 27 | find . -type f -name "*.pyc" -delete 28 | stestr run {posargs} 29 | allowlist_externals = find 30 | 31 | [testenv:pep8] 32 | commands = 33 | flake8 {posargs} 34 | doc8 35 | 36 | [testenv:fast8] 37 | envdir = {toxworkdir}/pep8 38 | commands = {toxinidir}/tools/fast8.sh 39 | 40 | [testenv:venv] 41 | commands = {posargs} 42 | 43 | [testenv:functional] 44 | setenv = 45 | OS_TEST_PATH=./brick_cinderclient_ext/tests/functional 46 | passenv = OS_* 47 | 48 | [testenv:functional-py{3,310,311,312,313}] 49 | setenv = 50 | {[testenv:functional]setenv} 51 | 52 | [testenv:cover] 53 | setenv = 54 | {[testenv]setenv} 55 | PYTHON=coverage run --source brick_cinderclient_ext --parallel-mode 56 | commands = 57 | stestr run {posargs} 58 | coverage combine 59 | coverage html -d cover 60 | coverage xml -o cover/coverage.xml 61 | 62 | [testenv:docs] 63 | deps = 64 | -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} 65 | -r{toxinidir}/requirements.txt 66 | -r{toxinidir}/doc/requirements.txt 67 | commands = sphinx-build -b html doc/source doc/build/html 68 | 69 | [testenv:releasenotes] 70 | deps = {[testenv:docs]deps} 71 | commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html 72 | 73 | [testenv:debug] 74 | commands = oslo_debug_helper -t brick_cinderclient_ext/tests/unit {posargs} 75 | 76 | [testenv:bindep] 77 | # Do not install any requirements. We want this to be fast and work even if 78 | # system dependencies are missing, since it's used to tell you what system 79 | # dependencies are missing! This also means that bindep must be installed 80 | # separately, outside of the requirements files, and develop mode disabled 81 | # explicitly to avoid unnecessarily installing the checked-out repo too (this 82 | # further relies on "tox.skipsdist = True" above). 83 | deps = bindep 84 | commands = bindep test 85 | usedevelop = False 86 | 87 | [flake8] 88 | show-source = True 89 | builtins = _ 90 | exclude=.venv,.git,.tox,dist,doc/*,*lib/python*,*egg,build 91 | 92 | [doc8] 93 | ignore-path=.tox,*.egg-info,doc/src/api,doc/source/drivers.rst,doc/build,.eggs/*/EGG-INFO/*.txt,doc/source/configuration/tables,./*.txt,releasenotes/build 94 | extension=.txt,.rst,.inc 95 | 96 | --------------------------------------------------------------------------------