././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1736516523.7365696 python-blazarclient-4.2.0/0000775000175000017500000000000000000000000015531 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/.stestr.conf0000664000175000017500000000006500000000000020003 0ustar00zuulzuul00000000000000[DEFAULT] test_path=./blazarclient/tests/ top_dir=./ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/.zuul.yaml0000664000175000017500000000022400000000000017470 0ustar00zuulzuul00000000000000- project: templates: - check-requirements - openstack-python3-jobs - release-notes-jobs-python3 - openstack-cover-jobs ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516523.0 python-blazarclient-4.2.0/AUTHORS0000664000175000017500000000467400000000000016614 0ustar00zuulzuul00000000000000Anand Bhat Andreas Jaeger Corey Bryant Cristian A Sanchez David Moreau Simard Dina Belova Doug Hellmann François Rossigneux Ghanshyam Mann Govardhan Chintha Harsh Shah Hervé Beraud Hiroaki Kobayashi Hiroki Ito Jason Anderson Jay Lau Jeremy Stanley John Garbutt Mark Powers Martin Kopec Masahito Muroi Matt Crees Michael Still Monty Taylor Nguyen Hai Nikolaj Starodubtsev Nikolay Starodubtsev Ondřej Nový OpenStack Release Bot Pablo Andres Fuente Pierre Riteau Pierre Riteau Radosław Piliszek Sairam Vengala Sam Morrison Sean McGinnis Sean McGinnis Sergey Lukjanov ShangXiao Steve Martinelli Sylvain Bauza Takashi Natsume Tetsuro Nakamura Tetsuro Nakamura Thomas Goirand Tony Breeds Vieri <15050873171@163.com> ZhijunWei chenghuiyu cmart jacky06 jakecoll kangyufei luqitao lvxianguo maaoyu melissaml niuke qingszhao rajat29 sunjia wangzihao wu.chunyang wuchunyang zhangboye ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516523.0 python-blazarclient-4.2.0/ChangeLog0000664000175000017500000001701600000000000017310 0ustar00zuulzuul00000000000000CHANGES ======= 4.2.0 ----- * Replace deprecated datetime.utcnow() * Fix syntax warning under Python 3.12/3.13 * reno: Update master for unmaintained/2023.1 * Update master for stable/2024.2 4.1.0 ----- * Bump hacking to 7.0.0 and fix linting issues * Support flavor-based instance reservation * reno: Update master for unmaintained/zed * Update master for stable/2024.1 * reno: Update master for unmaintained/xena * reno: Update master for unmaintained/wallaby * reno: Update master for unmaintained/victoria 4.0.1 ----- * reno: Update master for unmaintained/yoga 4.0.0 ----- * Update python classifier in setup.cfg * Use generic testing template * Update master for stable/2023.2 3.7.0 ----- * Update master for stable/2023.1 3.6.0 ----- * Add Resource Allocations API support * Separate ID parser argument from ShowCommand * Add Python3 antelope unit tests * Update master for stable/zed 3.5.0 ----- * remove unicode prefix from code * Remove Babel from requirements * [CI] Run Zed jobs instead of Yoga * Update configuration for compatibility with Sphinx 5 * Drop lower-constraints.txt and its testing * Update master for stable/yoga 3.4.0 ----- * Add commands for resource property API * Updating python testing classifier as per Yoga testing runtime * Add Python3 yoga unit tests * Update master for stable/xena 3.3.1 ----- * Fix up client init 3.3.0 ----- * Add openstackclient support * Migrate from testr to stestr * Default client service type to reservation * Add Python3 xena unit tests * Update master for stable/wallaby * Change dashes to underscore in setup.cfg 3.2.0 ----- * Provide a default affinity value * Uncap PrettyTable * Use unittest.mock instead of third party mock * Remove use of mock.seal() * Bump hacking max version to 3.0.1 and fix pep8 * Replace deprecated UPPER\_CONSTRAINTS\_FILE variable * Remove six * Remove the unused coding style modules * bump py37 to py38 in tox.ini * Remove install unnecessary packages * Add Python3 wallaby unit tests * Update master for stable/victoria 3.1.1 ----- * Use KSA loading to support more auth methods * Fixup failing tests due to passing date threshold * Fix test\_args2body\_start\_now unit test * migrate testing to ubuntu focal * Stop to use the \_\_future\_\_ module 3.1.0 ----- * Switch to newer openstackdocstheme and reno versions * Add py38 package metadata * Add Python3 victoria unit tests * Update master for stable/ussuri 3.0.1 ----- * Cleanup py27 support 3.0.0 ----- * Don't fetch entire list when looking up by ID * tox: Keeping going with docs and cleanup setup.cfg * Stop testing with py2 * Switch to Ussuri jobs * Update master for stable/train 2.2.1 ----- * Support floating IP reservation parameters in lease-update * Parse required\_floatingips parameter as a JSON array 2.2.0 ----- * Add release note for floating IP support * Clarify how reservation parameters should be defined * Fix parsing of network\_id reservation parameter * Add Python 3 Train unit tests * Cap sphinx for py2 to match global requirements * Add support for floating IP reservation * Replace git.openstack.org URLs with opendev.org URLs * OpenDev Migration Patch * Dropping the py35 testing * Update master for stable/stein * add python 3.7 unit test job * Update hacking version * Use template for lower-constraints * Fix typo in exception class name * Change openstack-dev to openstack-discuss * Add Python 3.6 classifier to setup.cfg * add python 3.6 unit test job 2.1.0 ----- * Add release notes link to README * add python 3.6 unit test job * switch documentation job to new PTI * import zuul job settings from project-config * Fix link to oslo.i18n usage documentation * Fix exception message when there are leases with the same name * Fix lease creation when start date is not provided * Update reno for stable/rocky 2.0.0 ----- * Set start date to 'now' rather than current time * Catch exceptions for session client * Add release note about region support * Add reno for release notes management * fix tox python3 overrides 1.1.1 ----- * Respect selected region in multi-region clouds 1.1.0 ----- * Support resource\_properties key in instance\_reservation * add lower-constraints job * Support hostname in show, update and delete host operations * Remove climate namespace * Fix URL to the hacking documentation * Updated from global requirements * Add Python3 to the setup configuration * Remove commas in setup.cfg package classifiers * Fix issues with Python3 1.0.0 ----- * Updated from global requirements * Align json indents * Remove mox from requirements * Fix incorrect descriptions of host commands * Updated from global requirements * Avoid tox\_install.sh for constraints support * Support update lease API for instance reservation plugin * Updated from global requirements * Replace six.iteritems() with .items() * Improve README 0.3.1 ----- * Restore backward compatibility for init client * Updated from global requirements * Updated from global requirements * Updated from global requirements * Migrate to keystoneauth1 * Stop using oslo\_utils.timeutils.strtime() * Updated from global requirements * Use fixtures instead of deprecated mockpatch module 0.3.0 ----- * Add a reservation parameter to the lease\_update command * Add before\_end\_date and before\_end parameters * Accept multiple properties * Fix failure to create physical reservations * Fix unicode issues with Python 3 * Convert integer style parameters to integer values * Output debug messages when using --debug option * Updated from global requirements * Remove unnecessary output * Updated from global requirements * Enable lease-update to update start/end time with date * Updated from global requirements * Updated from global requirements * Validation for --physical-reservation argument * Updated from global requirements * Remove log translations 0.2.0 ----- * Updated from global requirements * Updated from global requirements * Migrate Python namespace from climateclient to blazarclient * Support Keystone v3 API in CLI * Remove discover from test-requirements * Add Constraints support * Update default environment list * Updated from global requirements * Fix help messages for update commands * use oslotest * use oslo.i18n * use oslo.utils * Remove o/c/local.py and o/c/log.py * Update .gitreview for new namespace * Fix testing * Deprecated tox -downloadcache option removed * Updated from global requirements * Added option --advance-by to allow advancing of lease start date * Updated from global requirements * Updated from global requirements * Add the option defer-by to postpone start\_date * Add test coverage for base client modules * Update gitreview file for repo rename * Error message is not correct when list/show non-exist lease * Updated from global requirements * Climate client now shows the list of commands * Handle elapsed\_time params in climate client * Add new option to allow lease reductions * Updated from global requirements * Add sort-by option for climateclient list commands * Removing dependencies from test-requirements * Updated from global requirements * Updated from global requirements * Add test framework for climateclient * Updated from global requirements * Support not-json error responses * Fix PEP8 H302 import only modules 0.1.0 ----- * Add LICENSE and MANIFEST.in files * Fix setup.cfg * Fix exception rendering for client * Updated from global requirements * Implement support for provisioning hosts to ClimateClient * Fix date format in update lease * Implement Lease creation for Physical reservations * Initial Climate client implementation * Add HACKING.rst with link to the style guidelines * Added .gitreview ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/HACKING.rst0000664000175000017500000000035500000000000017332 0ustar00zuulzuul00000000000000Blazar Style Commandments ========================= - Step 1: Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/ - Step 2: Read on Blazar Specific Commandments ---------------------------- None so far ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/LICENSE0000664000175000017500000002363600000000000016550 0ustar00zuulzuul00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/MANIFEST.in0000664000175000017500000000020200000000000017261 0ustar00zuulzuul00000000000000include AUTHORS include README.rst include ChangeLog include LICENSE exclude .gitignore exclude .gitreview global-exclude *.pyc ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1736516523.7365696 python-blazarclient-4.2.0/PKG-INFO0000664000175000017500000000437300000000000016635 0ustar00zuulzuul00000000000000Metadata-Version: 1.2 Name: python-blazarclient Version: 4.2.0 Summary: Client for OpenStack Reservation Service Home-page: https://launchpad.net/blazar Author: OpenStack Author-email: openstack-discuss@lists.openstack.org License: Apache Software License Description: ======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/python-blazarclient.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on ============= Blazar client ============= This is a client for the OpenStack Blazar API. It provides a Python API (the **blazarclient** module) and a command-line script (**blazar**). Other Resources --------------- * Source code: * `Blazar `__ * `Nova scheduler filter `__ * `Client tools `__ * `Dashboard (Horizon plugin) `__ * Blueprints/Bugs: https://launchpad.net/blazar * Documentation: https://docs.openstack.org/blazar/latest/ * Release notes: https://docs.openstack.org/releasenotes/python-blazarclient/ Platform: UNKNOWN Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Environment :: OpenStack Classifier: Development Status :: 3 - Alpha Classifier: Framework :: Setuptools Plugin Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Requires-Python: >=3.8 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/README.rst0000664000175000017500000000172500000000000017225 0ustar00zuulzuul00000000000000======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/python-blazarclient.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on ============= Blazar client ============= This is a client for the OpenStack Blazar API. It provides a Python API (the **blazarclient** module) and a command-line script (**blazar**). Other Resources --------------- * Source code: * `Blazar `__ * `Nova scheduler filter `__ * `Client tools `__ * `Dashboard (Horizon plugin) `__ * Blueprints/Bugs: https://launchpad.net/blazar * Documentation: https://docs.openstack.org/blazar/latest/ * Release notes: https://docs.openstack.org/releasenotes/python-blazarclient/ ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1736516523.7205694 python-blazarclient-4.2.0/blazarclient/0000775000175000017500000000000000000000000020203 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/blazarclient/__init__.py0000664000175000017500000000000000000000000022302 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/blazarclient/base.py0000664000175000017500000001171000000000000021467 0ustar00zuulzuul00000000000000# Copyright (c) 2013 Mirantis Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from keystoneauth1 import adapter from oslo_serialization import jsonutils import requests from blazarclient import exception from blazarclient.i18n import _ class RequestManager(object): """Manager to create request from given Blazar URL and auth token.""" def __init__(self, blazar_url, auth_token, user_agent): self.blazar_url = blazar_url self.auth_token = auth_token self.user_agent = user_agent def get(self, url): """Sends get request to Blazar. :param url: URL to the wanted Blazar resource. :type url: str """ return self.request(url, 'GET') def post(self, url, body): """Sends post request to Blazar. :param url: URL to the wanted Blazar resource. :type url: str :param body: Values resource to be created from. :type body: dict """ return self.request(url, 'POST', body=body) def delete(self, url): """Sends delete request to Blazar. :param url: URL to the wanted Blazar resource. :type url: str """ return self.request(url, 'DELETE') def put(self, url, body): """Sends update request to Blazar. :param url: URL to the wanted Blazar resource. :type url: str :param body: Values resource to be updated from. :type body: dict """ return self.request(url, 'PUT', body=body) def patch(self, url, body): """Sends patch request to Blazar. :param url: URL to the wanted Blazar resource. :type url: str """ return self.request(url, 'PATCH', body=body) def request(self, url, method, **kwargs): """Base request method. Adds specific headers and URL prefix to the request. :param url: Resource URL. :type url: str :param method: Method to be called (GET, POST, PUT, DELETE). :type method: str :returns: Response and body. :rtype: tuple """ kwargs.setdefault('headers', kwargs.get('headers', {})) kwargs['headers']['User-Agent'] = self.user_agent kwargs['headers']['Accept'] = 'application/json' kwargs['headers']['x-auth-token'] = self.auth_token if 'body' in kwargs: kwargs['headers']['Content-Type'] = 'application/json' kwargs['data'] = jsonutils.dump_as_bytes(kwargs['body']) del kwargs['body'] resp = requests.request(method, self.blazar_url + url, **kwargs) try: body = jsonutils.loads(resp.text) except ValueError: body = None if resp.status_code >= 400: if body is not None: error_message = body.get('error_message', body) else: error_message = resp.text body = _("ERROR: {0}").format(error_message) raise exception.BlazarClientException(body, code=resp.status_code) return resp, body class SessionClient(adapter.LegacyJsonAdapter): """Manager to create request with keystoneauth1 session.""" def request(self, url, method, **kwargs): resp, body = super(SessionClient, self).request( url, method, raise_exc=False, **kwargs) if resp.status_code >= 400: if body is not None: error_message = body.get('error_message', body) else: error_message = resp.text msg = _("ERROR: {0}").format(error_message) raise exception.BlazarClientException(msg, code=resp.status_code) return resp, body class BaseClientManager(object): """Base class for managing resources of Blazar.""" user_agent = 'python-blazarclient' def __init__(self, blazar_url, auth_token, session, **kwargs): self.blazar_url = blazar_url self.auth_token = auth_token self.session = session if self.session: self.request_manager = SessionClient( session=self.session, user_agent=self.user_agent, **kwargs ) elif self.blazar_url and self.auth_token: self.request_manager = RequestManager(blazar_url=self.blazar_url, auth_token=self.auth_token, user_agent=self.user_agent) else: raise exception.InsufficientAuthInformation ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/blazarclient/client.py0000664000175000017500000000263200000000000022036 0ustar00zuulzuul00000000000000# Copyright (c) 2013 Mirantis Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from oslo_utils import importutils from blazarclient import exception from blazarclient.i18n import _ def Client(version=1, service_type='reservation', *args, **kwargs): version_map = { '1': 'blazarclient.v1.client.Client', '1a0': 'blazarclient.v1.client.Client', } try: client_path = version_map[str(version)] except (KeyError, ValueError): msg = _("Invalid client version '%(version)s'. " "Must be one of: %(available_version)s") % ({ 'version': version, 'available_version': ', '.join(version_map.keys()) }) raise exception.UnsupportedVersion(msg) return importutils.import_object(client_path, service_type=service_type, *args, **kwargs) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/blazarclient/command.py0000664000175000017500000003024000000000000022172 0ustar00zuulzuul00000000000000# Copyright (c) 2013 Mirantis Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import ast import logging from cliff import command from cliff.formatters import table from cliff import lister from cliff import show from blazarclient import exception from blazarclient import utils HEX_ELEM = '[0-9A-Fa-f]' UUID_PATTERN = '-'.join([HEX_ELEM + '{8}', HEX_ELEM + '{4}', HEX_ELEM + '{4}', HEX_ELEM + '{4}', HEX_ELEM + '{12}']) class OpenStackCommand(command.Command): """Base class for OpenStack commands.""" api = None def run(self, parsed_args): if not self.api: return else: return super(OpenStackCommand, self).run(parsed_args) def get_data(self, parsed_args): pass def take_action(self, parsed_args): return self.get_data(parsed_args) class TableFormatter(table.TableFormatter): """This class is used to keep consistency with prettytable 0.6.""" def emit_list(self, column_names, data, stdout, parsed_args): if column_names: super(TableFormatter, self).emit_list(column_names, data, stdout, parsed_args) else: stdout.write('\n') class BlazarCommand(OpenStackCommand): """Base Blazar CLI command.""" api = 'reservation' log = logging.getLogger(__name__ + '.BlazarCommand') values_specs = [] json_indent = None resource = None allow_names = True name_key = None id_pattern = UUID_PATTERN def __init__(self, app, app_args): super(BlazarCommand, self).__init__(app, app_args) # NOTE(dbelova): This is no longer supported in cliff version 1.5.2 # the same moment occurred in Neutron: # see https://bugs.launchpad.net/python-neutronclient/+bug/1265926 # if hasattr(self, 'formatters'): # self.formatters['table'] = TableFormatter() def get_client(self): # client_manager.reservation is used for osc_lib, and should be used # if it exists if hasattr(self.app, 'client_manager'): return self.app.client_manager.reservation else: return self.app.client def get_parser(self, prog_name): parser = super(BlazarCommand, self).get_parser(prog_name) return parser def format_output_data(self, data): for k, v in data.items(): if isinstance(v, str): try: # Deserialize if possible into dict, lists, tuples... v = ast.literal_eval(v) except SyntaxError: # NOTE(sbauza): This is probably a datetime string, we need # to keep it unchanged. pass except ValueError: # NOTE(sbauza): This is not something AST can evaluate, # probably a string. pass if isinstance(v, list): value = '\n'.join(utils.dumps( i, indent=self.json_indent) if isinstance(i, dict) else str(i) for i in v) data[k] = value elif isinstance(v, dict): value = utils.dumps(v, indent=self.json_indent) data[k] = value elif v is None: data[k] = '' def add_known_arguments(self, parser): pass def args2body(self, parsed_args): return {} class CreateCommand(BlazarCommand, show.ShowOne): """Create resource with passed args.""" api = 'reservation' resource = None log = None def get_data(self, parsed_args): self.log.debug('get_data(%s)' % parsed_args) blazar_client = self.get_client() body = self.args2body(parsed_args) resource_manager = getattr(blazar_client, self.resource) data = resource_manager.create(**body) self.format_output_data(data) if data: print('Created a new %s:' % self.resource, file=self.app.stdout) else: data = {'': ''} return list(zip(*sorted(data.items()))) class UpdateCommand(BlazarCommand): """Update resource's information.""" api = 'reservation' resource = None log = None def get_parser(self, prog_name): parser = super(UpdateCommand, self).get_parser(prog_name) if self.allow_names: help_str = 'ID or name of %s to update' else: help_str = 'ID of %s to update' parser.add_argument( 'id', metavar=self.resource.upper(), help=help_str % self.resource ) self.add_known_arguments(parser) return parser def run(self, parsed_args): self.log.debug('run(%s)' % parsed_args) blazar_client = self.get_client() body = self.args2body(parsed_args) if self.allow_names: res_id = utils.find_resource_id_by_name_or_id(blazar_client, self.resource, parsed_args.id, self.name_key, self.id_pattern) else: res_id = parsed_args.id resource_manager = getattr(blazar_client, self.resource) resource_manager.update(res_id, **body) print('Updated %s: %s' % (self.resource, parsed_args.id), file=self.app.stdout) return class DeleteCommand(BlazarCommand): """Delete a given resource.""" api = 'reservation' resource = None log = None def get_parser(self, prog_name): parser = super(DeleteCommand, self).get_parser(prog_name) if self.allow_names: help_str = 'ID or name of %s to delete' else: help_str = 'ID of %s to delete' parser.add_argument( 'id', metavar=self.resource.upper(), help=help_str % self.resource) return parser def run(self, parsed_args): self.log.debug('run(%s)' % parsed_args) blazar_client = self.get_client() resource_manager = getattr(blazar_client, self.resource) if self.allow_names: res_id = utils.find_resource_id_by_name_or_id(blazar_client, self.resource, parsed_args.id, self.name_key, self.id_pattern) else: res_id = parsed_args.id resource_manager.delete(res_id) print('Deleted %s: %s' % (self.resource, parsed_args.id), file=self.app.stdout) return class ListCommand(BlazarCommand, lister.Lister): """List resources that belong to a given tenant.""" api = 'reservation' resource = None log = None _formatters = {} list_columns = [] unknown_parts_flag = True def args2body(self, parsed_args): params = {} if parsed_args.sort_by: if parsed_args.sort_by in self.list_columns: params['sort_by'] = parsed_args.sort_by else: msg = 'Invalid sort option %s' % parsed_args.sort_by raise exception.BlazarClientException(msg) return params def get_parser(self, prog_name): parser = super(ListCommand, self).get_parser(prog_name) return parser def retrieve_list(self, parsed_args): """Retrieve a list of resources from Blazar server.""" blazar_client = self.get_client() body = self.args2body(parsed_args) resource_manager = getattr(blazar_client, self.resource) data = resource_manager.list(**body) return data def setup_columns(self, info, parsed_args): columns = len(info) > 0 and sorted(info[0].keys()) or [] if not columns: parsed_args.columns = [] elif parsed_args.columns: columns = [col for col in parsed_args.columns if col in columns] elif self.list_columns: columns = [col for col in self.list_columns if col in columns] return ( columns, (utils.get_item_properties(s, columns, formatters=self._formatters) for s in info) ) def get_data(self, parsed_args): self.log.debug('get_data(%s)' % parsed_args) data = self.retrieve_list(parsed_args) return self.setup_columns(data, parsed_args) class ShowCommand(BlazarCommand, show.ShowOne): """Show information of a given resource.""" api = 'reservation' resource = None log = None def get_parser(self, prog_name): parser = super(ShowCommand, self).get_parser(prog_name) return parser def get_data(self, parsed_args): self.log.debug('get_data(%s)' % parsed_args) blazar_client = self.get_client() if self.allow_names: res_id = utils.find_resource_id_by_name_or_id(blazar_client, self.resource, parsed_args.id, self.name_key, self.id_pattern) else: res_id = parsed_args.id resource_manager = getattr(blazar_client, self.resource) data = resource_manager.get(res_id) self.format_output_data(data) return list(zip(*sorted(data.items()))) class ShowPropertyCommand(BlazarCommand, show.ShowOne): """Show information of a given resource property.""" api = 'reservation' resource = None log = None def get_parser(self, prog_name): parser = super(ShowPropertyCommand, self).get_parser(prog_name) parser.add_argument('property_name', metavar='PROPERTY_NAME', help='Name of property.') return parser def get_data(self, parsed_args): self.log.debug('get_data(%s)' % parsed_args) blazar_client = self.get_client() resource_manager = getattr(blazar_client, self.resource) data = resource_manager.get_property(parsed_args.property_name) if parsed_args.formatter == 'table': self.format_output_data(data) return list(zip(*sorted(data.items()))) class UpdatePropertyCommand(BlazarCommand): api = 'reservation' resource = None log = None def run(self, parsed_args): self.log.debug('run(%s)' % parsed_args) blazar_client = self.get_client() body = self.args2body(parsed_args) resource_manager = getattr(blazar_client, self.resource) resource_manager.set_property(**body) print( 'Updated %s property: %s' % ( self.resource, parsed_args.property_name), file=self.app.stdout) return def get_parser(self, prog_name): parser = super(UpdatePropertyCommand, self).get_parser(prog_name) parser.add_argument( 'property_name', metavar='PROPERTY_NAME', help='Name of property to patch.' ) parser.add_argument( '--private', action='store_true', default=False, help='Set property to private.' ) parser.add_argument( '--public', action='store_true', default=False, help='Set property to public.' ) return parser def args2body(self, parsed_args): return dict( property_name=parsed_args.property_name, private=(parsed_args.private is True)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/blazarclient/exception.py0000664000175000017500000000601000000000000022550 0ustar00zuulzuul00000000000000# Copyright (c) 2013 Mirantis Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from blazarclient.i18n import _ class BlazarClientException(Exception): """Base exception class.""" message = _("An unknown exception occurred %s.") code = 500 def __init__(self, message=None, **kwargs): self.kwargs = kwargs if 'code' not in self.kwargs: try: self.kwargs['code'] = self.code except AttributeError: pass if not message: message = self.message % kwargs super(BlazarClientException, self).__init__(message) class CommandError(BlazarClientException): """Occurs if not all authentication vital options are set.""" message = _("You have to provide all options like user name or tenant " "id to make authentication possible.") code = 401 class NotAuthorized(BlazarClientException): """HTTP 401 - Not authorized. User have no enough rights to perform action. """ code = 401 message = _("Not authorized request.") class NoBlazarEndpoint(BlazarClientException): """Occurs if no endpoint for Blazar set in the Keystone.""" message = _("No publicURL endpoint for Blazar found. Set endpoint " "for Blazar in the Keystone.") code = 404 class NoUniqueMatch(BlazarClientException): """Occurs if there are more than one appropriate resources.""" message = _("There is no unique requested resource.") code = 409 class UnsupportedVersion(BlazarClientException): """Occurs if unsupported client version was requested.""" message = _("Unsupported client version requested.") code = 406 class IncorrectLease(BlazarClientException): """Occurs if lease parameters are incorrect.""" message = _("The lease parameters are incorrect.") code = 409 class DuplicatedLeaseParameters(BlazarClientException): """Occurs if lease parameters are duplicated.""" message = _("The lease parameters are duplicated.") code = 400 class InsufficientAuthInformation(BlazarClientException): """Occurs if the auth info passed to blazar client is insufficient.""" message = _("The passed arguments are insufficient " "for the authentication. The instance of " "keystoneauth1.session.Session class is required.") code = 400 class ResourcePropertyNotFound(BlazarClientException): """Occurs if the resource property specified does not exist""" message = _("The resource property does not exist.") code = 404 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/blazarclient/i18n.py0000664000175000017500000000153100000000000021334 0ustar00zuulzuul00000000000000# Copyright 2014 IBM Corp. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """oslo.i18n integration module. See https://docs.openstack.org/oslo.i18n/latest/user/usage.html . """ import oslo_i18n _translators = oslo_i18n.TranslatorFactory(domain='blazarclient') # The primary translation function using the well-known name "_" _ = _translators.primary ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1736516523.7205694 python-blazarclient-4.2.0/blazarclient/osc/0000775000175000017500000000000000000000000020767 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/blazarclient/osc/__init__.py0000664000175000017500000000000000000000000023066 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/blazarclient/osc/plugin.py0000664000175000017500000000411000000000000022633 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import logging from osc_lib import utils LOG = logging.getLogger(__name__) DEFAULT_API_VERSION = '1' # Required by the OSC plugin interface API_NAME = 'reservation' API_VERSION_OPTION = 'os_reservations_api_version' API_VERSIONS = { '1': 'blazarclient.v1.client.Client', } # Required by the OSC plugin interface def make_client(instance): reservation_client = utils.get_client_class( API_NAME, instance._api_version[API_NAME], API_VERSIONS) LOG.debug("Instantiating reservation client: %s", reservation_client) client = reservation_client( instance._api_version[API_NAME], session=instance.session, endpoint_override=instance.get_endpoint_for_service_type( API_NAME, interface=instance.interface, region_name=instance._region_name) ) return client # Required by the OSC plugin interface def build_option_parser(parser): """Hook to add global options. Called from openstackclient.shell.OpenStackShell.__init__() after the builtin parser has been initialized. This is where a plugin can add global options such as an API version setting. :param argparse.ArgumentParser parser: The parser object that has been initialized by OpenStackShell. """ parser.add_argument( "--os-reservation-api-version", metavar="", help="Reservation API version, default=" "{} (Env: OS_RESERVATION_API_VERSION)".format( DEFAULT_API_VERSION) ) return parser ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/blazarclient/shell.py0000664000175000017500000003324200000000000021670 0ustar00zuulzuul00000000000000# Copyright (c) 2013 Mirantis Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. """ Command-line interface to the Blazar APIs """ import argparse import logging import os import sys from cliff import app from cliff import commandmanager from keystoneauth1 import loading from oslo_utils import encodeutils from blazarclient import client as blazar_client from blazarclient import exception from blazarclient.v1.shell_commands import allocations from blazarclient.v1.shell_commands import floatingips from blazarclient.v1.shell_commands import hosts from blazarclient.v1.shell_commands import leases from blazarclient import version as base_version COMMANDS_V1 = { 'lease-list': leases.ListLeases, 'lease-show': leases.ShowLease, 'lease-create': leases.CreateLease, 'lease-update': leases.UpdateLease, 'lease-delete': leases.DeleteLease, 'host-list': hosts.ListHosts, 'host-show': hosts.ShowHost, 'host-create': hosts.CreateHost, 'host-update': hosts.UpdateHost, 'host-delete': hosts.DeleteHost, 'host-property-list': hosts.ListHostProperties, 'host-property-show': hosts.ShowHostProperty, 'host-property-set': hosts.UpdateHostProperty, 'floatingip-list': floatingips.ListFloatingIPs, 'floatingip-show': floatingips.ShowFloatingIP, 'floatingip-create': floatingips.CreateFloatingIP, 'floatingip-delete': floatingips.DeleteFloatingIP, 'allocation-list': allocations.ListAllocations, 'allocation-show': allocations.ShowAllocations, } VERSION = 1 DEFAULT_API_VERSION = 1 COMMANDS = {'v1': COMMANDS_V1} def run_command(cmd, cmd_parser, sub_argv): _argv = sub_argv index = -1 values_specs = [] if '--' in sub_argv: index = sub_argv.index('--') _argv = sub_argv[:index] values_specs = sub_argv[index:] known_args, _values_specs = cmd_parser.parse_known_args(_argv) cmd.values_specs = (index == -1 and _values_specs or values_specs) return cmd.run(known_args) def env(*_vars, **kwargs): """Search for the first defined of possibly many env vars. Returns the first environment variable defined in vars, or returns the default defined in kwargs. """ for v in _vars: value = os.environ.get(v, None) if value: return value return kwargs.get('default', '') class HelpAction(argparse.Action): """Provide a custom action so the -h and --help options to the main app will print a list of the commands. The commands are determined by checking the CommandManager instance, passed in as the "default" value for the action. """ def __call__(self, parser, namespace, values, option_string=None): outputs = [] max_len = 0 app = self.default parser.print_help(app.stdout) app.stdout.write('\nCommands for API %s:\n' % app.api_version) command_manager = app.command_manager for name, ep in sorted(command_manager): factory = ep.load() cmd = factory(self, None) one_liner = cmd.get_description().split('\n')[0] outputs.append((name, one_liner)) max_len = max(len(name), max_len) for (name, one_liner) in outputs: app.stdout.write(' %s %s\n' % (name.ljust(max_len), one_liner)) sys.exit(0) class BlazarShell(app.App): """Manager class for the Blazar CLI.""" CONSOLE_MESSAGE_FORMAT = '%(message)s' DEBUG_MESSAGE_FORMAT = '%(levelname)s: %(name)s %(message)s' log = logging.getLogger(__name__) def __init__(self): super(BlazarShell, self).__init__( description=__doc__.strip(), version=VERSION, command_manager=commandmanager.CommandManager('blazar.cli'), ) self.commands = COMMANDS def build_option_parser(self, description, version, argparse_kwargs=None): """Return an argparse option parser for this application. Subclasses may override this method to extend the parser with more global options. """ parser = argparse.ArgumentParser( description=description, add_help=False) parser.add_argument( '--version', action='version', version=base_version.__version__) parser.add_argument( '-v', '--verbose', action='count', dest='verbose_level', default=self.DEFAULT_VERBOSE_LEVEL, help='Increase verbosity of output. Can be repeated.') parser.add_argument( '-q', '--quiet', action='store_const', dest='verbose_level', const=0, help='suppress output except warnings and errors') help_action = parser.add_argument( '-h', '--help', action=HelpAction, nargs=0, default=self, help="show this help message and exit") parser.add_argument( '--debug', default=False, action='store_true', help='Print debugging output') # Removes help action to defer its execution self.deferred_help_action = help_action parser._actions.remove(help_action) del parser._option_string_actions['-h'] del parser._option_string_actions['--help'] parser.add_argument( '-h', '--help', action='store_true', dest='deferred_help', default=False, help="Show this help message and exit", ) # Global arguments parser.add_argument( '--os-reservation-api-version', default=env('OS_RESERVATION_API_VERSION', default=DEFAULT_API_VERSION), help='Accepts 1 now, defaults to 1.') parser.add_argument( '--os_reservation_api_version', help=argparse.SUPPRESS) # Deprecated arguments parser.add_argument( '--service-type', metavar='', default=env('BLAZAR_SERVICE_TYPE'), help=('(deprecated) Use --os-service-type instead. ' 'Defaults to env[BLAZAR_SERVICE_TYPE].')) parser.add_argument( '--endpoint-type', metavar='', default=env('OS_ENDPOINT_TYPE'), help=('(deprecated) Use --os-interface instead. ' 'Defaults to env[OS_ENDPOINT_TYPE].')) return parser def _bash_completion(self): """Prints all of the commands and options for bash-completion.""" commands = set() options = set() for option, _action in self.parser._option_string_actions.items(): options.add(option) for command_name, command in self.command_manager: commands.add(command_name) cmd_factory = command.load() cmd = cmd_factory(self, None) cmd_parser = cmd.get_parser('') for option, _action in cmd_parser._option_string_actions.items(): options.add(option) print(' '.join(commands | options)) def run(self, argv): """Equivalent to the main program for the application. :param argv: input arguments and options :paramtype argv: list of str """ loading.register_auth_argparse_arguments(self.parser, argv) loading.session.register_argparse_arguments(self.parser) loading.adapter.register_argparse_arguments( self.parser, service_type='reservation') try: self.options, remainder = self.parser.parse_known_args(argv) self.api_version = 'v%s' % self.options.os_reservation_api_version for k, v in self.commands[self.api_version].items(): self.command_manager.add_command(k, v) index = 0 command_pos = -1 help_pos = -1 help_command_pos = -1 for arg in argv: if arg == 'bash-completion': self._bash_completion() return 0 if arg in self.commands[self.api_version]: if command_pos == -1: command_pos = index elif arg in ('-h', '--help'): if help_pos == -1: help_pos = index elif arg == 'help': if help_command_pos == -1: help_command_pos = index index += 1 if -1 < command_pos < help_pos: argv = ['help', argv[command_pos]] if help_command_pos > -1 and command_pos == -1: argv[help_command_pos] = '--help' if self.options.deferred_help: self.deferred_help_action(self.parser, self.parser, None, None) self.configure_logging() self.interactive_mode = not remainder self.initialize_app(remainder) except Exception as err: if self.options.debug: self.log.exception(str(err)) raise else: self.log.error(str(err)) return 1 if self.interactive_mode: _argv = [sys.argv[0]] sys.argv = _argv result = self.interact() else: result = self.run_subcommand(remainder) return result def run_subcommand(self, argv): subcommand = self.command_manager.find_command(argv) cmd_factory, cmd_name, sub_argv = subcommand cmd = cmd_factory(self, self.options) result = 1 try: self.prepare_to_run_command(cmd) full_name = (cmd_name if self.interactive_mode else ' '.join([self.NAME, cmd_name])) cmd_parser = cmd.get_parser(full_name) return run_command(cmd, cmd_parser, sub_argv) except Exception as err: if self.options.debug: self.log.exception(str(err)) else: self.log.error(str(err)) try: self.clean_up(cmd, result, err) except Exception as err2: if self.options.debug: self.log.exception(str(err2)) else: self.log.error('Could not clean up: %s', str(err2)) if self.options.debug: raise else: try: self.clean_up(cmd, result, None) except Exception as err3: if self.options.debug: self.log.exception(str(err3)) else: self.log.error('Could not clean up: %s', str(err3)) return result def authenticate_user(self): """Authenticate user and set client by using passed params.""" auth = loading.load_auth_from_argparse_arguments(self.options) sess = loading.load_session_from_argparse_arguments( self.options, auth=auth) self.client = blazar_client.Client( self.options.os_reservation_api_version, session=sess, service_type=(self.options.service_type or self.options.os_service_type), interface=self.options.endpoint_type or self.options.os_interface, region_name=self.options.os_region_name, ) return def initialize_app(self, argv): """Global app init bits: * set up API versions * validate authentication info """ super(BlazarShell, self).initialize_app(argv) cmd_name = None if argv: cmd_info = self.command_manager.find_command(argv) cmd_factory, cmd_name, sub_argv = cmd_info if self.interactive_mode or cmd_name != 'help': self.authenticate_user() def clean_up(self, cmd, result, err): self.log.debug('clean_up %s', cmd.__class__.__name__) if err: self.log.debug('got an error: %s', str(err)) def configure_logging(self): """Create logging handlers for any log output.""" root_logger = logging.getLogger('') # Set up logging to a file root_logger.setLevel(logging.DEBUG) # Send higher-level messages to the console via stderr console = logging.StreamHandler(self.stderr) if self.options.debug: console_level = logging.DEBUG else: console_level = {0: logging.WARNING, 1: logging.INFO, 2: logging.DEBUG}.get(self.options.verbose_level, logging.DEBUG) console.setLevel(console_level) if logging.DEBUG == console_level: formatter = logging.Formatter(self.DEBUG_MESSAGE_FORMAT) else: formatter = logging.Formatter(self.CONSOLE_MESSAGE_FORMAT) console.setFormatter(formatter) root_logger.addHandler(console) return def main(argv=sys.argv[1:]): try: return BlazarShell().run(list(map(encodeutils.safe_decode, argv))) except exception.BlazarClientException: return 1 except Exception as e: print(str(e)) return 1 if __name__ == "__main__": sys.exit(main(sys.argv[1:])) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1736516523.7245696 python-blazarclient-4.2.0/blazarclient/tests/0000775000175000017500000000000000000000000021345 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/blazarclient/tests/__init__.py0000664000175000017500000000162200000000000023457 0ustar00zuulzuul00000000000000 # Copyright (c) 2014 Mirantis. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import fixtures from oslotest import base class TestCase(base.BaseTestCase): """Test case base class for all unit tests.""" def patch(self, obj, attr): """Returns a Mocked object on the patched attribute.""" mockfixture = self.useFixture(fixtures.MockPatchObject(obj, attr)) return mockfixture.mock ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/blazarclient/tests/test_base.py0000664000175000017500000001624100000000000023674 0ustar00zuulzuul00000000000000# Copyright (c) 2014 Mirantis Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from unittest import mock from blazarclient import base from blazarclient import exception from blazarclient import tests class RequestManagerTestCase(tests.TestCase): def setUp(self): super(RequestManagerTestCase, self).setUp() self.blazar_url = "www.fake.com/reservation" self.auth_token = "aaa-bbb-ccc" self.user_agent = "python-blazarclient" self.manager = base.RequestManager(blazar_url=self.blazar_url, auth_token=self.auth_token, user_agent=self.user_agent) @mock.patch('blazarclient.base.RequestManager.request', return_value=(200, {"fake": "FAKE"})) def test_get(self, m): url = '/leases' resp, body = self.manager.get(url) self.assertEqual(resp, 200) self.assertDictEqual(body, {"fake": "FAKE"}) m.assert_called_once_with(url, "GET") @mock.patch('blazarclient.base.RequestManager.request', return_value=(200, {"fake": "FAKE"})) def test_post(self, m): url = '/leases' req_body = { 'start': '2020-07-24 20:00', 'end': '2020-08-09 22:30', 'before_end': '2020-08-09 21:30', 'events': [], 'name': 'lease-test', 'reservations': [ { 'min': '1', 'max': '2', 'hypervisor_properties': '[">=", "$vcpus", "2"]', 'resource_properties': '["==", "$extra_key", "extra_value"]', 'resource_type': 'physical:host', 'before_end': 'default' } ] } resp, body = self.manager.post(url, req_body) self.assertEqual(resp, 200) self.assertDictEqual(body, {"fake": "FAKE"}) m.assert_called_once_with(url, "POST", body=req_body) @mock.patch('blazarclient.base.RequestManager.request', return_value=(200, {"fake": "FAKE"})) def test_delete(self, m): url = '/leases/aaa-bbb-ccc' resp, body = self.manager.delete(url) self.assertEqual(resp, 200) self.assertDictEqual(body, {"fake": "FAKE"}) m.assert_called_once_with(url, "DELETE") @mock.patch('blazarclient.base.RequestManager.request', return_value=(200, {"fake": "FAKE"})) def test_put(self, m): url = '/leases/aaa-bbb-ccc' req_body = { 'name': 'lease-test', } resp, body = self.manager.put(url, req_body) self.assertEqual(resp, 200) self.assertDictEqual(body, {"fake": "FAKE"}) m.assert_called_once_with(url, "PUT", body=req_body) @mock.patch('requests.request') def test_request_ok_with_body(self, m): m.return_value.status_code = 200 m.return_value.text = '{"resp_key": "resp_value"}' url = '/leases' kwargs = {"body": {"req_key": "req_value"}} self.assertEqual(self.manager.request(url, "POST", **kwargs), (m(), {"resp_key": "resp_value"})) @mock.patch('requests.request') def test_request_ok_without_body(self, m): m.return_value.status_code = 200 m.return_value.text = "resp" url = '/leases' kwargs = {"body": {"req_key": "req_value"}} self.assertEqual(self.manager.request(url, "POST", **kwargs), (m(), None)) @mock.patch('requests.request') def test_request_fail_with_body(self, m): m.return_value.status_code = 400 m.return_value.text = '{"resp_key": "resp_value"}' url = '/leases' kwargs = {"body": {"req_key": "req_value"}} self.assertRaises(exception.BlazarClientException, self.manager.request, url, "POST", **kwargs) @mock.patch('requests.request') def test_request_fail_without_body(self, m): m.return_value.status_code = 400 m.return_value.text = "resp" url = '/leases' kwargs = {"body": {"req_key": "req_value"}} self.assertRaises(exception.BlazarClientException, self.manager.request, url, "POST", **kwargs) class SessionClientTestCase(tests.TestCase): def setUp(self): super(SessionClientTestCase, self).setUp() self.manager = base.SessionClient(user_agent="python-blazarclient", session=mock.MagicMock()) @mock.patch('blazarclient.base.adapter.LegacyJsonAdapter.request') def test_request_ok(self, m): mock_resp = mock.Mock() mock_resp.status_code = 200 mock_body = {"resp_key": "resp_value"} m.return_value = (mock_resp, mock_body) url = '/leases' kwargs = {"body": {"req_key": "req_value"}} resp, body = self.manager.request(url, "POST", **kwargs) self.assertEqual((resp, body), (mock_resp, mock_body)) @mock.patch('blazarclient.base.adapter.LegacyJsonAdapter.request') def test_request_fail(self, m): resp = mock.Mock() resp.status_code = 400 body = {"error message": "error"} m.return_value = (resp, body) url = '/leases' kwargs = {"body": {"req_key": "req_value"}} self.assertRaises(exception.BlazarClientException, self.manager.request, url, "POST", **kwargs) class BaseClientManagerTestCase(tests.TestCase): def setUp(self): super(BaseClientManagerTestCase, self).setUp() self.blazar_url = "www.fake.com/reservation" self.auth_token = "aaa-bbb-ccc" self.session = mock.MagicMock() self.user_agent = "python-blazarclient" def test_init_with_session(self): manager = base.BaseClientManager(blazar_url=None, auth_token=None, session=self.session) self.assertIsInstance(manager.request_manager, base.SessionClient) def test_init_with_url_and_token(self): manager = base.BaseClientManager(blazar_url=self.blazar_url, auth_token=self.auth_token, session=None) self.assertIsInstance(manager.request_manager, base.RequestManager) def test_init_with_insufficient_info(self): self.assertRaises(exception.InsufficientAuthInformation, base.BaseClientManager, blazar_url=None, auth_token=self.auth_token, session=None) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/blazarclient/tests/test_client.py0000664000175000017500000000274400000000000024243 0ustar00zuulzuul00000000000000# Copyright (c) 2014 Mirantis Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from oslo_utils import importutils from blazarclient import client from blazarclient import exception from blazarclient import tests class BaseClientTestCase(tests.TestCase): def setUp(self): super(BaseClientTestCase, self).setUp() self.client = client self.import_obj = self.patch(importutils, "import_object") def test_with_v1(self): self.client.Client() self.import_obj.assert_called_once_with( 'blazarclient.v1.client.Client', service_type='reservation') def test_with_v1a0(self): self.client.Client(version='1a0') self.import_obj.assert_called_once_with( 'blazarclient.v1.client.Client', service_type='reservation') def test_with_wrong_vers(self): self.assertRaises(exception.UnsupportedVersion, self.client.Client, version='0.0') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/blazarclient/tests/test_command.py0000664000175000017500000001066700000000000024406 0ustar00zuulzuul00000000000000# Copyright (c) 2014 Mirantis Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from unittest import mock import testtools from blazarclient import command from blazarclient import tests class OpenstackCommandTestCase(tests.TestCase): def setUp(self): super(OpenstackCommandTestCase, self).setUp() @testtools.skip("Have no idea how to test super") def test_run(self): pass @testtools.skip("Unskip it when get_data will do smthg") def test_get_data(self): pass @testtools.skip("Unskip it when get_data will do smthg") def test_take_action(self): pass class TableFormatterTestCase(tests.TestCase): def setUp(self): super(TableFormatterTestCase, self).setUp() @testtools.skip("Have no idea how to test super") def test_emit_list(self): pass class BlazarCommandTestCase(tests.TestCase): def setUp(self): super(BlazarCommandTestCase, self).setUp() self.app = mock.MagicMock() self.parser = self.patch(command.OpenStackCommand, 'get_parser') self.command = command.BlazarCommand(self.app, []) def test_get_client(self): # Test that either client_manager.reservation or client is used, # whichever exists client_manager = self.app.client_manager del self.app.client_manager client = self.command.get_client() self.assertEqual(self.app.client, client) self.app.client_manager = client_manager del self.app.client client = self.command.get_client() self.assertEqual(self.app.client_manager.reservation, client) def test_get_parser(self): self.command.get_parser('TestCase') self.parser.assert_called_once_with('TestCase') def test_format_output_data(self): data_before = {'key_string': 'string_value', 'key_dict': {'key': 'value'}, 'key_list': ['1', '2', '3'], 'key_none': None} data_after = {'key_string': 'string_value', 'key_dict': '{"key": "value"}', 'key_list': '1\n2\n3', 'key_none': ''} self.command.format_output_data(data_before) self.assertEqual(data_after, data_before) class CreateCommandTestCase(tests.TestCase): def setUp(self): super(CreateCommandTestCase, self).setUp() self.app = mock.MagicMock() self.create_command = command.CreateCommand(self.app, []) self.client = self.patch(self.create_command, 'get_client') @testtools.skip("Under construction") def test_get_data_data(self): data = {'key_string': 'string_value', 'key_dict': "{'key0': 'value', 'key1': 'value'}", 'key_list': "['1', '2', '3',]", 'key_none': None} self.client.resource.return_value = mock.MagicMock(return_value=data) self.assertEqual(self.create_command.get_data({'a': 'b'}), None) @testtools.skip("Under construction") class UpdateCommandTestCase(tests.TestCase): def setUp(self): super(UpdateCommandTestCase, self).setUp() self.app = mock.MagicMock() self.update_command = command.UpdateCommand(self.app, []) @testtools.skip("Under construction") class DeleteCommandTestCase(tests.TestCase): def setUp(self): super(DeleteCommandTestCase, self).setUp() self.app = mock.MagicMock() self.delete_command = command.DeleteCommand(self.app, []) @testtools.skip("Under construction") class ListCommandTestCase(tests.TestCase): def setUp(self): super(ListCommandTestCase, self).setUp() self.app = mock.MagicMock() self.list_command = command.ListCommand(self.app, []) @testtools.skip("Under construction") class ShowCommandTestCase(tests.TestCase): def setUp(self): super(ShowCommandTestCase, self).setUp() self.app = mock.MagicMock() self.show_command = command.ShowCommand(self.app, []) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/blazarclient/tests/test_plugin.py0000664000175000017500000000230200000000000024251 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from unittest import mock from blazarclient.osc import plugin from blazarclient import tests class ReservationPluginTests(tests.TestCase): @mock.patch("blazarclient.v1.client.Client") def test_make_client(self, mock_client): instance = mock.Mock() instance._api_version = {"reservation": "1"} endpoint = "blazar_endpoint" instance.get_endpoint_for_service_type = mock.Mock( return_value=endpoint ) plugin.make_client(instance) mock_client.assert_called_with( "1", session=instance.session, endpoint_override=endpoint ) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/blazarclient/tests/test_shell.py0000664000175000017500000000617400000000000024075 0ustar00zuulzuul00000000000000# Copyright (c) 2014 Mirantis Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import io import re import sys import fixtures #note(n.s.): you may need it later #import mock import testtools #note(n.s.): you may need it later #from blazarclient import client as blazar_client #from blazarclient import exception from blazarclient import shell from blazarclient import tests FAKE_ENV = {'OS_USERNAME': 'username', 'OS_USER_DOMAIN_ID': 'user_domain_id', 'OS_PASSWORD': 'password', 'OS_TENANT_NAME': 'tenant_name', 'OS_PROJECT_NAME': 'project_name', 'OS_PROJECT_DOMAIN_ID': 'project_domain_id', 'OS_AUTH_URL': 'http://no.where'} class BlazarShellTestCase(tests.TestCase): def make_env(self, exclude=None, fake_env=FAKE_ENV): env = dict((k, v) for k, v in fake_env.items() if k != exclude) self.useFixture(fixtures.MonkeyPatch('os.environ', env)) def setUp(self): super(BlazarShellTestCase, self).setUp() #Create shell for non-specific tests self.blazar_shell = shell.BlazarShell() def shell(self, argstr, exitcodes=(0,)): orig = sys.stdout orig_stderr = sys.stderr try: sys.stdout = io.StringIO() sys.stderr = io.StringIO() _shell = shell.BlazarShell() _shell.initialize_app(argstr.split()) except SystemExit: exc_type, exc_value, exc_traceback = sys.exc_info() self.assertIn(exc_value.code, exitcodes) finally: stdout = sys.stdout.getvalue() sys.stdout.close() sys.stdout = orig stderr = sys.stderr.getvalue() sys.stderr.close() sys.stderr = orig_stderr return (stdout, stderr) def test_help_unknown_command(self): self.assertRaises(ValueError, self.shell, 'bash-completion') @testtools.skip('lol') def test_bash_completion(self): stdout, stderr = self.shell('bash-completion') # just check we have some output required = [ '.*--matching', '.*--wrap', '.*help', '.*secgroup-delete-rule', '.*--priority'] for r in required: self.assertThat((stdout + stderr), testtools.matchers.MatchesRegex( r, re.DOTALL | re.MULTILINE)) @testtools.skip('lol') def test_authenticate_user(self): obj = shell.BlazarShell() obj.initialize_app('list-leases') obj.options.os_token = 'aaaa-bbbb-cccc' obj.options.os_cacert = 'cert' obj.authenticate_user() ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1736516523.7245696 python-blazarclient-4.2.0/blazarclient/tests/v1/0000775000175000017500000000000000000000000021673 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/blazarclient/tests/v1/__init__.py0000664000175000017500000000000000000000000023772 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1736516523.7245696 python-blazarclient-4.2.0/blazarclient/tests/v1/shell_commands/0000775000175000017500000000000000000000000024663 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/blazarclient/tests/v1/shell_commands/__init__.py0000664000175000017500000000000000000000000026762 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/blazarclient/tests/v1/shell_commands/test_floatingips.py0000664000175000017500000001147000000000000030616 0ustar00zuulzuul00000000000000# Copyright (c) 2019 StackHPC Ltd. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import argparse from unittest import mock from blazarclient import shell from blazarclient import tests from blazarclient.v1.shell_commands import floatingips class CreateFloatingIPTest(tests.TestCase): def setUp(self): super(CreateFloatingIPTest, self).setUp() self.create_floatingip = floatingips.CreateFloatingIP( shell.BlazarShell(), mock.Mock()) def test_args2body(self): args = argparse.Namespace( network_id='1e17587e-a7ed-4b82-a17b-4beb32523e28', floating_ip_address='172.24.4.101', ) expected = { 'network_id': '1e17587e-a7ed-4b82-a17b-4beb32523e28', 'floating_ip_address': '172.24.4.101', } ret = self.create_floatingip.args2body(args) self.assertDictEqual(ret, expected) class ListFloatingIPsTest(tests.TestCase): def create_list_command(self, list_value): mock_floatingip_manager = mock.Mock() mock_floatingip_manager.list.return_value = list_value mock_client = mock.Mock() mock_client.floatingip = mock_floatingip_manager blazar_shell = shell.BlazarShell() blazar_shell.client = mock_client return (floatingips.ListFloatingIPs(blazar_shell, mock.Mock()), mock_floatingip_manager) def test_list_floatingips(self): list_value = [ {'id': '84c4d37e-1f8b-45ce-897b-16ad7f49b0e9'}, {'id': 'f180cf4c-f886-4dd1-8c36-854d17fbefb5'}, ] list_floatingips, floatingip_manager = self.create_list_command( list_value) args = argparse.Namespace(sort_by='id', columns=['id']) expected = [['id'], [('84c4d37e-1f8b-45ce-897b-16ad7f49b0e9',), ('f180cf4c-f886-4dd1-8c36-854d17fbefb5',)]] ret = list_floatingips.get_data(args) self.assertEqual(expected[0], ret[0]) self.assertEqual(expected[1], [x for x in ret[1]]) floatingip_manager.list.assert_called_once_with(sort_by='id') class ShowFloatingIPTest(tests.TestCase): def create_show_command(self, list_value, get_value): mock_floatingip_manager = mock.Mock() mock_floatingip_manager.list.return_value = list_value mock_floatingip_manager.get.return_value = get_value mock_client = mock.Mock() mock_client.floatingip = mock_floatingip_manager blazar_shell = shell.BlazarShell() blazar_shell.client = mock_client return (floatingips.ShowFloatingIP(blazar_shell, mock.Mock()), mock_floatingip_manager) def test_show_floatingip(self): list_value = [ {'id': '84c4d37e-1f8b-45ce-897b-16ad7f49b0e9'}, {'id': 'f180cf4c-f886-4dd1-8c36-854d17fbefb5'}, ] get_value = { 'id': '84c4d37e-1f8b-45ce-897b-16ad7f49b0e9'} show_floatingip, floatingip_manager = self.create_show_command( list_value, get_value) args = argparse.Namespace(id='84c4d37e-1f8b-45ce-897b-16ad7f49b0e9') expected = [('id',), ('84c4d37e-1f8b-45ce-897b-16ad7f49b0e9',)] ret = show_floatingip.get_data(args) self.assertEqual(ret, expected) floatingip_manager.get.assert_called_once_with( '84c4d37e-1f8b-45ce-897b-16ad7f49b0e9') class DeleteFloatingIPTest(tests.TestCase): def create_delete_command(self, list_value): mock_floatingip_manager = mock.Mock() mock_floatingip_manager.list.return_value = list_value mock_client = mock.Mock() mock_client.floatingip = mock_floatingip_manager blazar_shell = shell.BlazarShell() blazar_shell.client = mock_client return (floatingips.DeleteFloatingIP(blazar_shell, mock.Mock()), mock_floatingip_manager) def test_delete_floatingip(self): list_value = [ {'id': '84c4d37e-1f8b-45ce-897b-16ad7f49b0e9'}, {'id': 'f180cf4c-f886-4dd1-8c36-854d17fbefb5'}, ] delete_floatingip, floatingip_manager = self.create_delete_command( list_value) args = argparse.Namespace(id='84c4d37e-1f8b-45ce-897b-16ad7f49b0e9') delete_floatingip.run(args) floatingip_manager.delete.assert_called_once_with( '84c4d37e-1f8b-45ce-897b-16ad7f49b0e9') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/blazarclient/tests/v1/shell_commands/test_hosts.py0000664000175000017500000001753200000000000027444 0ustar00zuulzuul00000000000000# Copyright (c) 2018 NTT # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import argparse from unittest import mock from blazarclient import shell from blazarclient import tests from blazarclient.v1.shell_commands import hosts class CreateHostTest(tests.TestCase): def setUp(self): super(CreateHostTest, self).setUp() self.create_host = hosts.CreateHost(shell.BlazarShell(), mock.Mock()) def test_args2body(self): args = argparse.Namespace( name='test-host', extra_capabilities=[ 'extra_key1=extra_value1', 'extra_key2=extra_value2', ] ) expected = { 'name': 'test-host', 'extra_key1': 'extra_value1', 'extra_key2': 'extra_value2', } ret = self.create_host.args2body(args) self.assertDictEqual(ret, expected) class UpdateHostTest(tests.TestCase): def create_update_command(self, list_value): mock_host_manager = mock.Mock() mock_host_manager.list.return_value = list_value mock_client = mock.Mock() mock_client.host = mock_host_manager blazar_shell = shell.BlazarShell() blazar_shell.client = mock_client return hosts.UpdateHost(blazar_shell, mock.Mock()), mock_host_manager def test_update_host(self): list_value = [ {'id': '101', 'hypervisor_hostname': 'host-1'}, {'id': '201', 'hypervisor_hostname': 'host-2'}, ] update_host, host_manager = self.create_update_command(list_value) args = argparse.Namespace( id='101', extra_capabilities=[ 'key1=value1', 'key2=value2' ]) expected = { 'values': { 'key1': 'value1', 'key2': 'value2' } } update_host.run(args) host_manager.update.assert_called_once_with('101', **expected) def test_update_host_with_name(self): list_value = [ {'id': '101', 'hypervisor_hostname': 'host-1'}, {'id': '201', 'hypervisor_hostname': 'host-2'}, ] update_host, host_manager = self.create_update_command(list_value) args = argparse.Namespace( id='host-1', extra_capabilities=[ 'key1=value1', 'key2=value2' ]) expected = { 'values': { 'key1': 'value1', 'key2': 'value2' } } update_host.run(args) host_manager.update.assert_called_once_with('101', **expected) def test_update_host_with_name_startwith_number(self): list_value = [ {'id': '101', 'hypervisor_hostname': '1-host'}, {'id': '201', 'hypervisor_hostname': '2-host'}, ] update_host, host_manager = self.create_update_command(list_value) args = argparse.Namespace( id='1-host', extra_capabilities=[ 'key1=value1', 'key2=value2' ]) expected = { 'values': { 'key1': 'value1', 'key2': 'value2' } } update_host.run(args) host_manager.update.assert_called_once_with('101', **expected) class ShowHostTest(tests.TestCase): def create_show_command(self, list_value, get_value): mock_host_manager = mock.Mock() mock_host_manager.list.return_value = list_value mock_host_manager.get.return_value = get_value mock_client = mock.Mock() mock_client.host = mock_host_manager blazar_shell = shell.BlazarShell() blazar_shell.client = mock_client return hosts.ShowHost(blazar_shell, mock.Mock()), mock_host_manager def test_show_host(self): list_value = [ {'id': '101', 'hypervisor_hostname': 'host-1'}, {'id': '201', 'hypervisor_hostname': 'host-2'}, ] get_value = { 'id': '101', 'hypervisor_hostname': 'host-1'} show_host, host_manager = self.create_show_command(list_value, get_value) args = argparse.Namespace(id='101') expected = [('hypervisor_hostname', 'id'), ('host-1', '101')] ret = show_host.get_data(args) self.assertEqual(ret, expected) host_manager.get.assert_called_once_with('101') def test_show_host_with_name(self): list_value = [ {'id': '101', 'hypervisor_hostname': 'host-1'}, {'id': '201', 'hypervisor_hostname': 'host-2'}, ] get_value = { 'id': '101', 'hypervisor_hostname': 'host-1'} show_host, host_manager = self.create_show_command(list_value, get_value) args = argparse.Namespace(id='host-1') expected = [('hypervisor_hostname', 'id'), ('host-1', '101')] ret = show_host.get_data(args) self.assertEqual(ret, expected) host_manager.get.assert_called_once_with('101') def test_show_host_with_name_startwith_number(self): list_value = [ {'id': '101', 'hypervisor_hostname': '1-host'}, {'id': '201', 'hypervisor_hostname': '2-host'}, ] get_value = { 'id': '101', 'hypervisor_hostname': '1-host'} show_host, host_manager = self.create_show_command(list_value, get_value) args = argparse.Namespace(id='1-host') expected = [('hypervisor_hostname', 'id'), ('1-host', '101')] ret = show_host.get_data(args) self.assertEqual(ret, expected) host_manager.get.assert_called_once_with('101') class DeleteHostTest(tests.TestCase): def create_delete_command(self, list_value): mock_host_manager = mock.Mock() mock_host_manager.list.return_value = list_value mock_client = mock.Mock() mock_client.host = mock_host_manager blazar_shell = shell.BlazarShell() blazar_shell.client = mock_client return hosts.DeleteHost(blazar_shell, mock.Mock()), mock_host_manager def test_delete_host(self): list_value = [ {'id': '101', 'hypervisor_hostname': 'host-1'}, {'id': '201', 'hypervisor_hostname': 'host-2'}, ] delete_host, host_manager = self.create_delete_command(list_value) args = argparse.Namespace(id='101') delete_host.run(args) host_manager.delete.assert_called_once_with('101') def test_delete_host_with_name(self): list_value = [ {'id': '101', 'hypervisor_hostname': 'host-1'}, {'id': '201', 'hypervisor_hostname': 'host-2'}, ] delete_host, host_manager = self.create_delete_command(list_value) args = argparse.Namespace(id='host-1') delete_host.run(args) host_manager.delete.assert_called_once_with('101') def test_delete_host_with_name_startwith_number(self): list_value = [ {'id': '101', 'hypervisor_hostname': '1-host'}, {'id': '201', 'hypervisor_hostname': '2-host'}, ] delete_host, host_manager = self.create_delete_command(list_value) args = argparse.Namespace(id='1-host') delete_host.run(args) host_manager.delete.assert_called_once_with('101') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/blazarclient/tests/v1/shell_commands/test_leases.py0000664000175000017500000003153300000000000027555 0ustar00zuulzuul00000000000000# Copyright (c) 2017 NTT Corp. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import argparse from datetime import datetime from unittest import mock from blazarclient import exception from blazarclient import shell from blazarclient import tests from blazarclient.v1.shell_commands import leases mock_time = mock.Mock(return_value=datetime(2020, 6, 8)) FIRST_LEASE = 'd1e43d6d-8f6f-4c2e-b0a9-2982b39dc698' SECOND_LEASE = '424d21c3-45a2-448a-81ad-32eddc888375' @mock.patch('oslo_utils.timeutils.utcnow', mock_time) class CreateLeaseTestCase(tests.TestCase): def setUp(self): super(CreateLeaseTestCase, self).setUp() self.cl = leases.CreateLease(shell.BlazarShell(), mock.Mock()) def test_args2body_correct_phys_res_params(self): args = argparse.Namespace( start='2020-07-24 20:00', end='2020-08-09 22:30', before_end='2020-08-09 21:30', events=[], name='lease-test', reservations=[], physical_reservations=[ 'min=1,' 'max=2,' 'hypervisor_properties=' '["and", [">=", "$vcpus", "2"], ' '[">=", "$memory_mb", "2048"]],' 'resource_properties=' '["==", "$extra_key", "extra_value"],' 'before_end=default' ] ) expected = { 'start': '2020-07-24 20:00', 'end': '2020-08-09 22:30', 'before_end': '2020-08-09 21:30', 'events': [], 'name': 'lease-test', 'reservations': [ { 'min': 1, 'max': 2, 'hypervisor_properties': '["and", [">=", "$vcpus", "2"], ' '[">=", "$memory_mb", "2048"]]', 'resource_properties': '["==", "$extra_key", "extra_value"]', 'resource_type': 'physical:host', 'before_end': 'default' } ] } self.assertDictEqual(self.cl.args2body(args), expected) def test_args2body_incorrect_phys_res_params(self): args = argparse.Namespace( start='2020-07-24 20:00', end='2020-08-09 22:30', before_end='2020-08-09 21:30', events=[], name='lease-test', reservations=[], physical_reservations=[ 'incorrect_param=1,' 'min=1,' 'max=2,' 'hypervisor_properties=' '["and", [">=", "$vcpus", "2"], ' '[">=", "$memory_mb", "2048"]],' 'resource_properties=' '["==", "$extra_key", "extra_value"]' ] ) self.assertRaises(exception.IncorrectLease, self.cl.args2body, args) def test_args2body_duplicated_phys_res_params(self): args = argparse.Namespace( start='2020-07-24 20:00', end='2020-08-09 22:30', before_end='2020-08-09 21:30', events=[], name='lease-test', reservations=[], physical_reservations=[ 'min=1,' 'min=1,' 'max=2,' 'hypervisor_properties=' '["and", [">=", "$vcpus", "2"], ' '[">=", "$memory_mb", "2048"]],' 'resource_properties=' '["==", "$extra_key", "extra_value"]' ] ) self.assertRaises(exception.DuplicatedLeaseParameters, self.cl.args2body, args) def test_args2body_correct_instance_res_params(self): args = argparse.Namespace( start='2020-07-24 20:00', end='2020-08-09 22:30', before_end='2020-08-09 21:30', events=[], name='lease-test', reservations=[ 'vcpus=4,' 'memory_mb=1024,' 'disk_gb=10,' 'amount=2,' 'affinity=True,' 'resource_properties=' '["==", "$extra_key", "extra_value"],' 'resource_type=virtual:instance' ], physical_reservations=[ 'min=1,' 'max=2,' 'hypervisor_properties=' '["and", [">=", "$vcpus", "2"], ' '[">=", "$memory_mb", "2048"]],' 'resource_properties=' '["==", "$extra_key", "extra_value"],' 'before_end=default' ] ) expected = { 'start': '2020-07-24 20:00', 'end': '2020-08-09 22:30', 'before_end': '2020-08-09 21:30', 'events': [], 'name': 'lease-test', 'reservations': [ { 'min': 1, 'max': 2, 'hypervisor_properties': '["and", [">=", "$vcpus", "2"], ' '[">=", "$memory_mb", "2048"]]', 'resource_properties': '["==", "$extra_key", "extra_value"]', 'resource_type': 'physical:host', 'before_end': 'default' }, { 'vcpus': 4, 'memory_mb': 1024, 'disk_gb': 10, 'amount': 2, 'affinity': 'True', 'resource_properties': '["==", "$extra_key", "extra_value"]', 'resource_type': 'virtual:instance' } ] } self.assertDictEqual(self.cl.args2body(args), expected) def test_args2body_start_now(self): args = argparse.Namespace( start='now', end='2030-08-09 22:30', before_end='2030-08-09 21:30', events=[], name='lease-test', reservations=[], physical_reservations=[ 'min=1,' 'max=2,' 'hypervisor_properties=' '["and", [">=", "$vcpus", "2"], ' '[">=", "$memory_mb", "2048"]],' 'resource_properties=' '["==", "$extra_key", "extra_value"],' 'before_end=default' ] ) expected = { 'start': 'now', 'end': '2030-08-09 22:30', 'before_end': '2030-08-09 21:30', 'events': [], 'name': 'lease-test', 'reservations': [ { 'min': 1, 'max': 2, 'hypervisor_properties': '["and", [">=", "$vcpus", "2"], ' '[">=", "$memory_mb", "2048"]]', 'resource_properties': '["==", "$extra_key", "extra_value"]', 'resource_type': 'physical:host', 'before_end': 'default' } ] } self.assertDictEqual(self.cl.args2body(args), expected) class UpdateLeaseTestCase(tests.TestCase): def setUp(self): super(UpdateLeaseTestCase, self).setUp() self.cl = leases.UpdateLease(shell.BlazarShell(), mock.Mock()) def test_args2body_time_params(self): args = argparse.Namespace( name=None, prolong_for='1h', reduce_by=None, end_date=None, defer_by=None, advance_by=None, start_date=None, reservation=None ) expected = { 'prolong_for': '1h', } self.assertDictEqual(self.cl.args2body(args), expected) def test_args2body_host_reservation_params(self): args = argparse.Namespace( name=None, prolong_for=None, reduce_by=None, end_date=None, defer_by=None, advance_by=None, start_date=None, reservation=[ 'id=798379a6-194c-45dc-ba34-1b5171d5552f,' 'max=3,' 'hypervisor_properties=' '["and", [">=", "$vcpus", "4"], ' '[">=", "$memory_mb", "8192"]],' 'resource_properties=' '["==", "$extra_key", "extra_value"]' ] ) expected = { 'reservations': [ { 'id': '798379a6-194c-45dc-ba34-1b5171d5552f', 'max': 3, 'hypervisor_properties': '["and", [">=", "$vcpus", "4"], ' '[">=", "$memory_mb", "8192"]]', 'resource_properties': '["==", "$extra_key", "extra_value"]' } ] } self.assertDictEqual(self.cl.args2body(args), expected) def test_args2body_instance_reservation_params(self): args = argparse.Namespace( name=None, prolong_for=None, reduce_by=None, end_date=None, defer_by=None, advance_by=None, start_date=None, reservation=[ 'id=798379a6-194c-45dc-ba34-1b5171d5552f,' 'vcpus=3,memory_mb=1024,disk_gb=20,' 'amount=4,affinity=False' ] ) expected = { 'reservations': [ { 'id': '798379a6-194c-45dc-ba34-1b5171d5552f', 'vcpus': 3, 'memory_mb': 1024, 'disk_gb': 20, 'amount': 4, 'affinity': 'False' } ] } self.assertDictEqual(self.cl.args2body(args), expected) class ShowLeaseTestCase(tests.TestCase): def create_show_command(self): mock_lease_manager = mock.Mock() mock_client = mock.Mock() mock_client.lease = mock_lease_manager blazar_shell = shell.BlazarShell() blazar_shell.client = mock_client return (leases.ShowLease(blazar_shell, mock.Mock()), mock_lease_manager) def test_show_lease(self): show_lease, lease_manager = self.create_show_command() lease_manager.get.return_value = {'id': FIRST_LEASE} args = argparse.Namespace(id=FIRST_LEASE) expected = [('id',), (FIRST_LEASE,)] self.assertEqual(show_lease.get_data(args), expected) lease_manager.get.assert_called_once_with(FIRST_LEASE) def test_show_lease_by_name(self): show_lease, lease_manager = self.create_show_command() lease_manager.list.return_value = [ {'id': FIRST_LEASE, 'name': 'first-lease'}, {'id': SECOND_LEASE, 'name': 'second-lease'}, ] lease_manager.get.return_value = {'id': SECOND_LEASE} args = argparse.Namespace(id='second-lease') expected = [('id',), (SECOND_LEASE,)] self.assertEqual(show_lease.get_data(args), expected) lease_manager.list.assert_called_once_with() lease_manager.get.assert_called_once_with(SECOND_LEASE) class DeleteLeaseTestCase(tests.TestCase): def create_delete_command(self): mock_lease_manager = mock.Mock() mock_client = mock.Mock() mock_client.lease = mock_lease_manager blazar_shell = shell.BlazarShell() blazar_shell.client = mock_client return (leases.DeleteLease(blazar_shell, mock.Mock()), mock_lease_manager) def test_delete_lease(self): delete_lease, lease_manager = self.create_delete_command() lease_manager.delete.return_value = None args = argparse.Namespace(id=FIRST_LEASE) delete_lease.run(args) lease_manager.delete.assert_called_once_with(FIRST_LEASE) def test_delete_lease_by_name(self): delete_lease, lease_manager = self.create_delete_command() lease_manager.list.return_value = [ {'id': FIRST_LEASE, 'name': 'first-lease'}, {'id': SECOND_LEASE, 'name': 'second-lease'}, ] lease_manager.delete.return_value = None args = argparse.Namespace(id='second-lease') delete_lease.run(args) lease_manager.list.assert_called_once_with() lease_manager.delete.assert_called_once_with(SECOND_LEASE) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/blazarclient/utils.py0000664000175000017500000001354400000000000021724 0ustar00zuulzuul00000000000000# Copyright (c) 2013 Mirantis Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import datetime import os import re from oslo_serialization import jsonutils as json from blazarclient import exception from blazarclient.i18n import _ ELAPSED_TIME_REGEX = r'^(\d+)([s|m|h|d])$' LEASE_DATE_FORMAT = '%Y-%m-%dT%H:%M:%S.%f' API_DATE_FORMAT = '%Y-%m-%d %H:%M' def env(*args, **kwargs): """Returns the first environment variable set. if none are non-empty, defaults to '' or keyword arg default. """ for v in args: value = os.environ.get(v) if value: return value return kwargs.get('default', '') def to_primitive(value): if isinstance(value, list) or isinstance(value, tuple): o = [] for v in value: o.append(to_primitive(v)) return o elif isinstance(value, dict): o = {} for k, v in value.items(): o[k] = to_primitive(v) return o elif isinstance(value, datetime.datetime): return str(value) elif hasattr(value, 'iteritems'): return to_primitive(dict(value.items())) elif hasattr(value, '__iter__'): return to_primitive(list(value)) else: return value def dumps(value, indent=None): try: return json.dumps(value, indent=indent) except TypeError: pass return json.dumps(to_primitive(value)) def get_item_properties(item, fields, mixed_case_fields=None, formatters=None): """Return a tuple containing the item properties. :param item: a single item resource (e.g. Server, Tenant, etc) :param fields: tuple of strings with the desired field names :param mixed_case_fields: tuple of field names to preserve case :param formatters: dictionary mapping field names to callables to format the values """ row = [] if mixed_case_fields is None: mixed_case_fields = [] if formatters is None: formatters = {} for field in fields: if field in formatters: row.append(formatters[field](item)) else: if field in mixed_case_fields: field_name = field.replace(' ', '_') else: field_name = field.lower().replace(' ', '_') if not hasattr(item, field_name) and isinstance(item, dict): data = item[field_name] else: data = getattr(item, field_name, '') if data is None: data = '' row.append(data) return tuple(row) def find_resource_id_by_name_or_id(client, resource_type, name_or_id, name_key, id_pattern): if re.match(id_pattern, name_or_id): return name_or_id return _find_resource_id_by_name(client, resource_type, name_or_id, name_key) def _find_resource_id_by_name(client, resource_type, name, name_key): resource_manager = getattr(client, resource_type) resources = resource_manager.list() named_resources = [] key = name_key if name_key else 'name' for resource in resources: if resource[key] == name: named_resources.append(resource['id']) if len(named_resources) > 1: raise exception.NoUniqueMatch(message="There are more than one " "appropriate resources for the " "name '%s' and type '%s'" % (name, resource_type)) elif named_resources: return named_resources[0] else: message = "Unable to find resource with name '%s'" % name raise exception.BlazarClientException(message=message, status_code=404) def from_elapsed_time_to_seconds(elapsed_time, pos_sign=True): """Return the positive or negative amount of seconds based on the elapsed_time parameter with a sign depending on the sign parameter. :param: elapsed_time: a string that matches ELAPSED_TIME_REGEX :param: sign: if pos_sign is True, the returned value will be positive. Otherwise it will be positive. """ is_elapsed_time = re.match(ELAPSED_TIME_REGEX, elapsed_time) if is_elapsed_time is None: raise exception.BlazarClientException(_("Invalid time " "format for option.")) elapsed_time_value = int(is_elapsed_time.group(1)) elapsed_time_option = is_elapsed_time.group(2) seconds = { 's': lambda x: datetime.timedelta(seconds=x).total_seconds(), 'm': lambda x: datetime.timedelta(minutes=x).total_seconds(), 'h': lambda x: datetime.timedelta(hours=x).total_seconds(), 'd': lambda x: datetime.timedelta(days=x).total_seconds(), }[elapsed_time_option](elapsed_time_value) # the above code returns a "float" if pos_sign: return int(seconds) return int(seconds) * -1 def from_elapsed_time_to_delta(elapsed_time, pos_sign=True): """Return the positive or negative delta time based on the elapsed_time parameter. :param: elapsed_time: a string that matches ELAPSED_TIME_REGEX :param: sign: if sign is True, the returned value will be negative. Otherwise it will be positive. """ seconds = from_elapsed_time_to_seconds(elapsed_time, pos_sign=pos_sign) return datetime.timedelta(seconds=seconds) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1736516523.7245696 python-blazarclient-4.2.0/blazarclient/v1/0000775000175000017500000000000000000000000020531 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/blazarclient/v1/__init__.py0000664000175000017500000000000000000000000022630 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/blazarclient/v1/allocations.py0000664000175000017500000000247300000000000023421 0ustar00zuulzuul00000000000000# Copyright (c) 2019 University of Chicago. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from blazarclient import base class AllocationClientManager(base.BaseClientManager): """Manager for the ComputeHost connected requests.""" def get(self, resource, resource_id): """Get allocation for resource identified by type and ID.""" resp, body = self.request_manager.get( '/%s/%s/allocation' % (resource, resource_id)) return body['allocation'] def list(self, resource, sort_by=None): """List allocations for all resources of a type.""" resp, body = self.request_manager.get('/%s/allocations' % resource) allocations = body['allocations'] if sort_by: allocations = sorted(allocations, key=lambda alloc: alloc[sort_by]) return allocations ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/blazarclient/v1/client.py0000664000175000017500000000523700000000000022370 0ustar00zuulzuul00000000000000# Copyright (c) 2013 Mirantis Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import logging from blazarclient.v1 import allocations from blazarclient.v1 import floatingips from blazarclient.v1 import hosts from blazarclient.v1 import leases class Client(object): """Top level object to communicate with Blazar. Contains managers to control requests that should be passed to each type of resources - leases, events, etc. **Examples** client = Client() client.lease.list() client.event.list() ... """ version = '1' def __init__(self, blazar_url=None, auth_token=None, session=None, **kwargs): self.blazar_url = blazar_url self.auth_token = auth_token self.session = session if not self.session: logging.warning('Use a keystoneauth session object for the ' 'authentication. The authentication with ' 'blazar_url and auth_token is deprecated.') self.lease = leases.LeaseClientManager(blazar_url=self.blazar_url, auth_token=self.auth_token, session=self.session, version=self.version, **kwargs) self.host = hosts.ComputeHostClientManager(blazar_url=self.blazar_url, auth_token=self.auth_token, session=self.session, version=self.version, **kwargs) self.floatingip = floatingips.FloatingIPClientManager( blazar_url=self.blazar_url, auth_token=self.auth_token, session=self.session, version=self.version, **kwargs) self.allocation = allocations.AllocationClientManager( blazar_url=self.blazar_url, auth_token=self.auth_token, session=self.session, version=self.version, **kwargs) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/blazarclient/v1/floatingips.py0000664000175000017500000000336700000000000023433 0ustar00zuulzuul00000000000000# Copyright (c) 2019 StackHPC Ltd. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from blazarclient import base class FloatingIPClientManager(base.BaseClientManager): """Manager for floating IP requests.""" def create(self, network_id, floating_ip_address, **kwargs): """Creates a floating IP from values passed.""" values = {'floating_network_id': network_id, 'floating_ip_address': floating_ip_address} values.update(**kwargs) resp, body = self.request_manager.post('/floatingips', body=values) return body['floatingip'] def get(self, floatingip_id): """Show floating IP details.""" resp, body = self.request_manager.get( '/floatingips/%s' % floatingip_id) return body['floatingip'] def delete(self, floatingip_id): """Deletes floating IP with specified ID.""" resp, body = self.request_manager.delete( '/floatingips/%s' % floatingip_id) def list(self, sort_by=None): """List all floating IPs.""" resp, body = self.request_manager.get('/floatingips') floatingips = body['floatingips'] if sort_by: floatingips = sorted(floatingips, key=lambda fip: fip[sort_by]) return floatingips ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/blazarclient/v1/hosts.py0000664000175000017500000000641700000000000022253 0ustar00zuulzuul00000000000000# Copyright (c) 2014 Bull. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from blazarclient import base from blazarclient import exception from blazarclient.i18n import _ class ComputeHostClientManager(base.BaseClientManager): """Manager for the ComputeHost connected requests.""" def create(self, name, **kwargs): """Creates host from values passed.""" values = {'name': name} values.update(**kwargs) resp, body = self.request_manager.post('/os-hosts', body=values) return body['host'] def get(self, host_id): """Describe host specifications such as name and details.""" resp, body = self.request_manager.get('/os-hosts/%s' % host_id) return body['host'] def update(self, host_id, values): """Update attributes of the host.""" if not values: return _('No values to update passed.') resp, body = self.request_manager.put( '/os-hosts/%s' % host_id, body=values ) return body['host'] def delete(self, host_id): """Delete host with specified ID.""" resp, body = self.request_manager.delete('/os-hosts/%s' % host_id) def list(self, sort_by=None): """List all hosts.""" resp, body = self.request_manager.get('/os-hosts') hosts = body['hosts'] if sort_by: hosts = sorted(hosts, key=lambda host: host[sort_by]) return hosts def list_properties(self, detail=False, all=False, sort_by=None): url = '/os-hosts/properties' query_parts = [] if detail: query_parts.append("detail=True") if all: query_parts.append("all=True") if query_parts: url += "?" + "&".join(query_parts) resp, body = self.request_manager.get(url) resource_properties = body['resource_properties'] # Values is a reserved word in cliff so need to rename values column. if detail: for p in resource_properties: p['property_values'] = p['values'] del p['values'] if sort_by: resource_properties = sorted(resource_properties, key=lambda rp: rp[sort_by]) return resource_properties def get_property(self, property_name): resource_property = [ x for x in self.list_properties(detail=True) if x['property'] == property_name] if not resource_property: raise exception.ResourcePropertyNotFound() return resource_property[0] def set_property(self, property_name, private): data = {'private': private} resp, body = self.request_manager.patch( '/os-hosts/properties/%s' % property_name, body=data) return body['resource_property'] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/blazarclient/v1/leases.py0000664000175000017500000000772400000000000022371 0ustar00zuulzuul00000000000000# Copyright (c) 2013 Mirantis Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from oslo_utils import timeutils from blazarclient import base from blazarclient.i18n import _ from blazarclient import utils class LeaseClientManager(base.BaseClientManager): """Manager for the lease connected requests.""" def create(self, name, start, end, reservations, events, before_end=None): """Creates lease from values passed.""" values = {'name': name, 'start_date': start, 'end_date': end, 'reservations': reservations, 'events': events, 'before_end_date': before_end} resp, body = self.request_manager.post('/leases', body=values) return body['lease'] def get(self, lease_id): """Describes lease specifications such as name, status and locked condition. """ resp, body = self.request_manager.get('/leases/%s' % lease_id) return body['lease'] def update(self, lease_id, name=None, prolong_for=None, reduce_by=None, end_date=None, advance_by=None, defer_by=None, start_date=None, reservations=None): """Update attributes of the lease.""" values = {} if name: values['name'] = name lease_end_date_change = prolong_for or reduce_by or end_date lease_start_date_change = defer_by or advance_by or start_date lease = None if lease_end_date_change: lease = self.get(lease_id) if end_date: date = timeutils.parse_strtime(end_date, utils.API_DATE_FORMAT) values['end_date'] = date.strftime(utils.API_DATE_FORMAT) else: self._add_lease_date(values, lease, 'end_date', lease_end_date_change, prolong_for is not None) if lease_start_date_change: if lease is None: lease = self.get(lease_id) if start_date: date = timeutils.parse_strtime(start_date, utils.API_DATE_FORMAT) values['start_date'] = date.strftime(utils.API_DATE_FORMAT) else: self._add_lease_date(values, lease, 'start_date', lease_start_date_change, defer_by is not None) if reservations: values['reservations'] = reservations if not values: return _('No values to update passed.') resp, body = self.request_manager.put('/leases/%s' % lease_id, body=values) return body['lease'] def delete(self, lease_id): """Deletes lease with specified ID.""" resp, body = self.request_manager.delete('/leases/%s' % lease_id) def list(self, sort_by=None): """List all leases.""" resp, body = self.request_manager.get('/leases') leases = body['leases'] if sort_by: leases = sorted(leases, key=lambda lease: lease[sort_by]) return leases def _add_lease_date(self, values, lease, key, delta_date, positive_delta): delta_sec = utils.from_elapsed_time_to_delta( delta_date, pos_sign=positive_delta) date = timeutils.parse_strtime(lease[key], utils.LEASE_DATE_FORMAT) values[key] = (date + delta_sec).strftime(utils.API_DATE_FORMAT) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1736516523.7285695 python-blazarclient-4.2.0/blazarclient/v1/shell_commands/0000775000175000017500000000000000000000000023521 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/blazarclient/v1/shell_commands/__init__.py0000664000175000017500000000000000000000000025620 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/blazarclient/v1/shell_commands/allocations.py0000664000175000017500000001214600000000000026407 0ustar00zuulzuul00000000000000# Copyright (c) 2019 University of Chicago. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import logging from blazarclient import command from blazarclient import utils RESOURCE_ID_PATTERN = '^[0-9]+$' class ShowAllocations(command.ShowCommand): """Show allocations for resource identified by type and ID.""" resource = 'allocation' json_indent = 4 id_pattern = RESOURCE_ID_PATTERN name_key = 'hypervisor_hostname' log = logging.getLogger(__name__ + '.ShowHostAllocation') def get_parser(self, prog_name): parser = super(ShowAllocations, self).get_parser(prog_name) parser.add_argument( 'resource_type', choices=['host'], help='Show allocations for a resource type' ) if self.allow_names: help_str = 'ID or name of %s to look up' else: help_str = 'ID of %s to look up' parser.add_argument('id', metavar="RESOURCE", help=help_str % "resource") parser.add_argument( '--reservation-id', dest='reservation_id', default=None, help='Show only allocations with specific reservation_id' ) parser.add_argument( '--lease-id', dest='lease_id', default=None, help='Show only allocations with specific lease_id' ) return parser def get_data(self, parsed_args): self.log.debug('get_data(%s)' % parsed_args) blazar_client = self.get_client() resource_manager = getattr(blazar_client, self.resource) if self.allow_names: res_id = utils.find_resource_id_by_name_or_id( blazar_client, parsed_args.resource_type, parsed_args.id, self.name_key, self.id_pattern) else: res_id = parsed_args.id data = resource_manager.get( self.args2body(parsed_args)['resource'], res_id) if parsed_args.lease_id is not None: data['reservations'] = list( filter(lambda d: d['lease_id'] == parsed_args.lease_id, data['reservations'])) if parsed_args.reservation_id is not None: data['reservations'] = list( filter(lambda d: d['id'] == parsed_args.reservation_id, data['reservations'])) self.format_output_data(data) return list(zip(*sorted(data.items()))) def args2body(self, parsed_args): params = {} if parsed_args.resource_type == 'host': params.update(dict(resource='os-hosts')) return params class ListAllocations(command.ListCommand): """List allocations for all resources of a type.""" resource = 'allocation' log = logging.getLogger(__name__ + '.ListHostAllocations') list_columns = ['resource_id', 'reservations'] def get_parser(self, prog_name): parser = super(ListAllocations, self).get_parser(prog_name) parser.add_argument( 'resource_type', choices=['host'], help='Show allocations for a resource type' ) parser.add_argument( '--reservation-id', dest='reservation_id', default=None, help='Show only allocations with specific reservation_id' ) parser.add_argument( '--lease-id', dest='lease_id', default=None, help='Show only allocations with specific lease_id' ) parser.add_argument( '--sort-by', metavar="", help='column name used to sort result', default='resource_id' ) return parser def get_data(self, parsed_args): self.log.debug('get_data(%s)' % parsed_args) data = self.retrieve_list(parsed_args) for resource in data: if parsed_args.lease_id is not None: resource['reservations'] = list( filter(lambda d: d['lease_id'] == parsed_args.lease_id, resource['reservations'])) if parsed_args.reservation_id is not None: resource['reservations'] = list( filter(lambda d: d['id'] == parsed_args.reservation_id, resource['reservations'])) return self.setup_columns(data, parsed_args) def args2body(self, parsed_args): params = super(ListAllocations, self).args2body(parsed_args) if parsed_args.resource_type == 'host': params.update(dict(resource='os-hosts')) return params ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/blazarclient/v1/shell_commands/floatingips.py0000664000175000017500000000553100000000000026416 0ustar00zuulzuul00000000000000# Copyright (c) 2019 StackHPC Ltd. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import logging from blazarclient import command class ListFloatingIPs(command.ListCommand): """Print a list of floating IPs.""" resource = 'floatingip' log = logging.getLogger(__name__ + '.ListFloatingIPs') list_columns = ['id', 'floating_ip_address', 'floating_network_id'] def get_parser(self, prog_name): parser = super(ListFloatingIPs, self).get_parser(prog_name) parser.add_argument( '--sort-by', metavar="", help='column name used to sort result', default='id' ) return parser class ShowFloatingIP(command.ShowCommand): """Show floating IP details.""" resource = 'floatingip' allow_names = False json_indent = 4 log = logging.getLogger(__name__ + '.ShowFloatingIP') def get_parser(self, prog_name): parser = super(ShowFloatingIP, self).get_parser(prog_name) if self.allow_names: help_str = 'ID or name of %s to look up' else: help_str = 'ID of %s to look up' parser.add_argument('id', metavar=self.resource.upper(), help=help_str % self.resource) return parser class CreateFloatingIP(command.CreateCommand): """Create a floating IP.""" resource = 'floatingip' json_indent = 4 log = logging.getLogger(__name__ + '.CreateFloatingIP') def get_parser(self, prog_name): parser = super(CreateFloatingIP, self).get_parser(prog_name) parser.add_argument( 'network_id', metavar='NETWORK_ID', help='External network ID to which the floating IP belongs' ) parser.add_argument( 'floating_ip_address', metavar='FLOATING_IP_ADDRESS', help='Floating IP address to add to Blazar' ) return parser def args2body(self, parsed_args): params = {} if parsed_args.network_id: params['network_id'] = parsed_args.network_id if parsed_args.floating_ip_address: params['floating_ip_address'] = parsed_args.floating_ip_address return params class DeleteFloatingIP(command.DeleteCommand): """Delete a floating IP.""" resource = 'floatingip' allow_names = False log = logging.getLogger(__name__ + '.DeleteFloatingIP') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/blazarclient/v1/shell_commands/hosts.py0000664000175000017500000001460500000000000025241 0ustar00zuulzuul00000000000000# Copyright (c) 2014 Bull. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import logging from blazarclient import command from blazarclient import exception HOST_ID_PATTERN = '^[0-9]+$' class ListHosts(command.ListCommand): """Print a list of hosts.""" resource = 'host' log = logging.getLogger(__name__ + '.ListHosts') list_columns = ['id', 'hypervisor_hostname', 'vcpus', 'memory_mb', 'local_gb'] def get_parser(self, prog_name): parser = super(ListHosts, self).get_parser(prog_name) parser.add_argument( '--sort-by', metavar="", help='column name used to sort result', default='hypervisor_hostname' ) return parser class ShowHost(command.ShowCommand): """Show host details.""" resource = 'host' json_indent = 4 name_key = 'hypervisor_hostname' id_pattern = HOST_ID_PATTERN log = logging.getLogger(__name__ + '.ShowHost') def get_parser(self, prog_name): parser = super(ShowHost, self).get_parser(prog_name) if self.allow_names: help_str = 'ID or name of %s to look up' else: help_str = 'ID of %s to look up' parser.add_argument('id', metavar=self.resource.upper(), help=help_str % self.resource) return parser class CreateHost(command.CreateCommand): """Create a host.""" resource = 'host' json_indent = 4 log = logging.getLogger(__name__ + '.CreateHost') def get_parser(self, prog_name): parser = super(CreateHost, self).get_parser(prog_name) parser.add_argument( 'name', metavar=self.resource.upper(), help='Name of the host to add' ) parser.add_argument( '--extra', metavar='=', action='append', dest='extra_capabilities', default=[], help='Extra capabilities key/value pairs to add for the host' ) return parser def args2body(self, parsed_args): params = {} if parsed_args.name: params['name'] = parsed_args.name extras = {} if parsed_args.extra_capabilities: for capa in parsed_args.extra_capabilities: key, _sep, value = capa.partition('=') # NOTE(sbauza): multiple copies of the same capability will # result in only the last value to be stored extras[key] = value params.update(extras) return params class UpdateHost(command.UpdateCommand): """Update attributes of a host.""" resource = 'host' json_indent = 4 log = logging.getLogger(__name__ + '.UpdateHost') name_key = 'hypervisor_hostname' id_pattern = HOST_ID_PATTERN def get_parser(self, prog_name): parser = super(UpdateHost, self).get_parser(prog_name) parser.add_argument( '--extra', metavar='=', action='append', dest='extra_capabilities', default=[], help='Extra capabilities key/value pairs to update for the host' ) return parser def args2body(self, parsed_args): params = {} extras = {} if parsed_args.extra_capabilities: for capa in parsed_args.extra_capabilities: key, _sep, value = capa.partition('=') # NOTE(sbauza): multiple copies of the same capability will # result in only the last value to be stored extras[key] = value params['values'] = extras return params class DeleteHost(command.DeleteCommand): """Delete a host.""" resource = 'host' log = logging.getLogger(__name__ + '.DeleteHost') name_key = 'hypervisor_hostname' id_pattern = HOST_ID_PATTERN class ShowHostProperty(command.ShowPropertyCommand): """Show host property.""" resource = 'host' json_indent = 4 log = logging.getLogger(__name__ + '.ShowHostProperty') class ListHostProperties(command.ListCommand): """List host properties.""" resource = 'host' log = logging.getLogger(__name__ + '.ListHostProperties') list_columns = ['property', 'private', 'property_values'] def args2body(self, parsed_args): params = { 'detail': parsed_args.detail, 'all': parsed_args.all, } if parsed_args.sort_by: if parsed_args.sort_by in self.list_columns: params['sort_by'] = parsed_args.sort_by else: msg = 'Invalid sort option %s' % parsed_args.sort_by raise exception.BlazarClientException(msg) return params def retrieve_list(self, parsed_args): """Retrieve a list of resources from Blazar server.""" blazar_client = self.get_client() body = self.args2body(parsed_args) resource_manager = getattr(blazar_client, self.resource) data = resource_manager.list_properties(**body) return data def get_parser(self, prog_name): parser = super(ListHostProperties, self).get_parser(prog_name) parser.add_argument( '--detail', action='store_true', help='Return properties with values and attributes.', default=False ) parser.add_argument( '--sort-by', metavar="", help='column name used to sort result', default='property' ) parser.add_argument( '--all', action='store_true', help='Return all properties, public and private.', default=False ) return parser class UpdateHostProperty(command.UpdatePropertyCommand): """Update attributes of a host property.""" resource = 'host' json_indent = 4 log = logging.getLogger(__name__ + '.UpdateHostProperty') name_key = 'property_name' ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/blazarclient/v1/shell_commands/leases.py0000664000175000017500000004451200000000000025355 0ustar00zuulzuul00000000000000# Copyright (c) 2013 Mirantis Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import argparse import datetime import logging import re from oslo_serialization import jsonutils from oslo_utils import strutils from oslo_utils import timeutils from blazarclient import command from blazarclient import exception # All valid reservation parameters must be added to CREATE_RESERVATION_KEYS to # make them parsable. Note that setting the default value to None ensures that # the parameter is not included in the POST request if absent. CREATE_RESERVATION_KEYS = { "physical:host": { "min": "", "max": "", "hypervisor_properties": "", "resource_properties": "", "before_end": None, "resource_type": 'physical:host' }, "virtual:floatingip": { "amount": 1, "network_id": None, "required_floatingips": [], "resource_type": 'virtual:floatingip' }, "virtual:instance": { "vcpus": "", "memory_mb": "", "disk_gb": "", "amount": "", "affinity": "None", "resource_properties": "", "resource_type": 'virtual:instance' }, "flavor:instance": { "flavor_id": "", "amount": "", "affinity": "None", "resource_type": 'flavor:instance' }, "others": { ".*": None } } class ListLeases(command.ListCommand): """Print a list of leases.""" resource = 'lease' log = logging.getLogger(__name__ + '.ListLeases') list_columns = ['id', 'name', 'start_date', 'end_date'] def get_parser(self, prog_name): parser = super(ListLeases, self).get_parser(prog_name) parser.add_argument( '--sort-by', metavar="", help='column name used to sort result', default='name' ) return parser class ShowLease(command.ShowCommand): """Show details about the given lease.""" resource = 'lease' json_indent = 4 log = logging.getLogger(__name__ + '.ShowLease') def get_parser(self, prog_name): parser = super(ShowLease, self).get_parser(prog_name) if self.allow_names: help_str = 'ID or name of %s to look up' else: help_str = 'ID of %s to look up' parser.add_argument('id', metavar=self.resource.upper(), help=help_str % self.resource) return parser class CreateLeaseBase(command.CreateCommand): """Create a lease.""" resource = 'lease' json_indent = 4 log = logging.getLogger(__name__ + '.CreateLease') default_start = 'now' default_end = timeutils.utcnow() + datetime.timedelta(days=1) def get_parser(self, prog_name): parser = super(CreateLeaseBase, self).get_parser(prog_name) parser.add_argument( 'name', metavar=self.resource.upper(), help='Name for the %s' % self.resource ) parser.add_argument( '--start-date', dest='start', help='Time (YYYY-MM-DD HH:MM) UTC TZ for starting the lease ' '(default: current time on the server)', default=self.default_start ) parser.add_argument( '--end-date', dest='end', help='Time (YYYY-MM-DD HH:MM) UTC TZ for ending the lease ' '(default: 24h from now)', default=self.default_end ) parser.add_argument( '--before-end-date', dest='before_end', help='Time (YYYY-MM-DD HH:MM) UTC TZ for taking an action before ' 'the end of the lease (default: depends on system default)', default=None ) parser.add_argument( '--reservation', metavar="", action='append', dest='reservations', help='key/value pairs for creating a generic reservation. ' 'Specify option multiple times to create multiple ' 'reservations. ', default=[] ) parser.add_argument( '--event', metavar='', action='append', dest='events', help='Creates an event with key/value pairs for the lease. ' 'Specify option multiple times to create multiple events. ' 'event_type: type of event (e.g. notification). ' 'event_date: Time for event (YYYY-MM-DD HH:MM) UTC TZ. ', default=[] ) return parser def args2body(self, parsed_args): params = self._generate_params(parsed_args) if not params['reservations']: raise exception.IncorrectLease return params def _generate_params(self, parsed_args): params = {} if parsed_args.name: params['name'] = parsed_args.name if not isinstance(parsed_args.start, datetime.datetime): if parsed_args.start != 'now': try: parsed_args.start = datetime.datetime.strptime( parsed_args.start, '%Y-%m-%d %H:%M') except ValueError: raise exception.IncorrectLease if not isinstance(parsed_args.end, datetime.datetime): try: parsed_args.end = datetime.datetime.strptime( parsed_args.end, '%Y-%m-%d %H:%M') except ValueError: raise exception.IncorrectLease if parsed_args.start == 'now': start = timeutils.utcnow() else: start = parsed_args.start if start > parsed_args.end: raise exception.IncorrectLease if parsed_args.before_end: try: parsed_args.before_end = datetime.datetime.strptime( parsed_args.before_end, '%Y-%m-%d %H:%M') except ValueError: raise exception.IncorrectLease if (parsed_args.before_end < start or parsed_args.end < parsed_args.before_end): raise exception.IncorrectLease params['before_end'] = datetime.datetime.strftime( parsed_args.before_end, '%Y-%m-%d %H:%M') if parsed_args.start == 'now': params['start'] = parsed_args.start else: params['start'] = datetime.datetime.strftime(parsed_args.start, '%Y-%m-%d %H:%M') params['end'] = datetime.datetime.strftime(parsed_args.end, '%Y-%m-%d %H:%M') params['reservations'] = [] params['events'] = [] reservations = [] for res_str in parsed_args.reservations: err_msg = ("Invalid reservation argument '%s'. " "Reservation arguments must be of the " "form --reservation " % res_str) if "physical:host" in res_str: defaults = CREATE_RESERVATION_KEYS['physical:host'] elif "virtual:instance" in res_str: defaults = CREATE_RESERVATION_KEYS['virtual:instance'] elif "virtual:floatingip" in res_str: defaults = CREATE_RESERVATION_KEYS['virtual:floatingip'] elif "flavor:instance" in res_str: defaults = CREATE_RESERVATION_KEYS['flavor:instance'] else: defaults = CREATE_RESERVATION_KEYS['others'] res_info = self._parse_params(res_str, defaults, err_msg) reservations.append(res_info) if reservations: params['reservations'] += reservations events = [] for event_str in parsed_args.events: err_msg = ("Invalid event argument '%s'. " "Event arguments must be of the " "form --event " % event_str) event_info = {"event_type": "", "event_date": ""} for kv_str in event_str.split(","): try: k, v = kv_str.split("=", 1) except ValueError: raise exception.IncorrectLease(err_msg) if k in event_info: event_info[k] = v else: raise exception.IncorrectLease(err_msg) if not event_info['event_type'] and not event_info['event_date']: raise exception.IncorrectLease(err_msg) event_date = event_info['event_date'] try: date = datetime.datetime.strptime(event_date, '%Y-%m-%d %H:%M') event_date = datetime.datetime.strftime(date, '%Y-%m-%d %H:%M') event_info['event_date'] = event_date except ValueError: raise exception.IncorrectLease events.append(event_info) if events: params['events'] = events return params def _parse_params(self, str_params, default, err_msg): request_params = {} prog = re.compile('^(?:(.*),)?(%s)=(.*)$' % "|".join(default.keys())) while str_params != "": match = prog.search(str_params) if match is None: raise exception.IncorrectLease(err_msg) self.log.info("Matches: %s", match.groups()) k, v = match.group(2, 3) if k in request_params.keys(): raise exception.DuplicatedLeaseParameters(err_msg) else: if strutils.is_int_like(v): request_params[k] = int(v) elif isinstance(default[k], list): request_params[k] = jsonutils.loads(v) else: request_params[k] = v str_params = match.group(1) if match.group(1) else "" request_params.update({k: v for k, v in default.items() if k not in request_params.keys() and v is not None}) return request_params class CreateLease(CreateLeaseBase): def get_parser(self, prog_name): parser = super(CreateLease, self).get_parser(prog_name) parser.add_argument( '--physical-reservation', metavar="", action='append', dest='physical_reservations', help='Create a reservation for physical compute hosts. ' 'Specify option multiple times to create multiple ' 'reservations. ' 'min: minimum number of hosts to reserve. ' 'max: maximum number of hosts to reserve. ' 'hypervisor_properties: JSON string, see doc. ' 'resource_properties: JSON string, see doc. ' 'before_end: JSON string, see doc. ', default=[] ) return parser def args2body(self, parsed_args): params = self._generate_params(parsed_args) physical_reservations = [] for phys_res_str in parsed_args.physical_reservations: err_msg = ("Invalid physical-reservation argument '%s'. " "Reservation arguments must be of the " "form --physical-reservation " % phys_res_str) defaults = CREATE_RESERVATION_KEYS["physical:host"] phys_res_info = self._parse_params(phys_res_str, defaults, err_msg) if not (phys_res_info['min'] and phys_res_info['max']): raise exception.IncorrectLease(err_msg) if not (strutils.is_int_like(phys_res_info['min']) and strutils.is_int_like(phys_res_info['max'])): raise exception.IncorrectLease(err_msg) min_host = int(phys_res_info['min']) max_host = int(phys_res_info['max']) if min_host > max_host: err_msg = ("Invalid physical-reservation argument '%s'. " "Reservation argument min value must be " "less than max value" % phys_res_str) raise exception.IncorrectLease(err_msg) if min_host == 0 or max_host == 0: err_msg = ("Invalid physical-reservation argument '%s'. " "Reservation arguments min and max values " "must be greater than or equal to 1" % phys_res_str) raise exception.IncorrectLease(err_msg) # NOTE(sbauza): The resource type should be conf-driven mapped with # blazar.conf file but that's potentially on another # host phys_res_info['resource_type'] = 'physical:host' physical_reservations.append(phys_res_info) if physical_reservations: # We prepend the physical_reservations to preserve legacy order # of reservations params['reservations'] = physical_reservations \ + params['reservations'] return params class UpdateLease(command.UpdateCommand): """Update a lease.""" resource = 'lease' json_indent = 4 log = logging.getLogger(__name__ + '.UpdateLease') def get_parser(self, prog_name): parser = super(UpdateLease, self).get_parser(prog_name) parser.add_argument( '--name', help='New name for the lease', default=None ) parser.add_argument( '--reservation', metavar="", action='append', help='Reservation values to update. The reservation must be ' 'selected with the id= key-value pair.', default=None) #prolong-for and reduce_by are mutually exclusive group = parser.add_mutually_exclusive_group() group.add_argument( '--prolong-for', help='Time to prolong lease for', default=None ) group.add_argument( '--prolong_for', help=argparse.SUPPRESS, default=None ) group.add_argument( '--reduce-by', help='Time to reduce lease by', default=None ) group.add_argument( '--end-date', help='end date of the lease', default=None) #defer-by and a 'future' advance-by are mutually exclusive group = parser.add_mutually_exclusive_group() group.add_argument( '--defer-by', help='Time to defer the lease start', default=None ) group.add_argument( '--advance-by', help='Time to advance the lease start', default=None ) group.add_argument( '--start-date', help='start date of the lease', default=None) return parser def args2body(self, parsed_args): params = {} if parsed_args.name: params['name'] = parsed_args.name if parsed_args.prolong_for: params['prolong_for'] = parsed_args.prolong_for if parsed_args.reduce_by: params['reduce_by'] = parsed_args.reduce_by if parsed_args.end_date: params['end_date'] = parsed_args.end_date if parsed_args.defer_by: params['defer_by'] = parsed_args.defer_by if parsed_args.advance_by: params['advance_by'] = parsed_args.advance_by if parsed_args.start_date: params['start_date'] = parsed_args.start_date if parsed_args.reservation: keys = set([ # General keys 'id', # Keys for host reservation 'min', 'max', 'hypervisor_properties', 'resource_properties', # Keys for instance reservation 'vcpus', 'memory_mb', 'disk_gb', 'amount', 'affinity', # Keys for floating IP reservation 'amount', 'network_id', 'required_floatingips', ]) list_keys = ['required_floatingips'] params['reservations'] = [] reservations = [] for res_str in parsed_args.reservation: err_msg = ("Invalid reservation argument '%s'. " "Reservation arguments must be of the form " "--reservation " % res_str) res_info = {} prog = re.compile('^(?:(.*),)?(%s)=(.*)$' % '|'.join(keys)) def parse_params(params): match = prog.search(params) if match: k, v = match.group(2, 3) if k in list_keys: v = jsonutils.loads(v) elif strutils.is_int_like(v): v = int(v) res_info[k] = v if match.group(1) is not None: parse_params(match.group(1)) parse_params(res_str) if res_info: if 'id' not in res_info: raise exception.IncorrectLease( 'The key-value pair id= is ' 'required for the --reservation argument') reservations.append(res_info) if not reservations: raise exception.IncorrectLease(err_msg) params['reservations'] = reservations return params class DeleteLease(command.DeleteCommand): """Delete a lease.""" resource = 'lease' log = logging.getLogger(__name__ + '.DeleteLease') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/blazarclient/version.py0000664000175000017500000000125200000000000022242 0ustar00zuulzuul00000000000000# Copyright (c) 2013 Mirantis Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import pbr.version __version__ = pbr.version.VersionInfo('python-blazarclient').version_string() ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1736516523.7285695 python-blazarclient-4.2.0/doc/0000775000175000017500000000000000000000000016276 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/doc/requirements.txt0000664000175000017500000000045700000000000021570 0ustar00zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. openstackdocstheme>=2.2.1 # Apache-2.0 reno>=3.1.0 # Apache-2.0 sphinx>=2.0.0,!=2.1.0 # BSD ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1736516523.7285695 python-blazarclient-4.2.0/python_blazarclient.egg-info/0000775000175000017500000000000000000000000023276 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516523.0 python-blazarclient-4.2.0/python_blazarclient.egg-info/PKG-INFO0000664000175000017500000000437300000000000024402 0ustar00zuulzuul00000000000000Metadata-Version: 1.2 Name: python-blazarclient Version: 4.2.0 Summary: Client for OpenStack Reservation Service Home-page: https://launchpad.net/blazar Author: OpenStack Author-email: openstack-discuss@lists.openstack.org License: Apache Software License Description: ======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/python-blazarclient.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on ============= Blazar client ============= This is a client for the OpenStack Blazar API. It provides a Python API (the **blazarclient** module) and a command-line script (**blazar**). Other Resources --------------- * Source code: * `Blazar `__ * `Nova scheduler filter `__ * `Client tools `__ * `Dashboard (Horizon plugin) `__ * Blueprints/Bugs: https://launchpad.net/blazar * Documentation: https://docs.openstack.org/blazar/latest/ * Release notes: https://docs.openstack.org/releasenotes/python-blazarclient/ Platform: UNKNOWN Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Environment :: OpenStack Classifier: Development Status :: 3 - Alpha Classifier: Framework :: Setuptools Plugin Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Requires-Python: >=3.8 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516523.0 python-blazarclient-4.2.0/python_blazarclient.egg-info/SOURCES.txt0000664000175000017500000000605400000000000025167 0ustar00zuulzuul00000000000000.stestr.conf .zuul.yaml AUTHORS ChangeLog HACKING.rst LICENSE MANIFEST.in README.rst requirements.txt setup.cfg setup.py test-requirements.txt tox.ini blazarclient/__init__.py blazarclient/base.py blazarclient/client.py blazarclient/command.py blazarclient/exception.py blazarclient/i18n.py blazarclient/shell.py blazarclient/utils.py blazarclient/version.py blazarclient/osc/__init__.py blazarclient/osc/plugin.py blazarclient/tests/__init__.py blazarclient/tests/test_base.py blazarclient/tests/test_client.py blazarclient/tests/test_command.py blazarclient/tests/test_plugin.py blazarclient/tests/test_shell.py blazarclient/tests/v1/__init__.py blazarclient/tests/v1/shell_commands/__init__.py blazarclient/tests/v1/shell_commands/test_floatingips.py blazarclient/tests/v1/shell_commands/test_hosts.py blazarclient/tests/v1/shell_commands/test_leases.py blazarclient/v1/__init__.py blazarclient/v1/allocations.py blazarclient/v1/client.py blazarclient/v1/floatingips.py blazarclient/v1/hosts.py blazarclient/v1/leases.py blazarclient/v1/shell_commands/__init__.py blazarclient/v1/shell_commands/allocations.py blazarclient/v1/shell_commands/floatingips.py blazarclient/v1/shell_commands/hosts.py blazarclient/v1/shell_commands/leases.py doc/requirements.txt python_blazarclient.egg-info/PKG-INFO python_blazarclient.egg-info/SOURCES.txt python_blazarclient.egg-info/dependency_links.txt python_blazarclient.egg-info/entry_points.txt python_blazarclient.egg-info/not-zip-safe python_blazarclient.egg-info/pbr.json python_blazarclient.egg-info/requires.txt python_blazarclient.egg-info/top_level.txt releasenotes/notes/.placeholder releasenotes/notes/bug-1777548-6b5c770abc6ac360.yaml releasenotes/notes/bug-1783296-set-start-date-to-now-e329a6923c11432f.yaml releasenotes/notes/default-affinity-value-150947560fd7da3c.yaml releasenotes/notes/drop-python2-c3c1601e92a9b87a.yaml releasenotes/notes/flavor-based-instance-reservation-ec9730ddfeabdf15.yaml releasenotes/notes/floatingip-reservation-update-5823a21516135f17.yaml releasenotes/notes/floatingip-support-d184a565f324d31b.yaml releasenotes/notes/host-allocation-fad8e511cb13c0e8.yaml releasenotes/notes/host-resource-property-9ac5c21bd3ca6699.yaml releasenotes/notes/ksa-loading-9731c570772c826a.yaml releasenotes/notes/openstackclient-support-f591eef2dc3c1a8b.yaml releasenotes/notes/parse-required-floatingips-f79f79d652e371ae.yaml releasenotes/notes/respect-selected-region-a409773f851ccb47.yaml releasenotes/notes/separate-id-parser-argument-from-showcommand-ce92d0c52dd1963e.yaml releasenotes/source/2023.1.rst releasenotes/source/2023.2.rst releasenotes/source/2024.1.rst releasenotes/source/2024.2.rst releasenotes/source/conf.py releasenotes/source/index.rst releasenotes/source/rocky.rst releasenotes/source/stein.rst releasenotes/source/train.rst releasenotes/source/unreleased.rst releasenotes/source/ussuri.rst releasenotes/source/victoria.rst releasenotes/source/wallaby.rst releasenotes/source/xena.rst releasenotes/source/yoga.rst releasenotes/source/zed.rst releasenotes/source/_static/.placeholder releasenotes/source/_templates/.placeholder././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516523.0 python-blazarclient-4.2.0/python_blazarclient.egg-info/dependency_links.txt0000664000175000017500000000000100000000000027344 0ustar00zuulzuul00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516523.0 python-blazarclient-4.2.0/python_blazarclient.egg-info/entry_points.txt0000664000175000017500000000322700000000000026600 0ustar00zuulzuul00000000000000[console_scripts] blazar = blazarclient.shell:main [openstack.cli.extension] reservation = blazarclient.osc.plugin [openstack.reservation.v1] reservation_allocation_list = blazarclient.v1.shell_commands.allocations:ListAllocations reservation_allocation_show = blazarclient.v1.shell_commands.allocations:ShowAllocations reservation_floatingip_create = blazarclient.v1.shell_commands.floatingips:CreateFloatingIP reservation_floatingip_delete = blazarclient.v1.shell_commands.floatingips:DeleteFloatingIP reservation_floatingip_list = blazarclient.v1.shell_commands.floatingips:ListFloatingIPs reservation_floatingip_show = blazarclient.v1.shell_commands.floatingips:ShowFloatingIP reservation_host_create = blazarclient.v1.shell_commands.hosts:CreateHost reservation_host_delete = blazarclient.v1.shell_commands.hosts:DeleteHost reservation_host_list = blazarclient.v1.shell_commands.hosts:ListHosts reservation_host_property_list = blazarclient.v1.shell_commands.hosts:ListHostProperties reservation_host_property_set = blazarclient.v1.shell_commands.hosts:UpdateHostProperty reservation_host_property_show = blazarclient.v1.shell_commands.hosts:ShowHostProperty reservation_host_set = blazarclient.v1.shell_commands.hosts:UpdateHost reservation_host_show = blazarclient.v1.shell_commands.hosts:ShowHost reservation_lease_create = blazarclient.v1.shell_commands.leases:CreateLeaseBase reservation_lease_delete = blazarclient.v1.shell_commands.leases:DeleteLease reservation_lease_list = blazarclient.v1.shell_commands.leases:ListLeases reservation_lease_set = blazarclient.v1.shell_commands.leases:UpdateLease reservation_lease_show = blazarclient.v1.shell_commands.leases:ShowLease ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516523.0 python-blazarclient-4.2.0/python_blazarclient.egg-info/not-zip-safe0000664000175000017500000000000100000000000025524 0ustar00zuulzuul00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516523.0 python-blazarclient-4.2.0/python_blazarclient.egg-info/pbr.json0000664000175000017500000000005600000000000024755 0ustar00zuulzuul00000000000000{"git_version": "20c3119", "is_release": true}././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516523.0 python-blazarclient-4.2.0/python_blazarclient.egg-info/requires.txt0000664000175000017500000000022400000000000025674 0ustar00zuulzuul00000000000000PrettyTable>=0.7.1 cliff!=2.9.0,>=2.8.0 keystoneauth1>=3.4.0 osc-lib>=1.3.0 oslo.i18n>=3.15.3 oslo.log>=3.36.0 oslo.utils>=7.0.0 pbr!=2.1.0,>=2.0.0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516523.0 python-blazarclient-4.2.0/python_blazarclient.egg-info/top_level.txt0000664000175000017500000000001500000000000026024 0ustar00zuulzuul00000000000000blazarclient ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1736516523.7165694 python-blazarclient-4.2.0/releasenotes/0000775000175000017500000000000000000000000020222 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1736516523.7325697 python-blazarclient-4.2.0/releasenotes/notes/0000775000175000017500000000000000000000000021352 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/releasenotes/notes/.placeholder0000664000175000017500000000000000000000000023623 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/releasenotes/notes/bug-1777548-6b5c770abc6ac360.yaml0000664000175000017500000000107000000000000026141 0ustar00zuulzuul00000000000000--- fixes: - | When the blazar CLI client got an error code from the blazar server, it didn't display error messages created in the blazar server. Instead, it displayed `messages created in keystoneauth`_ with poor information. See the `bug report`_ for example. It was fixed to display original error messages which include useful information. .. _messages created in keystoneauth: https://github.com/openstack/keystoneauth/blob/master/keystoneauth1/exceptions/http.py .. _bug report: https://bugs.launchpad.net/blazar/+bug/1777548 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/releasenotes/notes/bug-1783296-set-start-date-to-now-e329a6923c11432f.yaml0000664000175000017500000000155700000000000032015 0ustar00zuulzuul00000000000000--- upgrade: - | When creating a lease using the CLI client, the default value for start date was changed to use the string 'now', which is resolved to the current time on the server rather than on the client. Note that if the request is sent at the end of a minute and interpreted by the service at the beginning of the next minute, this can result in leases that are one minute shorter than what the user might expect, as the end date is still specified by the client. Users who care about the exact timing of their leases should explicitly specify both start and end dates. fixes: - | Creating a lease using the CLI client without specifying a start date no longer fails if the request is sent to the Blazar service just before the end of a minute. For more details, see `bug 1783296 `_. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/releasenotes/notes/default-affinity-value-150947560fd7da3c.yaml0000664000175000017500000000022000000000000030733 0ustar00zuulzuul00000000000000--- fixes: - | Fixes creation of instance reservations when no affinity value is provided, in which case affinity is set to ``None``. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/releasenotes/notes/drop-python2-c3c1601e92a9b87a.yaml0000664000175000017500000000011600000000000027013 0ustar00zuulzuul00000000000000--- upgrade: - | Python 2 is no longer supported. Python 3 is required. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/releasenotes/notes/flavor-based-instance-reservation-ec9730ddfeabdf15.yaml0000664000175000017500000000012500000000000033460 0ustar00zuulzuul00000000000000--- features: - | Add support for creating flavor-based instance reservations. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/releasenotes/notes/floatingip-reservation-update-5823a21516135f17.yaml0000664000175000017500000000040600000000000032117 0ustar00zuulzuul00000000000000--- fixes: - | The command-line client now parses floating IP reservation values when using the ``lease-update`` command. Note that while accepted by the client, the Blazar service may prevent the update of some floating IP reservation values. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/releasenotes/notes/floatingip-support-d184a565f324d31b.yaml0000664000175000017500000000054200000000000030235 0ustar00zuulzuul00000000000000--- features: - | Added support for operators to manage reservable floating IPs using the following new commands: * ``floatingip-create`` * ``floatingip-delete`` * ``floatingip-list`` * ``floatingip-show`` - | Added support for users to create floating IP reservations using the ``virtual:floatingip`` resource type. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/releasenotes/notes/host-allocation-fad8e511cb13c0e8.yaml0000664000175000017500000000063400000000000027706 0ustar00zuulzuul00000000000000--- features: - | Adds support for querying the Resource Allocation API with the following commands: * ``blazar allocation-list`` * ``blazar allocation-show`` Only the ``host`` resource type is currently supported by the Blazar service. OpenStackClient commands are also available: * ``openstack reservation allocation list`` * ``openstack reservation allocation show`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/releasenotes/notes/host-resource-property-9ac5c21bd3ca6699.yaml0000664000175000017500000000031100000000000031215 0ustar00zuulzuul00000000000000--- features: - | Added support for managing host resource properties using the following new commands: * ``host-property-list`` * ``host-property-show`` * ``host-property-set`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/releasenotes/notes/ksa-loading-9731c570772c826a.yaml0000664000175000017500000000214500000000000026435 0ustar00zuulzuul00000000000000--- deprecations: - | The ``blazar`` command-line client has switched to the ``keystoneauth1.loading`` module. As a result, the following options are deprecated: * ``--service-type`` (use ``--os-service-type`` instead) * ``--endpoint-type`` (use ``--os-interface`` instead) The following options have been removed: * ``--os-auth-strategy`` (this option had not effect) * ``--os_auth_strategy`` (this option had not effect) * ``--os_auth_url`` (use ``--os-auth-url`` instead) * ``--os_project_name`` (use ``--os-project-name`` instead) * ``--os_project_id`` (use ``--os-project-id`` instead) * ``--os_project_domain_name`` (use ``--os-project-domain-name`` instead) * ``--os_project_domain_id`` (use ``--os-project-domain-id`` instead) * ``--os_tenant_name`` (use ``--os-project-name`` or ``--os-tenant-name`` instead) * ``--os_username`` (use ``--os-username`` instead) * ``--os_user_domain_name`` (use ``--os-user-domain-name`` instead) * ``--os_user_domain_id`` (use ``--os-user-domain-id`` instead) * ``--os_token`` (use ``--os-token`` instead) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/releasenotes/notes/openstackclient-support-f591eef2dc3c1a8b.yaml0000664000175000017500000000017600000000000031577 0ustar00zuulzuul00000000000000--- features: - | Add openstackclient plugin support, enabling blazar commands to be used within the openstack CLI. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/releasenotes/notes/parse-required-floatingips-f79f79d652e371ae.yaml0000664000175000017500000000077000000000000031747 0ustar00zuulzuul00000000000000--- fixes: - | Parse the ``required_floatingips`` command-line parameter as a list instead of a string, to pass it to the API in the expected format. For example, this parameter can be used in the following fashion: ``blazar lease-create --reservation 'resource_type=virtual:floatingip,network_id=81fabec7-00ae-497a-b485-72f4bf187d3e,amount=2,required_floatingips=["172.24.4.2","172.24.4.3"]' fip-lease`` For more details, see `bug 1843258 `_. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/releasenotes/notes/respect-selected-region-a409773f851ccb47.yaml0000664000175000017500000000035700000000000031122 0ustar00zuulzuul00000000000000--- fixes: - | The region name value provided via an environment variable or a command line argument is now respected by the client. Without this fix, the wrong reservation endpoint could be selected in a multi-region cloud. ././@PaxHeader0000000000000000000000000000021700000000000011455 xustar0000000000000000121 path=python-blazarclient-4.2.0/releasenotes/notes/separate-id-parser-argument-from-showcommand-ce92d0c52dd1963e.yaml 22 mtime=1736516447.0 python-blazarclient-4.2.0/releasenotes/notes/separate-id-parser-argument-from-showcommand-ce92d0c52d0000664000175000017500000000033100000000000033522 0ustar00zuulzuul00000000000000--- other: - | The ID parser argument is moved from the ShowCommand class to each resource class. This allows the ordering of arguments to be fully customised, instead of requiring the ID to come first. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1736516523.7365696 python-blazarclient-4.2.0/releasenotes/source/0000775000175000017500000000000000000000000021522 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/releasenotes/source/2023.1.rst0000664000175000017500000000021000000000000022772 0ustar00zuulzuul00000000000000=========================== 2023.1 Series Release Notes =========================== .. release-notes:: :branch: unmaintained/2023.1 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/releasenotes/source/2023.2.rst0000664000175000017500000000020200000000000022774 0ustar00zuulzuul00000000000000=========================== 2023.2 Series Release Notes =========================== .. release-notes:: :branch: stable/2023.2 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/releasenotes/source/2024.1.rst0000664000175000017500000000020200000000000022774 0ustar00zuulzuul00000000000000=========================== 2024.1 Series Release Notes =========================== .. release-notes:: :branch: stable/2024.1 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/releasenotes/source/2024.2.rst0000664000175000017500000000020200000000000022775 0ustar00zuulzuul00000000000000=========================== 2024.2 Series Release Notes =========================== .. release-notes:: :branch: stable/2024.2 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1736516523.7365696 python-blazarclient-4.2.0/releasenotes/source/_static/0000775000175000017500000000000000000000000023150 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/releasenotes/source/_static/.placeholder0000664000175000017500000000000000000000000025421 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1736516523.7365696 python-blazarclient-4.2.0/releasenotes/source/_templates/0000775000175000017500000000000000000000000023657 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/releasenotes/source/_templates/.placeholder0000664000175000017500000000000000000000000026130 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/releasenotes/source/conf.py0000664000175000017500000001305100000000000023021 0ustar00zuulzuul00000000000000# -*- coding: utf-8 -*- # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # # Configuration file for the Sphinx documentation builder. # # This file does only contain a selection of the most common options. For a # full list see the documentation: # http://www.sphinx-doc.org/en/master/config # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # # import os # import sys # sys.path.insert(0, os.path.abspath('.')) # -- Project information ----------------------------------------------------- project = 'Blazar Client Release Notes' copyright = '2018, Blazar Developers' author = 'Blazar Developers' # The short X.Y version version = '' # The full version, including alpha/beta/rc tags release = '' # -- General configuration --------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'openstackdocstheme', 'reno.sphinxext', ] # openstackdocstheme options openstackdocs_repo_name = 'openstack/python-blazarclient' openstackdocs_bug_project = 'blazar' openstackdocs_bug_tag = '' # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The master toctree document. master_doc = 'index' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. # language = None # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path . exclude_patterns = [] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'native' # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'openstackdocs' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Custom sidebar templates, must be a dictionary that maps document names # to template names. # # The default sidebars (for documents that don't match any pattern) are # defined by theme itself. Builtin themes are using these templates by # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', # 'searchbox.html']``. # # html_sidebars = {} # -- Options for HTMLHelp output --------------------------------------------- # Output file base name for HTML help builder. htmlhelp_basename = 'BlazarClientReleaseNotesdoc' # -- Options for LaTeX output ------------------------------------------------ latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'BlazarClientReleaseNotes.tex', 'Blazar Client Release Notes', 'Blazar Developers', 'manual'), ] # -- Options for manual page output ------------------------------------------ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'blazarclientreleasenotes', 'Blazar Client Release Notes', [author], 1) ] # -- Options for Texinfo output ---------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'BlazarClientReleaseNotes', 'Blazar Client Release Notes', author, 'BlazarClientReleaseNotes', 'Reservation service client.', 'Miscellaneous'), ] # -- Options for Internationalization output ------------------------------ locale_dirs = ['locale/'] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/releasenotes/source/index.rst0000664000175000017500000000040100000000000023356 0ustar00zuulzuul00000000000000============================= Blazar Client Release Notes ============================= .. toctree:: :maxdepth: 1 unreleased 2024.2 2024.1 2023.2 2023.1 zed yoga xena wallaby victoria ussuri train stein rocky ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/releasenotes/source/rocky.rst0000664000175000017500000000022100000000000023376 0ustar00zuulzuul00000000000000=================================== Rocky Series Release Notes =================================== .. release-notes:: :branch: stable/rocky ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/releasenotes/source/stein.rst0000664000175000017500000000022100000000000023371 0ustar00zuulzuul00000000000000=================================== Stein Series Release Notes =================================== .. release-notes:: :branch: stable/stein ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/releasenotes/source/train.rst0000664000175000017500000000017600000000000023375 0ustar00zuulzuul00000000000000========================== Train Series Release Notes ========================== .. release-notes:: :branch: stable/train ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/releasenotes/source/unreleased.rst0000664000175000017500000000016000000000000024400 0ustar00zuulzuul00000000000000============================== Current Series Release Notes ============================== .. release-notes:: ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/releasenotes/source/ussuri.rst0000664000175000017500000000020200000000000023600 0ustar00zuulzuul00000000000000=========================== Ussuri Series Release Notes =========================== .. release-notes:: :branch: stable/ussuri ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/releasenotes/source/victoria.rst0000664000175000017500000000022000000000000024066 0ustar00zuulzuul00000000000000============================= Victoria Series Release Notes ============================= .. release-notes:: :branch: unmaintained/victoria ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/releasenotes/source/wallaby.rst0000664000175000017500000000021400000000000023704 0ustar00zuulzuul00000000000000============================ Wallaby Series Release Notes ============================ .. release-notes:: :branch: unmaintained/wallaby ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/releasenotes/source/xena.rst0000664000175000017500000000020000000000000023177 0ustar00zuulzuul00000000000000========================= Xena Series Release Notes ========================= .. release-notes:: :branch: unmaintained/xena ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/releasenotes/source/yoga.rst0000664000175000017500000000020000000000000023203 0ustar00zuulzuul00000000000000========================= Yoga Series Release Notes ========================= .. release-notes:: :branch: unmaintained/yoga ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/releasenotes/source/zed.rst0000664000175000017500000000017400000000000023040 0ustar00zuulzuul00000000000000======================== Zed Series Release Notes ======================== .. release-notes:: :branch: unmaintained/zed ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/requirements.txt0000664000175000017500000000125200000000000021015 0ustar00zuulzuul00000000000000# Requirements lower bounds listed here are our best effort to keep them up to # date but we do not test them so no guarantee of having them all correct. If # you find any incorrect lower bounds, let us know or propose a fix. # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. pbr!=2.1.0,>=2.0.0 # Apache-2.0 cliff!=2.9.0,>=2.8.0 # Apache-2.0 PrettyTable>=0.7.1 # BSD oslo.i18n>=3.15.3 # Apache-2.0 oslo.log>=3.36.0 # Apache-2.0 oslo.utils>=7.0.0 # Apache-2.0 keystoneauth1>=3.4.0 # Apache-2.0 osc-lib>=1.3.0 # Apache-2.0 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1736516523.7365696 python-blazarclient-4.2.0/setup.cfg0000664000175000017500000000522000000000000017351 0ustar00zuulzuul00000000000000[metadata] name = python-blazarclient summary = Client for OpenStack Reservation Service description_file = README.rst license = Apache Software License python_requires = >=3.8 classifiers = Programming Language :: Python Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: 3 :: Only Programming Language :: Python :: 3 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 Environment :: OpenStack Development Status :: 3 - Alpha Framework :: Setuptools Plugin Intended Audience :: Information Technology Intended Audience :: System Administrators License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux author = OpenStack author_email = openstack-discuss@lists.openstack.org home_page = https://launchpad.net/blazar [files] packages = blazarclient [entry_points] console_scripts = blazar = blazarclient.shell:main openstack.cli.extension = reservation = blazarclient.osc.plugin openstack.reservation.v1 = reservation_allocation_list = blazarclient.v1.shell_commands.allocations:ListAllocations reservation_allocation_show = blazarclient.v1.shell_commands.allocations:ShowAllocations reservation_floatingip_create = blazarclient.v1.shell_commands.floatingips:CreateFloatingIP reservation_floatingip_delete = blazarclient.v1.shell_commands.floatingips:DeleteFloatingIP reservation_floatingip_list = blazarclient.v1.shell_commands.floatingips:ListFloatingIPs reservation_floatingip_show = blazarclient.v1.shell_commands.floatingips:ShowFloatingIP reservation_host_create = blazarclient.v1.shell_commands.hosts:CreateHost reservation_host_delete = blazarclient.v1.shell_commands.hosts:DeleteHost reservation_host_list = blazarclient.v1.shell_commands.hosts:ListHosts reservation_host_property_list = blazarclient.v1.shell_commands.hosts:ListHostProperties reservation_host_property_set = blazarclient.v1.shell_commands.hosts:UpdateHostProperty reservation_host_property_show = blazarclient.v1.shell_commands.hosts:ShowHostProperty reservation_host_set = blazarclient.v1.shell_commands.hosts:UpdateHost reservation_host_show = blazarclient.v1.shell_commands.hosts:ShowHost reservation_lease_create = blazarclient.v1.shell_commands.leases:CreateLeaseBase reservation_lease_delete = blazarclient.v1.shell_commands.leases:DeleteLease reservation_lease_list = blazarclient.v1.shell_commands.leases:ListLeases reservation_lease_set = blazarclient.v1.shell_commands.leases:UpdateLease reservation_lease_show = blazarclient.v1.shell_commands.leases:ShowLease [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/setup.py0000664000175000017500000000127100000000000017244 0ustar00zuulzuul00000000000000# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import setuptools setuptools.setup( setup_requires=['pbr>=2.0.0'], pbr=True) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/test-requirements.txt0000664000175000017500000000060700000000000021775 0ustar00zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. hacking>=7.0.0,<7.1.0 # Apache-2.0 oslotest>=3.2.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD stestr>=2.0.0 # Apache-2.0 testtools>=2.2.0 # MIT coverage!=4.4,>=4.0 # Apache-2.0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1736516447.0 python-blazarclient-4.2.0/tox.ini0000664000175000017500000000315500000000000017050 0ustar00zuulzuul00000000000000[tox] minversion = 3.18.0 envlist = py3,pep8 ignore_basepython_conflict = True [testenv] basepython = python3 install_command = pip install {opts} {packages} deps = -c{env:TOX_CONSTRAINTS_FILE:https://opendev.org/openstack/requirements/raw/branch/master/upper-constraints.txt} -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt setenv = VIRTUAL_ENV={envdir} DISCOVER_DIRECTORY=blazarclient/tests commands = stestr run --slowest '{posargs}' [testenv:pep8] commands = flake8 [flake8] show-source = true builtins = _ # Ignore currently failing tests for now # W504 skipped because it is overeager and unnecessary ignore = E265,H405,W504 exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg [hacking] import_exceptions = blazarclient.i18n [testenv:venv] deps = -c{env:TOX_CONSTRAINTS_FILE:https://opendev.org/openstack/requirements/raw/branch/master/upper-constraints.txt} -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt commands = {posargs} [testenv:cover] allowlist_externals = find setenv = {[testenv]setenv} PYTHON=coverage run --source blazarclient --parallel-mode commands = coverage erase find . -type f -name "*.pyc" -delete stestr run {posargs} coverage combine coverage html -d cover coverage xml -o cover/coverage.xml coverage report [testenv:releasenotes] deps = -c{env:TOX_CONSTRAINTS_FILE:https://opendev.org/openstack/requirements/raw/branch/master/upper-constraints.txt} -r{toxinidir}/doc/requirements.txt commands = sphinx-build -a -W -E -d releasenotes/build/doctrees --keep-going -b html releasenotes/source releasenotes/build/html