python-blazarclient-3.0.0/0000775000175000017500000000000013622743472015537 5ustar zuulzuul00000000000000python-blazarclient-3.0.0/requirements.txt0000664000175000017500000000073613622743424021026 0ustar zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. pbr!=2.1.0,>=2.0.0 # Apache-2.0 cliff!=2.9.0,>=2.8.0 # Apache-2.0 PrettyTable<0.8,>=0.7.1 # BSD six>=1.10.0 # MIT Babel!=2.4.0,>=2.3.4 # BSD oslo.i18n>=3.15.3 # Apache-2.0 oslo.log>=3.36.0 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 keystoneauth1>=3.4.0 # Apache-2.0 python-blazarclient-3.0.0/setup.py0000664000175000017500000000200613622743424017244 0ustar zuulzuul00000000000000# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT import setuptools # In python < 2.7.4, a lazy loading of package `pbr` will break # setuptools if some other modules registered functions in `atexit`. # solution from: http://bugs.python.org/issue15881#msg170215 try: import multiprocessing # noqa except ImportError: pass setuptools.setup( setup_requires=['pbr>=2.0.0'], pbr=True) python-blazarclient-3.0.0/tox.ini0000664000175000017500000000303713622743424017052 0ustar zuulzuul00000000000000[tox] minversion = 3.1.1 envlist = py36,py37,pep8 ignore_basepython_conflict = True [testenv] basepython = python3 install_command = pip install {opts} {packages} deps = -c{env:UPPER_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 = python setup.py testr --slowest --testr-args="{posargs}" [testenv:pep8] commands = flake8 [flake8] show-source = true builtins = _ # Ignore currently failing tests for now ignore = E265,H405 exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg [hacking] import_exceptions = blazarclient.i18n [testenv:venv] deps = -c{env:UPPER_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] commands = python setup.py testr --coverage --testr-args='{posargs}' [testenv:lower-constraints] deps = -c{toxinidir}/lower-constraints.txt -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt [testenv:releasenotes] deps = -c{env:UPPER_CONSTRAINTS_FILE:https://opendev.org/openstack/requirements/raw/branch/master/upper-constraints.txt} -r{toxinidir}/requirements.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 python-blazarclient-3.0.0/MANIFEST.in0000664000175000017500000000020213622743424017264 0ustar zuulzuul00000000000000include AUTHORS include README.rst include ChangeLog include LICENSE exclude .gitignore exclude .gitreview global-exclude *.pyc python-blazarclient-3.0.0/lower-constraints.txt0000664000175000017500000000153013622743424021771 0ustar zuulzuul00000000000000appdirs==1.3.0 Babel==2.3.4 cliff==2.8.0 cmd2==0.8.0 coverage==4.0 debtcollector==1.2.0 extras==1.0.0 fixtures==3.0.0 flake8==2.2.4 hacking==0.10.2 iso8601==0.1.11 keystoneauth1==3.4.0 linecache2==1.0.0 mccabe==0.2.1 mock==3.0.0 monotonic==0.6 mox3==0.20.0 msgpack-python==0.4.0 netaddr==0.7.18 netifaces==0.10.4 os-client-config==1.28.0 oslo.config==5.2.0 oslo.context==2.19.2 oslo.i18n==3.15.3 oslo.log==3.36.0 oslo.serialization==2.18.0 oslo.utils==3.33.0 oslotest==3.2.0 pbr==2.0.0 pep8==1.5.7 prettytable==0.7.1 pyflakes==0.8.1 pyinotify==0.9.6 pyparsing==2.1.0 pyperclip==1.5.27 python-dateutil==2.5.3 python-mimeparse==1.6.0 python-subunit==1.0.0 pytz==2013.6 PyYAML==3.12 requests==2.14.2 requestsexceptions==1.2.0 rfc3986==0.3.1 six==1.10.0 stevedore==1.20.0 testrepository==0.0.18 testtools==2.2.0 traceback2==1.4.0 unittest2==1.1.0 wrapt==1.7.0 python-blazarclient-3.0.0/LICENSE0000664000175000017500000002363613622743424016553 0ustar zuulzuul00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. python-blazarclient-3.0.0/HACKING.rst0000664000175000017500000000035513622743424017335 0ustar zuulzuul00000000000000Blazar 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 python-blazarclient-3.0.0/.testr.conf0000664000175000017500000000051313622743424017621 0ustar zuulzuul00000000000000[DEFAULT] test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \ OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \ OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \ ${PYTHON:-python} -m subunit.run discover $DISCOVER_DIRECTORY $LISTOPT $IDOPTION test_id_option=--load-list $IDFILE test_list_option=--list python-blazarclient-3.0.0/python_blazarclient.egg-info/0000775000175000017500000000000013622743472023304 5ustar zuulzuul00000000000000python-blazarclient-3.0.0/python_blazarclient.egg-info/SOURCES.txt0000664000175000017500000000423013622743472025167 0ustar zuulzuul00000000000000.testr.conf .zuul.yaml AUTHORS ChangeLog HACKING.rst LICENSE MANIFEST.in README.rst lower-constraints.txt 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/tests/__init__.py blazarclient/tests/test_base.py blazarclient/tests/test_client.py blazarclient/tests/test_command.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/client.py blazarclient/v1/floatingips.py blazarclient/v1/hosts.py blazarclient/v1/leases.py blazarclient/v1/shell_commands/__init__.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/drop-python2-c3c1601e92a9b87a.yaml releasenotes/notes/floatingip-reservation-update-5823a21516135f17.yaml releasenotes/notes/floatingip-support-d184a565f324d31b.yaml releasenotes/notes/parse-required-floatingips-f79f79d652e371ae.yaml releasenotes/notes/respect-selected-region-a409773f851ccb47.yaml releasenotes/source/conf.py releasenotes/source/index.rst releasenotes/source/rocky.rst releasenotes/source/stein.rst releasenotes/source/train.rst releasenotes/source/unreleased.rst releasenotes/source/_static/.placeholder releasenotes/source/_templates/.placeholderpython-blazarclient-3.0.0/python_blazarclient.egg-info/not-zip-safe0000664000175000017500000000000113622743472025532 0ustar zuulzuul00000000000000 python-blazarclient-3.0.0/python_blazarclient.egg-info/dependency_links.txt0000664000175000017500000000000113622743472027352 0ustar zuulzuul00000000000000 python-blazarclient-3.0.0/python_blazarclient.egg-info/top_level.txt0000664000175000017500000000001513622743472026032 0ustar zuulzuul00000000000000blazarclient python-blazarclient-3.0.0/python_blazarclient.egg-info/pbr.json0000664000175000017500000000005613622743472024763 0ustar zuulzuul00000000000000{"git_version": "3f30c49", "is_release": true}python-blazarclient-3.0.0/python_blazarclient.egg-info/entry_points.txt0000664000175000017500000000006413622743472026602 0ustar zuulzuul00000000000000[console_scripts] blazar = blazarclient.shell:main python-blazarclient-3.0.0/python_blazarclient.egg-info/requires.txt0000664000175000017500000000025413622743472025705 0ustar zuulzuul00000000000000pbr!=2.1.0,>=2.0.0 cliff!=2.9.0,>=2.8.0 PrettyTable<0.8,>=0.7.1 six>=1.10.0 Babel!=2.4.0,>=2.3.4 oslo.i18n>=3.15.3 oslo.log>=3.36.0 oslo.utils>=3.33.0 keystoneauth1>=3.4.0 python-blazarclient-3.0.0/python_blazarclient.egg-info/PKG-INFO0000664000175000017500000000377613622743472024416 0ustar zuulzuul00000000000000Metadata-Version: 1.1 Name: python-blazarclient Version: 3.0.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 :: 3 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 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 python-blazarclient-3.0.0/test-requirements.txt0000664000175000017500000000064513622743424022002 0ustar zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. hacking>=1.1.0,<1.2.0 # Apache-2.0 mock>=3.0.0 # BSD oslotest>=3.2.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD testrepository>=0.0.18 # Apache-2.0/BSD testtools>=2.2.0 # MIT coverage!=4.4,>=4.0 # Apache-2.0 python-blazarclient-3.0.0/AUTHORS0000664000175000017500000000346513622743472016617 0ustar zuulzuul00000000000000Andreas Jaeger Corey Bryant Cristian A Sanchez David Moreau Simard Dina Belova Doug Hellmann François Rossigneux Ghanshyam Mann Govardhan Chintha Harsh Shah Hiroaki Kobayashi Hiroki Ito Jason Anderson Jay Lau Jeremy Stanley Masahito Muroi Michael Still Monty Taylor Nguyen Hai Nikolaj Starodubtsev Nikolay Starodubtsev Ondřej Nový OpenStack Release Bot Pablo Andres Fuente Pierre Riteau Pierre Riteau Sean McGinnis Sergey Lukjanov ShangXiao Steve Martinelli Sylvain Bauza Tetsuro Nakamura Tetsuro Nakamura Tony Breeds Vieri <15050873171@163.com> ZhijunWei chenghuiyu cmart jacky06 kangyufei luqitao lvxianguo melissaml qingszhao rajat29 sunjia zhangboye python-blazarclient-3.0.0/blazarclient/0000775000175000017500000000000013622743472020211 5ustar zuulzuul00000000000000python-blazarclient-3.0.0/blazarclient/client.py0000664000175000017500000000243113622743424022036 0ustar zuulzuul00000000000000# 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, *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, *args, **kwargs) python-blazarclient-3.0.0/blazarclient/base.py0000664000175000017500000001135513622743424021477 0ustar zuulzuul00000000000000# 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 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 python-blazarclient-3.0.0/blazarclient/command.py0000664000175000017500000002423513622743424022204 0ustar zuulzuul00000000000000# 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 __future__ import print_function import ast import logging import six 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): 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, six.text_type): 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) 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 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()))) python-blazarclient-3.0.0/blazarclient/utils.py0000664000175000017500000001354313622743424021726 0ustar zuulzuul00000000000000# 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 = '^(\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) python-blazarclient-3.0.0/blazarclient/v1/0000775000175000017500000000000013622743472020537 5ustar zuulzuul00000000000000python-blazarclient-3.0.0/blazarclient/v1/client.py0000664000175000017500000000461613622743424022373 0ustar zuulzuul00000000000000# 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 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) python-blazarclient-3.0.0/blazarclient/v1/shell_commands/0000775000175000017500000000000013622743472023527 5ustar zuulzuul00000000000000python-blazarclient-3.0.0/blazarclient/v1/shell_commands/hosts.py0000664000175000017500000000762313622743424025246 0ustar zuulzuul00000000000000# 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 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') 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 python-blazarclient-3.0.0/blazarclient/v1/shell_commands/floatingips.py0000664000175000017500000000472113622743424026421 0ustar zuulzuul00000000000000# 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') 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') python-blazarclient-3.0.0/blazarclient/v1/shell_commands/__init__.py0000664000175000017500000000000013622743424025623 0ustar zuulzuul00000000000000python-blazarclient-3.0.0/blazarclient/v1/shell_commands/leases.py0000664000175000017500000004226313622743424025361 0ustar zuulzuul00000000000000# 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 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": "", "resource_properties": "", "resource_type": 'virtual: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') class CreateLease(command.CreateCommand): """Create a lease.""" resource = 'lease' json_indent = 4 log = logging.getLogger(__name__ + '.CreateLease') default_start = 'now' default_end = datetime.datetime.utcnow() + datetime.timedelta(days=1) def get_parser(self, prog_name): parser = super(CreateLease, 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( '--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=[] ) 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): def parse_params(str_params, default): 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(defaults[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 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 = datetime.datetime.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'] = [] 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 = parse_params(phys_res_str, defaults) 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: params['reservations'] += physical_reservations 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'] else: defaults = CREATE_RESERVATION_KEYS['others'] res_info = parse_params(res_str, defaults) reservations.append(res_info) if reservations: params['reservations'] += reservations if not params['reservations']: raise exception.IncorrectLease 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 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') python-blazarclient-3.0.0/blazarclient/v1/hosts.py0000664000175000017500000000356213622743424022254 0ustar zuulzuul00000000000000# 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.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): """Describes 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): """Deletes 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 l: l[sort_by]) return hosts python-blazarclient-3.0.0/blazarclient/v1/floatingips.py0000664000175000017500000000336313622743424023432 0ustar zuulzuul00000000000000# 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 l: l[sort_by]) return floatingips python-blazarclient-3.0.0/blazarclient/v1/__init__.py0000664000175000017500000000000013622743424022633 0ustar zuulzuul00000000000000python-blazarclient-3.0.0/blazarclient/v1/leases.py0000664000175000017500000000771413622743424022373 0ustar zuulzuul00000000000000# 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 l: l[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) python-blazarclient-3.0.0/blazarclient/version.py0000664000175000017500000000125213622743424022245 0ustar zuulzuul00000000000000# 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() python-blazarclient-3.0.0/blazarclient/__init__.py0000664000175000017500000000000013622743424022305 0ustar zuulzuul00000000000000python-blazarclient-3.0.0/blazarclient/exception.py0000664000175000017500000000550413622743424022562 0ustar zuulzuul00000000000000# 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 python-blazarclient-3.0.0/blazarclient/tests/0000775000175000017500000000000013622743472021353 5ustar zuulzuul00000000000000python-blazarclient-3.0.0/blazarclient/tests/test_shell.py0000664000175000017500000000617713622743424024103 0ustar zuulzuul00000000000000# 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 re import six 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 = six.StringIO() sys.stderr = six.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() python-blazarclient-3.0.0/blazarclient/tests/test_base.py0000664000175000017500000001622313622743424023677 0ustar zuulzuul00000000000000# 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 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) python-blazarclient-3.0.0/blazarclient/tests/test_client.py0000664000175000017500000000262413622743424024243 0ustar zuulzuul00000000000000# 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') def test_with_v1a0(self): self.client.Client(version='1a0') self.import_obj.assert_called_once_with( 'blazarclient.v1.client.Client') def test_with_wrong_vers(self): self.assertRaises(exception.UnsupportedVersion, self.client.Client, version='0.0') python-blazarclient-3.0.0/blazarclient/tests/v1/0000775000175000017500000000000013622743472021701 5ustar zuulzuul00000000000000python-blazarclient-3.0.0/blazarclient/tests/v1/shell_commands/0000775000175000017500000000000013622743472024671 5ustar zuulzuul00000000000000python-blazarclient-3.0.0/blazarclient/tests/v1/shell_commands/__init__.py0000664000175000017500000000000013622743424026765 0ustar zuulzuul00000000000000python-blazarclient-3.0.0/blazarclient/tests/v1/shell_commands/test_floatingips.py0000664000175000017500000001145213622743424030621 0ustar zuulzuul00000000000000# 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 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') python-blazarclient-3.0.0/blazarclient/tests/v1/shell_commands/test_leases.py0000664000175000017500000003150413622743424027556 0ustar zuulzuul00000000000000# 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 import mock from blazarclient import exception from blazarclient import shell from blazarclient import tests from blazarclient.v1.shell_commands import leases FIRST_LEASE = 'd1e43d6d-8f6f-4c2e-b0a9-2982b39dc698' SECOND_LEASE = '424d21c3-45a2-448a-81ad-32eddc888375' 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='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': 'now', '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) 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} mock.seal(lease_manager) 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} mock.seal(lease_manager) 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 mock.seal(lease_manager) 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 mock.seal(lease_manager) 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) python-blazarclient-3.0.0/blazarclient/tests/v1/shell_commands/test_hosts.py0000664000175000017500000001751413622743424027447 0ustar zuulzuul00000000000000# 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 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') python-blazarclient-3.0.0/blazarclient/tests/v1/__init__.py0000664000175000017500000000000013622743424023775 0ustar zuulzuul00000000000000python-blazarclient-3.0.0/blazarclient/tests/test_command.py0000664000175000017500000001005713622743424024402 0ustar zuulzuul00000000000000# 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 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): client = self.command.get_client() self.assertEqual(self.app.client, 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, []) python-blazarclient-3.0.0/blazarclient/tests/__init__.py0000664000175000017500000000162213622743424023462 0ustar zuulzuul00000000000000 # 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 python-blazarclient-3.0.0/blazarclient/i18n.py0000664000175000017500000000153113622743424021337 0ustar zuulzuul00000000000000# 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 python-blazarclient-3.0.0/blazarclient/shell.py0000664000175000017500000004643313622743424021701 0ustar zuulzuul00000000000000# 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 """ from __future__ import print_function import argparse import logging import os import sys from cliff import app from cliff import commandmanager from keystoneauth1 import identity from keystoneauth1 import session from oslo_utils import encodeutils import six from blazarclient import client as blazar_client from blazarclient import exception from blazarclient import utils 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, 'floatingip-list': floatingips.ListFloatingIPs, 'floatingip-show': floatingips.ShowFloatingIP, 'floatingip-create': floatingips.CreateFloatingIP, 'floatingip-delete': floatingips.DeleteFloatingIP, } 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) parser.add_argument( '--os-auth-strategy', metavar='', default=env('OS_AUTH_STRATEGY', default='keystone'), help='Authentication strategy (Env: OS_AUTH_STRATEGY' ', default keystone). For now, any other value will' ' disable the authentication') parser.add_argument( '--os_auth_strategy', help=argparse.SUPPRESS) parser.add_argument( '--os-auth-url', metavar='', default=env('OS_AUTH_URL'), help='Authentication URL (Env: OS_AUTH_URL)') parser.add_argument( '--os_auth_url', help=argparse.SUPPRESS) parser.add_argument( '--os-project-name', metavar='', default=env('OS_PROJECT_NAME'), help='Authentication project name (Env: OS_PROJECT_NAME)') parser.add_argument( '--os_project_name', help=argparse.SUPPRESS) parser.add_argument( '--os-project-id', metavar='', default=env('OS_PROJECT_ID'), help='Authentication project ID (Env: OS_PROJECT_ID)') parser.add_argument( '--os_project_id', help=argparse.SUPPRESS) parser.add_argument( '--os-project-domain-name', metavar='', default=env('OS_PROJECT_DOMAIN_NAME'), help='Authentication project domain name ' '(Env: OS_PROJECT_DOMAIN_NAME)') parser.add_argument( '--os_project_domain_name', help=argparse.SUPPRESS) parser.add_argument( '--os-project-domain-id', metavar='', default=env('OS_PROJECT_DOMAIN_ID'), help='Authentication project domain ID ' '(Env: OS_PROJECT_DOMAIN_ID)') parser.add_argument( '--os_project_domain_id', help=argparse.SUPPRESS) parser.add_argument( '--os-tenant-name', metavar='', default=env('OS_TENANT_NAME'), help='Authentication tenant name (Env: OS_TENANT_NAME)') parser.add_argument( '--os_tenant_name', help=argparse.SUPPRESS) parser.add_argument( '--os-tenant-id', metavar='', default=env('OS_TENANT_ID'), help='Authentication tenant name (Env: OS_TENANT_ID)') parser.add_argument( '--os-username', metavar='', default=utils.env('OS_USERNAME'), help='Authentication username (Env: OS_USERNAME)') parser.add_argument( '--os_username', help=argparse.SUPPRESS) parser.add_argument( '--os-user-domain-name', metavar='', default=env('OS_USER_DOMAIN_NAME'), help='Authentication user domain name (Env: OS_USER_DOMAIN_NAME)') parser.add_argument( '--os_user_domain_name', help=argparse.SUPPRESS) parser.add_argument( '--os-user-domain-id', metavar='', default=env('OS_USER_DOMAIN_ID'), help='Authentication user domain ID (Env: OS_USER_DOMAIN_ID)') parser.add_argument( '--os_user_domain_id', help=argparse.SUPPRESS) parser.add_argument( '--os-password', metavar='', default=utils.env('OS_PASSWORD'), help='Authentication password (Env: OS_PASSWORD)') parser.add_argument( '--os_password', help=argparse.SUPPRESS) parser.add_argument( '--os-region-name', metavar='', default=env('OS_REGION_NAME'), help='Authentication region name (Env: OS_REGION_NAME)') parser.add_argument( '--os_region_name', help=argparse.SUPPRESS) parser.add_argument( '--os-token', metavar='', default=env('OS_TOKEN'), help='Defaults to env[OS_TOKEN]') parser.add_argument( '--os_token', help=argparse.SUPPRESS) parser.add_argument( '--service-type', metavar='', default=env('BLAZAR_SERVICE_TYPE', default='reservation'), help='Defaults to env[BLAZAR_SERVICE_TYPE] or reservation.') parser.add_argument( '--endpoint-type', metavar='', default=env('OS_ENDPOINT_TYPE', default='publicURL'), help='Defaults to env[OS_ENDPOINT_TYPE] or publicURL.') parser.add_argument( '--os-cacert', metavar='', default=env('OS_CACERT', default=None), help="Specify a CA bundle file to use in " "verifying a TLS (https) server certificate. " "Defaults to env[OS_CACERT]") parser.add_argument( '--insecure', action='store_true', default=env('BLAZARCLIENT_INSECURE', default=False), help="Explicitly allow blazarclient to perform \"insecure\" " "SSL (https) requests. The server's certificate will " "not be verified against any certificate authorities. " "This option should be used with caution.") 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 """ 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(six.text_type(err)) raise else: self.log.error(six.text_type(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(six.text_type(err)) else: self.log.error(six.text_type(err)) try: self.clean_up(cmd, result, err) except Exception as err2: if self.options.debug: self.log.exception(six.text_type(err2)) else: self.log.error('Could not clean up: %s', six.text_type(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(six.text_type(err3)) else: self.log.error('Could not clean up: %s', six.text_type(err3)) return result def authenticate_user(self): """Authenticate user and set client by using passed params.""" if self.options.os_token: auth = identity.Token( auth_url=self.options.os_auth_url, token=self.options.os_token, tenant_id=self.options.os_tenant_id, tenant_name=self.options.os_tenant_name, project_id=self.options.os_project_id, project_name=self.options.os_project_name, project_domain_id=self.options.os_project_domain_id, project_domain_name=self.options.os_project_domain_name ) else: auth = identity.Password( auth_url=self.options.os_auth_url, username=self.options.os_username, tenant_id=self.options.os_tenant_id, tenant_name=self.options.os_tenant_name, password=self.options.os_password, project_id=self.options.os_project_id, project_name=self.options.os_project_name, project_domain_id=self.options.os_project_domain_id, project_domain_name=self.options.os_project_domain_name, user_domain_id=self.options.os_user_domain_id, user_domain_name=self.options.os_user_domain_name ) sess = session.Session( auth=auth, verify=(self.options.os_cacert or not self.options.insecure) ) self.client = blazar_client.Client( self.options.os_reservation_api_version, session=sess, service_type=self.options.service_type, interface=self.options.endpoint_type, 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', six.text_type(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(six.text_type(e)) return 1 if __name__ == "__main__": sys.exit(main(sys.argv[1:])) python-blazarclient-3.0.0/releasenotes/0000775000175000017500000000000013622743472020230 5ustar zuulzuul00000000000000python-blazarclient-3.0.0/releasenotes/source/0000775000175000017500000000000013622743472021530 5ustar zuulzuul00000000000000python-blazarclient-3.0.0/releasenotes/source/_static/0000775000175000017500000000000013622743472023156 5ustar zuulzuul00000000000000python-blazarclient-3.0.0/releasenotes/source/_static/.placeholder0000664000175000017500000000000013622743424025424 0ustar zuulzuul00000000000000python-blazarclient-3.0.0/releasenotes/source/rocky.rst0000664000175000017500000000022113622743424023401 0ustar zuulzuul00000000000000=================================== Rocky Series Release Notes =================================== .. release-notes:: :branch: stable/rocky python-blazarclient-3.0.0/releasenotes/source/stein.rst0000664000175000017500000000022113622743424023374 0ustar zuulzuul00000000000000=================================== Stein Series Release Notes =================================== .. release-notes:: :branch: stable/stein python-blazarclient-3.0.0/releasenotes/source/conf.py0000664000175000017500000001301413622743424023023 0ustar zuulzuul00000000000000# -*- coding: utf-8 -*- # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # # 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 = u'Blazar Client Release Notes' copyright = u'2018, Blazar Developers' author = u'Blazar Developers' # The short X.Y version version = u'' # The full version, including alpha/beta/rc tags release = u'' # -- 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 repository_name = 'openstack/python-blazarclient' bug_project = 'blazar' 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 = 'sphinx' # -- 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', u'Blazar Client Release Notes', u'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', u'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', u'Blazar Client Release Notes', author, 'BlazarClientReleaseNotes', 'Reservation service client.', 'Miscellaneous'), ] # -- Options for Internationalization output ------------------------------ locale_dirs = ['locale/'] python-blazarclient-3.0.0/releasenotes/source/unreleased.rst0000664000175000017500000000016013622743424024403 0ustar zuulzuul00000000000000============================== Current Series Release Notes ============================== .. release-notes:: python-blazarclient-3.0.0/releasenotes/source/_templates/0000775000175000017500000000000013622743472023665 5ustar zuulzuul00000000000000python-blazarclient-3.0.0/releasenotes/source/_templates/.placeholder0000664000175000017500000000000013622743424026133 0ustar zuulzuul00000000000000python-blazarclient-3.0.0/releasenotes/source/index.rst0000664000175000017500000000024113622743424023363 0ustar zuulzuul00000000000000============================= Blazar Client Release Notes ============================= .. toctree:: :maxdepth: 1 unreleased train stein rocky python-blazarclient-3.0.0/releasenotes/source/train.rst0000664000175000017500000000017613622743424023400 0ustar zuulzuul00000000000000========================== Train Series Release Notes ========================== .. release-notes:: :branch: stable/train python-blazarclient-3.0.0/releasenotes/notes/0000775000175000017500000000000013622743472021360 5ustar zuulzuul00000000000000python-blazarclient-3.0.0/releasenotes/notes/floatingip-support-d184a565f324d31b.yaml0000664000175000017500000000054213622743424030240 0ustar zuulzuul00000000000000--- 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. python-blazarclient-3.0.0/releasenotes/notes/drop-python2-c3c1601e92a9b87a.yaml0000664000175000017500000000011613622743424027016 0ustar zuulzuul00000000000000--- upgrade: - | Python 2 is no longer supported. Python 3 is required. python-blazarclient-3.0.0/releasenotes/notes/floatingip-reservation-update-5823a21516135f17.yaml0000664000175000017500000000040613622743424032122 0ustar zuulzuul00000000000000--- 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. python-blazarclient-3.0.0/releasenotes/notes/bug-1783296-set-start-date-to-now-e329a6923c11432f.yaml0000664000175000017500000000155713622743424032020 0ustar zuulzuul00000000000000--- 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 `_. python-blazarclient-3.0.0/releasenotes/notes/.placeholder0000664000175000017500000000000013622743424023626 0ustar zuulzuul00000000000000python-blazarclient-3.0.0/releasenotes/notes/bug-1777548-6b5c770abc6ac360.yaml0000664000175000017500000000107013622743424026144 0ustar zuulzuul00000000000000--- 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 python-blazarclient-3.0.0/releasenotes/notes/respect-selected-region-a409773f851ccb47.yaml0000664000175000017500000000035713622743424031125 0ustar zuulzuul00000000000000--- 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. python-blazarclient-3.0.0/releasenotes/notes/parse-required-floatingips-f79f79d652e371ae.yaml0000664000175000017500000000077013622743424031752 0ustar zuulzuul00000000000000--- 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 `_. python-blazarclient-3.0.0/setup.cfg0000664000175000017500000000162313622743472017362 0ustar zuulzuul00000000000000[metadata] name = python-blazarclient summary = Client for OpenStack Reservation Service description-file = README.rst license = Apache Software License classifiers = Programming Language :: Python Programming Language :: Python :: 3 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 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 [build_sphinx] all_files = 1 build-dir = doc/build source-dir = doc/source [egg_info] tag_build = tag_date = 0 python-blazarclient-3.0.0/doc/0000775000175000017500000000000013622743472016304 5ustar zuulzuul00000000000000python-blazarclient-3.0.0/doc/requirements.txt0000664000175000017500000000050613622743424021566 0ustar zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. openstackdocstheme>=1.18.1 # Apache-2.0 reno>=2.5.0 # Apache-2.0 sphinx>=1.8.0,!=2.1.0;python_version>='3.4' # BSD python-blazarclient-3.0.0/.zuul.yaml0000664000175000017500000000024713622743424017500 0ustar zuulzuul00000000000000- project: templates: - check-requirements - openstack-lower-constraints-jobs - openstack-python3-ussuri-jobs - release-notes-jobs-python3 python-blazarclient-3.0.0/PKG-INFO0000664000175000017500000000377613622743472016651 0ustar zuulzuul00000000000000Metadata-Version: 1.1 Name: python-blazarclient Version: 3.0.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 :: 3 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 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 python-blazarclient-3.0.0/ChangeLog0000664000175000017500000001216613622743472017317 0ustar zuulzuul00000000000000CHANGES ======= 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 python-blazarclient-3.0.0/README.rst0000664000175000017500000000172513622743424017230 0ustar zuulzuul00000000000000======================== 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/