python-ironic-inspector-client-3.4.0/0000775000175000017500000000000013364122251017621 5ustar zuulzuul00000000000000python-ironic-inspector-client-3.4.0/lower-constraints.txt0000666000175000017500000000276013364122067024073 0ustar zuulzuul00000000000000alabaster==0.7.10 appdirs==1.3.0 asn1crypto==0.23.0 Babel==2.3.4 cffi==1.7.0 chardet==3.0.4 cliff==2.8.0 cmd2==0.8.0 coverage==4.0 cryptography==2.1 debtcollector==1.2.0 decorator==3.4.0 deprecation==1.0 doc8==0.6.0 docutils==0.11 dogpile.cache==0.6.2 dulwich==0.15.0 extras==1.0.0 fasteners==0.7.0 fixtures==3.0.0 flake8==2.5.5 hacking==1.0.0 idna==2.6 imagesize==0.7.1 iso8601==0.1.11 Jinja2==2.10 jmespath==0.9.0 jsonpatch==1.16 jsonpointer==1.13 jsonschema==2.6.0 keystoneauth1==3.4.0 linecache2==1.0.0 MarkupSafe==1.0 mccabe==0.2.1 mock==2.0.0 monotonic==0.6 msgpack-python==0.4.0 munch==2.1.0 netaddr==0.7.18 netifaces==0.10.4 openstackdocstheme==1.18.1 openstacksdk==0.11.2 os-client-config==1.28.0 os-service-types==1.2.0 osc-lib==1.8.0 oslo.concurrency==3.25.0 oslo.config==5.2.0 oslo.i18n==3.15.3 oslo.serialization==2.18.0 oslo.utils==3.33.0 pbr==2.0.0 pep8==1.5.7 positional==1.2.1 prettytable==0.7.2 pycparser==2.18 pyflakes==0.8.1 Pygments==2.2.0 pyOpenSSL==17.1.0 pyparsing==2.1.0 pyperclip==1.5.27 python-cinderclient==3.3.0 python-glanceclient==2.8.0 python-keystoneclient==3.8.0 python-mimeparse==1.6.0 python-novaclient==9.1.0 python-openstackclient==3.12.0 pytz==2013.6 PyYAML==3.12 reno==2.5.0 requests==2.14.2 requests-mock==1.2.0 requestsexceptions==1.2.0 restructuredtext-lint==1.1.1 rfc3986==0.3.1 simplejson==3.5.1 six==1.10.0 snowballstemmer==1.2.1 Sphinx==1.6.2 sphinxcontrib-websupport==1.0.1 stevedore==1.20.0 testtools==2.2.0 traceback2==1.4.0 unittest2==1.1.0 warlock==1.2.0 wrapt==1.7.0 python-ironic-inspector-client-3.4.0/requirements.txt0000666000175000017500000000066213364122067023120 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.4.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 PyYAML>=3.12 # MIT requests>=2.14.2 # Apache-2.0 six>=1.10.0 # MIT python-ironic-inspector-client-3.4.0/README.rst0000666000175000017500000000237113364122067021322 0ustar zuulzuul00000000000000Ironic Inspector Client ======================= .. image:: https://governance.openstack.org/tc/badges/python-ironic-inspector-client.svg :target: https://governance.openstack.org/tc/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://storyboard.openstack.org/#!/project/958 * Downloads: https://pypi.org/project/python-ironic-inspector-client * Release Notes: https://docs.openstack.org/releasenotes/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://docs.openstack.org/ironic-inspector/latest/ .. _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.4.0/setup.py0000666000175000017500000000200613364122067021340 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.4.0/tox.ini0000666000175000017500000000447613364122067021156 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 = python3 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:functional-py35] basepython = python3 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 = python3 deps = {[testenv:functional]deps} commands = {[testenv:functional]commands} [testenv:venv] basepython = python3 deps = -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt commands = {posargs} [testenv:releasenotes] basepython = python3 deps = -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [testenv:docs] basepython = python3 deps = -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt commands = sphinx-build -W -b html doc/source doc/build/html [flake8] max-complexity=15 [hacking] import_exceptions = ironic_inspector_client.common.i18n [testenv:lower-constraints] basepython = python3 deps = -c{toxinidir}/lower-constraints.txt -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt python-ironic-inspector-client-3.4.0/AUTHORS0000664000175000017500000000236313364122251020675 0ustar zuulzuul00000000000000Andreas Jaeger Anton Arefiev Bob Fournier Cao Xuan Hoang Clenimar Filemon Dmitry Tantsur Dmitry Tantsur Doug Hellmann Flavio Percoco James E. Blair Janonymous Jay Faulkner Jim Rollenhagen John L. Villalovos Julia Kreger Luong Anh Tuan Monty Taylor Nguyen Hung Phuong OpenStack Release Bot Ruby Loo Sean McGinnis Tang Chen Tao Li Yuiko Takada chenxing dparalen fpxie gengchc2 ghanshyam jacky06 jinxingfang melissaml sonu.kumar wu.chunyang python-ironic-inspector-client-3.4.0/ChangeLog0000664000175000017500000002116313364122251021376 0ustar zuulzuul00000000000000CHANGES ======= 3.4.0 ----- * Change basepython to python3 * Support loading introspection rules from YAML files * add python 3.6 unit test job * switch documentation job to new PTI * import zuul job settings from project-config * CI: stop trying to pull the tempest plugin from ironic-inspector * Update reno for stable/rocky * Fix setup.cfg for release 3.3.0 ----- * Fix errors in package metadata * Update the home-page link * Provide proper error message if interface name is invalid * Add release note link in README * Support passing manage\_boot argument in Python API * Follow the new PTI for document build * fix tox python3 overrides * add lower-constraints job * Trivial: Update pypi url to new url 3.2.0 ----- * Gate fix: Cap hacking to avoid gate failure * Update bug tracker URL * Updated from global requirements * Switch the CI to hardware types and clean up playbook * Use the regular inspector tests in the CI * Update links in README * Add the api help information * Clean imports in code * Updated from global requirements * Zuul: Remove project name * Updated from global requirements * Update reno for stable/queens 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.4.0/playbooks/0000775000175000017500000000000013364122251021624 5ustar zuulzuul00000000000000python-ironic-inspector-client-3.4.0/playbooks/legacy/0000775000175000017500000000000013364122251023070 5ustar zuulzuul00000000000000python-ironic-inspector-client-3.4.0/playbooks/legacy/python-ironic-inspector-client-tempest-dsvm/0000775000175000017500000000000013364122251033600 5ustar zuulzuul00000000000000././@LongLink0000000000000000000000000000015400000000000011215 Lustar 00000000000000python-ironic-inspector-client-3.4.0/playbooks/legacy/python-ironic-inspector-client-tempest-dsvm/post.yamlpython-ironic-inspector-client-3.4.0/playbooks/legacy/python-ironic-inspector-client-tempest-dsvm/po0000666000175000017500000000063313364122067034152 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 ././@LongLink0000000000000000000000000000015300000000000011214 Lustar 00000000000000python-ironic-inspector-client-3.4.0/playbooks/legacy/python-ironic-inspector-client-tempest-dsvm/run.yamlpython-ironic-inspector-client-3.4.0/playbooks/legacy/python-ironic-inspector-client-tempest-dsvm/ru0000666000175000017500000001201413364122067034156 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_VM_SPECS_RAM=384" export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_RAMDISK_TYPE=tinyipa" EOF chdir: '{{ ansible_user_dir }}/workspace' environment: '{{ zuul | zuul_legacy_vars }}' - shell: cmd: | cat << 'EOF' >> ironic-extra-vars export DEVSTACK_GATE_TEMPEST_REGEX="InspectorBasicTest" 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" 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=ipmi export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_DEFAULT_DEPLOY_INTERFACE=direct" # direct deploy requires Swift temporary URLs export DEVSTACK_LOCAL_CONFIG+=$'\n'"SWIFT_ENABLE_TEMPURLS=True" export DEVSTACK_LOCAL_CONFIG+=$'\n'"SWIFT_TEMPURL_KEY=secretkey" 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 export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_TEMPEST_WHOLE_DISK_IMAGE=True" export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_VM_EPHEMERAL_DISK=0" export DEVSTACK_GATE_IRONIC_BUILD_RAMDISK=0 export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_INSPECTOR_BUILD_RAMDISK=False" 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 }}' python-ironic-inspector-client-3.4.0/setup.cfg0000666000175000017500000000403613364122251021447 0ustar zuulzuul00000000000000[metadata] name = python-ironic-inspector-client summary = Python client for Ironic Inspector description-file = README.rst author = OpenStack author-email = openstack-dev@lists.openstack.org home-page = https://docs.openstack.org/python-ironic-inspector-client/latest/ 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 [extras] cli = python-openstackclient>=3.12.0 # Apache-2.0 [egg_info] tag_build = tag_date = 0 python-ironic-inspector-client-3.4.0/LICENSE0000666000175000017500000002613613364122067020645 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.4.0/test-requirements.txt0000666000175000017500000000072613364122067024076 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,<1.1.0 # Apache-2.0 mock>=2.0.0 # BSD requests-mock>=1.2.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.4.0/doc/0000775000175000017500000000000013364122251020366 5ustar zuulzuul00000000000000python-ironic-inspector-client-3.4.0/doc/requirements.txt0000666000175000017500000000021013364122067023652 0ustar zuulzuul00000000000000sphinx!=1.6.6,!=1.6.7,>=1.6.2 # BSD openstackdocstheme>=1.18.1 # Apache-2.0 reno>=2.5.0 # Apache-2.0 sphinxcontrib-apidoc>=0.2.0 # BSD python-ironic-inspector-client-3.4.0/doc/source/0000775000175000017500000000000013364122251021666 5ustar zuulzuul00000000000000python-ironic-inspector-client-3.4.0/doc/source/reference/0000775000175000017500000000000013364122251023624 5ustar zuulzuul00000000000000python-ironic-inspector-client-3.4.0/doc/source/reference/index.rst0000666000175000017500000000216513364122067025500 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.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.DEFAULT_API_VERSION` * :py:const:`ironic_inspector_client.MAX_API_VERSION` API Reference ------------- .. toctree:: :maxdepth: 2 api/ironic_inspector_client .. toctree:: :hidden: api/modules .. _Keystone session: https://docs.openstack.org/keystoneauth/latest/using-sessions.html python-ironic-inspector-client-3.4.0/doc/source/index.rst0000666000175000017500000000046613364122067023544 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.4.0/doc/source/conf.py0000666000175000017500000000553213364122067023201 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 = ['sphinxcontrib.apidoc', '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'] # sphinxcontrib.apidoc options apidoc_module_dir = '../../ironic_inspector_client' apidoc_output_dir = 'reference/api' apidoc_excluded_paths = [ 'test/*', 'test', 'common/i18n*', 'shell*'] apidoc_separate_modules = True # 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.4.0/doc/source/cli/0000775000175000017500000000000013364122251022435 5ustar zuulzuul00000000000000python-ironic-inspector-client-3.4.0/doc/source/cli/index.rst0000666000175000017500000001171613364122067024313 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.4.0/zuul.d/0000775000175000017500000000000013364122251021042 5ustar zuulzuul00000000000000python-ironic-inspector-client-3.4.0/zuul.d/project.yaml0000666000175000017500000000120013364122067023374 0ustar zuulzuul00000000000000- project: templates: - openstack-python-jobs - openstack-python35-jobs - openstack-python36-jobs - publish-openstack-docs-pti - check-requirements - release-notes-jobs-python3 - openstackclient-plugin-jobs check: jobs: - openstack-tox-functional - openstack-tox-functional-py35 - python-ironic-inspector-client-tempest-dsvm - openstack-tox-lower-constraints gate: jobs: - openstack-tox-functional - openstack-tox-functional-py35 - python-ironic-inspector-client-tempest-dsvm - openstack-tox-lower-constraints python-ironic-inspector-client-3.4.0/zuul.d/legacy-python-ironic-inspector-jobs.yaml0000666000175000017500000000157113364122067030744 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.4.0/functest-requirements.txt0000666000175000017500000000047513364122067024753 0ustar zuulzuul00000000000000# NOTE(jroll) these are pinned to the same SHA, update when needed. git+git://git.openstack.org/openstack/ironic-inspector@be3f7eec18ad13dd6d5a94fe2963b45456fad19e#egg=ironic-inspector -r https://git.openstack.org/cgit/openstack/ironic-inspector/plain/test-requirements.txt?h=be3f7eec18ad13dd6d5a94fe2963b45456fad19e python-ironic-inspector-client-3.4.0/python_ironic_inspector_client.egg-info/0000775000175000017500000000000013364122251027623 5ustar zuulzuul00000000000000python-ironic-inspector-client-3.4.0/python_ironic_inspector_client.egg-info/requires.txt0000664000175000017500000000052313364122251032223 0ustar zuulzuul00000000000000keystoneauth1>=3.4.0 osc-lib>=1.8.0 oslo.i18n>=3.15.3 oslo.utils>=3.33.0 pbr!=2.1.0,>=2.0.0 PyYAML>=3.12 requests>=2.14.2 six>=1.10.0 [cli] python-openstackclient>=3.12.0 [test] coverage!=4.4,>=4.0 doc8>=0.6.0 fixtures>=3.0.0 hacking<1.1.0,>=1.0.0 mock>=2.0.0 requests-mock>=1.2.0 oslo.concurrency>=3.25.0 python-openstackclient>=3.12.0 python-ironic-inspector-client-3.4.0/python_ironic_inspector_client.egg-info/entry_points.txt0000664000175000017500000000225613364122251033126 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.4.0/python_ironic_inspector_client.egg-info/pbr.json0000664000175000017500000000005613364122251031302 0ustar zuulzuul00000000000000{"git_version": "d057591", "is_release": true}python-ironic-inspector-client-3.4.0/python_ironic_inspector_client.egg-info/SOURCES.txt0000664000175000017500000000666513364122251031524 0ustar zuulzuul00000000000000AUTHORS ChangeLog LICENSE README.rst functest-requirements.txt lower-constraints.txt requirements.txt setup.cfg setup.py test-requirements.txt tox.ini doc/requirements.txt 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/change-error-msg-invalid-interface-4b6b70b92c27d6f6.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/manage-boot-3d77762952b354a1.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/rules-import-yaml-815ebc6ca6fe28b9.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/queens.rst releasenotes/source/rocky.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.4.0/python_ironic_inspector_client.egg-info/dependency_links.txt0000664000175000017500000000000113364122251033671 0ustar zuulzuul00000000000000 python-ironic-inspector-client-3.4.0/python_ironic_inspector_client.egg-info/top_level.txt0000664000175000017500000000003013364122251032346 0ustar zuulzuul00000000000000ironic_inspector_client python-ironic-inspector-client-3.4.0/python_ironic_inspector_client.egg-info/not-zip-safe0000664000175000017500000000000113364122251032051 0ustar zuulzuul00000000000000 python-ironic-inspector-client-3.4.0/python_ironic_inspector_client.egg-info/PKG-INFO0000664000175000017500000000447313364122251030730 0ustar zuulzuul00000000000000Metadata-Version: 2.1 Name: python-ironic-inspector-client Version: 3.4.0 Summary: Python client for Ironic Inspector Home-page: https://docs.openstack.org/python-ironic-inspector-client/latest/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: Apache-2 Description: Ironic Inspector Client ======================= .. image:: https://governance.openstack.org/tc/badges/python-ironic-inspector-client.svg :target: https://governance.openstack.org/tc/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://storyboard.openstack.org/#!/project/958 * Downloads: https://pypi.org/project/python-ironic-inspector-client * Release Notes: https://docs.openstack.org/releasenotes/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://docs.openstack.org/ironic-inspector/latest/ .. _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 Provides-Extra: cli Provides-Extra: test python-ironic-inspector-client-3.4.0/PKG-INFO0000664000175000017500000000447313364122251020726 0ustar zuulzuul00000000000000Metadata-Version: 2.1 Name: python-ironic-inspector-client Version: 3.4.0 Summary: Python client for Ironic Inspector Home-page: https://docs.openstack.org/python-ironic-inspector-client/latest/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: Apache-2 Description: Ironic Inspector Client ======================= .. image:: https://governance.openstack.org/tc/badges/python-ironic-inspector-client.svg :target: https://governance.openstack.org/tc/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://storyboard.openstack.org/#!/project/958 * Downloads: https://pypi.org/project/python-ironic-inspector-client * Release Notes: https://docs.openstack.org/releasenotes/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://docs.openstack.org/ironic-inspector/latest/ .. _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 Provides-Extra: cli Provides-Extra: test python-ironic-inspector-client-3.4.0/ironic_inspector_client/0000775000175000017500000000000013364122251024530 5ustar zuulzuul00000000000000python-ironic-inspector-client-3.4.0/ironic_inspector_client/resource.py0000666000175000017500000000752713364122067026753 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.4.0/ironic_inspector_client/version.py0000666000175000017500000000127313364122067026601 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.4.0/ironic_inspector_client/v1.py0000666000175000017500000004505013364122067025443 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.""" import collections 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, 13) """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, manage_boot=None): """Start introspection for a node. :param uuid: node UUID or name :param manage_boot: whether to manage boot during introspection of this node. If it is None (the default), then this argument is not passed to API and the server default is used instead. :raises: :py:class:`ironic_inspector_client.ClientError` on error reported from a server :raises: :py:class:`ironic_inspector_client.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) params = {} if manage_boot is not None: params['manage_boot'] = str(int(manage_boot)) self.request('post', '/introspection/%s' % uuid, params=params) def reprocess(self, uuid): """Reprocess stored introspection data. If swift support is disabled, introspection data won't be stored, this request will return error response with 404 code. :param uuid: node UUID or name. :raises: :py:class:`ironic_inspector_client.ClientError` on error reported from a server :raises: :py:class:`ironic_inspector_client.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:`ironic_inspector_client.ClientError` on error reported from a server :raises: :py:class:`ironic_inspector_client.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:`ironic_inspector_client.ClientError` on error reported from a server :raises: :py:class:`ironic_inspector_client.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:`ironic_inspector_client.WaitTimeoutError` on timeout :raises: :py:class:`ironic_inspector_client.ClientError` on error reported from a server :raises: :py:class:`ironic_inspector_client.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. If swift support is disabled, introspection data won't be stored, this request will return error response with 404 code. :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:`ironic_inspector_client.ClientError` on error reported from a server :raises: :py:class:`ironic_inspector_client.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:`ironic_inspector_client.ClientError` on error reported from a server :raises: :py:class:`ironic_inspector_client.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 :raises: ValueError if interface is not found. """ # Use OrderedDict to maintain order of user-entered fields iface_data = collections.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: raise ValueError( _("Interface %s was not found on this node") % interface) # 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:`ironic_inspector_client.ClientError` on error reported from a server :raises: :py:class:`ironic_inspector_client.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:`ironic_inspector_client.ClientError` on error reported from a server :raises: :py:class:`ironic_inspector_client.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:`ironic_inspector_client.ClientError` on error reported from a server :raises: :py:class:`ironic_inspector_client.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:`ironic_inspector_client.ClientError` on error reported from a server :raises: :py:class:`ironic_inspector_client.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:`ironic_inspector_client.ClientError` on error reported from a server :raises: :py:class:`ironic_inspector_client.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:`ironic_inspector_client.ClientError` on error reported from a server :raises: :py:class:`ironic_inspector_client.VersionNotSupported` if requested api_version is not supported """ self._request('delete', '/rules') python-ironic-inspector-client-3.4.0/ironic_inspector_client/test/0000775000175000017500000000000013364122251025507 5ustar zuulzuul00000000000000python-ironic-inspector-client-3.4.0/ironic_inspector_client/test/test_shell.py0000666000175000017500000004763113364122067030251 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 import collections 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_import_yaml(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 = collections.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 = collections.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.4.0/ironic_inspector_client/test/__init__.py0000666000175000017500000000000013364122067027615 0ustar zuulzuul00000000000000python-ironic-inspector-client-3.4.0/ironic_inspector_client/test/functional.py0000666000175000017500000004577413364122067030253 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 import six 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): if six.PY3: msg_missing_param = 'the following arguments are required' else: msg_missing_param = 'too few arguments' err = self.run_cli('start', expect_error=True) self.assertIn(msg_missing_param, err) err = self.run_cli('status', expect_error=True) self.assertIn(msg_missing_param, 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(msg_missing_param, 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(mode='w') 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(mode='w') 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) self.assertIn(expected_eth1, res) self.assertIn(expected_eth3, res) # Filter on vlan res = self.run_cli('interface', 'list', self.uuid, '--vlan', '106', parse_json=True) self.assertIn(expected_eth3, res) # Select fields res = self.run_cli('interface', 'list', self.uuid, '--fields', 'switch_port_mtu', parse_json=True) self.assertIn({u'Switch Port MTU': 1514}, res) self.assertIn({u'Switch Port MTU': 9216}, res) @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.4.0/ironic_inspector_client/test/test_common_http.py0000666000175000017500000001600613364122067031461 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.4.0/ironic_inspector_client/test/test_init.py0000666000175000017500000000217713364122067030101 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.4.0/ironic_inspector_client/test/test_v1.py0000666000175000017500000004115713364122067027465 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 collections 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, params={}) def test_invalid_input(self, mock_req): self.assertRaises(TypeError, self.get_client().introspect, 42) def test_manage_boot(self, mock_req): self.get_client().introspect(self.uuid, manage_boot=False) mock_req.assert_called_once_with( 'post', '/introspection/%s' % self.uuid, params={'manage_boot': '0'}) @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 = collections.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) def test_invalid_interface(self, mock_req): mock_req.return_value.json.return_value = self.inspector_db self.assertRaises(ValueError, self.get_client().get_interface_data, self.uuid, "em55", ["node_ident", "interface"]) python-ironic-inspector-client-3.4.0/ironic_inspector_client/common/0000775000175000017500000000000013364122251026020 5ustar zuulzuul00000000000000python-ironic-inspector-client-3.4.0/ironic_inspector_client/common/http.py0000666000175000017500000002164113364122067027364 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.4.0/ironic_inspector_client/common/__init__.py0000666000175000017500000000000013364122067030126 0ustar zuulzuul00000000000000python-ironic-inspector-client-3.4.0/ironic_inspector_client/common/i18n.py0000666000175000017500000000144213364122067027161 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.4.0/ironic_inspector_client/__init__.py0000666000175000017500000000150513364122067026651 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 __all__ = ['ClientV1', 'DEFAULT_API_VERSION', 'MAX_API_VERSION', 'ClientError', 'EndpointNotFound', 'VersionNotSupported'] python-ironic-inspector-client-3.4.0/ironic_inspector_client/shell.py0000666000175000017500000003065513364122067026231 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 yaml 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/YAML file.""" COLUMNS = ("UUID", "Description") def get_parser(self, prog_name): parser = super(RuleImportCommand, self).get_parser(prog_name) parser.add_argument('file', help='JSON or YAML 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 = yaml.safe_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.4.0/releasenotes/0000775000175000017500000000000013364122251022312 5ustar zuulzuul00000000000000python-ironic-inspector-client-3.4.0/releasenotes/source/0000775000175000017500000000000013364122251023612 5ustar zuulzuul00000000000000python-ironic-inspector-client-3.4.0/releasenotes/source/_static/0000775000175000017500000000000013364122251025240 5ustar zuulzuul00000000000000python-ironic-inspector-client-3.4.0/releasenotes/source/_static/.placeholder0000666000175000017500000000000013364122067027520 0ustar zuulzuul00000000000000python-ironic-inspector-client-3.4.0/releasenotes/source/_templates/0000775000175000017500000000000013364122251025747 5ustar zuulzuul00000000000000python-ironic-inspector-client-3.4.0/releasenotes/source/_templates/.placeholder0000666000175000017500000000000013364122067030227 0ustar zuulzuul00000000000000python-ironic-inspector-client-3.4.0/releasenotes/source/mitaka.rst0000666000175000017500000000023213364122067025616 0ustar zuulzuul00000000000000=================================== Mitaka Series Release Notes =================================== .. release-notes:: :branch: origin/stable/mitaka python-ironic-inspector-client-3.4.0/releasenotes/source/rocky.rst0000666000175000017500000000022113364122067025475 0ustar zuulzuul00000000000000=================================== Rocky Series Release Notes =================================== .. release-notes:: :branch: stable/rocky python-ironic-inspector-client-3.4.0/releasenotes/source/ocata.rst0000666000175000017500000000023013364122067025435 0ustar zuulzuul00000000000000=================================== Ocata Series Release Notes =================================== .. release-notes:: :branch: origin/stable/ocata python-ironic-inspector-client-3.4.0/releasenotes/source/newton.rst0000666000175000017500000000023213364122067025662 0ustar zuulzuul00000000000000=================================== Newton Series Release Notes =================================== .. release-notes:: :branch: origin/stable/newton python-ironic-inspector-client-3.4.0/releasenotes/source/pike.rst0000666000175000017500000000021713364122067025303 0ustar zuulzuul00000000000000=================================== Pike Series Release Notes =================================== .. release-notes:: :branch: stable/pike python-ironic-inspector-client-3.4.0/releasenotes/source/index.rst0000666000175000017500000000034513364122067025464 0ustar zuulzuul00000000000000====================================== Ironic Inspector Client Release Notes ====================================== .. toctree:: :maxdepth: 1 unreleased rocky queens pike ocata newton mitaka liberty python-ironic-inspector-client-3.4.0/releasenotes/source/unreleased.rst0000666000175000017500000000015313364122067026501 0ustar zuulzuul00000000000000============================ Current Series Release Notes ============================ .. release-notes:: python-ironic-inspector-client-3.4.0/releasenotes/source/conf.py0000666000175000017500000002207713364122067025130 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.4.0/releasenotes/source/liberty.rst0000666000175000017500000000021513364122067026023 0ustar zuulzuul00000000000000============================ Liberty Series Release Notes ============================ .. release-notes:: :branch: origin/stable/liberty python-ironic-inspector-client-3.4.0/releasenotes/source/queens.rst0000666000175000017500000000022313364122067025650 0ustar zuulzuul00000000000000=================================== Queens Series Release Notes =================================== .. release-notes:: :branch: stable/queens python-ironic-inspector-client-3.4.0/releasenotes/notes/0000775000175000017500000000000013364122251023442 5ustar zuulzuul00000000000000python-ironic-inspector-client-3.4.0/releasenotes/notes/rules-import-yaml-815ebc6ca6fe28b9.yaml0000666000175000017500000000020313364122067032317 0ustar zuulzuul00000000000000--- features: - | Supports importing introspection rules from YAML files (in addition to already supported JSON format). python-ironic-inspector-client-3.4.0/releasenotes/notes/client-get-data-7002c1e22f14cefd.yaml0000666000175000017500000000012413364122067031554 0ustar zuulzuul00000000000000--- features: - Add client.get_data() call for getting stored introspection data. python-ironic-inspector-client-3.4.0/releasenotes/notes/old-functions-80ddae9eaa1e7e1d.yaml0000666000175000017500000000036613364122067031640 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`` python-ironic-inspector-client-3.4.0/releasenotes/notes/remove-client-64778b2011c26f6b.yaml0000666000175000017500000000023213364122067031157 0ustar zuulzuul00000000000000--- upgrade: - | The deprecated module ``ironic_inspector_client.client`` was removed, please use ``ironic_inspector_client.ClientV1`` instead. python-ironic-inspector-client-3.4.0/releasenotes/notes/no-auth-token-c486915a6168d4a3.yaml0000666000175000017500000000041413364122067031110 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. python-ironic-inspector-client-3.4.0/releasenotes/notes/introspection-wait-a7e8fe832c3aaff9.yaml0000666000175000017500000000042013364122067032642 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. python-ironic-inspector-client-3.4.0/releasenotes/notes/service-catalog-45466d1cfd330231.yaml0000666000175000017500000000034413364122067031452 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. python-ironic-inspector-client-3.4.0/releasenotes/notes/interface-list-show-39cedaca3cd9db9b.yaml0000666000175000017500000000060213364122067033020 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. ././@LongLink0000000000000000000000000000016200000000000011214 Lustar 00000000000000python-ironic-inspector-client-3.4.0/releasenotes/notes/reprocess-stored-introspection-data-c4910325254426c5.yamlpython-ironic-inspector-client-3.4.0/releasenotes/notes/reprocess-stored-introspection-data-c49103250000666000175000017500000000020513364122067033471 0ustar zuulzuul00000000000000--- features: - Introduced command "openstack baremetal introspection reprocess " to reprocess stored introspection data ././@LongLink0000000000000000000000000000017000000000000011213 Lustar 00000000000000python-ironic-inspector-client-3.4.0/releasenotes/notes/UUID-started_at-finished_at-in-the-status-cafa48aeb5653412.yamlpython-ironic-inspector-client-3.4.0/releasenotes/notes/UUID-started_at-finished_at-in-the-status-ca0000666000175000017500000000022713364122067033632 0ustar zuulzuul00000000000000--- features: - | the introspection status returns the ``error``, ``finished``, ``finished_at``, ``links``, ``started_at`` and ``uuid`` fields ././@LongLink0000000000000000000000000000015300000000000011214 Lustar 00000000000000python-ironic-inspector-client-3.4.0/releasenotes/notes/drop-osc-client-requirements-efb31b432ddbb370.yamlpython-ironic-inspector-client-3.4.0/releasenotes/notes/drop-osc-client-requirements-efb31b432ddbb370000666000175000017500000000061513364122067033400 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.4.0/releasenotes/notes/no-default-uri-861f675ccb75e05d.yaml0000666000175000017500000000070613364122067031421 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. ././@LongLink0000000000000000000000000000014700000000000011217 Lustar 00000000000000python-ironic-inspector-client-3.4.0/releasenotes/notes/print-import-rule-result-b5c19e9b8679849e.yamlpython-ironic-inspector-client-3.4.0/releasenotes/notes/print-import-rule-result-b5c19e9b8679849e.ya0000666000175000017500000000015513364122067033123 0ustar zuulzuul00000000000000--- upgrade: - command `openstack baremetal introspection rule import` prints created rules to stdout. python-ironic-inspector-client-3.4.0/releasenotes/notes/api-1.2-33f0e1956b924447.yaml0000666000175000017500000000015613364122067027411 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.4.0/releasenotes/notes/data-save-9d9d4b3ac7c9851f.yaml0000666000175000017500000000016213364122067030514 0ustar zuulzuul00000000000000--- features: - Added "introspection data save" command to retrieve stored introspection data for the node. ././@LongLink0000000000000000000000000000016100000000000011213 Lustar 00000000000000python-ironic-inspector-client-3.4.0/releasenotes/notes/change-error-msg-invalid-interface-4b6b70b92c27d6f6.yamlpython-ironic-inspector-client-3.4.0/releasenotes/notes/change-error-msg-invalid-interface-4b6b70b920000666000175000017500000000036213364122067033336 0ustar zuulzuul00000000000000--- fixes: - The error message returned when running the `openstack baremetal introspection interface show` command with an interface not associated with the node has been fixed. It now indicates that the interface was invalid. python-ironic-inspector-client-3.4.0/releasenotes/notes/manage-boot-3d77762952b354a1.yaml0000666000175000017500000000015013364122067030523 0ustar zuulzuul00000000000000--- features: - Adds Python library support for passing ``manage_boot`` to the introspection API. python-ironic-inspector-client-3.4.0/releasenotes/notes/ks-session-ac614a9abda3e228.yaml0000666000175000017500000000034513364122067030777 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. ././@LongLink0000000000000000000000000000015300000000000011214 Lustar 00000000000000python-ironic-inspector-client-3.4.0/releasenotes/notes/deprecate-setting-ipmi-creds-1581ddc63b273811.yamlpython-ironic-inspector-client-3.4.0/releasenotes/notes/deprecate-setting-ipmi-creds-1581ddc63b273810000666000175000017500000000034013364122067033021 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.4.0/releasenotes/notes/rename-func-427aa11c60c2838b.yaml0000666000175000017500000000012513364122067030654 0ustar zuulzuul00000000000000--- other: - | The tox ``func`` environment has been renamed to ``functional``.python-ironic-inspector-client-3.4.0/releasenotes/notes/osc-lib-162db03fed2bc40c.yaml0000666000175000017500000000021013364122067030212 0ustar zuulzuul00000000000000--- upgrade: - osc-lib is a package of common support modules for writing OSC plugins. So use osc-lib instead of OpenStackClient. python-ironic-inspector-client-3.4.0/releasenotes/notes/abort-introspection-428ba16c991af207.yaml0000666000175000017500000000020513364122067032472 0ustar zuulzuul00000000000000--- features: - Introduced command "openstack baremetal introspection abort " to abort running introspection for a node. ././@LongLink0000000000000000000000000000015000000000000011211 Lustar 00000000000000python-ironic-inspector-client-3.4.0/releasenotes/notes/introspect-multiple-uuids-0790d57e0a0b9292.yamlpython-ironic-inspector-client-3.4.0/releasenotes/notes/introspect-multiple-uuids-0790d57e0a0b9292.y0000666000175000017500000000012213364122067033063 0ustar zuulzuul00000000000000--- features: - Allow multiple UUID's in the 'introspection start' CLI command. ././@LongLink0000000000000000000000000000015600000000000011217 Lustar 00000000000000python-ironic-inspector-client-3.4.0/releasenotes/notes/drop-setting-ipmi-creds-feature-4965aaba75a40326.yamlpython-ironic-inspector-client-3.4.0/releasenotes/notes/drop-setting-ipmi-creds-feature-4965aaba75a40000666000175000017500000000012213364122067033304 0ustar zuulzuul00000000000000--- upgrade: - | Experimental setting IPMI credentials feature was removed. python-ironic-inspector-client-3.4.0/releasenotes/notes/api-1.6-a020f6ee5756a7ab.yaml0000666000175000017500000000006313364122067027677 0ustar zuulzuul00000000000000--- other: - Bumped supported API version to 1.6.python-ironic-inspector-client-3.4.0/releasenotes/notes/.placeholder0000666000175000017500000000000013364122067025722 0ustar zuulzuul00000000000000python-ironic-inspector-client-3.4.0/releasenotes/notes/api-1.5-d5c64e5265fe56d3.yaml0000666000175000017500000000006413364122067027636 0ustar zuulzuul00000000000000--- other: - Bumped supported API version to 1.5. ././@LongLink0000000000000000000000000000015200000000000011213 Lustar 00000000000000python-ironic-inspector-client-3.4.0/releasenotes/notes/list-introspection-statuses-4ad9e7e56823e754.yamlpython-ironic-inspector-client-3.4.0/releasenotes/notes/list-introspection-statuses-4ad9e7e56823e7540000666000175000017500000000023313364122067033271 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