python-ironic-inspector-client-3.1.0/0000775000175100017510000000000013232476241017625 5ustar zuulzuul00000000000000python-ironic-inspector-client-3.1.0/PKG-INFO0000664000175100017510000000425413232476241020727 0ustar zuulzuul00000000000000Metadata-Version: 1.1 Name: python-ironic-inspector-client Version: 3.1.0 Summary: Python client for Ironic Inspector Home-page: https://launchpad.net/python-ironic-inspector-client Author: UNKNOWN Author-email: UNKNOWN License: Apache-2 Description-Content-Type: UNKNOWN Description: Ironic Inspector Client ======================= .. image:: https://governance.openstack.org/badges/python-ironic-inspector-client.svg :target: https://governance.openstack.org/reference/tags/index.html This is a client library and tool for `Ironic Inspector`_. * Free software: Apache license * Source: https://git.openstack.org/cgit/openstack/python-ironic-inspector-client * Documentation: https://docs.openstack.org/python-ironic-inspector-client/latest/ * Bugs: https://bugs.launchpad.net/python-ironic-inspector-client * Downloads: https://pypi.python.org/pypi/python-ironic-inspector-client Please follow usual OpenStack `Gerrit Workflow`_ to submit a patch, see `Inspector contributing guide`_ for more detail. Refer to the `HTTP API reference`_ for information on the **Ironic Inspector** HTTP API. .. _Gerrit Workflow: https://docs.openstack.org/infra/manual/developers.html#development-workflow .. _Ironic Inspector: https://pypi.python.org/pypi/ironic-inspector .. _Inspector contributing guide: https://docs.openstack.org/ironic-inspector/latest/contributor/index.html .. _HTTP API reference: https://docs.openstack.org/ironic-inspector/latest/user/http-api.html Platform: UNKNOWN Classifier: Environment :: Console Classifier: Environment :: OpenStack Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Information Technology Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 python-ironic-inspector-client-3.1.0/ChangeLog0000664000175100017510000001701313232476240021400 0ustar zuulzuul00000000000000CHANGES ======= 3.1.0 ----- * Updated from global requirements * Updated from global requirements * Updated from global requirements * Use the tempest plugin from openstack/ironic-tempest-plugin 3.0.0 ----- * Avoid tox\_install.sh for constraints support * Add missing test on session + explicit URL * Documentation clean up * Drop default URI, make either a session or inspector\_url required * Remove support for passing auth\_token to ClientV1 * Add missing python-openstackclient to test-requirements * Remove setting of version/release from releasenotes * Centralize list of irrelevant files * Updated from global requirements * Updated from global requirements * Using --option ARGUMENT * Use functional tox environment * Move legacy python-ironic-inspector-client jobs * Remove deprecated setting ipmi creds feature * Use default tox environments for docs and releasenotes * Remove deprecated ironic\_inspector\_client.client * Functional tests: pin \*inspector\* to HEAD * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Update Inspector docs links in README * Updated from global requirements * Update reno for stable/pike 2.0.0 ----- * Make recent release note more readable * Updated from global requirements * Updated from global requirements * Update the documentation link for doc migration * Introducing warning-is-error to docs * Introducing cli and contributor content * Updated from global requirements * Switch from oslosphinx to openstackdocstheme * Replace http with https * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Functional tests: pin \*inspector\* to HEAD * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Do not depend on python-openstackclient * Updated from global requirements * Remove log translations 1.12.0 ------ * Updated from global requirements * Updated from global requirements * Add new introspection commands for interface data including lldp * Updated from global requirements * Updated from global requirements * Remove support for py34 * Update reno for stable/ocata 1.11.0 ------ * Updated from global requirements * Clarify that node names can be used in addition to UUIDs * Deprecate setting IPMI credentials * Updated from global requirements * List introspection statuses support * Updated from global requirements * Pin functest-requirements to a specific SHA * Show team and repo badges on README * Updated from global requirements * Rework handling requirements for functional tests * Update to newer hacking * UUID, started\_at, finished\_at in the status * Updated from global requirements * Updated from global requirements * Updated from global requirements * Fix functional tests after recent API changes * Updated from global requirements * Bump hacking to 0.11.0 in test-requirements * Updated from global requirements * Updated from global requirements * Enable release notes translation * Updated from global requirements * Updated from global requirements * Updated from global requirements * Move documentation from README to Sphinx * Add simple Sphinx documentation * TrivialFix: Remove logging import unused * Add oslotest to test-requirements * Update reno for stable/newton 1.9.0 ----- * Sync tools/tox\_install.sh * Using assertIsNone() is preferred over assertIs(None,..) * Switch to osc-lib instead of cliff * Add functional tests for CLI * Updated from global requirements * Increase verbosity for functional tests * Add functional test for wait\_for\_finish * Use constraints for all the things * Fix functional tests broken by the latest refactoring * Deprecate global functions in favor of ClientV1 methods * Updated from global requirements * Updated from global requirements * Add Python 3.5 tox env and detailed setup.cfg classifiers * Updated from global requirements * Add a test dependency on requests-mock 1.8.0 ----- * Updated from global requirements * Print rule import results * Updated from global requirements * Use osc-lib instead of openstackclient * Updated from global requirements * Updated from global requirements * Switch to keystoneauth * Bump max api version to 1.6 * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements 1.7.0 ----- * Updated from global requirements * Bump supported API version to 1.5 * Introspection on stored data * Updated from global requirements * Update reno for stable/mitaka * Updated from global requirements 1.5.0 ----- * Bump MAX\_API\_VERSION to actually supported 1.3 * Try to fetch inspector URL from the service catalog * Updated from global requirements * Update README: \`start\` cli doesn't rely on Ironic * Updated from global requirements * Introduce command to abort introspection * Updated from global requirements * Add a missing unit test on 'introspection status' command * Update --wait release note with an upgrade notice * Set MAX\_API\_VERSION to actually supported 1.2 * Updated from global requirements * Add --wait flag to 'introspection start' to wait for results * Updated from global requirements * Switch to accepting keystone session objects in client constructor 1.4.0 ----- * Updated from global requirements * Implement 'introspection data save' command * Stop relying on deprecated and removed support for maintenance mode in tests * Put py34 first in the env order of tox * Removes MANIFEST.in as it is not needed explicitely by PBR * Updated from global requirements * Updated from global requirements * Updated from global requirements * Fixed links to the new ironic-inspector documentation 1.3.0 ----- * Updated from global requirements * Add missing release notes * Use Reno for release notes management * Allow several UUID's in 'introspection start' * Updated from global requirements * Updated from global requirements * Updated from global requirements * Copy gitignore file from ironic-inspector * Stop assuming that all shell unit tests are for rules API * Support for getting introspection data in Python library * Updated from global requirements 1.2.0 ----- * Add a simple 'introspection rule show' command * Support for introspection rules * Fix func test job broken by Inspector func tests changes * Add a proper client object 1.1.0 ----- * Updated from global requirements * Make our README friendly to OpenStack release-tools * Updated from global requirements * Make sure we expose all API elements in the top-level package * Drop comment about changing functional tests to use released inspector * Fix error message for unsupported API version * Updated from global requirements * Updated from global requirements * Implement optional API versioning * Create own functional tests for the client * Updated from global requirements * Updated from global requirements * Updated from global requirements * Use released ironic-inspector for functional testing * Don't repeat requirements in tox.ini * Add functional test * Updated from global requirements * Change to Capital letters 1.0.1 ----- * Explicitly depend on pbr * Add venv environment for tox * Use pbr without explicit version 1.0.0 ----- * Manual update from global requirements * Switch to pbr * Basic support for API versions * After-moving code reorganization * Sync remaining changes from ironic-inspector tree * Add gitreview file * Initial import python-ironic-inspector-client-3.1.0/README.rst0000666000175100017510000000224213232475743021324 0ustar zuulzuul00000000000000Ironic Inspector Client ======================= .. image:: https://governance.openstack.org/badges/python-ironic-inspector-client.svg :target: https://governance.openstack.org/reference/tags/index.html This is a client library and tool for `Ironic Inspector`_. * Free software: Apache license * Source: https://git.openstack.org/cgit/openstack/python-ironic-inspector-client * Documentation: https://docs.openstack.org/python-ironic-inspector-client/latest/ * Bugs: https://bugs.launchpad.net/python-ironic-inspector-client * Downloads: https://pypi.python.org/pypi/python-ironic-inspector-client Please follow usual OpenStack `Gerrit Workflow`_ to submit a patch, see `Inspector contributing guide`_ for more detail. Refer to the `HTTP API reference`_ for information on the **Ironic Inspector** HTTP API. .. _Gerrit Workflow: https://docs.openstack.org/infra/manual/developers.html#development-workflow .. _Ironic Inspector: https://pypi.python.org/pypi/ironic-inspector .. _Inspector contributing guide: https://docs.openstack.org/ironic-inspector/latest/contributor/index.html .. _HTTP API reference: https://docs.openstack.org/ironic-inspector/latest/user/http-api.html python-ironic-inspector-client-3.1.0/LICENSE0000666000175100017510000002613613232475743020652 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. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. python-ironic-inspector-client-3.1.0/tox.ini0000666000175100017510000000260513232475743021153 0ustar zuulzuul00000000000000[tox] envlist = py35,py27,pep8,functional [testenv] install_command = pip install {opts} {packages} usedevelop = True deps = -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = coverage run --branch --include "ironic_inspector_client*" -m unittest discover ironic_inspector_client.test coverage report -m --fail-under 90 setenv = PYTHONDONTWRITEBYTECODE=1 [testenv:pep8] basepython = python2.7 commands = flake8 ironic_inspector_client doc8 README.rst doc/source [testenv:functional] basepython = python2.7 deps = {[testenv]deps} -r{toxinidir}/functest-requirements.txt commands = python -m ironic_inspector_client.test.functional [testenv:func] # Replaced in CI with "functional" environment but kept here as a # backwards-compatibility shim for transition basepython = python2.7 deps = {[testenv:functional]deps} commands = {[testenv:functional]commands} [testenv:venv] commands = {posargs} [testenv:releasenotes] commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [testenv:docs] setenv = PYTHONHASHSEED=0 sitepackages = False commands = python setup.py build_sphinx [flake8] max-complexity=15 [hacking] import_exceptions = ironic_inspector_client.common.i18n python-ironic-inspector-client-3.1.0/functest-requirements.txt0000666000175100017510000000047513232475743024760 0ustar zuulzuul00000000000000# NOTE(jroll) these are pinned to the same SHA, update when needed. git+git://git.openstack.org/openstack/ironic-inspector@f02eda0315cb21b737d2a1f982b389ed2291617c#egg=ironic-inspector -r https://git.openstack.org/cgit/openstack/ironic-inspector/plain/test-requirements.txt?h=f02eda0315cb21b737d2a1f982b389ed2291617c python-ironic-inspector-client-3.1.0/setup.cfg0000666000175100017510000000437713232476241021463 0ustar zuulzuul00000000000000[metadata] name = python-ironic-inspector-client summary = Python client for Ironic Inspector description-file = README.rst home-page = https://launchpad.net/python-ironic-inspector-client license = Apache-2 classifier = Environment :: Console Environment :: OpenStack Intended Audience :: Developers Intended Audience :: Information Technology License :: OSI Approved :: Apache Software License Operating System :: OS Independent Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.5 [files] packages = ironic_inspector_client [entry_points] openstack.cli.extension = baremetal-introspection = ironic_inspector_client.shell openstack.baremetal_introspection.v1 = baremetal_introspection_start = ironic_inspector_client.shell:StartCommand baremetal_introspection_status = ironic_inspector_client.shell:StatusCommand baremetal_introspection_list = ironic_inspector_client.shell:StatusListCommand baremetal_introspection_reprocess = ironic_inspector_client.shell:ReprocessCommand baremetal_introspection_abort = ironic_inspector_client.shell:AbortCommand baremetal_introspection_data_save = ironic_inspector_client.shell:DataSaveCommand baremetal_introspection_rule_import = ironic_inspector_client.shell:RuleImportCommand baremetal_introspection_rule_list = ironic_inspector_client.shell:RuleListCommand baremetal_introspection_rule_show = ironic_inspector_client.shell:RuleShowCommand baremetal_introspection_rule_delete = ironic_inspector_client.shell:RuleDeleteCommand baremetal_introspection_rule_purge = ironic_inspector_client.shell:RulePurgeCommand baremetal_introspection_interface_list = ironic_inspector_client.shell:InterfaceListCommand baremetal_introspection_interface_show = ironic_inspector_client.shell:InterfaceShowCommand [pbr] autodoc_index_modules = True autodoc_exclude_modules = ironic_inspector_client.test.* ironic_inspector_client.common.i18n ironic_inspector_client.shell warnerrors = True api_doc_dir = reference/api [build_sphinx] all_files = 1 build-dir = doc/build source-dir = doc/source warning-is-error = 1 [extras] cli = python-openstackclient>=3.12.0 # Apache-2.0 [egg_info] tag_build = tag_date = 0 python-ironic-inspector-client-3.1.0/setup.py0000666000175100017510000000200613232475743021345 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-ironic-inspector-client-3.1.0/ironic_inspector_client/0000775000175100017510000000000013232476241024534 5ustar zuulzuul00000000000000python-ironic-inspector-client-3.1.0/ironic_inspector_client/version.py0000666000175100017510000000127313232475743026606 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import pbr.version version_info = pbr.version.VersionInfo('python-ironic-inspector-client') """Installed package version.""" python-ironic-inspector-client-3.1.0/ironic_inspector_client/test/0000775000175100017510000000000013232476241025513 5ustar zuulzuul00000000000000python-ironic-inspector-client-3.1.0/ironic_inspector_client/test/test_init.py0000666000175100017510000000217713232475743030106 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import types import unittest import ironic_inspector_client class TestExposedAPI(unittest.TestCase): def test_only_client_all_exposed(self): exposed = {x for x in dir(ironic_inspector_client) if not x.startswith('__') and not isinstance(getattr(ironic_inspector_client, x), types.ModuleType)} self.assertEqual({'ClientV1', 'ClientError', 'EndpointNotFound', 'VersionNotSupported', 'MAX_API_VERSION', 'DEFAULT_API_VERSION'}, exposed) python-ironic-inspector-client-3.1.0/ironic_inspector_client/test/test_common_http.py0000666000175100017510000001600613232475743031466 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import json import unittest from keystoneauth1 import exceptions from keystoneauth1 import session import mock from ironic_inspector_client.common import http class TestCheckVersion(unittest.TestCase): @mock.patch.object(http.BaseClient, 'server_api_versions', lambda *args, **kwargs: ((1, 0), (1, 99))) def _check(self, version): cli = http.BaseClient(1, inspector_url='http://127.0.0.1:5050') return cli._check_api_version(version) def test_tuple(self): self.assertEqual((1, 0), self._check((1, 0))) def test_small_tuple(self): self.assertEqual((1, 0), self._check((1,))) def test_int(self): self.assertEqual((1, 0), self._check(1)) def test_str(self): self.assertEqual((1, 0), self._check("1.0")) def test_invalid_tuple(self): self.assertRaises(TypeError, self._check, (1, "x")) self.assertRaises(ValueError, self._check, (1, 2, 3)) def test_invalid_str(self): self.assertRaises(ValueError, self._check, "a.b") self.assertRaises(ValueError, self._check, "1.2.3") self.assertRaises(ValueError, self._check, "foo") def test_unsupported(self): self.assertRaises(http.VersionNotSupported, self._check, (99, 42)) FAKE_HEADERS = { http._MIN_VERSION_HEADER: '1.0', http._MAX_VERSION_HEADER: '1.9' } @mock.patch.object(session.Session, 'get', autospec=True, **{'return_value.status_code': 200, 'return_value.headers': FAKE_HEADERS}) class TestServerApiVersions(unittest.TestCase): def _check(self, current=1): return http.BaseClient( api_version=current, inspector_url='http://127.0.0.1:5050').server_api_versions() def test_no_headers(self, mock_get): mock_get.return_value.headers = {} minv, maxv = self._check() self.assertEqual((1, 0), minv) self.assertEqual((1, 0), maxv) def test_with_headers(self, mock_get): mock_get.return_value.headers = { 'X-OpenStack-Ironic-Inspector-API-Minimum-Version': '1.1', 'X-OpenStack-Ironic-Inspector-API-Maximum-Version': '1.42', } minv, maxv = self._check(current=(1, 2)) self.assertEqual((1, 1), minv) self.assertEqual((1, 42), maxv) def test_with_404(self, mock_get): mock_get.return_value.status_code = 404 mock_get.return_value.headers = {} minv, maxv = self._check() self.assertEqual((1, 0), minv) self.assertEqual((1, 0), maxv) def test_with_other_error(self, mock_get): mock_get.return_value.status_code = 500 mock_get.return_value.headers = {} self.assertRaises(http.ClientError, self._check) class TestRequest(unittest.TestCase): base_url = 'http://127.0.0.1:5050/v1' def setUp(self): super(TestRequest, self).setUp() self.headers = {http._VERSION_HEADER: '1.0'} self.session = mock.Mock(spec=session.Session) self.session.get_endpoint.return_value = self.base_url self.req = self.session.request self.req.return_value.status_code = 200 @mock.patch.object(http.BaseClient, 'server_api_versions', lambda self: ((1, 0), (1, 42))) def get_client(self, version=1, inspector_url=None, use_session=True): if use_session: return http.BaseClient(version, session=self.session, inspector_url=inspector_url) else: return http.BaseClient(version, inspector_url=inspector_url) def test_ok(self): res = self.get_client().request('get', '/foo/bar') self.assertIs(self.req.return_value, res) self.req.assert_called_once_with(self.base_url + '/foo/bar', 'get', raise_exc=False, headers=self.headers) self.session.get_endpoint.assert_called_once_with( service_type='baremetal-introspection', interface=None, region_name=None) def test_no_endpoint(self): self.session.get_endpoint.return_value = None self.assertRaises(http.EndpointNotFound, self.get_client) self.session.get_endpoint.assert_called_once_with( service_type='baremetal-introspection', interface=None, region_name=None) def test_endpoint_not_found(self): self.session.get_endpoint.side_effect = exceptions.EndpointNotFound() self.assertRaises(http.EndpointNotFound, self.get_client) self.session.get_endpoint.assert_called_once_with( service_type='baremetal-introspection', interface=None, region_name=None) @mock.patch.object(session.Session, 'request', autospec=True, **{'return_value.status_code': 200}) def test_ok_no_auth(self, mock_req): res = self.get_client( use_session=False, inspector_url='http://some/host').request('get', '/foo/bar') self.assertIs(mock_req.return_value, res) mock_req.assert_called_once_with(mock.ANY, 'http://some/host/v1/foo/bar', 'get', raise_exc=False, headers=self.headers) def test_ok_with_session_and_url(self): res = self.get_client( use_session=True, inspector_url='http://some/host').request('get', '/foo/bar') self.assertIs(self.req.return_value, res) self.req.assert_called_once_with('http://some/host/v1/foo/bar', 'get', raise_exc=False, headers=self.headers) def test_explicit_version(self): res = self.get_client(version='1.2').request('get', '/foo/bar') self.assertIs(self.req.return_value, res) self.headers[http._VERSION_HEADER] = '1.2' self.req.assert_called_once_with(self.base_url + '/foo/bar', 'get', raise_exc=False, headers=self.headers) def test_error(self): self.req.return_value.status_code = 400 self.req.return_value.content = json.dumps( {'error': {'message': 'boom'}}).encode('utf-8') self.assertRaisesRegexp(http.ClientError, 'boom', self.get_client().request, 'get', 'url') def test_error_discoverd_style(self): self.req.return_value.status_code = 400 self.req.return_value.content = b'boom' self.assertRaisesRegexp(http.ClientError, 'boom', self.get_client().request, 'get', 'url') python-ironic-inspector-client-3.1.0/ironic_inspector_client/test/test_v1.py0000666000175100017510000004052313232475743027466 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from collections import OrderedDict import six import unittest from keystoneauth1 import session import mock from oslo_utils import netutils from oslo_utils import uuidutils import ironic_inspector_client from ironic_inspector_client.common import http from ironic_inspector_client import v1 FAKE_HEADERS = { http._MIN_VERSION_HEADER: '1.0', http._MAX_VERSION_HEADER: '1.9' } @mock.patch.object(session.Session, 'get', return_value=mock.Mock(headers=FAKE_HEADERS, status_code=200)) class TestInit(unittest.TestCase): my_ip = 'http://' + netutils.get_my_ipv4() + ':5050' def get_client(self, **kwargs): kwargs.setdefault('inspector_url', self.my_ip) return ironic_inspector_client.ClientV1(**kwargs) def test_ok(self, mock_get): self.get_client() mock_get.assert_called_once_with(self.my_ip, authenticated=False, raise_exc=False) def test_explicit_version(self, mock_get): self.get_client(api_version=(1, 2)) self.get_client(api_version=1) self.get_client(api_version='1.3') def test_unsupported_version(self, mock_get): self.assertRaises(ironic_inspector_client.VersionNotSupported, self.get_client, api_version=(1, 99)) self.assertRaises(ironic_inspector_client.VersionNotSupported, self.get_client, api_version=2) self.assertRaises(ironic_inspector_client.VersionNotSupported, self.get_client, api_version='1.42') def test_explicit_url(self, mock_get): self.get_client(inspector_url='http://host:port') mock_get.assert_called_once_with('http://host:port', authenticated=False, raise_exc=False) class BaseTest(unittest.TestCase): def setUp(self): super(BaseTest, self).setUp() self.uuid = uuidutils.generate_uuid() self.my_ip = 'http://' + netutils.get_my_ipv4() + ':5050' @mock.patch.object(http.BaseClient, 'server_api_versions', lambda self: ((1, 0), (1, 99))) def get_client(self, **kwargs): kwargs.setdefault('inspector_url', self.my_ip) return ironic_inspector_client.ClientV1(**kwargs) @mock.patch.object(http.BaseClient, 'request') class TestIntrospect(BaseTest): def test(self, mock_req): self.get_client().introspect(self.uuid) mock_req.assert_called_once_with( 'post', '/introspection/%s' % self.uuid) def test_invalid_input(self, mock_req): self.assertRaises(TypeError, self.get_client().introspect, 42) @mock.patch.object(http.BaseClient, 'request') class TestReprocess(BaseTest): def test(self, mock_req): self.get_client().reprocess(self.uuid) mock_req.assert_called_once_with( 'post', '/introspection/%s/data/unprocessed' % self.uuid ) def test_invalid_input(self, mock_req): self.assertRaises(TypeError, self.get_client().reprocess, 42) self.assertFalse(mock_req.called) @mock.patch.object(http.BaseClient, 'request') class TestGetStatus(BaseTest): def test(self, mock_req): mock_req.return_value.json.return_value = 'json' self.get_client().get_status(self.uuid) mock_req.assert_called_once_with( 'get', '/introspection/%s' % self.uuid) def test_invalid_input(self, _): self.assertRaises(TypeError, self.get_client().get_status, 42) @mock.patch.object(http.BaseClient, 'request') class TestListStatuses(BaseTest): def test_default(self, mock_req): mock_req.return_value.json.return_value = { 'introspection': None } params = { 'marker': None, 'limit': None } self.get_client().list_statuses() mock_req.assert_called_once_with('get', '/introspection', params=params) def test_nondefault(self, mock_req): mock_req.return_value.json.return_value = { 'introspection': None } params = { 'marker': 'uuid', 'limit': 42 } self.get_client().list_statuses(**params) mock_req.assert_called_once_with('get', '/introspection', params=params) def test_invalid_marker(self, _): six.assertRaisesRegex(self, TypeError, 'Expected a string value.*', self.get_client().list_statuses, marker=42) def test_invalid_limit(self, _): six.assertRaisesRegex(self, TypeError, 'Expected an integer.*', self.get_client().list_statuses, limit='42') @mock.patch.object(ironic_inspector_client.ClientV1, 'get_status', autospec=True) class TestWaitForFinish(BaseTest): def setUp(self): super(TestWaitForFinish, self).setUp() self.sleep = mock.Mock(spec=[]) def test_ok(self, mock_get_st): mock_get_st.side_effect = ( [{'finished': False, 'error': None}] * 5 + [{'finished': True, 'error': None}] ) res = self.get_client().wait_for_finish(['uuid1'], sleep_function=self.sleep) self.assertEqual({'uuid1': {'finished': True, 'error': None}}, res) self.sleep.assert_called_with(v1.DEFAULT_RETRY_INTERVAL) self.assertEqual(5, self.sleep.call_count) def test_timeout(self, mock_get_st): mock_get_st.return_value = {'finished': False, 'error': None} self.assertRaises(v1.WaitTimeoutError, self.get_client().wait_for_finish, ['uuid1'], sleep_function=self.sleep) self.sleep.assert_called_with(v1.DEFAULT_RETRY_INTERVAL) self.assertEqual(v1.DEFAULT_MAX_RETRIES, self.sleep.call_count) def test_multiple(self, mock_get_st): mock_get_st.side_effect = [ # attempt 1 {'finished': False, 'error': None}, {'finished': False, 'error': None}, {'finished': False, 'error': None}, # attempt 2 {'finished': True, 'error': None}, {'finished': False, 'error': None}, {'finished': True, 'error': 'boom'}, # attempt 3 (only uuid2) {'finished': True, 'error': None}, ] res = self.get_client().wait_for_finish(['uuid1', 'uuid2', 'uuid3'], sleep_function=self.sleep) self.assertEqual({'uuid1': {'finished': True, 'error': None}, 'uuid2': {'finished': True, 'error': None}, 'uuid3': {'finished': True, 'error': 'boom'}}, res) self.sleep.assert_called_with(v1.DEFAULT_RETRY_INTERVAL) self.assertEqual(2, self.sleep.call_count) @mock.patch.object(http.BaseClient, 'request') class TestGetData(BaseTest): def test_json(self, mock_req): mock_req.return_value.json.return_value = 'json' self.assertEqual('json', self.get_client().get_data(self.uuid)) mock_req.assert_called_once_with( 'get', '/introspection/%s/data' % self.uuid) def test_raw(self, mock_req): mock_req.return_value.content = b'json' self.assertEqual(b'json', self.get_client().get_data(self.uuid, raw=True)) mock_req.assert_called_once_with( 'get', '/introspection/%s/data' % self.uuid) def test_invalid_input(self, _): self.assertRaises(TypeError, self.get_client().get_data, 42) @mock.patch.object(http.BaseClient, 'request') class TestRules(BaseTest): def get_rules(self, **kwargs): return self.get_client(**kwargs).rules def test_create(self, mock_req): self.get_rules().create([{'cond': 'cond'}], [{'act': 'act'}]) mock_req.assert_called_once_with( 'post', '/rules', json={'conditions': [{'cond': 'cond'}], 'actions': [{'act': 'act'}], 'uuid': None, 'description': None}) def test_create_all_fields(self, mock_req): self.get_rules().create([{'cond': 'cond'}], [{'act': 'act'}], uuid='u', description='d') mock_req.assert_called_once_with( 'post', '/rules', json={'conditions': [{'cond': 'cond'}], 'actions': [{'act': 'act'}], 'uuid': 'u', 'description': 'd'}) def test_create_invalid_input(self, mock_req): self.assertRaises(TypeError, self.get_rules().create, {}, [{'act': 'act'}]) self.assertRaises(TypeError, self.get_rules().create, [{'cond': 'cond'}], {}) self.assertRaises(TypeError, self.get_rules().create, [{'cond': 'cond'}], [{'act': 'act'}], uuid=42) self.assertFalse(mock_req.called) def test_from_json(self, mock_req): self.get_rules().from_json({'foo': 'bar'}) mock_req.assert_called_once_with( 'post', '/rules', json={'foo': 'bar'}) def test_get_all(self, mock_req): mock_req.return_value.json.return_value = {'rules': ['rules']} res = self.get_rules().get_all() self.assertEqual(['rules'], res) mock_req.assert_called_once_with('get', '/rules') def test_get(self, mock_req): mock_req.return_value.json.return_value = {'answer': 42} res = self.get_rules().get('uuid1') self.assertEqual({'answer': 42}, res) mock_req.assert_called_once_with('get', '/rules/uuid1') def test_get_invalid_input(self, mock_req): self.assertRaises(TypeError, self.get_rules().get, 42) self.assertFalse(mock_req.called) def test_delete(self, mock_req): self.get_rules().delete('uuid1') mock_req.assert_called_once_with('delete', '/rules/uuid1') def test_delete_invalid_input(self, mock_req): self.assertRaises(TypeError, self.get_rules().delete, 42) self.assertFalse(mock_req.called) def test_delete_all(self, mock_req): self.get_rules().delete_all() mock_req.assert_called_once_with('delete', '/rules') @mock.patch.object(http.BaseClient, 'request') class TestAbort(BaseTest): def test(self, mock_req): self.get_client().abort(self.uuid) mock_req.assert_called_once_with('post', '/introspection/%s/abort' % self.uuid) def test_invalid_input(self, _): self.assertRaises(TypeError, self.get_client().abort, 42) @mock.patch.object(http.BaseClient, 'request') class TestInterfaceApi(BaseTest): def setUp(self): super(TestInterfaceApi, self).setUp() self.inspector_db = { "all_interfaces": { 'em1': {'mac': "00:11:22:33:44:55", 'ip': "10.10.1.1", "lldp_processed": { "switch_chassis_id": "99:aa:bb:cc:dd:ff", "switch_port_id": "555", "switch_port_vlans": [{"id": 101, "name": "vlan101"}, {"id": 102, "name": "vlan102"}, {"id": 104, "name": "vlan104"}, {"id": 201, "name": "vlan201"}, {"id": 203, "name": "vlan203"}], "switch_port_mtu": 1514} }, 'em2': {'mac': "00:11:22:66:77:88", 'ip': "10.10.1.2", "lldp_processed": { "switch_chassis_id": "99:aa:bb:cc:dd:ff", "switch_port_id": "777", "switch_port_vlans": [{"id": 201, "name": "vlan201"}, {"id": 203, "name": "vlan203"}], "switch_port_mtu": 9216} }, 'em3': {'mac': "00:11:22:aa:bb:cc", 'ip': "10.10.1.2"} } } def test_all_interfaces(self, mock_req): mock_req.return_value.json.return_value = self.inspector_db fields = ['interface', 'mac', 'switch_chassis_id', 'switch_port_id', 'switch_port_vlans'] expected = [['em1', '00:11:22:33:44:55', '99:aa:bb:cc:dd:ff', '555', [{"id": 101, "name": "vlan101"}, {"id": 102, "name": "vlan102"}, {"id": 104, "name": "vlan104"}, {"id": 201, "name": "vlan201"}, {"id": 203, "name": "vlan203"}]], ['em2', '00:11:22:66:77:88', '99:aa:bb:cc:dd:ff', '777', [{"id": 201, "name": "vlan201"}, {"id": 203, "name": "vlan203"}]], ['em3', '00:11:22:aa:bb:cc', None, None, None]] actual = self.get_client().get_all_interface_data(self.uuid, fields) self.assertEqual(sorted(expected), sorted(actual)) # Change fields fields = ['interface', 'switch_port_mtu'] expected = [ ['em1', 1514], ['em2', 9216], ['em3', None]] actual = self.get_client().get_all_interface_data(self.uuid, fields) self.assertEqual(expected, sorted(actual)) def test_all_interfaces_filtered(self, mock_req): mock_req.return_value.json.return_value = self.inspector_db fields = ['interface', 'mac', 'switch_chassis_id', 'switch_port_id', 'switch_port_vlan_ids'] expected = [['em1', '00:11:22:33:44:55', '99:aa:bb:cc:dd:ff', '555', [101, 102, 104, 201, 203]]] # Filter on expected VLAN vlan = [104] actual = self.get_client().get_all_interface_data(self.uuid, fields, vlan=vlan) self.assertEqual(expected, actual) # VLANs don't match existing vlans vlan = [111, 555] actual = self.get_client().get_all_interface_data(self.uuid, fields, vlan=vlan) self.assertEqual([], actual) def test_one_interface(self, mock_req): mock_req.return_value.json.return_value = self.inspector_db # Note that a value for 'switch_foo' will not be found fields = ["node_ident", "interface", "mac", "switch_port_vlan_ids", "switch_chassis_id", "switch_port_id", "switch_port_mtu", "switch_port_vlans", "switch_foo"] expected_values = OrderedDict( [('node_ident', self.uuid), ('interface', "em1"), ('mac', "00:11:22:33:44:55"), ('switch_port_vlan_ids', [101, 102, 104, 201, 203]), ('switch_chassis_id', "99:aa:bb:cc:dd:ff"), ('switch_port_id', "555"), ('switch_port_mtu', 1514), ('switch_port_vlans', [{"id": 101, "name": "vlan101"}, {"id": 102, "name": "vlan102"}, {"id": 104, "name": "vlan104"}, {"id": 201, "name": "vlan201"}, {"id": 203, "name": "vlan203"}]), ("switch_foo", None)]) iface_dict = self.get_client().get_interface_data( self.uuid, "em1", fields) self.assertEqual(expected_values, iface_dict) # Test interface name not in 'all_interfaces' expected_values = OrderedDict() iface_dict = self.get_client().get_interface_data( self.uuid, "em55", fields) self.assertEqual(expected_values, iface_dict) python-ironic-inspector-client-3.1.0/ironic_inspector_client/test/functional.py0000666000175100017510000004563713232475743030256 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import eventlet eventlet.monkey_patch() import copy import json import mock import os import sys import tempfile import unittest from ironic_inspector.common import swift from ironic_inspector import introspection_state as istate from ironic_inspector.test import functional from keystoneauth1 import session as ks_session from keystoneauth1 import token_endpoint from oslo_concurrency import processutils import ironic_inspector_client as client from ironic_inspector_client import shell class TestV1PythonAPI(functional.Base): def setUp(self): super(TestV1PythonAPI, self).setUp() self.auth = token_endpoint.Token(endpoint='http://127.0.0.1:5050', token='token') self.session = ks_session.Session(self.auth) self.client = client.ClientV1(session=self.session) functional.cfg.CONF.set_override('store_data', 'none', 'processing') def my_status_index(self, statuses): my_status = self._fake_status() return statuses.index(my_status) def test_introspect_get_status(self): self.client.introspect(self.uuid) eventlet.greenthread.sleep(functional.DEFAULT_SLEEP) self.cli.node.set_power_state.assert_called_once_with(self.uuid, 'reboot') status = self.client.get_status(self.uuid) self.check_status(status, finished=False, state=istate.States.waiting) res = self.call_continue(self.data) self.assertEqual({'uuid': self.uuid}, res) eventlet.greenthread.sleep(functional.DEFAULT_SLEEP) self.assertCalledWithPatch(self.patch, self.cli.node.update) self.cli.port.create.assert_called_once_with( node_uuid=self.uuid, address='11:22:33:44:55:66', pxe_enabled=True, extra={}) status = self.client.get_status(self.uuid) self.check_status(status, finished=True, state=istate.States.finished) def test_introspect_list_statuses(self): self.client.introspect(self.uuid) eventlet.greenthread.sleep(functional.DEFAULT_SLEEP) self.cli.node.set_power_state.assert_called_once_with(self.uuid, 'reboot') statuses = self.client.list_statuses() my_status = statuses[self.my_status_index(statuses)] self.check_status(my_status, finished=False, state=istate.States.waiting) res = self.call_continue(self.data) self.assertEqual({'uuid': self.uuid}, res) eventlet.greenthread.sleep(functional.DEFAULT_SLEEP) self.assertCalledWithPatch(self.patch, self.cli.node.update) self.cli.port.create.assert_called_once_with( node_uuid=self.uuid, address='11:22:33:44:55:66', pxe_enabled=True, extra={}) statuses = self.client.list_statuses() my_status = statuses[self.my_status_index(statuses)] self.check_status(my_status, finished=True, state=istate.States.finished) def test_wait_for_finish(self): shared = [0] # mutable structure to hold number of retries def fake_waiter(delay): shared[0] += 1 if shared[0] == 2: # On the second wait simulate data arriving res = self.call_continue(self.data) self.assertEqual({'uuid': self.uuid}, res) elif shared[0] > 2: # Just wait afterwards eventlet.greenthread.sleep(delay) self.client.introspect(self.uuid) eventlet.greenthread.sleep(functional.DEFAULT_SLEEP) status = self.client.get_status(self.uuid) self.check_status(status, finished=False, state=istate.States.waiting) self.client.wait_for_finish([self.uuid], sleep_function=fake_waiter, retry_interval=functional.DEFAULT_SLEEP) status = self.client.get_status(self.uuid) self.check_status(status, finished=True, state=istate.States.finished) @mock.patch.object(swift, 'store_introspection_data', autospec=True) @mock.patch.object(swift, 'get_introspection_data', autospec=True) def test_reprocess_stored_introspection_data(self, get_mock, store_mock): functional.cfg.CONF.set_override('store_data', 'swift', 'processing') port_create_call = mock.call(node_uuid=self.uuid, address='11:22:33:44:55:66', pxe_enabled=True, extra={}) get_mock.return_value = json.dumps(self.data) # assert reprocessing doesn't work before introspection self.assertRaises(client.ClientError, self.client.reprocess, self.uuid) self.client.introspect(self.uuid) eventlet.greenthread.sleep(functional.DEFAULT_SLEEP) self.cli.node.set_power_state.assert_called_once_with(self.uuid, 'reboot') status = self.client.get_status(self.uuid) self.check_status(status, finished=False, state=istate.States.waiting) res = self.call_continue(self.data) self.assertEqual({'uuid': self.uuid}, res) eventlet.greenthread.sleep(functional.DEFAULT_SLEEP) status = self.client.get_status(self.uuid) self.check_status(status, finished=True, state=istate.States.finished) self.cli.port.create.assert_has_calls([port_create_call], any_order=True) self.assertFalse(get_mock.called) self.assertTrue(store_mock.called) res = self.client.reprocess(self.uuid) self.assertEqual(202, res.status_code) self.assertEqual('', res.text) eventlet.greenthread.sleep(functional.DEFAULT_SLEEP) self.check_status(status, finished=True, state=istate.States.finished) self.cli.port.create.assert_has_calls([port_create_call, port_create_call], any_order=True) self.assertTrue(get_mock.called) # incoming, processing, reapplying data self.assertEqual(3, store_mock.call_count) def test_abort_introspection(self): # assert abort doesn't work before introspect request self.assertRaises(client.ClientError, self.client.abort, self.uuid) self.client.introspect(self.uuid) eventlet.greenthread.sleep(functional.DEFAULT_SLEEP) self.cli.node.set_power_state.assert_called_once_with(self.uuid, 'reboot') status = self.client.get_status(self.uuid) self.check_status(status, finished=False, state=istate.States.waiting) res = self.client.abort(self.uuid) eventlet.greenthread.sleep(functional.DEFAULT_SLEEP) self.assertEqual(202, res.status_code) self.assertEqual('', res.text) status = self.client.get_status(self.uuid) self.check_status(status, finished=True, state=istate.States.error, error='Canceled by operator') # assert continue doesn't work after abort self.call_continue(self.data, expect_error=400) def test_api_versions(self): minv, maxv = self.client.server_api_versions() self.assertEqual((1, 0), minv) self.assertGreaterEqual(maxv, (1, 0)) self.assertLess(maxv, (2, 0)) def test_client_init(self): self.assertRaises(client.VersionNotSupported, client.ClientV1, session=self.session, api_version=(1, 999)) self.assertRaises(client.VersionNotSupported, client.ClientV1, session=self.session, api_version=2) self.assertTrue(client.ClientV1( api_version=1, session=self.session).server_api_versions()) self.assertTrue(client.ClientV1( api_version='1.0', session=self.session).server_api_versions()) self.assertTrue(client.ClientV1( api_version=(1, 0), session=self.session).server_api_versions()) self.assertTrue( client.ClientV1(inspector_url='http://127.0.0.1:5050') .server_api_versions()) self.assertTrue( client.ClientV1(inspector_url='http://127.0.0.1:5050/v1') .server_api_versions()) def test_rules_api(self): res = self.client.rules.get_all() self.assertEqual([], res) rule = {'conditions': [], 'actions': [{'action': 'fail', 'message': 'boom'}], 'description': 'Cool actions', 'uuid': self.uuid} res = self.client.rules.from_json(rule) self.assertEqual(self.uuid, res['uuid']) rule['links'] = res['links'] self.assertEqual(rule, res) res = self.client.rules.get(self.uuid) self.assertEqual(rule, res) res = self.client.rules.get_all() self.assertEqual(rule['links'], res[0].pop('links')) self.assertEqual([{'uuid': self.uuid, 'description': 'Cool actions'}], res) self.client.rules.delete(self.uuid) res = self.client.rules.get_all() self.assertEqual([], res) for _ in range(3): res = self.client.rules.create(conditions=rule['conditions'], actions=rule['actions'], description=rule['description']) self.assertTrue(res['uuid']) for key in ('conditions', 'actions', 'description'): self.assertEqual(rule[key], res[key]) res = self.client.rules.get_all() self.assertEqual(3, len(res)) self.client.rules.delete_all() res = self.client.rules.get_all() self.assertEqual([], res) self.assertRaises(client.ClientError, self.client.rules.get, self.uuid) self.assertRaises(client.ClientError, self.client.rules.delete, self.uuid) BASE_CMD = [os.path.join(sys.prefix, 'bin', 'openstack'), '--os-auth-type', 'token_endpoint', '--os-token', 'fake', '--os-url', 'http://127.0.0.1:5050'] class BaseCLITest(functional.Base): def openstack(self, cmd, expect_error=False, parse_json=False): real_cmd = BASE_CMD + cmd if parse_json: real_cmd += ['-f', 'json'] try: out, _err = processutils.execute(*real_cmd) except processutils.ProcessExecutionError as exc: if expect_error: return exc.stderr else: raise else: if expect_error: raise AssertionError('Command %s returned unexpected success' % cmd) elif parse_json: return json.loads(out) else: return out def run_cli(self, *cmd, **kwargs): return self.openstack(['baremetal', 'introspection'] + list(cmd), **kwargs) class TestCLI(BaseCLITest): def setup_lldp(self): functional.cfg.CONF.set_override('store_data', 'swift', 'processing') self.all_interfaces = { 'eth1': {'mac': self.macs[0], 'ip': self.ips[0], 'client_id': None, 'lldp_processed': {'switch_chassis_id': "11:22:33:aa:bb:cc", 'switch_port_vlans': [{"name": "vlan101", "id": 101}, {"name": "vlan102", "id": 102}, {"name": "vlan104", "id": 104}, {"name": "vlan201", "id": 201}, {"name": "vlan203", "id": 203}], 'switch_port_id': "554", 'switch_port_mtu': 1514}}, 'eth3': {'mac': self.macs[1], 'ip': None, 'client_id': None, 'lldp_processed': {'switch_chassis_id': "11:22:33:aa:bb:cc", 'switch_port_vlans': [{"name": "vlan101", "id": 101}, {"name": "vlan102", "id": 102}, {"name": "vlan104", "id": 106}], 'switch_port_id': "557", 'switch_port_mtu': 9216}} } self.data['all_interfaces'] = self.all_interfaces def _fake_status(self, **kwargs): # to remove the hidden fields hidden_status_items = shell.StatusCommand.hidden_status_items fake_status = super(TestCLI, self)._fake_status(**kwargs) fake_status = dict(item for item in fake_status.items() if item[0] not in hidden_status_items) return fake_status def test_cli_negative(self): err = self.run_cli('start', expect_error=True) self.assertIn('too few arguments', err) err = self.run_cli('status', expect_error=True) self.assertIn('too few arguments', err) err = self.run_cli('rule', 'show', 'uuid', expect_error=True) self.assertIn('not found', err) err = self.run_cli('rule', 'delete', 'uuid', expect_error=True) self.assertIn('not found', err) err = self.run_cli('interface', 'list', expect_error=True) self.assertIn('too few arguments', err) def test_introspect_get_status(self): self.run_cli('start', self.uuid) eventlet.greenthread.sleep(functional.DEFAULT_SLEEP) self.cli.node.set_power_state.assert_called_once_with(self.uuid, 'reboot') status = self.run_cli('status', self.uuid, parse_json=True) self.check_status(status, finished=False, state=istate.States.waiting) res = self.call_continue(self.data) self.assertEqual({'uuid': self.uuid}, res) eventlet.greenthread.sleep(functional.DEFAULT_SLEEP) self.assertCalledWithPatch(self.patch, self.cli.node.update) self.cli.port.create.assert_called_once_with( node_uuid=self.uuid, address='11:22:33:44:55:66', pxe_enabled=True, extra={}) status = self.run_cli('status', self.uuid, parse_json=True) self.check_status(status, finished=True, state=istate.States.finished) def test_rules_api(self): res = self.run_cli('rule', 'list', parse_json=True) self.assertEqual([], res) rule = {'conditions': [], 'actions': [{'action': 'fail', 'message': 'boom'}], 'description': 'Cool actions', 'uuid': self.uuid} with tempfile.NamedTemporaryFile() as fp: json.dump(rule, fp) fp.flush() res = self.run_cli('rule', 'import', fp.name, parse_json=True) self.assertEqual([{'UUID': self.uuid, 'Description': 'Cool actions'}], res) res = self.run_cli('rule', 'show', self.uuid, parse_json=True) self.assertEqual(rule, res) res = self.run_cli('rule', 'list', parse_json=True) self.assertEqual([{'UUID': self.uuid, 'Description': 'Cool actions'}], res) self.run_cli('rule', 'delete', self.uuid) res = self.run_cli('rule', 'list', parse_json=True) self.assertEqual([], res) with tempfile.NamedTemporaryFile() as fp: rule.pop('uuid') json.dump([rule, rule], fp) fp.flush() res = self.run_cli('rule', 'import', fp.name, parse_json=True) self.run_cli('rule', 'purge') res = self.run_cli('rule', 'list', parse_json=True) self.assertEqual([], res) @mock.patch.object(swift, 'get_introspection_data', autospec=True) def test_interface_list(self, get_mock): self.setup_lldp() get_mock.return_value = json.dumps(copy.deepcopy(self.data)) expected_eth1 = {u'Interface': u'eth1', u'MAC Address': u'11:22:33:44:55:66', u'Switch Chassis ID': u'11:22:33:aa:bb:cc', u'Switch Port ID': u'554', u'Switch Port VLAN IDs': [101, 102, 104, 201, 203]} expected_eth3 = {u'Interface': u'eth3', u'MAC Address': u'66:55:44:33:22:11', u'Switch Chassis ID': u'11:22:33:aa:bb:cc', u'Switch Port ID': u'557', u'Switch Port VLAN IDs': [101, 102, 106]} res = self.run_cli('interface', 'list', self.uuid, parse_json=True) res.sort() self.assertEqual(expected_eth1, res[0]) self.assertEqual(expected_eth3, res[1]) # Filter on vlan res = self.run_cli('interface', 'list', self.uuid, '--vlan', '106', parse_json=True) res.sort() self.assertEqual(expected_eth3, res[0]) # Select fields res = self.run_cli('interface', 'list', self.uuid, '--fields', 'switch_port_mtu', parse_json=True) res.sort() self.assertEqual({u'Switch Port MTU': 1514}, res[0]) self.assertEqual({u'Switch Port MTU': 9216}, res[1]) @mock.patch.object(swift, 'get_introspection_data', autospec=True) def test_interface_show(self, get_mock): self.setup_lldp() get_mock.return_value = json.dumps(copy.deepcopy(self.data)) res = self.run_cli('interface', 'show', self.uuid, "eth1", parse_json=True) expected = {u'interface': u'eth1', u'mac': u'11:22:33:44:55:66', u'switch_chassis_id': u'11:22:33:aa:bb:cc', u'switch_port_id': u'554', u'switch_port_mtu': 1514, u'switch_port_vlan_ids': [101, 102, 104, 201, 203], u'switch_port_vlans': [{u'id': 101, u'name': u'vlan101'}, {u'id': 102, u'name': u'vlan102'}, {u'id': 104, u'name': u'vlan104'}, {u'id': 201, u'name': u'vlan201'}, {u'id': 203, u'name': u'vlan203'}]} self.assertDictContainsSubset(expected, res) if __name__ == '__main__': with functional.mocked_server(): unittest.main(verbosity=2) python-ironic-inspector-client-3.1.0/ironic_inspector_client/test/__init__.py0000666000175100017510000000000013232475743027622 0ustar zuulzuul00000000000000python-ironic-inspector-client-3.1.0/ironic_inspector_client/test/test_shell.py0000666000175100017510000004573313232475743030257 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import sys from collections import OrderedDict import mock from osc_lib.tests import utils import six import tempfile from ironic_inspector_client import shell from ironic_inspector_client import v1 class BaseTest(utils.TestCommand): def setUp(self): super(BaseTest, self).setUp() self.client = mock.Mock(spec=v1.ClientV1) self.rules_api = mock.Mock(spec=v1.RulesAPI) self.client.rules = self.rules_api self.app.client_manager.baremetal_introspection = self.client class TestIntrospect(BaseTest): def test_introspect_one(self): arglist = ['uuid1'] verifylist = [('node', arglist)] cmd = shell.StartCommand(self.app, None) parsed_args = self.check_parser(cmd, arglist, verifylist) result = cmd.take_action(parsed_args) self.assertEqual((shell.StartCommand.COLUMNS, []), result) self.client.introspect.assert_called_once_with('uuid1') def test_introspect_many(self): arglist = ['uuid1', 'uuid2', 'uuid3'] verifylist = [('node', arglist)] cmd = shell.StartCommand(self.app, None) parsed_args = self.check_parser(cmd, arglist, verifylist) cmd.take_action(parsed_args) calls = [mock.call(node) for node in arglist] self.assertEqual(calls, self.client.introspect.call_args_list) def test_introspect_many_fails(self): arglist = ['uuid1', 'uuid2', 'uuid3'] verifylist = [('node', arglist)] self.client.introspect.side_effect = (None, RuntimeError()) cmd = shell.StartCommand(self.app, None) parsed_args = self.check_parser(cmd, arglist, verifylist) self.assertRaises(RuntimeError, cmd.take_action, parsed_args) calls = [mock.call(node) for node in arglist[:2]] self.assertEqual(calls, self.client.introspect.call_args_list) def test_reprocess(self): node = 'uuid1' arglist = [node] verifylist = [('node', node)] response_mock = mock.Mock(status_code=202, content=b'') self.client.reprocess.return_value = response_mock cmd = shell.ReprocessCommand(self.app, None) parsed_args = self.check_parser(cmd, arglist, verifylist) result = cmd.take_action(parsed_args) self.client.reprocess.assert_called_once_with(node) self.assertIsNone(result) def test_wait(self): nodes = ['uuid1', 'uuid2', 'uuid3'] arglist = ['--wait'] + nodes verifylist = [('node', nodes), ('wait', True)] self.client.wait_for_finish.return_value = { 'uuid1': {'finished': True, 'error': None}, 'uuid2': {'finished': True, 'error': 'boom'}, 'uuid3': {'finished': True, 'error': None}, } cmd = shell.StartCommand(self.app, None) parsed_args = self.check_parser(cmd, arglist, verifylist) _c, values = cmd.take_action(parsed_args) calls = [mock.call(node) for node in nodes] self.assertEqual(calls, self.client.introspect.call_args_list) self.assertEqual([('uuid1', None), ('uuid2', 'boom'), ('uuid3', None)], sorted(values)) def test_abort(self): node = 'uuid1' arglist = [node] verifylist = [('node', node)] response_mock = mock.Mock(status_code=202, content=b'') self.client.abort.return_value = response_mock cmd = shell.AbortCommand(self.app, None) parsed_args = self.check_parser(cmd, arglist, verifylist) result = cmd.take_action(parsed_args) self.client.abort.assert_called_once_with(node) self.assertIsNone(result) class TestGetStatus(BaseTest): def test_get_status(self): arglist = ['uuid1'] verifylist = [('node', 'uuid1')] self.client.get_status.return_value = {'finished': True, 'error': 'boom'} cmd = shell.StatusCommand(self.app, None) parsed_args = self.check_parser(cmd, arglist, verifylist) result = cmd.take_action(parsed_args) self.assertEqual([('error', 'finished'), ('boom', True)], list(result)) self.client.get_status.assert_called_once_with('uuid1') class TestStatusList(BaseTest): def setUp(self): super(TestStatusList, self).setUp() self.COLUMNS = ('UUID', 'Started at', 'Finished at', 'Error') self.status1 = { 'error': None, 'finished': True, 'finished_at': '1970-01-01T00:10', 'links': None, 'started_at': '1970-01-01T00:00', 'uuid': 'uuid1' } self.status2 = { 'error': None, 'finished': False, 'finished_at': None, 'links': None, 'started_at': '1970-01-01T00:01', 'uuid': 'uuid2' } def status_row(self, status): status = dict(item for item in status.items() if item[0] != 'links') return (status['uuid'], status['started_at'], status['finished_at'], status['error']) def test_list_statuses(self): status_list = [self.status1, self.status2] self.client.list_statuses.return_value = status_list arglist = [] verifylist = [] cmd = shell.StatusListCommand(self.app, None) parsed_args = self.check_parser(cmd, arglist, verifylist) result = cmd.take_action(parsed_args) self.assertEqual((self.COLUMNS, [self.status_row(status) for status in status_list]), result) self.client.list_statuses.assert_called_once_with(limit=None, marker=None) def test_list_statuses_marker_limit(self): self.client.list_statuses.return_value = [] arglist = ['--marker', 'uuid1', '--limit', '42'] verifylist = [('marker', 'uuid1'), ('limit', 42)] cmd = shell.StatusListCommand(self.app, None) parsed_args = self.check_parser(cmd, arglist, verifylist) result = cmd.take_action(parsed_args) self.assertEqual((self.COLUMNS, []), result) self.client.list_statuses.assert_called_once_with(limit=42, marker='uuid1') class TestRules(BaseTest): def test_import_single(self): f = tempfile.NamedTemporaryFile() self.addCleanup(f.close) f.write(b'{"foo": "bar"}') f.flush() arglist = [f.name] verifylist = [('file', f.name)] self.rules_api.from_json.return_value = { 'uuid': '1', 'description': 'd', 'links': []} cmd = shell.RuleImportCommand(self.app, None) parsed_args = self.check_parser(cmd, arglist, verifylist) cols, values = cmd.take_action(parsed_args) self.assertEqual(('UUID', 'Description'), cols) self.assertEqual([('1', 'd')], values) self.rules_api.from_json.assert_called_once_with({'foo': 'bar'}) def test_import_multiple(self): f = tempfile.NamedTemporaryFile() self.addCleanup(f.close) f.write(b'[{"foo": "bar"}, {"answer": 42}]') f.flush() arglist = [f.name] verifylist = [('file', f.name)] self.rules_api.from_json.side_effect = iter([ {'uuid': '1', 'description': 'd1', 'links': []}, {'uuid': '2', 'description': 'd2', 'links': []} ]) cmd = shell.RuleImportCommand(self.app, None) parsed_args = self.check_parser(cmd, arglist, verifylist) cols, values = cmd.take_action(parsed_args) self.assertEqual(('UUID', 'Description'), cols) self.assertEqual([('1', 'd1'), ('2', 'd2')], values) self.rules_api.from_json.assert_any_call({'foo': 'bar'}) self.rules_api.from_json.assert_any_call({'answer': 42}) def test_list(self): self.rules_api.get_all.return_value = [ {'uuid': '1', 'description': 'd1', 'links': []}, {'uuid': '2', 'description': 'd2', 'links': []} ] cmd = shell.RuleListCommand(self.app, None) parsed_args = self.check_parser(cmd, [], []) cols, values = cmd.take_action(parsed_args) self.assertEqual(('UUID', 'Description'), cols) self.assertEqual([('1', 'd1'), ('2', 'd2')], values) self.rules_api.get_all.assert_called_once_with() def test_show(self): self.rules_api.get.return_value = { 'uuid': 'uuid1', 'links': [], 'description': 'd', 'conditions': [{}], 'actions': [{}] } arglist = ['uuid1'] verifylist = [('uuid', 'uuid1')] cmd = shell.RuleShowCommand(self.app, None) parsed_args = self.check_parser(cmd, arglist, verifylist) cols, values = cmd.take_action(parsed_args) self.assertEqual(('actions', 'conditions', 'description', 'uuid'), cols) self.assertEqual(([{}], [{}], 'd', 'uuid1'), values) self.rules_api.get.assert_called_once_with('uuid1') def test_delete(self): arglist = ['uuid1'] verifylist = [('uuid', 'uuid1')] cmd = shell.RuleDeleteCommand(self.app, None) parsed_args = self.check_parser(cmd, arglist, verifylist) cmd.take_action(parsed_args) self.rules_api.delete.assert_called_once_with('uuid1') def test_purge(self): cmd = shell.RulePurgeCommand(self.app, None) parsed_args = self.check_parser(cmd, [], []) cmd.take_action(parsed_args) self.rules_api.delete_all.assert_called_once_with() class TestDataSave(BaseTest): def test_stdout(self): self.client.get_data.return_value = {'answer': 42} buf = six.StringIO() arglist = ['uuid1'] verifylist = [('node', 'uuid1')] cmd = shell.DataSaveCommand(self.app, None) parsed_args = self.check_parser(cmd, arglist, verifylist) with mock.patch.object(sys, 'stdout', buf): cmd.take_action(parsed_args) self.assertEqual('{"answer": 42}', buf.getvalue()) self.client.get_data.assert_called_once_with('uuid1', raw=False) def test_file(self): self.client.get_data.return_value = b'{"answer": 42}' with tempfile.NamedTemporaryFile() as fp: arglist = ['--file', fp.name, 'uuid1'] verifylist = [('node', 'uuid1'), ('file', fp.name)] cmd = shell.DataSaveCommand(self.app, None) parsed_args = self.check_parser(cmd, arglist, verifylist) cmd.take_action(parsed_args) content = fp.read() self.assertEqual(b'{"answer": 42}', content) self.client.get_data.assert_called_once_with('uuid1', raw=True) class TestInterfaceCmds(BaseTest): def setUp(self): super(TestInterfaceCmds, self).setUp() self.inspector_db = { "all_interfaces": { 'em1': {'mac': "00:11:22:33:44:55", 'ip': "10.10.1.1", "lldp_processed": { "switch_chassis_id": "99:aa:bb:cc:dd:ff", "switch_port_id": "555", "switch_port_vlans": [{"id": 101, "name": "vlan101"}, {"id": 102, "name": "vlan102"}, {"id": 104, "name": "vlan104"}, {"id": 201, "name": "vlan201"}, {"id": 203, "name": "vlan203"}], "switch_port_mtu": 1514 } } } } def test_list(self): self.client.get_all_interface_data.return_value = [ ["em1", "00:11:22:33:44:55", [101, 102, 104, 201, 203], "99:aa:bb:cc:dd:ff", "555"], ["em2", "00:11:22:66:77:88", [201, 203], "99:aa:bb:cc:dd:ff", "777"], ["em3", "00:11:22:aa:bb:cc", '', '', '']] arglist = ['uuid1'] verifylist = [('node_ident', 'uuid1')] cmd = shell.InterfaceListCommand(self.app, None) parsed_args = self.check_parser(cmd, arglist, verifylist) cols, values = cmd.take_action(parsed_args) expected_cols = ("Interface", "MAC Address", "Switch Port VLAN IDs", "Switch Chassis ID", "Switch Port ID") # Note that em3 has no lldp data expected_rows = [["em1", "00:11:22:33:44:55", [101, 102, 104, 201, 203], "99:aa:bb:cc:dd:ff", "555"], ["em2", "00:11:22:66:77:88", [201, 203], "99:aa:bb:cc:dd:ff", "777"], ["em3", "00:11:22:aa:bb:cc", '', '', '']] self.assertEqual(expected_cols, cols) self.assertEqual(expected_rows, values) def test_list_field(self): self.client.get_all_interface_data.return_value = [ ["em1", 1514], ["em2", 9216], ["em3", '']] arglist = ['uuid1', '--fields', 'interface', "switch_port_mtu"] verifylist = [('node_ident', 'uuid1'), ('fields', ["interface", "switch_port_mtu"])] cmd = shell.InterfaceListCommand(self.app, None) parsed_args = self.check_parser(cmd, arglist, verifylist) cols, values = cmd.take_action(parsed_args) expected_cols = ("Interface", "Switch Port MTU") expected_rows = [["em1", 1514], ["em2", 9216], ["em3", '']] self.assertEqual(expected_cols, cols) self.assertEqual(expected_rows, values) def test_list_filtered(self): self.client.get_all_interface_data.return_value = [ ["em1", "00:11:22:33:44:55", [101, 102, 104, 201, 203], "99:aa:bb:cc:dd:ff", "555"]] arglist = ['uuid1', '--vlan', '104'] verifylist = [('node_ident', 'uuid1'), ('vlan', [104])] cmd = shell.InterfaceListCommand(self.app, None) parsed_args = self.check_parser(cmd, arglist, verifylist) cols, values = cmd.take_action(parsed_args) expected_cols = ("Interface", "MAC Address", "Switch Port VLAN IDs", "Switch Chassis ID", "Switch Port ID") expected_rows = [["em1", "00:11:22:33:44:55", [101, 102, 104, 201, 203], "99:aa:bb:cc:dd:ff", "555"]] self.assertEqual(expected_cols, cols) self.assertEqual(expected_rows, values) def test_list_no_data(self): self.client.get_all_interface_data.return_value = [[]] arglist = ['uuid1'] verifylist = [('node_ident', 'uuid1')] cmd = shell.InterfaceListCommand(self.app, None) parsed_args = self.check_parser(cmd, arglist, verifylist) cols, values = cmd.take_action(parsed_args) expected_cols = ("Interface", "MAC Address", "Switch Port VLAN IDs", "Switch Chassis ID", "Switch Port ID") expected_rows = [[]] self.assertEqual(expected_cols, cols) self.assertEqual(expected_rows, values) def test_show(self): self.client.get_data.return_value = self.inspector_db data = OrderedDict( [('node_ident', "uuid1"), ('interface', "em1"), ('mac', "00:11:22:33:44:55"), ('switch_chassis_id', "99:aa:bb:cc:dd:ff"), ('switch_port_id', "555"), ('switch_port_mtu', 1514), ('switch_port_vlans', [{"id": 101, "name": "vlan101"}, {"id": 102, "name": "vlan102"}, {"id": 104, "name": "vlan104"}, {"id": 201, "name": "vlan201"}, {"id": 203, "name": "vlan203"}])] ) self.client.get_interface_data.return_value = data arglist = ['uuid1', 'em1'] verifylist = [('node_ident', 'uuid1'), ('interface', 'em1')] cmd = shell.InterfaceShowCommand(self.app, None) parsed_args = self.check_parser(cmd, arglist, verifylist) cols, values = cmd.take_action(parsed_args) expected_cols = ("node_ident", "interface", "mac", "switch_chassis_id", "switch_port_id", "switch_port_mtu", "switch_port_vlans") expected_rows = ("uuid1", "em1", "00:11:22:33:44:55", "99:aa:bb:cc:dd:ff", "555", 1514, [{"id": 101, "name": "vlan101"}, {"id": 102, "name": "vlan102"}, {"id": 104, "name": "vlan104"}, {"id": 201, "name": "vlan201"}, {"id": 203, "name": "vlan203"}]) self.assertEqual(expected_cols, cols) self.assertEqual(expected_rows, values) def test_show_field(self): self.client.get_data.return_value = self.inspector_db data = OrderedDict([('node_ident', "uuid1"), ('interface', "em1"), ('switch_port_vlans', [{"id": 101, "name": "vlan101"}, {"id": 102, "name": "vlan102"}, {"id": 104, "name": "vlan104"}, {"id": 201, "name": "vlan201"}, {"id": 203, "name": "vlan203"}]) ]) self.client.get_interface_data.return_value = data arglist = ['uuid1', 'em1', '--fields', 'node_ident', 'interface', "switch_port_vlans"] verifylist = [('node_ident', 'uuid1'), ('interface', 'em1'), ('fields', ["node_ident", "interface", "switch_port_vlans"])] cmd = shell.InterfaceShowCommand(self.app, None) parsed_args = self.check_parser(cmd, arglist, verifylist) cols, values = cmd.take_action(parsed_args) expected_cols = ("node_ident", "interface", "switch_port_vlans") expected_rows = ("uuid1", "em1", [{"id": 101, "name": "vlan101"}, {"id": 102, "name": "vlan102"}, {"id": 104, "name": "vlan104"}, {"id": 201, "name": "vlan201"}, {"id": 203, "name": "vlan203"}]) self.assertEqual(expected_cols, cols) self.assertEqual(expected_rows, values) python-ironic-inspector-client-3.1.0/ironic_inspector_client/shell.py0000666000175100017510000003061713232475743026234 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. """OpenStackClient plugin for Ironic Inspector.""" from __future__ import print_function import json import sys from osc_lib.command import command from osc_lib import utils import ironic_inspector_client from ironic_inspector_client import resource as res API_NAME = 'baremetal_introspection' API_VERSION_OPTION = 'inspector_api_version' DEFAULT_API_VERSION = '1' API_VERSIONS = { "1": "ironic_inspector.shell", } for mversion in range(ironic_inspector_client.MAX_API_VERSION[1] + 1): API_VERSIONS["1.%d" % mversion] = API_VERSIONS["1"] def make_client(instance): return ironic_inspector_client.ClientV1( inspector_url=instance.get_configuration().get('inspector_url'), session=instance.session, api_version=instance._api_version[API_NAME], interface=instance._interface, region_name=instance._region_name) def build_option_parser(parser): parser.add_argument('--inspector-api-version', default=utils.env('INSPECTOR_VERSION', default=DEFAULT_API_VERSION), help='inspector API version, only 1 is supported now ' '(env: INSPECTOR_VERSION).') parser.add_argument('--inspector-url', default=utils.env('INSPECTOR_URL', default=None), help='inspector URL, defaults to localhost ' '(env: INSPECTOR_URL).') return parser class StartCommand(command.Lister): """Start the introspection.""" COLUMNS = ('UUID', 'Error') def get_parser(self, prog_name): parser = super(StartCommand, self).get_parser(prog_name) parser.add_argument('node', help='baremetal node UUID(s) or name(s)', nargs='+') parser.add_argument('--wait', action='store_true', help='wait for introspection to finish; the result' ' will be displayed in the end') return parser def take_action(self, parsed_args): client = self.app.client_manager.baremetal_introspection for uuid in parsed_args.node: client.introspect(uuid) if parsed_args.wait: print('Waiting for introspection to finish...', file=sys.stderr) result = client.wait_for_finish(parsed_args.node) result = [(uuid, s.get('error')) for uuid, s in result.items()] else: result = [] return self.COLUMNS, result class ReprocessCommand(command.Command): """Reprocess stored introspection data""" def get_parser(self, prog_name): parser = super(ReprocessCommand, self).get_parser(prog_name) parser.add_argument('node', help='baremetal node UUID or name') return parser def take_action(self, parsed_args): client = self.app.client_manager.baremetal_introspection client.reprocess(parsed_args.node) class StatusCommand(command.ShowOne): """Get introspection status.""" hidden_status_items = {'links'} @classmethod def status_attributes(cls, client_item): """Get status attributes from an API client dict. Filters the status fields according to the cls.hidden_status_items :param client_item: an item returned from either the get_status or the list_statuses client method :return: introspection status as a list of name, value pairs """ return [item for item in client_item.items() if item[0] not in cls.hidden_status_items] def get_parser(self, prog_name): parser = super(StatusCommand, self).get_parser(prog_name) parser.add_argument('node', help='baremetal node UUID or name') return parser def take_action(self, parsed_args): client = self.app.client_manager.baremetal_introspection status = client.get_status(parsed_args.node) return zip(*sorted(self.status_attributes(status))) class StatusListCommand(command.Lister): """List introspection statuses""" COLUMNS = ('UUID', 'Started at', 'Finished at', 'Error') @classmethod def status_row(cls, client_item): """Get a row from a client_item. The row columns are filtered&sorted according to cls.COLUMNS. :param client_item: an item returned from either the get_status or the list_statuses client method. :return: a list of client_item attributes as the row """ status = dict(StatusCommand.status_attributes(client_item)) return utils.get_dict_properties(status, cls.COLUMNS) def get_parser(self, prog_name): parser = super(StatusListCommand, self).get_parser(prog_name) parser.add_argument('--marker', help='UUID of the last item on the ' 'previous page', default=None) parser.add_argument('--limit', help='the amount of items to return', type=int, default=None) return parser def take_action(self, parsed_args): client = self.app.client_manager.baremetal_introspection statuses = client.list_statuses(marker=parsed_args.marker, limit=parsed_args.limit) rows = [self.status_row(status) for status in statuses] return self.COLUMNS, rows class AbortCommand(command.Command): """Abort running introspection for node.""" def get_parser(self, prog_name): parser = super(AbortCommand, self).get_parser(prog_name) parser.add_argument('node', help='baremetal node UUID or name') return parser def take_action(self, parsed_args): client = self.app.client_manager.baremetal_introspection client.abort(parsed_args.node) class RuleImportCommand(command.Lister): """Import one or several introspection rules from a json file.""" COLUMNS = ("UUID", "Description") def get_parser(self, prog_name): parser = super(RuleImportCommand, self).get_parser(prog_name) parser.add_argument('file', help='JSON file to import, may contain ' 'one or several rules') return parser def take_action(self, parsed_args): with open(parsed_args.file, 'r') as fp: rules = json.load(fp) if not isinstance(rules, list): rules = [rules] client = self.app.client_manager.baremetal_introspection result = [] for rule in rules: result.append(client.rules.from_json(rule)) result = [tuple(rule.get(col.lower()) for col in self.COLUMNS) for rule in result] return self.COLUMNS, result class RuleListCommand(command.Lister): """List all introspection rules.""" COLUMNS = ("UUID", "Description") def take_action(self, parsed_args): client = self.app.client_manager.baremetal_introspection rules = client.rules.get_all() rules = [tuple(rule.get(col.lower()) for col in self.COLUMNS) for rule in rules] return self.COLUMNS, rules class RuleShowCommand(command.ShowOne): """Show an introspection rule.""" def get_parser(self, prog_name): parser = super(RuleShowCommand, self).get_parser(prog_name) parser.add_argument('uuid', help='rule UUID') return parser def take_action(self, parsed_args): client = self.app.client_manager.baremetal_introspection rule = client.rules.get(parsed_args.uuid) del rule['links'] return self.dict2columns(rule) class RuleDeleteCommand(command.Command): """Delete an introspection rule.""" def get_parser(self, prog_name): parser = super(RuleDeleteCommand, self).get_parser(prog_name) parser.add_argument('uuid', help='rule UUID') return parser def take_action(self, parsed_args): client = self.app.client_manager.baremetal_introspection client.rules.delete(parsed_args.uuid) class RulePurgeCommand(command.Command): """Drop all introspection rules.""" def take_action(self, parsed_args): client = self.app.client_manager.baremetal_introspection client.rules.delete_all() class DataSaveCommand(command.Command): """Save or display raw introspection data.""" def get_parser(self, prog_name): parser = super(DataSaveCommand, self).get_parser(prog_name) parser.add_argument("--file", metavar="", help="downloaded introspection data filename " "(default: stdout)") parser.add_argument('node', help='baremetal node UUID or name') return parser def take_action(self, parsed_args): client = self.app.client_manager.baremetal_introspection data = client.get_data(parsed_args.node, raw=bool(parsed_args.file)) if parsed_args.file: with open(parsed_args.file, 'wb') as fp: fp.write(data) else: json.dump(data, sys.stdout) class InterfaceListCommand(command.Lister): """List interface data including attached switch port information.""" def get_parser(self, prog_name): parser = super(InterfaceListCommand, self).get_parser(prog_name) parser.add_argument('node_ident', help='baremetal node UUID or name') parser.add_argument("--vlan", action='append', default=[], type=int, help="List only interfaces configured " "for this vlan id, can be repeated") display_group = parser.add_mutually_exclusive_group() display_group.add_argument( '--long', dest='detail', action='store_true', default=False, help="Show detailed information about interfaces.") display_group.add_argument( '--fields', nargs='+', dest='fields', metavar='', choices=sorted(res.InterfaceResource(detailed=True).fields), help="Display one or more fields. " "Can not be used when '--long' is specified") return parser def take_action(self, parsed_args): client = self.app.client_manager.baremetal_introspection # If --long defined, use all fields interface_res = res.InterfaceResource(parsed_args.fields, parsed_args.detail) rows = client.get_all_interface_data(parsed_args.node_ident, interface_res.fields, vlan=parsed_args.vlan) return interface_res.labels, rows class InterfaceShowCommand(command.ShowOne): """Show interface data including attached switch port information.""" COLUMNS = ("Field", "Value") def get_parser(self, prog_name): parser = super(InterfaceShowCommand, self).get_parser(prog_name) parser.add_argument('node_ident', help='baremetal node UUID or name') parser.add_argument('interface', help='interface name') parser.add_argument( '--fields', nargs='+', dest='fields', metavar='', choices=sorted(res.InterfaceResource(detailed=True).fields), help="Display one or more fields.") return parser def take_action(self, parsed_args): client = self.app.client_manager.baremetal_introspection if parsed_args.fields: interface_res = res.InterfaceResource(parsed_args.fields) else: # Show all fields in detailed resource interface_res = res.InterfaceResource(detailed=True) iface_dict = client.get_interface_data(parsed_args.node_ident, parsed_args.interface, interface_res.fields) return tuple(zip(*(iface_dict.items()))) python-ironic-inspector-client-3.1.0/ironic_inspector_client/resource.py0000666000175100017510000000752713232475743026760 0ustar zuulzuul00000000000000 # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. class InterfaceResource(object): """InterfaceResource class This class is used to manage the fields including Link Layer Discovery Protocols (LLDP) fields, that an interface contains. An individual field consists of a 'field_id' (key) and a 'label' (value). """ FIELDS = { 'interface': 'Interface', 'mac': 'MAC Address', 'node_ident': 'Node', 'switch_capabilities_enabled': 'Switch Capabilities Enabled', 'switch_capabilities_support': 'Switch Capabilities Supported', 'switch_chassis_id': 'Switch Chassis ID', 'switch_port_autonegotiation_enabled': 'Switch Port Autonegotiation Enabled', 'switch_port_autonegotiation_support': 'Switch Port Autonegotiation Supported', 'switch_port_description': 'Switch Port Description', 'switch_port_id': 'Switch Port ID', 'switch_port_link_aggregation_enabled': 'Switch Port Link Aggregation Enabled', 'switch_port_link_aggregation_support': 'Switch Port Link Aggregation Supported', 'switch_port_link_aggregation_id': 'Switch Port Link Aggregation ID', 'switch_port_management_vlan_id': 'Switch Port Mgmt VLAN ID', 'switch_port_mau_type': 'Switch Port Mau Type', 'switch_port_mtu': 'Switch Port MTU', 'switch_port_physical_capabilities': 'Switch Port Physical Capabilities', 'switch_port_protocol_vlan_enabled': 'Switch Port Protocol VLAN Enabled', 'switch_port_protocol_vlan_support': 'Switch Port Protocol VLAN Supported', 'switch_port_protocol_vlan_ids': 'Switch Port Protocol VLAN IDs', 'switch_port_untagged_vlan_id': 'Switch Port Untagged VLAN', 'switch_port_vlans': 'Switch Port VLANs', 'switch_port_vlan_ids': 'Switch Port VLAN IDs', 'switch_protocol_identities': 'Switch Protocol Identities', 'switch_system_name': 'Switch System Name' } """A mapping of all known interface fields to their descriptions.""" DEFAULT_FIELD_IDS = ['interface', 'mac', 'switch_port_vlan_ids', 'switch_chassis_id', 'switch_port_id'] """Interface fields displayed by default.""" def __init__(self, field_ids=None, detailed=False): """Create an InterfaceResource object :param field_ids: A list of strings that the Resource object will contain. Each string must match an existing key in FIELDS. :param detailed: If True, use the all of the keys in FIELDS instead of input field_ids """ if field_ids is None: # Default field set in logical format, so don't sort field_ids = self.DEFAULT_FIELD_IDS if detailed: field_ids = sorted(self.FIELDS.keys()) self._fields = tuple(field_ids) self._labels = tuple(self.FIELDS[x] for x in field_ids) @property def fields(self): """List of fields displayed for this resource.""" return self._fields @property def labels(self): """List of labels for fields displayed for this resource.""" return self._labels INTERFACE_DEFAULT = InterfaceResource() python-ironic-inspector-client-3.1.0/ironic_inspector_client/common/0000775000175100017510000000000013232476241026024 5ustar zuulzuul00000000000000python-ironic-inspector-client-3.1.0/ironic_inspector_client/common/i18n.py0000666000175100017510000000144213232475743027166 0ustar zuulzuul00000000000000# Copyright 2015 NEC Corporation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import oslo_i18n _translators = oslo_i18n.TranslatorFactory( domain='python-ironic-inspector-client') # The primary translation function using the well-known name "_" _ = _translators.primary python-ironic-inspector-client-3.1.0/ironic_inspector_client/common/http.py0000666000175100017510000002164113232475743027371 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. """Generic code for inspector client.""" import json import logging from keystoneauth1 import exceptions as ks_exc from keystoneauth1 import session as ks_session import requests import six from ironic_inspector_client.common.i18n import _ _ERROR_ENCODING = 'utf-8' LOG = logging.getLogger('ironic_inspector_client') _MIN_VERSION_HEADER = 'X-OpenStack-Ironic-Inspector-API-Minimum-Version' _MAX_VERSION_HEADER = 'X-OpenStack-Ironic-Inspector-API-Maximum-Version' _VERSION_HEADER = 'X-OpenStack-Ironic-Inspector-API-Version' _AUTH_TOKEN_HEADER = 'X-Auth-Token' def _parse_version(api_version): try: return tuple(int(x) for x in api_version.split('.')) except (ValueError, TypeError): raise ValueError(_("Malformed API version: expect tuple, string " "in form of X.Y or integer")) class ClientError(requests.HTTPError): """Error returned from a server.""" def __init__(self, response): # inspector returns error message in body msg = response.content.decode(_ERROR_ENCODING) try: msg = json.loads(msg)['error']['message'] except ValueError: LOG.debug('Old style error response returned, assuming ' 'ironic-discoverd') except (KeyError, TypeError): LOG.exception('Bad error response from Ironic Inspector') LOG.debug('Inspector returned error "%(msg)s" (HTTP %(code)s)', {'msg': msg, 'code': response.status_code}) super(ClientError, self).__init__(msg, response=response) @classmethod def raise_if_needed(cls, response): """Raise exception if response contains error.""" if response.status_code >= 400: raise cls(response) class VersionNotSupported(Exception): """Denotes that requested API versions is not supported by the server. :ivar expected: requested version. :ivar supported: sequence with two items: minimum and maximum actually supported versions. """ def __init__(self, expected, supported): msg = (_('Version %(expected)s is not supported by the server, ' 'supported range is %(supported)s') % {'expected': expected, 'supported': ' to '.join(str(x) for x in supported)}) self.expected_version = expected self.supported_versions = supported super(Exception, self).__init__(msg) class EndpointNotFound(Exception): """Denotes that endpoint for the introspection service was not found. :ivar service_type: requested service type """ def __init__(self, service_type): self.service_type = service_type msg = _('Endpoint of type %s was not found in the service catalog ' 'and was not provided explicitly') % service_type super(Exception, self).__init__(msg) class BaseClient(object): """Base class for clients, provides common HTTP code.""" def __init__(self, api_version, inspector_url=None, session=None, service_type='baremetal-introspection', interface=None, region_name=None): """Create a client. :param api_version: minimum API version that must be supported by the server :param inspector_url: *Ironic Inspector* URL in form: http://host:port[/ver]. When session is provided, defaults to service URL from the catalog. As a last resort defaults to ``http://:5050/v``. :param session: existing keystone session. A session without authentication is created if this is set to None. :param service_type: service type to use when looking up the URL :param interface: interface type (public, internal, etc) to use when looking up the URL :param region_name: region name to use when looking up the URL :raises: EndpointNotFound if the introspection service endpoint was not provided via inspector_url and was not found in the service catalog. """ self._base_url = inspector_url if session is None: self._session = ks_session.Session(None) else: self._session = session if not inspector_url: try: self._base_url = session.get_endpoint( service_type=service_type, interface=interface, region_name=region_name) except ks_exc.CatalogException as exc: LOG.error('%(iface)s endpoint for %(stype)s in region ' '%(region)s was not found in the service ' 'catalog: %(error)s', {'iface': interface, 'stype': service_type, 'region': region_name, 'error': exc}) raise EndpointNotFound(service_type=service_type) if not self._base_url: # This handles the case when session=None and no inspector_url is # provided, as well as keystoneauth plugins that may return None. raise EndpointNotFound(service_type=service_type) self._base_url = self._base_url.rstrip('/') self._api_version = self._check_api_version(api_version) self._version_str = '%d.%d' % self._api_version ver_postfix = '/v%d' % self._api_version[0] if not self._base_url.endswith(ver_postfix): self._base_url += ver_postfix def _add_headers(self, headers): headers[_VERSION_HEADER] = self._version_str return headers def _check_api_version(self, api_version): if isinstance(api_version, int): api_version = (api_version, 0) if isinstance(api_version, six.string_types): api_version = _parse_version(api_version) api_version = tuple(api_version) if not all(isinstance(x, int) for x in api_version): raise TypeError(_("All API version components should be integers")) if len(api_version) == 1: api_version += (0,) elif len(api_version) > 2: raise ValueError(_("API version should be of length 1 or 2")) minv, maxv = self.server_api_versions() if api_version < minv or api_version > maxv: raise VersionNotSupported(api_version, (minv, maxv)) return api_version def request(self, method, url, **kwargs): """Make an HTTP request. :param method: HTTP method :param endpoint: relative endpoint :param kwargs: arguments to pass to 'requests' library """ headers = self._add_headers(kwargs.pop('headers', {})) url = self._base_url + '/' + url.lstrip('/') LOG.debug('Requesting %(method)s %(url)s (API version %(ver)s) ' 'with %(args)s', {'method': method.upper(), 'url': url, 'ver': self._version_str, 'args': kwargs}) res = self._session.request(url, method, headers=headers, raise_exc=False, **kwargs) LOG.debug('Got response for %(method)s %(url)s with status code ' '%(code)s', {'url': url, 'method': method.upper(), 'code': res.status_code}) ClientError.raise_if_needed(res) return res def server_api_versions(self): """Get minimum and maximum supported API versions from a server. :return: tuple (minimum version, maximum version) each version is returned as a tuple (X, Y) :raises: *requests* library exception on connection problems. :raises: ValueError if returned version cannot be parsed """ res = self._session.get(self._base_url, authenticated=False, raise_exc=False) # HTTP Not Found is a valid response for older (2.0.0) servers if res.status_code >= 400 and res.status_code != 404: ClientError.raise_if_needed(res) min_ver = res.headers.get(_MIN_VERSION_HEADER, '1.0') max_ver = res.headers.get(_MAX_VERSION_HEADER, '1.0') res = (_parse_version(min_ver), _parse_version(max_ver)) LOG.debug('Supported API version range for %(url)s is ' '[%(min)s, %(max)s]', {'url': self._base_url, 'min': min_ver, 'max': max_ver}) return res python-ironic-inspector-client-3.1.0/ironic_inspector_client/common/__init__.py0000666000175100017510000000000013232475743030133 0ustar zuulzuul00000000000000python-ironic-inspector-client-3.1.0/ironic_inspector_client/__init__.py0000666000175100017510000000127513232475743026662 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from .v1 import ClientV1, DEFAULT_API_VERSION, MAX_API_VERSION # noqa from .common.http import ClientError, EndpointNotFound, VersionNotSupported # noqa python-ironic-inspector-client-3.1.0/ironic_inspector_client/v1.py0000666000175100017510000004200313232475743025443 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. """Client for V1 API.""" from collections import OrderedDict import logging import time import six from ironic_inspector_client.common import http from ironic_inspector_client.common.i18n import _ DEFAULT_API_VERSION = (1, 0) """Server API version used by default.""" MAX_API_VERSION = (1, 8) """Maximum API version this client was designed to work with. This does not mean that other versions won't work at all - the server might still support them. """ # using huge timeout by default, as precise timeout should be set in # ironic-inspector settings DEFAULT_RETRY_INTERVAL = 10 """Default interval (in seconds) between retries when waiting for introspection to finish.""" DEFAULT_MAX_RETRIES = 3600 """Default number of retries when waiting for introspection to finish.""" LOG = logging.getLogger(__name__) class WaitTimeoutError(Exception): """Timeout while waiting for nodes to finish introspection.""" class ClientV1(http.BaseClient): """Client for API v1. Create this object to use Python API, for example:: import ironic_inspector_client client = ironic_inspector_client.ClientV1(session=keystone_session) This code creates a client with API version *1.0* and a given Keystone `session `_. The service URL is fetched from the service catalog in this case. Optional arguments ``service_type``, ``interface`` and ``region_name`` can be provided to modify how the URL is looked up. If the catalog lookup fails, the local host with port 5050 is tried. However, this behaviour is deprecated and should not be relied on. Also an explicit ``inspector_url`` can be passed to bypass service catalog. Optional ``api_version`` argument is a minimum API version that a server must support. It can be a tuple (MAJ, MIN), string "MAJ.MIN" or integer (only major, minimum supported minor version is assumed). :ivar rules: Reference to the introspection rules API. Instance of :py:class:`ironic_inspector_client.v1.RulesAPI`. """ def __init__(self, **kwargs): """Create a client. See :py:class:`ironic_inspector_client.common.http.HttpClient` for the list of acceptable arguments. :param kwargs: arguments to pass to the BaseClient constructor. api_version is set to DEFAULT_API_VERSION by default. """ kwargs.setdefault('api_version', DEFAULT_API_VERSION) super(ClientV1, self).__init__(**kwargs) self.rules = RulesAPI(self.request) def introspect(self, uuid): """Start introspection for a node. :param uuid: node UUID or name :raises: :py:class:`.ClientError` on error reported from a server :raises: :py:class:`.VersionNotSupported` if requested api_version is not supported :raises: *requests* library exception on connection problems. """ if not isinstance(uuid, six.string_types): raise TypeError( _("Expected string for uuid argument, got %r") % uuid) self.request('post', '/introspection/%s' % uuid) def reprocess(self, uuid): """Reprocess stored introspection data. :param uuid: node UUID or name. :raises: :py:class:`.ClientError` on error reported from a server :raises: :py:class:`.VersionNotSupported` if requested api_version is not supported :raises: *requests* library exception on connection problems. :raises: TypeError if uuid is not a string. """ if not isinstance(uuid, six.string_types): raise TypeError(_("Expected string for uuid argument, got" " %r instead") % uuid) return self.request('post', '/introspection/%s/data/unprocessed' % uuid) def list_statuses(self, marker=None, limit=None): """List introspection statuses. Supports pagination via the marker and limit params. The items are sorted by the server according to the `started_at` attribute, newer items first. :param marker: pagination maker, UUID or None :param limit: pagination limit, int or None :raises: :py:class:`.ClientError` on error reported from a server :raises: :py:class:`.VersionNotSupported` if requested api_version is not supported :raises: *requests* library exception on connection problems. :return: a list of status dictionaries with the keys: * `error` an error string or None, * `finished` whether introspection was finished, * `finished_at` an ISO8601 timestamp or None, * `links` with a self-link URL, * `started_at` an ISO8601 timestamp, * `uuid` the node UUID """ if not (marker is None or isinstance(marker, six.string_types)): raise TypeError(_('Expected a string value of the marker, got ' '%s instead') % marker) if not (limit is None or isinstance(limit, int)): raise TypeError(_('Expected an integer value of the limit, got ' '%s instead') % limit) params = { 'marker': marker, 'limit': limit, } response = self.request('get', '/introspection', params=params) return response.json()['introspection'] def get_status(self, uuid): """Get introspection status for a node. :param uuid: node UUID or name. :raises: :py:class:`.ClientError` on error reported from a server :raises: :py:class:`.VersionNotSupported` if requested api_version is not supported :raises: *requests* library exception on connection problems. :return: dictionary with the keys: * `error` an error string or None, * `finished` whether introspection was finished, * `finished_at` an ISO8601 timestamp or None, * `links` with a self-link URL, * `started_at` an ISO8601 timestamp, * `uuid` the node UUID """ if not isinstance(uuid, six.string_types): raise TypeError( _("Expected string for uuid argument, got %r") % uuid) return self.request('get', '/introspection/%s' % uuid).json() def wait_for_finish(self, uuids, retry_interval=DEFAULT_RETRY_INTERVAL, max_retries=DEFAULT_MAX_RETRIES, sleep_function=time.sleep): """Wait for introspection finishing for given nodes. :param uuids: collection of node UUIDs or names. :param retry_interval: sleep interval between retries. :param max_retries: maximum number of retries. :param sleep_function: function used for sleeping between retries. :raises: :py:class:`.WaitTimeoutError` on timeout :raises: :py:class:`.ClientError` on error reported from a server :raises: :py:class:`.VersionNotSupported` if requested api_version is not supported :raises: *requests* library exception on connection problems. :return: dictionary UUID -> status (the same as in get_status). """ result = {} # Number of attempts = number of retries + first attempt for attempt in range(max_retries + 1): new_active_uuids = [] for uuid in uuids: status = self.get_status(uuid) if status.get('finished'): result[uuid] = status else: new_active_uuids.append(uuid) if new_active_uuids: if attempt != max_retries: uuids = new_active_uuids LOG.debug('Still waiting for introspection results for ' '%(count)d nodes, attempt %(attempt)d of ' '%(total)d', {'count': len(new_active_uuids), 'attempt': attempt + 1, 'total': max_retries + 1}) sleep_function(retry_interval) else: return result raise WaitTimeoutError(_("Timeout while waiting for introspection " "of nodes %s") % new_active_uuids) def get_data(self, uuid, raw=False): """Get introspection data from the last introspection of a node. :param uuid: node UUID or name. :param raw: whether to return raw binary data or parsed JSON data :returns: bytes or a dict depending on the 'raw' argument :raises: :py:class:`.ClientError` on error reported from a server :raises: :py:class:`.VersionNotSupported` if requested api_version is not supported :raises: *requests* library exception on connection problems. :raises: TypeError if uuid is not a string """ if not isinstance(uuid, six.string_types): raise TypeError( _("Expected string for uuid argument, got %r") % uuid) resp = self.request('get', '/introspection/%s/data' % uuid) if raw: return resp.content else: return resp.json() def abort(self, uuid): """Abort running introspection for a node. :param uuid: node UUID or name. :raises: :py:class:`.ClientError` on error reported from a server :raises: :py:class:`.VersionNotSupported` if requested api_version is not supported :raises: *requests* library exception on connection problems. :raises: TypeError if uuid is not a string. """ if not isinstance(uuid, six.string_types): raise TypeError(_("Expected string for uuid argument, got" " %r") % uuid) return self.request('post', '/introspection/%s/abort' % uuid) def get_interface_data(self, node_ident, interface, field_sel): """Get interface data for the input node and interface To get LLDP data, collection must be enabled by the kernel parameter ``ipa-collect-lldp=1``, and the inspector plugin ``basic_lldp`` must be enabled. :param node_ident: node UUID or name :param interface: interface name :param field_sel: list of all fields for which to get data :returns: interface data in OrderedDict """ # Use OrderedDict to maintain order of user-entered fields iface_data = OrderedDict() data = self.get_data(node_ident) all_interfaces = data.get('all_interfaces', []) # Make sure interface name is valid if interface not in all_interfaces: return iface_data # If lldp data not available this will still return interface, # mac, node_ident etc. lldp_proc = all_interfaces[interface].get('lldp_processed', {}) for f in field_sel: if f == 'node_ident': iface_data[f] = node_ident elif f == 'interface': iface_data[f] = interface elif f == 'mac': iface_data[f] = all_interfaces[interface].get(f) elif f == 'switch_port_vlan_ids': iface_data[f] = [item['id'] for item in lldp_proc.get('switch_port_vlans', [])] else: iface_data[f] = lldp_proc.get(f) return iface_data def get_all_interface_data(self, node_ident, field_sel, vlan=None): """Get interface data for all of the interfaces on this node :param node_ident: node UUID or name :param field_sel: list of all fields for which to get data :param vlan: list of vlans used to filter the lists returned :returns: list of interface data, each interface in a list """ # Get inventory data for this node data = self.get_data(node_ident) all_interfaces = data.get('all_interfaces', []) rows = [] if vlan: vlan = set(vlan) # walk all interfaces, appending data to row if not filtered for interface in all_interfaces: iface_dict = self.get_interface_data(node_ident, interface, field_sel) values = list(iface_dict.values()) # Use (optional) vlans to filter row if not vlan: rows.append(values) continue # curr_vlans may be None curr_vlans = iface_dict.get('switch_port_vlan_ids', []) if curr_vlans and (vlan & set(curr_vlans)): rows.append(values) # vlan matches, display this row return rows class RulesAPI(object): """Introspection rules API. Do not create instances of this class directly, use :py:attr:`ironic_inspector_client.v1.ClientV1.rules` instead. """ def __init__(self, requester): self._request = requester def create(self, conditions, actions, uuid=None, description=None): """Create a new introspection rule. :param conditions: list of rule conditions :param actions: list of rule actions :param uuid: rule UUID, will be generated if not specified :param description: optional rule description :returns: rule representation :raises: :py:class:`.ClientError` on error reported from a server :raises: :py:class:`.VersionNotSupported` if requested api_version is not supported """ if uuid is not None and not isinstance(uuid, six.string_types): raise TypeError( _("Expected string for uuid argument, got %r") % uuid) for name, arg in [('conditions', conditions), ('actions', actions)]: if not isinstance(arg, list) or not all(isinstance(x, dict) for x in arg): raise TypeError(_("Expected list of dicts for %(arg)s " "argument, got %(real)r"), {'arg': name, 'real': arg}) body = {'uuid': uuid, 'conditions': conditions, 'actions': actions, 'description': description} return self.from_json(body) def from_json(self, json_rule): """Import an introspection rule from JSON data. :param json_rule: rule information as a dict with keys matching arguments of :py:meth:`RulesAPI.create`. :returns: rule representation :raises: :py:class:`.ClientError` on error reported from a server :raises: :py:class:`.VersionNotSupported` if requested api_version is not supported """ return self._request('post', '/rules', json=json_rule).json() def get_all(self): """List all introspection rules. :returns: list of short rule representations (uuid, description and links) :raises: :py:class:`.ClientError` on error reported from a server :raises: :py:class:`.VersionNotSupported` if requested api_version is not supported """ return self._request('get', '/rules').json()['rules'] def get(self, uuid): """Get detailed information about an introspection rule. :param uuid: rule UUID :returns: rule representation :raises: :py:class:`.ClientError` on error reported from a server :raises: :py:class:`.VersionNotSupported` if requested api_version is not supported """ if not isinstance(uuid, six.string_types): raise TypeError( _("Expected string for uuid argument, got %r") % uuid) return self._request('get', '/rules/%s' % uuid).json() def delete(self, uuid): """Delete an introspection rule. :param uuid: rule UUID :raises: :py:class:`.ClientError` on error reported from a server :raises: :py:class:`.VersionNotSupported` if requested api_version is not supported """ if not isinstance(uuid, six.string_types): raise TypeError( _("Expected string for uuid argument, got %r") % uuid) self._request('delete', '/rules/%s' % uuid) def delete_all(self): """Delete all introspection rules. :raises: :py:class:`.ClientError` on error reported from a server :raises: :py:class:`.VersionNotSupported` if requested api_version is not supported """ self._request('delete', '/rules') python-ironic-inspector-client-3.1.0/requirements.txt0000666000175100017510000000063713232475743023127 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. keystoneauth1>=3.3.0 # Apache-2.0 osc-lib>=1.8.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 pbr!=2.1.0,>=2.0.0 # Apache-2.0 requests>=2.14.2 # Apache-2.0 six>=1.10.0 # MIT python-ironic-inspector-client-3.1.0/playbooks/0000775000175100017510000000000013232476241021630 5ustar zuulzuul00000000000000python-ironic-inspector-client-3.1.0/playbooks/legacy/0000775000175100017510000000000013232476241023074 5ustar zuulzuul00000000000000python-ironic-inspector-client-3.1.0/playbooks/legacy/python-ironic-inspector-client-tempest-dsvm/0000775000175100017510000000000013232476241033604 5ustar zuulzuul00000000000000././@LongLink0000000000000000000000000000015300000000000011214 Lustar 00000000000000python-ironic-inspector-client-3.1.0/playbooks/legacy/python-ironic-inspector-client-tempest-dsvm/run.yamlpython-ironic-inspector-client-3.1.0/playbooks/legacy/python-ironic-inspector-client-tempest-dsvm/ru0000666000175100017510000002016113232475743034165 0ustar zuulzuul00000000000000- hosts: all name: Autoconverted job legacy-tempest-dsvm-python-ironic-inspector-client from old job gate-tempest-dsvm-python-ironic-inspector-client-ubuntu-xenial tasks: - name: Ensure legacy workspace directory file: path: '{{ ansible_user_dir }}/workspace' state: directory - shell: cmd: | set -e set -x cat > clonemap.yaml << EOF clonemap: - name: openstack-infra/devstack-gate dest: devstack-gate EOF /usr/zuul-env/bin/zuul-cloner -m clonemap.yaml --cache-dir /opt/git \ git://git.openstack.org \ openstack-infra/devstack-gate executable: /bin/bash chdir: '{{ ansible_user_dir }}/workspace' environment: '{{ zuul | zuul_legacy_vars }}' - shell: cmd: | cat << 'EOF' >> ironic-extra-vars export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_DEPLOY_DRIVER_ISCSI_WITH_IPA=True" # Standardize VM size for each supported ramdisk case "tinyipa" in 'tinyipa') export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_VM_SPECS_RAM=384" export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_RAMDISK_TYPE=tinyipa" ;; 'tinyipa256') export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_VM_SPECS_RAM=256" export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_RAMDISK_TYPE=tinyipa" ;; 'coreos') export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_VM_SPECS_RAM=1280" export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_RAMDISK_TYPE=coreos" ;; # if using a ramdisk without a known good value, use the devstack # default by not exporting any value for IRONIC_VM_SPECS_RAM esac EOF chdir: '{{ ansible_user_dir }}/workspace' environment: '{{ zuul | zuul_legacy_vars }}' - shell: cmd: | cat << 'EOF' >> ironic-extra-vars export DEVSTACK_GATE_TEMPEST_REGEX="InspectorSmokeTest" EOF chdir: '{{ ansible_user_dir }}/workspace' environment: '{{ zuul | zuul_legacy_vars }}' - shell: cmd: | cat << 'EOF' >> ironic-extra-vars export PROJECTS="openstack/ironic-inspector $PROJECTS" export PROJECTS="openstack/python-ironic-inspector-client $PROJECTS" export DEVSTACK_GATE_IRONIC_INSPECTOR=1 export DEVSTACK_LOCAL_CONFIG+=$'\n'"enable_plugin ironic-inspector git://git.openstack.org/openstack/ironic-inspector" # use tempest plugin if [[ "$ZUUL_BRANCH" != "master" ]] ; then # NOTE(jroll) if this is not a patch against master, then # fetch master to install the plugin export DEVSTACK_LOCAL_CONFIG+=$'\n'"TEMPEST_PLUGINS+=' git+git://git.openstack.org/openstack/ironic-inspector'" else # on master, use the local change, so we can pick up any changes to the plugin export DEVSTACK_LOCAL_CONFIG+=$'\n'"TEMPEST_PLUGINS+=' /opt/stack/new/ironic-inspector'" fi export IRONIC_INSPECTOR_AUTO_DISCOVERY=0 if [ "$IRONIC_INSPECTOR_AUTO_DISCOVERY" == "1" ]; then # discovery test requires sudo for iptables and virsh export DEVSTACK_GATE_REMOVE_STACK_SUDO=0 # enable enroll hook export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_INSPECTOR_NODE_NOT_FOUND_HOOK=enroll" # we are deleting node from ironic for simulate node discovery, # so inspector has to sync cache asap export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_INSPECTOR_CLEAN_UP_PERIOD=5" fi # Make IPXE configuration consistent between Mitaka and Master export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_IPXE_ENABLED=True" export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_INSPECTOR_RAMDISK_ELEMENT=ironic-agent" export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_INSPECTOR_MANAGE_FIREWALL=True" EOF chdir: '{{ ansible_user_dir }}/workspace' environment: '{{ zuul | zuul_legacy_vars }}' - shell: cmd: | cat << 'EOF' >> ironic-extra-vars export DEVSTACK_PROJECT_FROM_GIT="python-ironic-inspector-client,$DEVSTACK_PROJECT_FROM_GIT" EOF chdir: '{{ ansible_user_dir }}/workspace' environment: '{{ zuul | zuul_legacy_vars }}' - shell: cmd: | cat << 'EOF' >> ironic-vars-early # use tempest plugin export DEVSTACK_LOCAL_CONFIG+=$'\n'"TEMPEST_PLUGINS+=' /opt/stack/new/ironic-tempest-plugin'" export TEMPEST_CONCURRENCY=1 EOF chdir: '{{ ansible_user_dir }}/workspace' environment: '{{ zuul | zuul_legacy_vars }}' - shell: cmd: | set -e set -x export PROJECTS="openstack/ironic $PROJECTS" export PROJECTS="openstack/ironic-lib $PROJECTS" export PROJECTS="openstack/ironic-python-agent $PROJECTS" export PROJECTS="openstack/ironic-tempest-plugin $PROJECTS" export PROJECTS="openstack/python-ironicclient $PROJECTS" export PROJECTS="openstack/pyghmi $PROJECTS" export PROJECTS="openstack/virtualbmc $PROJECTS" export PYTHONUNBUFFERED=true export DEVSTACK_GATE_TEMPEST=1 export DEVSTACK_GATE_IRONIC=1 export DEVSTACK_GATE_NEUTRON=1 export DEVSTACK_GATE_VIRT_DRIVER=ironic export DEVSTACK_GATE_CONFIGDRIVE=1 export DEVSTACK_GATE_IRONIC_DRIVER=agent_ipmitool export BRANCH_OVERRIDE=default if [ "$BRANCH_OVERRIDE" != "default" ] ; then export OVERRIDE_ZUUL_BRANCH=$BRANCH_OVERRIDE fi if [[ ! "stable/newton stable/ocata stable/pike" =~ $ZUUL_BRANCH ]] ; then export DEVSTACK_GATE_TLSPROXY=1 fi if [ "agent_ipmitool" == "pxe_snmp" ] ; then # explicitly enable pxe_snmp driver export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_ENABLED_DRIVERS=fake,pxe_snmp" fi if [ "agent_ipmitool" == "redfish" ] ; then # When deploying with redfish we need to enable the "redfish" # hardware type export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_ENABLED_HARDWARE_TYPES=redfish" fi if [ "wholedisk" == "wholedisk" ] ; then export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_TEMPEST_WHOLE_DISK_IMAGE=True" export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_VM_EPHEMERAL_DISK=0" else export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_TEMPEST_WHOLE_DISK_IMAGE=False" export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_VM_EPHEMERAL_DISK=1" fi if [ -n "" ] ; then export DEVSTACK_GATE_IRONIC_BUILD_RAMDISK=1 export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_INSPECTOR_BUILD_RAMDISK=True" export DEVSTACK_LOCAL_CONFIG+=$'\n'"USE_SUBNETPOOL=False" else export DEVSTACK_GATE_IRONIC_BUILD_RAMDISK=0 export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_INSPECTOR_BUILD_RAMDISK=False" fi if [ "bios" == "uefi" ] ; then export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_BOOT_MODE=uefi" fi export DEVSTACK_PROJECT_FROM_GIT="" export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_VM_COUNT=1" # Ensure the ironic-vars-EARLY file exists touch ironic-vars-early # Pull in the EARLY variables injected by the optional builders source ironic-vars-early export DEVSTACK_LOCAL_CONFIG+=$'\n'"enable_plugin ironic git://git.openstack.org/openstack/ironic" # Ensure the ironic-EXTRA-vars file exists touch ironic-extra-vars # Pull in the EXTRA variables injected by the optional builders source ironic-extra-vars cp devstack-gate/devstack-vm-gate-wrap.sh ./safe-devstack-vm-gate-wrap.sh ./safe-devstack-vm-gate-wrap.sh executable: /bin/bash chdir: '{{ ansible_user_dir }}/workspace' environment: '{{ zuul | zuul_legacy_vars }}' ././@LongLink0000000000000000000000000000015400000000000011215 Lustar 00000000000000python-ironic-inspector-client-3.1.0/playbooks/legacy/python-ironic-inspector-client-tempest-dsvm/post.yamlpython-ironic-inspector-client-3.1.0/playbooks/legacy/python-ironic-inspector-client-tempest-dsvm/po0000666000175100017510000000063313232475743034157 0ustar zuulzuul00000000000000- hosts: primary tasks: - name: Copy files from {{ ansible_user_dir }}/workspace/ on node synchronize: src: '{{ ansible_user_dir }}/workspace/' dest: '{{ zuul.executor.log_root }}' mode: pull copy_links: true verify_host: true rsync_opts: - --include=/logs/** - --include=*/ - --exclude=* - --prune-empty-dirs python-ironic-inspector-client-3.1.0/releasenotes/0000775000175100017510000000000013232476241022316 5ustar zuulzuul00000000000000python-ironic-inspector-client-3.1.0/releasenotes/notes/0000775000175100017510000000000013232476241023446 5ustar zuulzuul00000000000000python-ironic-inspector-client-3.1.0/releasenotes/notes/abort-introspection-428ba16c991af207.yaml0000666000175100017510000000020513232475743032477 0ustar zuulzuul00000000000000--- features: - Introduced command "openstack baremetal introspection abort " to abort running introspection for a node. python-ironic-inspector-client-3.1.0/releasenotes/notes/api-1.5-d5c64e5265fe56d3.yaml0000666000175100017510000000006413232475743027643 0ustar zuulzuul00000000000000--- other: - Bumped supported API version to 1.5. python-ironic-inspector-client-3.1.0/releasenotes/notes/data-save-9d9d4b3ac7c9851f.yaml0000666000175100017510000000016213232475743030521 0ustar zuulzuul00000000000000--- features: - Added "introspection data save" command to retrieve stored introspection data for the node. python-ironic-inspector-client-3.1.0/releasenotes/notes/osc-lib-162db03fed2bc40c.yaml0000666000175100017510000000021013232475743030217 0ustar zuulzuul00000000000000--- upgrade: - osc-lib is a package of common support modules for writing OSC plugins. So use osc-lib instead of OpenStackClient. ././@LongLink0000000000000000000000000000016200000000000011214 Lustar 00000000000000python-ironic-inspector-client-3.1.0/releasenotes/notes/reprocess-stored-introspection-data-c4910325254426c5.yamlpython-ironic-inspector-client-3.1.0/releasenotes/notes/reprocess-stored-introspection-data-c49103250000666000175100017510000000020513232475743033476 0ustar zuulzuul00000000000000--- features: - Introduced command "openstack baremetal introspection reprocess " to reprocess stored introspection data python-ironic-inspector-client-3.1.0/releasenotes/notes/api-1.6-a020f6ee5756a7ab.yaml0000666000175100017510000000006313232475743027704 0ustar zuulzuul00000000000000--- other: - Bumped supported API version to 1.6.python-ironic-inspector-client-3.1.0/releasenotes/notes/introspection-wait-a7e8fe832c3aaff9.yaml0000666000175100017510000000042013232475743032647 0ustar zuulzuul00000000000000--- upgrade: - When setting IPMI credentials is requested, the introspection commands prints a notice to stdout. This was not desired, and it is not printed to stderr instead. features: - Introspection command got --wait flag to wait for introspection finish. ././@LongLink0000000000000000000000000000015000000000000011211 Lustar 00000000000000python-ironic-inspector-client-3.1.0/releasenotes/notes/introspect-multiple-uuids-0790d57e0a0b9292.yamlpython-ironic-inspector-client-3.1.0/releasenotes/notes/introspect-multiple-uuids-0790d57e0a0b9292.y0000666000175100017510000000012213232475743033070 0ustar zuulzuul00000000000000--- features: - Allow multiple UUID's in the 'introspection start' CLI command. ././@LongLink0000000000000000000000000000015300000000000011214 Lustar 00000000000000python-ironic-inspector-client-3.1.0/releasenotes/notes/deprecate-setting-ipmi-creds-1581ddc63b273811.yamlpython-ironic-inspector-client-3.1.0/releasenotes/notes/deprecate-setting-ipmi-creds-1581ddc63b273810000666000175100017510000000034013232475743033026 0ustar zuulzuul00000000000000--- deprecations: - | Support for setting IPMI credentials via ironic-inspector is deprecated and will be removed completely in Pike. For reasoning see https://bugs.launchpad.net/ironic-inspector/+bug/1654318. python-ironic-inspector-client-3.1.0/releasenotes/notes/remove-client-64778b2011c26f6b.yaml0000666000175100017510000000023213232475743031164 0ustar zuulzuul00000000000000--- upgrade: - | The deprecated module ``ironic_inspector_client.client`` was removed, please use ``ironic_inspector_client.ClientV1`` instead. python-ironic-inspector-client-3.1.0/releasenotes/notes/no-auth-token-c486915a6168d4a3.yaml0000666000175100017510000000041413232475743031115 0ustar zuulzuul00000000000000--- upgrade: - | Support for passing ``auth_token`` to ``ClientV1`` was removed. Please create a **keystoneauth** session and pass it via the ``session`` argument instead. If no ``session`` is passed, a new session without authentication is created. ././@LongLink0000000000000000000000000000017000000000000011213 Lustar 00000000000000python-ironic-inspector-client-3.1.0/releasenotes/notes/UUID-started_at-finished_at-in-the-status-cafa48aeb5653412.yamlpython-ironic-inspector-client-3.1.0/releasenotes/notes/UUID-started_at-finished_at-in-the-status-ca0000666000175100017510000000022713232475743033637 0ustar zuulzuul00000000000000--- features: - | the introspection status returns the ``error``, ``finished``, ``finished_at``, ``links``, ``started_at`` and ``uuid`` fields ././@LongLink0000000000000000000000000000015600000000000011217 Lustar 00000000000000python-ironic-inspector-client-3.1.0/releasenotes/notes/drop-setting-ipmi-creds-feature-4965aaba75a40326.yamlpython-ironic-inspector-client-3.1.0/releasenotes/notes/drop-setting-ipmi-creds-feature-4965aaba75a40000666000175100017510000000012213232475743033311 0ustar zuulzuul00000000000000--- upgrade: - | Experimental setting IPMI credentials feature was removed. python-ironic-inspector-client-3.1.0/releasenotes/notes/interface-list-show-39cedaca3cd9db9b.yaml0000666000175100017510000000060213232475743033025 0ustar zuulzuul00000000000000--- features: - Add ``introspection interface list`` and ``introspection interface show`` commands to display stored introspection data for the node including attached switch port information. In order to get switch data, LLDP data collection must be enabled by the kernel parameter ``ipa-collect-lldp=1`` and the inspector plugin ``basic_lldp`` must be enabled. python-ironic-inspector-client-3.1.0/releasenotes/notes/.placeholder0000666000175100017510000000000013232475743025727 0ustar zuulzuul00000000000000python-ironic-inspector-client-3.1.0/releasenotes/notes/service-catalog-45466d1cfd330231.yaml0000666000175100017510000000034413232475743031457 0ustar zuulzuul00000000000000--- features: - Inspector service URL can now be fetched from the service catalog. deprecations: - Using default service URL of locahost:5050 is deprecated now. Either use the service catalog or provide an explicit URL. ././@LongLink0000000000000000000000000000015200000000000011213 Lustar 00000000000000python-ironic-inspector-client-3.1.0/releasenotes/notes/list-introspection-statuses-4ad9e7e56823e754.yamlpython-ironic-inspector-client-3.1.0/releasenotes/notes/list-introspection-statuses-4ad9e7e56823e7540000666000175100017510000000023313232475743033276 0ustar zuulzuul00000000000000--- features: - | Add support for listing introspection statuses both for the API and the CLI upgrade: - | Service max API version bumped to 1.8 python-ironic-inspector-client-3.1.0/releasenotes/notes/client-get-data-7002c1e22f14cefd.yaml0000666000175100017510000000012413232475743031561 0ustar zuulzuul00000000000000--- features: - Add client.get_data() call for getting stored introspection data. python-ironic-inspector-client-3.1.0/releasenotes/notes/api-1.2-33f0e1956b924447.yaml0000666000175100017510000000015613232475743027416 0ustar zuulzuul00000000000000--- fixes: - Fixed MAX_API_VERSION incorrectly set to (1, 0) while API 1.2 is actually fully supported. python-ironic-inspector-client-3.1.0/releasenotes/notes/no-default-uri-861f675ccb75e05d.yaml0000666000175100017510000000070613232475743031426 0ustar zuulzuul00000000000000--- upgrade: - | There is no longer a default introspection API endpoint. Previously, if no endpoint was requested and no endpoint found in the service catalog, ``127.0.0.1:5050`` was used by default. other: - | The ``ClientV1`` constructor now raises a new ``EndpointNotFound`` exception when no introspection API endpoint can be detected. Previously this condition was ignored and ``127.0.0.1:5050`` was used as a fallback. python-ironic-inspector-client-3.1.0/releasenotes/notes/rename-func-427aa11c60c2838b.yaml0000666000175100017510000000012513232475743030661 0ustar zuulzuul00000000000000--- other: - | The tox ``func`` environment has been renamed to ``functional``.././@LongLink0000000000000000000000000000015300000000000011214 Lustar 00000000000000python-ironic-inspector-client-3.1.0/releasenotes/notes/drop-osc-client-requirements-efb31b432ddbb370.yamlpython-ironic-inspector-client-3.1.0/releasenotes/notes/drop-osc-client-requirements-efb31b432ddbb370000666000175100017510000000061513232475743033405 0ustar zuulzuul00000000000000--- upgrade: - | The ``python-openstackclient`` package is no longer a requirement for ``python-ironic-inspector-client`` to be installed. If you wish to use the **ironic-inspector** cli, you should install ``python-openstackclient`` manually or use setuptools "extra" option to install both packages automatically:: pip install python-ironic-inspector-client[cli] python-ironic-inspector-client-3.1.0/releasenotes/notes/old-functions-80ddae9eaa1e7e1d.yaml0000666000175100017510000000036613232475743031645 0ustar zuulzuul00000000000000--- deprecations: - | The following functions are deprecated in favor of ``ClientV1`` methods: * ``ironic_inspector_client.introspect`` * ``ironic_inspector_client.get_status`` * ``ironic_inspector_client.server_api_versions`` ././@LongLink0000000000000000000000000000014700000000000011217 Lustar 00000000000000python-ironic-inspector-client-3.1.0/releasenotes/notes/print-import-rule-result-b5c19e9b8679849e.yamlpython-ironic-inspector-client-3.1.0/releasenotes/notes/print-import-rule-result-b5c19e9b8679849e.ya0000666000175100017510000000015513232475743033130 0ustar zuulzuul00000000000000--- upgrade: - command `openstack baremetal introspection rule import` prints created rules to stdout. python-ironic-inspector-client-3.1.0/releasenotes/notes/ks-session-ac614a9abda3e228.yaml0000666000175100017510000000034513232475743031004 0ustar zuulzuul00000000000000--- features: - Python API now supports Keystone sessions instead of only authentication token. deprecations: - Passing auth_token directly to the client object constructor is deprecated, please pass session instead. python-ironic-inspector-client-3.1.0/releasenotes/source/0000775000175100017510000000000013232476241023616 5ustar zuulzuul00000000000000python-ironic-inspector-client-3.1.0/releasenotes/source/newton.rst0000666000175100017510000000023213232475743025667 0ustar zuulzuul00000000000000=================================== Newton Series Release Notes =================================== .. release-notes:: :branch: origin/stable/newton python-ironic-inspector-client-3.1.0/releasenotes/source/pike.rst0000666000175100017510000000021713232475743025310 0ustar zuulzuul00000000000000=================================== Pike Series Release Notes =================================== .. release-notes:: :branch: stable/pike python-ironic-inspector-client-3.1.0/releasenotes/source/_templates/0000775000175100017510000000000013232476241025753 5ustar zuulzuul00000000000000python-ironic-inspector-client-3.1.0/releasenotes/source/_templates/.placeholder0000666000175100017510000000000013232475743030234 0ustar zuulzuul00000000000000python-ironic-inspector-client-3.1.0/releasenotes/source/conf.py0000666000175100017510000002207713232475743025135 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. # Ironic Inspector Release Notes documentation build configuration file, # created by sphinx-quickstart on Tue Nov 3 17:40:50 2015. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'reno.sphinxext', ] try: import openstackdocstheme extensions.append('openstackdocstheme') except ImportError: openstackdocstheme = None # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'Ironic Inspector Client Release Notes' copyright = u'2015, Ironic Inspector Developers' # Release notes are version independent. # The full version, including alpha/beta/rc tags. release = '' # The short X.Y version. version = '' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all # documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. if openstackdocstheme is not None: html_theme = 'openstackdocs' else: html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'IronicInspectorClientReleaseNotesdoc' # -- 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': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'IronicInspectorClientReleaseNotes.tex', u'Ironic Inspector Client Release Notes Documentation', u'Ironic Inspector Developers', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'ironicinspectorclientreleasenotes', u'Ironic Inspector Client Release Notes Documentation', [u'Ironic Inspector Developers'], 1) ] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'IronicInspectorClientReleaseNotes', u'Ironic Inspector Client Release Notes Documentation', u'Ironic Inspector Developers', 'IronicInspectorClientReleaseNotes', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # texinfo_no_detailmenu = False # -- Options for Internationalization output ------------------------------ locale_dirs = ['locale/'] python-ironic-inspector-client-3.1.0/releasenotes/source/liberty.rst0000666000175100017510000000021513232475743026030 0ustar zuulzuul00000000000000============================ Liberty Series Release Notes ============================ .. release-notes:: :branch: origin/stable/liberty python-ironic-inspector-client-3.1.0/releasenotes/source/ocata.rst0000666000175100017510000000023013232475743025442 0ustar zuulzuul00000000000000=================================== Ocata Series Release Notes =================================== .. release-notes:: :branch: origin/stable/ocata python-ironic-inspector-client-3.1.0/releasenotes/source/unreleased.rst0000666000175100017510000000015313232475743026506 0ustar zuulzuul00000000000000============================ Current Series Release Notes ============================ .. release-notes:: python-ironic-inspector-client-3.1.0/releasenotes/source/index.rst0000666000175100017510000000032213232475743025464 0ustar zuulzuul00000000000000====================================== Ironic Inspector Client Release Notes ====================================== .. toctree:: :maxdepth: 1 unreleased pike ocata newton mitaka liberty python-ironic-inspector-client-3.1.0/releasenotes/source/mitaka.rst0000666000175100017510000000023213232475743025623 0ustar zuulzuul00000000000000=================================== Mitaka Series Release Notes =================================== .. release-notes:: :branch: origin/stable/mitaka python-ironic-inspector-client-3.1.0/releasenotes/source/_static/0000775000175100017510000000000013232476241025244 5ustar zuulzuul00000000000000python-ironic-inspector-client-3.1.0/releasenotes/source/_static/.placeholder0000666000175100017510000000000013232475743027525 0ustar zuulzuul00000000000000python-ironic-inspector-client-3.1.0/AUTHORS0000664000175100017510000000175313232476240020702 0ustar zuulzuul00000000000000Andreas Jaeger Anton Arefiev Bob Fournier Cao Xuan Hoang Clenimar Filemon Dmitry Tantsur Dmitry Tantsur Doug Hellmann Flavio Percoco Janonymous Jay Faulkner Jim Rollenhagen John L. Villalovos Julia Kreger Luong Anh Tuan Monty Taylor OpenStack Release Bot Ruby Loo Tang Chen Tao Li Yuiko Takada Zuul chenxing dparalen fpxie gengchc2 sonu.kumar python-ironic-inspector-client-3.1.0/test-requirements.txt0000666000175100017510000000105413232475743024076 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. coverage!=4.4,>=4.0 # Apache-2.0 doc8>=0.6.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD hacking>=1.0.0 # Apache-2.0 mock>=2.0.0 # BSD requests-mock>=1.1.0 # Apache-2.0 sphinx!=1.6.6,>=1.6.2 # BSD openstackdocstheme>=1.17.0 # Apache-2.0 reno>=2.5.0 # Apache-2.0 oslo.concurrency>=3.25.0 # Apache-2.0 python-openstackclient>=3.12.0 # Apache-2.0 python-ironic-inspector-client-3.1.0/zuul.d/0000775000175100017510000000000013232476241021046 5ustar zuulzuul00000000000000python-ironic-inspector-client-3.1.0/zuul.d/project.yaml0000666000175100017510000000043513232475743023412 0ustar zuulzuul00000000000000- project: name: openstack/python-ironic-inspector-client check: jobs: - openstack-tox-functional - python-ironic-inspector-client-tempest-dsvm gate: jobs: - openstack-tox-functional - python-ironic-inspector-client-tempest-dsvm python-ironic-inspector-client-3.1.0/zuul.d/legacy-python-ironic-inspector-jobs.yaml0000666000175100017510000000157113232475743030751 0ustar zuulzuul00000000000000- job: name: python-ironic-inspector-client-tempest-dsvm parent: legacy-dsvm-base irrelevant-files: - ^(func|)test-requirements.txt$ - ^.*\.rst$ - ^doc/.*$ - ^ironic_inspector_client/test/.*$ - ^releasenotes/.*$ - ^setup.cfg$ - ^tools/.*$ - ^tox.ini$ required-projects: - openstack-infra/devstack-gate - openstack/ironic - openstack/ironic-inspector - openstack/ironic-lib - openstack/ironic-python-agent - openstack/ironic-tempest-plugin - openstack/pyghmi - openstack/python-ironic-inspector-client - openstack/python-ironicclient - openstack/tempest - openstack/virtualbmc run: playbooks/legacy/python-ironic-inspector-client-tempest-dsvm/run.yaml post-run: playbooks/legacy/python-ironic-inspector-client-tempest-dsvm/post.yaml timeout: 10800 python-ironic-inspector-client-3.1.0/doc/0000775000175100017510000000000013232476241020372 5ustar zuulzuul00000000000000python-ironic-inspector-client-3.1.0/doc/source/0000775000175100017510000000000013232476241021672 5ustar zuulzuul00000000000000python-ironic-inspector-client-3.1.0/doc/source/conf.py0000666000175100017510000000540713232475743023207 0ustar zuulzuul00000000000000# -*- coding: utf-8 -*- # # -- General configuration ---------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', ] try: import openstackdocstheme extensions.append('openstackdocstheme') except ImportError: openstackdocstheme = None repository_name = 'openstack/python-ironic-inspector-client' bug_project = 'python-ironic-inspector-client' bug_tag = '' html_last_updated_fmt = '%Y-%m-%d %H:%M' wsme_protocols = ['restjson'] # autodoc generation is a bit aggressive and a nuisance when doing heavy # text edit cycles. # execute "export SPHINX_DEBUG=1" in your terminal to disable # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. project = u'Ironic Inspector Client' copyright = u'OpenStack Foundation' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. from ironic_inspector_client import version as il_version # The full version, including alpha/beta/rc tags. release = il_version.version_info.release_string() # The short X.Y version. version = il_version.version_info.version_string() # A list of ignored prefixes for module index sorting. modindex_common_prefix = ['ironic_inspector_client'] # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). add_module_names = True # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # -- Options for HTML output -------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. if openstackdocstheme is not None: html_theme = 'openstackdocs' else: html_theme = 'default' #html_theme_path = ["."] #html_theme = '_theme' #html_static_path = ['_static'] # Output file base name for HTML help builder. htmlhelp_basename = '%sdoc' % project # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass # [howto/manual]). latex_documents = [ ( 'index', '%s.tex' % project, u'%s Documentation' % project, u'OpenStack Foundation', 'manual' ), ] python-ironic-inspector-client-3.1.0/doc/source/reference/0000775000175100017510000000000013232476241023630 5ustar zuulzuul00000000000000python-ironic-inspector-client-3.1.0/doc/source/reference/index.rst0000666000175100017510000000210613232475743025500 0ustar zuulzuul00000000000000Library User Reference ====================== To use Python API first create a ``ClientV1`` object:: import ironic_inspector_client client = ironic_inspector_client.ClientV1(session=keystone_session) This code creates a client with API version *1.0* and a given `Keystone session`_. The service URL is fetched from the service catalog in this case. See :py:class:`ironic_inspector_client.v1.ClientV1` documentation for details. .. _api-versioning: API Versioning -------------- Starting with version 2.1.0 **Ironic Inspector** supports optional API versioning. Version is a tuple (X, Y), where X is always 1 for now. The server has maximum and minimum supported versions. If no version is requested, the server assumes the maximum it's supported. Two constants are exposed for convenience: * :py:const:`ironic_inspector_client.v1.DEFAULT_API_VERSION` * :py:const:`ironic_inspector_client.v1.MAX_API_VERSION` API Reference ------------- .. toctree:: :maxdepth: 1 api/autoindex .. _Keystone session: https://docs.openstack.org/keystoneauth/latest/using-sessions.html python-ironic-inspector-client-3.1.0/doc/source/cli/0000775000175100017510000000000013232476241022441 5ustar zuulzuul00000000000000python-ironic-inspector-client-3.1.0/doc/source/cli/index.rst0000666000175100017510000001171613232475743024320 0ustar zuulzuul00000000000000Command Line Reference ====================== CLI tool is based on OpenStackClient_ with prefix ``openstack baremetal introspection``. Common arguments ---------------- All commands accept the following arguments: * ``--inspector-url`` the **Ironic Inspector** API endpoint. If missing, the endpoint will be fetched from the service catalog. * ``--inspector-api-version`` requested API version, see :ref:`api-versioning` for details. Start introspection on a node ----------------------------- :: $ openstack baremetal introspection start [--wait] NODE_ID [NODE_ID ...] * ``NODE_ID`` - Ironic node UUID or name; Note that the CLI call accepts several UUID's and will stop on the first error. .. note:: This CLI call doesn't rely on Ironic, and the introspected node will be left in ``MANAGEABLE`` state. This means that the Ironic node is not protected from other operations being performed by Ironic, which could cause inconsistency in the node's state, and lead to operational errors. With ``--wait`` flag it waits until introspection ends for all given nodes, then displays the results as a table. Query introspection status -------------------------- :: $ openstack baremetal introspection status NODE_ID * ``NODE_ID`` - Ironic node UUID or name. Returns following information about a node introspection status: * ``error``: an error string or ``None`` * ``finished``: ``True/False`` * ``finished_at``: an ISO8601 timestamp or ``None`` if not finished * ``started_at``: an ISO8601 timestamp * ``uuid``: node UUID List introspection statuses --------------------------- This command supports pagination. :: $ openstack baremetal introspection list [--marker] [--limit] * ``--marker`` the last item on the previous page, a UUID * ``--limit`` the amount of items to list, an integer, 50 by default Shows a table with the following columns: * ``Error``: an error string or ``None`` * ``Finished at``: an ISO8601 timestamp or ``None`` if not finished * ``Started at``: and ISO8601 timestamp * ``UUID``: node UUID .. note:: The server orders the introspection status items according to the ``Started at`` column, newer items first. Retrieving introspection data ----------------------------- :: $ openstack baremetal introspection data save [--file file_name] NODE_ID * ``NODE_ID`` - Ironic node UUID or name; * ``file_name`` - file name to save data to. If file name is not provided, the data is dumped to stdout. .. note:: This feature requires Swift support to be enabled in **Ironic Inspector** by setting ``[processing]store_data`` configuration option to ``swift``. Aborting introspection ---------------------- :: $ openstack baremetal introspection abort NODE_ID * ``NODE_ID`` - Ironic node UUID or name. Reprocess stored introspection data ----------------------------------- :: $ openstack baremetal introspection reprocess NODE_ID * ``NODE_ID`` - Ironic node UUID or name. .. note:: This feature requires Swift store to be enabled for **Ironic Inspector** by setting ``[processing]store_data`` configuration option to ``swift``. Introspection Rules API ----------------------- Creating a rule ~~~~~~~~~~~~~~~ :: $ openstack baremetal introspection rule import * ``rule_json`` dictionary with a rule representation, see :py:meth:`ironic_inspector_client.RulesAPI.from_json` for details. Listing all rules ~~~~~~~~~~~~~~~~~ :: $ openstack baremetal introspection rule list Returns list of short rule representations, containing only description, UUID and links. Deleting all rules ~~~~~~~~~~~~~~~~~~ :: $ openstack baremetal introspection rule purge Deleting a rule ~~~~~~~~~~~~~~~ :: $ openstack baremetal introspection rule delete * ``UUID`` rule UUID. Using names instead of UUID --------------------------- Starting with baremetal introspection API 1.5 (provided by **Ironic Inspector** 3.3.0) it's possible to use node names instead of UUIDs in all Python and CLI calls. .. _introspection rules documentation: https://docs.openstack.org/ironic-inspector/latest/usage.html#introspection-rules List interface data ------------------- :: $ openstack baremetal introspection interface list NODE_IDENT [--fields=] [--vlan=] * ``NODE_IDENT`` - Ironic node UUID or name * ``fields`` - name of one or more interface columns to display. * ``vlan`` - list only interfaces configured for this vlan id Returns a list of interface data, including attached switch information, for each interface on the node. Show interface data ------------------- :: $ openstack baremetal introspection interface show NODE_IDENT INTERFACE [--fields=] * ``NODE_IDENT`` - Ironic node UUID or name * ``INTERFACE`` - interface name on this node * ``fields`` - name of one or more interface rows to display. Show interface data, including attached switch information, for a particular node and interface. .. _OpenStackClient: https://docs.openstack.org/python-openstackclient/latest/ python-ironic-inspector-client-3.1.0/doc/source/index.rst0000666000175100017510000000046613232475743023551 0ustar zuulzuul00000000000000================================== Welcome to Ironic Inspector Client ================================== .. include:: ../../README.rst Contents ======== .. toctree:: :maxdepth: 2 cli/index reference/index Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` python-ironic-inspector-client-3.1.0/python_ironic_inspector_client.egg-info/0000775000175100017510000000000013232476241027627 5ustar zuulzuul00000000000000python-ironic-inspector-client-3.1.0/python_ironic_inspector_client.egg-info/not-zip-safe0000664000175100017510000000000113232476216032057 0ustar zuulzuul00000000000000 python-ironic-inspector-client-3.1.0/python_ironic_inspector_client.egg-info/PKG-INFO0000664000175100017510000000425413232476240030730 0ustar zuulzuul00000000000000Metadata-Version: 1.1 Name: python-ironic-inspector-client Version: 3.1.0 Summary: Python client for Ironic Inspector Home-page: https://launchpad.net/python-ironic-inspector-client Author: UNKNOWN Author-email: UNKNOWN License: Apache-2 Description-Content-Type: UNKNOWN Description: Ironic Inspector Client ======================= .. image:: https://governance.openstack.org/badges/python-ironic-inspector-client.svg :target: https://governance.openstack.org/reference/tags/index.html This is a client library and tool for `Ironic Inspector`_. * Free software: Apache license * Source: https://git.openstack.org/cgit/openstack/python-ironic-inspector-client * Documentation: https://docs.openstack.org/python-ironic-inspector-client/latest/ * Bugs: https://bugs.launchpad.net/python-ironic-inspector-client * Downloads: https://pypi.python.org/pypi/python-ironic-inspector-client Please follow usual OpenStack `Gerrit Workflow`_ to submit a patch, see `Inspector contributing guide`_ for more detail. Refer to the `HTTP API reference`_ for information on the **Ironic Inspector** HTTP API. .. _Gerrit Workflow: https://docs.openstack.org/infra/manual/developers.html#development-workflow .. _Ironic Inspector: https://pypi.python.org/pypi/ironic-inspector .. _Inspector contributing guide: https://docs.openstack.org/ironic-inspector/latest/contributor/index.html .. _HTTP API reference: https://docs.openstack.org/ironic-inspector/latest/user/http-api.html Platform: UNKNOWN Classifier: Environment :: Console Classifier: Environment :: OpenStack Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Information Technology Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 python-ironic-inspector-client-3.1.0/python_ironic_inspector_client.egg-info/requires.txt0000664000175100017510000000023713232476240032230 0ustar zuulzuul00000000000000keystoneauth1>=3.3.0 osc-lib>=1.8.0 oslo.i18n>=3.15.3 oslo.utils>=3.33.0 pbr!=2.1.0,>=2.0.0 requests>=2.14.2 six>=1.10.0 [cli] python-openstackclient>=3.12.0 python-ironic-inspector-client-3.1.0/python_ironic_inspector_client.egg-info/SOURCES.txt0000664000175100017510000000622113232476241031514 0ustar zuulzuul00000000000000AUTHORS ChangeLog LICENSE README.rst functest-requirements.txt requirements.txt setup.cfg setup.py test-requirements.txt tox.ini doc/source/conf.py doc/source/index.rst doc/source/cli/index.rst doc/source/reference/index.rst ironic_inspector_client/__init__.py ironic_inspector_client/resource.py ironic_inspector_client/shell.py ironic_inspector_client/v1.py ironic_inspector_client/version.py ironic_inspector_client/common/__init__.py ironic_inspector_client/common/http.py ironic_inspector_client/common/i18n.py ironic_inspector_client/test/__init__.py ironic_inspector_client/test/functional.py ironic_inspector_client/test/test_common_http.py ironic_inspector_client/test/test_init.py ironic_inspector_client/test/test_shell.py ironic_inspector_client/test/test_v1.py playbooks/legacy/python-ironic-inspector-client-tempest-dsvm/post.yaml playbooks/legacy/python-ironic-inspector-client-tempest-dsvm/run.yaml python_ironic_inspector_client.egg-info/PKG-INFO python_ironic_inspector_client.egg-info/SOURCES.txt python_ironic_inspector_client.egg-info/dependency_links.txt python_ironic_inspector_client.egg-info/entry_points.txt python_ironic_inspector_client.egg-info/not-zip-safe python_ironic_inspector_client.egg-info/pbr.json python_ironic_inspector_client.egg-info/requires.txt python_ironic_inspector_client.egg-info/top_level.txt releasenotes/notes/.placeholder releasenotes/notes/UUID-started_at-finished_at-in-the-status-cafa48aeb5653412.yaml releasenotes/notes/abort-introspection-428ba16c991af207.yaml releasenotes/notes/api-1.2-33f0e1956b924447.yaml releasenotes/notes/api-1.5-d5c64e5265fe56d3.yaml releasenotes/notes/api-1.6-a020f6ee5756a7ab.yaml releasenotes/notes/client-get-data-7002c1e22f14cefd.yaml releasenotes/notes/data-save-9d9d4b3ac7c9851f.yaml releasenotes/notes/deprecate-setting-ipmi-creds-1581ddc63b273811.yaml releasenotes/notes/drop-osc-client-requirements-efb31b432ddbb370.yaml releasenotes/notes/drop-setting-ipmi-creds-feature-4965aaba75a40326.yaml releasenotes/notes/interface-list-show-39cedaca3cd9db9b.yaml releasenotes/notes/introspect-multiple-uuids-0790d57e0a0b9292.yaml releasenotes/notes/introspection-wait-a7e8fe832c3aaff9.yaml releasenotes/notes/ks-session-ac614a9abda3e228.yaml releasenotes/notes/list-introspection-statuses-4ad9e7e56823e754.yaml releasenotes/notes/no-auth-token-c486915a6168d4a3.yaml releasenotes/notes/no-default-uri-861f675ccb75e05d.yaml releasenotes/notes/old-functions-80ddae9eaa1e7e1d.yaml releasenotes/notes/osc-lib-162db03fed2bc40c.yaml releasenotes/notes/print-import-rule-result-b5c19e9b8679849e.yaml releasenotes/notes/remove-client-64778b2011c26f6b.yaml releasenotes/notes/rename-func-427aa11c60c2838b.yaml releasenotes/notes/reprocess-stored-introspection-data-c4910325254426c5.yaml releasenotes/notes/service-catalog-45466d1cfd330231.yaml releasenotes/source/conf.py releasenotes/source/index.rst releasenotes/source/liberty.rst releasenotes/source/mitaka.rst releasenotes/source/newton.rst releasenotes/source/ocata.rst releasenotes/source/pike.rst releasenotes/source/unreleased.rst releasenotes/source/_static/.placeholder releasenotes/source/_templates/.placeholder zuul.d/legacy-python-ironic-inspector-jobs.yaml zuul.d/project.yamlpython-ironic-inspector-client-3.1.0/python_ironic_inspector_client.egg-info/dependency_links.txt0000664000175100017510000000000113232476240033674 0ustar zuulzuul00000000000000 python-ironic-inspector-client-3.1.0/python_ironic_inspector_client.egg-info/top_level.txt0000664000175100017510000000003013232476240032351 0ustar zuulzuul00000000000000ironic_inspector_client python-ironic-inspector-client-3.1.0/python_ironic_inspector_client.egg-info/entry_points.txt0000664000175100017510000000225613232476240033131 0ustar zuulzuul00000000000000[openstack.baremetal_introspection.v1] baremetal_introspection_abort = ironic_inspector_client.shell:AbortCommand baremetal_introspection_data_save = ironic_inspector_client.shell:DataSaveCommand baremetal_introspection_interface_list = ironic_inspector_client.shell:InterfaceListCommand baremetal_introspection_interface_show = ironic_inspector_client.shell:InterfaceShowCommand baremetal_introspection_list = ironic_inspector_client.shell:StatusListCommand baremetal_introspection_reprocess = ironic_inspector_client.shell:ReprocessCommand baremetal_introspection_rule_delete = ironic_inspector_client.shell:RuleDeleteCommand baremetal_introspection_rule_import = ironic_inspector_client.shell:RuleImportCommand baremetal_introspection_rule_list = ironic_inspector_client.shell:RuleListCommand baremetal_introspection_rule_purge = ironic_inspector_client.shell:RulePurgeCommand baremetal_introspection_rule_show = ironic_inspector_client.shell:RuleShowCommand baremetal_introspection_start = ironic_inspector_client.shell:StartCommand baremetal_introspection_status = ironic_inspector_client.shell:StatusCommand [openstack.cli.extension] baremetal-introspection = ironic_inspector_client.shell python-ironic-inspector-client-3.1.0/python_ironic_inspector_client.egg-info/pbr.json0000664000175100017510000000005613232476240031305 0ustar zuulzuul00000000000000{"git_version": "c82b59f", "is_release": true}